diff options
author | Robert Speicher <rspeicher@gmail.com> | 2017-07-06 12:43:51 -0400 |
---|---|---|
committer | Robert Speicher <rspeicher@gmail.com> | 2017-07-06 12:43:51 -0400 |
commit | eef068754af7437baf327c5cb4e2b454ba40a617 (patch) | |
tree | 3583327140b2994432de317b4ac06d66b274b430 | |
parent | 9eeba8fb49c5da7cf0b2c22bc33cbd33a83918ed (diff) | |
parent | 9274c3c1598f3ff32339e681d5812feeb0f62605 (diff) | |
download | gitlab-ce-eef068754af7437baf327c5cb4e2b454ba40a617.tar.gz |
Merge branch 'master' into rs-sign_in
906 files changed, 11025 insertions, 5137 deletions
diff --git a/.eslintrc b/.eslintrc index 73cd7ecf66d..c72a5e0335b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -11,6 +11,7 @@ "gon": false, "localStorage": false }, + "parser": "babel-eslint", "plugins": [ "filenames", "import", diff --git a/.gitignore b/.gitignore index e529e33530a..0d6194dd1e5 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,4 @@ eslint-report.html /.gitlab_workhorse_secret /webpack-report/ /locale/**/LC_MESSAGES +/.rspec diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e52b656599c..a3ce1de50c2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -474,9 +474,8 @@ codeclimate: services: - docker:dind script: - - docker pull codeclimate/codeclimate - - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > codeclimate.json - - sed -i.bak 's/\({"body":"\)[^"]*\("}\)/\1\2/g' codeclimate.json + - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > raw_codeclimate.json + - cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json artifacts: paths: [codeclimate.json] diff --git a/.rspec b/.rspec deleted file mode 100644 index 35f4d7441e0..00000000000 --- a/.rspec +++ /dev/null @@ -1,2 +0,0 @@ ---color ---format Fuubar diff --git a/.rubocop.yml b/.rubocop.yml index 32ec60f540b..9785e7626f9 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -965,6 +965,10 @@ RSpec/AnyInstance: RSpec/BeEql: Enabled: true +# We don't enforce this as we use this technique in a few places. +RSpec/BeforeAfterAll: + Enabled: false + # Check that the first argument to the top level describe is the tested class or # module. RSpec/DescribeClass: @@ -1024,6 +1028,12 @@ RSpec/FilePath: RSpec/Focus: Enabled: true +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: is_expected, should +RSpec/ImplicitExpect: + Enabled: true + EnforcedStyle: is_expected + # Checks for the usage of instance variables. RSpec/InstanceVariable: Enabled: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 5ab4692dd60..2ec558e274f 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,10 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 54 -RSpec/BeforeAfterAll: - Enabled: false - # Offense count: 233 RSpec/EmptyLineAfterFinalLet: Enabled: false @@ -24,12 +20,6 @@ RSpec/EmptyLineAfterSubject: RSpec/HookArgument: Enabled: false -# Offense count: 12 -# Configuration parameters: EnforcedStyle, SupportedStyles. -# SupportedStyles: is_expected, should -RSpec/ImplicitExpect: - Enabled: false - # Offense count: 11 # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: it_behaves_like, it_should_behave_like diff --git a/CHANGELOG.md b/CHANGELOG.md index f372cbf91e8..4d2adb47a80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 9.3.5 (2017-07-05) + +- Remove "Remove from board" button from backlog and closed list. !12430 +- Do not delete protected branches when deleting all merged branches. !12624 +- Set default for Remove source branch to false. +- Prevent accidental deletion of protected MR source branch by repeating checks before actual deletion. +- Expires full_path cache after a repository is renamed/transferred. + +## 9.3.4 (2017-07-03) + +- No changes. + +## 9.3.3 (2017-06-30) + +- Fix head pipeline stored in merge request for external pipelines. !12478 +- Bring back branches badge to main project page. !12548 +- Fix diff of requirements.txt file by not matching newlines as part of package names. +- Perform housekeeping only when an import of a fresh project is completed. +- Fixed issue boards closed list not showing all closed issues. +- Fixed multi-line markdown tooltip buttons in issue edit form. + ## 9.3.2 (2017-06-27) - API: Fix optional arugments for POST :id/variables. !12474 diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 54d1a4f2a4a..a803cc227fe 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.13.0 +0.14.0 diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index c20c645d7e4..ac14c3dfaa8 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -5.0.6 +5.1.1 @@ -255,7 +255,7 @@ gem 'net-ssh', '~> 3.0.1' gem 'base32', '~> 0.3.0' # Sentry integration -gem 'sentry-raven', '~> 2.4.0' +gem 'sentry-raven', '~> 2.5.3' gem 'premailer-rails', '~> 1.9.7' @@ -285,6 +285,7 @@ group :metrics do # Prometheus gem 'prometheus-client-mmap', '~>0.7.0.beta5' + gem 'raindrops', '~> 0.18' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index f4ddd30da1b..70abc0669df 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -599,8 +599,8 @@ GEM premailer-rails (1.9.7) actionmailer (>= 3, < 6) premailer (~> 1.7, >= 1.7.9) - prometheus-client-mmap (0.7.0.beta5) - mmap2 (~> 2.2.6) + prometheus-client-mmap (0.7.0.beta8) + mmap2 (~> 2.2, >= 2.2.7) pry (0.10.4) coderay (~> 1.1.0) method_source (~> 0.8.1) @@ -658,7 +658,7 @@ GEM thor (>= 0.18.1, < 2.0) rainbow (2.2.2) rake - raindrops (0.17.0) + raindrops (0.18.0) rake (10.5.0) rblineprof (0.3.6) debugger-ruby_core_source (~> 1.3) @@ -775,7 +775,7 @@ GEM activesupport (>= 3.1) select2-rails (3.5.9.3) thor (~> 0.14) - sentry-raven (2.4.0) + sentry-raven (2.5.3) faraday (>= 0.7.6, < 1.0) settingslogic (2.0.9) sexp_processor (4.9.0) @@ -1062,6 +1062,7 @@ DEPENDENCIES rails-deprecated_sanitizer (~> 1.0.3) rails-i18n (~> 4.0.9) rainbow (~> 2.2) + raindrops (~> 0.18) rblineprof (~> 0.3.6) rdoc (~> 4.2) recaptcha (~> 3.0) @@ -1089,7 +1090,7 @@ DEPENDENCIES scss_lint (~> 0.47.0) seed-fu (~> 2.3.5) select2-rails (~> 3.5.9) - sentry-raven (~> 2.4.0) + sentry-raven (~> 2.5.3) settingslogic (~> 2.0.9) sham_rack (~> 1.3.6) shoulda-matchers (~> 2.8.0) diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index c34d80f0601..18cd04b176a 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -2,7 +2,6 @@ /* global Flash */ import Cookies from 'js-cookie'; -import * as Emoji from './emoji'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd'; @@ -24,27 +23,9 @@ const categoryLabelMap = { flags: 'Flags', }; -function renderCategory(name, emojiList, opts = {}) { - return ` - <h5 class="emoji-menu-title"> - ${name} - </h5> - <ul class="clearfix emoji-menu-list ${opts.menuListClass || ''}"> - ${emojiList.map(emojiName => ` - <li class="emoji-menu-list-item"> - <button class="emoji-menu-btn text-center js-emoji-btn" type="button"> - ${Emoji.glEmojiTag(emojiName, { - sprite: true, - })} - </button> - </li> - `).join('\n')} - </ul> - `; -} - -export default class AwardsHandler { - constructor() { +class AwardsHandler { + constructor(emoji) { + this.emoji = emoji; this.eventListeners = []; // If the user shows intent let's pre-build the menu this.registerEventListener('one', $(document), 'mouseenter focus', '.js-add-award', 'mouseenter focus', () => { @@ -78,10 +59,10 @@ export default class AwardsHandler { const $target = $(e.currentTarget); const $glEmojiElement = $target.find('gl-emoji'); const $spriteIconElement = $target.find('.icon'); - const emoji = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name'); + const emojiName = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name'); $target.closest('.js-awards-block').addClass('current'); - this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji); + this.addAward(this.getVotesBlock(), this.getAwardUrl(), emojiName); }); } @@ -139,16 +120,16 @@ export default class AwardsHandler { this.isCreatingEmojiMenu = true; // Render the first category - const categoryMap = Emoji.getEmojiCategoryMap(); + const categoryMap = this.emoji.getEmojiCategoryMap(); const categoryNameKey = Object.keys(categoryMap)[0]; const emojisInCategory = categoryMap[categoryNameKey]; - const firstCategory = renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory); + const firstCategory = this.renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory); // Render the frequently used const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis(); let frequentlyUsedCatgegory = ''; if (frequentlyUsedEmojis.length > 0) { - frequentlyUsedCatgegory = renderCategory('Frequently used', frequentlyUsedEmojis, { + frequentlyUsedCatgegory = this.renderCategory('Frequently used', frequentlyUsedEmojis, { menuListClass: 'frequent-emojis', }); } @@ -179,7 +160,7 @@ export default class AwardsHandler { } this.isAddingRemainingEmojiMenuCategories = true; - const categoryMap = Emoji.getEmojiCategoryMap(); + const categoryMap = this.emoji.getEmojiCategoryMap(); // Avoid the jank and render the remaining categories separately // This will take more time, but makes UI more responsive @@ -191,7 +172,7 @@ export default class AwardsHandler { promiseChain.then(() => new Promise((resolve) => { const emojisInCategory = categoryMap[categoryNameKey]; - const categoryMarkup = renderCategory( + const categoryMarkup = this.renderCategory( categoryLabelMap[categoryNameKey], emojisInCategory, ); @@ -216,6 +197,25 @@ export default class AwardsHandler { }); } + renderCategory(name, emojiList, opts = {}) { + return ` + <h5 class="emoji-menu-title"> + ${name} + </h5> + <ul class="clearfix emoji-menu-list ${opts.menuListClass || ''}"> + ${emojiList.map(emojiName => ` + <li class="emoji-menu-list-item"> + <button class="emoji-menu-btn text-center js-emoji-btn" type="button"> + ${this.emoji.glEmojiTag(emojiName, { + sprite: true, + })} + </button> + </li> + `).join('\n')} + </ul> + `; + } + positionMenu($menu, $addBtn) { const position = $addBtn.data('position'); // The menu could potentially be off-screen or in a hidden overflow element @@ -234,7 +234,7 @@ export default class AwardsHandler { } addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) { - const normalizedEmoji = Emoji.normalizeEmojiName(emoji); + const normalizedEmoji = this.emoji.normalizeEmojiName(emoji); const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent(); this.postEmoji($emojiButton, awardUrl, normalizedEmoji, () => { this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality); @@ -249,7 +249,7 @@ export default class AwardsHandler { this.checkMutuality(votesBlock, emoji); } this.addEmojiToFrequentlyUsedList(emoji); - const normalizedEmoji = Emoji.normalizeEmojiName(emoji); + const normalizedEmoji = this.emoji.normalizeEmojiName(emoji); const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent(); if ($emojiButton.length > 0) { if (this.isActive($emojiButton)) { @@ -374,7 +374,7 @@ export default class AwardsHandler { createAwardButtonForVotesBlock(votesBlock, emojiName) { const buttonHtml = ` <button class="btn award-control js-emoji-btn has-tooltip active" title="You" data-placement="bottom"> - ${Emoji.glEmojiTag(emojiName)} + ${this.emoji.glEmojiTag(emojiName)} <span class="award-control-text js-counter">1</span> </button> `; @@ -440,7 +440,7 @@ export default class AwardsHandler { } addEmojiToFrequentlyUsedList(emoji) { - if (Emoji.isEmojiNameValid(emoji)) { + if (this.emoji.isEmojiNameValid(emoji)) { this.frequentlyUsedEmojis = _.uniq(this.getFrequentlyUsedEmojis().concat(emoji)); Cookies.set('frequently_used_emojis', this.frequentlyUsedEmojis.join(','), { expires: 365 }); } @@ -450,7 +450,7 @@ export default class AwardsHandler { return this.frequentlyUsedEmojis || (() => { const frequentlyUsedEmojis = _.uniq((Cookies.get('frequently_used_emojis') || '').split(',')); this.frequentlyUsedEmojis = frequentlyUsedEmojis.filter( - inputName => Emoji.isEmojiNameValid(inputName), + inputName => this.emoji.isEmojiNameValid(inputName), ); return this.frequentlyUsedEmojis; @@ -493,7 +493,7 @@ export default class AwardsHandler { } findMatchingEmojiElements(query) { - const emojiMatches = Emoji.filterEmojiNamesByAlias(query); + const emojiMatches = this.emoji.filterEmojiNamesByAlias(query); const $emojiElements = $('.emoji-menu-list:not(.frequent-emojis) [data-name]'); const $matchingElements = $emojiElements .filter((i, elm) => emojiMatches.indexOf(elm.dataset.name) >= 0); @@ -507,3 +507,12 @@ export default class AwardsHandler { $('.emoji-menu').remove(); } } + +let awardsHandlerPromise = null; +export default function loadAwardsHandler(reload = false) { + if (!awardsHandlerPromise || reload) { + awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji') + .then(Emoji => new AwardsHandler(Emoji)); + } + return awardsHandlerPromise; +} diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js index 3bea460dcc6..e00af4b2fa8 100644 --- a/app/assets/javascripts/behaviors/autosize.js +++ b/app/assets/javascripts/behaviors/autosize.js @@ -1,23 +1,8 @@ import autosize from 'vendor/autosize'; -$(() => { - const $fields = $('.js-autosize'); +document.addEventListener('DOMContentLoaded', () => { + const autosizeEls = document.querySelectorAll('.js-autosize'); - $fields.on('autosize:resized', function resized() { - const $field = $(this); - $field.data('height', $field.outerHeight()); - }); - - $fields.on('resize.autosize', function resize() { - const $field = $(this); - if ($field.data('height') !== $field.outerHeight()) { - $field.data('height', $field.outerHeight()); - autosize.destroy($field); - $field.css('max-height', window.outerHeight); - } - }); - - autosize($fields); - autosize.update($fields); - $fields.css('resize', 'vertical'); + autosize(autosizeEls); + autosize.update(autosizeEls); }); diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js index 8156e491a42..7e98e04303a 100644 --- a/app/assets/javascripts/behaviors/gl_emoji.js +++ b/app/assets/javascripts/behaviors/gl_emoji.js @@ -1,5 +1,4 @@ import installCustomElements from 'document-register-element'; -import { emojiImageTag, emojiFallbackImageSrc } from '../emoji'; import isEmojiUnicodeSupported from '../emoji/support'; installCustomElements(window); @@ -32,11 +31,19 @@ export default function installGlEmojiElement() { // IE 11 doesn't like adding multiple at once :( this.classList.add('emoji-icon'); this.classList.add(fallbackSpriteClass); - } else if (hasImageFallback) { - this.innerHTML = emojiImageTag(name, fallbackSrc); } else { - const src = emojiFallbackImageSrc(name); - this.innerHTML = emojiImageTag(name, src); + import(/* webpackChunkName: 'emoji' */ '../emoji') + .then(({ emojiImageTag, emojiFallbackImageSrc }) => { + if (hasImageFallback) { + this.innerHTML = emojiImageTag(name, fallbackSrc); + } else { + const src = emojiFallbackImageSrc(name); + this.innerHTML = emojiImageTag(name, src); + } + }) + .catch(() => { + // do nothing + }); } } }; diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index c7afd4ead6b..590b7be36e3 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -34,7 +34,10 @@ gl.issueBoards.BoardSidebar = Vue.extend({ }, milestoneTitle() { return this.issue.milestone ? this.issue.milestone.title : 'No Milestone'; - } + }, + canRemove() { + return !this.list.preset; + }, }, watch: { detail: { diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js index 5597f128b80..6a900d4abd0 100644 --- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js +++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js @@ -46,8 +46,7 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({ }, template: ` <div - class="block list" - v-if="list.type !== 'closed'"> + class="block list"> <button class="btn btn-default btn-block" type="button" diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 9974e135022..1dfa064acfd 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -13,25 +13,21 @@ window.Build = (function () { this.options = options || $('.js-build-options').data(); this.pageUrl = this.options.pageUrl; - this.buildUrl = this.options.buildUrl; this.buildStatus = this.options.buildStatus; this.state = this.options.logState; this.buildStage = this.options.buildStage; this.$document = $(document); this.logBytes = 0; - this.scrollOffsetPadding = 30; this.hasBeenScrolled = false; this.updateDropdown = this.updateDropdown.bind(this); this.getBuildTrace = this.getBuildTrace.bind(this); - this.scrollToBottom = this.scrollToBottom.bind(this); - this.$body = $('body'); this.$buildTrace = $('#build-trace'); this.$buildRefreshAnimation = $('.js-build-refresh'); this.$truncatedInfo = $('.js-truncated-info'); this.$buildTraceOutput = $('.js-build-output'); - this.$scrollContainer = $('.js-scroll-container'); + this.$topBar = $('.js-top-bar'); // Scroll controllers this.$scrollTopBtn = $('.js-scroll-up'); @@ -63,13 +59,22 @@ window.Build = (function () { .off('click') .on('click', this.scrollToBottom.bind(this)); - const scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100); + this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100); - this.$scrollContainer + $(window) .off('scroll') .on('scroll', () => { - this.hasBeenScrolled = true; - scrollThrottled(); + const contentHeight = this.$buildTraceOutput.prop('scrollHeight'); + if (contentHeight > this.windowSize) { + // means the user did not scroll, the content was updated. + this.windowSize = contentHeight; + } else { + // User scrolled + this.hasBeenScrolled = true; + this.toggleScrollAnimation(false); + } + + this.scrollThrottled(); }); $(window) @@ -77,60 +82,73 @@ window.Build = (function () { .on('resize.build', _.throttle(this.sidebarOnResize.bind(this), 100)); this.updateArtifactRemoveDate(); + this.initAffixTopArea(); - // eslint-disable-next-line - this.getBuildTrace() - .then(() => this.toggleScroll()) - .then(() => { - if (!this.hasBeenScrolled) { - this.scrollToBottom(); - } - }); - - this.verifyTopPosition(); + this.getBuildTrace(); } + Build.prototype.initAffixTopArea = function () { + /** + If the browser does not support position sticky, it returns the position as static. + If the browser does support sticky, then we allow the browser to handle it, if not + then we default back to Bootstraps affix + **/ + if (this.$topBar.css('position') !== 'static') return; + + const offsetTop = this.$buildTrace.offset().top; + + this.$topBar.affix({ + offset: { + top: offsetTop, + }, + }); + }; + Build.prototype.canScroll = function () { - return (this.$scrollContainer.prop('scrollHeight') - this.scrollOffsetPadding) > this.$scrollContainer.height(); + return document.body.scrollHeight > window.innerHeight; }; - /** - * | | Up | Down | - * |--------------------------|----------|----------| - * | on scroll bottom | active | disabled | - * | on scroll top | disabled | active | - * | no scroll | disabled | disabled | - * | on.('scroll') is on top | disabled | active | - * | on('scroll) is on bottom | active | disabled | - * - */ Build.prototype.toggleScroll = function () { - const currentPosition = this.$scrollContainer.scrollTop(); - const bottomScroll = currentPosition + this.$scrollContainer.innerHeight(); + const currentPosition = document.body.scrollTop; + const windowHeight = window.innerHeight; if (this.canScroll()) { - if (currentPosition === 0) { + if (currentPosition > 0 && + (document.body.scrollHeight - currentPosition !== windowHeight)) { + // User is in the middle of the log + + this.toggleDisableButton(this.$scrollTopBtn, false); + this.toggleDisableButton(this.$scrollBottomBtn, false); + } else if (currentPosition === 0) { + // User is at Top of Build Log + this.toggleDisableButton(this.$scrollTopBtn, true); this.toggleDisableButton(this.$scrollBottomBtn, false); - } else if (bottomScroll === this.$scrollContainer.prop('scrollHeight')) { + } else if (document.body.scrollHeight - currentPosition === windowHeight) { + // User is at the bottom of the build log. + this.toggleDisableButton(this.$scrollTopBtn, false); this.toggleDisableButton(this.$scrollBottomBtn, true); - } else { - this.toggleDisableButton(this.$scrollTopBtn, false); - this.toggleDisableButton(this.$scrollBottomBtn, false); } + } else { + this.toggleDisableButton(this.$scrollTopBtn, true); + this.toggleDisableButton(this.$scrollBottomBtn, true); } }; - Build.prototype.scrollToTop = function () { + Build.prototype.scrollDown = function () { + document.body.scrollTop = document.body.scrollHeight; + }; + + Build.prototype.scrollToBottom = function () { + this.scrollDown(); this.hasBeenScrolled = true; - this.$scrollContainer.scrollTop(0); this.toggleScroll(); }; - Build.prototype.scrollToBottom = function () { + Build.prototype.scrollToTop = function () { + document.body.scrollTop = 0; this.hasBeenScrolled = true; - this.$scrollContainer.scrollTop(this.$scrollContainer.prop('scrollHeight')); this.toggleScroll(); }; @@ -143,47 +161,6 @@ window.Build = (function () { this.$scrollBottomBtn.toggleClass('animate', toggle); }; - /** - * Build trace top position depends on the space ocupied by the elments rendered before - */ - Build.prototype.verifyTopPosition = function () { - const $buildPage = $('.build-page'); - - const $flashError = $('.alert-wrapper'); - const $header = $('.build-header', $buildPage); - const $runnersStuck = $('.js-build-stuck', $buildPage); - const $startsEnvironment = $('.js-environment-container', $buildPage); - const $erased = $('.js-build-erased', $buildPage); - const prependTopDefault = 20; - - // header + navigation + margin - let topPostion = 168; - - if ($header.length) { - topPostion += $header.outerHeight(); - } - - if ($runnersStuck.length) { - topPostion += $runnersStuck.outerHeight(); - } - - if ($startsEnvironment.length) { - topPostion += $startsEnvironment.outerHeight() + prependTopDefault; - } - - if ($erased.length) { - topPostion += $erased.outerHeight() + prependTopDefault; - } - - if ($flashError.length) { - topPostion += $flashError.outerHeight(); - } - - this.$buildTrace.css({ - top: topPostion, - }); - }; - Build.prototype.initSidebar = function () { this.$sidebar = $('.js-build-sidebar'); this.$sidebar.niceScroll(); @@ -201,6 +178,8 @@ window.Build = (function () { this.state = log.state; } + this.windowSize = this.$buildTraceOutput.prop('scrollHeight'); + if (log.append) { this.$buildTraceOutput.append(log.html); this.logBytes += log.size; @@ -228,13 +207,7 @@ window.Build = (function () { } Build.timeout = setTimeout(() => { - //eslint-disable-next-line - this.getBuildTrace() - .then(() => { - if (!this.hasBeenScrolled) { - this.scrollToBottom(); - } - }); + this.getBuildTrace(); }, 4000); } else { this.$buildRefreshAnimation.remove(); @@ -247,7 +220,13 @@ window.Build = (function () { }) .fail(() => { this.$buildRefreshAnimation.remove(); - }); + }) + .then(() => { + if (!this.hasBeenScrolled) { + this.scrollDown(); + } + }) + .then(() => this.toggleScroll()); }; Build.prototype.shouldHideSidebarForViewport = function () { @@ -259,14 +238,11 @@ window.Build = (function () { const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined; const $toggleButton = $('.js-sidebar-build-toggle-header'); - this.$buildTrace - .toggleClass('sidebar-expanded', shouldShow) - .toggleClass('sidebar-collapsed', shouldHide); this.$sidebar .toggleClass('right-sidebar-expanded', shouldShow) .toggleClass('right-sidebar-collapsed', shouldHide); - $('.js-build-page') + this.$topBar .toggleClass('sidebar-expanded', shouldShow) .toggleClass('sidebar-collapsed', shouldHide); @@ -279,17 +255,10 @@ window.Build = (function () { Build.prototype.sidebarOnResize = function () { this.toggleSidebar(this.shouldHideSidebarForViewport()); - - this.verifyTopPosition(); - - if (this.canScroll()) { - this.toggleScroll(); - } }; Build.prototype.sidebarOnClick = function () { if (this.shouldHideSidebarForViewport()) this.toggleSidebar(); - this.verifyTopPosition(); }; Build.prototype.updateArtifactRemoveDate = function () { diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index 725ec7b9c70..1be9df19c81 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -1,6 +1,7 @@ /* eslint-disable class-methods-use-this */ import './lib/utils/url_utility'; +import FilesCommentButton from './files_comment_button'; const UNFOLD_COUNT = 20; let isBound = false; @@ -8,8 +9,10 @@ let isBound = false; class Diff { constructor() { const $diffFile = $('.files .diff-file'); + $diffFile.singleFileDiff(); - $diffFile.filesCommentButton(); + + FilesCommentButton.init($diffFile); $diffFile.each((index, file) => new gl.ImageFile(file)); diff --git a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js index 517bdb6be09..c37249c060a 100644 --- a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js +++ b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js @@ -139,9 +139,9 @@ const DiffNoteAvatars = Vue.extend({ const notesCount = this.notesCount; $(this.$el).closest('.js-avatar-container') - .toggleClass('js-no-comment-btn', notesCount > 0) + .toggleClass('no-comment-btn', notesCount > 0) .nextUntil('.js-avatar-container') - .toggleClass('js-no-comment-btn', notesCount > 0); + .toggleClass('no-comment-btn', notesCount > 0); }, toggleDiscussionsToggleState() { const $notesHolders = $(this.$el).closest('.code').find('.notes_holder'); diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 4247540de22..e924fde60bf 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -56,6 +56,7 @@ import GfmAutoComplete from './gfm_auto_complete'; import ShortcutsBlob from './shortcuts_blob'; import initSettingsPanels from './settings_panels'; import initExperimentalFlags from './experimental_flags'; +import OAuthRememberMe from './oauth_remember_me'; (function() { var Dispatcher; @@ -127,6 +128,7 @@ import initExperimentalFlags from './experimental_flags'; case 'sessions:new': new UsernameValidator(); new ActiveTabMemoizer(); + new OAuthRememberMe({ container: $(".omniauth-container") }).bindEvents(); break; case 'projects:boards:show': case 'projects:boards:index': diff --git a/app/assets/javascripts/environments/components/environment.vue b/app/assets/javascripts/environments/components/environment.vue index 8120ef182d4..91ed8c8467f 100644 --- a/app/assets/javascripts/environments/components/environment.vue +++ b/app/assets/javascripts/environments/components/environment.vue @@ -32,7 +32,6 @@ export default { state: store.state, visibility: 'available', isLoading: false, - isLoadingFolderContent: false, cssContainerClass: environmentsData.cssClass, endpoint: environmentsData.environmentsDataEndpoint, canCreateDeployment: environmentsData.canCreateDeployment, @@ -86,9 +85,6 @@ export default { errorCallback: this.errorCallback, notificationCallback: (isMakingRequest) => { this.isMakingRequest = isMakingRequest; - - // We need to verify if any folder is open to also fecth it - this.openFolders = this.store.getOpenFolders(); }, }); @@ -119,7 +115,7 @@ export default { this.store.toggleFolder(folder); if (!folder.isOpen) { - this.fetchChildEnvironments(folder, folderUrl); + this.fetchChildEnvironments(folder, folderUrl, true); } }, @@ -147,19 +143,17 @@ export default { .catch(this.errorCallback); }, - fetchChildEnvironments(folder, folderUrl) { - this.isLoadingFolderContent = true; + fetchChildEnvironments(folder, folderUrl, showLoader = false) { + this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader); this.service.getFolderContent(folderUrl) .then(resp => resp.json()) - .then((response) => { - this.store.setfolderContent(folder, response.environments); - this.isLoadingFolderContent = false; - }) + .then(response => this.store.setfolderContent(folder, response.environments)) + .then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false)) .catch(() => { - this.isLoadingFolderContent = false; // eslint-disable-next-line no-new new Flash('An error occurred while fetching the environments.'); + this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false); }); }, @@ -176,13 +170,13 @@ export default { successCallback(resp) { this.saveData(resp); - // If folders are open while polling we need to open them again - if (this.openFolders.length) { - this.openFolders.map((folder) => { + // We need to verify if any folder is open to also update it + const openFolders = this.store.getOpenFolders(); + if (openFolders.length) { + openFolders.forEach((folder) => { // TODO - Move this to the backend const folderUrl = `${window.location.pathname}/folders/${folder.folderName}`; - this.store.updateFolder(folder, 'isOpen', true); return this.fetchChildEnvironments(folder, folderUrl); }); } @@ -267,7 +261,7 @@ export default { :environments="state.environments" :can-create-deployment="canCreateDeploymentParsed" :can-read-environment="canReadEnvironmentParsed" - :is-loading-folder-content="isLoadingFolderContent" /> + /> </div> <table-pagination diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue index b1fd9db650b..175cc8f1f72 100644 --- a/app/assets/javascripts/environments/components/environments_table.vue +++ b/app/assets/javascripts/environments/components/environments_table.vue @@ -29,12 +29,6 @@ export default { required: false, default: false, }, - - isLoadingFolderContent: { - type: Boolean, - required: false, - default: false, - }, }, methods: { @@ -74,7 +68,7 @@ export default { /> <template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0"> - <div v-if="isLoadingFolderContent"> + <div v-if="model.isLoadingFolderContent"> <loading-icon size="2" /> </div> diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js index a5773dd7e4f..038c149be2d 100644 --- a/app/assets/javascripts/environments/stores/environments_store.js +++ b/app/assets/javascripts/environments/stores/environments_store.js @@ -35,14 +35,18 @@ export default class EnvironmentsStore { */ storeEnvironments(environments = []) { const filteredEnvironments = environments.map((env) => { + const oldEnvironmentState = this.state.environments + .find(element => element.id === env.latest.id) || {}; + let filtered = {}; if (env.size > 1) { filtered = Object.assign({}, env, { isFolder: true, + isLoadingFolderContent: oldEnvironmentState.isLoading || false, folderName: env.name, - isOpen: false, - children: [], + isOpen: oldEnvironmentState.isOpen || false, + children: oldEnvironmentState.children || [], }); } @@ -98,7 +102,7 @@ export default class EnvironmentsStore { * @return {Array} */ toggleFolder(folder) { - return this.updateFolder(folder, 'isOpen', !folder.isOpen); + return this.updateEnvironmentProp(folder, 'isOpen', !folder.isOpen); } /** @@ -125,23 +129,23 @@ export default class EnvironmentsStore { return updated; }); - return this.updateFolder(folder, 'children', updatedEnvironments); + return this.updateEnvironmentProp(folder, 'children', updatedEnvironments); } /** - * Given a folder a prop and a new value updates the correct folder. + * Given a environment, a prop and a new value updates the correct environment. * - * @param {Object} folder + * @param {Object} environment * @param {String} prop * @param {String|Boolean|Object|Array} newValue * @return {Array} */ - updateFolder(folder, prop, newValue) { + updateEnvironmentProp(environment, prop, newValue) { const environments = this.state.environments; const updatedEnvironments = environments.map((env) => { const updateEnv = Object.assign({}, env); - if (env.isFolder && env.id === folder.id) { + if (env.id === environment.id) { updateEnv[prop] = newValue; } @@ -149,8 +153,6 @@ export default class EnvironmentsStore { }); this.state.environments = updatedEnvironments; - - return updatedEnvironments; } getOpenFolders() { diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js index 534e651b030..d02e4cd5876 100644 --- a/app/assets/javascripts/files_comment_button.js +++ b/app/assets/javascripts/files_comment_button.js @@ -1,150 +1,73 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len, one-var, one-var-declaration-per-line, quotes, prefer-template, newline-per-chained-call, comma-dangle, new-cap, no-else-return, consistent-return */ -/* global FilesCommentButton */ /* global notes */ -let $commentButtonTemplate; - -window.FilesCommentButton = (function() { - var COMMENT_BUTTON_CLASS, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS; - - COMMENT_BUTTON_CLASS = '.add-diff-note'; - - LINE_HOLDER_CLASS = '.line_holder'; - - LINE_NUMBER_CLASS = 'diff-line-num'; - - LINE_CONTENT_CLASS = 'line_content'; - - UNFOLDABLE_LINE_CLASS = 'js-unfold'; - - EMPTY_CELL_CLASS = 'empty-cell'; - - OLD_LINE_CLASS = 'old_line'; - - LINE_COLUMN_CLASSES = "." + LINE_NUMBER_CLASS + ", .line_content"; - - TEXT_FILE_SELECTOR = '.text-file'; - - function FilesCommentButton(filesContainerElement) { - this.render = this.render.bind(this); - this.hideButton = this.hideButton.bind(this); - this.isParallelView = notes.isParallelView(); - filesContainerElement.on('mouseover', LINE_COLUMN_CLASSES, this.render) - .on('mouseleave', LINE_COLUMN_CLASSES, this.hideButton); - } - - FilesCommentButton.prototype.render = function(e) { - var $currentTarget, buttonParentElement, lineContentElement, textFileElement, $button; - $currentTarget = $(e.currentTarget); - - if ($currentTarget.hasClass('js-no-comment-btn')) return; - - lineContentElement = this.getLineContent($currentTarget); - buttonParentElement = this.getButtonParent($currentTarget); - - if (!this.validateButtonParent(buttonParentElement) || !this.validateLineContent(lineContentElement)) return; - - $button = $(COMMENT_BUTTON_CLASS, buttonParentElement); - buttonParentElement.addClass('is-over') - .nextUntil(`.${LINE_CONTENT_CLASS}`).addClass('is-over'); - - if ($button.length) { - return; +/* Developer beware! Do not add logic to showButton or hideButton + * that will force a reflow. Doing so will create a signficant performance + * bottleneck for pages with large diffs. For a comprehensive list of what + * causes reflows, visit https://gist.github.com/paulirish/5d52fb081b3570c81e3a + */ + +const LINE_NUMBER_CLASS = 'diff-line-num'; +const UNFOLDABLE_LINE_CLASS = 'js-unfold'; +const NO_COMMENT_CLASS = 'no-comment-btn'; +const EMPTY_CELL_CLASS = 'empty-cell'; +const OLD_LINE_CLASS = 'old_line'; +const LINE_COLUMN_CLASSES = `.${LINE_NUMBER_CLASS}, .line_content`; +const DIFF_CONTAINER_SELECTOR = '.files'; +const DIFF_EXPANDED_CLASS = 'diff-expanded'; + +export default { + init($diffFile) { + /* Caching is used only when the following members are *true*. This is because there are likely to be + * differently configured versions of diffs in the same session. However if these values are true, they + * will be true in all cases */ + + if (!this.userCanCreateNote) { + // data-can-create-note is an empty string when true, otherwise undefined + this.userCanCreateNote = $diffFile.closest(DIFF_CONTAINER_SELECTOR).data('can-create-note') === ''; } - textFileElement = this.getTextFileElement($currentTarget); - buttonParentElement.append(this.buildButton({ - discussionID: lineContentElement.attr('data-discussion-id'), - lineType: lineContentElement.attr('data-line-type'), - - noteableType: textFileElement.attr('data-noteable-type'), - noteableID: textFileElement.attr('data-noteable-id'), - commitID: textFileElement.attr('data-commit-id'), - noteType: lineContentElement.attr('data-note-type'), - - // LegacyDiffNote - lineCode: lineContentElement.attr('data-line-code'), - - // DiffNote - position: lineContentElement.attr('data-position') - })); - }; - - FilesCommentButton.prototype.hideButton = function(e) { - var $currentTarget = $(e.currentTarget); - var buttonParentElement = this.getButtonParent($currentTarget); - - buttonParentElement.removeClass('is-over') - .nextUntil(`.${LINE_CONTENT_CLASS}`).removeClass('is-over'); - }; - - FilesCommentButton.prototype.buildButton = function(buttonAttributes) { - return $commentButtonTemplate.clone().attr({ - 'data-discussion-id': buttonAttributes.discussionID, - 'data-line-type': buttonAttributes.lineType, - - 'data-noteable-type': buttonAttributes.noteableType, - 'data-noteable-id': buttonAttributes.noteableID, - 'data-commit-id': buttonAttributes.commitID, - 'data-note-type': buttonAttributes.noteType, - - // LegacyDiffNote - 'data-line-code': buttonAttributes.lineCode, - - // DiffNote - 'data-position': buttonAttributes.position - }); - }; - - FilesCommentButton.prototype.getTextFileElement = function(hoveredElement) { - return hoveredElement.closest(TEXT_FILE_SELECTOR); - }; - - FilesCommentButton.prototype.getLineContent = function(hoveredElement) { - if (hoveredElement.hasClass(LINE_CONTENT_CLASS)) { - return hoveredElement; - } - if (!this.isParallelView) { - return $(hoveredElement).closest(LINE_HOLDER_CLASS).find("." + LINE_CONTENT_CLASS); - } else { - return $(hoveredElement).next("." + LINE_CONTENT_CLASS); + if (typeof notes !== 'undefined' && !this.isParallelView) { + this.isParallelView = notes.isParallelView && notes.isParallelView(); } - }; - FilesCommentButton.prototype.getButtonParent = function(hoveredElement) { - if (!this.isParallelView) { - if (hoveredElement.hasClass(OLD_LINE_CLASS)) { - return hoveredElement; - } - return hoveredElement.parent().find("." + OLD_LINE_CLASS); - } else { - if (hoveredElement.hasClass(LINE_NUMBER_CLASS)) { - return hoveredElement; - } - return $(hoveredElement).prev("." + LINE_NUMBER_CLASS); + if (this.userCanCreateNote) { + $diffFile.on('mouseover', LINE_COLUMN_CLASSES, e => this.showButton(this.isParallelView, e)) + .on('mouseleave', LINE_COLUMN_CLASSES, e => this.hideButton(this.isParallelView, e)); } - }; + }, - FilesCommentButton.prototype.validateButtonParent = function(buttonParentElement) { - return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS); - }; + showButton(isParallelView, e) { + const buttonParentElement = this.getButtonParent(e.currentTarget, isParallelView); - FilesCommentButton.prototype.validateLineContent = function(lineContentElement) { - return lineContentElement.attr('data-note-type') && lineContentElement.attr('data-note-type') !== ''; - }; + if (!this.validateButtonParent(buttonParentElement)) return; - return FilesCommentButton; -})(); + buttonParentElement.classList.add('is-over'); + buttonParentElement.nextElementSibling.classList.add('is-over'); + }, -$.fn.filesCommentButton = function() { - $commentButtonTemplate = $('<button name="button" type="submit" class="add-diff-note js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>'); + hideButton(isParallelView, e) { + const buttonParentElement = this.getButtonParent(e.currentTarget, isParallelView); - if (!(this && (this.parent().data('can-create-note') != null))) { - return; - } - return this.each(function() { - if (!$.data(this, 'filesCommentButton')) { - return $.data(this, 'filesCommentButton', new FilesCommentButton($(this))); + buttonParentElement.classList.remove('is-over'); + buttonParentElement.nextElementSibling.classList.remove('is-over'); + }, + + getButtonParent(hoveredElement, isParallelView) { + if (isParallelView) { + if (!hoveredElement.classList.contains(LINE_NUMBER_CLASS)) { + return hoveredElement.previousElementSibling; + } + } else if (!hoveredElement.classList.contains(OLD_LINE_CLASS)) { + return hoveredElement.parentNode.querySelector(`.${OLD_LINE_CLASS}`); } - }); + return hoveredElement; + }, + + validateButtonParent(buttonParentElement) { + return !buttonParentElement.classList.contains(EMPTY_CELL_CLASS) && + !buttonParentElement.classList.contains(UNFOLDABLE_LINE_CLASS) && + !buttonParentElement.classList.contains(NO_COMMENT_CLASS) && + !buttonParentElement.parentNode.classList.contains(DIFF_EXPANDED_CLASS); + }, }; diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 10a64f9032b..2c56b718212 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -1,4 +1,3 @@ -import { validEmojiNames, glEmojiTag } from './emoji'; import glRegexp from './lib/utils/regexp'; import AjaxCache from './lib/utils/ajax_cache'; @@ -373,7 +372,12 @@ class GfmAutoComplete { if (this.cachedData[at]) { this.loadData($input, at, this.cachedData[at]); } else if (GfmAutoComplete.atTypeMap[at] === 'emojis') { - this.loadData($input, at, validEmojiNames); + import(/* webpackChunkName: 'emoji' */ './emoji') + .then(({ validEmojiNames, glEmojiTag }) => { + this.loadData($input, at, validEmojiNames); + GfmAutoComplete.glEmojiTag = glEmojiTag; + }) + .catch(() => { this.isLoadingData[at] = false; }); } else { AjaxCache.retrieve(this.dataSources[GfmAutoComplete.atTypeMap[at]], true) .then((data) => { @@ -428,12 +432,14 @@ GfmAutoComplete.atTypeMap = { }; // Emoji +GfmAutoComplete.glEmojiTag = null; GfmAutoComplete.Emoji = { templateFunction(name) { - return `<li> - ${name} ${glEmojiTag(name)} - </li> - `; + // glEmojiTag helper is loaded on-demand in fetchData() + if (GfmAutoComplete.glEmojiTag) { + return `<li>${name} ${GfmAutoComplete.glEmojiTag(name)}</li>`; + } + return `<li>${name}</li>`; }, }; // Team Members diff --git a/app/assets/javascripts/group_name.js b/app/assets/javascripts/group_name.js index 462d792b8d5..37c6765d942 100644 --- a/app/assets/javascripts/group_name.js +++ b/app/assets/javascripts/group_name.js @@ -1,13 +1,13 @@ - +import Cookies from 'js-cookie'; import _ from 'underscore'; export default class GroupName { constructor() { - this.titleContainer = document.querySelector('.title-container'); - this.title = document.querySelector('.title'); + this.titleContainer = document.querySelector('.js-title-container'); + this.title = this.titleContainer.querySelector('.title'); this.titleWidth = this.title.offsetWidth; - this.groupTitle = document.querySelector('.group-title'); - this.groups = document.querySelectorAll('.group-path'); + this.groupTitle = this.titleContainer.querySelector('.group-title'); + this.groups = this.titleContainer.querySelectorAll('.group-path'); this.toggle = null; this.isHidden = false; this.init(); @@ -33,11 +33,20 @@ export default class GroupName { createToggle() { this.toggle = document.createElement('button'); + this.toggle.setAttribute('type', 'button'); this.toggle.className = 'text-expander group-name-toggle'; this.toggle.setAttribute('aria-label', 'Toggle full path'); - this.toggle.innerHTML = '...'; + if (Cookies.get('new_nav') === 'true') { + this.toggle.innerHTML = '<i class="fa fa-ellipsis-h" aria-hidden="true"></i>'; + } else { + this.toggle.innerHTML = '...'; + } this.toggle.addEventListener('click', this.toggleGroups.bind(this)); - this.titleContainer.insertBefore(this.toggle, this.title); + if (Cookies.get('new_nav') === 'true') { + this.title.insertBefore(this.toggle, this.groupTitle); + } else { + this.titleContainer.insertBefore(this.toggle, this.title); + } this.toggleGroups(); } diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js index a8856120c5e..4f376599ba9 100644 --- a/app/assets/javascripts/issuable_bulk_update_sidebar.js +++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js @@ -5,6 +5,7 @@ /* global SubscriptionSelect */ import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; +import SidebarHeightManager from './sidebar_height_manager'; const HIDDEN_CLASS = 'hidden'; const DISABLED_CONTENT_CLASS = 'disabled-content'; @@ -56,18 +57,6 @@ export default class IssuableBulkUpdateSidebar { return navbarHeight + layoutNavHeight + subNavScroll; } - initSidebar() { - if (!this.navHeight) { - this.navHeight = this.getNavHeight(); - } - - if (!this.sidebarInitialized) { - $(document).off('scroll').on('scroll', _.throttle(this.setSidebarHeight, 10).bind(this)); - $(window).off('resize').on('resize', _.throttle(this.setSidebarHeight, 10).bind(this)); - this.sidebarInitialized = true; - } - } - setupBulkUpdateActions() { IssuableBulkUpdateActions.setOriginalDropdownData(); } @@ -97,7 +86,7 @@ export default class IssuableBulkUpdateSidebar { this.toggleCheckboxDisplay(enable); if (enable) { - this.initSidebar(); + SidebarHeightManager.init(); } } @@ -143,17 +132,6 @@ export default class IssuableBulkUpdateSidebar { this.$bulkEditSubmitBtn.enable(); } } - // loosely based on method of the same name in right_sidebar.js - setSidebarHeight() { - const currentScrollDepth = window.pageYOffset || 0; - const diff = this.navHeight - currentScrollDepth; - - if (diff > 0) { - this.$sidebar.outerHeight(window.innerHeight - diff); - } else { - this.$sidebar.outerHeight('100%'); - } - } static getCheckedIssueIds() { const $checkedIssues = $('.selected_issue:checked'); diff --git a/app/assets/javascripts/jobs/job_details_bundle.js b/app/assets/javascripts/jobs/job_details_bundle.js index 939d17129de..f92e669414a 100644 --- a/app/assets/javascripts/jobs/job_details_bundle.js +++ b/app/assets/javascripts/jobs/job_details_bundle.js @@ -26,14 +26,6 @@ document.addEventListener('DOMContentLoaded', () => { mounted() { this.mediator.initBuildClass(); }, - updated() { - // Wait for flash message to be appended - Vue.nextTick(() => { - if (this.mediator.build) { - this.mediator.build.verifyTopPosition(); - } - }); - }, render(createElement) { return createElement('job-header', { props: { diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index bfcc50996cc..1d1763c3963 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -112,29 +112,11 @@ window.dateFormat = dateFormat; return timefor; }; - w.gl.utils.cachedTimeagoElements = []; w.gl.utils.renderTimeago = function($els) { - if (!$els && !w.gl.utils.cachedTimeagoElements.length) { - w.gl.utils.cachedTimeagoElements = [].slice.call(document.querySelectorAll('.js-timeago-render')); - } else if ($els) { - w.gl.utils.cachedTimeagoElements = w.gl.utils.cachedTimeagoElements.concat($els.toArray()); - } - - w.gl.utils.cachedTimeagoElements.forEach(gl.utils.updateTimeagoText); - }; - - w.gl.utils.updateTimeagoText = function(el) { - const formattedDate = gl.utils.getTimeago().format(el.getAttribute('datetime'), lang); - - if (el.textContent !== formattedDate) { - el.textContent = formattedDate; - } - }; - - w.gl.utils.initTimeagoTimeout = function() { - gl.utils.renderTimeago(); + const timeagoEls = $els || document.querySelectorAll('.js-timeago-render'); - gl.utils.timeagoTimeout = setTimeout(gl.utils.initTimeagoTimeout, 1000); + // timeago.js sets timeouts internally for each timeago value to be updated in real time + gl.utils.getTimeago().render(timeagoEls, lang); }; w.gl.utils.getDayDifference = function(a, b) { diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index d27b4ec78c6..fe752d95b90 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -70,7 +70,7 @@ import './ajax_loading_spinner'; import './api'; import './aside'; import './autosave'; -import AwardsHandler from './awards_handler'; +import loadAwardsHandler from './awards_handler'; import './breakpoints'; import './broadcast_message'; import './build'; @@ -355,10 +355,10 @@ $(function () { $window.off('resize.app').on('resize.app', function () { return fitSidebarForSize(); }); - gl.awardsHandler = new AwardsHandler(); + loadAwardsHandler(); new Aside(); - gl.utils.initTimeagoTimeout(); + gl.utils.renderTimeago(); $(document).trigger('init.scrolling-tabs'); }); diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 3cf3233cc65..7840f05a8ae 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -144,7 +144,9 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion'; this.resetViewContainer(); this.mountPipelinesView(); } else { - this.expandView(); + if (Breakpoints.get().getBreakpointSize() !== 'xs') { + this.expandView(); + } this.resetViewContainer(); this.destroyPipelinesView(); } diff --git a/app/assets/javascripts/monitoring/components/monitoring_column.vue b/app/assets/javascripts/monitoring/components/monitoring_column.vue index 4f4792877ee..0cd62053d14 100644 --- a/app/assets/javascripts/monitoring/components/monitoring_column.vue +++ b/app/assets/javascripts/monitoring/components/monitoring_column.vue @@ -35,7 +35,7 @@ data() { return { - graphHeight: 500, + graphHeight: 450, graphWidth: 600, graphHeightOffset: 120, xScale: {}, @@ -88,7 +88,9 @@ }, paddingBottomRootSvg() { - return (Math.ceil(this.graphHeight * 100) / this.graphWidth) || 0; + return { + paddingBottom: `${(Math.ceil(this.graphHeight * 100) / this.graphWidth) || 0}%`, + }; }, }, @@ -104,7 +106,7 @@ } this.data = query.result[0].values; this.unitOfDisplay = query.unit || 'N/A'; - this.yAxisLabel = this.columnData.y_axis || 'Values'; + this.yAxisLabel = this.columnData.y_label || 'Values'; this.legendTitle = query.legend || 'Average'; this.graphWidth = this.$refs.baseSvg.clientWidth - this.margin.left - this.margin.right; @@ -157,12 +159,12 @@ const xAxis = d3.svg.axis() .scale(axisXScale) - .ticks(measurements.ticks) + .ticks(measurements.xTicks) .orient('bottom'); const yAxis = d3.svg.axis() .scale(this.yScale) - .ticks(measurements.ticks) + .ticks(measurements.yTicks) .orient('left'); d3.select(this.$refs.baseSvg).select('.x-axis').call(xAxis); @@ -170,8 +172,12 @@ const width = this.graphWidth; d3.select(this.$refs.baseSvg).select('.y-axis').call(yAxis) .selectAll('.tick') - .each(function createTickLines() { - d3.select(this).select('line').attr('x2', width); + .each(function createTickLines(d, i) { + if (i > 0) { + d3.select(this).select('line') + .attr('x2', width) + .attr('class', 'axis-tick'); + } // Avoid adding the class to the first tick, to prevent coloring }); // This will select all of the ticks once they're rendered this.xScale = d3.time.scale() @@ -198,7 +204,7 @@ watch: { updateAspectRatio() { if (this.updateAspectRatio) { - this.graphHeight = 500; + this.graphHeight = 450; this.graphWidth = 600; this.measurements = measurements.large; this.draw(); @@ -216,14 +222,14 @@ <div :class="classType"> <h5 - class="text-center"> + class="text-center graph-title"> {{columnData.title}} </h5> - <div - class="prometheus-svg-container"> + <div + class="prometheus-svg-container" + :style="paddingBottomRootSvg"> <svg :viewBox="outterViewBox" - :style="{ 'padding-bottom': paddingBottomRootSvg }" ref="baseSvg"> <g class="x-axis" diff --git a/app/assets/javascripts/monitoring/components/monitoring_flag.vue b/app/assets/javascripts/monitoring/components/monitoring_flag.vue index 180a771415b..5a0e50fcab3 100644 --- a/app/assets/javascripts/monitoring/components/monitoring_flag.vue +++ b/app/assets/javascripts/monitoring/components/monitoring_flag.vue @@ -87,14 +87,14 @@ </rect> <text class="text-metric text-metric-bold" - x="8" + x="16" y="35" transform="translate(-5, 20)"> {{formatTime}} </text> <text - class="text-metric-date" - x="8" + class="text-metric" + x="16" y="15" transform="translate(-5, 20)"> {{formatDate}} diff --git a/app/assets/javascripts/monitoring/components/monitoring_legends.vue b/app/assets/javascripts/monitoring/components/monitoring_legends.vue index b30ed3cc889..922a5e1bf0e 100644 --- a/app/assets/javascripts/monitoring/components/monitoring_legends.vue +++ b/app/assets/javascripts/monitoring/components/monitoring_legends.vue @@ -109,13 +109,13 @@ </text> <rect class="rect-axis-text" - :x="xPosition + 50" + :x="xPosition + 60" :y="graphHeight - 80" - width="50" + width="35" height="50"> </rect> <text - class="label-axis-text" + class="label-axis-text x-label-text" :x="xPosition + 60" :y="yPosition" dy=".35em"> @@ -131,13 +131,13 @@ <text class="text-metric-title" x="50" - :y="graphHeight - 40"> + :y="graphHeight - 25"> {{legendTitle}} </text> <text class="text-metric-usage" x="50" - :y="graphHeight - 25"> + :y="graphHeight - 10"> {{metricUsage}} </text> </g> diff --git a/app/assets/javascripts/monitoring/utils/measurements.js b/app/assets/javascripts/monitoring/utils/measurements.js index a60d2522f49..62cd19c86e1 100644 --- a/app/assets/javascripts/monitoring/utils/measurements.js +++ b/app/assets/javascripts/monitoring/utils/measurements.js @@ -8,14 +8,14 @@ export default { }, legends: { width: 15, - height: 30, + height: 25, }, backgroundLegend: { width: 30, height: 50, }, axisLabelLineOffset: -20, - legendOffset: 52, + legendOffset: 35, }, large: { // This covers both md and lg screen sizes margin: { @@ -26,14 +26,15 @@ export default { }, legends: { width: 20, - height: 35, + height: 30, }, backgroundLegend: { width: 30, height: 150, }, axisLabelLineOffset: 20, - legendOffset: 55, + legendOffset: 38, }, - ticks: 3, + xTicks: 8, + yTicks: 3, }; diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 34476f3303f..555b8c8a65c 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -18,6 +18,7 @@ import 'vendor/jquery.caret'; // required by jquery.atwho import 'vendor/jquery.atwho'; import AjaxCache from '~/lib/utils/ajax_cache'; import CommentTypeToggle from './comment_type_toggle'; +import loadAwardsHandler from './awards_handler'; import './autosave'; import './dropzone_input'; import './task_list'; @@ -291,8 +292,13 @@ export default class Notes { if ('emoji_award' in noteEntity.commands_changes) { votesBlock = $('.js-awards-block').eq(0); - gl.awardsHandler.addAwardToEmojiBar(votesBlock, noteEntity.commands_changes.emoji_award); - return gl.awardsHandler.scrollToAwards(); + + loadAwardsHandler().then((awardsHandler) => { + awardsHandler.addAwardToEmojiBar(votesBlock, noteEntity.commands_changes.emoji_award); + awardsHandler.scrollToAwards(); + }).catch(() => { + // ignore + }); } } } @@ -337,6 +343,10 @@ export default class Notes { if (!noteEntity.valid) { if (noteEntity.errors.commands_only) { + if (noteEntity.commands_changes && + Object.keys(noteEntity.commands_changes).length > 0) { + $notesList.find('.system-note.being-posted').remove(); + } this.addFlash(noteEntity.errors.commands_only, 'notice', this.parentTimeline); this.refresh(); } @@ -829,6 +839,8 @@ export default class Notes { */ setupDiscussionNoteForm(dataHolder, form) { // setup note target + const diffFileData = dataHolder.closest('.text-file'); + var discussionID = dataHolder.data('discussionId'); if (discussionID) { @@ -839,9 +851,10 @@ export default class Notes { form.attr('data-line-code', dataHolder.data('lineCode')); form.find('#line_type').val(dataHolder.data('lineType')); - form.find('#note_noteable_type').val(dataHolder.data('noteableType')); - form.find('#note_noteable_id').val(dataHolder.data('noteableId')); - form.find('#note_commit_id').val(dataHolder.data('commitId')); + form.find('#note_noteable_type').val(diffFileData.data('noteableType')); + form.find('#note_noteable_id').val(diffFileData.data('noteableId')); + form.find('#note_commit_id').val(diffFileData.data('commitId')); + form.find('#note_type').val(dataHolder.data('noteType')); // LegacyDiffNote diff --git a/app/assets/javascripts/oauth_remember_me.js b/app/assets/javascripts/oauth_remember_me.js new file mode 100644 index 00000000000..ffc2dd6bbca --- /dev/null +++ b/app/assets/javascripts/oauth_remember_me.js @@ -0,0 +1,32 @@ +/** + * OAuth-based login buttons have a separate "remember me" checkbox. + * + * Toggling this checkbox adds/removes a `remember_me` parameter to the + * login buttons' href, which is passed on to the omniauth callback. + **/ + +export default class OAuthRememberMe { + constructor(opts = {}) { + this.container = opts.container || ''; + this.loginLinkSelector = '.oauth-login'; + } + + bindEvents() { + $('#remember_me', this.container).on('click', this.toggleRememberMe); + } + + // eslint-disable-next-line class-methods-use-this + toggleRememberMe(event) { + const rememberMe = $(event.target).is(':checked'); + + $('.oauth-login', this.container).each((i, element) => { + const href = $(element).attr('href'); + + if (rememberMe) { + $(element).attr('href', `${href}?remember_me=1`); + } else { + $(element).attr('href', href.replace('?remember_me=1', '')); + } + }); + } +} diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index 322162afdb8..d8f1fe10b26 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -1,6 +1,7 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, object-shorthand, comma-dangle, no-else-return, no-param-reassign, max-len */ import Cookies from 'js-cookie'; +import SidebarHeightManager from './sidebar_height_manager'; (function() { this.Sidebar = (function() { @@ -8,10 +9,6 @@ import Cookies from 'js-cookie'; this.toggleTodo = this.toggleTodo.bind(this); this.sidebar = $('aside'); - this.$sidebarInner = this.sidebar.find('.issuable-sidebar'); - this.$navGitlab = $('.navbar-gitlab'); - this.$rightSidebar = $('.js-right-sidebar'); - this.removeListeners(); this.addEventListeners(); } @@ -25,16 +22,14 @@ import Cookies from 'js-cookie'; }; Sidebar.prototype.addEventListeners = function() { + SidebarHeightManager.init(); const $document = $(document); - const throttledSetSidebarHeight = _.throttle(this.setSidebarHeight.bind(this), 20); - const debouncedSetSidebarHeight = _.debounce(this.setSidebarHeight.bind(this), 200); this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked); $('.dropdown').on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden); $('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading); $('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded); - $(window).on('resize', () => throttledSetSidebarHeight()); - $document.on('scroll', () => debouncedSetSidebarHeight()); + $document.on('click', '.js-sidebar-toggle', function(e, triggered) { var $allGutterToggleIcons, $this, $thisIcon; e.preventDefault(); @@ -212,18 +207,6 @@ import Cookies from 'js-cookie'; } }; - Sidebar.prototype.setSidebarHeight = function() { - const $navHeight = this.$navGitlab.outerHeight(); - const diff = $navHeight - $(window).scrollTop(); - if (diff > 0) { - this.$rightSidebar.outerHeight($(window).height() - diff); - this.$sidebarInner.height('100%'); - } else { - this.$rightSidebar.outerHeight('100%'); - this.$sidebarInner.height(''); - } - }; - Sidebar.prototype.isOpen = function() { return this.sidebar.is('.right-sidebar-expanded'); }; diff --git a/app/assets/javascripts/sidebar_height_manager.js b/app/assets/javascripts/sidebar_height_manager.js new file mode 100644 index 00000000000..022415f22b2 --- /dev/null +++ b/app/assets/javascripts/sidebar_height_manager.js @@ -0,0 +1,33 @@ +export default { + init() { + if (!this.initialized) { + this.$window = $(window); + this.$rightSidebar = $('.js-right-sidebar'); + this.$navHeight = $('.navbar-gitlab').outerHeight() + + $('.layout-nav').outerHeight() + + $('.sub-nav-scroll').outerHeight(); + + const throttledSetSidebarHeight = _.throttle(() => this.setSidebarHeight(), 20); + const debouncedSetSidebarHeight = _.debounce(() => this.setSidebarHeight(), 200); + + this.$window.on('scroll', throttledSetSidebarHeight); + this.$window.on('resize', debouncedSetSidebarHeight); + this.initialized = true; + } + }, + + setSidebarHeight() { + const currentScrollDepth = window.pageYOffset || 0; + const diff = this.$navHeight - currentScrollDepth; + + if (diff > 0) { + const newSidebarHeight = window.innerHeight - diff; + this.$rightSidebar.outerHeight(newSidebarHeight); + this.sidebarHeightIsCustom = true; + } else if (this.sidebarHeightIsCustom) { + this.$rightSidebar.outerHeight('100%'); + this.sidebarHeightIsCustom = false; + } + }, +}; + diff --git a/app/assets/javascripts/signin_tabs_memoizer.js b/app/assets/javascripts/signin_tabs_memoizer.js index 2587facc582..3997a695d15 100644 --- a/app/assets/javascripts/signin_tabs_memoizer.js +++ b/app/assets/javascripts/signin_tabs_memoizer.js @@ -2,56 +2,54 @@ /* eslint no-new: "off" */ import AccessorUtilities from './lib/utils/accessor'; -((global) => { - /** - * Memorize the last selected tab after reloading a page. - * Does that setting the current selected tab in the localStorage - */ - class ActiveTabMemoizer { - constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) { - this.currentTabKey = currentTabKey; - this.tabSelector = tabSelector; - this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe(); - - this.bootstrap(); - } - - bootstrap() { - const tabs = document.querySelectorAll(this.tabSelector); - if (tabs.length > 0) { - tabs[0].addEventListener('click', (e) => { - if (e.target && e.target.nodeName === 'A') { - const anchorName = e.target.getAttribute('href'); - this.saveData(anchorName); - } - }); - } +/** + * Memorize the last selected tab after reloading a page. + * Does that setting the current selected tab in the localStorage + */ +class ActiveTabMemoizer { + constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) { + this.currentTabKey = currentTabKey; + this.tabSelector = tabSelector; + this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe(); + + this.bootstrap(); + } - this.showTab(); + bootstrap() { + const tabs = document.querySelectorAll(this.tabSelector); + if (tabs.length > 0) { + tabs[0].addEventListener('click', (e) => { + if (e.target && e.target.nodeName === 'A') { + const anchorName = e.target.getAttribute('href'); + this.saveData(anchorName); + } + }); } - showTab() { - const anchorName = this.readData(); - if (anchorName) { - const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`); - if (tab) { - tab.click(); - } + this.showTab(); + } + + showTab() { + const anchorName = this.readData(); + if (anchorName) { + const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`); + if (tab) { + tab.click(); } } + } - saveData(val) { - if (!this.isLocalStorageAvailable) return undefined; + saveData(val) { + if (!this.isLocalStorageAvailable) return undefined; - return window.localStorage.setItem(this.currentTabKey, val); - } + return window.localStorage.setItem(this.currentTabKey, val); + } - readData() { - if (!this.isLocalStorageAvailable) return null; + readData() { + if (!this.isLocalStorageAvailable) return null; - return window.localStorage.getItem(this.currentTabKey); - } + return window.localStorage.getItem(this.currentTabKey); } +} - global.ActiveTabMemoizer = ActiveTabMemoizer; -})(window); +window.ActiveTabMemoizer = ActiveTabMemoizer; diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js index c44892dae3d..00d04ce0c33 100644 --- a/app/assets/javascripts/single_file_diff.js +++ b/app/assets/javascripts/single_file_diff.js @@ -1,96 +1,98 @@ /* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, consistent-return, no-param-reassign, max-len */ -(function() { - window.SingleFileDiff = (function() { - var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER; +import FilesCommentButton from './files_comment_button'; - WRAPPER = '<div class="diff-content"></div>'; +window.SingleFileDiff = (function() { + var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER; - LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>'; + WRAPPER = '<div class="diff-content"></div>'; - ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>'; + LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>'; - COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>'; + ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>'; - function SingleFileDiff(file) { - this.file = file; - this.toggleDiff = this.toggleDiff.bind(this); - this.content = $('.diff-content', this.file); - this.$toggleIcon = $('.diff-toggle-caret', this.file); - this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path'); - this.isOpen = !this.diffForPath; - if (this.diffForPath) { - this.collapsedContent = this.content; - this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide(); - this.content = null; - this.collapsedContent.after(this.loadingContent); - this.$toggleIcon.addClass('fa-caret-right'); - } else { - this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide(); - this.content.after(this.collapsedContent); - this.$toggleIcon.addClass('fa-caret-down'); - } + COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>'; - $('.js-file-title, .click-to-expand', this.file).on('click', (function (e) { - this.toggleDiff($(e.target)); - }).bind(this)); + function SingleFileDiff(file) { + this.file = file; + this.toggleDiff = this.toggleDiff.bind(this); + this.content = $('.diff-content', this.file); + this.$toggleIcon = $('.diff-toggle-caret', this.file); + this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path'); + this.isOpen = !this.diffForPath; + if (this.diffForPath) { + this.collapsedContent = this.content; + this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide(); + this.content = null; + this.collapsedContent.after(this.loadingContent); + this.$toggleIcon.addClass('fa-caret-right'); + } else { + this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide(); + this.content.after(this.collapsedContent); + this.$toggleIcon.addClass('fa-caret-down'); } - SingleFileDiff.prototype.toggleDiff = function($target, cb) { - if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return; - this.isOpen = !this.isOpen; - if (!this.isOpen && !this.hasError) { - this.content.hide(); - this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down'); - this.collapsedContent.show(); - if (typeof gl.diffNotesCompileComponents !== 'undefined') { - gl.diffNotesCompileComponents(); + $('.js-file-title, .click-to-expand', this.file).on('click', (function (e) { + this.toggleDiff($(e.target)); + }).bind(this)); + } + + SingleFileDiff.prototype.toggleDiff = function($target, cb) { + if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return; + this.isOpen = !this.isOpen; + if (!this.isOpen && !this.hasError) { + this.content.hide(); + this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down'); + this.collapsedContent.show(); + if (typeof gl.diffNotesCompileComponents !== 'undefined') { + gl.diffNotesCompileComponents(); + } + } else if (this.content) { + this.collapsedContent.hide(); + this.content.show(); + this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right'); + if (typeof gl.diffNotesCompileComponents !== 'undefined') { + gl.diffNotesCompileComponents(); + } + } else { + this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right'); + return this.getContentHTML(cb); + } + }; + + SingleFileDiff.prototype.getContentHTML = function(cb) { + this.collapsedContent.hide(); + this.loadingContent.show(); + $.get(this.diffForPath, (function(_this) { + return function(data) { + _this.loadingContent.hide(); + if (data.html) { + _this.content = $(data.html); + _this.content.syntaxHighlight(); + } else { + _this.hasError = true; + _this.content = $(ERROR_HTML); } - } else if (this.content) { - this.collapsedContent.hide(); - this.content.show(); - this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right'); + _this.collapsedContent.after(_this.content); + if (typeof gl.diffNotesCompileComponents !== 'undefined') { gl.diffNotesCompileComponents(); } - } else { - this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right'); - return this.getContentHTML(cb); - } - }; - - SingleFileDiff.prototype.getContentHTML = function(cb) { - this.collapsedContent.hide(); - this.loadingContent.show(); - $.get(this.diffForPath, (function(_this) { - return function(data) { - _this.loadingContent.hide(); - if (data.html) { - _this.content = $(data.html); - _this.content.syntaxHighlight(); - } else { - _this.hasError = true; - _this.content = $(ERROR_HTML); - } - _this.collapsedContent.after(_this.content); - if (typeof gl.diffNotesCompileComponents !== 'undefined') { - gl.diffNotesCompileComponents(); - } + FilesCommentButton.init($(_this.file)); - if (cb) cb(); - }; - })(this)); - }; + if (cb) cb(); + }; + })(this)); + }; - return SingleFileDiff; - })(); + return SingleFileDiff; +})(); - $.fn.singleFileDiff = function() { - return this.each(function() { - if (!$.data(this, 'singleFileDiff')) { - return $.data(this, 'singleFileDiff', new window.SingleFileDiff(this)); - } - }); - }; -}).call(window); +$.fn.singleFileDiff = function() { + return this.each(function() { + if (!$.data(this, 'singleFileDiff')) { + return $.data(this, 'singleFileDiff', new window.SingleFileDiff(this)); + } + }); +}; diff --git a/app/assets/javascripts/smart_interval.js b/app/assets/javascripts/smart_interval.js index d1bdc353be2..2bf7a3a5d61 100644 --- a/app/assets/javascripts/smart_interval.js +++ b/app/assets/javascripts/smart_interval.js @@ -1,158 +1,157 @@ -/* -* Instances of SmartInterval extend the functionality of `setInterval`, make it configurable -* and controllable by a public API. -* -* */ - -(() => { - class SmartInterval { - /** - * @param { function } opts.callback Function to be called on each iteration (required) - * @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially - * @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this - * @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this - * when the page is hidden - * @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor - * @param { boolean } opts.lazyStart Configure if timer is initialized on - * instantiation or lazily - * @param { boolean } opts.immediateExecution Configure if callback should - * be executed before the first interval. - */ - constructor(opts = {}) { - this.cfg = { - callback: opts.callback, - startingInterval: opts.startingInterval, - maxInterval: opts.maxInterval, - hiddenInterval: opts.hiddenInterval, - incrementByFactorOf: opts.incrementByFactorOf, - lazyStart: opts.lazyStart, - immediateExecution: opts.immediateExecution, - }; - - this.state = { - intervalId: null, - currentInterval: this.cfg.startingInterval, - pageVisibility: 'visible', - }; - - this.initInterval(); - } - /* public */ - - start() { - const cfg = this.cfg; - const state = this.state; - - if (cfg.immediateExecution) { - cfg.immediateExecution = false; - cfg.callback(); - } +/** + * Instances of SmartInterval extend the functionality of `setInterval`, make it configurable + * and controllable by a public API. + */ + +class SmartInterval { + /** + * @param { function } opts.callback Function to be called on each iteration (required) + * @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially + * @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this + * @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this + * when the page is hidden + * @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor + * @param { boolean } opts.lazyStart Configure if timer is initialized on + * instantiation or lazily + * @param { boolean } opts.immediateExecution Configure if callback should + * be executed before the first interval. + */ + constructor(opts = {}) { + this.cfg = { + callback: opts.callback, + startingInterval: opts.startingInterval, + maxInterval: opts.maxInterval, + hiddenInterval: opts.hiddenInterval, + incrementByFactorOf: opts.incrementByFactorOf, + lazyStart: opts.lazyStart, + immediateExecution: opts.immediateExecution, + }; + + this.state = { + intervalId: null, + currentInterval: this.cfg.startingInterval, + pageVisibility: 'visible', + }; + + this.initInterval(); + } - state.intervalId = window.setInterval(() => { - cfg.callback(); + /* public */ - if (this.getCurrentInterval() === cfg.maxInterval) { - return; - } + start() { + const cfg = this.cfg; + const state = this.state; - this.incrementInterval(); - this.resume(); - }, this.getCurrentInterval()); + if (cfg.immediateExecution) { + cfg.immediateExecution = false; + cfg.callback(); } - // cancel the existing timer, setting the currentInterval back to startingInterval - cancel() { - this.setCurrentInterval(this.cfg.startingInterval); - this.stopTimer(); - } + state.intervalId = window.setInterval(() => { + cfg.callback(); - onVisibilityHidden() { - if (this.cfg.hiddenInterval) { - this.setCurrentInterval(this.cfg.hiddenInterval); - this.resume(); - } else { - this.cancel(); + if (this.getCurrentInterval() === cfg.maxInterval) { + return; } - } - // start a timer, using the existing interval - resume() { - this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped - this.start(); - } + this.incrementInterval(); + this.resume(); + }, this.getCurrentInterval()); + } - onVisibilityVisible() { - this.cancel(); - this.start(); - } + // cancel the existing timer, setting the currentInterval back to startingInterval + cancel() { + this.setCurrentInterval(this.cfg.startingInterval); + this.stopTimer(); + } - destroy() { + onVisibilityHidden() { + if (this.cfg.hiddenInterval) { + this.setCurrentInterval(this.cfg.hiddenInterval); + this.resume(); + } else { this.cancel(); - document.removeEventListener('visibilitychange', this.handleVisibilityChange); - $(document).off('visibilitychange').off('beforeunload'); } + } - /* private */ + // start a timer, using the existing interval + resume() { + this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped + this.start(); + } - initInterval() { - const cfg = this.cfg; + onVisibilityVisible() { + this.cancel(); + this.start(); + } - if (!cfg.lazyStart) { - this.start(); - } + destroy() { + this.cancel(); + document.removeEventListener('visibilitychange', this.handleVisibilityChange); + $(document).off('visibilitychange').off('beforeunload'); + } - this.initVisibilityChangeHandling(); - this.initPageUnloadHandling(); - } + /* private */ - initVisibilityChangeHandling() { - // cancel interval when tab no longer shown (prevents cached pages from polling) - document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this)); - } + initInterval() { + const cfg = this.cfg; - initPageUnloadHandling() { - // TODO: Consider refactoring in light of turbolinks removal. - // prevent interval continuing after page change, when kept in cache by Turbolinks - $(document).on('beforeunload', () => this.cancel()); + if (!cfg.lazyStart) { + this.start(); } - handleVisibilityChange(e) { - this.state.pageVisibility = e.target.visibilityState; - const intervalAction = this.isPageVisible() ? - this.onVisibilityVisible : - this.onVisibilityHidden; + this.initVisibilityChangeHandling(); + this.initPageUnloadHandling(); + } - intervalAction.apply(this); - } + initVisibilityChangeHandling() { + // cancel interval when tab no longer shown (prevents cached pages from polling) + document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this)); + } - getCurrentInterval() { - return this.state.currentInterval; - } + initPageUnloadHandling() { + // TODO: Consider refactoring in light of turbolinks removal. + // prevent interval continuing after page change, when kept in cache by Turbolinks + $(document).on('beforeunload', () => this.cancel()); + } - setCurrentInterval(newInterval) { - this.state.currentInterval = newInterval; - } + handleVisibilityChange(e) { + this.state.pageVisibility = e.target.visibilityState; + const intervalAction = this.isPageVisible() ? + this.onVisibilityVisible : + this.onVisibilityHidden; - incrementInterval() { - const cfg = this.cfg; - const currentInterval = this.getCurrentInterval(); - if (cfg.hiddenInterval && !this.isPageVisible()) return; - let nextInterval = currentInterval * cfg.incrementByFactorOf; + intervalAction.apply(this); + } - if (nextInterval > cfg.maxInterval) { - nextInterval = cfg.maxInterval; - } + getCurrentInterval() { + return this.state.currentInterval; + } + + setCurrentInterval(newInterval) { + this.state.currentInterval = newInterval; + } + + incrementInterval() { + const cfg = this.cfg; + const currentInterval = this.getCurrentInterval(); + if (cfg.hiddenInterval && !this.isPageVisible()) return; + let nextInterval = currentInterval * cfg.incrementByFactorOf; - this.setCurrentInterval(nextInterval); + if (nextInterval > cfg.maxInterval) { + nextInterval = cfg.maxInterval; } - isPageVisible() { return this.state.pageVisibility === 'visible'; } + this.setCurrentInterval(nextInterval); + } - stopTimer() { - const state = this.state; + isPageVisible() { return this.state.pageVisibility === 'visible'; } - state.intervalId = window.clearInterval(state.intervalId); - } + stopTimer() { + const state = this.state; + + state.intervalId = window.clearInterval(state.intervalId); } - gl.SmartInterval = SmartInterval; -})(window.gl || (window.gl = {})); +} + +window.gl.SmartInterval = SmartInterval; diff --git a/app/assets/javascripts/snippets_list.js b/app/assets/javascripts/snippets_list.js index 2128007113f..da7b9e08447 100644 --- a/app/assets/javascripts/snippets_list.js +++ b/app/assets/javascripts/snippets_list.js @@ -1,13 +1,9 @@ /* eslint-disable arrow-parens, no-param-reassign, space-before-function-paren, func-names, no-var, max-len */ -(global => { - global.gl = global.gl || {}; +window.gl.SnippetsList = function() { + var $holder = $('.snippets-list-holder'); - gl.SnippetsList = function() { - var $holder = $('.snippets-list-holder'); - - $holder.find('.pagination').on('ajax:success', (e, data) => { - $holder.replaceWith(data.html); - }); - }; -})(window); + $holder.find('.pagination').on('ajax:success', (e, data) => { + $holder.replaceWith(data.html); + }); +}; diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js index c75b44cc2fd..840ae1edd9d 100644 --- a/app/assets/javascripts/star.js +++ b/app/assets/javascripts/star.js @@ -1,30 +1,28 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, max-len */ /* global Flash */ -(function() { - this.Star = (function() { - function Star() { - $('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) { - var $starIcon, $starSpan, $this, toggleStar; - $this = $(this); - $starSpan = $this.find('span'); - $starIcon = $this.find('i'); - toggleStar = function(isStarred) { - $this.parent().find('.star-count').text(data.star_count); - if (isStarred) { - $starSpan.removeClass('starred').text('Star'); - $starIcon.removeClass('fa-star').addClass('fa-star-o'); - } else { - $starSpan.addClass('starred').text('Unstar'); - $starIcon.removeClass('fa-star-o').addClass('fa-star'); - } - }; - toggleStar($starSpan.hasClass('starred')); - }).on('ajax:error', function(e, xhr, status, error) { - new Flash('Star toggle failed. Try again later.', 'alert'); - }); - } +window.Star = (function() { + function Star() { + $('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) { + var $starIcon, $starSpan, $this, toggleStar; + $this = $(this); + $starSpan = $this.find('span'); + $starIcon = $this.find('i'); + toggleStar = function(isStarred) { + $this.parent().find('.star-count').text(data.star_count); + if (isStarred) { + $starSpan.removeClass('starred').text('Star'); + $starIcon.removeClass('fa-star').addClass('fa-star-o'); + } else { + $starSpan.addClass('starred').text('Unstar'); + $starIcon.removeClass('fa-star-o').addClass('fa-star'); + } + }; + toggleStar($starSpan.hasClass('starred')); + }).on('ajax:error', function(e, xhr, status, error) { + new Flash('Star toggle failed. Try again later.', 'alert'); + }); + } - return Star; - })(); -}).call(window); + return Star; +})(); diff --git a/app/assets/javascripts/subscription.js b/app/assets/javascripts/subscription.js index 5f9a3e00c22..bb4d68fcd49 100644 --- a/app/assets/javascripts/subscription.js +++ b/app/assets/javascripts/subscription.js @@ -1,47 +1,45 @@ -(() => { - class Subscription { - constructor(containerElm) { - this.containerElm = containerElm; +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)); - } + 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'); + 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; + const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe'; + const toggleActionUrl = this.containerElm.dataset.url; - $.post(toggleActionUrl, () => { - button.classList.remove('disabled'); + $.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')) { - gl.issueBoards.boardStoreIssueSet( - 'subscribed', - !gl.issueBoards.BoardsStore.detail.issue.subscribed, - ); - } else { - buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe'; - } - }); - } + // hack to allow this to work with the issue boards Vue object + if (document.querySelector('html').classList.contains('issue-boards-page')) { + gl.issueBoards.boardStoreIssueSet( + '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)); - } + static bindAll(selector) { + [].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm)); } +} - window.gl = window.gl || {}; - window.gl.Subscription = Subscription; -})(); +window.gl = window.gl || {}; +window.gl.Subscription = Subscription; diff --git a/app/assets/javascripts/subscription_select.js b/app/assets/javascripts/subscription_select.js index 0cd591c7320..a48434181b6 100644 --- a/app/assets/javascripts/subscription_select.js +++ b/app/assets/javascripts/subscription_select.js @@ -1,34 +1,33 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, max-len */ -(function() { - this.SubscriptionSelect = (function() { - function SubscriptionSelect() { - $('.js-subscription-event').each(function(i, el) { - var fieldName; - fieldName = $(el).data("field-name"); - return $(el).glDropdown({ - selectable: true, - fieldName: fieldName, - toggleLabel: (function(_this) { - return function(selected, el, instance) { - var $item, label; - label = 'Subscription'; - $item = instance.dropdown.find('.is-active'); - if ($item.length) { - label = $item.text(); - } - return label; - }; - })(this), - clicked: function(options) { - return options.e.preventDefault(); - }, - id: function(obj, el) { - return $(el).data("id"); - } - }); + +window.SubscriptionSelect = (function() { + function SubscriptionSelect() { + $('.js-subscription-event').each(function(i, el) { + var fieldName; + fieldName = $(el).data("field-name"); + return $(el).glDropdown({ + selectable: true, + fieldName: fieldName, + toggleLabel: (function(_this) { + return function(selected, el, instance) { + var $item, label; + label = 'Subscription'; + $item = instance.dropdown.find('.is-active'); + if ($item.length) { + label = $item.text(); + } + return label; + }; + })(this), + clicked: function(options) { + return options.e.preventDefault(); + }, + id: function(obj, el) { + return $(el).data("id"); + } }); - } + }); + } - return SubscriptionSelect; - })(); -}).call(window); + return SubscriptionSelect; +})(); diff --git a/app/assets/javascripts/syntax_highlight.js b/app/assets/javascripts/syntax_highlight.js index 7c063fae045..662d6b36c16 100644 --- a/app/assets/javascripts/syntax_highlight.js +++ b/app/assets/javascripts/syntax_highlight.js @@ -9,19 +9,18 @@ // // <div class="js-syntax-highlight"></div> // -(function() { - $.fn.syntaxHighlight = function() { - var $children; - if ($(this).hasClass('js-syntax-highlight')) { - // Given the element itself, apply highlighting - return $(this).addClass(gon.user_color_scheme); - } else { - // Given a parent element, recurse to any of its applicable children - $children = $(this).find('.js-syntax-highlight'); - if ($children.length) { - return $children.syntaxHighlight(); - } +$.fn.syntaxHighlight = function() { + var $children; + + if ($(this).hasClass('js-syntax-highlight')) { + // Given the element itself, apply highlighting + return $(this).addClass(gon.user_color_scheme); + } else { + // Given a parent element, recurse to any of its applicable children + $children = $(this).find('.js-syntax-highlight'); + if ($children.length) { + return $children.syntaxHighlight(); } - }; -}).call(window); + } +}; diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js index 76a821c7a17..77ae6109bc6 100644 --- a/app/assets/javascripts/tree.js +++ b/app/assets/javascripts/tree.js @@ -1,68 +1,66 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, max-len */ -(function() { - this.TreeView = (function() { - function TreeView() { - this.initKeyNav(); - // Code browser tree slider - // Make the entire tree-item row clickable, but not if clicking another link (like a commit message) - $(".tree-content-holder .tree-item").on('click', function(e) { - var $clickedEl, path; - $clickedEl = $(e.target); - path = $('.tree-item-file-name a', this).attr('href'); - if (!$clickedEl.is('a') && !$clickedEl.is('.str-truncated')) { - if (e.metaKey || e.which === 2) { - e.preventDefault(); - return window.open(path, '_blank'); - } else { - return gl.utils.visitUrl(path); - } +window.TreeView = (function() { + function TreeView() { + this.initKeyNav(); + // Code browser tree slider + // Make the entire tree-item row clickable, but not if clicking another link (like a commit message) + $(".tree-content-holder .tree-item").on('click', function(e) { + var $clickedEl, path; + $clickedEl = $(e.target); + path = $('.tree-item-file-name a', this).attr('href'); + if (!$clickedEl.is('a') && !$clickedEl.is('.str-truncated')) { + if (e.metaKey || e.which === 2) { + e.preventDefault(); + return window.open(path, '_blank'); + } else { + return gl.utils.visitUrl(path); } - }); - // Show the "Loading commit data" for only the first element - $('span.log_loading:first').removeClass('hide'); - } + } + }); + // Show the "Loading commit data" for only the first element + $('span.log_loading:first').removeClass('hide'); + } - TreeView.prototype.initKeyNav = function() { - var li, liSelected; - li = $("tr.tree-item"); - liSelected = null; - return $('body').keydown(function(e) { - var next, path; - if ($("input:focus").length > 0 && (e.which === 38 || e.which === 40)) { - return false; - } - if (e.which === 40) { - if (liSelected) { - next = liSelected.next(); - if (next.length > 0) { - liSelected.removeClass("selected"); - liSelected = next.addClass("selected"); - } - } else { - liSelected = li.eq(0).addClass("selected"); - } - return $(liSelected).focus(); - } else if (e.which === 38) { - if (liSelected) { - next = liSelected.prev(); - if (next.length > 0) { - liSelected.removeClass("selected"); - liSelected = next.addClass("selected"); - } - } else { - liSelected = li.last().addClass("selected"); + TreeView.prototype.initKeyNav = function() { + var li, liSelected; + li = $("tr.tree-item"); + liSelected = null; + return $('body').keydown(function(e) { + var next, path; + if ($("input:focus").length > 0 && (e.which === 38 || e.which === 40)) { + return false; + } + if (e.which === 40) { + if (liSelected) { + next = liSelected.next(); + if (next.length > 0) { + liSelected.removeClass("selected"); + liSelected = next.addClass("selected"); } - return $(liSelected).focus(); - } else if (e.which === 13) { - path = $('.tree-item.selected .tree-item-file-name a').attr('href'); - if (path) { - return gl.utils.visitUrl(path); + } else { + liSelected = li.eq(0).addClass("selected"); + } + return $(liSelected).focus(); + } else if (e.which === 38) { + if (liSelected) { + next = liSelected.prev(); + if (next.length > 0) { + liSelected.removeClass("selected"); + liSelected = next.addClass("selected"); } + } else { + liSelected = li.last().addClass("selected"); + } + return $(liSelected).focus(); + } else if (e.which === 13) { + path = $('.tree-item.selected .tree-item-file-name a').attr('href'); + if (path) { + return gl.utils.visitUrl(path); } - }); - }; + } + }); + }; - return TreeView; - })(); -}).call(window); + return TreeView; +})(); diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/user.js index 19c9efe7fbd..3ab9ef5408e 100644 --- a/app/assets/javascripts/user.js +++ b/app/assets/javascripts/user.js @@ -2,34 +2,35 @@ import Cookies from 'js-cookie'; -((global) => { - global.User = class { - constructor({ action }) { - this.action = action; - this.placeProfileAvatarsToTop(); - this.initTabs(); - this.hideProjectLimitMessage(); - } +class User { + constructor({ action }) { + this.action = action; + this.placeProfileAvatarsToTop(); + this.initTabs(); + this.hideProjectLimitMessage(); + } - placeProfileAvatarsToTop() { - $('.profile-groups-avatars').tooltip({ - placement: 'top' - }); - } + placeProfileAvatarsToTop() { + $('.profile-groups-avatars').tooltip({ + placement: 'top' + }); + } - initTabs() { - return new global.UserTabs({ - parentEl: '.user-profile', - action: this.action - }); - } + initTabs() { + return new window.gl.UserTabs({ + parentEl: '.user-profile', + action: this.action + }); + } - hideProjectLimitMessage() { - $('.hide-project-limit-message').on('click', e => { - e.preventDefault(); - Cookies.set('hide_project_limit_message', 'false'); - $(this).parents('.project-limit-message').remove(); - }); - } - }; -})(window.gl || (window.gl = {})); + hideProjectLimitMessage() { + $('.hide-project-limit-message').on('click', e => { + e.preventDefault(); + Cookies.set('hide_project_limit_message', 'false'); + $(this).parents('.project-limit-message').remove(); + }); + } +} + +window.gl = window.gl || {}; +window.gl.User = User; diff --git a/app/assets/javascripts/user_tabs.js b/app/assets/javascripts/user_tabs.js index ce7eb76dc71..be70f4cb4e2 100644 --- a/app/assets/javascripts/user_tabs.js +++ b/app/assets/javascripts/user_tabs.js @@ -59,117 +59,118 @@ content on the Users#show page. </div> </div> */ -((global) => { - class UserTabs { - constructor ({ defaultAction, action, parentEl }) { - this.loaded = {}; - this.defaultAction = defaultAction || 'activity'; - this.action = action || this.defaultAction; - this.$parentEl = $(parentEl) || $(document); - this._location = window.location; - this.$parentEl.find('.nav-links a') - .each((i, navLink) => { - this.loaded[$(navLink).attr('data-action')] = false; - }); - this.actions = Object.keys(this.loaded); - this.bindEvents(); - - if (this.action === 'show') { - this.action = this.defaultAction; - } - this.activateTab(this.action); +class UserTabs { + constructor ({ defaultAction, action, parentEl }) { + this.loaded = {}; + this.defaultAction = defaultAction || 'activity'; + this.action = action || this.defaultAction; + this.$parentEl = $(parentEl) || $(document); + this._location = window.location; + this.$parentEl.find('.nav-links a') + .each((i, navLink) => { + this.loaded[$(navLink).attr('data-action')] = false; + }); + this.actions = Object.keys(this.loaded); + this.bindEvents(); + + if (this.action === 'show') { + this.action = this.defaultAction; } - bindEvents() { - this.changeProjectsPageWrapper = this.changeProjectsPage.bind(this); + this.activateTab(this.action); + } - this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]') - .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event)); + bindEvents() { + this.changeProjectsPageWrapper = this.changeProjectsPage.bind(this); - this.$parentEl.on('click', '.gl-pagination a', this.changeProjectsPageWrapper); - } + this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]') + .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event)); - changeProjectsPage(e) { - e.preventDefault(); + this.$parentEl.on('click', '.gl-pagination a', this.changeProjectsPageWrapper); + } - $('.tab-pane.active').empty(); - const endpoint = $(e.target).attr('href'); - this.loadTab(this.getCurrentAction(), endpoint); - } + changeProjectsPage(e) { + e.preventDefault(); - tabShown(event) { - const $target = $(event.target); - const action = $target.data('action'); - const source = $target.attr('href'); - const endpoint = $target.data('endpoint'); - this.setTab(action, endpoint); - return this.setCurrentAction(source); - } + $('.tab-pane.active').empty(); + const endpoint = $(e.target).attr('href'); + this.loadTab(this.getCurrentAction(), endpoint); + } - activateTab(action) { - return this.$parentEl.find(`.nav-links .js-${action}-tab a`) - .tab('show'); - } + tabShown(event) { + const $target = $(event.target); + const action = $target.data('action'); + const source = $target.attr('href'); + const endpoint = $target.data('endpoint'); + this.setTab(action, endpoint); + return this.setCurrentAction(source); + } - setTab(action, endpoint) { - if (this.loaded[action]) { - return; - } - if (action === 'activity') { - this.loadActivities(); - } + activateTab(action) { + return this.$parentEl.find(`.nav-links .js-${action}-tab a`) + .tab('show'); + } - const loadableActions = ['groups', 'contributed', 'projects', 'snippets']; - if (loadableActions.indexOf(action) > -1) { - return this.loadTab(action, endpoint); - } + setTab(action, endpoint) { + if (this.loaded[action]) { + return; + } + if (action === 'activity') { + this.loadActivities(); } - loadTab(action, endpoint) { - return $.ajax({ - beforeSend: () => this.toggleLoading(true), - complete: () => this.toggleLoading(false), - dataType: 'json', - type: 'GET', - url: endpoint, - success: (data) => { - const tabSelector = `div#${action}`; - this.$parentEl.find(tabSelector).html(data.html); - this.loaded[action] = true; - return gl.utils.localTimeAgo($('.js-timeago', tabSelector)); - } - }); + const loadableActions = ['groups', 'contributed', 'projects', 'snippets']; + if (loadableActions.indexOf(action) > -1) { + return this.loadTab(action, endpoint); } + } - loadActivities() { - if (this.loaded['activity']) { - return; + loadTab(action, endpoint) { + return $.ajax({ + beforeSend: () => this.toggleLoading(true), + complete: () => this.toggleLoading(false), + dataType: 'json', + type: 'GET', + url: endpoint, + success: (data) => { + const tabSelector = `div#${action}`; + this.$parentEl.find(tabSelector).html(data.html); + this.loaded[action] = true; + return gl.utils.localTimeAgo($('.js-timeago', tabSelector)); } - const $calendarWrap = this.$parentEl.find('.user-calendar'); - $calendarWrap.load($calendarWrap.data('href')); - new gl.Activities(); - return this.loaded['activity'] = true; - } + }); + } - toggleLoading(status) { - return this.$parentEl.find('.loading-status .loading') - .toggle(status); + loadActivities() { + if (this.loaded['activity']) { + return; } + const $calendarWrap = this.$parentEl.find('.user-calendar'); + $calendarWrap.load($calendarWrap.data('href')); + new gl.Activities(); + return this.loaded['activity'] = true; + } - setCurrentAction(source) { - let new_state = source; - new_state = new_state.replace(/\/+$/, ''); - new_state += this._location.search + this._location.hash; - history.replaceState({ - url: new_state - }, document.title, new_state); - return new_state; - } + toggleLoading(status) { + return this.$parentEl.find('.loading-status .loading') + .toggle(status); + } - getCurrentAction() { - return this.$parentEl.find('.nav-links .active a').data('action'); - } + setCurrentAction(source) { + let new_state = source; + new_state = new_state.replace(/\/+$/, ''); + new_state += this._location.search + this._location.hash; + history.replaceState({ + url: new_state + }, document.title, new_state); + return new_state; } - global.UserTabs = UserTabs; -})(window.gl || (window.gl = {})); + + getCurrentAction() { + return this.$parentEl.find('.nav-links .active a').data('action'); + } +} + +window.gl = window.gl || {}; +window.gl.UserTabs = UserTabs; diff --git a/app/assets/javascripts/username_validator.js b/app/assets/javascripts/username_validator.js index 137cefa3b8e..abe6c30f4f3 100644 --- a/app/assets/javascripts/username_validator.js +++ b/app/assets/javascripts/username_validator.js @@ -1,135 +1,133 @@ /* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */ -((global) => { - const debounceTimeoutDuration = 1000; - const invalidInputClass = 'gl-field-error-outline'; - const successInputClass = 'gl-field-success-outline'; - const unavailableMessageSelector = '.username .validation-error'; - const successMessageSelector = '.username .validation-success'; - const pendingMessageSelector = '.username .validation-pending'; - const invalidMessageSelector = '.username .gl-field-error'; - - class UsernameValidator { - constructor() { - this.inputElement = $('#new_user_username'); - this.inputDomElement = this.inputElement.get(0); - this.state = { - available: false, - valid: false, - pending: false, - empty: true - }; - - const debounceTimeout = _.debounce((username) => { - this.validateUsername(username); - }, debounceTimeoutDuration); - - this.inputElement.on('keyup.username_check', () => { - const username = this.inputElement.val(); - - this.state.valid = this.inputDomElement.validity.valid; - this.state.empty = !username.length; - - if (this.state.valid) { - return debounceTimeout(username); - } - - this.renderState(); - }); - - // Override generic field validation - this.inputElement.on('invalid', this.interceptInvalid.bind(this)); - } +const debounceTimeoutDuration = 1000; +const invalidInputClass = 'gl-field-error-outline'; +const successInputClass = 'gl-field-success-outline'; +const unavailableMessageSelector = '.username .validation-error'; +const successMessageSelector = '.username .validation-success'; +const pendingMessageSelector = '.username .validation-pending'; +const invalidMessageSelector = '.username .gl-field-error'; + +class UsernameValidator { + constructor() { + this.inputElement = $('#new_user_username'); + this.inputDomElement = this.inputElement.get(0); + this.state = { + available: false, + valid: false, + pending: false, + empty: true + }; + + const debounceTimeout = _.debounce((username) => { + this.validateUsername(username); + }, debounceTimeoutDuration); + + this.inputElement.on('keyup.username_check', () => { + const username = this.inputElement.val(); + + this.state.valid = this.inputDomElement.validity.valid; + this.state.empty = !username.length; - renderState() { - // Clear all state - this.clearFieldValidationState(); - - if (this.state.valid && this.state.available) { - return this.setSuccessState(); + if (this.state.valid) { + return debounceTimeout(username); } - if (this.state.empty) { - return this.clearFieldValidationState(); - } + this.renderState(); + }); - if (this.state.pending) { - return this.setPendingState(); - } + // Override generic field validation + this.inputElement.on('invalid', this.interceptInvalid.bind(this)); + } - if (!this.state.available) { - return this.setUnavailableState(); - } + renderState() { + // Clear all state + this.clearFieldValidationState(); - if (!this.state.valid) { - return this.setInvalidState(); - } + if (this.state.valid && this.state.available) { + return this.setSuccessState(); } - interceptInvalid(event) { - event.preventDefault(); - event.stopPropagation(); + if (this.state.empty) { + return this.clearFieldValidationState(); } - validateUsername(username) { - if (this.state.valid) { - this.state.pending = true; - this.state.available = false; - this.renderState(); - return $.ajax({ - type: 'GET', - url: `${gon.relative_url_root}/users/${username}/exists`, - dataType: 'json', - success: (res) => this.setAvailabilityState(res.exists) - }); - } + if (this.state.pending) { + return this.setPendingState(); } - setAvailabilityState(usernameTaken) { - if (usernameTaken) { - this.state.valid = false; - this.state.available = false; - } else { - this.state.available = true; - } - this.state.pending = false; - this.renderState(); + if (!this.state.available) { + return this.setUnavailableState(); } - clearFieldValidationState() { - this.inputElement.siblings('p').hide(); - - this.inputElement.removeClass(invalidInputClass) - .removeClass(successInputClass); + if (!this.state.valid) { + return this.setInvalidState(); } + } - setUnavailableState() { - const $usernameUnavailableMessage = this.inputElement.siblings(unavailableMessageSelector); - this.inputElement.addClass(invalidInputClass).removeClass(successInputClass); - $usernameUnavailableMessage.show(); - } + interceptInvalid(event) { + event.preventDefault(); + event.stopPropagation(); + } - setSuccessState() { - const $usernameSuccessMessage = this.inputElement.siblings(successMessageSelector); - this.inputElement.addClass(successInputClass).removeClass(invalidInputClass); - $usernameSuccessMessage.show(); + validateUsername(username) { + if (this.state.valid) { + this.state.pending = true; + this.state.available = false; + this.renderState(); + return $.ajax({ + type: 'GET', + url: `${gon.relative_url_root}/users/${username}/exists`, + dataType: 'json', + success: (res) => this.setAvailabilityState(res.exists) + }); } + } - setPendingState() { - const $usernamePendingMessage = $(pendingMessageSelector); - if (this.state.pending) { - $usernamePendingMessage.show(); - } else { - $usernamePendingMessage.hide(); - } + setAvailabilityState(usernameTaken) { + if (usernameTaken) { + this.state.valid = false; + this.state.available = false; + } else { + this.state.available = true; } + this.state.pending = false; + this.renderState(); + } + + clearFieldValidationState() { + this.inputElement.siblings('p').hide(); - setInvalidState() { - const $inputErrorMessage = $(invalidMessageSelector); - this.inputElement.addClass(invalidInputClass).removeClass(successInputClass); - $inputErrorMessage.show(); + this.inputElement.removeClass(invalidInputClass) + .removeClass(successInputClass); + } + + setUnavailableState() { + const $usernameUnavailableMessage = this.inputElement.siblings(unavailableMessageSelector); + this.inputElement.addClass(invalidInputClass).removeClass(successInputClass); + $usernameUnavailableMessage.show(); + } + + setSuccessState() { + const $usernameSuccessMessage = this.inputElement.siblings(successMessageSelector); + this.inputElement.addClass(successInputClass).removeClass(invalidInputClass); + $usernameSuccessMessage.show(); + } + + setPendingState() { + const $usernamePendingMessage = $(pendingMessageSelector); + if (this.state.pending) { + $usernamePendingMessage.show(); + } else { + $usernamePendingMessage.hide(); } } - global.UsernameValidator = UsernameValidator; -})(window); + setInvalidState() { + const $inputErrorMessage = $(invalidMessageSelector); + this.inputElement.addClass(invalidInputClass).removeClass(successInputClass); + $inputErrorMessage.show(); + } +} + +window.UsernameValidator = UsernameValidator; diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index 46efdcf4202..5728afb4c59 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -206,8 +206,6 @@ function UsersSelect(currentUser, els) { return $dropdown.glDropdown({ showMenuAbove: showMenuAbove, data: function(term, callback) { - var isAuthorFilter; - isAuthorFilter = $('.js-author-search'); return _this.users(term, options, function(users) { // GitLabDropdownFilter returns this.instance // GitLabDropdownRemote returns this.options.instance diff --git a/app/assets/javascripts/visibility_select.js b/app/assets/javascripts/visibility_select.js index f712d7ba930..b6bbbaa0936 100644 --- a/app/assets/javascripts/visibility_select.js +++ b/app/assets/javascripts/visibility_select.js @@ -1,27 +1,24 @@ -(() => { - const gl = window.gl || (window.gl = {}); - - class VisibilitySelect { - constructor(container) { - if (!container) throw new Error('VisibilitySelect requires a container element as argument 1'); - this.container = container; - this.helpBlock = this.container.querySelector('.help-block'); - this.select = this.container.querySelector('select'); - } +class VisibilitySelect { + constructor(container) { + if (!container) throw new Error('VisibilitySelect requires a container element as argument 1'); + this.container = container; + this.helpBlock = this.container.querySelector('.help-block'); + this.select = this.container.querySelector('select'); + } - init() { - if (this.select) { - this.updateHelpText(); - this.select.addEventListener('change', this.updateHelpText.bind(this)); - } else { - this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock; - } + init() { + if (this.select) { + this.updateHelpText(); + this.select.addEventListener('change', this.updateHelpText.bind(this)); + } else { + this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock; } + } - updateHelpText() { - this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description; - } + updateHelpText() { + this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description; } +} - gl.VisibilitySelect = VisibilitySelect; -})(); +window.gl = window.gl || {}; +window.gl.VisibilitySelect = VisibilitySelect; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js index e8b3cf2f729..c02e10128e2 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js @@ -17,9 +17,6 @@ export default { return hasCI && !ciStatus; }, - hasPipeline() { - return Object.keys(this.mr.pipeline || {}).length > 0; - }, svg() { return statusIconEntityMap.icon_status_failed; }, @@ -33,11 +30,7 @@ export default { template: ` <div class="mr-widget-heading"> <div class="ci-widget"> - <template v-if="!hasPipeline"> - <i class="fa fa-spinner fa-spin append-right-10" aria-hidden="true"></i> - Waiting for pipeline... - </template> - <template v-else-if="hasCIError"> + <template v-if="hasCIError"> <div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error"> <span class="js-icon-link icon-link"> <span diff --git a/app/assets/javascripts/webpack.js b/app/assets/javascripts/webpack.js new file mode 100644 index 00000000000..9a9cf395fb8 --- /dev/null +++ b/app/assets/javascripts/webpack.js @@ -0,0 +1,9 @@ +/** + * This is the first script loaded by webpack's runtime. It is used to manually configure + * config.output.publicPath to account for relative_url_root or CDN settings which cannot be + * baked-in to our webpack bundles. + */ + +if (gon && gon.webpack_public_path) { + __webpack_public_path__ = gon.webpack_public_path; // eslint-disable-line +} diff --git a/app/assets/javascripts/wikis.js b/app/assets/javascripts/wikis.js index 4194c1bc08d..03d183ebd84 100644 --- a/app/assets/javascripts/wikis.js +++ b/app/assets/javascripts/wikis.js @@ -4,66 +4,65 @@ import 'vendor/jquery.nicescroll'; import './breakpoints'; -((global) => { - class Wikis { - constructor() { - this.bp = Breakpoints.get(); - this.sidebarEl = document.querySelector('.js-wiki-sidebar'); - this.sidebarExpanded = false; - $(this.sidebarEl).niceScroll(); +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)); - } + 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)); + } - window.addEventListener('resize', () => this.renderSidebar()); - this.renderSidebar(); + this.newWikiForm = document.querySelector('form.new-wiki-page'); + if (this.newWikiForm) { + this.newWikiForm.addEventListener('submit', e => this.handleNewWikiSubmit(e)); } - handleNewWikiSubmit(e) { - if (!this.newWikiForm) return; + window.addEventListener('resize', () => this.renderSidebar()); + this.renderSidebar(); + } - const slugInput = this.newWikiForm.querySelector('#new_wiki_path'); - const slug = gl.text.slugify(slugInput.value); + handleNewWikiSubmit(e) { + if (!this.newWikiForm) return; - if (slug.length > 0) { - const wikisPath = slugInput.getAttribute('data-wikis-path'); - window.location.href = `${wikisPath}/${slug}`; - e.preventDefault(); - } - } + const slugInput = this.newWikiForm.querySelector('#new_wiki_path'); + const slug = gl.text.slugify(slugInput.value); - handleToggleSidebar(e) { + if (slug.length > 0) { + const wikisPath = slugInput.getAttribute('data-wikis-path'); + window.location.href = `${wikisPath}/${slug}`; e.preventDefault(); - this.sidebarExpanded = !this.sidebarExpanded; - this.renderSidebar(); } + } - sidebarCanCollapse() { - const bootstrapBreakpoint = this.bp.getBreakpointSize(); - return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm'; - } + 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'); + 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 = {})); +window.gl = window.gl || {}; +window.gl.Wikis = Wikis; diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js index b7fe552dec2..08f80735e93 100644 --- a/app/assets/javascripts/zen_mode.js +++ b/app/assets/javascripts/zen_mode.js @@ -34,65 +34,64 @@ window.Dropzone = Dropzone; // **Cancelable** No // **Target** a.js-zen-leave // -(function() { - this.ZenMode = (function() { - function ZenMode() { - this.active_backdrop = null; - this.active_textarea = null; - $(document).on('click', '.js-zen-enter', function(e) { - e.preventDefault(); - return $(e.currentTarget).trigger('zen_mode:enter'); - }); - $(document).on('click', '.js-zen-leave', function(e) { + +window.ZenMode = (function() { + function ZenMode() { + this.active_backdrop = null; + this.active_textarea = null; + $(document).on('click', '.js-zen-enter', function(e) { + e.preventDefault(); + return $(e.currentTarget).trigger('zen_mode:enter'); + }); + $(document).on('click', '.js-zen-leave', function(e) { + e.preventDefault(); + return $(e.currentTarget).trigger('zen_mode:leave'); + }); + $(document).on('zen_mode:enter', (function(_this) { + return function(e) { + return _this.enter($(e.target).closest('.md-area').find('.zen-backdrop')); + }; + })(this)); + $(document).on('zen_mode:leave', (function(_this) { + return function(e) { + return _this.exit(); + }; + })(this)); + $(document).on('keydown', function(e) { + // Esc + if (e.keyCode === 27) { e.preventDefault(); - return $(e.currentTarget).trigger('zen_mode:leave'); - }); - $(document).on('zen_mode:enter', (function(_this) { - return function(e) { - return _this.enter($(e.target).closest('.md-area').find('.zen-backdrop')); - }; - })(this)); - $(document).on('zen_mode:leave', (function(_this) { - return function(e) { - return _this.exit(); - }; - })(this)); - $(document).on('keydown', function(e) { - // Esc - if (e.keyCode === 27) { - e.preventDefault(); - return $(document).trigger('zen_mode:leave'); - } - }); - } + return $(document).trigger('zen_mode:leave'); + } + }); + } - ZenMode.prototype.enter = function(backdrop) { - Mousetrap.pause(); - this.active_backdrop = $(backdrop); - this.active_backdrop.addClass('fullscreen'); - this.active_textarea = this.active_backdrop.find('textarea'); - // Prevent a user-resized textarea from persisting to fullscreen - this.active_textarea.removeAttr('style'); - return this.active_textarea.focus(); - }; + ZenMode.prototype.enter = function(backdrop) { + Mousetrap.pause(); + this.active_backdrop = $(backdrop); + this.active_backdrop.addClass('fullscreen'); + this.active_textarea = this.active_backdrop.find('textarea'); + // Prevent a user-resized textarea from persisting to fullscreen + this.active_textarea.removeAttr('style'); + return this.active_textarea.focus(); + }; - ZenMode.prototype.exit = function() { - if (this.active_textarea) { - Mousetrap.unpause(); - this.active_textarea.closest('.zen-backdrop').removeClass('fullscreen'); - this.scrollTo(this.active_textarea); - this.active_textarea = null; - this.active_backdrop = null; - return Dropzone.forElement('.div-dropzone').enable(); - } - }; + ZenMode.prototype.exit = function() { + if (this.active_textarea) { + Mousetrap.unpause(); + this.active_textarea.closest('.zen-backdrop').removeClass('fullscreen'); + this.scrollTo(this.active_textarea); + this.active_textarea = null; + this.active_backdrop = null; + return Dropzone.forElement('.div-dropzone').enable(); + } + }; - ZenMode.prototype.scrollTo = function(zen_area) { - return $.scrollTo(zen_area, 0, { - offset: -150 - }); - }; + ZenMode.prototype.scrollTo = function(zen_area) { + return $.scrollTo(zen_area, 0, { + offset: -150 + }); + }; - return ZenMode; - })(); -}).call(window); + return ZenMode; +})(); diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 245117b2559..c7c2684d548 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -17,6 +17,8 @@ max-width: $limited-layout-width-sm; margin-left: auto; margin-right: auto; + padding-top: 64px; + padding-bottom: 64px; } } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 5cf9330b8f8..542b641e3dd 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -92,7 +92,7 @@ @mixin maintain-sidebar-dimensions { display: block; width: $gutter-width; - padding: 10px 20px; + padding: 10px 0; } .issues-bulk-update.right-sidebar { diff --git a/app/assets/stylesheets/framework/wells.scss b/app/assets/stylesheets/framework/wells.scss index 1c1392f8f67..b1ff2659131 100644 --- a/app/assets/stylesheets/framework/wells.scss +++ b/app/assets/stylesheets/framework/wells.scss @@ -3,6 +3,7 @@ color: $gl-text-color; border: 1px solid $border-color; border-radius: $border-radius-default; + margin-bottom: $gl-padding; .well-segment { padding: $gl-padding; @@ -21,6 +22,11 @@ font-size: 12px; } } + + &.admin-well h4 { + border-bottom: 1px solid $border-color; + padding-bottom: 8px; + } } .icon-container { @@ -53,6 +59,14 @@ padding: 15px; } +.dark-well { + background-color: $gray-normal; + + .btn { + width: 100%; + } +} + .well-centered { h1 { font-weight: normal; diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss index 441bfc479f6..bfb7a0c7e25 100644 --- a/app/assets/stylesheets/new_nav.scss +++ b/app/assets/stylesheets/new_nav.scss @@ -11,20 +11,19 @@ header.navbar-gitlab-new { padding-left: 0; .title-container { + align-items: stretch; padding-top: 0; overflow: visible; } .title { - display: block; - height: 100%; + display: flex; padding-right: 0; color: currentColor; > a { display: flex; align-items: center; - height: 100%; padding-top: 3px; padding-right: $gl-padding; padding-left: $gl-padding; @@ -265,3 +264,127 @@ header.navbar-gitlab-new { } } } + +.breadcrumbs { + display: flex; + min-height: 60px; + padding-top: $gl-padding-top; + padding-bottom: $gl-padding-top; + color: $gl-text-color; + border-bottom: 1px solid $border-color; + + .dropdown-toggle-caret { + position: relative; + top: -1px; + padding: 0 5px; + color: rgba($black, .65); + font-size: 10px; + line-height: 1; + background: none; + border: 0; + + &:focus { + outline: 0; + } + } +} + +.breadcrumbs-container { + display: flex; + width: 100%; + position: relative; + + .dropdown-menu-projects { + margin-top: -$gl-padding; + margin-left: $gl-padding; + } +} + +.breadcrumbs-links { + flex: 1; + align-self: center; + color: $black-transparent; + + a { + color: rgba($black, .65); + + &:not(:first-child), + &.group-path { + margin-left: 4px; + } + + &:not(:last-of-type), + &.group-path { + margin-right: 3px; + } + } + + .title { + white-space: nowrap; + + > a { + &:last-of-type { + font-weight: 600; + } + } + } + + .avatar-tile { + margin-right: 5px; + border: 1px solid $border-color; + border-radius: 50%; + vertical-align: sub; + + &.identicon { + float: left; + width: 16px; + height: 16px; + margin-top: 2px; + font-size: 10px; + } + } + + .text-expander { + margin-left: 4px; + margin-right: 4px; + + > i { + position: relative; + top: 1px; + } + } +} + +.breadcrumbs-extra { + flex: 0 0 auto; + margin-left: auto; +} + +.breadcrumbs-sub-title { + margin: 2px 0 0; + font-size: 16px; + font-weight: normal; + + ul { + margin: 0; + } + + li { + display: inline-block; + + &:not(:last-child) { + &::after { + content: "/"; + margin: 0 2px 0 5px; + } + } + + &:last-child a { + font-weight: 600; + } + } + + a { + color: $gl-text-color; + } +} diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index be4cc02b3ea..17f23f7fce3 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -49,6 +49,7 @@ $new-sidebar-width: 220px; position: fixed; z-index: 400; width: $new-sidebar-width; + transition: width $sidebar-transition-duration; top: 50px; bottom: 0; left: 0; @@ -62,6 +63,8 @@ $new-sidebar-width: 220px; } li { + white-space: nowrap; + a { display: block; padding: 12px 14px; @@ -72,6 +75,10 @@ $new-sidebar-width: 220px; color: $gl-text-color; text-decoration: none; } + + @media (max-width: $screen-xs-max) { + width: 0; + } } .sidebar-sub-level-items { diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 9cff99b839c..23c06eca3c3 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -37,65 +37,77 @@ } .build-page { - .sticky { - position: absolute; - left: 0; - right: 0; + .build-trace-container { + position: relative; } - .build-trace-container { - position: absolute; - top: 225px; - left: 15px; - bottom: 10px; + .build-trace { background: $black; color: $gray-darkest; - font-family: $monospace_font; + white-space: pre; + overflow-x: auto; font-size: 12px; + border-radius: 0; + border: none; - &.sidebar-expanded { - right: 305px; + .bash { + display: block; } + } - &.sidebar-collapsed { - right: 16px; + .top-bar { + height: 35px; + display: flex; + justify-content: flex-end; + background: $gray-light; + border: 1px solid $border-color; + color: $gl-text-color; + position: sticky; + position: -webkit-sticky; + top: 50px; + + &.affix { + top: 50px; } - code { - background: $black; - color: $gray-darkest; + // with sidebar + &.affix.sidebar-expanded { + right: 306px; + left: 16px; } - .top-bar { - top: 0; - height: 35px; - display: flex; - justify-content: flex-end; - background: $gray-light; - border: 1px solid $border-color; - color: $gl-text-color; + // without sidebar + &.affix.sidebar-collapsed { + right: 16px; + left: 16px; + } - .truncated-info { - margin: 0 auto; - align-self: center; + &.affix-top { + position: absolute; + right: 0; + left: 0; + } - .truncated-info-size { - margin: 0 5px; - } + .truncated-info { + margin: 0 auto; + align-self: center; - .raw-link { - color: $gl-text-color; - margin-left: 5px; - text-decoration: underline; - } + .truncated-info-size { + margin: 0 5px; + } + + .raw-link { + color: $gl-text-color; + margin-left: 5px; + text-decoration: underline; } } .controllers { display: flex; - align-self: center; font-size: 15px; - margin-bottom: 4px; + justify-content: center; + align-items: center; svg { height: 15px; @@ -103,17 +115,9 @@ fill: $gl-text-color; } - .controllers-buttons, - .btn-scroll { - color: $gl-text-color; - height: 15px; - vertical-align: middle; - padding: 0; - width: 12px; - } - .controllers-buttons { - margin: 1px 10px; + color: $gl-text-color; + margin: 0 10px; } .btn-scroll.animate { @@ -143,15 +147,6 @@ } } - .bash { - top: 35px; - left: 10px; - bottom: 0; - padding: 10px 20px 20px 5px; - white-space: pre-wrap; - overflow: auto; - } - .environment-information { border: 1px solid $border-color; padding: 8px $gl-padding 12px; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index b58922626fa..55011e8a21b 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -20,8 +20,6 @@ } .diff-content { - overflow: auto; - overflow-y: hidden; background: $white-light; color: $gl-text-color; border-radius: 0 0 3px 3px; @@ -476,6 +474,7 @@ height: 19px; width: 19px; margin-left: -15px; + z-index: 100; &:hover { .diff-comment-avatar, @@ -491,7 +490,7 @@ transform: translateX((($i * $x-pos) - $x-pos)); &:hover { - transform: translateX((($i * $x-pos) - $x-pos)) scale(1.2); + transform: translateX((($i * $x-pos) - $x-pos)); } } } @@ -542,6 +541,7 @@ height: 19px; padding: 0; transition: transform .1s ease-out; + z-index: 100; svg { position: absolute; @@ -555,10 +555,6 @@ fill: $white-light; } - &:hover { - transform: scale(1.2); - } - &:focus { outline: 0; } diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index a2be957655f..00ebf4e26ac 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -187,8 +187,7 @@ } .text-metric { - font-weight: 600; - font-size: 14px; + font-size: 12px; } .selected-metric-line { @@ -232,10 +231,6 @@ width: 100%; padding: 0; padding-bottom: 100%; - - .text-metric-bold { - font-weight: 600; - } } .prometheus-svg-container > svg { @@ -250,11 +245,15 @@ stroke-width: 0; } + .text-metric-bold { + font-weight: 600; + } + .label-axis-text, .text-metric-usage { fill: $black; font-weight: 500; - font-size: 14px; + font-size: 12px; } .legend-axis-text { @@ -262,7 +261,20 @@ } .tick > text { - font-size: 14px; + font-size: 12px; + } + + .text-metric-title { + font-size: 12px; + } + + .y-label-text, + .x-label-text { + fill: $gray-darkest; + } + + .axis-tick { + stroke: $gray-darker; } @media (max-width: $screen-sm-max) { @@ -277,3 +289,9 @@ } } } + +.prometheus-row { + h5 { + font-size: 16px; + } +} diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index e3ebcc8af6c..47f50083726 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -200,7 +200,6 @@ right: 0; transition: width .3s; background: $gray-light; - padding: 0 20px; z-index: 200; overflow: hidden; @@ -224,6 +223,10 @@ } } + .issuable-sidebar { + padding: 0 20px; + } + .issuable-sidebar-header { padding-top: 10px; } @@ -597,7 +600,38 @@ .issue-info-container { -webkit-flex: 1; flex: 1; + display: flex; padding-right: $gl-padding; + + .issue-main-info { + flex: 1 auto; + margin-right: 10px; + } + + .issuable-meta { + display: flex; + flex-direction: column; + align-items: flex-end; + flex: 1 0 auto; + + .controls { + margin-bottom: 2px; + line-height: 20px; + padding: 0; + } + + .issue-updated-at { + line-height: 20px; + } + } + + @media(max-width: $screen-xs-max) { + .issuable-meta { + .controls li { + margin-right: 0; + } + } + } } .issue-check { @@ -609,6 +643,30 @@ vertical-align: text-top; } } + + .issuable-milestone, + .issuable-info, + .task-status, + .issuable-updated-at { + font-weight: normal; + color: $gl-text-color-secondary; + + a { + color: $gl-text-color; + + .fa { + color: $gl-text-color-secondary; + } + } + } + + @media(max-width: $screen-md-max) { + .task-status, + .issuable-due-date, + .project-ref-path { + display: none; + } + } } } diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index b158416b940..ee48f7a3626 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -279,5 +279,9 @@ .label-link { display: inline-block; - vertical-align: text-top; + vertical-align: top; + + .label { + vertical-align: inherit; + } } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 53d5cf2f7bc..303425041df 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -628,8 +628,14 @@ ul.notes { * Line note button on the side of diffs */ +.line_holder .is-over:not(.no-comment-btn) { + .add-diff-note { + opacity: 1; + } +} + .add-diff-note { - display: none; + opacity: 0; margin-top: -2px; border-radius: 50%; background: $white-light; @@ -642,13 +648,11 @@ ul.notes { width: 23px; height: 23px; border: 1px solid $blue-500; - transition: transform .1s ease-in-out; &:hover { background: $blue-500; border-color: $blue-600; color: $white-light; - transform: scale(1.15); } &:active { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index ba530bf7f9b..7d7c34115f9 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -483,11 +483,12 @@ a.deploy-project-label { .project-stats { font-size: 0; text-align: center; + max-width: 100%; + border-bottom: 1px solid $border-color; .nav { padding-top: 12px; padding-bottom: 12px; - border-bottom: 1px solid $border-color; } .nav > li { diff --git a/app/assets/stylesheets/pages/runners.scss b/app/assets/stylesheets/pages/runners.scss index 9b6ff237557..57c73295d1e 100644 --- a/app/assets/stylesheets/pages/runners.scss +++ b/app/assets/stylesheets/pages/runners.scss @@ -33,3 +33,20 @@ font-weight: normal; } } + +.admin-runner-btn-group-cell { + min-width: 150px; + + .btn-sm { + padding: 4px 9px; + } + + .btn-default { + color: $gl-text-color-secondary; + } + + .fa-pause, + .fa-play { + font-size: 11px; + } +} diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 9b2ed0d68a1..dc88cf3e699 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -1,4 +1,5 @@ .tree-holder { + .nav-block { margin: 10px 0; @@ -15,6 +16,11 @@ .btn-group { margin-left: 10px; } + + .control { + float: left; + margin-left: 10px; + } } .tree-ref-holder { diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 4d4b8a8425f..f978ce478c7 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -71,6 +71,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController params[:application_setting][:disabled_oauth_sign_in_sources] = AuthHelper.button_based_providers.map(&:to_s) - Array(enabled_oauth_sign_in_sources) + + params[:application_setting][:restricted_visibility_levels]&.delete("") params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file] params.require(:application_setting).permit( diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index a1975c0e341..984d5398708 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -40,14 +40,14 @@ class Admin::ProjectsController < Admin::ApplicationController ::Projects::TransferService.new(@project, current_user, params.dup).execute(namespace) @project.reload - redirect_to admin_namespace_project_path(@project.namespace, @project) + redirect_to admin_project_path(@project) end def repository_check RepositoryCheck::SingleRepositoryWorker.perform_async(@project.id) redirect_to( - admin_namespace_project_path(@project.namespace, @project), + admin_project_path(@project), notice: 'Repository check was triggered.' ) end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 824ce845706..b4c0cd0487f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -110,6 +110,8 @@ class ApplicationController < ActionController::Base end def log_exception(exception) + Raven.capture_exception(exception) if sentry_enabled? + application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace application_trace.map!{ |t| " #{t}\n" } logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}" diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index f87db4d9e84..782f0be9c4a 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -78,8 +78,7 @@ module CreatesCommit end def new_merge_request_path - namespace_project_new_merge_request_path( - @project_to_commit_into.namespace, + project_new_merge_request_path( @project_to_commit_into, merge_request: { source_project_id: @project_to_commit_into.id, @@ -91,7 +90,7 @@ module CreatesCommit end def existing_merge_request_path - namespace_project_merge_request_path(@project.namespace, @project, @merge_request) + project_merge_request_path(@project, @merge_request) end def merge_request_exists? diff --git a/app/controllers/concerns/milestone_actions.rb b/app/controllers/concerns/milestone_actions.rb index 1ff785ac2ca..081f3336780 100644 --- a/app/controllers/concerns/milestone_actions.rb +++ b/app/controllers/concerns/milestone_actions.rb @@ -45,7 +45,7 @@ module MilestoneActions def milestone_redirect_path if @project - namespace_project_milestone_path(@project.namespace, @project, @milestone) + project_milestone_path(@project, @milestone) elsif @group group_milestone_path(@group, @milestone.safe_title, title: @milestone.title) else diff --git a/app/controllers/concerns/repository_settings_redirect.rb b/app/controllers/concerns/repository_settings_redirect.rb index 0854c73a02f..0576f0e6e70 100644 --- a/app/controllers/concerns/repository_settings_redirect.rb +++ b/app/controllers/concerns/repository_settings_redirect.rb @@ -2,6 +2,6 @@ module RepositorySettingsRedirect extend ActiveSupport::Concern def redirect_to_repository_settings(project) - redirect_to namespace_project_settings_repository_path(project.namespace, project) + redirect_to project_settings_repository_path(project) end end diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb index b68d76aeff0..ada0dde87fb 100644 --- a/app/controllers/concerns/spammable_actions.rb +++ b/app/controllers/concerns/spammable_actions.rb @@ -9,9 +9,9 @@ module SpammableActions def mark_as_spam if SpamService.new(spammable).mark_as_spam! - redirect_to spammable, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully." + redirect_to spammable_path, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully." else - redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.' + redirect_to spammable_path, alert: 'Error with Akismet. Please check the logs for more info.' end end @@ -25,7 +25,7 @@ module SpammableActions def recaptcha_check_with_fallback(&fallback) if spammable.valid? - redirect_to spammable + redirect_to spammable_path elsif render_recaptcha? ensure_spam_config_loaded! @@ -56,6 +56,10 @@ module SpammableActions raise NotImplementedError, "#{self.class} does not implement #{__method__}" end + def spammable_path + raise NotImplementedError, "#{self.class} does not implement #{__method__}" + end + def authorize_submit_spammable! access_denied! unless current_user.admin? end diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index e52fa766044..6b1d418fc9a 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -11,6 +11,9 @@ class Groups::MilestonesController < Groups::ApplicationController @milestone_states = GlobalMilestone.states_count(@projects) @milestones = Kaminari.paginate_array(milestones).page(params[:page]) end + format.json do + render json: milestones.map { |m| m.for_display.slice(:title, :name) } + end end end diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index 7625187c7be..0982a61902b 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -63,7 +63,7 @@ class InvitesController < ApplicationController when Project project = member.source label = "project #{project.name_with_namespace}" - path = namespace_project_path(project.namespace, project) + path = project_path(project) when Group group = member.source label = "group #{group.name}" diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index b82681b197e..323d5d26eb6 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -1,5 +1,6 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController include AuthenticatesWithTwoFactor + include Devise::Controllers::Rememberable protect_from_forgery except: [:kerberos, :saml, :cas3] @@ -115,8 +116,10 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController if @user.persisted? && @user.valid? log_audit_event(@user, with: oauth['provider']) if @user.two_factor_enabled? + params[:remember_me] = '1' if remember_me? prompt_for_two_factor(@user) else + remember_me(@user) if remember_me? sign_in_and_redirect(@user) end else @@ -147,4 +150,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController AuditEventService.new(user, user, options) .for_authentication.security_event end + + def remember_me? + request_params = request.env['omniauth.params'] + (request_params['remember_me'] == '1') if request_params.present? + end end diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 3d7ce4f0222..95de3a44641 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -76,13 +76,13 @@ class Projects::ApplicationController < ApplicationController def require_non_empty_project # Be sure to return status code 303 to avoid a double DELETE: # http://api.rubyonrails.org/classes/ActionController/Redirecting.html - redirect_to namespace_project_path(@project.namespace, @project), status: 303 if @project.empty_repo? + redirect_to project_path(@project), status: 303 if @project.empty_repo? end def require_branch_head unless @repository.branch_exists?(@ref) redirect_to( - namespace_project_tree_path(@project.namespace, @project, @ref), + project_tree_path(@project, @ref), notice: "This action is not allowed unless you are on a branch" ) end diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index ea036b1f705..f637a9a803b 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -46,7 +46,7 @@ class Projects::ArtifactsController < Projects::ApplicationController def keep build.keep_artifacts! - redirect_to namespace_project_job_path(project.namespace, project, build) + redirect_to project_job_path(project, build) end def latest_succeeded diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index a82d6fd5a4a..49ea2945675 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -27,9 +27,9 @@ class Projects::BlobController < Projects::ApplicationController def create create_commit(Files::CreateService, success_notice: "The file has been successfully created.", - success_path: -> { namespace_project_blob_path(@project.namespace, @project, File.join(@branch_name, @file_path)) }, + success_path: -> { project_blob_path(@project, File.join(@branch_name, @file_path)) }, failure_view: :new, - failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref)) + failure_path: project_new_blob_path(@project, @ref)) end def show @@ -63,7 +63,7 @@ class Projects::BlobController < Projects::ApplicationController @path = params[:file_path] if params[:file_path].present? create_commit(Files::UpdateService, success_path: -> { after_edit_path }, failure_view: :edit, - failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) + failure_path: project_blob_path(@project, @id)) rescue Files::UpdateService::FileChangedError @conflict = true @@ -83,9 +83,9 @@ class Projects::BlobController < Projects::ApplicationController def destroy create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.", - success_path: -> { namespace_project_tree_path(@project.namespace, @project, @branch_name) }, + success_path: -> { project_tree_path(@project, @branch_name) }, failure_view: :show, - failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) + failure_path: project_blob_path(@project, @id)) end def diff @@ -118,7 +118,7 @@ class Projects::BlobController < Projects::ApplicationController else if tree = @repository.tree(@commit.id, @path) if tree.entries.any? - return redirect_to namespace_project_tree_path(@project.namespace, @project, File.join(@ref, @path)) + return redirect_to project_tree_path(@project, File.join(@ref, @path)) end end @@ -143,10 +143,10 @@ class Projects::BlobController < Projects::ApplicationController def after_edit_path from_merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:from_merge_request_iid]) if from_merge_request && @branch_name == @ref - diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + + diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) + "##{hexdigest(@path)}" else - namespace_project_blob_path(@project.namespace, @project, File.join(@branch_name, @path)) + project_blob_path(@project, File.join(@branch_name, @path)) end end diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 94a752c21eb..86058531179 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -52,7 +52,7 @@ class Projects::BranchesController < Projects::ApplicationController redirect_to url_to_autodeploy_setup(project, branch_name), notice: view_context.autodeploy_flash_notice(branch_name) else - redirect_to namespace_project_tree_path(@project.namespace, @project, branch_name) + redirect_to project_tree_path(@project, branch_name) end else @error = result[:message] @@ -62,7 +62,7 @@ class Projects::BranchesController < Projects::ApplicationController format.json do if result[:status] == :success - render json: { name: branch_name, url: namespace_project_tree_url(@project.namespace, @project, branch_name) } + render json: { name: branch_name, url: project_tree_url(@project, branch_name) } else render json: result[:messsage], status: :unprocessable_entity end @@ -79,7 +79,7 @@ class Projects::BranchesController < Projects::ApplicationController flash_type = result[:status] == :error ? :alert : :notice flash[flash_type] = result[:message] - redirect_to namespace_project_branches_path(@project.namespace, @project), status: 303 + redirect_to project_branches_path(@project), status: 303 end format.js { render nothing: true, status: result[:return_code] } @@ -90,7 +90,7 @@ class Projects::BranchesController < Projects::ApplicationController def destroy_all_merged DeleteMergedBranchesService.new(@project, current_user).async_execute - redirect_to namespace_project_branches_path(@project.namespace, @project), + redirect_to project_branches_path(@project), notice: 'Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.' end @@ -106,8 +106,7 @@ class Projects::BranchesController < Projects::ApplicationController end def url_to_autodeploy_setup(project, branch_name) - namespace_project_new_blob_path( - project.namespace, + project_new_blob_path( project, branch_name, file_name: '.gitlab-ci.yml', diff --git a/app/controllers/projects/build_artifacts_controller.rb b/app/controllers/projects/build_artifacts_controller.rb index f34a198634e..b45e5d7ff43 100644 --- a/app/controllers/projects/build_artifacts_controller.rb +++ b/app/controllers/projects/build_artifacts_controller.rb @@ -7,23 +7,23 @@ class Projects::BuildArtifactsController < Projects::ApplicationController before_action :validate_artifacts! def download - redirect_to download_namespace_project_job_artifacts_path(project.namespace, project, job) + redirect_to download_project_job_artifacts_path(project, job) end def browse - redirect_to browse_namespace_project_job_artifacts_path(project.namespace, project, job, path: params[:path]) + redirect_to browse_project_job_artifacts_path(project, job, path: params[:path]) end def file - redirect_to file_namespace_project_job_artifacts_path(project.namespace, project, job, path: params[:path]) + redirect_to file_project_job_artifacts_path(project, job, path: params[:path]) end def raw - redirect_to raw_namespace_project_job_artifacts_path(project.namespace, project, job, path: params[:path]) + redirect_to raw_project_job_artifacts_path(project, job, path: params[:path]) end def latest_succeeded - redirect_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, job, ref_name_and_path: params[:ref_name_and_path], job: params[:job]) + redirect_to latest_succeeded_project_artifacts_path(project, job, ref_name_and_path: params[:ref_name_and_path], job: params[:job]) end private diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 1334a231788..230b072dcea 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -2,15 +2,15 @@ class Projects::BuildsController < Projects::ApplicationController before_action :authorize_read_build! def index - redirect_to namespace_project_jobs_path(project.namespace, project) + redirect_to project_jobs_path(project) end def show - redirect_to namespace_project_job_path(project.namespace, project, job) + redirect_to project_job_path(project, job) end def raw - redirect_to raw_namespace_project_job_path(project.namespace, project, job) + redirect_to raw_project_job_path(project, job) end private diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 7c3cce1c241..14a1e11a6ea 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -80,16 +80,16 @@ class Projects::CommitController < Projects::ApplicationController end def successful_change_path - referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @branch_name) + referenced_merge_request_url || project_commits_url(@project, @branch_name) end def failed_change_path - referenced_merge_request_url || namespace_project_commit_url(@project.namespace, @project, params[:id]) + referenced_merge_request_url || project_commit_url(@project, params[:id]) end def referenced_merge_request_url if merge_request = @commit.merged_merge_request(current_user) - namespace_project_merge_request_url(merge_request.target_project.namespace, merge_request.target_project, merge_request) + project_merge_request_url(merge_request.target_project, merge_request) end end diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index ef400c4d745..c8613c0d634 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -31,9 +31,9 @@ class Projects::CompareController < Projects::ApplicationController from: params[:from].presence, to: params[:to].presence } - redirect_to namespace_project_compare_index_path(@project.namespace, @project, from_to_vars) + redirect_to project_compare_index_path(@project, from_to_vars) else - redirect_to namespace_project_compare_path(@project.namespace, @project, + redirect_to project_compare_path(@project, params[:from], params[:to]) end end diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index f88a1ffd1e9..29e223a5273 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -15,6 +15,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController respond_to do |format| format.html format.json do + Gitlab::PollingInterval.set_header(response, interval: 3_000) + render json: { environments: EnvironmentSerializer .new(project: @project, current_user: @current_user) @@ -63,7 +65,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController @environment = project.environments.create(environment_params) if @environment.persisted? - redirect_to namespace_project_environment_path(project.namespace, project, @environment) + redirect_to project_environment_path(project, @environment) else render :new end @@ -71,7 +73,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController def update if @environment.update(environment_params) - redirect_to namespace_project_environment_path(project.namespace, project, @environment) + redirect_to project_environment_path(project, @environment) else render :edit end @@ -86,7 +88,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController if stop_action polymorphic_url([project.namespace.becomes(Namespace), project, stop_action]) else - namespace_project_environment_url(project.namespace, project, @environment) + project_environment_url(project, @environment) end respond_to do |format| diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb index 1eb3800e49d..3f83bef2c79 100644 --- a/app/controllers/projects/forks_controller.rb +++ b/app/controllers/projects/forks_controller.rb @@ -44,12 +44,12 @@ class Projects::ForksController < Projects::ApplicationController if @forked_project.saved? && @forked_project.forked? if @forked_project.import_in_progress? - redirect_to namespace_project_import_path(@forked_project.namespace, @forked_project, continue: continue_params) + redirect_to project_import_path(@forked_project, continue: continue_params) else if continue_params redirect_to continue_params[:to], notice: continue_params[:notice] else - redirect_to namespace_project_path(@forked_project.namespace, @forked_project), notice: "The project '#{@forked_project.name}' was successfully forked." + redirect_to project_path(@forked_project), notice: "The project '#{@forked_project.name}' was successfully forked." end end else diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index df5221fe95f..57372f9e79d 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -29,7 +29,7 @@ class Projects::GraphsController < Projects::ApplicationController end def ci - redirect_to charts_namespace_project_pipelines_path(@project.namespace, @project) + redirect_to charts_project_pipelines_path(@project) end private diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index deb33a2f0ff..8fc614b414d 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -22,7 +22,7 @@ class Projects::GroupLinksController < Projects::ApplicationController flash[:alert] = 'Please select a group.' end - redirect_to namespace_project_settings_members_path(project.namespace, project) + redirect_to project_settings_members_path(project) end def update @@ -36,7 +36,7 @@ class Projects::GroupLinksController < Projects::ApplicationController respond_to do |format| format.html do - redirect_to namespace_project_settings_members_path(project.namespace, project), status: 302 + redirect_to project_settings_members_path(project), status: 302 end format.js { head :ok } end diff --git a/app/controllers/projects/hook_logs_controller.rb b/app/controllers/projects/hook_logs_controller.rb index 354f0d6db3a..b9c4b29580a 100644 --- a/app/controllers/projects/hook_logs_controller.rb +++ b/app/controllers/projects/hook_logs_controller.rb @@ -18,7 +18,7 @@ class Projects::HookLogsController < Projects::ApplicationController set_hook_execution_notice(status, message) - redirect_to edit_namespace_project_hook_path(@project.namespace, @project, @hook) + redirect_to edit_project_hook_path(@project, @hook) end private diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index f5143280154..18895c3f0f3 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -17,7 +17,7 @@ class Projects::HooksController < Projects::ApplicationController @hooks = @project.hooks.select(&:persisted?) flash[:alert] = @hook.errors.full_messages.join.html_safe end - redirect_to namespace_project_settings_integrations_path(@project.namespace, @project) + redirect_to project_settings_integrations_path(@project) end def edit @@ -26,7 +26,7 @@ class Projects::HooksController < Projects::ApplicationController def update if hook.update_attributes(hook_params) flash[:notice] = 'Hook was successfully updated.' - redirect_to namespace_project_settings_integrations_path(@project.namespace, @project) + redirect_to project_settings_integrations_path(@project) else render 'edit' end @@ -47,7 +47,7 @@ class Projects::HooksController < Projects::ApplicationController def destroy hook.destroy - redirect_to namespace_project_settings_integrations_path(@project.namespace, @project), status: 302 + redirect_to project_settings_integrations_path(@project), status: 302 end private diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb index 4b143434ea5..49aa32119ef 100644 --- a/app/controllers/projects/imports_controller.rb +++ b/app/controllers/projects/imports_controller.rb @@ -17,7 +17,7 @@ class Projects::ImportsController < Projects::ApplicationController @project.reload.import_schedule end - redirect_to namespace_project_import_path(@project.namespace, @project) + redirect_to project_import_path(@project) end def show @@ -25,10 +25,10 @@ class Projects::ImportsController < Projects::ApplicationController if continue_params redirect_to continue_params[:to], notice: continue_params[:notice] else - redirect_to namespace_project_path(@project.namespace, @project), notice: finished_notice + redirect_to project_path(@project), notice: finished_notice end elsif @project.import_failed? - redirect_to new_namespace_project_import_path(@project.namespace, @project) + redirect_to new_project_import_path(@project) else if continue_params && continue_params[:notice_now] flash.now[:notice] = continue_params[:notice_now] @@ -50,19 +50,19 @@ class Projects::ImportsController < Projects::ApplicationController def require_no_repo if @project.repository_exists? - redirect_to namespace_project_path(@project.namespace, @project) + redirect_to project_path(@project) end end def redirect_if_progress if @project.import_in_progress? - redirect_to namespace_project_import_path(@project.namespace, @project) + redirect_to project_import_path(@project) end end def redirect_if_no_import if @project.repository_exists? && @project.no_import? - redirect_to namespace_project_path(@project.namespace, @project) + redirect_to project_path(@project) end end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index dfc6baa34a4..c9e636fb65e 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -227,7 +227,7 @@ class Projects::IssuesController < Projects::ApplicationController def issue return @issue if defined?(@issue) # The Sortable default scope causes performance issues when used with find_by - @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take! + @noteable = @issue ||= @project.issues.find_by!(iid: params[:id]) return render_404 unless can?(current_user, :read_issue, @issue) @@ -238,6 +238,10 @@ class Projects::IssuesController < Projects::ApplicationController alias_method :awardable, :issue alias_method :spammable, :issue + def spammable_path + project_issue_path(@project, @issue) + end + def authorize_update_issue! return render_404 unless can?(current_user, :update_issue, @issue) end @@ -267,10 +271,22 @@ class Projects::IssuesController < Projects::ApplicationController end def issue_params - params.require(:issue).permit( - :title, :assignee_id, :position, :description, :confidential, - :milestone_id, :due_date, :state_event, :task_num, :lock_version, label_ids: [], assignee_ids: [] - ) + params.require(:issue).permit(*issue_params_attributes) + end + + def issue_params_attributes + %i[ + title + assignee_id + position + description + confidential + milestone_id + due_date + state_event + task_num + lock_version + ] + [{ label_ids: [], assignee_ids: [] }] end def authenticate_user! diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb index cb4f46388fd..96abdac91b6 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -38,7 +38,7 @@ class Projects::JobsController < Projects::ApplicationController build.cancel if can?(current_user, :update_build, build) end - redirect_to namespace_project_jobs_path(project.namespace, project) + redirect_to project_jobs_path(project) end def show @@ -108,7 +108,7 @@ class Projects::JobsController < Projects::ApplicationController def erase if @build.erase(erased_by: current_user) - redirect_to namespace_project_job_path(project.namespace, project, @build), + redirect_to project_job_path(project, @build), notice: "Build has been successfully erased!" else respond_422 @@ -137,6 +137,6 @@ class Projects::JobsController < Projects::ApplicationController end def build_path(build) - namespace_project_job_path(build.project.namespace, build.project, build) + project_job_path(build.project, build) end end diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index daa973c9281..480a2dff262 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -33,7 +33,7 @@ class Projects::LabelsController < Projects::ApplicationController if @label.valid? respond_to do |format| - format.html { redirect_to namespace_project_labels_path(@project.namespace, @project) } + format.html { redirect_to project_labels_path(@project) } format.json { render json: @label } end else @@ -51,7 +51,7 @@ class Projects::LabelsController < Projects::ApplicationController @label = Labels::UpdateService.new(label_params).execute(@label) if @label.valid? - redirect_to namespace_project_labels_path(@project.namespace, @project) + redirect_to project_labels_path(@project) else render :edit end @@ -61,12 +61,11 @@ class Projects::LabelsController < Projects::ApplicationController Gitlab::IssuesLabels.generate(@project) if params[:redirect] == 'issues' - redirect_to namespace_project_issues_path(@project.namespace, @project) + redirect_to project_issues_path(@project) elsif params[:redirect] == 'merge_requests' - redirect_to namespace_project_merge_requests_path(@project.namespace, - @project) + redirect_to project_merge_requests_path(@project) else - redirect_to namespace_project_labels_path(@project.namespace, @project) + redirect_to project_labels_path(@project) end end @@ -74,7 +73,7 @@ class Projects::LabelsController < Projects::ApplicationController @label.destroy @labels = find_labels - redirect_to namespace_project_labels_path(@project.namespace, @project), + redirect_to project_labels_path(@project), status: 302, notice: 'Label was removed' end @@ -114,7 +113,7 @@ class Projects::LabelsController < Projects::ApplicationController return render_404 unless promote_service.execute(@label) respond_to do |format| format.html do - redirect_to(namespace_project_labels_path(@project.namespace, @project), + redirect_to(project_labels_path(@project), notice: 'Label was promoted to a Group Label') end format.js @@ -125,7 +124,7 @@ class Projects::LabelsController < Projects::ApplicationController respond_to do |format| format.html do - redirect_to(namespace_project_labels_path(@project.namespace, @project), + redirect_to(project_labels_path(@project), notice: 'Failed to promote label due to internal error. Please contact administrators.') end format.js diff --git a/app/controllers/projects/mattermosts_controller.rb b/app/controllers/projects/mattermosts_controller.rb index 38f7e6eb5e9..0f6add3e287 100644 --- a/app/controllers/projects/mattermosts_controller.rb +++ b/app/controllers/projects/mattermosts_controller.rb @@ -16,12 +16,10 @@ class Projects::MattermostsController < Projects::ApplicationController if result flash[:notice] = 'This service is now configured' - redirect_to edit_namespace_project_service_path( - @project.namespace, @project, service) + redirect_to edit_project_service_path(@project, service) else flash[:alert] = message || 'Failed to configure service' - redirect_to new_namespace_project_mattermost_path( - @project.namespace, @project) + redirect_to new_project_mattermost_path(@project) end end diff --git a/app/controllers/projects/merge_requests/conflicts_controller.rb b/app/controllers/projects/merge_requests/conflicts_controller.rb index a71f23e790d..28afef101a9 100644 --- a/app/controllers/projects/merge_requests/conflicts_controller.rb +++ b/app/controllers/projects/merge_requests/conflicts_controller.rb @@ -52,7 +52,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.' - render json: { redirect_to: namespace_project_merge_request_url(@project.namespace, @project, @merge_request, resolved_conflicts: true) } + render json: { redirect_to: project_merge_request_url(@project, @merge_request, resolved_conflicts: true) } rescue Gitlab::Conflict::ResolutionError => e render status: :bad_request, json: { message: e.message } end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 04f8e95aa09..a573b392591 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -211,21 +211,18 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo stop_url = if environment.stop_action? && can?(current_user, :create_deployment, environment) - stop_namespace_project_environment_path(project.namespace, project, environment) + stop_project_environment_path(project, environment) end metrics_url = if can?(current_user, :read_environment, environment) && environment.has_metrics? - metrics_namespace_project_environment_deployment_path(environment.project.namespace, - environment.project, - environment, - deployment) + metrics_project_environment_deployment_path(environment.project, environment, deployment) end { id: environment.id, name: environment.name, - url: namespace_project_environment_path(project.namespace, project, environment), + url: project_environment_path(project, environment), metrics_url: metrics_url, stop_url: stop_url, external_url: environment.external_url, diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index 953b1e83e49..a80562e77ce 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -51,8 +51,7 @@ class Projects::MilestonesController < Projects::ApplicationController @milestone = Milestones::CreateService.new(project, current_user, milestone_params).execute if @milestone.save - redirect_to namespace_project_milestone_path(@project.namespace, - @project, @milestone) + redirect_to project_milestone_path(@project, @milestone) else render "new" end @@ -65,8 +64,7 @@ class Projects::MilestonesController < Projects::ApplicationController format.js format.html do if @milestone.valid? - redirect_to namespace_project_milestone_path(@project.namespace, - @project, @milestone) + redirect_to project_milestone_path(@project, @milestone) else render :edit end diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb index 33a152ad34f..dfa5e4f7f46 100644 --- a/app/controllers/projects/network_controller.rb +++ b/app/controllers/projects/network_controller.rb @@ -8,8 +8,8 @@ class Projects::NetworkController < Projects::ApplicationController before_action :assign_commit def show - @url = namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json)) - @commit_url = namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s") + @url = project_network_path(@project, @ref, @options.merge(format: :json)) + @commit_url = project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s") respond_to do |format| format.html do diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb index 28b383e69eb..d421b1a8eb5 100644 --- a/app/controllers/projects/pages_controller.rb +++ b/app/controllers/projects/pages_controller.rb @@ -15,7 +15,7 @@ class Projects::PagesController < Projects::ApplicationController respond_to do |format| format.html do - redirect_to namespace_project_pages_path(@project.namespace, @project), + redirect_to project_pages_path(@project), status: 302, notice: 'Pages were removed' end diff --git a/app/controllers/projects/pages_domains_controller.rb b/app/controllers/projects/pages_domains_controller.rb index dbd011f6c5d..15e77d854dc 100644 --- a/app/controllers/projects/pages_domains_controller.rb +++ b/app/controllers/projects/pages_domains_controller.rb @@ -16,7 +16,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController @domain = @project.pages_domains.create(pages_domain_params) if @domain.valid? - redirect_to namespace_project_pages_path(@project.namespace, @project) + redirect_to project_pages_path(@project) else render 'new' end @@ -27,7 +27,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController respond_to do |format| format.html do - redirect_to namespace_project_pages_path(@project.namespace, @project), + redirect_to project_pages_path(@project), status: 302, notice: 'Domain was removed' end diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb index 60db179277b..0d967a7e691 100644 --- a/app/controllers/projects/pipeline_schedules_controller.rb +++ b/app/controllers/projects/pipeline_schedules_controller.rb @@ -34,7 +34,7 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController def update if schedule.update(schedule_params) - redirect_to namespace_project_pipeline_schedules_path(@project.namespace.becomes(Namespace), @project) + redirect_to project_pipeline_schedules_path(@project) else render :edit end diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 303e91a8dc0..a3bfbf0694e 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -60,7 +60,7 @@ class Projects::PipelinesController < Projects::ApplicationController .execute(:web, ignore_skip_ci: true, save_on_errors: false) if @pipeline.persisted? - redirect_to namespace_project_pipeline_path(project.namespace, project, @pipeline) + redirect_to project_pipeline_path(project, @pipeline) else render 'new' end @@ -111,7 +111,7 @@ class Projects::PipelinesController < Projects::ApplicationController respond_to do |format| format.html do - redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) + redirect_back_or_default default: project_pipelines_path(project) end format.json { head :no_content } @@ -123,7 +123,7 @@ class Projects::PipelinesController < Projects::ApplicationController respond_to do |format| format.html do - redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) + redirect_back_or_default default: project_pipelines_path(project) end format.json { head :no_content } diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb index 38a47651000..9d24ebe2138 100644 --- a/app/controllers/projects/pipelines_settings_controller.rb +++ b/app/controllers/projects/pipelines_settings_controller.rb @@ -2,13 +2,13 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController before_action :authorize_admin_pipeline! def show - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project, params: params) + redirect_to project_settings_ci_cd_path(@project, params: params) end def update if @project.update_attributes(update_params) flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated." - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project) + redirect_to project_settings_ci_cd_path(@project) else render 'show' end @@ -23,7 +23,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController def update_params params.require(:project).permit( :runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex, - :public_builds, :auto_cancel_pending_pipelines + :public_builds, :auto_cancel_pending_pipelines, :ci_config_path ) end end diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index d2d26738582..57a6686f66c 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -7,7 +7,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController def index sort = params[:sort].presence || sort_value_name - redirect_to namespace_project_settings_members_path(@project.namespace, @project, sort: sort) + redirect_to project_settings_members_path(@project, sort: sort) end def update @@ -19,7 +19,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController end def resend_invite - redirect_path = namespace_project_settings_members_path(@project.namespace, @project) + redirect_path = project_settings_members_path(@project) @project_member = @project.project_members.find(params[:id]) @@ -42,7 +42,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController return render_404 end - redirect_to(namespace_project_settings_members_path(project.namespace, project), + redirect_to(project_settings_members_path(project), notice: notice) end diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 2a0b58fae7c..1eb78d8b522 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -13,21 +13,21 @@ class Projects::RefsController < Projects::ApplicationController new_path = case params[:destination] when "tree" - namespace_project_tree_path(@project.namespace, @project, @id) + project_tree_path(@project, @id) when "blob" - namespace_project_blob_path(@project.namespace, @project, @id) + project_blob_path(@project, @id) when "graph" - namespace_project_network_path(@project.namespace, @project, @id, @options) + project_network_path(@project, @id, @options) when "graphs" - namespace_project_graph_path(@project.namespace, @project, @id) + project_graph_path(@project, @id) when "find_file" - namespace_project_find_file_path(@project.namespace, @project, @id) + project_find_file_path(@project, @id) when "graphs_commits" - commits_namespace_project_graph_path(@project.namespace, @project, @id) + commits_project_graph_path(@project, @id) when "badges" - namespace_project_pipelines_settings_path(@project.namespace, @project, ref: @id) + project_pipelines_settings_path(@project, ref: @id) else - namespace_project_commits_path(@project.namespace, @project, @id) + project_commits_path(@project, @id) end redirect_to new_path @@ -62,7 +62,7 @@ class Projects::RefsController < Projects::ApplicationController offset = (@offset + @limit) if contents.size > offset - @more_log_url = logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '', offset: offset) + @more_log_url = logs_file_project_ref_path(@project, @ref, @path || '', offset: offset) end respond_to do |format| diff --git a/app/controllers/projects/registry/repositories_controller.rb b/app/controllers/projects/registry/repositories_controller.rb index 98e78585be8..71e7dc70a4d 100644 --- a/app/controllers/projects/registry/repositories_controller.rb +++ b/app/controllers/projects/registry/repositories_controller.rb @@ -10,11 +10,11 @@ module Projects def destroy if image.destroy - redirect_to project_container_registry_path(@project), + redirect_to project_container_registry_index_path(@project), status: 302, notice: 'Image repository has been removed successfully!' else - redirect_to project_container_registry_path(@project), + redirect_to project_container_registry_index_path(@project), status: 302, alert: 'Failed to remove image repository!' end diff --git a/app/controllers/projects/registry/tags_controller.rb b/app/controllers/projects/registry/tags_controller.rb index 5050dba3aab..ae72bd03cfb 100644 --- a/app/controllers/projects/registry/tags_controller.rb +++ b/app/controllers/projects/registry/tags_controller.rb @@ -5,11 +5,11 @@ module Projects def destroy if tag.delete - redirect_to project_container_registry_path(@project), + redirect_to project_container_registry_index_path(@project), status: 302, notice: 'Registry tag has been removed successfully!' else - redirect_to project_container_registry_path(@project), + redirect_to project_container_registry_index_path(@project), status: 302, alert: 'Failed to remove registry tag!' end diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb index 2c097cb4d8d..3e0a530fdb9 100644 --- a/app/controllers/projects/releases_controller.rb +++ b/app/controllers/projects/releases_controller.rb @@ -19,7 +19,7 @@ class Projects::ReleasesController < Projects::ApplicationController release.destroy end - redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name) + redirect_to project_tag_path(@project, @tag.name) end private diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb index 160e632648a..9f9773575a5 100644 --- a/app/controllers/projects/runners_controller.rb +++ b/app/controllers/projects/runners_controller.rb @@ -5,7 +5,7 @@ class Projects::RunnersController < Projects::ApplicationController layout 'project_settings' def index - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project) + redirect_to project_settings_ci_cd_path(@project) end def edit @@ -49,7 +49,7 @@ class Projects::RunnersController < Projects::ApplicationController def toggle_shared_runners project.toggle!(:shared_runners_enabled) - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project) + redirect_to project_settings_ci_cd_path(@project) end protected diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 704f8cc8a79..d54a1111f11 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -15,7 +15,7 @@ class Projects::ServicesController < Projects::ApplicationController def update if @service.save(context: :manual_change) - redirect_to(namespace_project_settings_integrations_path(@project.namespace, @project), notice: success_message) + redirect_to(project_settings_integrations_path(@project), notice: success_message) else render 'edit' end diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 98dd307bd9d..d07143d294f 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -30,7 +30,7 @@ class Projects::SnippetsController < Projects::ApplicationController ).execute @snippets = @snippets.page(params[:page]) if @snippets.out_of_range? && @snippets.total_pages != 0 - redirect_to namespace_project_snippets_path(page: @snippets.total_pages) + redirect_to project_snippets_path(@project, page: @snippets.total_pages) end end @@ -79,7 +79,7 @@ class Projects::SnippetsController < Projects::ApplicationController @snippet.destroy - redirect_to namespace_project_snippets_path(@project.namespace, @project), status: 302 + redirect_to project_snippets_path(@project), status: 302 end protected @@ -90,6 +90,10 @@ class Projects::SnippetsController < Projects::ApplicationController alias_method :awardable, :snippet alias_method :spammable, :snippet + def spammable_path + project_snippet_path(@project, @snippet) + end + def authorize_read_project_snippet! return render_404 unless can?(current_user, :read_project_snippet, @snippet) end diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index ebc9f4edab4..b62d7d9b7c5 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -35,7 +35,7 @@ class Projects::TagsController < Projects::ApplicationController if result[:status] == :success @tag = result[:tag] - redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name) + redirect_to project_tag_path(@project, @tag.name) else @error = result[:message] @message = params[:message] @@ -50,7 +50,7 @@ class Projects::TagsController < Projects::ApplicationController respond_to do |format| if result[:status] == :success format.html do - redirect_to namespace_project_tags_path(@project.namespace, @project), status: 303 + redirect_to project_tags_path(@project), status: 303 end format.js @@ -58,7 +58,7 @@ class Projects::TagsController < Projects::ApplicationController @error = result[:message] format.html do - redirect_to namespace_project_tags_path(@project.namespace, @project), + redirect_to project_tags_path(@project), alert: @error, status: 303 end diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index 266a15c1cf9..30181ac3bdf 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -16,7 +16,7 @@ class Projects::TreeController < Projects::ApplicationController if tree.entries.empty? if @repository.blob_at(@commit.id, @path) return redirect_to( - namespace_project_blob_path(@project.namespace, @project, + project_blob_path(@project, File.join(@ref, @path)) ) elsif @path.present? @@ -37,8 +37,8 @@ class Projects::TreeController < Projects::ApplicationController return render_404 unless @commit_params.values.all? create_commit(Files::CreateDirService, success_notice: "The directory has been successfully created.", - success_path: namespace_project_tree_path(@project.namespace, @project, File.join(@branch_name, @dir_name)), - failure_path: namespace_project_tree_path(@project.namespace, @project, @ref)) + success_path: project_tree_path(@project, File.join(@branch_name, @dir_name)), + failure_path: project_tree_path(@project, @ref)) end private diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb index e86adddd77f..a5b17fa65ea 100644 --- a/app/controllers/projects/triggers_controller.rb +++ b/app/controllers/projects/triggers_controller.rb @@ -7,7 +7,7 @@ class Projects::TriggersController < Projects::ApplicationController layout 'project_settings' def index - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project) + redirect_to project_settings_ci_cd_path(@project) end def create @@ -19,7 +19,7 @@ class Projects::TriggersController < Projects::ApplicationController flash[:alert] = 'You could not create a new trigger.' end - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project) + redirect_to project_settings_ci_cd_path(@project) end def take_ownership @@ -29,7 +29,7 @@ class Projects::TriggersController < Projects::ApplicationController flash[:alert] = 'You could not take ownership of trigger.' end - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project) + redirect_to project_settings_ci_cd_path(@project) end def edit @@ -37,7 +37,7 @@ class Projects::TriggersController < Projects::ApplicationController def update if trigger.update(trigger_params) - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project), notice: 'Trigger was successfully updated.' + redirect_to project_settings_ci_cd_path(@project), notice: 'Trigger was successfully updated.' else render action: "edit" end @@ -50,7 +50,7 @@ class Projects::TriggersController < Projects::ApplicationController flash[:alert] = "Could not remove the trigger." end - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project), status: 302 + redirect_to project_settings_ci_cd_path(@project), status: 302 end private diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb index 50e25a00f03..326d31ecec2 100644 --- a/app/controllers/projects/variables_controller.rb +++ b/app/controllers/projects/variables_controller.rb @@ -4,7 +4,7 @@ class Projects::VariablesController < Projects::ApplicationController layout 'project_settings' def index - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project) + redirect_to project_settings_ci_cd_path(@project) end def show @@ -14,19 +14,19 @@ class Projects::VariablesController < Projects::ApplicationController def update @variable = @project.variables.find(params[:id]) - if @variable.update_attributes(project_params) - redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variable was successfully updated.' + if @variable.update_attributes(variable_params) + redirect_to project_variables_path(project), notice: 'Variable was successfully updated.' else render action: "show" end end def create - @variable = Ci::Variable.new(project_params) + @variable = @project.variables.new(variable_params) - if @variable.valid? && @project.variables << @variable + if @variable.save flash[:notice] = 'Variables were successfully updated.' - redirect_to namespace_project_settings_ci_cd_path(project.namespace, project) + redirect_to project_settings_ci_cd_path(project) else render "show" end @@ -36,15 +36,18 @@ class Projects::VariablesController < Projects::ApplicationController @key = @project.variables.find(params[:id]) @key.destroy - redirect_to namespace_project_settings_ci_cd_path(project.namespace, project), + redirect_to project_settings_ci_cd_path(project), status: 302, notice: 'Variable was successfully removed.' end private - def project_params - params.require(:variable) - .permit([:id, :key, :value, :protected, :_destroy]) + def variable_params + params.require(:variable).permit(*variable_params_attributes) + end + + def variable_params_attributes + %i[id key value protected _destroy] end end diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index e54b90b8d52..ac98470c2b1 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -49,7 +49,7 @@ class Projects::WikisController < Projects::ApplicationController if @page.valid? redirect_to( - namespace_project_wiki_path(@project.namespace, @project, @page), + project_wiki_path(@project, @page), notice: 'Wiki was successfully updated.' ) else @@ -62,7 +62,7 @@ class Projects::WikisController < Projects::ApplicationController if @page.persisted? redirect_to( - namespace_project_wiki_path(@project.namespace, @project, @page), + project_wiki_path(@project, @page), notice: 'Wiki was successfully updated.' ) else @@ -75,7 +75,7 @@ class Projects::WikisController < Projects::ApplicationController unless @page redirect_to( - namespace_project_wiki_path(@project.namespace, @project, :home), + project_wiki_path(@project, :home), notice: "Page not found" ) end @@ -85,7 +85,7 @@ class Projects::WikisController < Projects::ApplicationController @page = @project_wiki.find_page(params[:id]) WikiPages::DestroyService.new(@project, current_user).execute(@page) - redirect_to namespace_project_wiki_path(@project.namespace, @project, :home), + redirect_to project_wiki_path(@project, :home), status: 302, notice: "Page was successfully deleted" end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 450895cdf3a..87a69e8e6f9 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -92,7 +92,7 @@ class ProjectsController < Projects::ApplicationController def show if @project.import_in_progress? - redirect_to namespace_project_import_path(@project.namespace, @project) + redirect_to project_import_path(@project) return end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 4a579601785..d58c8d14a75 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -44,7 +44,7 @@ class SearchController < ApplicationController query = params[:search].strip.downcase found_by_commit_sha = Commit.valid_hash?(query) && only_commit.sha.start_with?(query) - redirect_to namespace_project_commit_path(@project.namespace, @project, only_commit) if found_by_commit_sha + redirect_to project_commit_path(@project, only_commit) if found_by_commit_sha end end end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 3d86dd2ea2c..8c3abd0a085 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -107,6 +107,10 @@ class SnippetsController < ApplicationController alias_method :awardable, :snippet alias_method :spammable, :snippet + def spammable_path + snippet_path(@snippet) + end + def authorize_read_snippet! return if can?(current_user, :read_personal_snippet, @snippet) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 558f8b5e2e5..7bc2117f61e 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -20,6 +20,7 @@ # class IssuableFinder NONE = '0'.freeze + IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze attr_accessor :current_user, :params @@ -62,7 +63,7 @@ class IssuableFinder # grouping and counting within that query. # def count_by_state - count_params = params.merge(state: nil, sort: nil) + count_params = params.merge(state: nil, sort: nil, for_counting: true) labels_count = label_names.any? ? label_names.count : 1 finder = self.class.new(current_user, count_params) counts = Hash.new(0) @@ -86,6 +87,10 @@ class IssuableFinder execute.find_by!(*params) end + def state_counter_cache_key(state) + Digest::SHA1.hexdigest(state_counter_cache_key_components(state).flatten.join('-')) + end + def group return @group if defined?(@group) @@ -418,4 +423,13 @@ class IssuableFinder def current_user_related? params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' end + + def state_counter_cache_key_components(state) + opts = params.with_indifferent_access + opts[:state] = state + opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY) + opts.delete_if { |_, value| value.blank? } + + ['issuables_count', klass.to_ability_name, opts.sort] + end end diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index 3da5508aefd..85230ff1293 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -16,14 +16,72 @@ # sort: string # class IssuesFinder < IssuableFinder + CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER + def klass Issue end + def with_confidentiality_access_check + return Issue.all if user_can_see_all_confidential_issues? + return Issue.where('issues.confidential IS NOT TRUE') if user_cannot_see_confidential_issues? + + Issue.where(' + issues.confidential IS NOT TRUE + OR (issues.confidential = TRUE + AND (issues.author_id = :user_id + OR EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = :user_id AND issue_id = issues.id) + OR issues.project_id IN(:project_ids)))', + user_id: current_user.id, + project_ids: current_user.authorized_projects(CONFIDENTIAL_ACCESS_LEVEL).select(:id)) + end + private def init_collection - IssuesFinder.not_restricted_by_confidentiality(current_user) + with_confidentiality_access_check + end + + def user_can_see_all_confidential_issues? + return @user_can_see_all_confidential_issues if defined?(@user_can_see_all_confidential_issues) + + return @user_can_see_all_confidential_issues = false if current_user.blank? + return @user_can_see_all_confidential_issues = true if current_user.full_private_access? + + @user_can_see_all_confidential_issues = + project? && + project && + project.team.max_member_access(current_user.id) >= CONFIDENTIAL_ACCESS_LEVEL + end + + # Anonymous users can't see any confidential issues. + # + # Users without access to see _all_ confidential issues (as in + # `user_can_see_all_confidential_issues?`) are more complicated, because they + # can see confidential issues where: + # 1. They are an assignee. + # 2. They are an author. + # + # That's fine for most cases, but if we're just counting, we need to cache + # effectively. If we cached this accurately, we'd have a cache key for every + # authenticated user without sufficient access to the project. Instead, when + # we are counting, we treat them as if they can't see any confidential issues. + # + # This does mean the counts may be wrong for those users, but avoids an + # explosion in cache keys. + def user_cannot_see_confidential_issues?(for_counting: false) + return false if user_can_see_all_confidential_issues? + + current_user.blank? || for_counting || params[:for_counting] + end + + def state_counter_cache_key_components(state) + extra_components = [ + user_can_see_all_confidential_issues?, + user_cannot_see_confidential_issues?(for_counting: true) + ] + + super + extra_components end def by_assignee(items) @@ -38,21 +96,6 @@ class IssuesFinder < IssuableFinder end end - def self.not_restricted_by_confidentiality(user) - return Issue.where('issues.confidential IS NOT TRUE') if user.blank? - - return Issue.all if user.full_private_access? - - Issue.where(' - issues.confidential IS NOT TRUE - OR (issues.confidential = TRUE - AND (issues.author_id = :user_id - OR EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = :user_id AND issue_id = issues.id) - OR issues.project_id IN(:project_ids)))', - user_id: user.id, - project_ids: user.authorized_projects(Gitlab::Access::REPORTER).select(:id)) - end - def item_project_ids(items) items&.reorder(nil)&.select(:project_id) end diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index 042d792dada..ce432ddbfe6 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -83,7 +83,12 @@ class LabelsFinder < UnionFinder def projects return @projects if defined?(@projects) - @projects = skip_authorization ? Project.all : ProjectsFinder.new(current_user: current_user).execute + @projects = if skip_authorization + Project.all + else + ProjectsFinder.new(params: { non_archived: true }, current_user: current_user).execute + end + @projects = @projects.in_namespace(params[:group_id]) if group? @projects = @projects.where(id: params[:project_ids]) if projects? @projects = @projects.reorder(nil) diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index 8bfbe37c543..aa80dfc3f37 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -28,7 +28,14 @@ class ProjectsFinder < UnionFinder end def execute - collection = init_collection + user = params.delete(:user) + collection = + if user + PersonalProjectsFinder.new(user).execute(current_user) + else + init_collection + end + collection = by_ids(collection) collection = by_personal(collection) collection = by_starred(collection) diff --git a/app/finders/users_finder.rb b/app/finders/users_finder.rb index dbd50d1db7c..07deceb827b 100644 --- a/app/finders/users_finder.rb +++ b/app/finders/users_finder.rb @@ -60,13 +60,13 @@ class UsersFinder end def by_external_identity(users) - return users unless current_user.admin? && params[:extern_uid] && params[:provider] + return users unless current_user&.admin? && params[:extern_uid] && params[:provider] users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid])) end def by_external(users) - return users = users.where.not(external: true) unless current_user.admin? + return users = users.where.not(external: true) unless current_user&.admin? return users unless params[:external] users.external diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 7be8e3b96cf..1c165700b19 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -298,10 +298,6 @@ module ApplicationHelper end end - def can_toggle_new_nav? - Rails.env.development? - end - def show_new_nav? cookies["new_nav"] == "true" end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index ca326dd0627..f652f4901b7 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -34,17 +34,17 @@ module ApplicationSettingsHelper # Return a group of checkboxes that use Bootstrap's button plugin for a # toggle button effect. - def restricted_level_checkboxes(help_block_id) + def restricted_level_checkboxes(help_block_id, checkbox_name) Gitlab::VisibilityLevel.options.map do |name, level| checked = restricted_visibility_levels(true).include?(level) css_class = checked ? 'active' : '' - checkbox_name = "application_setting[restricted_visibility_levels][]" + tag_name = "application_setting_visibility_level_#{level}" - label_tag(name, class: css_class) do + label_tag(tag_name, class: css_class) do check_box_tag(checkbox_name, level, checked, autocomplete: 'off', 'aria-describedby' => help_block_id, - id: name) + visibility_level_icon(level) + name + id: tag_name) + visibility_level_icon(level) + name end end end diff --git a/app/helpers/award_emoji_helper.rb b/app/helpers/award_emoji_helper.rb index 024cf38469e..86b19368cfd 100644 --- a/app/helpers/award_emoji_helper.rb +++ b/app/helpers/award_emoji_helper.rb @@ -7,7 +7,7 @@ module AwardEmojiHelper if awardable.for_personal_snippet? toggle_award_emoji_snippet_note_path(awardable.noteable, awardable) else - toggle_award_emoji_namespace_project_note_path(@project.namespace, @project, awardable.id) + toggle_award_emoji_project_note_path(@project, awardable.id) end else url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable]) diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index ee36617ba9a..e964d7a5e16 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -9,7 +9,7 @@ module BlobHelper end def edit_path(project = @project, ref = @ref, path = @path, options = {}) - namespace_project_edit_blob_path(project.namespace, project, + project_edit_blob_path(project, tree_join(ref, path), options[:link_opts]) end @@ -33,7 +33,7 @@ module BlobHelper notice: edit_in_new_fork_notice, notice_now: edit_in_new_fork_notice_now } - fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params) + fork_path = project_forks_path(project, namespace_key: current_user.namespace.id, continue: continue_params) button_tag 'Edit', class: "#{common_classes} js-edit-blob-link-fork-toggler", @@ -62,7 +62,7 @@ module BlobHelper notice: edit_in_new_fork_notice + " Try to #{action} this file again.", notice_now: edit_in_new_fork_notice_now } - fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params) + fork_path = project_forks_path(project, namespace_key: current_user.namespace.id, continue: continue_params) button_tag label, class: "#{common_classes} js-edit-blob-link-fork-toggler", @@ -120,15 +120,15 @@ module BlobHelper def blob_raw_url if @build && @entry - raw_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path: @entry.path) + raw_project_job_artifacts_path(@project, @build, path: @entry.path) elsif @snippet if @snippet.project_id - raw_namespace_project_snippet_path(@project.namespace, @project, @snippet) + raw_project_snippet_path(@project, @snippet) else raw_snippet_path(@snippet) end elsif @blob - namespace_project_raw_path(@project.namespace, @project, @id) + project_raw_path(@project, @id) end end @@ -279,12 +279,12 @@ module BlobHelper options = [] if can?(current_user, :create_issue, project) - options << link_to("submit an issue", new_namespace_project_issue_path(project.namespace, project)) + options << link_to("submit an issue", new_project_issue_path(project)) end merge_project = can?(current_user, :create_merge_request, project) ? project : (current_user && current_user.fork_of(project)) if merge_project - options << link_to("create a merge request", namespace_project_new_merge_request_path(project.namespace, project)) + options << link_to("create a merge request", project_new_merge_request_path(project)) end options diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb index e2df52e3833..8b33c362a9c 100644 --- a/app/helpers/boards_helper.rb +++ b/app/helpers/boards_helper.rb @@ -3,12 +3,12 @@ module BoardsHelper board = @board || @boards.first { - endpoint: namespace_project_boards_path(@project.namespace, @project), + endpoint: project_boards_path(@project), board_id: board.id, disabled: "#{!can?(current_user, :admin_list, @project)}", - issue_link_base: namespace_project_issues_path(@project.namespace, @project), + issue_link_base: project_issues_path(@project), root_path: root_path, - bulk_update_path: bulk_update_namespace_project_issues_path(@project.namespace, @project), + bulk_update_path: bulk_update_project_issues_path(@project), default_avatar: image_path(default_avatar) } end diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb index 59519c1335b..686437fc99a 100644 --- a/app/helpers/branches_helper.rb +++ b/app/helpers/branches_helper.rb @@ -7,7 +7,7 @@ module BranchesHelper options = exist_opts.merge(options) - namespace_project_branches_path(@project.namespace, @project, @id, options) + project_branches_path(@project, @id, options) end def can_push_branch?(project, branch_name) diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb index f0a0d245dc0..85bc784d53c 100644 --- a/app/helpers/builds_helper.rb +++ b/app/helpers/builds_helper.rb @@ -20,8 +20,8 @@ module BuildsHelper def javascript_build_options { - page_url: namespace_project_job_url(@project.namespace, @project, @build), - build_url: namespace_project_job_url(@project.namespace, @project, @build, :json), + page_url: project_job_url(@project, @build), + build_url: project_job_url(@project, @build, :json), build_status: @build.status, build_stage: @build.stage, log_state: '' @@ -31,7 +31,7 @@ module BuildsHelper def build_failed_issue_options { title: "Build Failed ##{@build.id}", - description: namespace_project_job_url(@project.namespace, @project, @build) + description: project_job_url(@project, @build) } end end diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 21c0eb8b54c..8022547a6ad 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -8,7 +8,7 @@ module CiStatusHelper def ci_status_path(pipeline) project = pipeline.project - namespace_project_pipeline_path(project.namespace, project, pipeline) + project_pipeline_path(project, pipeline) end def ci_label_for_status(status) @@ -99,10 +99,7 @@ module CiStatusHelper def render_project_pipeline_status(pipeline_status, tooltip_placement: 'auto left') project = pipeline_status.project - path = pipelines_namespace_project_commit_path( - project.namespace, - project, - pipeline_status.sha) + path = pipelines_project_commit_path(project, pipeline_status.sha) render_status_with_link( 'commit', @@ -113,10 +110,7 @@ module CiStatusHelper def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left') project = commit.project - path = pipelines_namespace_project_commit_path( - project.namespace, - project, - commit) + path = pipelines_project_commit_path(project, commit) render_status_with_link( 'commit', @@ -127,7 +121,7 @@ module CiStatusHelper def render_pipeline_status(pipeline, tooltip_placement: 'auto left') project = pipeline.project - path = namespace_project_pipeline_path(project.namespace, project, pipeline) + path = project_pipeline_path(project, pipeline) render_status_with_link('pipeline', pipeline.status, path, tooltip_placement: tooltip_placement) end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 0accd1f8d77..d08e346d605 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -30,7 +30,7 @@ module CommitsHelper crumbs = content_tag(:li) do link_to( @project.path, - namespace_project_commits_path(@project.namespace, @project, @ref) + project_commits_path(@project, @ref) ) end @@ -42,8 +42,7 @@ module CommitsHelper # The text is just the individual part, but the link needs all the parts before it link_to( part, - namespace_project_commits_path( - @project.namespace, + project_commits_path( @project, tree_join(@ref, parts[0..i].join('/')) ) @@ -86,20 +85,20 @@ module CommitsHelper if @path.blank? return link_to( _("Browse Files"), - namespace_project_tree_path(project.namespace, project, commit), + project_tree_path(project, commit), class: "btn btn-default" ) elsif @repo.blob_at(commit.id, @path) return link_to( _("Browse File"), - namespace_project_blob_path(project.namespace, project, + project_blob_path(project, tree_join(commit.id, @path)), class: "btn btn-default" ) elsif @path.present? return link_to( _("Browse Directory"), - namespace_project_tree_path(project.namespace, project, + project_tree_path(project, tree_join(commit.id, @path)), class: "btn btn-default" ) @@ -165,7 +164,7 @@ module CommitsHelper notice: "#{edit_in_new_fork_notice} Try to #{action} this commit again.", notice_now: edit_in_new_fork_notice_now } - fork_path = namespace_project_forks_path(@project.namespace, @project, + fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params) @@ -175,7 +174,7 @@ module CommitsHelper def view_file_button(commit_sha, diff_new_path, project) link_to( - namespace_project_blob_path(project.namespace, project, + project_blob_path(project, tree_join(commit_sha, diff_new_path)), class: 'btn view-file js-view-file' ) do diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb index 424ded2b69d..2c28dd81c87 100644 --- a/app/helpers/compare_helper.rb +++ b/app/helpers/compare_helper.rb @@ -9,8 +9,7 @@ module CompareHelper end def create_mr_path(from = params[:from], to = params[:to], project = @project) - namespace_project_new_merge_request_path( - project.namespace, + project_new_merge_request_path( project, merge_request: { source_branch: to, diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 16a99addd0b..926502bf239 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -103,18 +103,18 @@ module DiffHelper end def diff_file_blob_raw_path(diff_file) - namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.content_sha, diff_file.file_path)) + project_raw_path(@project, tree_join(diff_file.content_sha, diff_file.file_path)) end def diff_file_old_blob_raw_path(diff_file) sha = diff_file.old_content_sha return unless sha - namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.old_content_sha, diff_file.old_path)) + project_raw_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path)) end def diff_file_html_data(project, diff_file_path, diff_commit_id) { - blob_diff_path: namespace_project_blob_diff_path(project.namespace, project, + blob_diff_path: project_blob_diff_path(project, tree_join(diff_commit_id, diff_file_path)), view: diff_view } @@ -142,7 +142,7 @@ module DiffHelper diff_file = viewer.diff_file options = [] - blob_url = namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.content_sha, diff_file.file_path)) + blob_url = project_blob_path(@project, tree_join(diff_file.content_sha, diff_file.file_path)) options << link_to('view the blob', blob_url) options @@ -163,17 +163,17 @@ module DiffHelper end def commit_diff_whitespace_link(project, commit, options) - url = namespace_project_commit_path(project.namespace, project, commit.id, params_with_whitespace) + url = project_commit_path(project, commit.id, params_with_whitespace) toggle_whitespace_link(url, options) end def diff_merge_request_whitespace_link(project, merge_request, options) - url = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, params_with_whitespace) + url = diffs_project_merge_request_path(project, merge_request, params_with_whitespace) toggle_whitespace_link(url, options) end def diff_compare_whitespace_link(project, from, to, options) - url = namespace_project_compare_path(project.namespace, project, from, to, params_with_whitespace) + url = project_compare_path(project, from, to, params_with_whitespace) toggle_whitespace_link(url, options) end diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb index ff8550439d0..1e78a189c08 100644 --- a/app/helpers/environment_helper.rb +++ b/app/helpers/environment_helper.rb @@ -8,7 +8,7 @@ module EnvironmentHelper def environment_link_for_build(project, build) environment = environment_for_build(project, build) if environment - link_to environment.name, namespace_project_environment_path(project.namespace, project, environment) + link_to environment.name, project_environment_path(project, environment) else content_tag :span, build.expanded_environment_name end diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb index 515e802e01e..4ce89f89fa9 100644 --- a/app/helpers/environments_helper.rb +++ b/app/helpers/environments_helper.rb @@ -1,7 +1,7 @@ module EnvironmentsHelper def environments_list_data { - endpoint: namespace_project_environments_path(@project.namespace, @project, format: :json) + endpoint: project_environments_path(@project, format: :json) } end end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 751d61955b7..48c87dca217 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -99,13 +99,12 @@ module EventsHelper def event_feed_url(event) if event.issue? - namespace_project_issue_url(event.project.namespace, event.project, + project_issue_url(event.project, event.issue) elsif event.merge_request? - namespace_project_merge_request_url(event.project.namespace, - event.project, event.merge_request) + project_merge_request_url(event.project, event.merge_request) elsif event.commit_note? - namespace_project_commit_url(event.project.namespace, event.project, + project_commit_url(event.project, event.note_target) elsif event.note? if event.note_target @@ -119,15 +118,15 @@ module EventsHelper def push_event_feed_url(event) if event.push_with_commits? && event.md_ref? if event.commits_count > 1 - namespace_project_compare_url(event.project.namespace, event.project, + project_compare_url(event.project, from: event.commit_from, to: event.commit_to) else - namespace_project_commit_url(event.project.namespace, event.project, + project_commit_url(event.project, id: event.commit_to) end else - namespace_project_commits_url(event.project.namespace, event.project, + project_commits_url(event.project, event.ref_name) end end @@ -146,15 +145,9 @@ module EventsHelper def event_note_target_path(event) if event.commit_note? - namespace_project_commit_path(event.project.namespace, - event.project, - event.note_target, - anchor: dom_id(event.target)) + project_commit_path(event.project, event.note_target, anchor: dom_id(event.target)) elsif event.project_snippet_note? - namespace_project_snippet_path(event.project.namespace, - event.project, - event.note_target, - anchor: dom_id(event.target)) + project_snippet_path(event.project, event.note_target, anchor: dom_id(event.target)) else polymorphic_path([event.project.namespace.becomes(Namespace), event.project, event.note_target], diff --git a/app/helpers/external_wiki_helper.rb b/app/helpers/external_wiki_helper.rb index defd87d6bbe..8cf890b74a8 100644 --- a/app/helpers/external_wiki_helper.rb +++ b/app/helpers/external_wiki_helper.rb @@ -4,7 +4,7 @@ module ExternalWikiHelper if external_wiki_service external_wiki_service.properties['external_wiki_url'] else - namespace_project_wiki_path(project.namespace, project, :home) + project_wiki_path(project, :home) end end end diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb index 8ceb5c36bda..9247b1f72de 100644 --- a/app/helpers/form_helper.rb +++ b/app/helpers/form_helper.rb @@ -16,8 +16,8 @@ module FormHelper end end - def issue_dropdown_options(issuable, has_multiple_assignees = true) - options = { + def issue_assignees_dropdown_options + { toggle_class: 'js-user-search js-assignee-search js-multiselect js-save-user-data', title: 'Select assignee', filter: true, @@ -27,8 +27,8 @@ module FormHelper first_user: current_user&.username, null_user: true, current_user: true, - project_id: issuable.project.try(:id), - field_name: "#{issuable.class.model_name.param_key}[assignee_ids][]", + project_id: @project.id, + field_name: 'issue[assignee_ids][]', default_label: 'Unassigned', 'max-select': 1, 'dropdown-header': 'Assignee', @@ -38,13 +38,5 @@ module FormHelper current_user_info: current_user.to_json(only: [:id, :name]) } } - - if has_multiple_assignees - options[:title] = 'Select assignee(s)' - options[:data][:'dropdown-header'] = 'Assignee(s)' - options[:data].delete(:'max-select') - end - - options end end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 8c7af62e199..b5f4bbe97dc 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -1,144 +1,89 @@ -# Shorter routing method for project and project items -# Since update to rails 4.1.9 we are now allowed to use `/` in project routing -# so we use nested routing for project resources which include project and -# project namespace. To avoid writing long methods every time we define shortcuts for -# some of routing. -# -# For example instead of this: -# -# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request) -# -# We can simply use shortcut: -# -# merge_request_path(merge_request) -# +# Shorter routing method for some project items module GitlabRoutingHelper - # Project - def project_path(project, *args) - namespace_project_path(project.namespace, project, *args) - end - - def project_url(project, *args) - namespace_project_url(project.namespace, project, *args) - end - - def edit_project_path(project, *args) - edit_namespace_project_path(project.namespace, project, *args) - end - - def edit_project_url(project, *args) - edit_namespace_project_url(project.namespace, project, *args) - end - - def project_files_path(project, *args) - namespace_project_tree_path(project.namespace, project, @ref || project.repository.root_ref) - end - - def project_commits_path(project, *args) - namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref) - end - - def project_pipelines_path(project, *args) - namespace_project_pipelines_path(project.namespace, project, *args) - end - - def project_environments_path(project, *args) - namespace_project_environments_path(project.namespace, project, *args) - end + extend ActiveSupport::Concern - def project_cycle_analytics_path(project, *args) - namespace_project_cycle_analytics_path(project.namespace, project, *args) + # Project + def project_tree_path(project, ref = nil, *args) + namespace_project_tree_path(project.namespace, project, ref || @ref || project.repository.root_ref, *args) # rubocop:disable Cop/ProjectPathHelper end - def project_jobs_path(project, *args) - namespace_project_jobs_path(project.namespace, project, *args) + def project_commits_path(project, ref = nil, *args) + namespace_project_commits_path(project.namespace, project, ref || @ref || project.repository.root_ref, *args) # rubocop:disable Cop/ProjectPathHelper end def project_ref_path(project, ref_name, *args) - namespace_project_commits_path(project.namespace, project, ref_name, *args) - end - - def project_container_registry_path(project, *args) - namespace_project_container_registry_index_path(project.namespace, project, *args) - end - - def activity_project_path(project, *args) - activity_namespace_project_path(project.namespace, project, *args) + project_commits_path(project, ref_name, *args) end def runners_path(project, *args) - namespace_project_runners_path(project.namespace, project, *args) + project_runners_path(project, *args) end def runner_path(runner, *args) - namespace_project_runner_path(@project.namespace, @project, runner, *args) + project_runner_path(@project, runner, *args) end def environment_path(environment, *args) - namespace_project_environment_path(environment.project.namespace, environment.project, environment, *args) + project_environment_path(environment.project, environment, *args) end def environment_metrics_path(environment, *args) - metrics_namespace_project_environment_path(environment.project.namespace, environment.project, environment, *args) + metrics_project_environment_path(environment.project, environment, *args) end def issue_path(entity, *args) - namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args) + project_issue_path(entity.project, entity, *args) end def merge_request_path(entity, *args) - namespace_project_merge_request_path(entity.project.namespace, entity.project, entity, *args) + project_merge_request_path(entity.project, entity, *args) end def pipeline_path(pipeline, *args) - namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, *args) + project_pipeline_path(pipeline.project, pipeline.id, *args) end def milestone_path(entity, *args) - namespace_project_milestone_path(entity.project.namespace, entity.project, entity, *args) + project_milestone_path(entity.project, entity, *args) end def issue_url(entity, *args) - namespace_project_issue_url(entity.project.namespace, entity.project, entity, *args) + project_issue_url(entity.project, entity, *args) end def merge_request_url(entity, *args) - namespace_project_merge_request_url(entity.project.namespace, entity.project, entity, *args) + project_merge_request_url(entity.project, entity, *args) end def pipeline_url(pipeline, *args) - namespace_project_pipeline_url(pipeline.project.namespace, pipeline.project, pipeline.id, *args) + project_pipeline_url(pipeline.project, pipeline.id, *args) end def pipeline_job_url(pipeline, build, *args) - namespace_project_job_url(pipeline.project.namespace, pipeline.project, build.id, *args) + project_job_url(pipeline.project, build.id, *args) end def commits_url(entity, *args) - namespace_project_commits_url(entity.project.namespace, entity.project, entity.ref, *args) + project_commits_url(entity.project, entity.ref, *args) end def commit_url(entity, *args) - namespace_project_commit_url(entity.project.namespace, entity.project, entity.sha, *args) - end - - def project_snippet_url(entity, *args) - namespace_project_snippet_url(entity.project.namespace, entity.project, entity, *args) + project_commit_url(entity.project, entity.sha, *args) end def preview_markdown_path(project, *args) if @snippet.is_a?(PersonalSnippet) preview_markdown_snippets_path else - preview_markdown_namespace_project_path(project.namespace, project, *args) + preview_markdown_project_path(project, *args) end end def toggle_subscription_path(entity, *args) if entity.is_a?(Issue) - toggle_subscription_namespace_project_issue_path(entity.project.namespace, entity.project, entity) + toggle_subscription_project_issue_path(entity.project, entity) else - toggle_subscription_namespace_project_merge_request_path(entity.project.namespace, entity.project, entity) + toggle_subscription_project_merge_request_path(entity.project, entity) end end @@ -152,32 +97,27 @@ module GitlabRoutingHelper ## Members def project_members_url(project, *args) - namespace_project_project_members_url(project.namespace, project) + project_project_members_url(project) end def project_member_path(project_member, *args) - namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) + project_project_member_path(project_member.source, project_member) end def request_access_project_members_path(project, *args) - request_access_namespace_project_project_members_path(project.namespace, project) + request_access_project_project_members_path(project) end def leave_project_members_path(project, *args) - leave_namespace_project_project_members_path(project.namespace, project) + leave_project_project_members_path(project) end def approve_access_request_project_member_path(project_member, *args) - approve_access_request_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) + approve_access_request_project_project_member_path(project_member.source, project_member) end def resend_invite_project_member_path(project_member, *args) - resend_invite_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) - end - - # Snippets - def personal_snippet_url(snippet, *args) - snippet_url(snippet) + resend_invite_project_project_member_path(project_member.source, project_member) end # Groups @@ -211,50 +151,37 @@ module GitlabRoutingHelper def artifacts_action_path(path, project, build) action, path_params = path.split('/', 2) - args = [project.namespace, project, build, path_params] + args = [project, build, path_params] case action when 'download' - download_namespace_project_job_artifacts_path(*args) + download_project_job_artifacts_path(*args) when 'browse' - browse_namespace_project_job_artifacts_path(*args) + browse_project_job_artifacts_path(*args) when 'file' - file_namespace_project_job_artifacts_path(*args) + file_project_job_artifacts_path(*args) when 'raw' - raw_namespace_project_job_artifacts_path(*args) + raw_project_job_artifacts_path(*args) end end # Pipeline Schedules def pipeline_schedules_path(project, *args) - namespace_project_pipeline_schedules_path(project.namespace, project, *args) + project_pipeline_schedules_path(project, *args) end def pipeline_schedule_path(schedule, *args) project = schedule.project - namespace_project_pipeline_schedule_path(project.namespace, project, schedule, *args) + project_pipeline_schedule_path(project, schedule, *args) end def edit_pipeline_schedule_path(schedule) project = schedule.project - edit_namespace_project_pipeline_schedule_path(project.namespace, project, schedule) + edit_project_pipeline_schedule_path(project, schedule) end def take_ownership_pipeline_schedule_path(schedule, *args) project = schedule.project - take_ownership_namespace_project_pipeline_schedule_path(project.namespace, project, schedule, *args) - end - - # Settings - def project_settings_integrations_path(project, *args) - namespace_project_settings_integrations_path(project.namespace, project, *args) - end - - def project_settings_members_path(project, *args) - namespace_project_settings_members_path(project.namespace, project, *args) - end - - def project_settings_ci_cd_path(project, *args) - namespace_project_settings_ci_cd_path(project.namespace, project, *args) + take_ownership_project_pipeline_schedule_path(project, schedule, *args) end end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index eb45241615f..8cd61f738e1 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -16,11 +16,12 @@ module GroupsHelper full_title = '' group.ancestors.reverse.each do |parent| - full_title += link_to(simple_sanitize(parent.name), group_path(parent), class: 'group-path hidable') + full_title += group_title_link(parent, hidable: true) + full_title += '<span class="hidable"> / </span>'.html_safe end - full_title += link_to(simple_sanitize(group.name), group_path(group), class: 'group-path') + full_title += group_title_link(group) full_title += ' · '.html_safe + link_to(simple_sanitize(name), url, class: 'group-path') if name content_tag :span, class: 'group-title' do @@ -56,4 +57,25 @@ module GroupsHelper def group_issues(group) IssuesFinder.new(current_user, group_id: group.id).execute end + + def remove_group_message(group) + _("You are going to remove %{group_name}.\nRemoved groups CANNOT be restored!\nAre you ABSOLUTELY sure?") % + { group_name: group.name } + end + + private + + def group_title_link(group, hidable: false) + link_to(group_path(group), class: "group-path #{'hidable' if hidable}") do + output = + if show_new_nav? + image_tag(group_icon(group), class: "avatar-tile", width: 16, height: 16) + else + "" + end + + output << simple_sanitize(group.name) + output.html_safe + end + end end diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 3259a9c1933..b5366519ed9 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -26,9 +26,9 @@ module IssuablesHelper project = issuable.project if issuable.is_a?(MergeRequest) - namespace_project_merge_request_path(project.namespace, project, issuable.iid, :json) + project_merge_request_path(project, issuable.iid, :json) else - namespace_project_issue_path(project.namespace, project, issuable.iid, :json) + project_issue_path(project, issuable.iid, :json) end end @@ -165,11 +165,7 @@ module IssuablesHelper } state_title = titles[state] || state.to_s.humanize - - count = - Rails.cache.fetch(issuables_state_counter_cache_key(issuable_type, state), expires_in: 2.minutes) do - issuables_count_for_state(issuable_type, state) - end + count = issuables_count_for_state(issuable_type, state) html = content_tag(:span, state_title) html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge') @@ -201,7 +197,7 @@ module IssuablesHelper def issuable_initial_data(issuable) data = { - endpoint: namespace_project_issue_path(@project.namespace, @project, issuable), + endpoint: project_issue_path(@project, issuable), canUpdate: can?(current_user, :update_issue, issuable), canDestroy: can?(current_user, :destroy_issue, issuable), canMove: current_user ? issuable.can_move?(current_user) : false, @@ -237,6 +233,18 @@ module IssuablesHelper } end + def issuables_count_for_state(issuable_type, state, finder: nil) + finder ||= public_send("#{issuable_type}_finder") + cache_key = finder.state_counter_cache_key(state) + + @counts ||= {} + @counts[cache_key] ||= Rails.cache.fetch(cache_key, expires_in: 2.minutes) do + finder.count_by_state + end + + @counts[cache_key][state] + end + private def sidebar_gutter_collapsed? @@ -255,24 +263,6 @@ module IssuablesHelper end end - def issuables_count_for_state(issuable_type, state) - @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].freeze - private_constant :IRRELEVANT_PARAMS_FOR_CACHE_KEY - - def issuables_state_counter_cache_key(issuable_type, state) - 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 - def issuable_templates(issuable) @issuable_templates ||= case issuable @@ -305,7 +295,7 @@ module IssuablesHelper mark_icon: (is_collapsed ? icon('check-square', class: 'todo-undone') : nil), issuable_id: issuable.id, issuable_type: issuable.class.name.underscore, - url: namespace_project_todos_path(@project.namespace, @project), + url: project_todos_path(@project), delete_path: (dashboard_todo_path(todo) if todo), placement: (is_collapsed ? 'left' : nil), container: (is_collapsed ? 'body' : nil) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 82288f1da35..42b6cfdf02f 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -150,7 +150,7 @@ module IssuesHelper Gitlab::UrlBuilder.build(single_discussion.first_note) else project = merge_request.project - namespace_project_merge_request_path(project.namespace, project, merge_request) + project_merge_request_path(project, merge_request) end link_to link_text, path diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 6baf6f31d8f..4b99de1b6a5 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -57,14 +57,14 @@ module LabelsHelper def edit_label_path(label) case label when GroupLabel then edit_group_label_path(label.group, label) - when ProjectLabel then edit_namespace_project_label_path(label.project.namespace, label.project, label) + when ProjectLabel then edit_project_label_path(label.project, label) end end def destroy_label_path(label) case label when GroupLabel then group_label_path(label.group, label) - when ProjectLabel then namespace_project_label_path(label.project.namespace, label.project, label) + when ProjectLabel then project_label_path(label.project, label) end end @@ -127,7 +127,7 @@ module LabelsHelper project = @target_project || @project if project - namespace_project_labels_path(project.namespace, project, :json) + project_labels_path(project, :json) else dashboard_labels_path(:json) end @@ -149,8 +149,8 @@ module LabelsHelper case label_subscription_status(label, project) when 'group-level' then toggle_subscription_group_label_path(label.group, label) - when 'project-level' then toggle_subscription_namespace_project_label_path(project.namespace, project, label) - when 'unsubscribed' then toggle_subscription_namespace_project_label_path(project.namespace, project, label) + when 'project-level' then toggle_subscription_project_label_path(project, label) + when 'unsubscribed' then toggle_subscription_project_label_path(project, label) end end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 54d6f86fa11..78cf7b26a31 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -1,8 +1,7 @@ module MergeRequestsHelper def new_mr_path_from_push_event(event) target_project = event.project.default_merge_request_target - namespace_project_new_merge_request_path( - event.project.namespace, + project_new_merge_request_path( event.project, new_mr_from_push_event(event, target_project) ) @@ -48,8 +47,8 @@ module MergeRequestsHelper end def mr_change_branches_path(merge_request) - namespace_project_new_merge_request_path( - @project.namespace, @project, + project_new_merge_request_path( + @project, merge_request: { source_project_id: merge_request.source_project_id, target_project_id: merge_request.target_project_id, @@ -82,9 +81,7 @@ module MergeRequestsHelper end def merge_request_version_path(project, merge_request, merge_request_diff, start_sha = nil) - diffs_namespace_project_merge_request_path( - project.namespace, project, merge_request, - diff_id: merge_request_diff.id, start_sha: start_sha) + diffs_project_merge_request_path(project, merge_request, diff_id: merge_request_diff.id, start_sha: start_sha) end def version_index(merge_request_diff) diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index a230db22fa2..8c7851dcfc2 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -1,7 +1,7 @@ module MilestonesHelper def milestones_filter_path(opts = {}) if @project - namespace_project_milestones_path(@project.namespace, @project, opts) + project_milestones_path(@project, opts) elsif @group group_milestones_path(@group, opts) else @@ -11,7 +11,7 @@ module MilestonesHelper def milestones_label_path(opts = {}) if @project - namespace_project_issues_path(@project.namespace, @project, opts) + project_issues_path(@project, opts) elsif @group issues_group_path(@group, opts) else @@ -73,7 +73,9 @@ module MilestonesHelper def milestones_filter_dropdown_path project = @target_project || @project if project - namespace_project_milestones_path(project.namespace, project, :json) + project_milestones_path(project, :json) + elsif @group + group_milestones_path(@group, :json) else dashboard_milestones_path(:json) end @@ -118,7 +120,7 @@ module MilestonesHelper def milestone_merge_request_tab_path(milestone) if @project - merge_requests_namespace_project_milestone_path(@project.namespace, @project, milestone, format: :json) + merge_requests_project_milestone_path(@project, milestone, format: :json) elsif @group merge_requests_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json) else @@ -128,7 +130,7 @@ module MilestonesHelper def milestone_participants_tab_path(milestone) if @project - participants_namespace_project_milestone_path(@project.namespace, @project, milestone, format: :json) + participants_project_milestone_path(@project, milestone, format: :json) elsif @group participants_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json) else @@ -138,7 +140,7 @@ module MilestonesHelper def milestone_labels_tab_path(milestone) if @project - labels_namespace_project_milestone_path(@project.namespace, @project, milestone, format: :json) + labels_project_milestone_path(@project, milestone, format: :json) elsif @group labels_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json) else diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 64ad7b280cb..0a0881d95cf 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -47,6 +47,18 @@ module NotesHelper data end + def add_diff_note_button(line_code, position, line_type) + return if @diff_notes_disabled + + button_tag '', + class: 'add-diff-note js-add-diff-note-button', + type: 'submit', name: 'button', + data: diff_view_line_data(line_code, position, line_type), + title: 'Add a comment to this line' do + icon('comment-o') + end + end + def link_to_reply_discussion(discussion, line_type = nil) return unless current_user @@ -69,11 +81,11 @@ module NotesHelper path_params = version_params.merge(anchor: discussion.line_code) - diffs_namespace_project_merge_request_path(discussion.project.namespace, discussion.project, discussion.noteable, path_params) + diffs_project_merge_request_path(discussion.project, discussion.noteable, path_params) elsif discussion.for_commit? anchor = discussion.line_code if discussion.diff_discussion? - namespace_project_commit_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: anchor) + project_commit_path(discussion.project, discussion.noteable, anchor: anchor) end end @@ -81,12 +93,7 @@ module NotesHelper if @snippet.is_a?(PersonalSnippet) snippet_notes_path(@snippet) else - namespace_project_noteable_notes_path( - namespace_id: @project.namespace, - project_id: @project, - target_id: @noteable.id, - target_type: @noteable.class.name.underscore - ) + project_noteable_notes_path(@project, target_id: @noteable.id, target_type: @noteable.class.name.underscore) end end @@ -94,7 +101,7 @@ module NotesHelper if note.noteable.is_a?(PersonalSnippet) snippet_note_path(note.noteable, note) else - namespace_project_note_path(project.namespace, project, note) + project_note_path(project, note) end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index c04b1419a19..5022b291f7f 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -58,7 +58,17 @@ module ProjectsHelper link_to(simple_sanitize(owner.name), user_path(owner)) end - project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" } + project_link = link_to project_path(project), { class: "project-item-select-holder" } do + output = + if show_new_nav? + project_icon(project, alt: project.name, class: 'avatar-tile', width: 16, height: 16) + else + "" + end + + output << simple_sanitize(project.name) + output.html_safe + end if current_user project_link << button_tag(type: 'button', class: 'dropdown-toggle-caret js-projects-dropdown-toggle', aria: { label: 'Toggle switch project dropdown' }, data: { target: '.js-dropdown-menu-projects', toggle: 'dropdown', order_by: 'last_activity_at' }) do @@ -337,8 +347,7 @@ module ProjectsHelper def add_special_file_path(project, file_name:, commit_message: nil, branch_name: nil, context: nil) commit_message ||= s_("CommitMessage|Add %{file_name}") % { file_name: file_name.downcase } - namespace_project_new_blob_path( - project.namespace, + project_new_blob_path( project, project.default_branch || 'master', file_name: file_name, @@ -349,8 +358,7 @@ module ProjectsHelper end def add_koding_stack_path(project) - namespace_project_new_blob_path( - project.namespace, + project_new_blob_path( project, project.default_branch || 'master', file_name: '.koding.yml', @@ -404,8 +412,7 @@ module ProjectsHelper def contribution_guide_path(project) if project && contribution_guide = project.repository.contribution_guide - namespace_project_blob_path( - project.namespace, + project_blob_path( project, tree_join(project.default_branch, contribution_guide.name) @@ -435,7 +442,7 @@ module ProjectsHelper def project_wiki_path_with_version(proj, page, version, is_newest) url_params = is_newest ? {} : { version_id: version } - namespace_project_wiki_path(proj.namespace, proj, page, url_params) + project_wiki_path(proj, page, url_params) end def project_status_css_class(status) @@ -460,8 +467,7 @@ module ProjectsHelper def filename_path(project, filename) if project && blob = project.repository.send(filename) - namespace_project_blob_path( - project.namespace, + project_blob_path( project, tree_join(project.default_branch, blob.name) ) diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 8f15904f068..8c44f4b0934 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -67,16 +67,16 @@ module SearchHelper ref = @ref || @project.repository.root_ref [ - { category: "Current Project", label: "Files", url: namespace_project_tree_path(@project.namespace, @project, ref) }, - { category: "Current Project", label: "Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) }, - { category: "Current Project", label: "Network", url: namespace_project_network_path(@project.namespace, @project, ref) }, - { category: "Current Project", label: "Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) }, - { category: "Current Project", label: "Issues", url: namespace_project_issues_path(@project.namespace, @project) }, - { category: "Current Project", label: "Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) }, - { category: "Current Project", label: "Milestones", url: namespace_project_milestones_path(@project.namespace, @project) }, - { category: "Current Project", label: "Snippets", url: namespace_project_snippets_path(@project.namespace, @project) }, - { category: "Current Project", label: "Members", url: namespace_project_settings_members_path(@project.namespace, @project) }, - { category: "Current Project", label: "Wiki", url: namespace_project_wikis_path(@project.namespace, @project) } + { category: "Current Project", label: "Files", url: project_tree_path(@project, ref) }, + { category: "Current Project", label: "Commits", url: project_commits_path(@project, ref) }, + { category: "Current Project", label: "Network", url: project_network_path(@project, ref) }, + { category: "Current Project", label: "Graph", url: project_graph_path(@project, ref) }, + { category: "Current Project", label: "Issues", url: project_issues_path(@project) }, + { category: "Current Project", label: "Merge Requests", url: project_merge_requests_path(@project) }, + { category: "Current Project", label: "Milestones", url: project_milestones_path(@project) }, + { category: "Current Project", label: "Snippets", url: project_snippets_path(@project) }, + { category: "Current Project", label: "Members", url: project_settings_members_path(@project) }, + { category: "Current Project", label: "Wiki", url: project_wikis_path(@project) } ] else [] @@ -104,7 +104,7 @@ module SearchHelper id: p.id, value: "#{search_result_sanitize(p.name)}", label: "#{search_result_sanitize(p.name_with_namespace)}", - url: namespace_project_path(p.namespace, p) + url: project_path(p) } end end @@ -126,6 +126,18 @@ module SearchHelper search_path(options) end + def search_filter_input_options(type) + { + id: "filtered-search-#{type}", + placeholder: 'Search or filter results...', + data: { + 'project-id' => @project.id, + 'username-params' => @users.to_json(only: [:id, :username]), + 'base-endpoint' => project_path(@project) + } + } + end + # Sanitize a HTML field for search display. Most tags are stripped out and the # maximum length is set to 200 characters. def search_md_sanitize(object, field) diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb index 2fd64b3441e..b447d4952e7 100644 --- a/app/helpers/snippets_helper.rb +++ b/app/helpers/snippets_helper.rb @@ -1,8 +1,7 @@ module SnippetsHelper def reliable_snippet_path(snippet, opts = nil) if snippet.project_id? - namespace_project_snippet_path(snippet.project.namespace, - snippet.project, snippet, opts) + project_snippet_path(snippet.project, snippet, opts) else snippet_path(snippet, opts) end @@ -10,7 +9,7 @@ module SnippetsHelper def download_snippet_path(snippet) if snippet.project_id - raw_namespace_project_snippet_path(@project.namespace, @project, snippet, inline: false) + raw_project_snippet_path(@project, snippet, inline: false) else raw_snippet_path(snippet, inline: false) end @@ -21,7 +20,7 @@ module SnippetsHelper # @returns String, path to snippet index def subject_snippets_path(subject = nil, opts = nil) if subject.is_a?(Project) - namespace_project_snippets_path(subject.namespace, subject, opts) + project_snippets_path(subject, opts) else # assume subject === User dashboard_snippets_path(opts) end diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index 1a55ee05996..ee701076a14 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -107,8 +107,7 @@ module TabHelper def branches_tab_class if current_controller?(:protected_branches) || current_controller?(:branches) || - current_page?(namespace_project_repository_path(@project.namespace, - @project)) + current_page?(project_repository_path(@project)) 'active' end end diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb index 31aaf9e5607..d000d6b1c0a 100644 --- a/app/helpers/tags_helper.rb +++ b/app/helpers/tags_helper.rb @@ -10,7 +10,7 @@ module TagsHelper } options = exist_opts.merge(options) - namespace_project_tags_path(@project.namespace, @project, @id, options) + project_tags_path(@project, @id, options) end def tag_list(project) diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 3d1b3a4711a..2a7aa299e83 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -39,7 +39,7 @@ module TodosHelper anchor = dom_id(todo.note) if todo.note.present? if todo.for_commit? - namespace_project_commit_path(todo.project.namespace.becomes(Namespace), todo.project, + project_commit_path(todo.project, todo.target, anchor: anchor) else path = [todo.project.namespace.becomes(Namespace), todo.project, todo.target] diff --git a/app/helpers/webpack_helper.rb b/app/helpers/webpack_helper.rb index 6bacda9fe75..0386df22374 100644 --- a/app/helpers/webpack_helper.rb +++ b/app/helpers/webpack_helper.rb @@ -11,20 +11,29 @@ module WebpackHelper paths = Webpack::Rails::Manifest.asset_paths(source) if extension - paths = paths.select { |p| p.ends_with? ".#{extension}" } + paths.select! { |p| p.ends_with? ".#{extension}" } end - # include full webpack-dev-server url for rspec tests running locally + force_host = webpack_public_host + if force_host + paths.map! { |p| "#{force_host}#{p}" } + end + + paths + end + + def webpack_public_host if Rails.env.test? && Rails.configuration.webpack.dev_server.enabled host = Rails.configuration.webpack.dev_server.host port = Rails.configuration.webpack.dev_server.port protocol = Rails.configuration.webpack.dev_server.https ? 'https' : 'http' - - paths.map! do |p| - "#{protocol}://#{host}:#{port}#{p}" - end + "#{protocol}://#{host}:#{port}" + else + ActionController::Base.asset_host.try(:chomp, '/') end + end - paths + def webpack_public_path + "#{webpack_public_host}/#{Rails.application.config.webpack.public_path}/" end end diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index 0f847841295..64ca2d2eacf 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -31,7 +31,7 @@ module Emails setup_issue_mail(issue_id, recipient_id) @label_names = label_names - @labels_url = namespace_project_labels_url(@project.namespace, @project) + @labels_url = project_labels_url(@project) mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id)) end @@ -56,7 +56,7 @@ module Emails def setup_issue_mail(issue_id, recipient_id) @issue = Issue.find(issue_id) @project = @issue.project - @target_url = namespace_project_issue_url(@project.namespace, @project, @issue) + @target_url = project_issue_url(@project, @issue) @sent_notification = SentNotification.record(@issue, recipient_id, reply_key) end diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index ec27ac517db..3626f8ce416 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -22,7 +22,7 @@ module Emails setup_merge_request_mail(merge_request_id, recipient_id) @label_names = label_names - @labels_url = namespace_project_labels_url(@project.namespace, @project) + @labels_url = project_labels_url(@project) mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id)) end @@ -59,7 +59,7 @@ module Emails def setup_merge_request_mail(merge_request_id, recipient_id) @merge_request = MergeRequest.find(merge_request_id) @project = @merge_request.project - @target_url = namespace_project_merge_request_url(@project.namespace, @project, @merge_request) + @target_url = project_merge_request_url(@project, @merge_request) @sent_notification = SentNotification.record(@merge_request, recipient_id, reply_key) end diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index 00707a0023e..77a82b895ce 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -4,7 +4,7 @@ module Emails setup_note_mail(note_id, recipient_id) @commit = @note.noteable - @target_url = namespace_project_commit_url(*note_target_url_options) + @target_url = project_commit_url(*note_target_url_options) mail_answer_thread(@commit, note_thread_options(recipient_id)) end @@ -12,7 +12,7 @@ module Emails setup_note_mail(note_id, recipient_id) @issue = @note.noteable - @target_url = namespace_project_issue_url(*note_target_url_options) + @target_url = project_issue_url(*note_target_url_options) mail_answer_thread(@issue, note_thread_options(recipient_id)) end @@ -20,7 +20,7 @@ module Emails setup_note_mail(note_id, recipient_id) @merge_request = @note.noteable - @target_url = namespace_project_merge_request_url(*note_target_url_options) + @target_url = project_merge_request_url(*note_target_url_options) mail_answer_thread(@merge_request, note_thread_options(recipient_id)) end @@ -28,7 +28,7 @@ module Emails setup_note_mail(note_id, recipient_id) @snippet = @note.noteable - @target_url = namespace_project_snippet_url(*note_target_url_options) + @target_url = project_snippet_url(*note_target_url_options) mail_answer_thread(@snippet, note_thread_options(recipient_id)) end @@ -43,7 +43,7 @@ module Emails private def note_target_url_options - [@project.namespace, @project, @note.noteable, anchor: "note_#{@note.id}"] + [@project, @note.noteable, anchor: "note_#{@note.id}"] end def note_thread_options(recipient_id) diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index e0af7081411..761d873c01c 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -3,7 +3,7 @@ module Emails def project_was_moved_email(project_id, user_id, old_path_with_namespace) @current_user = @user = User.find user_id @project = Project.find project_id - @target_url = namespace_project_url(@project.namespace, @project) + @target_url = project_url(@project) @old_path_with_namespace = old_path_with_namespace mail(to: @user.notification_email, subject: subject("Project was moved")) diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index f315e38bcaa..eaac6fcb548 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -1,5 +1,6 @@ class Notify < BaseMailer include ActionDispatch::Routing::PolymorphicRoutes + include GitlabRoutingHelper include Emails::Issues include Emails::MergeRequests diff --git a/app/models/ability.rb b/app/models/ability.rb index d2b8a8447b5..0b6bcbde5d9 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -1,4 +1,4 @@ -require 'declarative_policy' +require_dependency 'declarative_policy' class Ability class << self diff --git a/app/models/appearance.rb b/app/models/appearance.rb index c79326e8427..f9c48482be7 100644 --- a/app/models/appearance.rb +++ b/app/models/appearance.rb @@ -10,5 +10,5 @@ class Appearance < ActiveRecord::Base mount_uploader :logo, AttachmentUploader mount_uploader :header_logo, AttachmentUploader - has_many :uploads, as: :model, dependent: :destroy + has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 668caef0d2c..b0d7f7ef5f5 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -13,13 +13,13 @@ class ApplicationSetting < ActiveRecord::Base [\r\n] # any number of newline characters }x - serialize :restricted_visibility_levels # rubocop:disable Cop/ActiverecordSerialize - serialize :import_sources # rubocop:disable Cop/ActiverecordSerialize - serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiverecordSerialize - serialize :domain_whitelist, Array # rubocop:disable Cop/ActiverecordSerialize - serialize :domain_blacklist, Array # rubocop:disable Cop/ActiverecordSerialize - serialize :repository_storages # rubocop:disable Cop/ActiverecordSerialize - serialize :sidekiq_throttling_queues, Array # rubocop:disable Cop/ActiverecordSerialize + serialize :restricted_visibility_levels # rubocop:disable Cop/ActiveRecordSerialize + serialize :import_sources # rubocop:disable Cop/ActiveRecordSerialize + serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiveRecordSerialize + serialize :domain_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize + serialize :domain_blacklist, Array # rubocop:disable Cop/ActiveRecordSerialize + serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize + serialize :sidekiq_throttling_queues, Array # rubocop:disable Cop/ActiveRecordSerialize cache_markdown_field :sign_in_text cache_markdown_field :help_page_text diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb index 46d412fbd72..112a8778b4e 100644 --- a/app/models/audit_event.rb +++ b/app/models/audit_event.rb @@ -1,5 +1,5 @@ class AuditEvent < ActiveRecord::Base - serialize :details, Hash # rubocop:disable Cop/ActiverecordSerialize + serialize :details, Hash # rubocop:disable Cop/ActiveRecordSerialize belongs_to :user, foreign_key: :author_id diff --git a/app/models/board.rb b/app/models/board.rb index 18081a32157..97d0f550925 100644 --- a/app/models/board.rb +++ b/app/models/board.rb @@ -1,7 +1,7 @@ class Board < ActiveRecord::Base belongs_to :project - has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all + has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent validates :project, presence: true diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index a300536532b..48586ba8910 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -19,8 +19,8 @@ module Ci ) end - serialize :options # rubocop:disable Cop/ActiverecordSerialize - serialize :yaml_variables, Gitlab::Serializer::Ci::Variables # rubocop:disable Cop/ActiverecordSerialize + serialize :options # rubocop:disable Cop/ActiveRecordSerialize + serialize :yaml_variables, Gitlab::Serializer::Ci::Variables # rubocop:disable Cop/ActiveRecordSerialize delegate :name, to: :project, prefix: true @@ -176,13 +176,22 @@ module Ci # * Lowercased # * Anything not matching [a-z0-9-] is replaced with a - # * Maximum length is 63 bytes + # * First/Last Character is not a hyphen def ref_slug - slugified = ref.to_s.downcase - slugified.gsub(/[^a-z0-9]/, '-')[0..62] + ref.to_s + .downcase + .gsub(/[^a-z0-9]/, '-')[0..62] + .gsub(/(\A-+|-+\z)/, '') end # Variables whose value does not depend on environment def simple_variables + variables(environment: nil) + end + + # All variables, including those dependent on environment, which could + # contain unexpanded variables. + def variables(environment: persisted_environment) variables = predefined_variables variables += project.predefined_variables variables += pipeline.predefined_variables @@ -191,15 +200,11 @@ module Ci variables += project.deployment_variables if has_environment? variables += yaml_variables variables += user_variables - variables += project.secret_variables_for(ref).map(&:to_runner_variable) + variables += secret_variables(environment: environment) variables += trigger_request.user_variables if trigger_request - variables - end + variables += persisted_environment_variables if environment - # All variables, including those dependent on environment, which could - # contain unexpanded variables. - def variables - simple_variables.concat(persisted_environment_variables) + variables end def merge_request @@ -367,6 +372,11 @@ module Ci ] end + def secret_variables(environment: persisted_environment) + project.secret_variables_for(ref: ref, environment: environment) + .map(&:to_runner_variable) + end + def steps [Gitlab::Ci::Build::Step.from_commands(self), Gitlab::Ci::Build::Step.from_after_script(self)].compact diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 364858964b0..b646b32fc64 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -14,7 +14,7 @@ module Ci has_many :stages has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id has_many :builds, foreign_key: :commit_id - has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id + has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent # Merge requests for which the current pipeline is running against # the merge request's latest commit. @@ -326,10 +326,24 @@ module Ci end end + def ci_yaml_file_path + if project.ci_config_path.blank? + '.gitlab-ci.yml' + else + project.ci_config_path + end + end + def ci_yaml_file return @ci_yaml_file if defined?(@ci_yaml_file) - @ci_yaml_file = project.repository.gitlab_ci_yml_for(sha) rescue nil + @ci_yaml_file = begin + project.repository.gitlab_ci_yml_for(sha, ci_yaml_file_path) + rescue Rugged::ReferenceError, GRPC::NotFound, GRPC::Internal + self.yaml_errors = + "Failed to load CI/CD config file at #{ci_yaml_file_path}" + nil + end end def has_yaml_errors? @@ -377,7 +391,8 @@ module Ci def predefined_variables [ - { key: 'CI_PIPELINE_ID', value: id.to_s, public: true } + { key: 'CI_PIPELINE_ID', value: id.to_s, public: true }, + { key: 'CI_CONFIG_PATH', value: ci_yaml_file_path, public: true } ] end diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index d12f96f3d0b..f5790f9744f 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -8,7 +8,7 @@ module Ci FORM_EDITABLE = %i[description tag_list active run_untagged locked].freeze has_many :builds - has_many :runner_projects, dependent: :destroy + has_many :runner_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :projects, through: :runner_projects has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build' diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb index 564334ad1ad..c58ce5c3717 100644 --- a/app/models/ci/trigger_request.rb +++ b/app/models/ci/trigger_request.rb @@ -6,7 +6,7 @@ module Ci belongs_to :pipeline, foreign_key: :commit_id has_many :builds - serialize :variables # rubocop:disable Cop/ActiverecordSerialize + serialize :variables # rubocop:disable Cop/ActiveRecordSerialize def user_variables return [] unless variables diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index 96d6e120998..0b8d0ff881a 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -5,7 +5,7 @@ module Ci belongs_to :project - validates :key, uniqueness: { scope: :project_id } + validates :key, uniqueness: { scope: [:project_id, :environment_scope] } scope :unprotected, -> { where(protected: false) } end diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb index a7fd0a15f0f..f4f9b037957 100644 --- a/app/models/concerns/awardable.rb +++ b/app/models/concerns/awardable.rb @@ -2,7 +2,7 @@ module Awardable extend ActiveSupport::Concern included do - has_many :award_emoji, -> { includes(:user).order(:id) }, as: :awardable, dependent: :destroy + has_many :award_emoji, -> { includes(:user).order(:id) }, as: :awardable, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent if self < Participable # By default we always load award_emoji user association diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb index eb32bf3d32a..95152dcd68c 100644 --- a/app/models/concerns/cache_markdown_field.rb +++ b/app/models/concerns/cache_markdown_field.rb @@ -78,7 +78,7 @@ module CacheMarkdownField def cached_html_up_to_date?(markdown_field) html_field = cached_markdown_fields.html_field(markdown_field) - cached = !cached_html_for(markdown_field).nil? && !__send__(markdown_field).nil? + cached = cached_html_for(markdown_field).present? && __send__(markdown_field).present? return false unless cached markdown_changed = attribute_changed?(markdown_field) || false diff --git a/app/models/concerns/feature_gate.rb b/app/models/concerns/feature_gate.rb new file mode 100644 index 00000000000..5db64fe82c4 --- /dev/null +++ b/app/models/concerns/feature_gate.rb @@ -0,0 +1,7 @@ +module FeatureGate + def flipper_id + return nil if new_record? + + "#{self.class.name}:#{id}" + end +end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index d178ee4422b..23cb85600da 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -30,7 +30,7 @@ module Issuable belongs_to :updated_by, class_name: "User" belongs_to :last_edited_by, class_name: 'User' belongs_to :milestone - has_many :notes, as: :noteable, inverse_of: :noteable, dependent: :destroy do + has_many :notes, as: :noteable, inverse_of: :noteable, dependent: :destroy do # rubocop:disable Cop/ActiveRecordDependent def authors_loaded? # We check first if we're loaded to not load unnecessarily. loaded? && to_a.all? { |note| note.association(:author).loaded? } @@ -42,9 +42,9 @@ module Issuable end end - has_many :label_links, as: :target, dependent: :destroy + has_many :label_links, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :labels, through: :label_links - has_many :todos, as: :target, dependent: :destroy + has_many :todos, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_one :metrics @@ -102,6 +102,14 @@ module Issuable def locking_enabled? title_changed? || description_changed? end + + def allows_multiple_assignees? + false + end + + def has_multiple_assignees? + assignees.count > 1 + end end module ClassMethods diff --git a/app/models/concerns/mentionable/reference_regexes.rb b/app/models/concerns/mentionable/reference_regexes.rb index 1848230ec7e..2d86a70c395 100644 --- a/app/models/concerns/mentionable/reference_regexes.rb +++ b/app/models/concerns/mentionable/reference_regexes.rb @@ -14,7 +14,7 @@ module Mentionable end EXTERNAL_PATTERN = begin - issue_pattern = ExternalIssue.reference_pattern + issue_pattern = IssueTrackerService.reference_pattern link_patterns = URI.regexp(%w(http https)) reference_pattern(link_patterns, issue_pattern) end diff --git a/app/models/concerns/protected_ref.rb b/app/models/concerns/protected_ref.rb index 47e71c58557..fc6b840f7a8 100644 --- a/app/models/concerns/protected_ref.rb +++ b/app/models/concerns/protected_ref.rb @@ -17,7 +17,7 @@ module ProtectedRef class_methods do def protected_ref_access_levels(*types) types.each do |type| - has_many :"#{type}_access_levels", dependent: :destroy + has_many :"#{type}_access_levels", dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent validates :"#{type}_access_levels", length: { is: 1, message: "are restricted to a single instance per #{self.model_name.human}." } diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb index ec7796a9dbb..f5048d17d80 100644 --- a/app/models/concerns/routable.rb +++ b/app/models/concerns/routable.rb @@ -4,8 +4,8 @@ module Routable extend ActiveSupport::Concern included do - has_one :route, as: :source, autosave: true, dependent: :destroy - has_many :redirect_routes, as: :source, autosave: true, dependent: :destroy + has_one :route, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :redirect_routes, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent validates_associated :route validates :route, presence: true @@ -103,8 +103,12 @@ module Routable def full_path return uncached_full_path unless RequestStore.active? - key = "routable/full_path/#{self.class.name}/#{self.id}" - RequestStore[key] ||= uncached_full_path + RequestStore[full_path_key] ||= uncached_full_path + end + + def expires_full_path_cache + RequestStore.delete(full_path_key) if RequestStore.active? + @full_path = nil end def build_full_path @@ -135,6 +139,10 @@ module Routable path_changed? || parent_changed? end + def full_path_key + @full_path_key ||= "routable/full_path/#{self.class.name}/#{self.id}" + end + def build_full_name if parent && name parent.human_name + ' / ' + name diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb index a155a064032..fdacfa5a194 100644 --- a/app/models/concerns/sortable.rb +++ b/app/models/concerns/sortable.rb @@ -5,6 +5,25 @@ module Sortable extend ActiveSupport::Concern + module DropDefaultScopeOnFinders + # Override these methods to drop the `ORDER BY id DESC` default scope. + # See http://dba.stackexchange.com/a/110919 for why we do this. + %i[find find_by find_by!].each do |meth| + define_method meth do |*args, &block| + return super(*args, &block) if block + + unordered_relation = unscope(:order) + + # We cannot simply call `meth` on `unscope(:order)`, since that is also + # an instance of the same relation class this module is included into, + # which means we'd get infinite recursion. + # We explicitly use the original implementation to prevent this. + original_impl = method(__method__).super_method.unbind + original_impl.bind(unordered_relation).call(*args) + end + end + end + included do # By default all models should be ordered # by created_at field starting from newest @@ -18,6 +37,10 @@ module Sortable scope :order_updated_asc, -> { reorder(updated_at: :asc) } scope :order_name_asc, -> { reorder(name: :asc) } scope :order_name_desc, -> { reorder(name: :desc) } + + # All queries (relations) on this model are instances of this `relation_klass`. + relation_klass = relation_delegate_class(ActiveRecord::Relation) + relation_klass.prepend DropDefaultScopeOnFinders end module ClassMethods diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb index 647a6cad3d7..bd75f25a210 100644 --- a/app/models/concerns/spammable.rb +++ b/app/models/concerns/spammable.rb @@ -8,7 +8,7 @@ module Spammable end included do - has_one :user_agent_detail, as: :subject, dependent: :destroy + has_one :user_agent_detail, as: :subject, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent attr_accessor :spam attr_accessor :spam_log diff --git a/app/models/concerns/subscribable.rb b/app/models/concerns/subscribable.rb index f60a0f8f438..274b38a7708 100644 --- a/app/models/concerns/subscribable.rb +++ b/app/models/concerns/subscribable.rb @@ -9,7 +9,7 @@ module Subscribable extend ActiveSupport::Concern included do - has_many :subscriptions, dependent: :destroy, as: :subscribable + has_many :subscriptions, dependent: :destroy, as: :subscribable # rubocop:disable Cop/ActiveRecordDependent end def subscribed?(user, project = nil) diff --git a/app/models/concerns/time_trackable.rb b/app/models/concerns/time_trackable.rb index 9cf83440784..b517ddaebd7 100644 --- a/app/models/concerns/time_trackable.rb +++ b/app/models/concerns/time_trackable.rb @@ -18,7 +18,7 @@ module TimeTrackable validates :time_estimate, numericality: { message: 'has an invalid format' }, allow_nil: false validate :check_negative_time_spent - has_many :timelogs, dependent: :destroy + has_many :timelogs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent end def spend_time(options) diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb index 053f2a11aa0..51768dd96bc 100644 --- a/app/models/deploy_key.rb +++ b/app/models/deploy_key.rb @@ -1,5 +1,5 @@ class DeployKey < Key - has_many :deploy_keys_projects, dependent: :destroy + has_many :deploy_keys_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :projects, through: :deploy_keys_projects scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) } diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 20ef1378500..e9a60e6ce09 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -6,9 +6,9 @@ class DiffNote < Note NOTEABLE_TYPES = %w(MergeRequest Commit).freeze - serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize - serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize - serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize + serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize + serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize + serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize validates :original_position, presence: true validates :position, presence: true diff --git a/app/models/environment.rb b/app/models/environment.rb index 66c96d0f586..e9ebf0637f3 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -6,7 +6,7 @@ class Environment < ActiveRecord::Base belongs_to :project, required: true, validate: true - has_many :deployments, dependent: :destroy + has_many :deployments, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_one :last_deployment, -> { order('deployments.id DESC') }, class_name: 'Deployment' before_validation :nullify_external_url @@ -218,8 +218,7 @@ class Environment < ActiveRecord::Base end def etag_cache_key - Gitlab::Routing.url_helpers.namespace_project_environments_path( - project.namespace, + Gitlab::Routing.url_helpers.project_environments_path( project, format: :json) end diff --git a/app/models/event.rb b/app/models/event.rb index 29bc141c5cd..8d93a228494 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -50,7 +50,7 @@ class Event < ActiveRecord::Base belongs_to :target, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations # For Hash only - serialize :data # rubocop:disable Cop/ActiverecordSerialize + serialize :data # rubocop:disable Cop/ActiveRecordSerialize # Callbacks after_create :reset_project_activity diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb index e63f89a9f85..0bf18e529f0 100644 --- a/app/models/external_issue.rb +++ b/app/models/external_issue.rb @@ -38,11 +38,6 @@ class ExternalIssue @project.id end - # Pattern used to extract `JIRA-123` issue references from text - def self.reference_pattern - @reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)} - end - def to_reference(_from_project = nil, full: nil) id end diff --git a/app/models/forked_project_link.rb b/app/models/forked_project_link.rb index 36cf7ad6a28..8d35864eff6 100644 --- a/app/models/forked_project_link.rb +++ b/app/models/forked_project_link.rb @@ -1,4 +1,4 @@ class ForkedProjectLink < ActiveRecord::Base - belongs_to :forked_to_project, class_name: 'Project' - belongs_to :forked_from_project, class_name: 'Project' + belongs_to :forked_to_project, -> { where.not(pending_delete: true) }, class_name: 'Project' + belongs_to :forked_from_project, -> { where.not(pending_delete: true) }, class_name: 'Project' end diff --git a/app/models/group.rb b/app/models/group.rb index a6fdb30f84c..b93fce6100d 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -8,7 +8,7 @@ class Group < Namespace include Referable include SelectForProjectAuthorization - has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source + has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent alias_method :members, :group_members has_many :users, through: :group_members has_many :owners, @@ -16,11 +16,11 @@ class Group < Namespace through: :group_members, source: :user - has_many :requesters, -> { where.not(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' + has_many :requesters, -> { where.not(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent - has_many :project_group_links, dependent: :destroy + has_many :project_group_links, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :shared_projects, through: :project_group_links, source: :project - has_many :notification_settings, dependent: :destroy, as: :source + has_many :notification_settings, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent has_many :labels, class_name: 'GroupLabel' validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } @@ -31,7 +31,7 @@ class Group < Namespace validates :two_factor_grace_period, presence: true, numericality: { greater_than_or_equal_to: 0 } mount_uploader :avatar, AvatarUploader - has_many :uploads, as: :model, dependent: :destroy + has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent after_create :post_create_hook after_destroy :post_destroy_hook diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 7503f3739c3..7a9f8997959 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -12,7 +12,7 @@ class WebHook < ActiveRecord::Base default_value_for :repository_update_events, false default_value_for :enable_ssl_verification, true - has_many :web_hook_logs, dependent: :destroy + has_many :web_hook_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent scope :push_hooks, -> { where(push_events: true) } scope :tag_push_hooks, -> { where(tag_push_events: true) } diff --git a/app/models/hooks/web_hook_log.rb b/app/models/hooks/web_hook_log.rb index d73cfcf630d..e72c125fb69 100644 --- a/app/models/hooks/web_hook_log.rb +++ b/app/models/hooks/web_hook_log.rb @@ -1,9 +1,9 @@ class WebHookLog < ActiveRecord::Base belongs_to :web_hook - serialize :request_headers, Hash # rubocop:disable Cop/ActiverecordSerialize - serialize :request_data, Hash # rubocop:disable Cop/ActiverecordSerialize - serialize :response_headers, Hash # rubocop:disable Cop/ActiverecordSerialize + serialize :request_headers, Hash # rubocop:disable Cop/ActiveRecordSerialize + serialize :request_data, Hash # rubocop:disable Cop/ActiveRecordSerialize + serialize :response_headers, Hash # rubocop:disable Cop/ActiveRecordSerialize validates :web_hook, presence: true diff --git a/app/models/issue.rb b/app/models/issue.rb index 3a9a6dba601..01f985823e1 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -23,9 +23,14 @@ class Issue < ActiveRecord::Base belongs_to :project belongs_to :moved_to, class_name: 'Issue' - has_many :events, as: :target, dependent: :destroy + has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent - has_many :merge_requests_closing_issues, class_name: 'MergeRequestsClosingIssues', dependent: :delete_all + has_many :merge_requests_closing_issues, + class_name: 'MergeRequestsClosingIssues', + dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent + + has_many :issue_assignees + has_many :assignees, class_name: "User", through: :issue_assignees has_many :issue_assignees has_many :assignees, class_name: "User", through: :issue_assignees @@ -295,11 +300,7 @@ class Issue < ActiveRecord::Base end def expire_etag_cache - key = Gitlab::Routing.url_helpers.realtime_changes_namespace_project_issue_path( - project.namespace, - project, - self - ) + key = Gitlab::Routing.url_helpers.realtime_changes_project_issue_path(project, self) Gitlab::EtagCaching::Store.new.touch(key) end end diff --git a/app/models/label.rb b/app/models/label.rb index ed6a8411da9..674bb3f2720 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -15,9 +15,9 @@ class Label < ActiveRecord::Base default_value_for :color, DEFAULT_COLOR - has_many :lists, dependent: :destroy + has_many :lists, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :priorities, class_name: 'LabelPriority' - has_many :label_links, dependent: :destroy + has_many :label_links, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :issues, through: :label_links, source: :target, source_type: 'Issue' has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest' diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb index 2d5909ab25e..c36be956ff0 100644 --- a/app/models/legacy_diff_note.rb +++ b/app/models/legacy_diff_note.rb @@ -7,7 +7,7 @@ class LegacyDiffNote < Note include NoteOnDiff - serialize :st_diff # rubocop:disable Cop/ActiverecordSerialize + serialize :st_diff # rubocop:disable Cop/ActiveRecordSerialize validates :line_code, presence: true, line_code: true diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb index 7712d5783e0..b7cf96abe83 100644 --- a/app/models/lfs_object.rb +++ b/app/models/lfs_object.rb @@ -1,5 +1,5 @@ class LfsObject < ActiveRecord::Base - has_many :lfs_objects_projects, dependent: :destroy + has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :projects, through: :lfs_objects_projects validates :oid, presence: true, uniqueness: true diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index c099d731082..2752181ed79 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -12,19 +12,21 @@ class MergeRequest < ActiveRecord::Base belongs_to :source_project, class_name: "Project" belongs_to :merge_user, class_name: "User" - has_many :merge_request_diffs, dependent: :destroy + has_many :merge_request_diffs has_one :merge_request_diff, -> { order('merge_request_diffs.id DESC') } belongs_to :head_pipeline, foreign_key: "head_pipeline_id", class_name: "Ci::Pipeline" - has_many :events, as: :target, dependent: :destroy + has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent - has_many :merge_requests_closing_issues, class_name: 'MergeRequestsClosingIssues', dependent: :delete_all + has_many :merge_requests_closing_issues, + class_name: 'MergeRequestsClosingIssues', + dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent belongs_to :assignee, class_name: "User" - serialize :merge_params, Hash # rubocop:disable Cop/ActiverecordSerialize + serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize after_create :ensure_merge_request_diff, unless: :importing? after_update :reload_diff_if_branch_changed @@ -197,11 +199,19 @@ class MergeRequest < ActiveRecord::Base } end - # This method is needed for compatibility with issues to not mess view and other code + # These method are needed for compatibility with issues to not mess view and other code def assignees Array(assignee) end + def assignee_ids + Array(assignee_id) + end + + def assignee_ids=(ids) + write_attribute(:assignee_id, ids.last) + end + def assignee_or_author?(user) author_id == user.id || assignee_id == user.id end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index f1ee4d3f7a9..0cbf58a2325 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -12,8 +12,8 @@ class MergeRequestDiff < ActiveRecord::Base belongs_to :merge_request has_many :merge_request_diff_files, -> { order(:merge_request_diff_id, :relative_order) } - serialize :st_commits # rubocop:disable Cop/ActiverecordSerialize - serialize :st_diffs # rubocop:disable Cop/ActiverecordSerialize + serialize :st_commits # rubocop:disable Cop/ActiveRecordSerialize + serialize :st_diffs # rubocop:disable Cop/ActiveRecordSerialize state_machine :state, initial: :empty do state :collected diff --git a/app/models/milestone.rb b/app/models/milestone.rb index d2e2749f70d..c0ccbf8e27e 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -21,7 +21,7 @@ class Milestone < ActiveRecord::Base has_many :issues has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues has_many :merge_requests - has_many :events, as: :target, dependent: :destroy + has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent scope :active, -> { with_state(:active) } scope :closed, -> { with_state(:closed) } diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 743e0513e02..15713fc5f6d 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -15,13 +15,13 @@ class Namespace < ActiveRecord::Base cache_markdown_field :description, pipeline: :description - has_many :projects, dependent: :destroy + has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :project_statistics belongs_to :owner, class_name: "User" belongs_to :parent, class_name: "Namespace" has_many :children, class_name: "Namespace", foreign_key: :parent_id - has_one :chat_team, dependent: :destroy + has_one :chat_team, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent validates :owner, presence: true, unless: ->(n) { n.type == "Group" } validates :name, @@ -219,6 +219,12 @@ class Namespace < ActiveRecord::Base parent.present? end + def soft_delete_without_removing_associations + # We can't use paranoia's `#destroy` since this will hard-delete projects. + # Project uses `pending_delete` instead of the acts_as_paranoia gem. + self.deleted_at = Time.now + end + private def repository_storage_paths diff --git a/app/models/note.rb b/app/models/note.rb index ca6999427c0..3d39047d32f 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -46,8 +46,8 @@ class Note < ActiveRecord::Base belongs_to :updated_by, class_name: "User" belongs_to :last_edited_by, class_name: 'User' - has_many :todos, dependent: :destroy - has_many :events, as: :target, dependent: :destroy + has_many :todos, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_one :system_note_metadata delegate :gfm_reference, :local_reference, to: :noteable @@ -330,8 +330,7 @@ class Note < ActiveRecord::Base def expire_etag_cache return unless for_issue? - key = Gitlab::Routing.url_helpers.namespace_project_noteable_notes_path( - noteable.project.namespace, + key = Gitlab::Routing.url_helpers.project_noteable_notes_path( noteable.project, target_type: noteable_type.underscore, target_id: noteable.id diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb index 6e13f9b2089..654be927ed8 100644 --- a/app/models/personal_access_token.rb +++ b/app/models/personal_access_token.rb @@ -3,7 +3,7 @@ class PersonalAccessToken < ActiveRecord::Base include TokenAuthenticatable add_authentication_token_field :token - serialize :scopes, Array # rubocop:disable Cop/ActiverecordSerialize + serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize belongs_to :user diff --git a/app/models/project.rb b/app/models/project.rb index a75c5209955..74c15d2508b 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -59,6 +59,7 @@ class Project < ActiveRecord::Base update_column(:last_repository_updated_at, self.created_at) end + before_destroy :remove_private_deploy_keys after_destroy :remove_pages # update visibility_level of forks @@ -80,96 +81,108 @@ class Project < ActiveRecord::Base belongs_to :namespace has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event' - has_many :boards, before_add: :validate_board_limit, dependent: :destroy + has_many :boards, before_add: :validate_board_limit # Project services - has_one :campfire_service, dependent: :destroy - has_one :drone_ci_service, dependent: :destroy - has_one :emails_on_push_service, dependent: :destroy - has_one :pipelines_email_service, dependent: :destroy - has_one :irker_service, dependent: :destroy - has_one :pivotaltracker_service, dependent: :destroy - has_one :hipchat_service, dependent: :destroy - has_one :flowdock_service, dependent: :destroy - has_one :assembla_service, dependent: :destroy - has_one :asana_service, dependent: :destroy - has_one :gemnasium_service, dependent: :destroy - has_one :mattermost_slash_commands_service, dependent: :destroy - has_one :mattermost_service, dependent: :destroy - has_one :slack_slash_commands_service, dependent: :destroy - has_one :slack_service, dependent: :destroy - has_one :buildkite_service, dependent: :destroy - has_one :bamboo_service, dependent: :destroy - has_one :teamcity_service, dependent: :destroy - has_one :pushover_service, dependent: :destroy - has_one :jira_service, dependent: :destroy - has_one :redmine_service, dependent: :destroy - has_one :custom_issue_tracker_service, dependent: :destroy - has_one :bugzilla_service, dependent: :destroy - has_one :gitlab_issue_tracker_service, dependent: :destroy, inverse_of: :project - has_one :external_wiki_service, dependent: :destroy - has_one :kubernetes_service, dependent: :destroy, inverse_of: :project - has_one :prometheus_service, dependent: :destroy, inverse_of: :project - has_one :mock_ci_service, dependent: :destroy - has_one :mock_deployment_service, dependent: :destroy - has_one :mock_monitoring_service, dependent: :destroy - has_one :microsoft_teams_service, dependent: :destroy - - has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" + has_one :campfire_service + has_one :drone_ci_service + has_one :emails_on_push_service + has_one :pipelines_email_service + has_one :irker_service + has_one :pivotaltracker_service + has_one :hipchat_service + has_one :flowdock_service + has_one :assembla_service + has_one :asana_service + has_one :gemnasium_service + has_one :mattermost_slash_commands_service + has_one :mattermost_service + has_one :slack_slash_commands_service + has_one :slack_service + has_one :buildkite_service + has_one :bamboo_service + has_one :teamcity_service + has_one :pushover_service + has_one :jira_service + has_one :redmine_service + has_one :custom_issue_tracker_service + has_one :bugzilla_service + has_one :gitlab_issue_tracker_service, inverse_of: :project + has_one :external_wiki_service + has_one :kubernetes_service, inverse_of: :project + has_one :prometheus_service, inverse_of: :project + has_one :mock_ci_service + has_one :mock_deployment_service + has_one :mock_monitoring_service + has_one :microsoft_teams_service + + has_one :forked_project_link, foreign_key: "forked_to_project_id" has_one :forked_from_project, through: :forked_project_link has_many :forked_project_links, foreign_key: "forked_from_project_id" has_many :forks, through: :forked_project_links, source: :forked_to_project # Merge Requests for target project should be removed with it - has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id' - has_many :issues, dependent: :destroy - has_many :labels, dependent: :destroy, class_name: 'ProjectLabel' - has_many :services, dependent: :destroy - has_many :events, dependent: :destroy - has_many :milestones, dependent: :destroy - has_many :notes, dependent: :destroy - has_many :snippets, dependent: :destroy, class_name: 'ProjectSnippet' - has_many :hooks, dependent: :destroy, class_name: 'ProjectHook' - has_many :protected_branches, dependent: :destroy - has_many :protected_tags, dependent: :destroy + has_many :merge_requests, foreign_key: 'target_project_id' + has_many :issues + has_many :labels, class_name: 'ProjectLabel' + has_many :services + has_many :events + has_many :milestones + has_many :notes + has_many :snippets, class_name: 'ProjectSnippet' + has_many :hooks, class_name: 'ProjectHook' + has_many :protected_branches + has_many :protected_tags has_many :project_authorizations has_many :authorized_users, through: :project_authorizations, source: :user, class_name: 'User' - has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source + has_many :project_members, -> { where(requested_at: nil) }, + as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent + alias_method :members, :project_members has_many :users, through: :project_members - has_many :requesters, -> { where.not(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'ProjectMember' + has_many :requesters, -> { where.not(requested_at: nil) }, + as: :source, class_name: 'ProjectMember', dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent - has_many :deploy_keys_projects, dependent: :destroy + has_many :deploy_keys_projects has_many :deploy_keys, through: :deploy_keys_projects - has_many :users_star_projects, dependent: :destroy + has_many :users_star_projects has_many :starrers, through: :users_star_projects, source: :user - has_many :releases, dependent: :destroy - has_many :lfs_objects_projects, dependent: :destroy + has_many :releases + has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :lfs_objects, through: :lfs_objects_projects - has_many :project_group_links, dependent: :destroy + has_many :project_group_links has_many :invited_groups, through: :project_group_links, source: :group - has_many :pages_domains, dependent: :destroy - has_many :todos, dependent: :destroy - has_many :notification_settings, dependent: :destroy, as: :source - - has_one :import_data, dependent: :delete, class_name: 'ProjectImportData' - has_one :project_feature, dependent: :destroy - has_one :statistics, class_name: 'ProjectStatistics', dependent: :delete - has_many :container_repositories, dependent: :destroy - - has_many :commit_statuses, dependent: :destroy - has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline' - has_many :builds, class_name: 'Ci::Build' # the builds are created from the commit_statuses - has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' + has_many :pages_domains + has_many :todos + has_many :notification_settings, as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent + + has_one :import_data, class_name: 'ProjectImportData' + has_one :project_feature + has_one :statistics, class_name: 'ProjectStatistics' + + # Container repositories need to remove data from the container registry, + # which is not managed by the DB. Hence we're still using dependent: :destroy + # here. + has_many :container_repositories, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + + has_many :commit_statuses + has_many :pipelines, class_name: 'Ci::Pipeline' + + # Ci::Build objects store data on the file system such as artifact files and + # build traces. Currently there's no efficient way of removing this data in + # bulk that doesn't involve loading the rows into memory. As a result we're + # still using `dependent: :destroy` here. + has_many :builds, class_name: 'Ci::Build', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :runner_projects, class_name: 'Ci::RunnerProject' has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner' has_many :variables, class_name: 'Ci::Variable' - has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger' - has_many :environments, dependent: :destroy - has_many :deployments, dependent: :destroy - has_many :pipeline_schedules, dependent: :destroy, class_name: 'Ci::PipelineSchedule' + has_many :triggers, class_name: 'Ci::Trigger' + has_many :environments + has_many :deployments + has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule' has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner' @@ -186,6 +199,11 @@ class Project < ActiveRecord::Base # Validations validates :creator, presence: true, on: :create validates :description, length: { maximum: 2000 }, allow_blank: true + validates :ci_config_path, + format: { without: /\.{2}/, + message: 'cannot include directory traversal.' }, + length: { maximum: 255 }, + allow_blank: true validates :name, presence: true, length: { maximum: 255 }, @@ -219,7 +237,7 @@ class Project < ActiveRecord::Base before_save :ensure_runners_token mount_uploader :avatar, AvatarUploader - has_many :uploads, as: :model, dependent: :destroy + has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent # Scopes scope :pending_delete, -> { where(pending_delete: true) } @@ -521,6 +539,11 @@ class Project < ActiveRecord::Base import_data&.destroy end + def ci_config_path=(value) + # Strip all leading slashes so that //foo -> foo + super(value&.sub(%r{\A/+}, '')&.delete("\0")) + end + def import_url=(value) return super(value) unless Gitlab::UrlSanitizer.valid?(value) @@ -675,7 +698,7 @@ class Project < ActiveRecord::Base end def web_url - Gitlab::Routing.url_helpers.namespace_project_url(self.namespace, self) + Gitlab::Routing.url_helpers.project_url(self) end def new_issue_address(author) @@ -727,8 +750,8 @@ class Project < ActiveRecord::Base end end - def issue_reference_pattern - issues_tracker.reference_pattern + def external_issue_reference_pattern + external_issue_tracker.class.reference_pattern end def default_issues_tracker? @@ -815,7 +838,7 @@ class Project < ActiveRecord::Base end def ci_service - @ci_service ||= ci_services.reorder(nil).find_by(active: true) + @ci_service ||= ci_services.find_by(active: true) end def deployment_services @@ -823,7 +846,7 @@ class Project < ActiveRecord::Base end def deployment_service - @deployment_service ||= deployment_services.reorder(nil).find_by(active: true) + @deployment_service ||= deployment_services.find_by(active: true) end def monitoring_services @@ -831,7 +854,7 @@ class Project < ActiveRecord::Base end def monitoring_service - @monitoring_service ||= monitoring_services.reorder(nil).find_by(active: true) + @monitoring_service ||= monitoring_services.find_by(active: true) end def jira_tracker? @@ -851,7 +874,7 @@ class Project < ActiveRecord::Base def avatar_url(**args) # We use avatar_path instead of overriding avatar_url because of carrierwave. # See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11001/diffs#note_28659864 - avatar_path(args) || (Gitlab::Routing.url_helpers.namespace_project_avatar_url(namespace, self) if avatar_in_git) + avatar_path(args) || (Gitlab::Routing.url_helpers.project_avatar_url(self) if avatar_in_git) end # For compatibility with old code @@ -963,6 +986,7 @@ class Project < ActiveRecord::Base begin gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") send_move_instructions(old_path_with_namespace) + expires_full_path_cache @old_path_with_namespace = old_path_with_namespace @@ -1014,7 +1038,8 @@ class Project < ActiveRecord::Base namespace: namespace.name, visibility_level: visibility_level, path_with_namespace: path_with_namespace, - default_branch: default_branch + default_branch: default_branch, + ci_config_path: ci_config_path } # Backward compatibility @@ -1073,21 +1098,21 @@ class Project < ActiveRecord::Base merge_requests.where(source_project_id: self.id) end - def create_repository + def create_repository(force: false) # Forked import is handled asynchronously - unless forked? - if gitlab_shell.add_repository(repository_storage_path, path_with_namespace) - repository.after_create - true - else - errors.add(:base, 'Failed to create repository via gitlab-shell') - false - end + return if forked? && !force + + if gitlab_shell.add_repository(repository_storage_path, path_with_namespace) + repository.after_create + true + else + errors.add(:base, 'Failed to create repository via gitlab-shell') + false end end def ensure_repository - create_repository unless repository_exists? + create_repository(force: true) unless repository_exists? end def repository_exists? @@ -1228,7 +1253,13 @@ class Project < ActiveRecord::Base File.join(pages_path, 'public') end + def remove_private_deploy_keys + deploy_keys.where(public: false).delete_all + end + def remove_pages + ::Projects::UpdatePagesConfigurationService.new(self).execute + # 1. We rename pages to temporary directory # 2. We wait 5 minutes, due to NFS caching # 3. We asynchronously remove pages with force @@ -1314,7 +1345,8 @@ class Project < ActiveRecord::Base variables end - def secret_variables_for(ref) + def secret_variables_for(ref:, environment: nil) + # EE would use the environment if protected_for?(ref) variables else diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index e3cafd4d1c6..37730474324 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -10,7 +10,7 @@ class ProjectImportData < ActiveRecord::Base insecure_mode: true, algorithm: 'aes-256-cbc' - serialize :data, JSON # rubocop:disable Cop/ActiverecordSerialize + serialize :data, JSON # rubocop:disable Cop/ActiveRecordSerialize validates :project, presence: true diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb index ad4eb9536e1..5e31f393bbe 100644 --- a/app/models/project_services/gitlab_issue_tracker_service.rb +++ b/app/models/project_services/gitlab_issue_tracker_service.rb @@ -12,26 +12,26 @@ class GitlabIssueTrackerService < IssueTrackerService end def project_url - namespace_project_issues_url(project.namespace, project) + project_issues_url(project) end def new_issue_url - new_namespace_project_issue_url(namespace_id: project.namespace, project_id: project) + new_project_issue_url(project) end def issue_url(iid) - namespace_project_issue_url(namespace_id: project.namespace, project_id: project, id: iid) + project_issue_url(project, id: iid) end def project_path - namespace_project_issues_path(project.namespace, project) + project_issues_path(project) end def new_issue_path - new_namespace_project_issue_path(namespace_id: project.namespace, project_id: project) + new_project_issue_path(project) end def issue_path(iid) - namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: iid) + project_issue_path(project, id: iid) end end diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index ff138b9066d..1fa4cd4db30 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -5,7 +5,10 @@ class IssueTrackerService < Service # Pattern used to extract links from comments # Override this method on services that uses different patterns - def reference_pattern + # This pattern does not support cross-project references + # The other code assumes that this pattern is a superset of all + # overriden patterns. See ReferenceRegexes::EXTERNAL_PATTERN + def self.reference_pattern @reference_pattern ||= %r{(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?<issue>\d+)} end @@ -18,7 +21,7 @@ class IssueTrackerService < Service end def project_path - project_url + read_attribute(:project_url) end def new_issue_path diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 2450fb43212..8af642b44aa 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -18,7 +18,7 @@ class JiraService < IssueTrackerService end # {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1 - def reference_pattern + def self.reference_pattern @reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)} end @@ -152,8 +152,8 @@ class JiraService < IssueTrackerService url: resource_url(user_path(author)) }, project: { - name: self.project.path_with_namespace, - url: resource_url(namespace_project_path(project.namespace, self.project)) + name: project.path_with_namespace, + url: resource_url(namespace_project_path(project.namespace, project)) # rubocop:disable Cop/ProjectPathHelper }, entity: { name: noteable_type.humanize.downcase, diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb index 48e7802c557..62f7c057c5b 100644 --- a/app/models/project_services/kubernetes_service.rb +++ b/app/models/project_services/kubernetes_service.rb @@ -96,10 +96,13 @@ class KubernetesService < DeploymentService end def predefined_variables + config = YAML.dump(kubeconfig) + variables = [ { key: 'KUBE_URL', value: api_url, public: true }, { key: 'KUBE_TOKEN', value: token, public: false }, - { key: 'KUBE_NAMESPACE', value: actual_namespace, public: true } + { key: 'KUBE_NAMESPACE', value: actual_namespace, public: true }, + { key: 'KUBECONFIG', value: config, public: false, file: true } ] if ca_pem.present? @@ -135,6 +138,14 @@ class KubernetesService < DeploymentService private + def kubeconfig + to_kubeconfig( + url: api_url, + namespace: actual_namespace, + token: token, + ca_pem: ca_pem) + end + def namespace_placeholder default_namespace || TEMPLATE_PLACEHOLDER end diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb index 4592cb747a0..eb4da68bb7e 100644 --- a/app/models/project_services/slash_commands_service.rb +++ b/app/models/project_services/slash_commands_service.rb @@ -5,7 +5,7 @@ class SlashCommandsService < Service prop_accessor :token - has_many :chat_names, foreign_key: :service_id, dependent: :destroy + has_many :chat_names, foreign_key: :service_id, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent def valid_token?(token) self.respond_to?(:token) && diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index f26ee57510c..beaadbbd1ab 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -31,7 +31,7 @@ class ProjectWiki end def web_url - Gitlab::Routing.url_helpers.namespace_project_wiki_url(@project.namespace, @project, :home) + Gitlab::Routing.url_helpers.project_wiki_url(@project, :home) end def url_to_repo diff --git a/app/models/repository.rb b/app/models/repository.rb index 8c24e722a8b..10b429c707e 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -931,7 +931,7 @@ class Repository def is_ancestor?(ancestor_id, descendant_id) return false if ancestor_id.nil? || descendant_id.nil? - + Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled| if is_enabled raw_repository.is_ancestor?(ancestor_id, descendant_id) @@ -1078,8 +1078,8 @@ class Repository blob_data_at(sha, '.gitlab/route-map.yml') end - def gitlab_ci_yml_for(sha) - blob_data_at(sha, '.gitlab-ci.yml') + def gitlab_ci_yml_for(sha, path = '.gitlab-ci.yml') + blob_data_at(sha, path) end private diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index edde7bedbab..298569cb7a6 100644 --- a/app/models/sent_notification.rb +++ b/app/models/sent_notification.rb @@ -1,5 +1,5 @@ class SentNotification < ActiveRecord::Base - serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize + serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize belongs_to :project belongs_to :noteable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations diff --git a/app/models/service.rb b/app/models/service.rb index 6a0b0a5c522..6b64079215f 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -2,7 +2,7 @@ # and implement a set of methods class Service < ActiveRecord::Base include Sortable - serialize :properties, JSON # rubocop:disable Cop/ActiverecordSerialize + serialize :properties, JSON # rubocop:disable Cop/ActiveRecordSerialize default_value_for :active, false default_value_for :push_events, true @@ -51,6 +51,14 @@ class Service < ActiveRecord::Base active end + def show_active_box? + true + end + + def editable? + true + end + def template? template end diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 54014df43b0..09d5ff46618 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -30,16 +30,14 @@ class Snippet < ActiveRecord::Base belongs_to :author, class_name: 'User' belongs_to :project - has_many :notes, as: :noteable, dependent: :destroy + has_many :notes, as: :noteable, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent delegate :name, :email, to: :author, prefix: true, allow_nil: true validates :author, presence: true validates :title, presence: true, length: { maximum: 255 } validates :file_name, - length: { maximum: 255 }, - format: { with: Gitlab::Regex.file_name_regex, - message: Gitlab::Regex.file_name_regex_message } + length: { maximum: 255 } validates :content, presence: true validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values } diff --git a/app/models/user.rb b/app/models/user.rb index 35e0d021c47..4411a06d429 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -11,6 +11,7 @@ class User < ActiveRecord::Base include CaseSensitivity include TokenAuthenticatable include IgnorableColumn + include FeatureGate DEFAULT_NOTIFICATION_LEVEL = :participating @@ -40,7 +41,7 @@ class User < ActiveRecord::Base otp_secret_encryption_key: Gitlab::Application.secrets.otp_key_base devise :two_factor_backupable, otp_number_of_backup_codes: 10 - serialize :otp_backup_codes, JSON # rubocop:disable Cop/ActiverecordSerialize + serialize :otp_backup_codes, JSON # rubocop:disable Cop/ActiveRecordSerialize devise :lockable, :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :confirmable, :registerable @@ -66,24 +67,24 @@ class User < ActiveRecord::Base # # Namespace for personal projects - has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, autosave: true + has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, autosave: true # rubocop:disable Cop/ActiveRecordDependent # Profile has_many :keys, -> do type = Key.arel_table[:type] where(type.not_eq('DeployKey').or(type.eq(nil))) - end, dependent: :destroy - has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :destroy + end, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent - has_many :emails, dependent: :destroy - has_many :personal_access_tokens, dependent: :destroy - has_many :identities, dependent: :destroy, autosave: true - has_many :u2f_registrations, dependent: :destroy - has_many :chat_names, dependent: :destroy + has_many :emails, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :personal_access_tokens, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :identities, dependent: :destroy, autosave: true # rubocop:disable Cop/ActiveRecordDependent + has_many :u2f_registrations, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :chat_names, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent # Groups - has_many :members, dependent: :destroy - has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, source: 'GroupMember' + has_many :members, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, source: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent has_many :groups, through: :group_members has_many :owned_groups, -> { where members: { access_level: Gitlab::Access::OWNER } }, through: :group_members, source: :group has_many :masters_groups, -> { where members: { access_level: Gitlab::Access::MASTER } }, through: :group_members, source: :group @@ -91,35 +92,35 @@ class User < ActiveRecord::Base # Projects has_many :groups_projects, through: :groups, source: :projects has_many :personal_projects, through: :namespace, source: :projects - has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy + has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :projects, through: :project_members has_many :created_projects, foreign_key: :creator_id, class_name: 'Project' - has_many :users_star_projects, dependent: :destroy + has_many :users_star_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :starred_projects, through: :users_star_projects, source: :project has_many :project_authorizations has_many :authorized_projects, through: :project_authorizations, source: :project - has_many :snippets, dependent: :destroy, foreign_key: :author_id - has_many :notes, dependent: :destroy, foreign_key: :author_id - has_many :issues, dependent: :destroy, foreign_key: :author_id - has_many :merge_requests, dependent: :destroy, foreign_key: :author_id - has_many :events, dependent: :destroy, foreign_key: :author_id - has_many :subscriptions, dependent: :destroy + has_many :snippets, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent + has_many :notes, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent + has_many :issues, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent + has_many :merge_requests, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent + has_many :events, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent + has_many :subscriptions, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id, class_name: "Event" - has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy - has_one :abuse_report, dependent: :destroy, foreign_key: :user_id - has_many :reported_abuse_reports, dependent: :destroy, foreign_key: :reporter_id, class_name: "AbuseReport" - has_many :spam_logs, dependent: :destroy - has_many :builds, dependent: :nullify, class_name: 'Ci::Build' - has_many :pipelines, dependent: :nullify, class_name: 'Ci::Pipeline' - has_many :todos, dependent: :destroy - has_many :notification_settings, dependent: :destroy - has_many :award_emoji, dependent: :destroy - has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id + has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_one :abuse_report, dependent: :destroy, foreign_key: :user_id # rubocop:disable Cop/ActiveRecordDependent + has_many :reported_abuse_reports, dependent: :destroy, foreign_key: :reporter_id, class_name: "AbuseReport" # rubocop:disable Cop/ActiveRecordDependent + has_many :spam_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :builds, dependent: :nullify, class_name: 'Ci::Build' # rubocop:disable Cop/ActiveRecordDependent + has_many :pipelines, dependent: :nullify, class_name: 'Ci::Pipeline' # rubocop:disable Cop/ActiveRecordDependent + has_many :todos, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :notification_settings, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :award_emoji, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id # rubocop:disable Cop/ActiveRecordDependent has_many :issue_assignees has_many :assigned_issues, class_name: "Issue", through: :issue_assignees, source: :issue - has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" + has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent # # Validations @@ -210,7 +211,7 @@ class User < ActiveRecord::Base end mount_uploader :avatar, AvatarUploader - has_many :uploads, as: :model, dependent: :destroy + has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent # Scopes scope :admins, -> { where(admin: true) } diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb index 00067ce756e..a605a3457c8 100644 --- a/app/policies/base_policy.rb +++ b/app/policies/base_policy.rb @@ -1,6 +1,8 @@ -require 'declarative_policy' +require_dependency 'declarative_policy' class BasePolicy < DeclarativePolicy::Base + include Gitlab::CurrentSettings + desc "User is an instance admin" with_options scope: :user, score: 0 condition(:admin) { @user&.admin? } @@ -10,4 +12,9 @@ class BasePolicy < DeclarativePolicy::Base with_options scope: :user, score: 0 condition(:can_create_group) { @user&.can_create_group } + + desc "The application is restricted from public visibility" + condition(:restricted_public_level, scope: :global) do + current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) + end end diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb index 535faa922dd..55eefa76d3f 100644 --- a/app/policies/global_policy.rb +++ b/app/policies/global_policy.rb @@ -11,10 +11,16 @@ class GlobalPolicy < BasePolicy with_options scope: :user, score: 0 condition(:access_locked) { @user.access_locked? } - rule { anonymous }.prevent_all + rule { anonymous }.policy do + prevent :log_in + prevent :access_api + prevent :access_git + prevent :receive_notifications + prevent :use_quick_actions + prevent :create_group + end rule { default }.policy do - enable :read_users_list enable :log_in enable :access_api enable :access_git @@ -37,4 +43,8 @@ class GlobalPolicy < BasePolicy rule { access_locked }.policy do prevent :log_in end + + rule { ~restricted_public_level }.policy do + enable :read_users_list + end end diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb index 0181ddf85e0..0905ddd9b38 100644 --- a/app/policies/user_policy.rb +++ b/app/policies/user_policy.rb @@ -1,11 +1,4 @@ class UserPolicy < BasePolicy - include Gitlab::CurrentSettings - - desc "The application is restricted from public visibility" - condition(:restricted_public_level, scope: :global) do - current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) - end - desc "The current user is the user in question" condition(:user_is_self, score: 0) { @subject == @user } diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb index 8bf35953d29..6ba1d3165e9 100644 --- a/app/presenters/merge_request_presenter.rb +++ b/app/presenters/merge_request_presenter.rb @@ -20,30 +20,25 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated def cancel_merge_when_pipeline_succeeds_path if can_cancel_merge_when_pipeline_succeeds?(current_user) - cancel_merge_when_pipeline_succeeds_namespace_project_merge_request_path( - project.namespace, - project, - merge_request) + cancel_merge_when_pipeline_succeeds_project_merge_request_path(project, merge_request) end end def create_issue_to_resolve_discussions_path if can?(current_user, :create_issue, project) && project.issues_enabled? - new_namespace_project_issue_path(project.namespace, - project, - merge_request_to_resolve_discussions_of: iid) + new_project_issue_path(project, merge_request_to_resolve_discussions_of: iid) end end def remove_wip_path if can?(current_user, :update_merge_request, merge_request.project) - remove_wip_namespace_project_merge_request_path(project.namespace, project, merge_request) + remove_wip_project_merge_request_path(project, merge_request) end end def merge_path if can_be_merged_by?(current_user) - merge_namespace_project_merge_request_path(project.namespace, project, merge_request) + merge_project_merge_request_path(project, merge_request) end end @@ -55,7 +50,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated notice_now: edit_in_new_fork_notice_now } - namespace_project_forks_path(merge_request.project.namespace, merge_request.project, + project_forks_path(merge_request.project, namespace_key: current_user.namespace.id, continue: continue_params) end @@ -69,7 +64,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated notice_now: edit_in_new_fork_notice_now } - namespace_project_forks_path(project.namespace, project, + project_forks_path(project, namespace_key: current_user.namespace.id, continue: continue_params) end @@ -77,19 +72,19 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated def conflict_resolution_path if conflicts.can_be_resolved_in_ui? && conflicts.can_be_resolved_by?(current_user) - conflicts_namespace_project_merge_request_path(project.namespace, project, merge_request) + conflicts_project_merge_request_path(project, merge_request) end end def target_branch_commits_path if target_branch_exists? - namespace_project_commits_path(project.namespace, project, target_branch) + project_commits_path(project, target_branch) end end def source_branch_path if source_branch_exists? - namespace_project_branch_path(source_project.namespace, source_project, source_branch) + project_branch_path(source_project, source_branch) end end @@ -99,7 +94,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated if source_branch_exists? namespace = link_to(namespace, project_path(source_project)) - branch = link_to(branch, namespace_project_commits_path(source_project.namespace, source_project, source_branch)) + branch = link_to(branch, project_commits_path(source_project, source_branch)) end if for_fork? @@ -136,7 +131,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated merge_request: merge_request, closes_issues: closing_issues ).assignable_issues - path = assign_related_issues_namespace_project_merge_request_path(project.namespace, project, merge_request) + path = assign_related_issues_project_merge_request_path(project, merge_request) if issues.present? pluralize_this_issue = issues.count > 1 ? "these issues" : "this issue" link_to "Assign yourself to #{pluralize_this_issue}", path, method: :post diff --git a/app/serializers/build_action_entity.rb b/app/serializers/build_action_entity.rb index 301b718d060..f2d76a8ad81 100644 --- a/app/serializers/build_action_entity.rb +++ b/app/serializers/build_action_entity.rb @@ -6,10 +6,7 @@ class BuildActionEntity < Grape::Entity end expose :path do |build| - play_namespace_project_job_path( - build.project.namespace, - build.project, - build) + play_project_job_path(build.project, build) end expose :playable?, as: :playable diff --git a/app/serializers/build_artifact_entity.rb b/app/serializers/build_artifact_entity.rb index cb55c98f7c6..6e0e33bc09b 100644 --- a/app/serializers/build_artifact_entity.rb +++ b/app/serializers/build_artifact_entity.rb @@ -9,24 +9,15 @@ class BuildArtifactEntity < Grape::Entity expose :artifacts_expire_at, as: :expire_at expose :path do |job| - download_namespace_project_job_artifacts_path( - project.namespace, - project, - job) + download_project_job_artifacts_path(project, job) end expose :keep_path, if: -> (*) { job.has_expiring_artifacts? } do |job| - keep_namespace_project_job_artifacts_path( - project.namespace, - project, - job) + keep_project_job_artifacts_path(project, job) end expose :browse_path do |job| - browse_namespace_project_job_artifacts_path( - project.namespace, - project, - job) + browse_project_job_artifacts_path(project, job) end private diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb index eeb5399aa8b..20f9938f038 100644 --- a/app/serializers/build_details_entity.rb +++ b/app/serializers/build_details_entity.rb @@ -7,7 +7,7 @@ class BuildDetailsEntity < JobEntity expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :update_build, project) } do |build| - erase_namespace_project_job_path(project.namespace, project, build) + erase_project_job_path(project, build) end expose :merge_request, if: -> (*) { can?(current_user, :read_merge_request, build.merge_request) } do @@ -16,23 +16,23 @@ class BuildDetailsEntity < JobEntity end expose :path do |build| - namespace_project_merge_request_path(project.namespace, project, build.merge_request) + project_merge_request_path(project, build.merge_request) end end expose :new_issue_path, if: -> (*) { can?(request.current_user, :create_issue, project) && build.failed? } do |build| - new_namespace_project_issue_path(project.namespace, project, issue: build_failed_issue_options) + new_project_issue_path(project, issue: build_failed_issue_options) end expose :raw_path do |build| - raw_namespace_project_job_path(project.namespace, project, build) + raw_project_job_path(project, build) end private def build_failed_issue_options { title: "Build Failed ##{build.id}", - description: namespace_project_job_path(project.namespace, project, build) } + description: project_job_path(project, build) } end def current_user diff --git a/app/serializers/commit_entity.rb b/app/serializers/commit_entity.rb index 31763955f97..e4e9d8ef90a 100644 --- a/app/serializers/commit_entity.rb +++ b/app/serializers/commit_entity.rb @@ -8,16 +8,10 @@ class CommitEntity < API::Entities::RepoCommit end expose :commit_url do |commit| - namespace_project_commit_url( - request.project.namespace, - request.project, - commit) + project_commit_url(request.project, commit) end expose :commit_path do |commit| - namespace_project_commit_path( - request.project.namespace, - request.project, - commit) + project_commit_path(request.project, commit) end end diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb index e493c9162fd..241c689bccd 100644 --- a/app/serializers/deployment_entity.rb +++ b/app/serializers/deployment_entity.rb @@ -11,10 +11,7 @@ class DeploymentEntity < Grape::Entity end expose :ref_path do |deployment| - namespace_project_tree_path( - deployment.project.namespace, - deployment.project, - id: deployment.ref) + project_tree_path(deployment.project, id: deployment.ref) end end diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb index 4e8a3c67b21..dcaccc3007d 100644 --- a/app/serializers/environment_entity.rb +++ b/app/serializers/environment_entity.rb @@ -10,32 +10,20 @@ class EnvironmentEntity < Grape::Entity expose :stop_action? expose :metrics_path, if: -> (environment, _) { environment.has_metrics? } do |environment| - metrics_namespace_project_environment_path( - environment.project.namespace, - environment.project, - environment) + metrics_project_environment_path(environment.project, environment) end expose :environment_path do |environment| - namespace_project_environment_path( - environment.project.namespace, - environment.project, - environment) + project_environment_path(environment.project, environment) end expose :stop_path do |environment| - stop_namespace_project_environment_path( - environment.project.namespace, - environment.project, - environment) + stop_project_environment_path(environment.project, environment) end expose :terminal_path, if: ->(environment, _) { environment.has_terminals? } do |environment| can?(request.current_user, :admin_environment, environment.project) && - terminal_namespace_project_environment_path( - environment.project.namespace, - environment.project, - environment) + terminal_project_environment_path(environment.project, environment) end expose :created_at, :updated_at diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb index 35df95549b7..c189a4992da 100644 --- a/app/serializers/issue_entity.rb +++ b/app/serializers/issue_entity.rb @@ -11,6 +11,6 @@ class IssueEntity < IssuableEntity expose :labels, using: LabelEntity expose :web_url do |issue| - namespace_project_issue_path(issue.project.namespace, issue.project, issue) + project_issue_path(issue.project, issue) end end diff --git a/app/serializers/merge_request_entity.rb b/app/serializers/merge_request_entity.rb index 7bb981041cc..7ec2dbd0efe 100644 --- a/app/serializers/merge_request_entity.rb +++ b/app/serializers/merge_request_entity.rb @@ -99,9 +99,7 @@ class MergeRequestEntity < IssuableEntity expose :new_blob_path do |merge_request| if can?(current_user, :push_code, merge_request.project) - namespace_project_new_blob_path(merge_request.project.namespace, - merge_request.project, - merge_request.source_branch) + project_new_blob_path(merge_request.project, merge_request.source_branch) end end @@ -134,30 +132,19 @@ class MergeRequestEntity < IssuableEntity end expose :email_patches_path do |merge_request| - namespace_project_merge_request_path(merge_request.project.namespace, - merge_request.project, - merge_request, - format: :patch) + project_merge_request_path(merge_request.project, merge_request, format: :patch) end expose :plain_diff_path do |merge_request| - namespace_project_merge_request_path(merge_request.project.namespace, - merge_request.project, - merge_request, - format: :diff) + project_merge_request_path(merge_request.project, merge_request, format: :diff) end expose :status_path do |merge_request| - namespace_project_merge_request_path(merge_request.target_project.namespace, - merge_request.target_project, - merge_request, - format: :json) + project_merge_request_path(merge_request.target_project, merge_request, format: :json) end expose :ci_environments_status_path do |merge_request| - ci_environments_status_namespace_project_merge_request_path(merge_request.project.namespace, - merge_request.project, - merge_request) + ci_environments_status_project_merge_request_path(merge_request.project, merge_request) end expose :merge_commit_message_with_description do |merge_request| @@ -173,9 +160,7 @@ class MergeRequestEntity < IssuableEntity end expose :commit_change_content_path do |merge_request| - commit_change_content_namespace_project_merge_request_path(merge_request.project.namespace, - merge_request.project, - merge_request) + commit_change_content_project_merge_request_path(merge_request.project, merge_request) end private diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index 6d1fd9d459f..c4f000b0ca3 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -10,10 +10,7 @@ class PipelineEntity < Grape::Entity expose :created_at, :updated_at expose :path do |pipeline| - namespace_project_pipeline_path( - pipeline.project.namespace, - pipeline.project, - pipeline) + project_pipeline_path(pipeline.project, pipeline) end expose :flags do @@ -48,15 +45,11 @@ class PipelineEntity < Grape::Entity expose :commit, using: CommitEntity expose :retry_path, if: -> (*) { can_retry? } do |pipeline| - retry_namespace_project_pipeline_path(pipeline.project.namespace, - pipeline.project, - pipeline.id) + retry_project_pipeline_path(pipeline.project, pipeline) end expose :cancel_path, if: -> (*) { can_cancel? } do |pipeline| - cancel_namespace_project_pipeline_path(pipeline.project.namespace, - pipeline.project, - pipeline.id) + cancel_project_pipeline_path(pipeline.project, pipeline) end expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? } diff --git a/app/serializers/project_entity.rb b/app/serializers/project_entity.rb index a471a7e6a88..dc283ba3e7a 100644 --- a/app/serializers/project_entity.rb +++ b/app/serializers/project_entity.rb @@ -5,7 +5,7 @@ class ProjectEntity < Grape::Entity expose :name expose :full_path do |project| - namespace_project_path(project.namespace, project) + project_path(project) end expose :full_name do |project| diff --git a/app/serializers/runner_entity.rb b/app/serializers/runner_entity.rb index ed7dacc2dbd..e9999a36d8a 100644 --- a/app/serializers/runner_entity.rb +++ b/app/serializers/runner_entity.rb @@ -5,7 +5,7 @@ class RunnerEntity < Grape::Entity expose :edit_path, if: -> (*) { can?(request.current_user, :admin_build, project) && runner.specific? } do |runner| - edit_namespace_project_runner_path(project.namespace, project, runner) + edit_project_runner_path(project, runner) end private diff --git a/app/serializers/stage_entity.rb b/app/serializers/stage_entity.rb index cee0089056f..4523b15152e 100644 --- a/app/serializers/stage_entity.rb +++ b/app/serializers/stage_entity.rb @@ -14,16 +14,14 @@ class StageEntity < Grape::Entity expose :detailed_status, as: :status, with: StatusEntity expose :path do |stage| - namespace_project_pipeline_path( - stage.pipeline.project.namespace, + project_pipeline_path( stage.pipeline.project, stage.pipeline, anchor: stage.name) end expose :dropdown_path do |stage| - stage_namespace_project_pipeline_path( - stage.pipeline.project.namespace, + stage_project_pipeline_path( stage.pipeline.project, stage.pipeline, stage: stage.name, diff --git a/app/services/access_token_validation_service.rb b/app/services/access_token_validation_service.rb index b2a543daa00..9c00ea789ec 100644 --- a/app/services/access_token_validation_service.rb +++ b/app/services/access_token_validation_service.rb @@ -5,10 +5,11 @@ class AccessTokenValidationService REVOKED = :revoked INSUFFICIENT_SCOPE = :insufficient_scope - attr_reader :token + attr_reader :token, :request - def initialize(token) + def initialize(token, request: nil) @token = token + @request = request end def validate(scopes: []) @@ -27,12 +28,23 @@ class AccessTokenValidationService end # True if the token's scope contains any of the passed scopes. - def include_any_scope?(scopes) - if scopes.blank? + def include_any_scope?(required_scopes) + if required_scopes.blank? true else - # Check whether the token is allowed access to any of the required scopes. - Set.new(scopes).intersection(Set.new(token.scopes)).present? + # We're comparing each required_scope against all token scopes, which would + # take quadratic time. This consideration is irrelevant here because of the + # small number of records involved. + # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12300/#note_33689006 + token_scopes = token.scopes.map(&:to_sym) + + required_scopes.any? do |scope| + if scope.respond_to?(:sufficient?) + scope.sufficient?(token_scopes, request) + else + API::Scope.new(scope).sufficient?(token_scopes, request) + end + end end end end diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 942145c4a8c..4f35255fb53 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -33,7 +33,7 @@ module Ci unless pipeline.config_processor unless pipeline.ci_yaml_file - return error('Missing .gitlab-ci.yml file') + return error("Missing #{pipeline.ci_yaml_file_path} file") end return error(pipeline.yaml_errors, save: save_on_errors) end diff --git a/app/services/delete_merged_branches_service.rb b/app/services/delete_merged_branches_service.rb index 3b611588466..5c9e2a16c71 100644 --- a/app/services/delete_merged_branches_service.rb +++ b/app/services/delete_merged_branches_service.rb @@ -10,6 +10,8 @@ class DeleteMergedBranchesService < BaseService branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) } # Prevent deletion of branches relevant to open merge requests branches -= merge_request_branch_names + # Prevent deletion of protected branches + branches -= project.protected_branches.pluck(:name) branches.each do |branch| DeleteBranchService.new(project, current_user).execute(branch) diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb index d222d1e63aa..eab65d09299 100644 --- a/app/services/git_hooks_service.rb +++ b/app/services/git_hooks_service.rb @@ -3,8 +3,8 @@ class GitHooksService attr_accessor :oldrev, :newrev, :ref - def execute(user, repo_path, oldrev, newrev, ref) - @repo_path = repo_path + def execute(user, project, oldrev, newrev, ref) + @project = project @user = Gitlab::GlId.gl_id(user) @oldrev = oldrev @newrev = newrev @@ -26,7 +26,7 @@ class GitHooksService private def run_hook(name) - hook = Gitlab::Git::Hook.new(name, @repo_path) + hook = Gitlab::Git::Hook.new(name, @project) hook.trigger(@user, oldrev, newrev, ref) end end diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index ed6ea638235..43636fde0be 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -120,7 +120,7 @@ class GitOperationService def with_hooks(ref, newrev, oldrev) GitHooksService.new.execute( user, - repository.path_to_repo, + repository.project, oldrev, newrev, ref) do |service| diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb index d40d280140a..80c51cb5a72 100644 --- a/app/services/groups/destroy_service.rb +++ b/app/services/groups/destroy_service.rb @@ -1,8 +1,7 @@ module Groups class DestroyService < Groups::BaseService def async_execute - # Soft delete via paranoia gem - group.destroy + group.soft_delete_without_removing_associations job_id = GroupDestroyWorker.perform_async(group.id, current_user.id) Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}") end diff --git a/app/services/merge_requests/get_urls_service.rb b/app/services/merge_requests/get_urls_service.rb index 5dd40e07c0d..668a1741736 100644 --- a/app/services/merge_requests/get_urls_service.rb +++ b/app/services/merge_requests/get_urls_service.rb @@ -49,13 +49,13 @@ module MergeRequests def url_for_new_merge_request(branch_name) merge_request_params = { source_branch: branch_name } - url = Gitlab::Routing.url_helpers.namespace_project_new_merge_request_url(project.namespace, project, merge_request: merge_request_params) + url = Gitlab::Routing.url_helpers.project_new_merge_request_url(project, merge_request: merge_request_params) { branch_name: branch_name, url: url, new_merge_request: true } end def url_for_existing_merge_request(merge_request) target_project = merge_request.target_project - url = Gitlab::Routing.url_helpers.namespace_project_merge_request_url(target_project.namespace, target_project, merge_request) + url = Gitlab::Routing.url_helpers.project_merge_request_url(target_project, merge_request) { branch_name: merge_request.source_branch, url: url, new_merge_request: false } end end diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index b247cb89e5e..bc846e07f24 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -61,8 +61,12 @@ module MergeRequests MergeRequests::PostMergeService.new(project, current_user).execute(merge_request) if params[:should_remove_source_branch].present? || @merge_request.force_remove_source_branch? - DeleteBranchService.new(@merge_request.source_project, branch_deletion_user) - .execute(merge_request.source_branch) + # Verify again that the source branch can be removed, since branch may be protected, + # or the source branch may have been updated. + if @merge_request.can_remove_source_branch?(branch_deletion_user) + DeleteBranchService.new(@merge_request.source_project, branch_deletion_user) + .execute(merge_request.source_branch) + end end end diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index fd701e33524..4bb98e5cb4e 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -78,6 +78,7 @@ module Projects Gitlab::PagesTransfer.new.move_project(project.path, @old_namespace.full_path, @new_namespace.full_path) project.old_path_with_namespace = @old_path + project.expires_full_path_cache execute_system_hooks end diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index 17cf71cf098..e60b854f916 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -93,10 +93,11 @@ module Projects end # Requires UnZip at least 6.00 Info-ZIP. + # -qq be (very) quiet # -n never overwrite existing files # We add * to end of SITE_PATH, because we want to extract SITE_PATH and all subdirectories site_path = File.join(SITE_PATH, '*') - unless system(*%W(unzip -n #{artifacts} #{site_path} -d #{temp_path})) + unless system(*%W(unzip -qq -n #{artifacts} #{site_path} -d #{temp_path})) raise 'pages failed to extract' end end diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb index 6816b137361..e4dfe87e614 100644 --- a/app/services/quick_actions/interpret_service.rb +++ b/app/services/quick_actions/interpret_service.rb @@ -92,9 +92,12 @@ module QuickActions desc 'Assign' explanation do |users| - "Assigns #{users.first.to_reference}." if users.any? + users = issuable.allows_multiple_assignees? ? users : users.take(1) + "Assigns #{users.map(&:to_reference).to_sentence}." + end + params do + issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user' end - params '@user' condition do current_user.can?(:"admin_#{issuable.to_ability_name}", project) end @@ -104,28 +107,69 @@ module QuickActions command :assign do |users| next if users.empty? - if issuable.is_a?(Issue) - @updates[:assignee_ids] = [users.last.id] + @updates[:assignee_ids] = + if issuable.allows_multiple_assignees? + issuable.assignees.pluck(:id) + users.map(&:id) + else + [users.last.id] + end + end + + desc do + if issuable.allows_multiple_assignees? + 'Remove all or specific assignee(s)' else - @updates[:assignee_id] = users.last.id + 'Remove assignee' end end - - desc 'Remove assignee' explanation do - "Removes assignee #{issuable.assignees.first.to_reference}." + "Removes #{'assignee'.pluralize(issuable.assignees.size)} #{issuable.assignees.map(&:to_reference).to_sentence}." + end + params do + issuable.allows_multiple_assignees? ? '@user1 @user2' : '' end condition do issuable.persisted? && issuable.assignees.any? && current_user.can?(:"admin_#{issuable.to_ability_name}", project) end - command :unassign do - if issuable.is_a?(Issue) - @updates[:assignee_ids] = [] - else - @updates[:assignee_id] = nil - end + parse_params do |unassign_param| + # When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed + extract_users(unassign_param) if issuable.allows_multiple_assignees? + end + command :unassign do |users = nil| + @updates[:assignee_ids] = + if users&.any? + issuable.assignees.pluck(:id) - users.map(&:id) + else + [] + end + end + + desc do + "Change assignee#{'(s)' if issuable.allows_multiple_assignees?}" + end + explanation do |users| + users = issuable.allows_multiple_assignees? ? users : users.take(1) + "Change #{'assignee'.pluralize(users.size)} to #{users.map(&:to_reference).to_sentence}." + end + params do + issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user' + end + condition do + issuable.persisted? && + current_user.can?(:"admin_#{issuable.to_ability_name}", project) + end + parse_params do |assignee_param| + extract_users(assignee_param) + end + command :reassign do |users| + @updates[:assignee_ids] = + if issuable.allows_multiple_assignees? + users.map(&:id) + else + [users.last.id] + end end desc 'Set milestone' diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 0837c07e6aa..da0f21d449a 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -282,7 +282,7 @@ module SystemNoteService body = "changed this line in" if version_params = merge_request.version_params_for(diff_refs) line_code = change_position.line_code(project.repository) - url = url_helpers.diffs_namespace_project_merge_request_url(project.namespace, project, merge_request, version_params.merge(anchor: line_code)) + url = url_helpers.diffs_project_merge_request_url(project, merge_request, version_params.merge(anchor: line_code)) body << " [version #{version_index} of the diff](#{url})" else @@ -413,7 +413,7 @@ module SystemNoteService # # "created branch `201-issue-branch-button`" def new_issue_branch(issue, project, author, branch) - link = url_helpers.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch) + link = url_helpers.project_compare_url(project, from: project.default_branch, to: branch) body = "created branch [`#{branch}`](#{link})" @@ -630,10 +630,9 @@ module SystemNoteService def diff_comparison_url(merge_request, project, oldrev) diff_id = merge_request.merge_request_diff.id - url_helpers.diffs_namespace_project_merge_request_url( - project.namespace, + url_helpers.diffs_project_merge_request_url( project, - merge_request.iid, + merge_request, diff_id: diff_id, start_sha: oldrev ) diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index b21d5665970..5f5eeb8b9a9 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -22,7 +22,9 @@ .form-group = f.label :restricted_visibility_levels, class: 'control-label col-sm-2' .col-sm-10 - - restricted_level_checkboxes('restricted-visibility-help').each do |level| + - checkbox_name = 'application_setting[restricted_visibility_levels][]' + = hidden_field_tag(checkbox_name) + - restricted_level_checkboxes('restricted-visibility-help', checkbox_name).each do |level| .checkbox = level %span.help-block#restricted-visibility-help diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 3c9f932a225..128b5dc01ab 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -5,182 +5,182 @@ .admin-dashboard.prepend-top-default .row .col-md-4 - %h4 Statistics - %hr - %p - Forks - %span.light.pull-right - = number_with_delimiter(ForkedProjectLink.count) - %p - Issues - %span.light.pull-right - = number_with_delimiter(Issue.count) - %p - Merge Requests - %span.light.pull-right - = number_with_delimiter(MergeRequest.count) - %p - Notes - %span.light.pull-right - = number_with_delimiter(Note.count) - %p - Snippets - %span.light.pull-right - = number_with_delimiter(Snippet.count) - %p - SSH Keys - %span.light.pull-right - = number_with_delimiter(Key.count) - %p - Milestones - %span.light.pull-right - = number_with_delimiter(Milestone.count) - %p - Active Users - %span.light.pull-right - = number_with_delimiter(User.active.count) + .info-well + .well-segment.admin-well + %h4 Statistics + %p + Forks + %span.light.pull-right + = number_with_delimiter(ForkedProjectLink.count) + %p + Issues + %span.light.pull-right + = number_with_delimiter(Issue.count) + %p + Merge Requests + %span.light.pull-right + = number_with_delimiter(MergeRequest.count) + %p + Notes + %span.light.pull-right + = number_with_delimiter(Note.count) + %p + Snippets + %span.light.pull-right + = number_with_delimiter(Snippet.count) + %p + SSH Keys + %span.light.pull-right + = number_with_delimiter(Key.count) + %p + Milestones + %span.light.pull-right + = number_with_delimiter(Milestone.count) + %p + Active Users + %span.light.pull-right + = number_with_delimiter(User.active.count) .col-md-4 - %h4 - Features - %hr - - sign_up = "Sign up" - %p{ "aria-label" => "#{sign_up}: status " + (signup_enabled? ? "on" : "off") } - = sign_up - %span.light.pull-right - = boolean_to_icon signup_enabled? - - ldap = "LDAP" - %p{ "aria-label" => "#{ldap}: status " + (Gitlab.config.ldap.enabled ? "on" : "off") } - = ldap - %span.light.pull-right - = boolean_to_icon Gitlab.config.ldap.enabled - - gravatar = "Gravatar" - %p{ "aria-label" => "#{gravatar}: status " + (gravatar_enabled? ? "on" : "off") } - = gravatar - %span.light.pull-right - = boolean_to_icon gravatar_enabled? - - omniauth = "OmniAuth" - %p{ "aria-label" => "#{omniauth}: status " + (Gitlab.config.omniauth.enabled ? "on" : "off") } - = omniauth - %span.light.pull-right - = boolean_to_icon Gitlab.config.omniauth.enabled - - reply_email = "Reply by email" - %p{ "aria-label" => "#{reply_email}: status " + (Gitlab::IncomingEmail.enabled? ? "on" : "off") } - = reply_email - %span.light.pull-right - = boolean_to_icon Gitlab::IncomingEmail.enabled? - - container_reg = "Container Registry" - %p{ "aria-label" => "#{container_reg}: status " + (Gitlab.config.registry.enabled ? "on" : "off") } - = container_reg - %span.light.pull-right - = boolean_to_icon Gitlab.config.registry.enabled - - gitlab_pages = 'GitLab Pages' - - gitlab_pages_enabled = Gitlab.config.pages.enabled - %p{ "aria-label" => "#{gitlab_pages}: status " + (gitlab_pages_enabled ? "on" : "off") } - = gitlab_pages - %span.light.pull-right - = boolean_to_icon gitlab_pages_enabled - - gitlab_shared_runners = 'Shared Runners' - - gitlab_shared_runners_enabled = Gitlab.config.gitlab_ci.shared_runners_enabled - %p{ "aria-label" => "#{gitlab_shared_runners}: status " + (gitlab_shared_runners_enabled ? "on" : "off") } - = gitlab_shared_runners - %span.light.pull-right - = boolean_to_icon gitlab_shared_runners_enabled - + .info-well + .well-segment.admin-well + %h4 Features + - sign_up = "Sign up" + %p{ "aria-label" => "#{sign_up}: status " + (signup_enabled? ? "on" : "off") } + = sign_up + %span.light.pull-right + = boolean_to_icon signup_enabled? + - ldap = "LDAP" + %p{ "aria-label" => "#{ldap}: status " + (Gitlab.config.ldap.enabled ? "on" : "off") } + = ldap + %span.light.pull-right + = boolean_to_icon Gitlab.config.ldap.enabled + - gravatar = "Gravatar" + %p{ "aria-label" => "#{gravatar}: status " + (gravatar_enabled? ? "on" : "off") } + = gravatar + %span.light.pull-right + = boolean_to_icon gravatar_enabled? + - omniauth = "OmniAuth" + %p{ "aria-label" => "#{omniauth}: status " + (Gitlab.config.omniauth.enabled ? "on" : "off") } + = omniauth + %span.light.pull-right + = boolean_to_icon Gitlab.config.omniauth.enabled + - reply_email = "Reply by email" + %p{ "aria-label" => "#{reply_email}: status " + (Gitlab::IncomingEmail.enabled? ? "on" : "off") } + = reply_email + %span.light.pull-right + = boolean_to_icon Gitlab::IncomingEmail.enabled? + - container_reg = "Container Registry" + %p{ "aria-label" => "#{container_reg}: status " + (Gitlab.config.registry.enabled ? "on" : "off") } + = container_reg + %span.light.pull-right + = boolean_to_icon Gitlab.config.registry.enabled + - gitlab_pages = 'GitLab Pages' + - gitlab_pages_enabled = Gitlab.config.pages.enabled + %p{ "aria-label" => "#{gitlab_pages}: status " + (gitlab_pages_enabled ? "on" : "off") } + = gitlab_pages + %span.light.pull-right + = boolean_to_icon gitlab_pages_enabled + - gitlab_shared_runners = 'Shared Runners' + - gitlab_shared_runners_enabled = Gitlab.config.gitlab_ci.shared_runners_enabled + %p{ "aria-label" => "#{gitlab_shared_runners}: status " + (gitlab_shared_runners_enabled ? "on" : "off") } + = gitlab_shared_runners + %span.light.pull-right + = boolean_to_icon gitlab_shared_runners_enabled .col-md-4 - %h4 - Components - - if current_application_settings.version_check_enabled - .pull-right - = version_status_badge - - %hr - %p - GitLab - %span.pull-right - = Gitlab::VERSION - %p - GitLab Shell - %span.pull-right - = Gitlab::Shell.new.version - %p - GitLab Workhorse - %span.pull-right - = gitlab_workhorse_version - %p - GitLab API - %span.pull-right - = API::API::version - %p - Git - %span.pull-right - = Gitlab::Git.version - %p - Ruby - %span.pull-right - #{RUBY_VERSION}p#{RUBY_PATCHLEVEL} - - %p - Rails - %span.pull-right - #{Rails::VERSION::STRING} - - %p - = Gitlab::Database.adapter_name - %span.pull-right - = Gitlab::Database.version - %hr + .info-well + .well-segment.admin-well + %h4 + Components + - if current_application_settings.version_check_enabled + .pull-right + = version_status_badge + %p + GitLab + %span.pull-right + = Gitlab::VERSION + %p + GitLab Shell + %span.pull-right + = Gitlab::Shell.new.version + %p + GitLab Workhorse + %span.pull-right + = gitlab_workhorse_version + %p + GitLab API + %span.pull-right + = API::API::version + %p + Git + %span.pull-right + = Gitlab::Git.version + %p + Ruby + %span.pull-right + #{RUBY_VERSION}p#{RUBY_PATCHLEVEL} + %p + Rails + %span.pull-right + #{Rails::VERSION::STRING} + %p + = Gitlab::Database.adapter_name + %span.pull-right + = Gitlab::Database.version .row .col-sm-4 - .light-well.well-centered - %h4 Projects - .data + .info-well.dark-well + .well-segment.well-centered = link_to admin_projects_path do - %h1= number_with_delimiter(Project.cached_count) + %h3.text-center + Projects: + = number_with_delimiter(Project.cached_count) %hr = link_to('New project', new_project_path, class: "btn btn-new") .col-sm-4 - .light-well.well-centered - %h4 Users - .data + .info-well.dark-well + .well-segment.well-centered = link_to admin_users_path do - %h1= number_with_delimiter(User.count) + %h3.text-center + Users: + = number_with_delimiter(User.count) %hr = link_to 'New user', new_admin_user_path, class: "btn btn-new" .col-sm-4 - .light-well.well-centered - %h4 Groups - .data + .info-well.dark-well + .well-segment.well-centered = link_to admin_groups_path do - %h1= number_with_delimiter(Group.count) + %h3.text-center + Groups + = number_with_delimiter(Group.count) %hr = link_to 'New group', new_admin_group_path, class: "btn btn-new" - - .row.prepend-top-10 + .row .col-md-4 - %h4 Latest projects - %hr - - @projects.each do |project| - %p - = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60' - %span.light.pull-right - #{time_ago_with_tooltip(project.created_at)} - + .info-well + .well-segment.admin-well + %h4 Latest projects + - @projects.each do |project| + %p + = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60' + %span.light.pull-right + #{time_ago_with_tooltip(project.created_at)} .col-md-4 - %h4 Latest users - %hr - - @users.each do |user| - %p - = link_to [:admin, user], class: 'str-truncated-60' do - = user.name - %span.light.pull-right - #{time_ago_with_tooltip(user.created_at)} - + .info-well + .well-segment.admin-well + %h4 Latest users + - @users.each do |user| + %p + = link_to [:admin, user], class: 'str-truncated-60' do + = user.name + %span.light.pull-right + #{time_ago_with_tooltip(user.created_at)} .col-md-4 - %h4 Latest groups - %hr - - @groups.each do |group| - %p - = link_to [:admin, group], class: 'str-truncated-60' do - = group.full_name - %span.light.pull-right - #{time_ago_with_tooltip(group.created_at)} + .info-well + .well-segment.admin-well + %h4 Latest groups + - @groups.each do |group| + %p + = link_to [:admin, group], class: 'str-truncated-60' do + = group.full_name + %span.light.pull-right + #{time_ago_with_tooltip(group.created_at)} diff --git a/app/views/admin/projects/_projects.html.haml b/app/views/admin/projects/_projects.html.haml index 596f367a00d..c69c2761189 100644 --- a/app/views/admin/projects/_projects.html.haml +++ b/app/views/admin/projects/_projects.html.haml @@ -4,7 +4,7 @@ - @projects.each_with_index do |project| %li.project-row{ class: ('no-description' if project.description.blank?) } .controls - = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn" + = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn" = link_to 'Delete', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-remove" .stats %span.badge diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 08a8f627113..fb9057b2db5 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -108,7 +108,7 @@ .panel-heading Transfer project .panel-body - = form_for @project, url: transfer_admin_namespace_project_path(@project.namespace, @project), method: :put, html: { class: 'form-horizontal' } do |f| + = form_for @project, url: transfer_admin_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f| .form-group = f.label :new_namespace_id, "Namespace", class: 'control-label' .col-sm-10 @@ -128,7 +128,7 @@ .panel-heading Repository check .panel-body - = form_for @project, url: repository_check_admin_namespace_project_path(@project.namespace, @project), method: :post do |f| + = form_for @project, url: repository_check_admin_project_path(@project), method: :post do |f| .form-group - if @project.last_repository_check_at.nil? This repository has never been checked. diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml index d4d166ab7b6..140688b52d3 100644 --- a/app/views/admin/runners/_runner.html.haml +++ b/app/views/admin/runners/_runner.html.haml @@ -32,13 +32,16 @@ #{time_ago_in_words(runner.contacted_at)} ago - else Never - %td - .pull-right - = link_to 'Edit', admin_runner_path(runner), class: 'btn btn-sm' + %td.admin-runner-btn-group-cell + .pull-right.btn-group + = link_to admin_runner_path(runner), class: 'btn btn-sm btn-default has-tooltip', title: 'Edit', ref: 'tooltip', aria: { label: 'Edit' }, data: { placement: 'top', container: 'body'} do + = icon('pencil') - if runner.active? - = link_to 'Pause', [:pause, :admin, runner], data: { confirm: "Are you sure?" }, method: :get, class: 'btn btn-danger btn-sm' + = link_to [:pause, :admin, runner], method: :get, class: 'btn btn-sm btn-default has-tooltip', title: 'Pause', ref: 'tooltip', aria: { label: 'Pause' }, data: { placement: 'top', container: 'body', confirm: "Are you sure?" } do + = icon('pause') - else - = link_to 'Resume', [:resume, :admin, runner], method: :get, class: 'btn btn-success btn-sm' - = link_to 'Remove', [:admin, runner], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' - + = link_to [:resume, :admin, runner], method: :get, class: 'btn btn-default btn-sm has-tooltip', title: 'Resume', ref: 'tooltip', aria: { label: 'Resume' }, data: { placement: 'top', container: 'body'} do + = icon('play') + = link_to [:admin, runner], method: :delete, class: 'btn btn-danger btn-sm has-tooltip', title: 'Remove', ref: 'tooltip', aria: { label: 'Remove' }, data: { placement: 'top', container: 'body', confirm: "Are you sure?" } do + = icon('remove') diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index 801430e525e..df2bf27be9d 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -85,7 +85,7 @@ %tr.build %td.id - if project - = link_to namespace_project_job_path(project.namespace, project, build) do + = link_to project_job_path(project, build) do %strong ##{build.id} - else %strong ##{build.id} diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml index 15eaf1c0e67..4a440f3f6d4 100644 --- a/app/views/admin/users/projects.html.haml +++ b/app/views/admin/users/projects.html.haml @@ -33,7 +33,7 @@ - member = project.team.find_member(@user.id) %li.project_member .list-item-name - = link_to admin_namespace_project_path(project.namespace, project), class: dom_class(project) do + = link_to admin_project_path(project), class: dom_class(project) do = project.name_with_namespace - if member @@ -44,5 +44,5 @@ %span.light.vertical-align-middle= member.human_access - if member.respond_to? :project - = link_to namespace_project_project_member_path(project.namespace, project, member), data: { confirm: remove_member_message(member) }, remote: true, method: :delete, class: "btn-xs btn btn-remove prepend-left-10", title: 'Remove user from project' do + = link_to project_project_member_path(project, member), data: { confirm: remove_member_message(member) }, remote: true, method: :delete, class: "btn-xs btn btn-remove prepend-left-10", title: 'Remove user from project' do %i.fa.fa-times diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml index f893c3e1675..ad35d05c29a 100644 --- a/app/views/dashboard/activity.html.haml +++ b/app/views/dashboard/activity.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - @no_container = true = content_for :meta_tags do diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml index f9b45a539a1..1cea8182733 100644 --- a/app/views/dashboard/groups/index.html.haml +++ b/app/views/dashboard/groups/index.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title "Groups" - header_title "Groups", dashboard_groups_path = render 'dashboard/groups_head' diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml index 664ec618b79..ef1467c4d78 100644 --- a/app/views/dashboard/milestones/index.html.haml +++ b/app/views/dashboard/milestones/index.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title 'Milestones' - header_title 'Milestones', dashboard_milestones_path diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml index 5e63a61e21b..7ac6cf06fb9 100644 --- a/app/views/dashboard/projects/index.html.haml +++ b/app/views/dashboard/projects/index.html.haml @@ -1,4 +1,6 @@ - @no_container = true +- @hide_top_links = true +- @breadcrumb_title = "Projects" = content_for :meta_tags do = auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity") diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml index 85cbe0bf0e6..e86b1ab3116 100644 --- a/app/views/dashboard/snippets/index.html.haml +++ b/app/views/dashboard/snippets/index.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title "Snippets" - header_title "Snippets", dashboard_snippets_path diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml index f92f89e73ff..e80d10dc8f1 100644 --- a/app/views/devise/shared/_omniauth_box.html.haml +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -6,4 +6,7 @@ - providers.each do |provider| %span.light - has_icon = provider_has_icon?(provider) - = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn') + = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: 'oauth-login' + (has_icon ? ' oauth-image-link' : ' btn'), id: "oauth-login-#{provider}" + %fieldset + = check_box_tag :remember_me + = label_tag :remember_me, 'Remember Me' diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index d696577278d..298604dee8c 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -25,7 +25,7 @@ %div - if Gitlab::Recaptcha.enabled? = recaptcha_tags - %div + .submit-container = f.submit "Register", class: "btn-register btn" .clearfix.submit-container %p diff --git a/app/views/discussions/_new_issue_for_all_discussions.html.haml b/app/views/discussions/_new_issue_for_all_discussions.html.haml index ca9e0e8728a..cab346fb514 100644 --- a/app/views/discussions/_new_issue_for_all_discussions.html.haml +++ b/app/views/discussions/_new_issue_for_all_discussions.html.haml @@ -3,4 +3,4 @@ .btn.btn-default.discussion-create-issue-btn.has-tooltip{ title: "Resolve all discussions in new issue", "aria-label" => "Resolve all discussions in a new issue", "data-container" => "body" } - = link_to custom_icon('icon_mr_issue'), new_namespace_project_issue_path(@project.namespace, @project, merge_request_to_resolve_discussions_of: merge_request.iid), title: "Resolve all discussions in new issue", class: 'new-issue-for-discussion' + = link_to custom_icon('icon_mr_issue'), new_project_issue_path(@project, merge_request_to_resolve_discussions_of: merge_request.iid), title: "Resolve all discussions in new issue", class: 'new-issue-for-discussion' diff --git a/app/views/discussions/_new_issue_for_discussion.html.haml b/app/views/discussions/_new_issue_for_discussion.html.haml index df5546a1e32..a9bc317b8f8 100644 --- a/app/views/discussions/_new_issue_for_discussion.html.haml +++ b/app/views/discussions/_new_issue_for_discussion.html.haml @@ -5,4 +5,4 @@ .btn.btn-default.discussion-create-issue-btn.has-tooltip{ title: "Resolve this discussion in a new issue", "aria-label" => "Resolve this discussion in a new issue", "data-container" => "body" } - = link_to custom_icon('icon_mr_issue'), new_namespace_project_issue_path(@project.namespace, @project, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id), title: "Resolve this discussion in a new issue", class: 'new-issue-for-discussion' + = link_to custom_icon('icon_mr_issue'), new_project_issue_path(@project, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id), title: "Resolve this discussion in a new issue", class: 'new-issue-for-discussion' diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml index 3c64f1be5ff..ad434a64556 100644 --- a/app/views/events/_commit.html.haml +++ b/app/views/events/_commit.html.haml @@ -1,5 +1,5 @@ %li.commit .commit-row-title - = link_to truncate_sha(commit[:id]), namespace_project_commit_path(project.namespace, project, commit[:id]), class: "commit-sha", alt: '', title: truncate_sha(commit[:id]) + = link_to truncate_sha(commit[:id]), project_commit_path(project, commit[:id]), class: "commit-sha", alt: '', title: truncate_sha(commit[:id]) · = markdown event_commit_title(commit[:message]), project: project, pipeline: :single_line, author: event.author diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml index f8f0bcb7608..9fcacfbbf36 100644 --- a/app/views/events/_event_push.atom.haml +++ b/app/views/events/_event_push.atom.haml @@ -2,7 +2,7 @@ - event.commits.first(15).each do |commit| %p %strong= commit[:author][:name] - = link_to "(##{truncate_sha(commit[:id])})", namespace_project_commit_path(event.project.namespace, event.project, id: commit[:id]) + = link_to "(##{truncate_sha(commit[:id])})", project_commit_path(event.project, id: commit[:id]) %i at = commit[:timestamp].to_time.to_s(:short) diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml index 769ac655d0a..54b414cc62a 100644 --- a/app/views/events/event/_push.html.haml +++ b/app/views/events/event/_push.html.haml @@ -6,7 +6,7 @@ %span.author_name= link_to_author event %span.pushed #{event.action_name} #{event.ref_type} %strong - - commits_link = namespace_project_commits_path(project.namespace, project, event.ref_name) + - commits_link = project_commits_path(project, event.ref_name) = link_to_if project.repository.branch_exists?(event.ref_name), event.ref_name, commits_link, class: 'ref-name' = render "events/event_scope", event: event @@ -31,7 +31,7 @@ - from = project.default_branch - from_label = from - = link_to namespace_project_compare_path(project.namespace, project, from: from, to: event.commit_to) do + = link_to project_compare_path(project, from: from, to: event.commit_to) do Compare #{from_label}...#{truncate_sha(event.commit_to)} - if create_mr diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 7d5add3cc1c..9ebb3894c55 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -45,10 +45,13 @@ .panel.panel-danger .panel-heading Remove group .panel-body - %p - Removing group will cause all child projects and resources to be removed. - %br - %strong Removed group can not be restored! + = form_tag(@group, method: :delete) do + %p + Removing group will cause all child projects and resources to be removed. + %br + %strong Removed group can not be restored! - .form-actions - = link_to 'Remove group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove" + .form-actions + = button_to 'Remove group', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_group_message(@group) } + += render 'shared/confirm_modal', phrase: @group.path diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml index 62ad47972b9..7a2e688a114 100644 --- a/app/views/groups/projects.html.haml +++ b/app/views/groups/projects.html.haml @@ -20,8 +20,8 @@ %span.label.label-warning archived %span.badge = storage_counter(project.statistics.storage_size) - = link_to 'Members', namespace_project_project_members_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" - = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" + = link_to 'Members', project_project_members_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" + = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" = link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-sm btn-remove" - if @projects.blank? .nothing-here-block This group has no projects yet diff --git a/app/views/import/base/create.js.haml b/app/views/import/base/create.js.haml index 57e8c3ca1e1..fde671e25a9 100644 --- a/app/views/import/base/create.js.haml +++ b/app/views/import/base/create.js.haml @@ -4,7 +4,7 @@ job.attr("id", "project_#{@project.id}") target_field = job.find(".import-target") target_field.empty() - target_field.append('#{link_to @project.path_with_namespace, namespace_project_path(@project.namespace, @project)}') + target_field.append('#{link_to @project.path_with_namespace, project_path(@project)}') $("table.import-jobs tbody").prepend(job) job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started") - else diff --git a/app/views/invites/show.html.haml b/app/views/invites/show.html.haml index 882fdf1317d..ad6213b4efd 100644 --- a/app/views/invites/show.html.haml +++ b/app/views/invites/show.html.haml @@ -12,7 +12,7 @@ - project = @member.source project %strong - = link_to project.name_with_namespace, namespace_project_url(project.namespace, project) + = link_to project.name_with_namespace, project_url(project) - when Group - group = @member.source group diff --git a/app/views/issues/_issue.atom.builder b/app/views/issues/_issue.atom.builder index 2ed78bb3b65..0c113c08526 100644 --- a/app/views/issues/_issue.atom.builder +++ b/app/views/issues/_issue.atom.builder @@ -1,6 +1,6 @@ xml.entry do - xml.id namespace_project_issue_url(issue.project.namespace, issue.project, issue) - xml.link href: namespace_project_issue_url(issue.project.namespace, issue.project, issue) + xml.id project_issue_url(issue.project, issue) + xml.link href: project_issue_url(issue.project, issue) xml.title truncate(issue.title, length: 80) xml.updated issue.updated_at.xmlschema xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email)) diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index cc710f4ec7d..abb6dc2e9f3 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -38,7 +38,7 @@ = Gon::Base.render_data - = webpack_bundle_tag "runtime" + = webpack_bundle_tag "webpack_runtime" = webpack_bundle_tag "common" = webpack_bundle_tag "locale" = webpack_bundle_tag "main" diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml index 6caaba240bb..4bb0dfc73fd 100644 --- a/app/views/layouts/_init_auto_complete.html.haml +++ b/app/views/layouts/_init_auto_complete.html.haml @@ -5,10 +5,10 @@ :javascript gl.GfmAutoComplete = gl.GfmAutoComplete || {}; gl.GfmAutoComplete.dataSources = { - members: "#{members_namespace_project_autocomplete_sources_path(project.namespace, project, type: noteable_type, type_id: params[:id])}", - issues: "#{issues_namespace_project_autocomplete_sources_path(project.namespace, project)}", - mergeRequests: "#{merge_requests_namespace_project_autocomplete_sources_path(project.namespace, project)}", - labels: "#{labels_namespace_project_autocomplete_sources_path(project.namespace, project)}", - milestones: "#{milestones_namespace_project_autocomplete_sources_path(project.namespace, project)}", - commands: "#{commands_namespace_project_autocomplete_sources_path(project.namespace, project, type: noteable_type, type_id: params[:id])}" + members: "#{members_project_autocomplete_sources_path(project, type: noteable_type, type_id: params[:id])}", + issues: "#{issues_project_autocomplete_sources_path(project)}", + mergeRequests: "#{merge_requests_project_autocomplete_sources_path(project)}", + labels: "#{labels_project_autocomplete_sources_path(project)}", + milestones: "#{milestones_project_autocomplete_sources_path(project)}", + commands: "#{commands_project_autocomplete_sources_path(project, type: noteable_type, type_id: params[:id])}" }; diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 62a76a1b00e..1a9f5401a78 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -14,6 +14,8 @@ = render "layouts/broadcast" = render "layouts/flash" = yield :flash_message + - if show_new_nav? + = render "layouts/nav/breadcrumbs" %div{ class: "#{(container_class unless @no_container)} #{@content_class}" } .content{ id: "content-body" } = yield diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index b689991bb6d..59f16b47bf7 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -5,7 +5,7 @@ - if @group && @group.persisted? && @group.path - group_data_attrs = { group_path: j(@group.path), name: @group.name, issues_path: issues_group_path(j(@group.path)), mr_path: merge_requests_group_path(j(@group.path)) } - if @project && @project.persisted? - - project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: namespace_project_issues_path(@project.namespace, @project), mr_path: namespace_project_merge_requests_path(@project.namespace, @project) } + - project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: project_issues_path(@project), mr_path: project_merge_requests_path(@project) } .search.search-form{ class: "#{'has-location-badge' if label.present?}" } = form_tag search_path, method: :get, class: 'navbar-form' do |f| .search-input-container diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index f056c0af968..ed44263741e 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -17,7 +17,7 @@ = link_to root_path, class: 'home', title: 'Dashboard', id: 'logo' do = brand_header_logo - .title-container + .title-container.js-title-container %h1.title{ class: ('initializing' if @has_group_title) }= title .navbar-collapse.collapse @@ -74,9 +74,8 @@ = link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username } %li = link_to "Settings", profile_path - - if can_toggle_new_nav? - %li - = link_to "Turn on new nav", profile_preferences_path(anchor: "new-navigation") + %li + = link_to "Turn on new nav", profile_preferences_path(anchor: "new-navigation") %li.divider %li = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link" @@ -96,4 +95,4 @@ - if @project && !@project.empty_repo? - if ref = @ref || @project.repository.root_ref :javascript - var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, ref)}"; + var findFileURL = "#{project_find_file_path(@project, ref)}"; diff --git a/app/views/layouts/header/_new.html.haml b/app/views/layouts/header/_new.html.haml index c0833c64911..bee7291da45 100644 --- a/app/views/layouts/header/_new.html.haml +++ b/app/views/layouts/header/_new.html.haml @@ -83,11 +83,9 @@ = icon('ellipsis-v', class: 'js-navbar-toggle-right') = icon('times', class: 'js-navbar-toggle-left', style: 'display: none;') - = yield :header_content - = render 'shared/outdated_browser' - if @project && !@project.empty_repo? - if ref = @ref || @project.repository.root_ref :javascript - var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, ref)}"; + var findFileURL = "#{project_find_file_path(@project, ref)}"; diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml index 4d41579168c..9da739b0974 100644 --- a/app/views/layouts/header/_new_dropdown.haml +++ b/app/views/layouts/header/_new_dropdown.haml @@ -30,13 +30,13 @@ %li.dropdown-bold-header This project - if create_project_issue %li - = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project) + = link_to 'New issue', new_project_issue_path(@project) - if merge_project %li - = link_to 'New merge request', namespace_project_new_merge_request_path(merge_project.namespace, merge_project) + = link_to 'New merge request', project_new_merge_request_path(merge_project) - if create_project_snippet %li.header-new-project-snippet - = link_to 'New snippet', new_namespace_project_snippet_path(@project.namespace, @project) + = link_to 'New snippet', new_project_snippet_path(@project) %li.divider %li.dropdown-bold-header GitLab - if current_user.can_create_project? diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml new file mode 100644 index 00000000000..5f1641f4300 --- /dev/null +++ b/app/views/layouts/nav/_breadcrumbs.html.haml @@ -0,0 +1,19 @@ +- breadcrumb_title = @breadcrumb_title || controller.controller_name.humanize +- hide_top_links = @hide_top_links || false + +%nav.breadcrumbs{ role: "navigation" } + .breadcrumbs-container{ class: container_class } + .breadcrumbs-links.js-title-container + - unless hide_top_links + .title + = link_to "GitLab", root_path + \/ + = header_title + %h2.breadcrumbs-sub-title + %ul.list-unstyled + - if content_for?(:sub_title_before) + = yield :sub_title_before + %li= link_to breadcrumb_title, request.path + - if content_for?(:breadcrumbs_extra) + .breadcrumbs-extra.hidden-xs= yield :breadcrumbs_extra + = yield :header_content diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml index eae9da5da14..6e483353a2d 100644 --- a/app/views/layouts/nav/_new_project_sidebar.html.haml +++ b/app/views/layouts/nav/_new_project_sidebar.html.haml @@ -27,52 +27,52 @@ - if project_nav_tab? :files = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do - = link_to project_files_path(@project), title: 'Repository', class: 'shortcuts-tree' do + = link_to project_tree_path(@project), title: 'Repository', class: 'shortcuts-tree' do %span Repository %ul.sidebar-sub-level-items = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do - = link_to project_files_path(@project) do + = link_to project_tree_path(@project) do #{ _('Files') } = nav_link(controller: [:commit, :commits]) do - = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do + = link_to project_commits_path(@project, current_ref) do #{ _('Commits') } = nav_link(html_options: {class: branches_tab_class}) do - = link_to namespace_project_branches_path(@project.namespace, @project) do + = link_to project_branches_path(@project) do #{ _('Branches') } = nav_link(controller: [:tags, :releases]) do - = link_to namespace_project_tags_path(@project.namespace, @project) do + = link_to project_tags_path(@project) do #{ _('Tags') } = nav_link(path: 'graphs#show') do - = link_to namespace_project_graph_path(@project.namespace, @project, current_ref) do + = link_to project_graph_path(@project, current_ref) do #{ _('Contributors') } = nav_link(controller: %w(network)) do - = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do + = link_to project_network_path(@project, current_ref) do #{ s_('ProjectNetworkGraph|Graph') } = nav_link(controller: :compare) do - = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do + = link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do #{ _('Compare') } = nav_link(path: 'graphs#charts') do - = link_to charts_namespace_project_graph_path(@project.namespace, @project, current_ref) do + = link_to charts_project_graph_path(@project, current_ref) do #{ _('Charts') } - if project_nav_tab? :container_registry = nav_link(controller: %w[projects/registry/repositories]) do - = link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do + = link_to project_container_registry_index_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do %span Registry - if project_nav_tab? :issues = nav_link(controller: @project.default_issues_tracker? ? [:issues, :labels, :milestones, :boards] : :issues) do - = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues', class: 'shortcuts-issues' do + = link_to project_issues_path(@project), title: 'Issues', class: 'shortcuts-issues' do %span Issues - if @project.default_issues_tracker? @@ -81,36 +81,36 @@ %ul.sidebar-sub-level-items - if project_nav_tab?(:issues) && !current_controller?(:merge_requests) = nav_link(controller: :issues) do - = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do + = link_to project_issues_path(@project), title: 'Issues' do %span List = nav_link(controller: :boards) do - = link_to namespace_project_boards_path(@project.namespace, @project), title: 'Board' do + = link_to project_boards_path(@project), title: 'Board' do %span Board - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests) = nav_link(controller: :merge_requests) do - = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do + = link_to project_merge_requests_path(@project), title: 'Merge Requests' do %span Merge Requests - if project_nav_tab? :labels = nav_link(controller: :labels) do - = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do + = link_to project_labels_path(@project), title: 'Labels' do %span Labels - if project_nav_tab? :milestones = nav_link(controller: :milestones) do - = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do + = link_to project_milestones_path(@project), title: 'Milestones' do %span Milestones - if project_nav_tab? :merge_requests = nav_link(controller: @project.default_issues_tracker? ? :merge_requests : [:merge_requests, :labels, :milestones]) do - = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do + = link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do %span Merge Requests %span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count) @@ -148,7 +148,7 @@ - if @project.feature_available?(:builds, current_user) && !@project.empty_repo? = nav_link(path: 'pipelines#charts') do - = link_to charts_namespace_project_pipelines_path(@project.namespace, @project), title: 'Charts', class: 'shortcuts-pipelines-charts' do + = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do %span Charts @@ -160,7 +160,7 @@ - if project_nav_tab? :snippets = nav_link(controller: :snippets) do - = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do + = link_to project_snippets_path(@project), title: 'Snippets', class: 'shortcuts-snippets' do %span Snippets @@ -187,23 +187,23 @@ %span Integrations = nav_link(controller: :repository) do - = link_to namespace_project_settings_repository_path(@project.namespace, @project), title: 'Repository' do + = link_to project_settings_repository_path(@project), title: 'Repository' do %span Repository - if @project.feature_available?(:builds, current_user) = nav_link(controller: :ci_cd) do - = link_to namespace_project_settings_ci_cd_path(@project.namespace, @project), title: 'Pipelines' do + = link_to project_settings_ci_cd_path(@project), title: 'Pipelines' do %span Pipelines - if Gitlab.config.pages.enabled = nav_link(controller: :pages) do - = link_to namespace_project_pages_path(@project.namespace, @project), title: 'Pages' do + = link_to project_pages_path(@project), title: 'Pages' do %span Pages - else = nav_link(path: %w[members#show]) do - = link_to namespace_project_settings_members_path(@project.namespace, @project), title: 'Settings', class: 'shortcuts-tree' do + = link_to project_settings_members_path(@project), title: 'Settings', class: 'shortcuts-tree' do %span Settings @@ -216,18 +216,18 @@ -# Shortcut to Repository > Graph (formerly, Network) - if project_nav_tab? :network %li.hidden - = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do + = link_to project_network_path(@project, current_ref), title: 'Network', class: 'shortcuts-network' do Graph -# Shortcut to Repository > Charts (formerly, top-nav item "Graphs") - unless @project.empty_repo? %li.hidden - = link_to charts_namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do + = link_to charts_project_graph_path(@project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do Charts -# Shortcut to Issues > New Issue %li.hidden - = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'shortcuts-new-issue' do + = link_to new_project_issue_path(@project), class: 'shortcuts-new-issue' do Create a new issue -# Shortcut to Pipelines > Jobs @@ -244,4 +244,4 @@ -# Shortcut to issue boards %li.hidden - = link_to 'Issue Boards', namespace_project_boards_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards' + = link_to 'Issue Boards', project_boards_path(@project), title: 'Issue Boards', class: 'shortcuts-issue-boards' diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 68024d782a6..14deb46eee3 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -12,32 +12,32 @@ - if project_nav_tab? :files = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do - = link_to project_files_path(@project), title: 'Repository', class: 'shortcuts-tree' do + = link_to project_tree_path(@project), title: 'Repository', class: 'shortcuts-tree' do %span Repository - if project_nav_tab? :container_registry = nav_link(controller: %w[projects/registry/repositories]) do - = link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do + = link_to project_container_registry_index_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do %span Registry - if project_nav_tab? :issues = nav_link(controller: @project.default_issues_tracker? ? [:issues, :labels, :milestones, :boards] : :issues) do - = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues', class: 'shortcuts-issues' do + = link_to project_issues_path(@project), title: 'Issues', class: 'shortcuts-issues' do %span Issues - if @project.default_issues_tracker? - %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count) + %span.badge.count.issue_counter= number_with_delimiter(issuables_count_for_state(:issues, :opened, finder: IssuesFinder.new(current_user, project_id: @project.id))) - if project_nav_tab? :merge_requests - controllers = [:merge_requests, 'projects/merge_requests/conflicts'] - controllers.push(:merge_requests, :labels, :milestones) unless @project.default_issues_tracker? = nav_link(controller: controllers) do - = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do + = link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do %span Merge Requests - %span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count) + %span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(issuables_count_for_state(:merge_requests, :opened, finder: MergeRequestsFinder.new(current_user, project_id: @project.id))) - if project_nav_tab? :pipelines = nav_link(controller: [:pipelines, :builds, :environments, :artifacts]) do @@ -53,7 +53,7 @@ - if project_nav_tab? :snippets = nav_link(controller: :snippets) do - = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do + = link_to project_snippets_path(@project), title: 'Snippets', class: 'shortcuts-snippets' do %span Snippets @@ -64,7 +64,7 @@ Settings - else = nav_link(path: %w[members#show]) do - = link_to namespace_project_settings_members_path(@project.namespace, @project), title: 'Settings', class: 'shortcuts-tree' do + = link_to project_settings_members_path(@project), title: 'Settings', class: 'shortcuts-tree' do %span Settings @@ -77,18 +77,18 @@ -# Shortcut to Repository > Graph (formerly, Network) - if project_nav_tab? :network %li.hidden - = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do + = link_to project_network_path(@project, current_ref), title: 'Network', class: 'shortcuts-network' do Graph -# Shortcut to Repository > Charts (formerly, top-nav item "Graphs") - unless @project.empty_repo? %li.hidden - = link_to charts_namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do + = link_to charts_project_graph_path(@project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do Charts -# Shortcut to Issues > New Issue %li.hidden - = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'shortcuts-new-issue' do + = link_to new_project_issue_path(@project), class: 'shortcuts-new-issue' do Create a new issue -# Shortcut to Pipelines > Jobs @@ -105,4 +105,4 @@ -# Shortcut to issue boards %li.hidden - = link_to 'Issue Boards', namespace_project_boards_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards' + = link_to 'Issue Boards', project_boards_path(@project), title: 'Issue Boards', class: 'shortcuts-issue-boards' diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index 4458c3c2c23..99adb83cd1f 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -11,7 +11,7 @@ - project = @target_project || @project - if current_user :javascript - window.uploads_path = "#{namespace_project_uploads_path project.namespace,project}"; + window.uploads_path = "#{project_uploads_path(project)}"; - content_for :header_content do .js-dropdown-menu-projects diff --git a/app/views/notify/closed_issue_email.text.haml b/app/views/notify/closed_issue_email.text.haml index bc12e38675f..b35d4b7502d 100644 --- a/app/views/notify/closed_issue_email.text.haml +++ b/app/views/notify/closed_issue_email.text.haml @@ -1,3 +1,3 @@ Issue was closed by #{@updated_by.name} -Issue ##{@issue.iid}: #{namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)} +Issue ##{@issue.iid}: #{project_issue_url(@issue.project, @issue)} diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml index d0c96b83976..c4e06cb3cb1 100644 --- a/app/views/notify/closed_merge_request_email.text.haml +++ b/app/views/notify/closed_merge_request_email.text.haml @@ -1,6 +1,6 @@ Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name} -Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} +Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)} = merge_path_description(@merge_request, 'to') diff --git a/app/views/notify/issue_moved_email.html.haml b/app/views/notify/issue_moved_email.html.haml index 40f7d61fe19..472c31e9a5e 100644 --- a/app/views/notify/issue_moved_email.html.haml +++ b/app/views/notify/issue_moved_email.html.haml @@ -2,5 +2,5 @@ Issue was moved to another project. %p New issue: - = link_to namespace_project_issue_url(@new_project.namespace, @new_project, @new_issue) do + = link_to project_issue_url(@new_project, @new_issue) do = @new_issue.title diff --git a/app/views/notify/issue_moved_email.text.erb b/app/views/notify/issue_moved_email.text.erb index b3bd43c2055..66ede43635b 100644 --- a/app/views/notify/issue_moved_email.text.erb +++ b/app/views/notify/issue_moved_email.text.erb @@ -1,4 +1,4 @@ Issue was moved to another project. New issue location: -<%= namespace_project_issue_url(@new_project.namespace, @new_project, @new_issue) %> +<%= project_issue_url(@new_project, @new_issue) %> diff --git a/app/views/notify/issue_status_changed_email.text.erb b/app/views/notify/issue_status_changed_email.text.erb index e6ab3fcde77..4200881f7e8 100644 --- a/app/views/notify/issue_status_changed_email.text.erb +++ b/app/views/notify/issue_status_changed_email.text.erb @@ -1,4 +1,4 @@ Issue was <%= @issue_status %> by <%= @updated_by.name %> -Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %> +Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %> diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml index 4c9719ba732..ae2a2933865 100644 --- a/app/views/notify/merge_request_status_email.text.haml +++ b/app/views/notify/merge_request_status_email.text.haml @@ -1,6 +1,6 @@ Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name} -Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} +Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)} = merge_path_description(@merge_request, 'to') diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml index 46c1c9dee0b..661c23bcbe2 100644 --- a/app/views/notify/merged_merge_request_email.text.haml +++ b/app/views/notify/merged_merge_request_email.text.haml @@ -1,6 +1,6 @@ Merge Request #{@merge_request.to_reference} was merged -Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} +Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)} = merge_path_description(@merge_request, 'to') diff --git a/app/views/notify/new_issue_email.text.erb b/app/views/notify/new_issue_email.text.erb index 13f1ac08e94..3c716f77296 100644 --- a/app/views/notify/new_issue_email.text.erb +++ b/app/views/notify/new_issue_email.text.erb @@ -1,6 +1,6 @@ New Issue was created. -Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %> +Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %> Author: <%= @issue.author_name %> Assignee: <%= @issue.assignee_list %> diff --git a/app/views/notify/new_mention_in_issue_email.text.erb b/app/views/notify/new_mention_in_issue_email.text.erb index f19ac3adfc7..23213106c5b 100644 --- a/app/views/notify/new_mention_in_issue_email.text.erb +++ b/app/views/notify/new_mention_in_issue_email.text.erb @@ -1,6 +1,6 @@ You have been mentioned in an issue. -Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %> +Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %> Author: <%= @issue.author_name %> Assignee: <%= @issue.assignee_list %> diff --git a/app/views/notify/new_mention_in_merge_request_email.text.erb b/app/views/notify/new_mention_in_merge_request_email.text.erb index 5bf0282e097..6fcebb22fc4 100644 --- a/app/views/notify/new_mention_in_merge_request_email.text.erb +++ b/app/views/notify/new_mention_in_merge_request_email.text.erb @@ -1,6 +1,6 @@ You have been mentioned in Merge Request <%= @merge_request.to_reference %> -<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %> +<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %> <%= merge_path_description(@merge_request, 'to') %> Author: <%= @merge_request.author_name %> diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb index 3c8f178ac77..7d98400e6fe 100644 --- a/app/views/notify/new_merge_request_email.text.erb +++ b/app/views/notify/new_merge_request_email.text.erb @@ -1,6 +1,6 @@ New Merge Request <%= @merge_request.to_reference %> -<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %> +<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %> <%= merge_path_description(@merge_request, 'to') %> Author: <%= @merge_request.author_name %> diff --git a/app/views/notify/project_was_exported_email.html.haml b/app/views/notify/project_was_exported_email.html.haml index 3def26342a1..f0ba7827cef 100644 --- a/app/views/notify/project_was_exported_email.html.haml +++ b/app/views/notify/project_was_exported_email.html.haml @@ -2,7 +2,7 @@ Project #{@project.name} was exported successfully. %p The project export can be downloaded from: - = link_to download_export_namespace_project_url(@project.namespace, @project), rel: 'nofollow', download: '' do + = link_to download_export_project_url(@project), rel: 'nofollow', download: '' do = @project.name_with_namespace + " export" %p The download link will expire in 24 hours. diff --git a/app/views/notify/project_was_exported_email.text.erb b/app/views/notify/project_was_exported_email.text.erb index 42c4d176876..cd3a1f7934f 100644 --- a/app/views/notify/project_was_exported_email.text.erb +++ b/app/views/notify/project_was_exported_email.text.erb @@ -1,6 +1,6 @@ Project <%= @project.name %> was exported successfully. The project export can be downloaded from: -<%= download_export_namespace_project_url(@project.namespace, @project) %> +<%= download_export_project_url(@project) %> The download link will expire in 24 hours. diff --git a/app/views/notify/project_was_moved_email.html.haml b/app/views/notify/project_was_moved_email.html.haml index 87b3ff7f0b3..c476a39b661 100644 --- a/app/views/notify/project_was_moved_email.html.haml +++ b/app/views/notify/project_was_moved_email.html.haml @@ -2,7 +2,7 @@ Project #{@old_path_with_namespace} was moved to another location %p The project is now located under - = link_to namespace_project_url(@project.namespace, @project) do + = link_to project_url(@project) do = @project.name_with_namespace %p To update the remote url in your local repository run (for ssh): diff --git a/app/views/notify/project_was_moved_email.text.erb b/app/views/notify/project_was_moved_email.text.erb index b2c5f71e465..7c45163e0e8 100644 --- a/app/views/notify/project_was_moved_email.text.erb +++ b/app/views/notify/project_was_moved_email.text.erb @@ -1,7 +1,7 @@ Project <%= @old_path_with_namespace %> was moved to another location The project is now located under -<%= namespace_project_url(@project.namespace, @project) %> +<%= project_url(@project) %> To update the remote url in your local repository run (for ssh): diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index 546376aeed8..5c5520f4cb8 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -3,7 +3,7 @@ %h3 #{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name} - at #{link_to(@message.project_name_with_namespace, namespace_project_url(@message.project_namespace, @message.project))} + at #{link_to(@message.project_name_with_namespace, project_url(@message.project))} - if @message.compare - if @message.reverse_compare? @@ -17,7 +17,7 @@ %ul - @message.commits.each do |commit| %li - %strong= link_to(commit.short_id, namespace_project_commit_url(@message.project_namespace, @message.project, commit)) + %strong= link_to(commit.short_id, project_commit_url(@message.project, commit)) %div %span by #{commit.author_name} %i at #{commit.committed_date.to_s(:iso8601)} diff --git a/app/views/notify/resolved_all_discussions_email.text.erb b/app/views/notify/resolved_all_discussions_email.text.erb index b0d380af8fc..2881f3e699e 100644 --- a/app/views/notify/resolved_all_discussions_email.text.erb +++ b/app/views/notify/resolved_all_discussions_email.text.erb @@ -1,3 +1,3 @@ All discussions on Merge Request <%= @merge_request.to_reference %> were resolved by <%= @resolved_by.name %> -<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %> +<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %> diff --git a/app/views/profiles/chat_names/_chat_name.html.haml b/app/views/profiles/chat_names/_chat_name.html.haml index 1ec1e7c70e4..fe1cf802971 100644 --- a/app/views/profiles/chat_names/_chat_name.html.haml +++ b/app/views/profiles/chat_names/_chat_name.html.haml @@ -10,7 +10,7 @@ %td %strong - if can?(current_user, :admin_project, project) - = link_to service.title, edit_namespace_project_service_path(project.namespace, project, service) + = link_to service.title, edit_project_service_path(project, service) - else = service.title %td diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index a089aeb2447..bd602071384 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -16,25 +16,22 @@ .preview= image_tag "#{scheme.css_class}-scheme-preview.png" = f.radio_button :color_scheme_id, scheme.id = scheme.name - - if can_toggle_new_nav? - .col-sm-12 - %hr - .col-lg-3.profile-settings-sidebar#new-navigation - %h4.prepend-top-0 - New Navigation - %p - This setting allows you to turn on or off the new upcoming navigation concept. - = succeed '.' do - = link_to 'Learn more', '', target: '_blank' - .col-lg-9.syntax-theme - = label_tag do - .preview= image_tag "old_nav.png" - %input.js-experiment-feature-toggle{ type: "radio", value: "false", name: "new_nav", checked: !show_new_nav? } - Old - = label_tag do - .preview= image_tag "new_nav.png" - %input.js-experiment-feature-toggle{ type: "radio", value: "true", name: "new_nav", checked: show_new_nav? } - New + .col-sm-12 + %hr + .col-lg-4.profile-settings-sidebar#new-navigation + %h4.prepend-top-0 + New Navigation + %p + This setting allows you to turn on or off the new upcoming navigation concept. + .col-lg-8.syntax-theme + = label_tag do + .preview= image_tag "old_nav.png" + %input.js-experiment-feature-toggle{ type: "radio", value: "false", name: "new_nav", checked: !show_new_nav? } + Old + = label_tag do + .preview= image_tag "new_nav.png" + %input.js-experiment-feature-toggle{ type: "radio", value: "true", name: "new_nav", checked: show_new_nav? } + New .col-sm-12 %hr .col-lg-4.profile-settings-sidebar diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index 10f581d751b..ecc966ed453 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -1,7 +1,7 @@ %div{ class: container_class } .nav-block.activity-filter-block.activities .controls - = link_to namespace_project_path(@project.namespace, @project, rss_url_options), title: "Subscribe", class: 'btn rss-btn has-tooltip' do + = link_to project_path(@project, rss_url_options), title: "Subscribe", class: 'btn rss-btn has-tooltip' do = icon('rss') = render 'shared/event_filter' diff --git a/app/views/projects/_find_file_link.html.haml b/app/views/projects/_find_file_link.html.haml index cb4d2bbacf5..da1b2d7f9b6 100644 --- a/app/views/projects/_find_file_link.html.haml +++ b/app/views/projects/_find_file_link.html.haml @@ -1,3 +1,3 @@ -= link_to namespace_project_find_file_path(@project.namespace, @project, @ref), class: 'btn shortcuts-find-file', rel: 'nofollow' do += link_to project_find_file_path(@project, @ref), class: 'btn shortcuts-find-file', rel: 'nofollow' do = icon('search') %span= _('Find file') diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml index f1ef50d2de2..1a71bfca2e2 100644 --- a/app/views/projects/_last_push.html.haml +++ b/app/views/projects/_last_push.html.haml @@ -5,7 +5,7 @@ .event-last-push-text %span You pushed to %strong - = link_to event.ref_name, namespace_project_commits_path(event.project.namespace, event.project, event.ref_name), class: 'ref-name' + = link_to event.ref_name, project_commits_path(event.project, event.ref_name), class: 'ref-name' - if event.project != @project %span at diff --git a/app/views/projects/_wiki.html.haml b/app/views/projects/_wiki.html.haml index 2bab22e125d..a56c3503c77 100644 --- a/app/views/projects/_wiki.html.haml +++ b/app/views/projects/_wiki.html.haml @@ -14,5 +14,5 @@ Add a homepage to your wiki that contains information about your project %p We recommend you - = link_to "add a homepage", namespace_project_wiki_path(@project.namespace, @project, :home) + = link_to "add a homepage", project_wiki_path(@project, :home) to your project's wiki and GitLab will show it here instead of this message. diff --git a/app/views/projects/artifacts/_tree_directory.html.haml b/app/views/projects/artifacts/_tree_directory.html.haml index e2966ec33c2..03be6f15313 100644 --- a/app/views/projects/artifacts/_tree_directory.html.haml +++ b/app/views/projects/artifacts/_tree_directory.html.haml @@ -1,4 +1,4 @@ -- path_to_directory = browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path: directory.path) +- path_to_directory = browse_project_job_artifacts_path(@project, @build, path: directory.path) %tr.tree-item{ 'data-link' => path_to_directory } %td.tree-item-file-name diff --git a/app/views/projects/artifacts/_tree_file.html.haml b/app/views/projects/artifacts/_tree_file.html.haml index ea0b43b85cf..8edb9be049a 100644 --- a/app/views/projects/artifacts/_tree_file.html.haml +++ b/app/views/projects/artifacts/_tree_file.html.haml @@ -1,4 +1,4 @@ -- path_to_file = file_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path: file.path) +- path_to_file = file_project_job_artifacts_path(@project, @build, path: file.path) %tr.tree-item{ 'data-link' => path_to_file } - blob = file.blob diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml index 961c805dc7c..576e5b385af 100644 --- a/app/views/projects/artifacts/browse.html.haml +++ b/app/views/projects/artifacts/browse.html.haml @@ -6,17 +6,17 @@ .tree-holder .nav-block .tree-controls - = link_to download_namespace_project_job_artifacts_path(@project.namespace, @project, @build), + = link_to download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'btn btn-default download' do = icon('download') Download artifacts archive %ul.breadcrumb.repo-breadcrumb %li - = link_to 'Artifacts', browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build) + = link_to 'Artifacts', browse_project_job_artifacts_path(@project, @build) - path_breadcrumbs do |title, path| %li - = link_to truncate(title, length: 40), browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path) + = link_to truncate(title, length: 40), browse_project_job_artifacts_path(@project, @build, path) .tree-content-holder %table.table.tree-table diff --git a/app/views/projects/artifacts/file.html.haml b/app/views/projects/artifacts/file.html.haml index b25c7c95196..18e86ac5a92 100644 --- a/app/views/projects/artifacts/file.html.haml +++ b/app/views/projects/artifacts/file.html.haml @@ -7,15 +7,15 @@ .nav-block %ul.breadcrumb.repo-breadcrumb %li - = link_to 'Artifacts', browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build) + = link_to 'Artifacts', browse_project_job_artifacts_path(@project, @build) - path_breadcrumbs do |title, path| - title = truncate(title, length: 40) %li - if path == @path - = link_to file_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path) do + = link_to file_project_job_artifacts_path(@project, @build, path) do %strong= title - else - = link_to title, browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path) + = link_to title, browse_project_job_artifacts_path(@project, @build, path) %article.file-holder diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index 3627f72f5e1..f11afe8fc22 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -22,9 +22,9 @@ = author_avatar(commit, size: 36) .commit-row-title %strong - = link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark" + = link_to_gfm truncate(commit.title, length: 35), project_commit_path(@project, commit.id), class: "cdark" .pull-right - = link_to commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit), class: "commit-sha" + = link_to commit.short_id, project_commit_path(@project, commit), class: "commit-sha" .light = commit_author_link(commit, avatar: false) diff --git a/app/views/projects/blob/_breadcrumb.html.haml b/app/views/projects/blob/_breadcrumb.html.haml index 2c944b516a4..1c148de9678 100644 --- a/app/views/projects/blob/_breadcrumb.html.haml +++ b/app/views/projects/blob/_breadcrumb.html.haml @@ -6,16 +6,16 @@ %ul.breadcrumb.repo-breadcrumb %li - = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do + = link_to project_tree_path(@project, @ref) do = @project.path - path_breadcrumbs do |title, path| - title = truncate(title, length: 40) %li - if path == @path - = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, path)) do + = link_to project_blob_path(@project, tree_join(@ref, path)) do %strong= title - else - = link_to title, namespace_project_tree_path(@project.namespace, @project, tree_join(@ref, path)) + = link_to title, project_tree_path(@project, tree_join(@ref, path)) .tree-controls = render 'projects/find_file_link' @@ -24,14 +24,14 @@ -# only show normal/blame view links for text files - if blob.readable_text? - if blame - = link_to 'Normal view', namespace_project_blob_path(@project.namespace, @project, @id), + = link_to 'Normal view', project_blob_path(@project, @id), class: 'btn' - else - = link_to 'Blame', namespace_project_blame_path(@project.namespace, @project, @id), + = link_to 'Blame', project_blame_path(@project, @id), class: 'btn js-blob-blame-link' unless blob.empty? - = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), + = link_to 'History', project_commits_path(@project, @id), class: 'btn' - = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project, + = link_to 'Permalink', project_blob_path(@project, tree_join(@commit.sha, @path)), class: 'btn js-data-file-blob-permalink-url' diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml index 40978583e8b..b2959ef6d31 100644 --- a/app/views/projects/blob/_new_dir.html.haml +++ b/app/views/projects/blob/_new_dir.html.haml @@ -5,7 +5,7 @@ %a.close{ href: "#", "data-dismiss" => "modal" } × %h3.page-title= _('Create New Directory') .modal-body - = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-quick-submit js-requires-input' do + = form_tag project_create_dir_path(@project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-quick-submit js-requires-input' do .form-group = label_tag :dir_name, _('Directory name'), class: 'control-label' .col-sm-10 diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml index c8ca0406213..6a4a657fa8c 100644 --- a/app/views/projects/blob/_remove.html.haml +++ b/app/views/projects/blob/_remove.html.haml @@ -6,7 +6,7 @@ %h3.page-title Delete #{@blob.name} .modal-body - = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-delete-blob-form js-quick-submit js-requires-input' do + = form_tag project_blob_path(@project, @id), method: :delete, class: 'form-horizontal js-delete-blob-form js-quick-submit js-requires-input' do = render 'shared/new_commit_form', placeholder: "Delete #{@blob.name}" .form-group diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 4af62461151..f8cb612a2b4 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -9,7 +9,7 @@ - if @conflict .alert.alert-danger Someone edited the file the same time you did. Please check out - = link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@branch_name, @file_path)), target: "_blank", rel: 'noopener noreferrer' + = link_to "the file", project_blob_path(@project, tree_join(@branch_name, @file_path)), target: "_blank", rel: 'noopener noreferrer' and make sure your changes will not unintentionally remove theirs. .editor-title-row %h3.page-title.blob-edit-page-title @@ -22,13 +22,13 @@ Write %li - = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do + = link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id) do = editing_preview_title(@blob.name) - = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do + = form_tag(project_update_blob_path(@project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" = hidden_field_tag 'last_commit_sha', @last_commit_sha = hidden_field_tag 'content', '', id: "file-content" = hidden_field_tag 'from_merge_request_iid', params[:from_merge_request_iid] - = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id) + = render 'projects/commit_button', ref: @ref, cancel_path: project_blob_path(@project, @id) diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index 2afb909572a..8620a470041 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -7,10 +7,10 @@ New file = render 'template_selectors' .file-editor - = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths) do + = form_tag(project_create_blob_path(@project, @id), method: :post, class: 'form-horizontal js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths) do = render 'projects/blob/editor', ref: @ref = render 'shared/new_commit_form', placeholder: "Add new file" = hidden_field_tag 'content', '', id: 'file-content' = render 'projects/commit_button', ref: @ref, - cancel_path: namespace_project_tree_path(@project.namespace, @project, @id) + cancel_path: project_tree_path(@project, @id) diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index 41f75a491a5..6e2ae4717cd 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -16,4 +16,4 @@ = render 'projects/blob/remove' - title = "Replace #{@blob.name}" - = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put + = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put diff --git a/app/views/projects/blob/viewers/_changelog.html.haml b/app/views/projects/blob/viewers/_changelog.html.haml index 53921e63b5f..46e3e7f798a 100644 --- a/app/views/projects/blob/viewers/_changelog.html.haml +++ b/app/views/projects/blob/viewers/_changelog.html.haml @@ -1,4 +1,4 @@ = icon('history fw') = succeed '.' do To find the state of this project's repository at the time of any of these versions, check out - = link_to "the tags", namespace_project_tags_path(viewer.project.namespace, viewer.project) + = link_to "the tags", project_tags_path(viewer.project) diff --git a/app/views/projects/blob/viewers/_readme.html.haml b/app/views/projects/blob/viewers/_readme.html.haml index 334b33faf48..507f44d4745 100644 --- a/app/views/projects/blob/viewers/_readme.html.haml +++ b/app/views/projects/blob/viewers/_readme.html.haml @@ -1,4 +1,4 @@ = icon('info-circle fw') = succeed '.' do To learn more about this project, read - = link_to "the wiki", namespace_project_wikis_path(viewer.project.namespace, viewer.project) + = link_to "the wiki", project_wikis_path(viewer.project) diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml index 6684ecfce81..07272ea2df1 100644 --- a/app/views/projects/boards/_show.html.haml +++ b/app/views/projects/boards/_show.html.haml @@ -2,6 +2,10 @@ - @content_class = "issue-boards-content" - page_title "Boards" +- if show_new_nav? + - content_for :sub_title_before do + %li= link_to "Issues", project_issues_path(@project) + - content_for :page_specific_javascripts do = webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'filtered_search' @@ -30,7 +34,7 @@ ":key" => "_uid" } = render "projects/boards/components/sidebar" %board-add-issues-modal{ "blank-state-image" => render('shared/empty_states/icons/issues.svg'), - "new-issue-path" => new_namespace_project_issue_path(@project.namespace, @project), + "new-issue-path" => new_project_issue_path(@project), "milestone-path" => milestones_filter_dropdown_path, "label-path" => labels_filter_path, ":issue-link-base" => "issueLinkBase", diff --git a/app/views/projects/boards/components/_sidebar.html.haml b/app/views/projects/boards/components/_sidebar.html.haml index 24d76da6f06..09d70f658a3 100644 --- a/app/views/projects/boards/components/_sidebar.html.haml +++ b/app/views/projects/boards/components/_sidebar.html.haml @@ -23,4 +23,5 @@ = render "projects/boards/components/sidebar/labels" = render "projects/boards/components/sidebar/notifications" %remove-btn{ ":issue" => "issue", - ":list" => "list" } + ":list" => "list", + "v-if" => "canRemove" } diff --git a/app/views/projects/boards/components/sidebar/_assignee.html.haml b/app/views/projects/boards/components/sidebar/_assignee.html.haml index e8db868f49b..8d957613be1 100644 --- a/app/views/projects/boards/components/sidebar/_assignee.html.haml +++ b/app/views/projects/boards/components/sidebar/_assignee.html.haml @@ -19,10 +19,11 @@ ":data-name" => "assignee.name", ":data-username" => "assignee.username" } .dropdown - %button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: "button", ref: "assigneeDropdown", data: { toggle: "dropdown", field_name: "issue[assignee_ids][]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, null_user: "true", multi_select: "true", 'max-select' => 1, dropdown: { header: 'Assignee' } }, + - dropdown_options = issue_assignees_dropdown_options + %button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: 'button', ref: 'assigneeDropdown', data: { toggle: 'dropdown', field_name: 'issue[assignee_ids][]', first_user: current_user&.username, current_user: 'true', project_id: @project.id, null_user: 'true', multi_select: 'true', 'dropdown-header': dropdown_options[:data][:'dropdown-header'], 'max-select': dropdown_options[:data][:'max-select'] }, ":data-issuable-id" => "issue.id", - ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } - Select assignee + ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" } + = dropdown_options[:title] = icon("chevron-down") .dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author = dropdown_title("Assign to") diff --git a/app/views/projects/boards/components/sidebar/_due_date.html.haml b/app/views/projects/boards/components/sidebar/_due_date.html.haml index 1a3b88e28c5..f44a9d49a54 100644 --- a/app/views/projects/boards/components/sidebar/_due_date.html.haml +++ b/app/views/projects/boards/components/sidebar/_due_date.html.haml @@ -23,7 +23,7 @@ .dropdown %button.dropdown-menu-toggle.js-due-date-select.js-issue-boards-due-date{ type: 'button', data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" }, - ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } + ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" } %span.dropdown-toggle-text Due date = icon('chevron-down') .dropdown-menu.dropdown-menu-due-date diff --git a/app/views/projects/boards/components/sidebar/_labels.html.haml b/app/views/projects/boards/components/sidebar/_labels.html.haml index bee0f3dd065..7d0c35fe183 100644 --- a/app/views/projects/boards/components/sidebar/_labels.html.haml +++ b/app/views/projects/boards/components/sidebar/_labels.html.haml @@ -19,8 +19,8 @@ ":value" => "label.id" } .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-board-sidebar{ type: "button", - data: { toggle: "dropdown", field_name: "issue[label_names][]", show_no: "true", show_any: "true", project_id: @project.id, labels: namespace_project_labels_path(@project.namespace, @project, :json), namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path) }, - ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } + data: { toggle: "dropdown", field_name: "issue[label_names][]", show_no: "true", show_any: "true", project_id: @project.id, labels: project_labels_path(@project, :json), namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path) }, + ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" } %span.dropdown-toggle-text Label = icon('chevron-down') diff --git a/app/views/projects/boards/components/sidebar/_milestone.html.haml b/app/views/projects/boards/components/sidebar/_milestone.html.haml index 4e46351bf8a..002e9994ee0 100644 --- a/app/views/projects/boards/components/sidebar/_milestone.html.haml +++ b/app/views/projects/boards/components/sidebar/_milestone.html.haml @@ -16,10 +16,10 @@ name: "issue[milestone_id]", "v-if" => "issue.milestone" } .dropdown - %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: "issue", use_id: "true", default_no: "true" }, + %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: project_milestones_path(@project, :json), ability_name: "issue", use_id: "true", default_no: "true" }, ":data-selected" => "milestoneTitle", ":data-issuable-id" => "issue.id", - ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } + ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" } Milestone = icon("chevron-down") .dropdown-menu.dropdown-select.dropdown-menu-selectable diff --git a/app/views/projects/boards/components/sidebar/_notifications.html.haml b/app/views/projects/boards/components/sidebar/_notifications.html.haml index a08c7f2af09..aaddd7e249f 100644 --- a/app/views/projects/boards/components/sidebar/_notifications.html.haml +++ b/app/views/projects/boards/components/sidebar/_notifications.html.haml @@ -1,5 +1,5 @@ - if current_user - .block.light.subscription{ ":data-url" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '/toggle_subscription'" } + .block.light.subscription{ ":data-url" => "'#{project_issues_path(@project)}/' + issue.id + '/toggle_subscription'" } %span.issuable-header-text.hide-collapsed.pull-left Notifications %button.btn.btn-default.pull-right.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" } diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 869633e016d..19712a8f1be 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -6,7 +6,7 @@ - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project)) %li{ class: "js-branch-#{branch.name}" } %div - = link_to namespace_project_tree_path(@project.namespace, @project, branch.name), class: 'item-title str-truncated ref-name' do + = link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated ref-name' do = icon('code-fork') = branch.name @@ -25,7 +25,7 @@ Merge request - if branch.name != @repository.root_ref - = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: "btn btn-default #{'prepend-left-10' unless merge_project}", method: :post, title: "Compare" do + = link_to project_compare_index_path(@project, from: @repository.root_ref, to: branch.name), class: "btn btn-default #{'prepend-left-10' unless merge_project}", method: :post, title: "Compare" do Compare = render 'projects/buttons/download', project: @project, ref: branch.name, pipeline: @refs_pipelines[branch.name] @@ -42,7 +42,7 @@ title: "Delete protected branch", data: { toggle: "modal", target: "#modal-delete-branch", - delete_path: namespace_project_branch_path(@project.namespace, @project, branch.name), + delete_path: project_branch_path(@project, branch.name), branch_name: branch.name } } = icon("trash-o") - else @@ -51,7 +51,7 @@ title: "Only a project master or owner can delete a protected branch" } = icon("trash-o") - else - = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), + = link_to project_branch_path(@project, branch.name), class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip", title: "Delete branch", method: :delete, diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml index ad8f9da0621..18fbb81c167 100644 --- a/app/views/projects/branches/_commit.html.haml +++ b/app/views/projects/branches/_commit.html.haml @@ -1,9 +1,9 @@ .branch-commit .icon-container.commit-icon = custom_icon("icon_commit") - = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-sha" + = link_to commit.short_id, project_commit_path(project, commit.id), class: "commit-sha" · %span.str-truncated - = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" + = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message" · #{time_ago_with_tooltip(commit.committed_date)} diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 4bade77a077..8bc1996452b 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -6,7 +6,7 @@ .top-area.adjust .nav-text Protected branches can be managed in - = link_to 'project settings', namespace_project_protected_branches_path(@project.namespace, @project) + = link_to 'project settings', project_protected_branches_path(@project) .nav-controls = form_tag(filter_branches_path, method: :get) do @@ -25,9 +25,9 @@ = link_to title, filter_branches_path(sort: value), class: ("is-active" if @sort == value) - if can? current_user, :push_code, @project - = link_to namespace_project_merged_branches_path(@project.namespace, @project), class: 'btn btn-inverted btn-remove has-tooltip', title: "Delete all branches that are merged into '#{@project.repository.root_ref}'", method: :delete, data: { confirm: "Deleting the merged branches cannot be undone. Are you sure?", container: 'body' } do + = link_to project_merged_branches_path(@project), class: 'btn btn-inverted btn-remove has-tooltip', title: "Delete all branches that are merged into '#{@project.repository.root_ref}'", method: :delete, data: { confirm: "Deleting the merged branches cannot be undone. Are you sure?", container: 'body' } do Delete merged branches - = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do + = link_to new_project_branch_path(@project), class: 'btn btn-create' do New branch - if @branches.any? diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml index 5a0eba3551f..03eefcc2b4d 100644 --- a/app/views/projects/branches/new.html.haml +++ b/app/views/projects/branches/new.html.haml @@ -27,7 +27,7 @@ .help-block Existing branch name, tag, or commit SHA .form-actions = button_tag 'Create branch', class: 'btn btn-create', tabindex: 3 - = link_to 'Cancel', namespace_project_branches_path(@project.namespace, @project), class: 'btn btn-cancel' + = link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel' :javascript var availableRefs = #{@project.repository.ref_names.to_json}; diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index a73ddd5eb33..883922dbf04 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -10,19 +10,19 @@ %li.dropdown-header #{ _('Source code') } %li - = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow', download: '' do + = link_to archive_project_repository_path(project, ref: ref, format: 'zip'), rel: 'nofollow', download: '' do %i.fa.fa-download %span= _('Download zip') %li - = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow', download: '' do + = link_to archive_project_repository_path(project, ref: ref, format: 'tar.gz'), rel: 'nofollow', download: '' do %i.fa.fa-download %span= _('Download tar.gz') %li - = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow', download: '' do + = link_to archive_project_repository_path(project, ref: ref, format: 'tar.bz2'), rel: 'nofollow', download: '' do %i.fa.fa-download %span= _('Download tar.bz2') %li - = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow', download: '' do + = link_to archive_project_repository_path(project, ref: ref, format: 'tar'), rel: 'nofollow', download: '' do %i.fa.fa-download %span= _('Download tar') @@ -37,7 +37,7 @@ %li.dropdown-header Previous Artifacts - artifacts.each do |job| %li - = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do + = link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do %i.fa.fa-download %span #{ s_('DownloadArtifacts|Download') } '#{job.name}' diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index aa1a533b5cb..b04d6a1fa5e 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -10,19 +10,19 @@ - if can_create_issue %li - = link_to new_namespace_project_issue_path(@project.namespace, @project) do + = link_to new_project_issue_path(@project) do = icon('exclamation-circle fw') #{ _('New issue') } - if merge_project %li - = link_to namespace_project_new_merge_request_path(merge_project.namespace, merge_project) do + = link_to project_new_merge_request_path(merge_project) do = icon('tasks fw') #{ _('New merge request') } - if can_create_snippet %li - = link_to new_namespace_project_snippet_path(@project.namespace, @project) do + = link_to new_project_snippet_path(@project) do = icon('file-text-o fw') #{ _('New snippet') } @@ -31,28 +31,28 @@ - if can?(current_user, :push_code, @project) %li - = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do + = link_to project_new_blob_path(@project, @project.default_branch || 'master') do = icon('file fw') #{ _('New file') } %li - = link_to new_namespace_project_branch_path(@project.namespace, @project) do + = link_to new_project_branch_path(@project) do = icon('code-fork fw') #{ _('New branch') } %li - = link_to new_namespace_project_tag_path(@project.namespace, @project) do + = link_to new_project_tag_path(@project) do = icon('tags fw') #{ _('New tag') } - elsif current_user && current_user.already_forked?(@project) %li - = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do + = link_to project_new_blob_path(@project, @project.default_branch || 'master') do = icon('file fw') #{ _('New file') } - elsif can?(current_user, :fork_project, @project) %li - - continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'), + - continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'), notice: edit_in_new_fork_notice, notice_now: edit_in_new_fork_notice_now } - - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id, + - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params) = link_to fork_path, method: :post do = icon('file fw') diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml index 42f8c75f57b..f45cc7f0f45 100644 --- a/app/views/projects/buttons/_fork.html.haml +++ b/app/views/projects/buttons/_fork.html.haml @@ -5,14 +5,14 @@ = custom_icon('icon_fork') %span= s_('GoToYourFork|Fork') - elsif !current_user.can_create_project? - = link_to new_namespace_project_fork_path(@project.namespace, @project), title: _('You have reached your project limit'), class: 'btn has-tooltip disabled' do + = link_to new_project_fork_path(@project), title: _('You have reached your project limit'), class: 'btn has-tooltip disabled' do = custom_icon('icon_fork') %span= s_('CreateNewFork|Fork') - else - = link_to new_namespace_project_fork_path(@project.namespace, @project), class: 'btn' do + = link_to new_project_fork_path(@project), class: 'btn' do = custom_icon('icon_fork') %span= s_('CreateNewFork|Fork') .count-with-arrow %span.arrow - = link_to namespace_project_forks_path(@project.namespace, @project), title: n_('Fork', 'Forks', @project.forks_count), class: 'count' do + = link_to project_forks_path(@project), title: n_('Fork', 'Forks', @project.forks_count), class: 'count' do = @project.forks_count diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml index 58413e2fc52..e248676be0d 100644 --- a/app/views/projects/buttons/_star.html.haml +++ b/app/views/projects/buttons/_star.html.haml @@ -1,5 +1,5 @@ - if current_user - = link_to toggle_star_namespace_project_path(@project.namespace, @project), { class: 'btn star-btn toggle-star', method: :post, remote: true } do + = link_to toggle_star_project_path(@project), { class: 'btn star-btn toggle-star', method: :post, remote: true } do - if current_user.starred?(@project) = icon('star') %span.starred= _('Unstar') diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index d9f28d66b66..c1842527480 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -14,7 +14,7 @@ %td.branch-commit - if can?(current_user, :read_build, job) - = link_to namespace_project_job_url(job.project.namespace, job.project, job) do + = link_to project_job_url(job.project, job) do %span.build-link ##{job.id} - else %span.build-link ##{job.id} @@ -30,7 +30,7 @@ = custom_icon("icon_commit") - if commit_sha - = link_to job.short_sha, namespace_project_commit_path(job.project.namespace, job.project, job.sha), class: "commit-sha" + = link_to job.short_sha, project_commit_path(job.project, job.sha), class: "commit-sha" - if job.stuck? = icon('warning', class: 'text-warning has-tooltip', title: 'Job is stuck. Check runners.') @@ -63,7 +63,7 @@ - if admin %td - if job.project - = link_to job.project.name_with_namespace, admin_namespace_project_path(job.project.namespace, job.project) + = link_to job.project.name_with_namespace, admin_project_path(job.project) %td - if job.try(:runner) = runner_link(job.runner) @@ -95,16 +95,16 @@ %td .pull-right - if can?(current_user, :read_build, job) && job.artifacts? - = link_to download_namespace_project_job_artifacts_path(job.project.namespace, job.project, job), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do + = link_to download_project_job_artifacts_path(job.project, job), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do = icon('download') - if can?(current_user, :update_build, job) - if job.active? - = link_to cancel_namespace_project_job_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do + = link_to cancel_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do = icon('remove', class: 'cred') - elsif allow_retry - if job.playable? && !admin && can?(current_user, :update_build, job) - = link_to play_namespace_project_job_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do + = link_to play_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do = custom_icon('icon_play') - elsif job.retryable? - = link_to retry_namespace_project_job_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do + = link_to retry_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do = icon('repeat') diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml index 2267f123e38..d0a380516f9 100644 --- a/app/views/projects/commit/_change.html.haml +++ b/app/views/projects/commit/_change.html.haml @@ -22,7 +22,7 @@ = label_tag 'start_branch', branch_label, class: 'control-label' .col-sm-10 = hidden_field_tag :start_branch, @project.default_branch, id: 'start_branch' - = dropdown_tag(@project.default_branch, options: { title: s_("BranchSwitcherTitle|Switch branch"), filter: true, placeholder: s_("BranchSwitcherPlaceholder|Search branches"), toggle_class: 'js-project-refs-dropdown dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "start_branch", selected: @project.default_branch, start_branch: @project.default_branch, refs_url: namespace_project_branches_path(@project.namespace, @project), submit_form_on_click: false } }) + = dropdown_tag(@project.default_branch, options: { title: s_("BranchSwitcherTitle|Switch branch"), filter: true, placeholder: s_("BranchSwitcherPlaceholder|Search branches"), toggle_class: 'js-project-refs-dropdown dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "start_branch", selected: @project.default_branch, start_branch: @project.default_branch, refs_url: project_branches_path(@project), submit_form_on_click: false } }) - if can?(current_user, :push_code, @project) = render 'shared/new_merge_request_checkbox' diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml index 8aed88da38b..f3f11b5b405 100644 --- a/app/views/projects/commit/_ci_menu.html.haml +++ b/app/views/projects/commit/_ci_menu.html.haml @@ -1,10 +1,10 @@ %ul.nav-links.no-top.no-bottom.commit-ci-menu = nav_link(path: 'commit#show') do - = link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do + = link_to project_commit_path(@project, @commit.id) do Changes %span.badge= @diffs.size - if can?(current_user, :read_pipeline, @project) = nav_link(path: 'commit#pipelines') do - = link_to pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id) do + = link_to pipelines_project_commit_path(@project, @commit.id) do Pipelines %span.badge= @commit.pipelines.size diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 7fe44816bae..572c368990e 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -21,7 +21,7 @@ %span.btn.disabled.btn-grouped.hidden-xs.append-right-10 = icon('comment') = @notes_count - = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-default append-right-10 hidden-xs hidden-sm" do + = link_to project_tree_path(@project, @commit), class: "btn btn-default append-right-10 hidden-xs hidden-sm" do #{ _('Browse files') } .dropdown.inline %a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } } @@ -29,22 +29,22 @@ = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right %li.visible-xs-block.visible-sm-block - = link_to namespace_project_tree_path(@project.namespace, @project, @commit) do + = link_to project_tree_path(@project, @commit) do _('Browse Files') - unless @commit.has_been_reverted?(current_user) %li.clearfix - = revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false) + = revert_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false) %li.clearfix - = cherry_pick_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false) + = cherry_pick_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false) - if can_collaborate_with_project? %li.clearfix - = link_to s_("CreateTag|Tag"), new_namespace_project_tag_path(@project.namespace, @project, ref: @commit) + = link_to s_("CreateTag|Tag"), new_project_tag_path(@project, ref: @commit) %li.divider %li.dropdown-header #{ _('Download') } - unless @commit.parents.length > 1 - %li= link_to s_("DownloadCommit|Email Patches"), namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch) - %li= link_to s_("DownloadCommit|Plain Diff"), namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff) + %li= link_to s_("DownloadCommit|Email Patches"), project_commit_path(@project, @commit, format: :patch) + %li= link_to s_("DownloadCommit|Plain Diff"), project_commit_path(@project, @commit, format: :diff) .commit-box %h3.commit-title @@ -59,7 +59,7 @@ = custom_icon("icon_commit") %span.cgray= n_('parent', 'parents', @commit.parents.count) - @commit.parents.each do |parent| - = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "commit-sha" + = link_to parent.short_id, project_commit_path(@project, parent), class: "commit-sha" %span.commit-info.branches %i.fa.fa-spinner.fa-spin @@ -67,10 +67,10 @@ - last_pipeline = @commit.last_pipeline .well-segment.pipeline-info .status-icon-container{ class: "ci-status-icon-#{@commit.status}" } - = link_to namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id) do + = link_to project_pipeline_path(@project, last_pipeline.id) do = ci_icon_for_status(last_pipeline.status) #{ _('Pipeline') } - = link_to "##{last_pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id) + = link_to "##{last_pipeline.id}", project_pipeline_path(@project, last_pipeline.id) = ci_label_for_status(last_pipeline.status) - if last_pipeline.stages_count.nonzero? #{ n_(s_('Pipeline|with stage'), s_('Pipeline|with stages'), last_pipeline.stages_count) } @@ -80,4 +80,4 @@ = time_interval_in_words last_pipeline.duration :javascript - $(".commit-info.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}"); + $(".commit-info.branches").load("#{branches_project_commit_path(@project, @commit.id)}"); diff --git a/app/views/projects/commit/pipelines.html.haml b/app/views/projects/commit/pipelines.html.haml index ac93eac41ac..c66ea873dba 100644 --- a/app/views/projects/commit/pipelines.html.haml +++ b/app/views/projects/commit/pipelines.html.haml @@ -2,4 +2,4 @@ = render 'commit_box' = render 'ci_menu' -= render 'projects/commit/pipelines_list', endpoint: pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id) += render 'projects/commit/pipelines_list', endpoint: pipelines_project_commit_path(@project, @commit.id) diff --git a/app/views/projects/commits/_commit.atom.builder b/app/views/projects/commits/_commit.atom.builder index 1657fb46163..d806acdda13 100644 --- a/app/views/projects/commits/_commit.atom.builder +++ b/app/views/projects/commits/_commit.atom.builder @@ -1,6 +1,6 @@ xml.entry do - xml.id namespace_project_commit_url(@project.namespace, @project, id: commit.id) - xml.link href: namespace_project_commit_url(@project.namespace, @project, id: commit.id) + xml.id project_commit_url(@project, id: commit.id) + xml.link href: project_commit_url(@project, id: commit.id) xml.title truncate(commit.title, length: 80) xml.updated commit.committed_date.xmlschema xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(commit.author_email)) diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 8a4ef5a45b3..1033bad0d49 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -16,7 +16,7 @@ .commit-detail .commit-content - = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message item-title" + = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message item-title" %span.commit-row-message.visible-xs-inline · = commit.short_id @@ -39,6 +39,6 @@ .commit-actions.flex-row.hidden-xs - if commit.status(ref) = render_commit_status(commit, ref: ref) - = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-sha btn btn-transparent" + = link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha btn btn-transparent" = clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard")) = link_to_browse_code(project, commit) diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml index cf8dffc8957..c764e35dd2a 100644 --- a/app/views/projects/commits/_commits.html.haml +++ b/app/views/projects/commits/_commits.html.haml @@ -12,4 +12,4 @@ - if hidden > 0 %li.alert.alert-warning - = n_('%d additional commit has been omitted to prevent performance issues.', '%d additional commits have been omitted to prevent performance issues.', hidden) % number_with_delimiter(hidden) + = n_('%s additional commit has been omitted to prevent performance issues.', '%s additional commits have been omitted to prevent performance issues.', hidden) % number_with_delimiter(hidden) diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index ebeaab863bc..e1549baef89 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -4,33 +4,33 @@ .nav-links.sub-nav.scrolling-tabs %ul{ class: (container_class) } = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do - = link_to project_files_path(@project) do + = link_to project_tree_path(@project) do #{ _('Files') } = nav_link(controller: [:commit, :commits]) do - = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do + = link_to project_commits_path(@project, current_ref) do #{ _('Commits') } = nav_link(html_options: {class: branches_tab_class}) do - = link_to namespace_project_branches_path(@project.namespace, @project) do + = link_to project_branches_path(@project) do #{ _('Branches') } = nav_link(controller: [:tags, :releases]) do - = link_to namespace_project_tags_path(@project.namespace, @project) do + = link_to project_tags_path(@project) do #{ _('Tags') } = nav_link(path: 'graphs#show') do - = link_to namespace_project_graph_path(@project.namespace, @project, current_ref) do + = link_to project_graph_path(@project, current_ref) do #{ _('Contributors') } = nav_link(controller: %w(network)) do - = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do + = link_to project_network_path(@project, current_ref) do #{ s_('ProjectNetworkGraph|Graph') } = nav_link(controller: :compare) do - = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do + = link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do #{ _('Compare') } = nav_link(path: 'graphs#charts') do - = link_to charts_namespace_project_graph_path(@project.namespace, @project, current_ref) do + = link_to charts_project_graph_path(@project, current_ref) do #{ _('Charts') } diff --git a/app/views/projects/commits/_inline_commit.html.haml b/app/views/projects/commits/_inline_commit.html.haml index 5fb89935467..48cefbe45f2 100644 --- a/app/views/projects/commits/_inline_commit.html.haml +++ b/app/views/projects/commits/_inline_commit.html.haml @@ -1,8 +1,8 @@ %li.commit.inline-commit .commit-row-title - = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-sha" + = link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha" %span.str-truncated - = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" + = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message" .pull-right #{time_ago_with_tooltip(commit.committed_date)} diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder index 9cf792e1721..a9b77631474 100644 --- a/app/views/projects/commits/show.atom.builder +++ b/app/views/projects/commits/show.atom.builder @@ -1,7 +1,7 @@ xml.title "#{@project.name}:#{@ref} commits" -xml.link href: namespace_project_commits_url(@project.namespace, @project, @ref, rss_url_options), rel: "self", type: "application/atom+xml" -xml.link href: namespace_project_commits_url(@project.namespace, @project, @ref), rel: "alternate", type: "text/html" -xml.id namespace_project_commits_url(@project.namespace, @project, @ref) +xml.link href: project_commits_url(@project, @ref, rss_url_options), rel: "self", type: "application/atom+xml" +xml.link href: project_commits_url(@project, @ref), rel: "alternate", type: "text/html" +xml.id project_commits_url(@project, @ref) xml.updated @commits.first.committed_date.xmlschema if @commits.any? xml << render(@commits) if @commits.any? diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index fabd825aec8..b8547c10c73 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -2,33 +2,34 @@ - page_title _("Commits"), @ref = content_for :meta_tags do - = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") + = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") = content_for :sub_nav do = render "head" %div{ class: container_class } - .row-content-block.second-block.content-component-block.flex-container-block - .tree-ref-holder - = render 'shared/ref_switcher', destination: 'commits' + .tree-holder + .nav-block + .tree-ref-container + .tree-ref-holder + = render 'shared/ref_switcher', destination: 'commits' + + %ul.breadcrumb.repo-breadcrumb + = commits_breadcrumbs + .tree-controls.hidden-xs.hidden-sm + - if @merge_request.present? + .control + = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn' + - elsif create_mr_button?(@repository.root_ref, @ref) + .control + = link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' - %ul.breadcrumb.repo-breadcrumb - = commits_breadcrumbs - - .block-controls.hidden-xs.hidden-sm - - if @merge_request.present? .control - = link_to _("View open merge request"), namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn' - - elsif create_mr_button?(@repository.root_ref, @ref) + = form_tag(project_commits_path(@project, @id), method: :get, class: 'commits-search-form') do + = search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false } .control - = link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' - - .control - = form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') do - = search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false } - .control - = link_to namespace_project_commits_path(@project.namespace, @project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do - = icon("rss") + = link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do + = icon("rss") %div{ id: dom_id(@project) } %ol#commits-list.list-unstyled.content_list diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index adb724c1b8d..94b7db5eb25 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -1,4 +1,4 @@ -= form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do += form_tag project_compare_index_path(@project), method: :post, class: 'form-inline js-requires-input' do .clearfix - if params[:to] && params[:from] .compare-switch-container @@ -7,7 +7,7 @@ .input-group.inline-input-group %span.input-group-addon from = hidden_field_tag :from, params[:from] - = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do + = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do .dropdown-toggle-text.str-truncated= params[:from] || 'Select branch/tag' = render 'shared/ref_dropdown' .compare-ellipsis.inline ... @@ -15,12 +15,12 @@ .input-group.inline-input-group %span.input-group-addon to = hidden_field_tag :to, params[:to] - = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do + = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do .dropdown-toggle-text.str-truncated= params[:to] || 'Select branch/tag' = render 'shared/ref_dropdown' = button_tag "Compare", class: "btn btn-create commits-compare-btn" - if @merge_request.present? - = link_to "View open merge request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'prepend-left-10 btn' + = link_to "View open merge request", project_merge_request_path(@project, @merge_request), class: 'prepend-left-10 btn' - elsif create_mr_button? = link_to "Create merge request", create_mr_path, class: 'prepend-left-10 btn' diff --git a/app/views/projects/deploy_keys/_index.html.haml b/app/views/projects/deploy_keys/_index.html.haml index cb98ce04430..45985a5ecef 100644 --- a/app/views/projects/deploy_keys/_index.html.haml +++ b/app/views/projects/deploy_keys/_index.html.haml @@ -12,4 +12,4 @@ Create a new deploy key for this project = render @deploy_keys.form_partial_path %hr - #js-deploy-keys{ data: { endpoint: namespace_project_deploy_keys_path } } + #js-deploy-keys{ data: { endpoint: project_deploy_keys_path(@project) } } diff --git a/app/views/projects/deploy_keys/edit.html.haml b/app/views/projects/deploy_keys/edit.html.haml index 37219f8d7ae..cd910b82b57 100644 --- a/app/views/projects/deploy_keys/edit.html.haml +++ b/app/views/projects/deploy_keys/edit.html.haml @@ -7,4 +7,4 @@ = render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key } .form-actions = f.submit 'Save changes', class: 'btn-save btn' - = link_to 'Cancel', namespace_project_settings_repository_path(@project.namespace, @project), class: 'btn btn-cancel' + = link_to 'Cancel', project_settings_repository_path(@project), class: 'btn btn-cancel' diff --git a/app/views/projects/deployments/_commit.html.haml b/app/views/projects/deployments/_commit.html.haml index 4502c397d29..4c22166c256 100644 --- a/app/views/projects/deployments/_commit.html.haml +++ b/app/views/projects/deployments/_commit.html.haml @@ -6,12 +6,12 @@ = link_to deployment.ref, project_ref_path(@project, deployment.ref), class: "ref-name" .icon-container.commit-icon = custom_icon("icon_commit") - = link_to deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-sha" + = link_to deployment.short_sha, project_commit_path(@project, deployment.sha), class: "commit-sha" %p.commit-title.flex-truncate-parent %span.flex-truncate-child - if commit_title = deployment.commit_title = author_avatar(deployment.commit, size: 20) - = link_to_gfm commit_title, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-row-message" + = link_to_gfm commit_title, project_commit_path(@project, deployment.sha), class: "commit-row-message" - else Cant find HEAD commit for this branch diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml index 43708d22a0c..cd0fb21f8a7 100644 --- a/app/views/projects/diffs/_line.html.haml +++ b/app/views/projects/diffs/_line.html.haml @@ -19,6 +19,7 @@ - if plain = link_text - else + = add_diff_note_button(line_code, diff_file.position(line), type) %a{ href: "##{line_code}", data: { linenumber: link_text } } - discussion = line_discussions.try(:first) - if discussion && discussion.resolvable? && !plain @@ -29,7 +30,7 @@ = link_text - else %a{ href: "##{line_code}", data: { linenumber: link_text } } - %td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, diff_file.position(line), type) unless plain) }< + %td.line_content.noteable_line{ class: type }< - if email %pre= line.text - else diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml index 8e5f4d2573d..56d63250714 100644 --- a/app/views/projects/diffs/_parallel_view.html.haml +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -1,4 +1,5 @@ / Side-by-side diff view + .text-file.diff-wrap-lines.code.js-syntax-highlight{ data: diff_view_data } %table - diff_file.parallel_diff_lines.each do |line| @@ -18,11 +19,12 @@ - left_line_code = diff_file.line_code(left) - left_position = diff_file.position(left) %td.old_line.diff-line-num.js-avatar-container{ id: left_line_code, class: left.type, data: { linenumber: left.old_pos } } + = add_diff_note_button(left_line_code, left_position, 'old') %a{ href: "##{left_line_code}", data: { linenumber: left.old_pos } } - discussion_left = discussions_left.try(:first) - if discussion_left && discussion_left.resolvable? %diff-note-avatars{ "discussion-id" => discussion_left.id } - %td.line_content.parallel.noteable_line{ class: left.type, data: diff_view_line_data(left_line_code, left_position, 'old') }= diff_line_content(left.text) + %td.line_content.parallel.noteable_line{ class: left.type }= diff_line_content(left.text) - else %td.old_line.diff-line-num.empty-cell %td.line_content.parallel @@ -38,11 +40,12 @@ - right_line_code = diff_file.line_code(right) - right_position = diff_file.position(right) %td.new_line.diff-line-num.js-avatar-container{ id: right_line_code, class: right.type, data: { linenumber: right.new_pos } } + = add_diff_note_button(right_line_code, right_position, 'new') %a{ href: "##{right_line_code}", data: { linenumber: right.new_pos } } - discussion_right = discussions_right.try(:first) - if discussion_right && discussion_right.resolvable? %diff-note-avatars{ "discussion-id" => discussion_right.id } - %td.line_content.parallel.noteable_line{ class: right.type, data: diff_view_line_data(right_line_code, right_position, 'new') }= diff_line_content(right.text) + %td.line_content.parallel.noteable_line{ class: right.type }= diff_line_content(right.text) - else %td.old_line.diff-line-num.empty-cell %td.line_content.parallel diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml index 402c18c447e..da34a83d8e0 100644 --- a/app/views/projects/diffs/_warning.html.haml +++ b/app/views/projects/diffs/_warning.html.haml @@ -3,8 +3,8 @@ Too many changes to show. .pull-right - if current_controller?(:commit) - = link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-sm" - = link_to "Email patch", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch), class: "btn btn-sm" + = link_to "Plain diff", project_commit_path(@project, @commit, format: :diff), class: "btn btn-sm" + = link_to "Email patch", project_commit_path(@project, @commit, format: :patch), class: "btn btn-sm" - elsif current_controller?('projects/merge_requests/diffs') && @merge_request&.persisted? = link_to "Plain diff", merge_request_path(@merge_request, format: :diff), class: "btn btn-sm" = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-sm" diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index 19d08181223..33d3dcbeafa 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -15,7 +15,7 @@ .two-up.view %span.wrap .frame.deleted - %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_content_sha, diff_file.old_path)) } + %a{ href: project_blob_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path)) } %img{ src: old_blob_raw_path, alt: diff_file.old_path } %p.image-info.hide %span.meta-filesize= number_to_human_size(old_blob.size) @@ -27,7 +27,7 @@ %span.meta-height %span.wrap .frame.added - %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.content_sha, diff_file.new_path)) } + %a{ href: project_blob_path(@project, tree_join(diff_file.content_sha, diff_file.new_path)) } %img{ src: blob_raw_path, alt: diff_file.new_path } %p.image-info.hide %span.meta-filesize= number_to_human_size(blob.size) diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 78057facde7..087cb804449 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -134,7 +134,7 @@ .help-block The maximum file size allowed is 200KB. - if @project.avatar? %hr - = link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" + = link_to 'Remove avatar', project_avatar_path(@project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" = f.submit 'Save changes', class: "btn btn-save" .row.prepend-top-default @@ -148,7 +148,7 @@ Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects. .col-lg-8 - = link_to 'Housekeeping', housekeeping_namespace_project_path(@project.namespace, @project), + = link_to 'Housekeeping', housekeeping_project_path(@project), method: :post, class: "btn btn-default" %hr .row.prepend-top-default @@ -164,12 +164,12 @@ .col-lg-8 - if @project.export_project_path - = link_to 'Download export', download_export_namespace_project_path(@project.namespace, @project), + = link_to 'Download export', download_export_project_path(@project), rel: 'nofollow', download: '', method: :get, class: "btn btn-default" - = link_to 'Generate new export', generate_new_export_namespace_project_path(@project.namespace, @project), + = link_to 'Generate new export', generate_new_export_project_path(@project), method: :post, class: "btn btn-default" - else - = link_to 'Export project', export_namespace_project_path(@project.namespace, @project), + = link_to 'Export project', export_project_path(@project), method: :post, class: "btn btn-default" .bs-callout.bs-callout-info @@ -207,13 +207,13 @@ - if @project.archived? %p %strong Once active this project shows up in the search and on the dashboard. - = link_to 'Unarchive project', unarchive_namespace_project_path(@project.namespace, @project), + = link_to 'Unarchive project', unarchive_project_path(@project), data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." }, method: :post, class: "btn btn-success" - else %p %strong Archived projects cannot be committed to! - = link_to 'Archive project', archive_namespace_project_path(@project.namespace, @project), + = link_to 'Archive project', archive_project_path(@project), data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." }, method: :post, class: "btn btn-warning" %hr @@ -252,7 +252,7 @@ %p.append-bottom-0 Please select the group you want to transfer this project to in the dropdown to the right. .col-lg-8 - = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_namespace_project_path(@project.namespace, @project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } ) do |f| + = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } ) do |f| .form-group = label_tag :new_namespace_id, nil, class: 'label-light' do %span Select a new namespace @@ -276,7 +276,7 @@ = succeed "." do = link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project) .col-lg-8 - = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_namespace_project_path(@project.namespace, @project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f| + = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_project_path(@project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f| %p %strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source. = button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) } @@ -289,7 +289,7 @@ %p.append-bottom-0 Removing the project will delete its repository and all related resources including issues, merge requests etc. .col-lg-8 - = form_tag(namespace_project_path(@project.namespace, @project), method: :delete) do + = form_tag(project_path(@project), method: :delete) do %p %strong Removed projects cannot be restored! = button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) } diff --git a/app/views/projects/environments/_form.html.haml b/app/views/projects/environments/_form.html.haml index 6d040f5cfe6..1605f3a3351 100644 --- a/app/views/projects/environments/_form.html.haml +++ b/app/views/projects/environments/_form.html.haml @@ -19,4 +19,4 @@ .form-actions = f.submit 'Save', class: 'btn btn-save' - = link_to 'Cancel', namespace_project_environments_path(@project.namespace, @project), class: 'btn btn-cancel' + = link_to 'Cancel', project_environments_path(@project), class: 'btn btn-cancel' diff --git a/app/views/projects/environments/_stop.html.haml b/app/views/projects/environments/_stop.html.haml index 14a2d627203..c35f9af2873 100644 --- a/app/views/projects/environments/_stop.html.haml +++ b/app/views/projects/environments/_stop.html.haml @@ -1,5 +1,5 @@ - if can?(current_user, :create_deployment, environment) && environment.stop_action? .inline - = link_to stop_namespace_project_environment_path(@project.namespace, @project, environment), method: :post, + = link_to stop_project_environment_path(@project, environment), method: :post, class: 'btn stop-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to stop this environment?' } do = icon('stop', class: 'stop-env-icon') diff --git a/app/views/projects/environments/_terminal_button.html.haml b/app/views/projects/environments/_terminal_button.html.haml index 97de9c95de7..a6201bdbc42 100644 --- a/app/views/projects/environments/_terminal_button.html.haml +++ b/app/views/projects/environments/_terminal_button.html.haml @@ -1,3 +1,3 @@ - if environment.has_terminals? && can?(current_user, :admin_environment, @project) - = link_to terminal_namespace_project_environment_path(@project.namespace, @project, environment), class: 'btn terminal-button' do + = link_to terminal_project_environment_path(@project, environment), class: 'btn terminal-button' do = icon('terminal') diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 80d2b6f5d95..30cdbc5ae04 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -12,6 +12,6 @@ "can-create-environment" => can?(current_user, :create_environment, @project).to_s, "project-environments-path" => project_environments_path(@project), "project-stopped-environments-path" => project_environments_path(@project, scope: :stopped), - "new-environment-path" => new_namespace_project_environment_path(@project.namespace, @project), + "new-environment-path" => new_project_environment_path(@project), "help-page-path" => help_page_path("ci/environments"), "css-class" => container_class } } diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml index c5722cf5997..e9e1ad9ef30 100644 --- a/app/views/projects/environments/metrics.html.haml +++ b/app/views/projects/environments/metrics.html.haml @@ -10,12 +10,12 @@ .top-area .row .col-sm-6 - %h3.page-title + %h3 Environment: = link_to @environment.name, environment_path(@environment) - #prometheus-graphs{ data: { "settings-path": edit_namespace_project_service_path(@project.namespace, @project, 'prometheus'), + #prometheus-graphs{ data: { "settings-path": edit_project_service_path(@project, 'prometheus'), "documentation-path": help_page_path('administration/monitoring/prometheus/index.md'), - "additional-metrics": additional_metrics_namespace_project_environment_path(@project.namespace, @project, @environment, format: :json), - "has-metrics": "#{@environment.has_metrics?}", deployment_endpoint: namespace_project_environment_deployments_path(@project.namespace, @project, @environment, format: :json) } } + "additional-metrics": additional_metrics_project_environment_path(@project, @environment, format: :json), + "has-metrics": "#{@environment.has_metrics?}", deployment_endpoint: project_environment_deployments_path(@project, @environment, format: :json) } } diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 31e2bb11ce8..0ce0f5465fc 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -12,9 +12,9 @@ = render 'projects/environments/external_url', environment: @environment = render 'projects/environments/metrics_button', environment: @environment - if can?(current_user, :update_environment, @environment) - = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' + = link_to 'Edit', edit_project_environment_path(@project, @environment), class: 'btn' - if can?(current_user, :stop_environment, @environment) - = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post + = link_to 'Stop', stop_project_environment_path(@project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post .environments-container - if @deployments.blank? diff --git a/app/views/projects/environments/terminal.html.haml b/app/views/projects/environments/terminal.html.haml index 4c4aa0baff3..464135b5ac7 100644 --- a/app/views/projects/environments/terminal.html.haml +++ b/app/views/projects/environments/terminal.html.haml @@ -22,4 +22,4 @@ = render 'projects/deployments/actions', deployment: @environment.last_deployment .terminal-container{ class: container_class } - #terminal{ data: { project_path: "#{terminal_namespace_project_environment_path(@project.namespace, @project, @environment)}.ws" } } + #terminal{ data: { project_path: "#{terminal_project_environment_path(@project, @environment)}.ws" } } diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml index 8a409541fe5..e3bf48ee47f 100644 --- a/app/views/projects/find_file/show.html.haml +++ b/app/views/projects/find_file/show.html.haml @@ -7,7 +7,7 @@ = render 'shared/ref_switcher', destination: 'find_file', path: @path %ul.breadcrumb.repo-breadcrumb %li - = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do + = link_to project_tree_path(@project, @ref) do = @project.path %li.file-finder %input#file_find.form-control.file-finder-input{ type: "text", placeholder: _('Find by path'), autocomplete: 'off' } @@ -20,8 +20,8 @@ :javascript var projectFindFile = new ProjectFindFile($(".file-finder-holder"), { - url: "#{escape_javascript(namespace_project_files_path(@project.namespace, @project, @ref, @options.merge(format: :json)))}", - treeUrl: "#{escape_javascript(namespace_project_tree_path(@project.namespace, @project, @ref))}", - blobUrlTemplate: "#{escape_javascript(namespace_project_blob_path(@project.namespace, @project, @id || @commit.id))}" + url: "#{escape_javascript(project_files_path(@project, @ref, @options.merge(format: :json)))}", + treeUrl: "#{escape_javascript(project_tree_path(@project, @ref))}", + blobUrlTemplate: "#{escape_javascript(project_blob_path(@project, @id || @commit.id))}" }); new ShortcutsFindFile(projectFindFile); diff --git a/app/views/projects/forks/error.html.haml b/app/views/projects/forks/error.html.haml index 524b77783ef..d365bcd4ecc 100644 --- a/app/views/projects/forks/error.html.haml +++ b/app/views/projects/forks/error.html.haml @@ -20,6 +20,6 @@ = error %p - = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork", class: "btn" do + = link_to new_project_fork_path(@project), title: "Fork", class: "btn" do %i.fa.fa-code-fork Try to fork again diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml index f4aa523b32d..111cbcda266 100644 --- a/app/views/projects/forks/index.html.haml +++ b/app/views/projects/forks/index.html.haml @@ -34,7 +34,7 @@ = custom_icon('icon_fork') %span Fork - else - = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do + = link_to new_project_fork_path(@project), title: "Fork project", class: 'btn btn-new' do = custom_icon('icon_fork') %span Fork diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml index 5242bc72b71..0f36e1a7353 100644 --- a/app/views/projects/forks/new.html.haml +++ b/app/views/projects/forks/new.html.haml @@ -30,7 +30,7 @@ = namespace.human_name - else .fork-thumbnail - = link_to namespace_project_forks_path(@project.namespace, @project, namespace_key: namespace.id), method: "POST" do + = link_to project_forks_path(@project, namespace_key: namespace.id), method: "POST" do - if /no_((\w*)_)*avatar/.match(avatar) .no-avatar = icon 'question' 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 b23bbadbdb4..b98dc09534f 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 @@ -20,14 +20,14 @@ - 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) + = link_to generic_commit_status.ref, project_commits_path(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-sha" + = link_to generic_commit_status.short_sha, project_commit_path(generic_commit_status.project, generic_commit_status.sha), class: "commit-sha" - if retried = icon('warning', class: 'text-warning has-tooltip', title: 'Status was retried.') @@ -53,7 +53,7 @@ - if admin %td - 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) + = link_to generic_commit_status.project.name_with_namespace, admin_project_path(generic_commit_status.project) %td - if generic_commit_status.try(:runner) = runner_link(generic_commit_status.runner) diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 680f8ae6c8f..640e0d689ca 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -35,7 +35,7 @@ :javascript $.ajax({ type: "GET", - url: "#{namespace_project_graph_path(@project.namespace, @project, current_ref, format: :json)}", + url: "#{project_graph_path(@project, current_ref, format: :json)}", dataType: "json", success: function (data) { var graph = new ContributorsStatGraph(); diff --git a/app/views/projects/hook_logs/_index.html.haml b/app/views/projects/hook_logs/_index.html.haml index 6962b223451..05b06cfc8b2 100644 --- a/app/views/projects/hook_logs/_index.html.haml +++ b/app/views/projects/hook_logs/_index.html.haml @@ -28,7 +28,7 @@ %td.light = time_ago_with_tooltip(hook_log.created_at) %td - = link_to 'View details', namespace_project_hook_hook_log_path(project.namespace, project, hook, hook_log) + = link_to 'View details', project_hook_hook_log_path(project, hook, hook_log) = paginate hook_logs, theme: 'gitlab' diff --git a/app/views/projects/hook_logs/show.html.haml b/app/views/projects/hook_logs/show.html.haml index 2eabe92f8eb..ab5a7b117d7 100644 --- a/app/views/projects/hook_logs/show.html.haml +++ b/app/views/projects/hook_logs/show.html.haml @@ -6,6 +6,6 @@ Request details .col-lg-9 - = link_to 'Resend Request', retry_namespace_project_hook_hook_log_path(@project.namespace, @project, @hook, @hook_log), class: "btn btn-default pull-right prepend-left-10" + = link_to 'Resend Request', retry_project_hook_hook_log_path(@project, @hook, @hook_log), class: "btn btn-default pull-right prepend-left-10" = render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log } diff --git a/app/views/projects/hooks/edit.html.haml b/app/views/projects/hooks/edit.html.haml index fd382c1d63f..4944e0c8041 100644 --- a/app/views/projects/hooks/edit.html.haml +++ b/app/views/projects/hooks/edit.html.haml @@ -9,14 +9,13 @@ #{link_to 'Webhooks', help_page_path('user/project/integrations/webhooks')} can be used for binding events when something is happening within the project. .col-lg-9.append-bottom-default - = form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hook_path do |f| + = form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: project_hook_path(@project, @hook) do |f| = render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook } = f.submit 'Save changes', class: 'btn btn-create' - = link_to 'Test hook', test_namespace_project_hook_path(@project.namespace, @project, @hook), class: 'btn btn-default' - = link_to 'Remove', namespace_project_hook_path(@project.namespace, @project, @hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' } + = link_to 'Test hook', test_project_hook_path(@project, @hook), class: 'btn btn-default' + = link_to 'Remove', project_hook_path(@project, @hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' } %hr = render partial: 'projects/hook_logs/index', locals: { hook: @hook, hook_logs: @hook_logs, project: @project } - diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml index 25a87411cac..778ff91362d 100644 --- a/app/views/projects/imports/new.html.haml +++ b/app/views/projects/imports/new.html.haml @@ -12,7 +12,7 @@ :preserve #{h(sanitize_repo_path(@project, @project.import_error))} -= form_for @project, url: namespace_project_import_path(@project.namespace, @project), method: :post, html: { class: 'form-horizontal' } do |f| += form_for @project, url: project_import_path(@project), method: :post, html: { class: 'form-horizontal' } do |f| = render "shared/import_form", f: f .form-actions diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml index 7a188cb6445..e9f21594a71 100644 --- a/app/views/projects/issues/_head.html.haml +++ b/app/views/projects/issues/_head.html.haml @@ -5,29 +5,29 @@ %ul{ class: (container_class) } - if project_nav_tab?(:issues) && !current_controller?(:merge_requests) = nav_link(controller: :issues) do - = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do + = link_to project_issues_path(@project), title: 'Issues' do %span List = nav_link(controller: :boards) do - = link_to namespace_project_boards_path(@project.namespace, @project), title: 'Board' do + = link_to project_boards_path(@project), title: 'Board' do %span Board - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests) = nav_link(controller: :merge_requests) do - = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do + = link_to project_merge_requests_path(@project), title: 'Merge Requests' do %span Merge Requests - if project_nav_tab? :labels = nav_link(controller: :labels) do - = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do + = link_to project_labels_path(@project), title: 'Labels' do %span Labels - if project_nav_tab? :milestones = nav_link(controller: :milestones) do - = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do + = link_to project_milestones_path(@project), title: 'Milestones' do %span Milestones diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 9e4e6934ca9..7dc35be57a6 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -4,43 +4,49 @@ .issue-check.hidden = check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected_issue" .issue-info-container - .issue-title.title - %span.issue-title-text - = confidential_icon(issue) - = link_to issue.title, issue_path(issue) + .issue-main-info + .issue-title.title + %span.issue-title-text + = confidential_icon(issue) + = link_to issue.title, issue_path(issue) + - if issue.tasks? + %span.task-status.hidden-xs + + = issue.task_status + + .issuable-info + %span.issuable-reference + #{issuable_reference(issue)} + %span.issuable-authored.hidden-xs + · + opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} + by #{link_to_member(@project, issue.author, avatar: false)} + - if issue.milestone + %span.issuable-milestone.hidden-xs + + = link_to project_issues_path(issue.project, milestone_title: issue.milestone.title) do + = icon('clock-o') + = issue.milestone.title + - if issue.due_date + %span.issuable-due-date.hidden-xs{ class: "#{'cred' if issue.overdue?}" } + + = icon('calendar') + = issue.due_date.to_s(:medium) + - if issue.labels.any? + + - issue.labels.each do |label| + = link_to_label(label, subject: issue.project, css_class: 'label-link') + + .issuable-meta %ul.controls - if issue.closed? - %li + %li.issuable-status CLOSED - - if issue.assignees.any? %li = render 'shared/issuable/assignees', project: @project, issue: issue = render 'shared/issuable_meta_data', issuable: issue - .issue-info - #{issuable_reference(issue)} · - opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} - by #{link_to_member(@project, issue.author, avatar: false)} - - if issue.milestone - - = link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do - = icon('clock-o') - = issue.milestone.title - - if issue.due_date - %span{ class: "#{'cred' if issue.overdue?}" } - - = icon('calendar') - = issue.due_date.to_s(:medium) - - if issue.labels.any? - - - issue.labels.each do |label| - = link_to_label(label, subject: issue.project, css_class: 'label-link') - - if issue.tasks? - - %span.task-status - = issue.task_status - - .pull-right.issue-updated-at + .pull-right.issuable-updated-at.hidden-xs %span updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')} diff --git a/app/views/projects/issues/_issue_by_email.html.haml b/app/views/projects/issues/_issue_by_email.html.haml index 35b7d1b920c..264032a3a31 100644 --- a/app/views/projects/issues/_issue_by_email.html.haml +++ b/app/views/projects/issues/_issue_by_email.html.haml @@ -30,5 +30,5 @@ Anyone who gets ahold of it can create issues as if they were you. You should - = link_to 'reset it', new_issue_address_namespace_project_path(@project.namespace, @project), class: 'incoming-email-token-reset' + = link_to 'reset it', new_issue_address_project_path(@project), class: 'incoming-email-token-reset' if that ever happens. diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml index bda52fe461c..6a567487514 100644 --- a/app/views/projects/issues/_merge_requests.html.haml +++ b/app/views/projects/issues/_merge_requests.html.haml @@ -18,7 +18,7 @@ - unless @issue.project.id == merge_request.target_project.id in - project = merge_request.target_project - = link_to project.name_with_namespace, namespace_project_path(project.namespace, project) + = link_to project.name_with_namespace, project_path(project) - if merge_request.merged? %span.merge-request-status.prepend-left-10.merged diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml new file mode 100644 index 00000000000..756faf4625e --- /dev/null +++ b/app/views/projects/issues/_nav_btns.html.haml @@ -0,0 +1,10 @@ += link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10 has-tooltip', title: 'Subscribe' do + = icon('rss') +- if @can_bulk_update + = button_tag "Edit Issues", class: "btn btn-default append-right-10 js-bulk-update-toggle" += link_to "New issue", new_project_issue_path(@project, + issue: { assignee_id: issues_finder.assignee.try(:id), + milestone_id: issues_finder.milestones.first.try(:id) }), + class: "btn btn-new", + title: "New issue", + id: "new_issue_link" diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml index dba092c8844..e1b4a49850a 100644 --- a/app/views/projects/issues/_new_branch.html.haml +++ b/app/views/projects/issues/_new_branch.html.haml @@ -1,5 +1,5 @@ - if can?(current_user, :push_code, @project) - .create-mr-dropdown-wrap{ data: { can_create_path: can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue), create_mr_path: create_merge_request_namespace_project_issue_path(@project.namespace, @project, @issue), create_branch_path: namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid) } } + .create-mr-dropdown-wrap{ data: { can_create_path: can_create_branch_project_issue_path(@project, @issue), create_mr_path: create_merge_request_project_issue_path(@project, @issue), create_branch_path: project_branches_path(@project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid) } } .btn-group.unavailable %button.btn.btn-grouped{ type: 'button', disabled: 'disabled' } = icon('spinner', class: 'fa-spin') diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml index 8c9f6f3b4df..1df38db9fd4 100644 --- a/app/views/projects/issues/_related_branches.html.haml +++ b/app/views/projects/issues/_related_branches.html.haml @@ -11,4 +11,4 @@ = render_pipeline_status(pipeline) %span.related-branch-info %strong - = link_to branch, namespace_project_compare_path(@project.namespace, @project, from: @project.default_branch, to: branch), class: "ref-name" + = link_to branch, project_compare_path(@project, from: @project.default_branch, to: branch), class: "ref-name" diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder index 61346884346..4029926f373 100644 --- a/app/views/projects/issues/index.atom.builder +++ b/app/views/projects/issues/index.atom.builder @@ -1,7 +1,7 @@ xml.title "#{@project.name} issues" xml.link href: url_for(params), rel: "self", type: "application/atom+xml" -xml.link href: namespace_project_issues_url(@project.namespace, @project), rel: "alternate", type: "text/html" -xml.id namespace_project_issues_url(@project.namespace, @project) +xml.link href: project_issues_url(@project), rel: "alternate", type: "text/html" +xml.id project_issues_url(@project) xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any? xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any? diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 7183794ce72..aacb057840d 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -13,23 +13,16 @@ = content_for :meta_tags do = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@project.name} issues") +- if show_new_nav? + - content_for :breadcrumbs_extra do + = render "projects/issues/nav_btns" + - if project_issues(@project).exists? %div{ class: (container_class) } .top-area = render 'shared/issuable/nav', type: :issues - .nav-controls - = link_to params.merge(rss_url_options), class: 'btn append-right-10 has-tooltip', title: 'Subscribe' do - = icon('rss') - - if @can_bulk_update - = button_tag "Edit Issues", class: "btn btn-default js-bulk-update-toggle" - = link_to new_namespace_project_issue_path(@project.namespace, - @project, - issue: { assignee_id: issues_finder.assignee.try(:id), - milestone_id: issues_finder.milestones.first.try(:id) }), - class: "btn btn-new", - title: "New issue", - id: "new_issue_link" do - New issue + .nav-controls{ class: ("visible-xs" if show_new_nav?) } + = render "projects/issues/nav_btns" = render 'shared/issuable/search_bar', type: :issues - if @can_bulk_update @@ -40,4 +33,4 @@ - if new_issue_email = render 'issue_by_email', email: new_issue_email - else - = render 'shared/empty_states/issues', button_path: new_namespace_project_issue_path(@project.namespace, @project) + = render 'shared/empty_states/issues', button_path: new_project_issue_path(@project) diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index d909b0bfbbd..cf8493faba8 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -31,26 +31,26 @@ %ul - if can_update_issue %li - = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'issuable-edit' + = link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'issuable-edit' %li = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' %li = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' - if can_report_spam %li - = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam' + = link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam' - if can_update_issue || can_report_spam %li.divider %li - = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link' + = link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link' - if can_update_issue - = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit' + = link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit' = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' - if can_report_spam - = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam' - = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do + = link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam' + = link_to new_project_issue_path(@project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do New issue .issue-details.issuable-details @@ -65,10 +65,10 @@ = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago') - #merge-requests{ data: { url: referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue) } } + #merge-requests{ data: { url: referenced_merge_requests_project_issue_url(@project, @issue) } } // This element is filled in using JavaScript. - #related-branches{ data: { url: related_branches_namespace_project_issue_url(@project.namespace, @project, @issue) } } + #related-branches{ data: { url: related_branches_project_issue_url(@project, @issue) } } // This element is filled in using JavaScript. .content-block.emoji-block diff --git a/app/views/projects/jobs/_header.html.haml b/app/views/projects/jobs/_header.html.haml index ad72ab5b199..d81b8f6bb4c 100644 --- a/app/views/projects/jobs/_header.html.haml +++ b/app/views/projects/jobs/_header.html.haml @@ -6,13 +6,13 @@ = render 'ci/status/badge', status: @build.detailed_status(current_user), link: false, title: @build.status_title %strong Job - = link_to "##{@build.id}", namespace_project_job_path(@project.namespace, @project, @build), class: 'js-build-id' + = link_to "##{@build.id}", project_job_path(@project, @build), class: 'js-build-id' in pipeline %strong = link_to "##{pipeline.id}", pipeline_path(pipeline) for %strong - = link_to pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, pipeline.sha), class: 'commit-sha' + = link_to pipeline.short_sha, project_commit_path(@project, pipeline.sha), class: 'commit-sha' from %strong = link_to @build.ref, project_ref_path(@project, @build.ref), class: 'ref-name' @@ -24,8 +24,8 @@ - if show_controls .nav-controls - if can?(current_user, :create_issue, @project) && @build.failed? - = link_to "New issue", new_namespace_project_issue_path(@project.namespace, @project, issue: build_failed_issue_options), class: 'btn btn-new btn-inverted' + = link_to "New issue", new_project_issue_path(@project, issue: build_failed_issue_options), class: 'btn btn-new btn-inverted' - if can?(current_user, :update_build, @build) && @build.retryable? - = link_to "Retry job", retry_namespace_project_job_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary', method: :post + = link_to "Retry job", retry_project_job_path(@project, @build), class: 'btn btn-inverted-secondary', method: :post %button.btn.btn-default.pull-right.visible-xs-block.visible-sm-block.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" } = icon('angle-double-left') diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml index 93e8a4e385c..bddb587ddc6 100644 --- a/app/views/projects/jobs/_sidebar.html.haml +++ b/app/views/projects/jobs/_sidebar.html.haml @@ -26,14 +26,14 @@ - if @build.artifacts? .btn-group.btn-group-justified{ role: :group } - if @build.has_expiring_artifacts? && can?(current_user, :update_build, @build) - = link_to keep_namespace_project_job_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post do + = link_to keep_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default', method: :post do Keep - = link_to download_namespace_project_job_artifacts_path(@project.namespace, @project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do + = link_to download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do Download - if @build.artifacts_metadata? - = link_to browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do + = link_to browse_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default' do Browse - if @build.trigger_request @@ -58,7 +58,7 @@ .block %p Commit - = link_to @build.pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, @build.pipeline.sha), class: 'commit-sha link-commit' + = link_to @build.pipeline.short_sha, project_commit_path(@project, @build.pipeline.sha), class: 'commit-sha link-commit' = clipboard_button(text: @build.pipeline.short_sha, title: "Copy commit SHA to clipboard") - if @build.merge_request in @@ -73,9 +73,9 @@ %span{ class: "ci-status-icon-#{@build.pipeline.status}" } = ci_icon_for_status(@build.pipeline.status) Pipeline - = link_to "##{@build.pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, @build.pipeline), class: 'link-commit' + = link_to "##{@build.pipeline.id}", project_pipeline_path(@project, @build.pipeline), class: 'link-commit' from - = link_to "#{@build.pipeline.ref}", namespace_project_branch_path(@project.namespace, @project, @build.pipeline.ref), class: 'link-commit' + = link_to "#{@build.pipeline.ref}", project_branch_path(@project, @build.pipeline.ref), class: 'link-commit' %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } %span.stage-selection More = icon('chevron-down') @@ -88,7 +88,7 @@ - HasStatus::ORDERED_STATUSES.each do |build_status| - builds.select{|build| build.status == build_status}.each do |build| .build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } } - = link_to namespace_project_job_path(@project.namespace, @project, build) do + = link_to project_job_path(@project, build) do = icon('arrow-right') %span{ class: "ci-status-icon-#{build.status}" } = ci_icon_for_status(build.status) diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml index a33e3978ee1..8604c7d3ea4 100644 --- a/app/views/projects/jobs/index.html.haml +++ b/app/views/projects/jobs/index.html.haml @@ -10,7 +10,7 @@ .nav-controls - if can?(current_user, :update_build, @project) - if @all_builds.running_or_pending.any? - = link_to 'Cancel running', cancel_all_namespace_project_jobs_path(@project.namespace, @project), + = link_to 'Cancel running', cancel_all_project_jobs_path(@project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post - unless @repository.gitlab_ci_yml diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index c73bae0a2c9..fa086413fbe 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -21,7 +21,7 @@ %br Go to - = link_to namespace_project_runners_path(@build.project.namespace, @build.project) do + = link_to project_runners_path(@build.project) do Runners page - if @build.starts_environment? @@ -54,23 +54,24 @@ - else Job has been erased #{time_ago_with_tooltip(@build.erased_at)} - .build-trace-container#build-trace - .top-bar.sticky + .build-trace-container.prepend-top-default + .top-bar.js-top-bar .js-truncated-info.truncated-info.hidden< Showing last %span.js-truncated-info-size.truncated-info-size>< KiB of log - - %a.js-raw-link.raw-link{ href: raw_namespace_project_job_path(@project.namespace, @project, @build) }>< Complete Raw + %a.js-raw-link.raw-link{ href: raw_project_job_path(@project, @build) }>< Complete Raw + .controllers - if @build.has_trace? - = link_to raw_namespace_project_job_path(@project.namespace, @project, @build), + = link_to raw_project_job_path(@project, @build), title: 'Show complete raw', data: { placement: 'top', container: 'body' }, class: 'js-raw-link-controller has-tooltip controllers-buttons' do = icon('file-text-o') - if can?(current_user, :update_build, @project) && @build.erasable? - = link_to erase_namespace_project_job_path(@project.namespace, @project, @build), + = link_to erase_project_job_path(@project, @build), method: :post, data: { confirm: 'Are you sure you want to erase this build?', placement: 'top', container: 'body' }, title: 'Erase job log', @@ -82,15 +83,17 @@ .has-tooltip.controllers-buttons{ title: 'Scroll to bottom', data: { placement: 'top', container: 'body'} } %button.js-scroll-down.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true } = custom_icon('scroll_down') - .bash.sticky.js-scroll-container - %code.js-build-output + + %pre.build-trace#build-trace + %code.bash.js-build-output .build-loader-animation.js-build-refresh + = render "sidebar" .js-build-options{ data: javascript_build_options } -#js-job-details-vue{ data: { endpoint: namespace_project_job_path(@project.namespace, @project, @build, format: :json) } } +#js-job-details-vue{ data: { endpoint: project_job_path(@project, @build, format: :json) } } - content_for :page_specific_javascripts do = webpack_bundle_tag('common_vue') diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml index 7f0059cdcda..84b0b65d1c0 100644 --- a/app/views/projects/labels/edit.html.haml +++ b/app/views/projects/labels/edit.html.haml @@ -6,4 +6,4 @@ %h3.page-title Edit Label %hr - = render 'shared/labels/form', url: namespace_project_label_path(@project.namespace.becomes(Namespace), @project, @label), back_path: namespace_project_labels_path(@project.namespace, @project) + = render 'shared/labels/form', url: project_label_path(@project, @label), back_path: project_labels_path(@project) diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index fc72c4fb635..8fbc4588902 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -11,7 +11,7 @@ .nav-controls - if can?(current_user, :admin_label, @project) - = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do + = link_to new_project_label_path(@project), class: "btn btn-new" do New label .labels @@ -20,7 +20,7 @@ - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') .prioritized-labels{ class: ('hide' if hide) } %h5 Prioritized Labels - %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } + %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_project_labels_path(@project) } #js-priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" } = render 'shared/empty_states/priority_labels' - if @prioritized_labels.present? diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml index 8f6c085a361..79e90b7ca3b 100644 --- a/app/views/projects/labels/new.html.haml +++ b/app/views/projects/labels/new.html.haml @@ -6,4 +6,4 @@ %h3.page-title New Label %hr - = render 'shared/labels/form', url: namespace_project_labels_path(@project.namespace.becomes(Namespace), @project), back_path: namespace_project_labels_path(@project.namespace, @project) + = render 'shared/labels/form', url: project_labels_path(@project), back_path: project_labels_path(@project) diff --git a/app/views/projects/mattermosts/_no_teams.html.haml b/app/views/projects/mattermosts/_no_teams.html.haml index aac74a25b75..243dcfdc187 100644 --- a/app/views/projects/mattermosts/_no_teams.html.haml +++ b/app/views/projects/mattermosts/_no_teams.html.haml @@ -13,4 +13,4 @@ and try again. %hr .clearfix - = link_to 'Go back', edit_namespace_project_service_path(@project.namespace, @project, @service), class: 'btn btn-lg pull-right' + = link_to 'Go back', edit_project_service_path(@project, @service), class: 'btn btn-lg pull-right' diff --git a/app/views/projects/mattermosts/_team_selection.html.haml b/app/views/projects/mattermosts/_team_selection.html.haml index 04bd4e8b683..3bdb5d0adc4 100644 --- a/app/views/projects/mattermosts/_team_selection.html.haml +++ b/app/views/projects/mattermosts/_team_selection.html.haml @@ -2,7 +2,7 @@ This service will be installed on the Mattermost instance at %strong= link_to Gitlab.config.mattermost.host, Gitlab.config.mattermost.host %hr -= form_for(:mattermost, method: :post, url: namespace_project_mattermost_path(@project.namespace, @project), html: { class: 'js-requires-input'} ) do |f| += form_for(:mattermost, method: :post, url: project_mattermost_path(@project), html: { class: 'js-requires-input'} ) do |f| %h4 Team %p = @teams.one? ? 'The team' : 'Select the team' @@ -42,5 +42,5 @@ %hr .clearfix .pull-right - = link_to 'Cancel', edit_namespace_project_service_path(@project.namespace, @project, @service), class: 'btn btn-lg' + = link_to 'Cancel', edit_project_service_path(@project, @service), class: 'btn btn-lg' = f.submit 'Install', class: 'btn btn-save btn-lg' diff --git a/app/views/projects/merge_requests/_head.html.haml b/app/views/projects/merge_requests/_head.html.haml index b7f73fe5339..1e505222887 100644 --- a/app/views/projects/merge_requests/_head.html.haml +++ b/app/views/projects/merge_requests/_head.html.haml @@ -4,18 +4,18 @@ .nav-links.sub-nav.scrolling-tabs %ul{ class: (container_class) } = nav_link(controller: :merge_requests) do - = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do + = link_to project_merge_requests_path(@project), title: 'Merge Requests' do %span List - if project_nav_tab? :labels = nav_link(controller: :labels) do - = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do + = link_to project_labels_path(@project), title: 'Labels' do %span Labels - if project_nav_tab? :milestones = nav_link(controller: :milestones) do - = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do + = link_to project_milestones_path(@project), title: 'Milestones' do %span Milestones diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index c13110deb16..0a1ebcb8124 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -4,58 +4,60 @@ = check_box_tag dom_id(merge_request, "selected"), nil, false, 'data-id' => merge_request.id, class: "selected_issue" .issue-info-container - .merge-request-title.title - %span.merge-request-title-text - = link_to merge_request.title, merge_request_path(merge_request) + .issue-main-info + .merge-request-title.title + %span.merge-request-title-text + = link_to merge_request.title, merge_request_path(merge_request) + - if merge_request.tasks? + %span.task-status.hidden-xs + + = merge_request.task_status + + .issuable-info + %span.issuable-reference + #{issuable_reference(merge_request)} + %span.issuable-authored.hidden-xs + · + opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} + by #{link_to_member(@project, merge_request.author, avatar: false)} + - if merge_request.milestone + %span.issuable-milestone.hidden-xs + + = link_to project_merge_requests_path(merge_request.project, milestone_title: merge_request.milestone.title) do + = icon('clock-o') + = merge_request.milestone.title + - if merge_request.target_project.default_branch != merge_request.target_branch + %span.project-ref-path + + = link_to project_ref_path(merge_request.project, merge_request.target_branch), class: 'ref-name' do + = icon('code-fork') + = merge_request.target_branch + - if merge_request.labels.any? + + - merge_request.labels.each do |label| + = link_to_label(label, subject: merge_request.project, type: :merge_request, css_class: 'label-link') + + .issuable-meta %ul.controls - if merge_request.merged? - %li + %li.issuable-status.hidden-xs MERGED - elsif merge_request.closed? - %li + %li.issuable-status.hidden-xs = icon('ban') CLOSED - - if merge_request.head_pipeline - %li + %li.issuable-pipeline-status.hidden-xs = render_pipeline_status(merge_request.head_pipeline) - - if merge_request.open? && merge_request.broken? - %li + %li.issuable-pipeline-broken.hidden-xs = link_to merge_request_path(merge_request), class: "has-tooltip", title: "Cannot be merged automatically", data: { container: 'body' } do = icon('exclamation-triangle') - - if merge_request.assignee %li = link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name") = render 'shared/issuable_meta_data', issuable: merge_request - .merge-request-info - #{issuable_reference(merge_request)} · - opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} - by #{link_to_member(@project, merge_request.author, avatar: false)} - - if merge_request.target_project.default_branch != merge_request.target_branch - - = link_to project_ref_path(merge_request.project, merge_request.target_branch), class: 'ref-name' do - = icon('code-fork') - = merge_request.target_branch - - - if merge_request.milestone - - = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, milestone_title: merge_request.milestone.title) do - = icon('clock-o') - = merge_request.milestone.title - - - if merge_request.labels.any? - - - merge_request.labels.each do |label| - = link_to_label(label, subject: merge_request.project, type: :merge_request, css_class: 'label-link') - - - if merge_request.tasks? - - %span.task-status - = merge_request.task_status - - .pull-right.hidden-xs + .pull-right.issuable-updated-at.hidden-xs %span updated #{time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago')} diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml index d9428b8562e..3182aecd0a8 100644 --- a/app/views/projects/merge_requests/_mr_title.html.haml +++ b/app/views/projects/merge_requests/_mr_title.html.haml @@ -28,8 +28,8 @@ %li{ class: merge_request_button_visibility(@merge_request, false) } = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request' %li - = link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit' + = link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: 'issuable-edit' = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{merge_request_button_visibility(@merge_request, true)}", title: 'Close merge request' = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen reopen-mr-link #{merge_request_button_visibility(@merge_request, false)}", title: 'Reopen merge request' - = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit" do + = link_to edit_project_merge_request_path(@project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit" do Edit diff --git a/app/views/projects/merge_requests/_nav_btns.html.haml b/app/views/projects/merge_requests/_nav_btns.html.haml new file mode 100644 index 00000000000..e92f2712347 --- /dev/null +++ b/app/views/projects/merge_requests/_nav_btns.html.haml @@ -0,0 +1,5 @@ +- if @can_bulk_update + = button_tag "Edit Merge Requests", class: "btn js-bulk-update-toggle" +- if merge_project + = link_to new_merge_request_path, class: "btn btn-new", title: "New merge request" do + New merge request diff --git a/app/views/projects/merge_requests/_pipelines.html.haml b/app/views/projects/merge_requests/_pipelines.html.haml index 2f1dbe87619..473b7b919c8 100644 --- a/app/views/projects/merge_requests/_pipelines.html.haml +++ b/app/views/projects/merge_requests/_pipelines.html.haml @@ -1,4 +1,4 @@ -- endpoint_path = local_assigns[:endpoint] || pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :json) +- endpoint_path = local_assigns[:endpoint] || pipelines_project_merge_request_path(@project, @merge_request, format: :json) - disable_initialization = local_assigns.fetch(:disable_initialization, false) = render 'projects/commit/pipelines_list', endpoint: endpoint_path, disable_initialization: disable_initialization diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml index f016b9c13b3..454bc359b6b 100644 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ b/app/views/projects/merge_requests/conflicts.html.haml @@ -10,8 +10,8 @@ = render 'shared/issuable/sidebar', issuable: @merge_request -#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request, format: :json), - resolve_conflicts_path: resolve_conflicts_namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request) } } +#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_project_merge_request_path(@merge_request.project, @merge_request, format: :json), + resolve_conflicts_path: resolve_conflicts_project_merge_request_path(@merge_request.project, @merge_request) } } .loading{ "v-if" => "isLoading" } %i.fa.fa-spinner.fa-spin diff --git a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml index e675e1830d0..13026b7566a 100644 --- a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml +++ b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml @@ -13,4 +13,4 @@ %button.btn.btn-success.js-submit-button{ type: "button", "@click" => "commit()", ":disabled" => "!readyToCommit" } %span {{commitButtonText}} .col-xs-6.text-right - = link_to "Cancel", namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request), class: "btn btn-cancel" + = link_to "Cancel", project_merge_request_path(@merge_request.project, @merge_request), class: "btn btn-cancel" diff --git a/app/views/projects/merge_requests/conflicts/show.html.haml b/app/views/projects/merge_requests/conflicts/show.html.haml index f016b9c13b3..454bc359b6b 100644 --- a/app/views/projects/merge_requests/conflicts/show.html.haml +++ b/app/views/projects/merge_requests/conflicts/show.html.haml @@ -10,8 +10,8 @@ = render 'shared/issuable/sidebar', issuable: @merge_request -#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request, format: :json), - resolve_conflicts_path: resolve_conflicts_namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request) } } +#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_project_merge_request_path(@merge_request.project, @merge_request, format: :json), + resolve_conflicts_path: resolve_conflicts_project_merge_request_path(@merge_request.project, @merge_request) } } .loading{ "v-if" => "isLoading" } %i.fa.fa-spinner.fa-spin diff --git a/app/views/projects/merge_requests/creations/_new_compare.html.haml b/app/views/projects/merge_requests/creations/_new_compare.html.haml index 7cda326afef..4e5aae496b1 100644 --- a/app/views/projects/merge_requests/creations/_new_compare.html.haml +++ b/app/views/projects/merge_requests/creations/_new_compare.html.haml @@ -1,7 +1,7 @@ %h3.page-title New Merge Request -= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: namespace_project_new_merge_request_path(@project.namespace, @project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: project_new_merge_request_path(@project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f| .hide.alert.alert-danger.mr-compare-errors .merge-request-branches.row .col-md-6 @@ -69,7 +69,7 @@ :javascript new Compare({ - targetProjectUrl: "#{namespace_project_new_merge_request_update_branches_path(@source_project.namespace, @source_project)}", - sourceBranchUrl: "#{namespace_project_new_merge_request_branch_from_path(@source_project.namespace, @source_project)}", - targetBranchUrl: "#{namespace_project_new_merge_request_branch_to_path(@source_project.namespace, @source_project)}" + targetProjectUrl: "#{project_new_merge_request_update_branches_path(@source_project)}", + sourceBranchUrl: "#{project_new_merge_request_branch_from_path(@source_project)}", + targetBranchUrl: "#{project_new_merge_request_branch_to_path(@source_project)}" }); diff --git a/app/views/projects/merge_requests/diffs/_versions.html.haml b/app/views/projects/merge_requests/diffs/_versions.html.haml index 0999b95c9c9..9f7152b9824 100644 --- a/app/views/projects/merge_requests/diffs/_versions.html.haml +++ b/app/views/projects/merge_requests/diffs/_versions.html.haml @@ -77,7 +77,7 @@ = icon('info-circle') Selected versions have different base commits. Changes will include - = link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do + = link_to project_compare_path(@project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do new commits from = succeed '.' do @@ -94,4 +94,4 @@ of the diff. .pull-right - = link_to 'Show latest version', diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-sm' + = link_to 'Show latest version', diffs_project_merge_request_path(@project, @merge_request), class: 'btn btn-sm' diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 86996e488a1..bfeb746ee83 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -1,5 +1,7 @@ - @no_container = true - @can_bulk_update = can?(current_user, :admin_merge_request, @project) +- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project)) +- new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project - page_title "Merge Requests" - unless @project.default_issues_tracker? @@ -10,6 +12,9 @@ = webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'filtered_search' +- if show_new_nav? + - content_for :breadcrumbs_extra do + = render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path = render 'projects/last_push' @@ -17,13 +22,8 @@ %div{ class: container_class } .top-area = render 'shared/issuable/nav', type: :merge_requests - .nav-controls - - if @can_bulk_update - = button_tag "Edit Merge Requests", class: "btn js-bulk-update-toggle" - - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project)) - - if merge_project - = link_to namespace_project_new_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New merge request" do - New merge request + .nav-controls{ class: ("visible-xs" if show_new_nav?) } + = render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path = render 'shared/issuable/search_bar', type: :merge_requests @@ -33,4 +33,4 @@ .merge-requests-holder = render 'merge_requests' - else - = render 'shared/empty_states/merge_requests', button_path: namespace_project_new_merge_request_path(@project.namespace, @project) + = render 'shared/empty_states/merge_requests', button_path: new_merge_request_path diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index dbbf1bde088..13012151349 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -35,21 +35,21 @@ .nav-links.scrolling-tabs %ul.merge-request-tabs %li.notes-tab - = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'show', toggle: 'tab' } do + = link_to project_merge_request_path(@project, @merge_request), data: { target: 'div#notes', action: 'show', toggle: 'tab' } do Discussion %span.badge= @merge_request.related_notes.user.count - if @merge_request.source_project %li.commits-tab - = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do + = link_to commits_project_merge_request_path(@project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do Commits %span.badge= @commits_count - if @pipelines.any? %li.pipelines-tab - = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do + = link_to pipelines_project_merge_request_path(@project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do Pipelines %span.badge= @pipelines.size %li.diffs-tab - = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do + = link_to diffs_project_merge_request_path(@project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do Changes %span.badge= @merge_request.diff_size #resolve-count-app.line-resolve-all-container.prepend-top-10{ "v-cloak" => true } @@ -76,7 +76,7 @@ -# This tab is always loaded via AJAX #pipelines.pipelines.tab-pane - if @pipelines.any? - = render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) + = render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_project_merge_request_path(@project, @merge_request) #diffs.diffs.tab-pane -# This tab is always loaded via AJAX diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index 9a95b2a82ff..2e74b1b83cb 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -19,7 +19,7 @@ .form-actions - if @milestone.new_record? = f.submit 'Create milestone', class: "btn-create btn" - = link_to "Cancel", namespace_project_milestones_path(@project.namespace, @project), class: "btn btn-cancel" + = link_to "Cancel", project_milestones_path(@project), class: "btn btn-cancel" - else = f.submit 'Save changes', class: "btn-save btn" - = link_to "Cancel", namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-cancel" + = link_to "Cancel", project_milestone_path(@project, @milestone), class: "btn btn-cancel" diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml index 77b566db6b6..bc82b45f902 100644 --- a/app/views/projects/milestones/_milestone.html.haml +++ b/app/views/projects/milestones/_milestone.html.haml @@ -1,5 +1,5 @@ = render 'shared/milestones/milestone', - milestone_path: namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), - issues_path: namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title), - merge_requests_path: namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title), + milestone_path: project_milestone_path(milestone.project, milestone), + issues_path: project_issues_path(milestone.project, milestone_title: milestone.title), + merge_requests_path: project_merge_requests_path(milestone.project, milestone_title: milestone.title), milestone: milestone diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index e1096bd1d67..e53fcd6e425 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -9,7 +9,7 @@ .nav-controls = render 'shared/milestones_sort_dropdown' - if can?(current_user, :admin_milestone, @project) - = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New milestone' do + = link_to new_project_milestone_path(@project), class: 'btn btn-new', title: 'New milestone' do New milestone .milestones diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 4b692aba11c..0bf0e11c107 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -23,14 +23,14 @@ .milestone-buttons - if can?(current_user, :admin_milestone, @project) - if @milestone.active? - = link_to 'Close milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped" + = link_to 'Close milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped" - else - = link_to 'Reopen milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped" + = link_to 'Reopen milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped" - = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do + = link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-grouped btn-nr" do Edit - = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do + = link_to project_milestone_path(@project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do Delete %a.btn.btn-default.btn-grouped.pull-right.visible-xs-block.js-sidebar-toggle{ href: "#" } diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index ed6077f6c6b..e8c26636be9 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -6,7 +6,7 @@ %div{ class: container_class } .project-network .controls - = form_tag namespace_project_network_path(@project.namespace, @project, @id), method: :get, class: 'form-inline network-form' do |f| + = form_tag project_network_path(@project, @id), method: :get, class: 'form-inline network-form' do |f| = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Git revision", class: 'search-input form-control input-mx-250 search-sha' = button_tag class: 'btn btn-success' do = icon('search') diff --git a/app/views/projects/no_repo.html.haml b/app/views/projects/no_repo.html.haml index 1cf286ddc40..ba5845877e5 100644 --- a/app/views/projects/no_repo.html.haml +++ b/app/views/projects/no_repo.html.haml @@ -9,12 +9,12 @@ %hr .no-repo-actions - = link_to namespace_project_repository_path(@project.namespace, @project), method: :post, class: 'btn btn-primary' do + = link_to project_repository_path(@project), method: :post, class: 'btn btn-primary' do #{ _('Create empty bare repository') } %strong.prepend-left-10.append-right-10 or - = link_to new_namespace_project_import_path(@project.namespace, @project), class: 'btn' do + = link_to new_project_import_path(@project), class: 'btn' do #{ _('Import repository') } - if can? current_user, :remove_project, @project diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml index 42d9ef5ccba..7d6c30b7f8d 100644 --- a/app/views/projects/pages/_destroy.haml +++ b/app/views/projects/pages/_destroy.haml @@ -7,6 +7,6 @@ %p Removing the pages will prevent from exposing them to outside world. .form-actions - = link_to 'Remove pages', namespace_project_pages_path(@project.namespace, @project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove" + = link_to 'Remove pages', project_pages_path(@project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove" - else .nothing-here-block Only the project owner can remove pages diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml index 4f2dd1a1398..a85cda407af 100644 --- a/app/views/projects/pages/_list.html.haml +++ b/app/views/projects/pages/_list.html.haml @@ -6,8 +6,8 @@ - @domains.each do |domain| %li .pull-right - = link_to 'Details', namespace_project_pages_domain_path(@project.namespace, @project, domain), class: "btn btn-sm btn-grouped" - = link_to 'Remove', namespace_project_pages_domain_path(@project.namespace, @project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped" + = link_to 'Details', project_pages_domain_path(@project, domain), class: "btn btn-sm btn-grouped" + = link_to 'Remove', project_pages_domain_path(@project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped" .clearfix %span= link_to domain.domain, domain.url %p diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml index b22a54d75c8..098b0ef56ef 100644 --- a/app/views/projects/pages/show.html.haml +++ b/app/views/projects/pages/show.html.haml @@ -5,7 +5,7 @@ Pages - if can?(current_user, :update_pages, @project) && (Gitlab.config.pages.external_http || Gitlab.config.pages.external_https) - = link_to new_namespace_project_pages_domain_path(@project.namespace, @project), class: 'btn btn-new pull-right', title: 'New Domain' do + = link_to new_project_pages_domain_path(@project), class: 'btn btn-new pull-right', title: 'New Domain' do %i.fa.fa-plus New Domain diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml index 966d6cd8495..08ccd57c81a 100644 --- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml +++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml @@ -9,7 +9,7 @@ %td - if pipeline_schedule.last_pipeline .status-icon-container{ class: "ci-status-icon-#{pipeline_schedule.last_pipeline.status}" } - = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline_schedule.last_pipeline.id) do + = link_to project_pipeline_path(@project, pipeline_schedule.last_pipeline.id) do = ci_icon_for_status(pipeline_schedule.last_pipeline.status) %span ##{pipeline_schedule.last_pipeline.id} - else diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml index c296152e54f..05fe80e5fed 100644 --- a/app/views/projects/pipeline_schedules/index.html.haml +++ b/app/views/projects/pipeline_schedules/index.html.haml @@ -13,7 +13,7 @@ = render "tabs", schedule_path_proc: schedule_path_proc, all_schedules: @all_schedules, scope: @scope .nav-controls - = link_to new_namespace_project_pipeline_schedule_path(@project.namespace, @project), class: 'btn btn-create' do + = link_to new_project_pipeline_schedule_path(@project), class: 'btn btn-create' do %span= _('New schedule') - if @schedules.present? diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml index d2f0cb0806f..ee2f236cec4 100644 --- a/app/views/projects/pipelines/_head.html.haml +++ b/app/views/projects/pipelines/_head.html.haml @@ -29,6 +29,6 @@ - if @project.feature_available?(:builds, current_user) && !@project.empty_repo? = nav_link(path: 'pipelines#charts') do - = link_to charts_namespace_project_pipelines_path(@project.namespace, @project), title: 'Charts', class: 'shortcuts-pipelines-charts' do + = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do %span Charts diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index 673c3370b62..f5149306734 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -26,10 +26,10 @@ .well-segment.branch-info .icon-container.commit-icon = custom_icon("icon_commit") - = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @pipeline.sha), class: "commit-sha js-details-short" + = link_to @commit.short_id, project_commit_path(@project, @pipeline.sha), class: "commit-sha js-details-short" = link_to("#", class: "js-details-expand hidden-xs hidden-sm") do %span.text-expander \... %span.js-details-content.hide - = link_to @pipeline.sha, namespace_project_commit_path(@project.namespace, @project, @pipeline.sha), class: "commit-sha commit-hash-full" + = link_to @pipeline.sha, project_commit_path(@project, @pipeline.sha), class: "commit-sha commit-hash-full" = clipboard_button(text: @pipeline.sha, title: "Copy commit SHA to clipboard") diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml index 85550e8fd32..ad61f033a1c 100644 --- a/app/views/projects/pipelines/_with_tabs.html.haml +++ b/app/views/projects/pipelines/_with_tabs.html.haml @@ -3,15 +3,15 @@ .tabs-holder %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 + = link_to project_pipeline_path(@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 + = link_to builds_project_pipeline_path(@project, @pipeline), data: {target: 'div#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do Jobs %span.badge.js-builds-counter= pipeline.statuses.count - if failed_builds.present? %li.js-failures-tab-link - = link_to failures_namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: {target: 'div#js-tab-failures', action: 'failures', toggle: 'tab' }, class: 'failures-tab' do + = link_to failures_project_pipeline_path(@project, @pipeline), data: {target: 'div#js-tab-failures', action: 'failures', toggle: 'tab' }, class: 'failures-tab' do Failed Jobs %span.badge.js-failures-counter= failed_builds.count diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml index 8ffddfe6154..78002e8cd64 100644 --- a/app/views/projects/pipelines/charts.html.haml +++ b/app/views/projects/pipelines/charts.html.haml @@ -1,5 +1,5 @@ - @no_container = true -- page_title "Charts", "Pipelines" +- page_title _("Charts"), _("Pipelines") - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_d3') = page_specific_javascript_bundle_tag('graphs') @@ -8,7 +8,7 @@ %div{ class: container_class } .sub-header-block .oneline - A collection of graphs for Continuous Integration + = _("A collection of graphs regarding Continuous Integration") #charts.ci-charts .row diff --git a/app/views/projects/pipelines/charts/_overall.haml b/app/views/projects/pipelines/charts/_overall.haml index 93083397d5b..66786c7ff59 100644 --- a/app/views/projects/pipelines/charts/_overall.haml +++ b/app/views/projects/pipelines/charts/_overall.haml @@ -1,15 +1,15 @@ -%h4 Overall stats +%h4= s_("PipelineCharts|Overall statistics") %ul %li - Total: - %strong= pluralize @counts[:total], 'pipeline' + = s_("PipelineCharts|Total:") + %strong= n_("1 pipeline", "%d pipelines", @counts[:total]) % @counts[:total] %li - Successful: - %strong= pluralize @counts[:success], 'pipeline' + = s_("PipelineCharts|Successful:") + %strong= n_("1 pipeline", "%d pipelines", @counts[:success]) % @counts[:success] %li - Failed: - %strong= pluralize @counts[:failed], 'pipeline' + = s_("PipelineCharts|Failed:") + %strong= n_("1 pipeline", "%d pipelines", @counts[:failed]) % @counts[:failed] %li - Success ratio: + = s_("PipelineCharts|Success ratio:") %strong #{success_ratio(@counts)}% diff --git a/app/views/projects/pipelines/charts/_pipeline_times.haml b/app/views/projects/pipelines/charts/_pipeline_times.haml index aee7c5492aa..1292f580a81 100644 --- a/app/views/projects/pipelines/charts/_pipeline_times.haml +++ b/app/views/projects/pipelines/charts/_pipeline_times.haml @@ -1,6 +1,6 @@ %div %p.light - Commit duration in minutes for last 30 commits + = _("Commit duration in minutes for last 30 commits") %canvas#build_timesChart{ height: 200 } diff --git a/app/views/projects/pipelines/charts/_pipelines.haml b/app/views/projects/pipelines/charts/_pipelines.haml index b6f453b9736..be884448087 100644 --- a/app/views/projects/pipelines/charts/_pipelines.haml +++ b/app/views/projects/pipelines/charts/_pipelines.haml @@ -1,29 +1,29 @@ -%h4 Pipelines charts +%h4= _("Pipelines charts") %p %span.cgreen = icon("circle") - success + = s_("Pipeline|success") %span.cgray = icon("circle") - all + = s_("Pipeline|all") .prepend-top-default %p.light - Jobs for last week + = _("Jobs for last week") (#{date_from_to(Date.today - 7.days, Date.today)}) %canvas#weekChart{ height: 200 } .prepend-top-default %p.light - Jobs for last month + = _("Jobs for last month") (#{date_from_to(Date.today - 30.days, Date.today)}) %canvas#monthChart{ height: 200 } .prepend-top-default %p.light - Jobs for last year + = _("Jobs for last year") %canvas#yearChart.padded{ height: 250 } - [:week, :month, :year].each do |scope| diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 38237d2d97d..c1729850cf4 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -2,10 +2,10 @@ - page_title "Pipelines" = render "projects/pipelines/head" -#pipelines-list-vue{ data: { endpoint: namespace_project_pipelines_path(@project.namespace, @project, format: :json), +#pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json), "css-class" => container_class, "help-page-path" => help_page_path('ci/quick_start/README'), - "new-pipeline-path" => new_namespace_project_pipeline_path(@project.namespace, @project), + "new-pipeline-path" => new_project_pipeline_path(@project), "can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s, "all-path" => project_pipelines_path(@project), "pending-path" => project_pipelines_path(@project, scope: :pending), diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml index 71a8e490c3e..308f2611e02 100644 --- a/app/views/projects/pipelines/new.html.haml +++ b/app/views/projects/pipelines/new.html.haml @@ -4,7 +4,7 @@ New Pipeline %hr -= form_for @pipeline, as: :pipeline, url: namespace_project_pipelines_path(@project.namespace, @project), html: { id: "new-pipeline-form", class: "form-horizontal js-new-pipeline-form js-requires-input" } do |f| += form_for @pipeline, as: :pipeline, url: project_pipelines_path(@project), html: { id: "new-pipeline-form", class: "form-horizontal js-new-pipeline-form js-requires-input" } do |f| = form_errors(@pipeline) .form-group = f.label :ref, 'Create for', class: 'control-label' @@ -17,7 +17,7 @@ .help-block Existing branch name, tag .form-actions = f.submit 'Create pipeline', class: 'btn btn-create', tabindex: 3 - = link_to 'Cancel', namespace_project_pipelines_path(@project.namespace, @project), class: 'btn btn-cancel' + = link_to 'Cancel', project_pipelines_path(@project), class: 'btn btn-cancel' :javascript var availableRefs = #{@project.repository.ref_names.to_json}; diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml index b39453a50fb..63f85fc69a2 100644 --- a/app/views/projects/pipelines/show.html.haml +++ b/app/views/projects/pipelines/show.html.haml @@ -8,7 +8,7 @@ = render "projects/pipelines/with_tabs", pipeline: @pipeline -.js-pipeline-details-vue{ data: { endpoint: namespace_project_pipeline_path(@project.namespace, @project, @pipeline, format: :json) } } +.js-pipeline-details-vue{ data: { endpoint: project_pipeline_path(@project, @pipeline, format: :json) } } - content_for :page_specific_javascripts do = webpack_bundle_tag('common_vue') diff --git a/app/views/projects/pipelines_settings/_show.html.haml b/app/views/projects/pipelines_settings/_show.html.haml index 580129ca809..255d7ef38e0 100644 --- a/app/views/projects/pipelines_settings/_show.html.haml +++ b/app/views/projects/pipelines_settings/_show.html.haml @@ -3,7 +3,7 @@ %h4.prepend-top-0 Pipelines .col-lg-8 - = form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project) do |f| + = form_for @project, url: project_pipelines_settings_path(@project) do |f| %fieldset.builds-feature - unless @repository.gitlab_ci_yml .form-group @@ -47,6 +47,14 @@ %hr .form-group + = f.label :ci_config_path, 'Custom CI config path', class: 'label-light' + = f.text_field :ci_config_path, class: 'form-control', placeholder: '.gitlab-ci.yml' + %p.help-block + The path to CI config file. Defaults to <code>.gitlab-ci.yml</code> + = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'custom-ci-config-path'), target: '_blank' + + %hr + .form-group .checkbox = f.label :public_builds do = f.check_box :public_builds diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml index 8bf2246662a..bf5b11ea30c 100644 --- a/app/views/projects/project_members/_new_project_member.html.haml +++ b/app/views/projects/project_members/_new_project_member.html.haml @@ -1,6 +1,6 @@ .row .col-sm-12 - = form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'users-project-form' } do |f| + = form_for @project_member, as: :project_member, url: project_project_members_path(@project), html: { class: 'users-project-form' } do |f| .form-group = label_tag :user_ids, "Select members to invite", class: "label-light" = users_select_tag(:user_ids, multiple: true, class: "input-clamp", scope: :all, email_user: true, placeholder: "Search for members to update or invite") @@ -18,4 +18,4 @@ = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date' %i.clear-icon.js-clear-input = f.submit "Add to project", class: "btn btn-create" - = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default", title: "Import members from another project" + = link_to "Import", import_project_project_members_path(@project), class: "btn btn-default", title: "Import members from another project" diff --git a/app/views/projects/project_members/_new_shared_group.html.haml b/app/views/projects/project_members/_new_shared_group.html.haml index 643569db646..c10ef648a8f 100644 --- a/app/views/projects/project_members/_new_shared_group.html.haml +++ b/app/views/projects/project_members/_new_shared_group.html.haml @@ -1,6 +1,6 @@ .row .col-sm-12 - = form_tag namespace_project_group_links_path(@project.namespace, @project), class: 'js-requires-input', method: :post do + = form_tag project_group_links_path(@project), class: 'js-requires-input', method: :post do .form-group = label_tag :link_group_id, "Select a group to share with", class: "label-light" = groups_select_tag(:link_group_id, data: { skip_groups: @skip_groups }, class: "input-clamp", required: true) diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 7b1a26043e1..7ed467c8841 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -5,7 +5,7 @@ %strong #{@project.name} %span.badge= @project_members.total_count - = form_tag namespace_project_settings_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do + = form_tag project_settings_members_path(@project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do .form-group = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } diff --git a/app/views/projects/project_members/import.html.haml b/app/views/projects/project_members/import.html.haml index 42ce4f8001b..03b33eb2da7 100644 --- a/app/views/projects/project_members/import.html.haml +++ b/app/views/projects/project_members/import.html.haml @@ -5,11 +5,11 @@ %p.light Only project members will be imported. Group members will be skipped. %hr -= form_tag apply_import_namespace_project_project_members_path(@project.namespace, @project), method: 'post', class: 'form-horizontal' do += form_tag apply_import_project_project_members_path(@project), method: 'post', class: 'form-horizontal' do .form-group = label_tag :source_project_id, "Project", class: 'control-label' .col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true) .form-actions = button_tag 'Import project members', class: "btn btn-create" - = link_to "Cancel", namespace_project_settings_members_path(@project.namespace, @project), class: "btn btn-cancel" + = link_to "Cancel", project_settings_members_path(@project), class: "btn btn-cancel" diff --git a/app/views/projects/protected_branches/_matching_branch.html.haml b/app/views/projects/protected_branches/_matching_branch.html.haml index 27896272733..98793d632e6 100644 --- a/app/views/projects/protected_branches/_matching_branch.html.haml +++ b/app/views/projects/protected_branches/_matching_branch.html.haml @@ -6,5 +6,5 @@ %span.label.label-info.prepend-left-5 default %td - commit = @project.commit(matching_branch.name) - = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha') + = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha') = time_ago_with_tooltip(commit.committed_date) diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml index 0f80de94392..e4dadc42cc0 100644 --- a/app/views/projects/protected_branches/_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_protected_branch.html.haml @@ -1,4 +1,4 @@ -%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } } +%tr.js-protected-branch-edit-form{ data: { url: project_protected_branch_path(@project, protected_branch) } } %td %span.ref-name= protected_branch.name @@ -7,10 +7,10 @@ %td - if protected_branch.wildcard? - matching_branches = protected_branch.matching(repository.branches) - = link_to pluralize(matching_branches.count, "matching branch"), namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) + = link_to pluralize(matching_branches.count, "matching branch"), project_protected_branch_path(@project, protected_branch) - else - if commit = protected_branch.commit - = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha') + = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha') = time_ago_with_tooltip(commit.committed_date) - else (branch was removed from repository) diff --git a/app/views/projects/protected_tags/_matching_tag.html.haml b/app/views/projects/protected_tags/_matching_tag.html.haml index f17353df122..05f102d1ca3 100644 --- a/app/views/projects/protected_tags/_matching_tag.html.haml +++ b/app/views/projects/protected_tags/_matching_tag.html.haml @@ -6,5 +6,5 @@ %span.label.label-info.prepend-left-5 default %td - commit = @project.commit(matching_tag.name) - = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha') + = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha') = time_ago_with_tooltip(commit.committed_date) diff --git a/app/views/projects/protected_tags/_protected_tag.html.haml b/app/views/projects/protected_tags/_protected_tag.html.haml index f11ce0483a9..5162da5e429 100644 --- a/app/views/projects/protected_tags/_protected_tag.html.haml +++ b/app/views/projects/protected_tags/_protected_tag.html.haml @@ -1,4 +1,4 @@ -%tr.js-protected-tag-edit-form{ data: { url: namespace_project_protected_tag_path(@project.namespace, @project, protected_tag) } } +%tr.js-protected-tag-edit-form{ data: { url: project_protected_tag_path(@project, protected_tag) } } %td %span.ref-name= protected_tag.name @@ -7,10 +7,10 @@ %td - if protected_tag.wildcard? - matching_tags = protected_tag.matching(repository.tags) - = link_to pluralize(matching_tags.count, "matching tag"), namespace_project_protected_tag_path(@project.namespace, @project, protected_tag) + = link_to pluralize(matching_tags.count, "matching tag"), project_protected_tag_path(@project, protected_tag) - else - if commit = protected_tag.commit - = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha') + = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha') = time_ago_with_tooltip(commit.committed_date) - else (tag was removed from repository) diff --git a/app/views/projects/registry/repositories/_image.html.haml b/app/views/projects/registry/repositories/_image.html.haml index dcdc432b654..a0535edafc3 100644 --- a/app/views/projects/registry/repositories/_image.html.haml +++ b/app/views/projects/registry/repositories/_image.html.haml @@ -8,7 +8,7 @@ - if can?(current_user, :update_container_image, @project) .controls.hidden-xs.pull-right - = link_to namespace_project_container_registry_path(@project.namespace, @project, image), + = link_to project_container_registry_path(@project, image), class: 'btn btn-remove has-tooltip', title: 'Remove repository', data: { confirm: 'Are you sure?' }, @@ -30,4 +30,3 @@ = render partial: 'tag', collection: image.tags - else .nothing-here-block No tags in Container Registry for this container image. - diff --git a/app/views/projects/registry/repositories/_tag.html.haml b/app/views/projects/registry/repositories/_tag.html.haml index 378a23f07e6..0b082a2137f 100644 --- a/app/views/projects/registry/repositories/_tag.html.haml +++ b/app/views/projects/registry/repositories/_tag.html.haml @@ -25,7 +25,7 @@ - if can?(current_user, :update_container_image, @project) %td.content .controls.hidden-xs.pull-right - = link_to namespace_project_registry_repository_tag_path(@project.namespace, @project, tag.repository, tag.name), + = link_to project_registry_repository_tag_path(@project, tag.repository, tag.name), method: :delete, class: 'btn btn-remove has-tooltip', title: 'Remove tag', diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml index 93ee9382a6e..0a5a38a3694 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/releases/edit.html.haml @@ -10,11 +10,11 @@ %strong= @tag.name - = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f| + = form_for(@release, method: :put, url: project_tag_release_path(@project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f| = render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..." = render 'shared/notes/hints' .error-alert .prepend-top-default = f.submit 'Save changes', class: 'btn btn-save' - = link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel" + = link_to "Cancel", project_tag_path(@project, @tag.name), class: "btn btn-default btn-cancel" diff --git a/app/views/projects/remove_fork.js.haml b/app/views/projects/remove_fork.js.haml index 17b9fecfeb1..6d083c5c516 100644 --- a/app/views/projects/remove_fork.js.haml +++ b/app/views/projects/remove_fork.js.haml @@ -1,2 +1,2 @@ :plain - location.href = "#{edit_namespace_project_path(@project.namespace, @project)}"; + location.href = "#{edit_project_path(@project)}"; diff --git a/app/views/projects/repositories/_feed.html.haml b/app/views/projects/repositories/_feed.html.haml index d9c39fb87b7..170f9e259df 100644 --- a/app/views/projects/repositories/_feed.html.haml +++ b/app/views/projects/repositories/_feed.html.haml @@ -1,7 +1,7 @@ - commit = update %tr %td - = link_to namespace_project_commits_path(@project.namespace, @project, commit.head.name) do + = link_to project_commits_path(@project, commit.head.name) do %strong = commit.head.name - if @project.root_ref?(commit.head.name) @@ -9,7 +9,7 @@ %td %div - = link_to namespace_project_commits_path(@project.namespace, @project, commit.id) do + = link_to project_commits_path(@project, commit.id) do %code= commit.short_id = image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: '' = markdown(truncate(commit.title, length: 40), pipeline: :single_line, author: commit.author) diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml index 674f87e8220..abc97bcdff5 100644 --- a/app/views/projects/runners/_runner.html.haml +++ b/app/views/projects/runners/_runner.html.haml @@ -9,7 +9,7 @@ = icon('lock', class: 'has-tooltip', title: 'Locked to current projects') %small - = link_to edit_namespace_project_runner_path(@project.namespace, @project, runner) do + = link_to edit_project_runner_path(@project, runner) do %i.fa.fa-edit.btn - else %span.commit-sha @@ -21,7 +21,7 @@ = link_to 'Remove Runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' - else - runner_project = @project.runner_projects.find_by(runner_id: runner) - = link_to 'Disable for this project', namespace_project_runner_project_path(@project.namespace, @project, runner_project), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' + = link_to 'Disable for this project', project_runner_project_path(@project, runner_project), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' - elsif runner.specific? = form_for [@project.namespace.becomes(Namespace), @project, @project.runner_projects.new] do |f| = f.hidden_field :runner_id, value: runner.id diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml index 0671dd66e78..a4e820628f3 100644 --- a/app/views/projects/runners/_shared_runners.html.haml +++ b/app/views/projects/runners/_shared_runners.html.haml @@ -9,10 +9,10 @@ on GitLab.com). %hr - if @project.shared_runners_enabled? - = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-warning', method: :post do + = link_to toggle_shared_runners_project_runners_path(@project), class: 'btn btn-warning', method: :post do Disable shared Runners - else - = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-success', method: :post do + = link_to toggle_shared_runners_project_runners_path(@project), class: 'btn btn-success', method: :post do Enable shared Runners for this project diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index 6dffc026392..b842fd57cf3 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -9,20 +9,21 @@ %p= @service.description .col-lg-9 - = form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_namespace_project_service_path } }) do |form| + = form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form| = render 'shared/service_settings', form: form, subject: @service - .footer-block.row-content-block - %button.btn.btn-save{ type: 'submit' } - = icon('spinner spin', class: 'hidden js-btn-spinner') - %span.js-btn-label - Save changes - - - if @service.valid? && @service.activated? - - unless @service.can_test? - - disabled_class = 'disabled' - - disabled_title = @service.disabled_title + - if @service.editable? + .footer-block.row-content-block + %button.btn.btn-save{ type: 'submit' } + = icon('spinner spin', class: 'hidden js-btn-spinner') + %span.js-btn-label + Save changes + + - if @service.valid? && @service.activated? + - unless @service.can_test? + - disabled_class = 'disabled' + - disabled_title = @service.disabled_title - = link_to 'Cancel', namespace_project_settings_integrations_path(@project.namespace, @project), class: 'btn btn-cancel' + = link_to 'Cancel', project_settings_integrations_path(@project), class: 'btn btn-cancel' - if lookup_context.template_exists?('show', "projects/services/#{@service.to_param}", true) %hr diff --git a/app/views/projects/services/_index.html.haml b/app/views/projects/services/_index.html.haml index 997b702da33..915c6b22162 100644 --- a/app/views/projects/services/_index.html.haml +++ b/app/views/projects/services/_index.html.haml @@ -21,7 +21,7 @@ %td{ "aria-label" => "#{service.title}: status " + (service.activated? ? "on" : "off") } = boolean_to_icon service.activated? %td - = link_to edit_namespace_project_service_path(@project.namespace, @project, service.to_param) do + = link_to edit_project_service_path(@project, service.to_param) do %strong= service.title %td.hidden-xs = service.description diff --git a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml b/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml index fcc91be11cd..44c0b7a90dc 100644 --- a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml +++ b/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml @@ -2,6 +2,6 @@ - unless @service.activated? .row .col-sm-9.col-sm-offset-3 - = link_to new_namespace_project_mattermost_path(@project.namespace, @project), class: 'btn btn-lg' do + = link_to new_project_mattermost_path(@project), class: 'btn btn-lg' do = custom_icon('mattermost_logo', size: 15) Add to Mattermost diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml index c4ac384ca1a..0996ec06ab7 100644 --- a/app/views/projects/services/prometheus/_show.html.haml +++ b/app/views/projects/services/prometheus/_show.html.haml @@ -11,7 +11,7 @@ = link_to 'More information', '#' .col-lg-9 - .panel.panel-default.js-panel-monitored-metrics{ data: { "active-metrics" => "#{namespace_project_prometheus_active_metrics_path(@project.namespace, @project, :json)}" } } + .panel.panel-default.js-panel-monitored-metrics{ data: { "active-metrics" => "#{project_prometheus_active_metrics_path(@project, :json)}" } } .panel-heading %h3.panel-title Monitored diff --git a/app/views/projects/settings/_head.html.haml b/app/views/projects/settings/_head.html.haml index 00bd563999f..b5773acb5a4 100644 --- a/app/views/projects/settings/_head.html.haml +++ b/app/views/projects/settings/_head.html.haml @@ -19,16 +19,16 @@ %span Integrations = nav_link(controller: :repository) do - = link_to namespace_project_settings_repository_path(@project.namespace, @project), title: 'Repository' do + = link_to project_settings_repository_path(@project), title: 'Repository' do %span Repository - if @project.feature_available?(:builds, current_user) = nav_link(controller: :ci_cd) do - = link_to namespace_project_settings_ci_cd_path(@project.namespace, @project), title: 'Pipelines' do + = link_to project_settings_ci_cd_path(@project), title: 'Pipelines' do %span Pipelines - if Gitlab.config.pages.enabled = nav_link(controller: :pages) do - = link_to namespace_project_pages_path(@project.namespace, @project), title: 'Pages' do + = link_to project_pages_path(@project), title: 'Pages' do %span Pages diff --git a/app/views/projects/settings/integrations/_project_hook.html.haml b/app/views/projects/settings/integrations/_project_hook.html.haml index a6640592dba..00700e286c3 100644 --- a/app/views/projects/settings/integrations/_project_hook.html.haml +++ b/app/views/projects/settings/integrations/_project_hook.html.haml @@ -9,8 +9,8 @@ .col-md-4.col-lg-5.text-right-lg.prepend-top-5 %span.append-right-10.inline SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"} - = link_to "Edit", edit_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm" - = link_to "Test", test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm" - = link_to namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent" do + = link_to "Edit", edit_project_hook_path(@project, hook), class: "btn btn-sm" + = link_to "Test", test_project_hook_path(@project, hook), class: "btn btn-sm" + = link_to project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent" do %span.sr-only Remove = icon('trash') diff --git a/app/views/projects/show.atom.builder b/app/views/projects/show.atom.builder index ed34f5c0520..39f8cb9a0e0 100644 --- a/app/views/projects/show.atom.builder +++ b/app/views/projects/show.atom.builder @@ -1,7 +1,7 @@ xml.title "#{@project.name} activity" -xml.link href: namespace_project_url(@project.namespace, @project, rss_url_options), rel: "self", type: "application/atom+xml" -xml.link href: namespace_project_url(@project.namespace, @project), rel: "alternate", type: "text/html" -xml.id namespace_project_url(@project.namespace, @project) +xml.link href: project_url(@project, rss_url_options), rel: "self", type: "application/atom+xml" +xml.link href: project_url(@project), rel: "alternate", type: "text/html" +xml.id project_url(@project) xml.updated @events[0].updated_at.xmlschema if @events[0] xml << render(@events) if @events.any? diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index e1e70a53709..ea780b1cb83 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,7 +1,7 @@ - @no_container = true = content_for :meta_tags do - = auto_discovery_link_tag(:atom, namespace_project_path(@project.namespace, @project, rss_url_options), title: "#{@project.name} activity") + = auto_discovery_link_tag(:atom, project_path(@project, rss_url_options), title: "#{@project.name} activity") = content_for :flash_message do - if current_user && can?(current_user, :download_code, @project) @@ -16,16 +16,16 @@ %nav.project-stats{ class: container_class } %ul.nav %li - = link_to project_files_path(@project) do + = link_to project_tree_path(@project) do #{_('Files')} (#{storage_counter(@project.statistics.total_repository_size)}) %li - = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do + = link_to project_commits_path(@project, current_ref) do #{n_('Commit', 'Commits', @project.statistics.commit_count)} (#{number_with_delimiter(@project.statistics.commit_count)}) %li - = link_to namespace_project_branches_path(@project.namespace, @project) do + = link_to project_branches_path(@project) do #{n_('Branch', 'Branches', @repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)}) %li - = link_to namespace_project_tags_path(@project.namespace, @project) do + = link_to project_tags_path(@project) do #{n_('Tag', 'Tags', @repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)}) - if default_project_view != 'readme' && @repository.readme @@ -73,7 +73,7 @@ = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml', commit_message: 'Set up auto deploy', branch_name: 'auto-deploy', context: 'autodeploy') do #{ _('Set up auto deploy') } -%div{ class: container_class } +%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } - if @project.archived? .text-warning.center.prepend-top-20 %p diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml index 34ee4ff1937..f09871c7fcc 100644 --- a/app/views/projects/snippets/_actions.html.haml +++ b/app/views/projects/snippets/_actions.html.haml @@ -2,16 +2,16 @@ .hidden-xs - if can?(current_user, :update_project_snippet, @snippet) - = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped" do + = link_to edit_project_snippet_path(@project, @snippet), class: "btn btn-grouped" do Edit - if can?(current_user, :update_project_snippet, @snippet) - = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do + = link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do Delete - if can?(current_user, :create_project_snippet, @project) - = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do + = link_to new_project_snippet_path(@project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do New snippet - if @snippet.submittable_as_spam_by?(current_user) - = link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam' + = link_to 'Submit as spam', mark_as_spam_project_snippet_path(@project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam' - if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet) .visible-xs-block.dropdown %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } @@ -21,16 +21,16 @@ %ul - if can?(current_user, :create_project_snippet, @project) %li - = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New snippet" do + = link_to new_project_snippet_path(@project), title: "New snippet" do New snippet - if can?(current_user, :update_project_snippet, @snippet) %li - = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do + = link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do Delete - if can?(current_user, :update_project_snippet, @snippet) %li - = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do + = link_to edit_project_snippet_path(@project, @snippet) do Edit - if @snippet.submittable_as_spam_by?(current_user) %li - = link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post + = link_to 'Submit as spam', mark_as_spam_project_snippet_path(@project, @snippet), method: :post diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml index 24b92094b7d..d41cc8e0425 100644 --- a/app/views/projects/snippets/edit.html.haml +++ b/app/views/projects/snippets/edit.html.haml @@ -3,4 +3,4 @@ %h3.page-title Edit Snippet %hr -= render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet) += render "shared/snippets/form", url: project_snippet_path(@project, @snippet) diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index 84e05cd6d88..4f8ce526c83 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -7,13 +7,13 @@ .nav-controls.hidden-xs - if can?(current_user, :create_project_snippet, @project) - = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New snippet" do + = link_to new_project_snippet_path(@project), class: "btn btn-new", title: "New snippet" do New snippet - if can?(current_user, :create_project_snippet, @project) .visible-xs - = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new btn-block", title: "New snippet" do + = link_to new_project_snippet_path(@project), class: "btn btn-new btn-block", title: "New snippet" do New snippet = render 'snippets/snippets' diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml index cfed3a79bc5..d3e6b456f48 100644 --- a/app/views/projects/snippets/new.html.haml +++ b/app/views/projects/snippets/new.html.haml @@ -3,4 +3,4 @@ %h3.page-title New Snippet %hr -= render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet) += render "shared/snippets/form", url: project_snippets_path(@project, @snippet) diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index 44cb734d7b9..468ab922542 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -2,7 +2,7 @@ - release = @releases.find { |release| release.tag == tag.name } %li.flex-row .row-main-content.str-truncated - = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'item-title ref-name' do + = link_to project_tag_path(@project, tag.name), class: 'item-title ref-name' do = icon('tag') = tag.name @@ -29,9 +29,9 @@ = render 'projects/buttons/download', project: @project, ref: tag.name, pipeline: @tags_pipelines[tag.name] - if can?(current_user, :push_code, @project) - = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn has-tooltip', title: "Edit release notes", data: { container: "body" } do + = link_to edit_project_tag_release_path(@project, tag.name), class: 'btn has-tooltip', title: "Edit release notes", data: { container: "body" } do = icon("pencil") - if can?(current_user, :admin_project, @project) - = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do + = link_to project_tag_path(@project, tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do = icon("trash-o") diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 56656ea3d86..bf97cbc1f68 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -24,7 +24,7 @@ %li = link_to title, filter_tags_path(sort: value), class: ("is-active" if @sort == value) - if can?(current_user, :push_code, @project) - = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do + = link_to new_project_tag_path(@project), class: 'btn btn-create new-tag-btn' do New tag .tags diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index 52af295bddd..f1bbaf40387 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -39,7 +39,7 @@ .help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page. .form-actions = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3 - = link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel' + = link_to 'Cancel', project_tags_path(@project), class: 'btn btn-cancel' :javascript window.gl = window.gl || { }; diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index 2b81ce4b9fa..d02cd70f4c3 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -19,17 +19,17 @@ .nav-controls.controls-flex - if can?(current_user, :push_code, @project) - = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Edit release notes' do + = link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Edit release notes' do = icon("pencil") - = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse files' do + = link_to project_tree_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse files' do = icon('files-o') - = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse commits' do + = link_to project_commits_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse commits' do = icon('history') .btn-container.controls-item = render 'projects/buttons/download', project: @project, ref: @tag.name - if can?(current_user, :admin_project, @project) .btn-container.controls-item-full - = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, @tag) ? 'disabled' : ''}", title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do + = link_to project_tag_path(@project, @tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, @tag) ? 'disabled' : ''}", title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do %i.fa.fa-trash-o - if @tag.message.present? diff --git a/app/views/projects/transfer.js.haml b/app/views/projects/transfer.js.haml index 17b9fecfeb1..6d083c5c516 100644 --- a/app/views/projects/transfer.js.haml +++ b/app/views/projects/transfer.js.haml @@ -1,2 +1,2 @@ :plain - location.href = "#{edit_namespace_project_path(@project.namespace, @project)}"; + location.href = "#{edit_project_path(@project)}"; diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml index 425b460eb09..fd8175e1e01 100644 --- a/app/views/projects/tree/_blob_item.html.haml +++ b/app/views/projects/tree/_blob_item.html.haml @@ -2,7 +2,7 @@ %td.tree-item-file-name = tree_icon(type, blob_item.mode, blob_item.name) - file_name = blob_item.name - = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@id || @commit.id, blob_item.name)), title: file_name do + = link_to project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)), title: file_name do %span.str-truncated= file_name %td.hidden-xs.tree-commit %td.tree-time-ago.cgray.text-right diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml index f9147815427..4579a912f39 100644 --- a/app/views/projects/tree/_readme.html.haml +++ b/app/views/projects/tree/_readme.html.haml @@ -2,8 +2,8 @@ %article.file-holder.readme-holder{ class: ("limited-width-container" unless fluid_layout) } .js-file-title.file-title = blob_icon readme.mode, readme.name - = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, readme.path)) do + = link_to project_blob_path(@project, tree_join(@ref, readme.path)) do %strong = readme.name - = render 'projects/blob/viewer', viewer: readme.rich_viewer, viewer_url: namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, readme.path), viewer: :rich, format: :json) + = render 'projects/blob/viewer', viewer: readme.rich_viewer, viewer_url: project_blob_path(@project, tree_join(@ref, readme.path), viewer: :rich, format: :json) diff --git a/app/views/projects/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml index 84da16b6bb1..f3d4706809f 100644 --- a/app/views/projects/tree/_tree_commit_column.html.haml +++ b/app/views/projects/tree/_tree_commit_column.html.haml @@ -1,2 +1,2 @@ %span.str-truncated - = link_to_gfm commit.full_title, namespace_project_commit_path(@project.namespace, @project, commit.id), class: "tree-commit-link" + = link_to_gfm commit.full_title, project_commit_path(@project, commit.id), class: "tree-commit-link" diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml index 7854e1305db..6560bd5ab3f 100644 --- a/app/views/projects/tree/_tree_content.html.haml +++ b/app/views/projects/tree/_tree_content.html.haml @@ -10,7 +10,7 @@ - if @path.present? %tr.tree-item %td.tree-item-file-name - = link_to "..", namespace_project_tree_path(@project.namespace, @project, up_dir_path), class: 'prepend-left-10' + = link_to "..", project_tree_path(@project, up_dir_path), class: 'prepend-left-10' %td %td.hidden-xs @@ -20,7 +20,7 @@ = render "projects/tree/readme", readme: tree.readme - if can_edit_tree? - = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post + = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post = render 'projects/blob/new_dir' :javascript diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index 00da76349da..858418ff8df 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -4,11 +4,11 @@ %ul.breadcrumb.repo-breadcrumb %li - = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do + = link_to project_tree_path(@project, @ref) do = @project.path - path_breadcrumbs do |title, path| %li - = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, tree_join(@ref, path)) + = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path)) - if current_user %li @@ -23,7 +23,7 @@ %ul.dropdown-menu - if can_edit_tree? %li - = link_to namespace_project_new_blob_path(@project.namespace, @project, @id) do + = link_to project_new_blob_path(@project, @id) do = icon('pencil fw') #{ _('New file') } %li @@ -36,10 +36,10 @@ #{ _('New directory') } - elsif can?(current_user, :fork_project, @project) %li - - continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @id), + - continue_params = { to: project_new_blob_path(@project, @id), notice: edit_in_new_fork_notice, notice_now: edit_in_new_fork_notice_now } - - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id, + - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params) = link_to fork_path, method: :post do = icon('pencil fw') @@ -48,7 +48,7 @@ - continue_params = { to: request.fullpath, notice: edit_in_new_fork_notice + " Try to upload a file again.", notice_now: edit_in_new_fork_notice_now } - - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id, + - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params) = link_to fork_path, method: :post do = icon('file fw') @@ -57,7 +57,7 @@ - continue_params = { to: request.fullpath, notice: edit_in_new_fork_notice + " Try to create a new directory again.", notice_now: edit_in_new_fork_notice_now } - - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id, + - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params) = link_to fork_path, method: :post do = icon('folder fw') @@ -65,17 +65,17 @@ %li.divider %li - = link_to new_namespace_project_branch_path(@project.namespace, @project) do + = link_to new_project_branch_path(@project) do = icon('code-fork fw') #{ _('New branch') } %li - = link_to new_namespace_project_tag_path(@project.namespace, @project) do + = link_to new_project_tag_path(@project) do = icon('tags fw') #{ _('New tag') } .tree-controls = render 'projects/find_file_link' - = link_to s_('Commits|History'), namespace_project_commits_path(@project.namespace, @project, @id), class: 'btn' + = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn' = render 'projects/buttons/download', project: @project, ref: @ref diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml index 15c9536133c..0c9c8750f2c 100644 --- a/app/views/projects/tree/_tree_item.html.haml +++ b/app/views/projects/tree/_tree_item.html.haml @@ -2,7 +2,7 @@ %td.tree-item-file-name = tree_icon(type, tree_item.mode, tree_item.name) - path = flatten_tree(tree_item) - = link_to namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path)), title: path do + = link_to project_tree_path(@project, tree_join(@id || @commit.id, path)), title: path do %span.str-truncated= path %td.hidden-xs.tree-commit %td.tree-time-ago.text-right diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index 96a08f9f8be..130980556c1 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -2,10 +2,9 @@ - page_title @path.presence || _("Files"), @ref = content_for :meta_tags do - = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") + = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") = render "projects/commits/head" -= render 'projects/last_push' - -%div{ class: container_class } +%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } + = render 'projects/last_push' = render 'projects/files', commit: @last_commit, project: @project, ref: @ref diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml index 9b5f63ae81a..6249c32b7cc 100644 --- a/app/views/projects/triggers/_trigger.html.haml +++ b/app/views/projects/triggers/_trigger.html.haml @@ -33,10 +33,10 @@ - take_ownership_confirmation = "By taking ownership you will bind this trigger to your user account. With this the trigger will have access to all your projects as if it was you. Are you sure?" - revoke_trigger_confirmation = "By revoking a trigger you will break any processes making use of it. Are you sure?" - if trigger.owner != current_user && can?(current_user, :manage_trigger, trigger) - = link_to 'Take ownership', take_ownership_namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: take_ownership_confirmation }, method: :post, class: "btn btn-default btn-sm btn-trigger-take-ownership" + = link_to 'Take ownership', take_ownership_project_trigger_path(@project, trigger), data: { confirm: take_ownership_confirmation }, method: :post, class: "btn btn-default btn-sm btn-trigger-take-ownership" - if can?(current_user, :admin_trigger, trigger) - = link_to edit_namespace_project_trigger_path(@project.namespace, @project, trigger), method: :get, title: "Edit", class: "btn btn-default btn-sm" do + = link_to edit_project_trigger_path(@project, trigger), method: :get, title: "Edit", class: "btn btn-default btn-sm" do %i.fa.fa-pencil - if can?(current_user, :manage_trigger, trigger) - = link_to namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: revoke_trigger_confirmation }, method: :delete, title: "Revoke", class: "btn btn-default btn-warning btn-sm btn-trigger-revoke" do + = link_to project_trigger_path(@project, trigger), data: { confirm: revoke_trigger_confirmation }, method: :delete, title: "Revoke", class: "btn btn-default btn-warning btn-sm btn-trigger-revoke" do %i.fa.fa-trash diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml index dcf1f767bf7..2c05ebe52ae 100644 --- a/app/views/projects/update.js.haml +++ b/app/views/projects/update.js.haml @@ -1,6 +1,6 @@ - if @project.valid? :plain - location.href = "#{edit_namespace_project_path(@project.namespace, @project)}"; + location.href = "#{edit_project_path(@project)}"; - else :plain $(".project-edit-errors").html("#{escape_javascript(render('errors'))}"); diff --git a/app/views/projects/variables/_table.html.haml b/app/views/projects/variables/_table.html.haml index 59cd3c4b592..4ce6a828812 100644 --- a/app/views/projects/variables/_table.html.haml +++ b/app/views/projects/variables/_table.html.haml @@ -18,11 +18,11 @@ %td.variable-value{ "data-value" => variable.value }****** %td.variable-protected= Gitlab::Utils.boolean_to_yes_no(variable.protected) %td.variable-menu - = link_to namespace_project_variable_path(@project.namespace, @project, variable), class: "btn btn-transparent btn-variable-edit" do + = link_to project_variable_path(@project, variable), class: "btn btn-transparent btn-variable-edit" do %span.sr-only Update = icon("pencil") - = link_to namespace_project_variable_path(@project.namespace, @project, variable), class: "btn btn-transparent btn-variable-delete", method: :delete, data: { confirm: "Are you sure?" } do + = link_to project_variable_path(@project, variable), class: "btn btn-transparent btn-variable-delete", method: :delete, data: { confirm: "Are you sure?" } do %span.sr-only Remove = icon("trash") diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index c10b3004bc3..fc6b7a33943 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -12,7 +12,7 @@ .form-group .col-sm-12= f.label :content, class: 'control-label-full-width' .col-sm-12 - = render layout: 'projects/md_preview', locals: { url: namespace_project_wiki_preview_markdown_path(@project.namespace, @project, @page.slug) } do + = render layout: 'projects/md_preview', locals: { url: project_wiki_preview_markdown_path(@project, @page.slug) } do = render 'projects/zen', f: f, attr: :content, classes: 'note-textarea', placeholder: 'Write your content or drag files here...' = render 'shared/notes/hints' @@ -36,8 +36,8 @@ - if @page && @page.persisted? = f.submit 'Save changes', class: "btn-save btn" .pull-right - = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-cancel btn-grouped" + = link_to "Cancel", project_wiki_path(@project, @page), class: "btn btn-cancel btn-grouped" - else = f.submit 'Create page', class: "btn-create btn" .pull-right - = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, :home), class: "btn btn-cancel" + = link_to "Cancel", project_wiki_path(@project, :home), class: "btn btn-cancel" diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml index 6a578dbf640..3bbd8042c3a 100644 --- a/app/views/projects/wikis/_main_links.html.haml +++ b/app/views/projects/wikis/_main_links.html.haml @@ -2,8 +2,8 @@ - if can?(current_user, :create_wiki, @project) = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do New page - = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do + = link_to project_wiki_history_path(@project, @page), class: "btn" do Page history - if can?(current_user, :create_wiki, @project) && @page.latest? - = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn js-wiki-edit" do + = link_to project_wiki_edit_path(@project, @page), class: "btn js-wiki-edit" do Edit diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml index 1e553940593..13dd8461433 100644 --- a/app/views/projects/wikis/_new.html.haml +++ b/app/views/projects/wikis/_new.html.haml @@ -9,7 +9,7 @@ .form-group = label_tag :new_wiki_path do %span Page slug - = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true + = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => project_wikis_path(@project), autofocus: true %span.new-wiki-page-slug-tip = icon('lightbulb-o') Tip: You can specify the full path for the new file. diff --git a/app/views/projects/wikis/_pages_wiki_page.html.haml b/app/views/projects/wikis/_pages_wiki_page.html.haml index 6298cf6c8da..7c2f562d422 100644 --- a/app/views/projects/wikis/_pages_wiki_page.html.haml +++ b/app/views/projects/wikis/_pages_wiki_page.html.haml @@ -1,5 +1,5 @@ %li - = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page) + = link_to wiki_page.title, project_wiki_path(@project, wiki_page) %small (#{wiki_page.format}) .pull-right %small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)} diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml index c2f9e65015d..62873d3aa66 100644 --- a/app/views/projects/wikis/_sidebar.html.haml +++ b/app/views/projects/wikis/_sidebar.html.haml @@ -3,7 +3,7 @@ %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) + - git_access_url = project_wikis_git_access_path(@project) = link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do = succeed ' ' do = icon('cloud-download') @@ -15,7 +15,7 @@ = render @sidebar_wiki_entries, context: 'sidebar' .block - = link_to namespace_project_wikis_pages_path(@project.namespace, @project), class: 'btn btn-block' do + = link_to project_wikis_pages_path(@project), class: 'btn btn-block' do More Pages = render 'projects/wikis/new' diff --git a/app/views/projects/wikis/_sidebar_wiki_page.html.haml b/app/views/projects/wikis/_sidebar_wiki_page.html.haml index 0a61d90177b..2423ac6abce 100644 --- a/app/views/projects/wikis/_sidebar_wiki_page.html.haml +++ b/app/views/projects/wikis/_sidebar_wiki_page.html.haml @@ -1,3 +1,3 @@ %li{ class: active_when(params[:id] == wiki_page.slug) } - = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_page) do + = link_to project_wiki_path(@project, wiki_page) do = wiki_page.title.capitalize diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml index fbe192a40ec..df0ec14eb3b 100644 --- a/app/views/projects/wikis/edit.html.haml +++ b/app/views/projects/wikis/edit.html.haml @@ -8,7 +8,7 @@ .nav-text %h2.wiki-page-title - if @page.persisted? - = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) + = link_to @page.title.capitalize, project_wiki_path(@project, @page) - else = @page.title.capitalize %span.light @@ -23,10 +23,10 @@ = 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 + = link_to project_wiki_history_path(@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 + = link_to project_wiki_path(@project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do Delete = render 'form' diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml index 0e47e2a5fa3..306feeff259 100644 --- a/app/views/projects/wikis/history.html.haml +++ b/app/views/projects/wikis/history.html.haml @@ -6,7 +6,7 @@ .nav-text %h2.wiki-page-title - = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) + = link_to @page.title.capitalize, project_wiki_path(@project, @page) %span.light · History diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml index 5fba2b1a5ae..dece1fad0bb 100644 --- a/app/views/projects/wikis/pages.html.haml +++ b/app/views/projects/wikis/pages.html.haml @@ -9,7 +9,7 @@ Wiki Pages .nav-controls - = link_to namespace_project_wikis_git_access_path(@project.namespace, @project), class: 'btn' do + = link_to project_wikis_git_access_path(@project), class: 'btn' do = icon('cloud-download') Clone repository diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index f003ff6b63f..13591dd8e74 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -22,7 +22,7 @@ - if @page.historical? .warning_message This is an old version of this page. - You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}. + You can view the #{link_to "most recent version", project_wiki_path(@project, @page)} or browse the #{link_to "history", project_wiki_history_path(@project, @page)}. .wiki-holder.prepend-top-default.append-bottom-default .wiki diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml index 7f1f807e2e7..de473c23d66 100644 --- a/app/views/search/results/_blob.html.haml +++ b/app/views/search/results/_blob.html.haml @@ -3,7 +3,7 @@ .file-holder .js-file-title.file-title - ref = @search_results.repository_ref - - blob_link = namespace_project_blob_path(@project.namespace, @project, tree_join(ref, file_name)) + - blob_link = project_blob_path(@project, tree_join(ref, file_name)) = link_to blob_link do %i.fa.fa-file %strong diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml index 026f404ce07..aef825691e0 100644 --- a/app/views/search/results/_snippet_title.html.haml +++ b/app/views/search/results/_snippet_title.html.haml @@ -11,7 +11,7 @@ %small.pull-right.cgray - if snippet_title.project_id? - = link_to snippet_title.project.name_with_namespace, namespace_project_path(snippet_title.project.namespace, snippet_title.project) + = link_to snippet_title.project.name_with_namespace, project_path(snippet_title.project) .snippet-info = snippet_title.to_reference diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml index d87f9df2677..16a0e432d62 100644 --- a/app/views/search/results/_wiki_blob.html.haml +++ b/app/views/search/results/_wiki_blob.html.haml @@ -2,7 +2,7 @@ .blob-result .file-holder .js-file-title.file-title - = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_blob.basename) do + = link_to project_wiki_path(@project, wiki_blob.basename) do %i.fa.fa-file %strong = wiki_blob.basename diff --git a/app/views/shared/_issuable_meta_data.html.haml b/app/views/shared/_issuable_meta_data.html.haml index 1d4fd71522d..435acbc634c 100644 --- a/app/views/shared/_issuable_meta_data.html.haml +++ b/app/views/shared/_issuable_meta_data.html.haml @@ -5,21 +5,21 @@ - issuable_mr = @issuable_meta_data[issuable.id].merge_requests_count - if issuable_mr > 0 - %li + %li.issuable-mr.hidden-xs = image_tag('icon-merge-request-unmerged.svg', class: 'icon-merge-request-unmerged') = issuable_mr - if upvotes > 0 - %li + %li.issuable-upvotes.hidden-xs = icon('thumbs-up') = upvotes - if downvotes > 0 - %li + %li.issuable-downvotes.hidden-xs = icon('thumbs-down') = downvotes -%li +%li.issuable-comments.hidden-xs = link_to issuable_url, class: ('no-comments' if note_count.zero?) do = icon('comments') = note_count diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml index 3a49227961f..49555b6ff4e 100644 --- a/app/views/shared/_issues.html.haml +++ b/app/views/shared/_issues.html.haml @@ -1,6 +1,6 @@ - if @issues.to_a.any? .panel.panel-default.panel-small.panel-without-border - %ul.content-list.issues-list + %ul.content-list.issues-list.issuable-list = render partial: 'projects/issues/issue', collection: @issues = paginate @issues, theme: "gitlab" - else diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index de0281e97c6..2f776a17f45 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -23,7 +23,7 @@ - if can_subscribe_to_label_in_different_levels?(label) %a.js-unsubscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path } } %span Unsubscribe - %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } } + %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_project_label_path(@project, label) } } %span Subscribe at project level %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_group_label_path(label.group, label) } } %span Subscribe at group level @@ -56,7 +56,7 @@ = icon('chevron-down') %ul.dropdown-menu %li - %a.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } } + %a.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_project_label_path(@project, label) } } Project level %a.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_group_label_path(label.group, label) } } Group level @@ -66,7 +66,7 @@ = icon('spinner spin', class: 'label-subscribe-button-loading') - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group) - = link_to promote_namespace_project_label_path(label.project.namespace, label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "Promoting this label will make this label available to all projects inside this group. Existing project labels with the same name will be merged. Are you sure?", toggle: "tooltip"}, method: :post do + = link_to promote_project_label_path(label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "Promoting this label will make this label available to all projects inside this group. Existing project labels with the same name will be merged. Are you sure?", toggle: "tooltip"}, method: :post do %span.sr-only Promote to Group = icon('level-up') - if can?(current_user, :admin_label, label) diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 7b599dff0e3..7f58298c60f 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -2,7 +2,7 @@ - if can?(current_user, :admin_label, @project) .draggable-handler = icon('bars') - .js-toggle-priority.toggle-priority{ data: { url: remove_priority_namespace_project_label_path(@project.namespace, @project, label), + .js-toggle-priority.toggle-priority{ data: { url: remove_priority_project_label_path(@project, label), dom_id: dom_id(label), type: label.type } } %button.add-priority.btn.has-tooltip{ title: 'Prioritize', type: 'button', :'data-placement' => 'top' } = icon('star-o') diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml index eecbb32e90e..0517896cfbd 100644 --- a/app/views/shared/_merge_requests.html.haml +++ b/app/views/shared/_merge_requests.html.haml @@ -1,6 +1,6 @@ - if @merge_requests.to_a.any? .panel.panel-default.panel-small.panel-without-border - %ul.content-list.mr-list + %ul.content-list.mr-list.issuable-list = render partial: 'projects/merge_requests/merge_request', collection: @merge_requests = paginate @merge_requests, theme: "gitlab" diff --git a/app/views/shared/_mini_pipeline_graph.html.haml b/app/views/shared/_mini_pipeline_graph.html.haml index aa93572bf94..dff847159d3 100644 --- a/app/views/shared/_mini_pipeline_graph.html.haml +++ b/app/views/shared/_mini_pipeline_graph.html.haml @@ -6,7 +6,7 @@ - status_klass = "ci-status-icon ci-status-icon-#{detailed_status.group}" .stage-container.dropdown{ class: klass } - %button.mini-pipeline-graph-dropdown-toggle.has-tooltip.js-builds-dropdown-button{ class: "ci-status-icon-#{detailed_status.group}", type: 'button', data: { toggle: 'dropdown', title: "#{stage.name}: #{detailed_status.label}", placement: 'top', "stage-endpoint" => stage_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline, stage: stage.name) } } + %button.mini-pipeline-graph-dropdown-toggle.has-tooltip.js-builds-dropdown-button{ class: "ci-status-icon-#{detailed_status.group}", type: 'button', data: { toggle: 'dropdown', title: "#{stage.name}: #{detailed_status.label}", placement: 'top', "stage-endpoint" => stage_project_pipeline_path(pipeline.project, pipeline, stage: stage.name) } } = custom_icon(icon_status) = icon('caret-down') diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml index d52bb6b4dd7..4498c8f8349 100644 --- a/app/views/shared/_ref_switcher.html.haml +++ b/app/views/shared/_ref_switcher.html.haml @@ -1,12 +1,12 @@ - dropdown_toggle_text = @ref || @project.default_branch -= form_tag switch_namespace_project_refs_path(@project.namespace, @project), method: :get, class: "project-refs-form" do += form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do = hidden_field_tag :destination, destination - if defined?(path) = hidden_field_tag :path, path - @options && @options.each do |key, value| = hidden_field_tag key, value, id: nil .dropdown - = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_namespace_project_path(@project.namespace, @project), field_name: 'ref', submit_form_on_click: true }, { toggle_class: "js-project-refs-dropdown" } + = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project), field_name: 'ref', submit_form_on_click: true }, { toggle_class: "js-project-refs-dropdown" } .dropdown-menu.dropdown-menu-selectable.git-revision-dropdown{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) } = dropdown_title _("Switch branch/tag") = dropdown_filter _("Search branches and tags") diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml index b200e5fc528..7ca14ac93cc 100644 --- a/app/views/shared/_service_settings.html.haml +++ b/app/views/shared/_service_settings.html.haml @@ -7,10 +7,11 @@ = markdown @service.help .service-settings - .form-group - = form.label :active, "Active", class: "control-label" - .col-sm-10 - = form.check_box :active + - if @service.show_active_box? + .form-group + = form.label :active, "Active", class: "control-label" + .col-sm-10 + = form.check_box :active - if @service.supported_events.present? .form-group diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml index a212c714826..785a500e44e 100644 --- a/app/views/shared/_sort_dropdown.html.haml +++ b/app/views/shared/_sort_dropdown.html.haml @@ -1,3 +1,5 @@ +- viewing_issues = controller.controller_name == 'issues' || controller.action_name == 'issues' + .dropdown.inline.prepend-left-10 %button.dropdown-toggle{ type: 'button', data: {toggle: 'dropdown' } } - if @sort.present? @@ -23,7 +25,7 @@ = sort_title_milestone_soon = link_to page_filter_path(sort: sort_value_milestone_later, label: true) do = sort_title_milestone_later - - if controller.controller_name == 'issues' || controller.action_name == 'issues' + - if viewing_issues = link_to page_filter_path(sort: sort_value_due_date_soon, label: true) do = sort_title_due_date_soon = link_to page_filter_path(sort: sort_value_due_date_later, label: true) do diff --git a/app/views/shared/empty_states/_labels.html.haml b/app/views/shared/empty_states/_labels.html.haml index 5e2f4cf109d..bfda522f2f6 100644 --- a/app/views/shared/empty_states/_labels.html.haml +++ b/app/views/shared/empty_states/_labels.html.haml @@ -7,5 +7,5 @@ %h4 Labels can be applied to issues and merge requests to categorize them. %p You can also star a label to make it a priority label. - if can?(current_user, :admin_label, @project) - = link_to 'New label', new_namespace_project_label_path(@project.namespace, @project), class: 'btn btn-new', title: 'New label', id: 'new_label_link' - = link_to 'Generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post, class: 'btn btn-success btn-inverted', title: 'Generate a default set of labels', id: 'generate_labels_link' + = link_to 'New label', new_project_label_path(@project), class: 'btn btn-new', title: 'New label', id: 'new_label_link' + = link_to 'Generate a default set of labels', generate_project_labels_path(@project), method: :post, class: 'btn btn-success btn-inverted', title: 'Generate a default set of labels', id: 'generate_labels_link' diff --git a/app/views/shared/issuable/_bulk_update_sidebar.html.haml b/app/views/shared/issuable/_bulk_update_sidebar.html.haml index 7cfdfb6e6ee..964fe5220f7 100644 --- a/app/views/shared/issuable/_bulk_update_sidebar.html.haml +++ b/app/views/shared/issuable/_bulk_update_sidebar.html.haml @@ -31,7 +31,7 @@ .title Milestone .filter-item - = dropdown_tag("Select milestone", options: { title: "Assign milestone", toggle_class: "js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true, default_label: "Milestone" } }) + = dropdown_tag("Select milestone", options: { title: "Assign milestone", toggle_class: "js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: project_milestones_path(@project, :json), use_id: true, default_label: "Milestone" } }) .block .title Labels diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml index 9a8529c6cbb..e8feff32d26 100644 --- a/app/views/shared/issuable/_label_page_default.html.haml +++ b/app/views/shared/issuable/_label_page_default.html.haml @@ -20,7 +20,7 @@ %a.dropdown-toggle-page{ href: "#" } Create new label %li - = link_to namespace_project_labels_path(@project.namespace, @project), :"data-is-link" => true do + = link_to project_labels_path(@project), :"data-is-link" => true do - if show_create && @project && can?(current_user, :admin_label, @project) Manage labels - else diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index 6750921338a..955b8866c2c 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -11,10 +11,10 @@ %ul.dropdown-footer-list - if can? current_user, :admin_milestone, project %li - = link_to new_namespace_project_milestone_path(project.namespace, project), title: "New Milestone" do + = link_to new_project_milestone_path(project), title: "New Milestone" do Create new %li - = link_to namespace_project_milestones_path(project.namespace, project) do + = link_to project_milestones_path(project) do - if can? current_user, :admin_milestone, project Manage milestones - else diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index d3d290692a2..ae890567225 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -23,7 +23,7 @@ .scroll-container %ul.tokens-container.list-unstyled %li.input-token - %input.form-control.filtered-search{ id: "filtered-search-#{type.to_s}", placeholder: 'Search or filter results...', data: { 'project-id' => @project.id, 'username-params' => @users.to_json(only: [:id, :username]), 'base-endpoint' => namespace_project_path(@project.namespace, @project) } } + %input.form-control.filtered-search{ search_filter_input_options(type) } = icon('filter') #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown %ul{ data: { dropdown: true } } diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 745f1ee62da..ecbaa901792 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -37,13 +37,13 @@ = link_to 'Edit', '#', class: 'edit-link pull-right' .value.hide-collapsed - if issuable.milestone - = link_to issuable.milestone.title, namespace_project_milestone_path(@project.namespace, @project, issuable.milestone), class: "bold has-tooltip", title: milestone_remaining_days(issuable.milestone), data: { container: "body", html: 1 } + = link_to issuable.milestone.title, project_milestone_path(@project, issuable.milestone), class: "bold has-tooltip", title: milestone_remaining_days(issuable.milestone), data: { container: "body", html: 1 } - else %span.no-value None .selectbox.hide-collapsed = f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil - = dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true, default_no: true, selected: (issuable.milestone.name if issuable.milestone), null_default: true }}) + = dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: project_milestones_path(@project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true, default_no: true, selected: (issuable.milestone.name if issuable.milestone), null_default: true }}) - if issuable.has_attribute?(:time_estimate) #issuable-time-tracker.block // Fallback while content is loading @@ -106,7 +106,7 @@ - selected_labels.each do |label| = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil .dropdown - %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project) } } + %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (project_labels_path(@project, :json) if @project) } } %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) } = multi_label_name(selected_labels, "Labels") = icon('chevron-down', 'aria-hidden': 'true') diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml index 2ea5eb960c0..57392cd7fbb 100644 --- a/app/views/shared/issuable/_sidebar_assignees.html.haml +++ b/app/views/shared/issuable/_sidebar_assignees.html.haml @@ -37,19 +37,20 @@ - issuable.assignees.each do |assignee| = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username } - - options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } } - + - options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: current_user&.username, current_user: true, project_id: @project&.id, author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } } - title = 'Select assignee' - if issuable.is_a?(Issue) - unless issuable.assignees.any? = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil + - dropdown_options = issue_assignees_dropdown_options + - title = dropdown_options[:title] - options[:toggle_class] += ' js-multiselect js-save-user-data' - data = { field_name: "#{issuable.to_ability_name}[assignee_ids][]" } - data[:multi_select] = true - data['dropdown-title'] = title - - data['dropdown-header'] = 'Assignee' - - data['max-select'] = 1 + - data['dropdown-header'] = dropdown_options[:data][:'dropdown-header'] + - data['max-select'] = dropdown_options[:data][:'max-select'] - options[:data].merge!(data) = dropdown_tag(title, options: options) diff --git a/app/views/shared/issuable/form/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml index bfa91629e1e..8f6509a8ce8 100644 --- a/app/views/shared/issuable/form/_merge_params.html.haml +++ b/app/views/shared/issuable/form/_merge_params.html.haml @@ -11,8 +11,7 @@ .col-sm-10.col-sm-offset-2 - if issuable.can_remove_source_branch?(current_user) .checkbox - - initial_checkbox_value = issuable.merge_params.key?('force_remove_source_branch') ? issuable.force_remove_source_branch? : true = 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', initial_checkbox_value + = 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/views/shared/issuable/form/_metadata_issue_assignee.html.haml b/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml index 77175c839a6..567cde764e2 100644 --- a/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml +++ b/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml @@ -7,5 +7,5 @@ - if issuable.assignees.length === 0 = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil, data: { meta: '' } - = dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_dropdown_options(issuable,false)) + = dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_assignees_dropdown_options) = link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}" diff --git a/app/views/shared/members/_access_request_buttons.html.haml b/app/views/shared/members/_access_request_buttons.html.haml index d97fdf179d7..40224cec9e8 100644 --- a/app/views/shared/members/_access_request_buttons.html.haml +++ b/app/views/shared/members/_access_request_buttons.html.haml @@ -1,18 +1,20 @@ - model_name = source.model_name.to_s.downcase -.project-action-button.inline - - if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id)) +- if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id)) + .project-action-button.inline - link_text = source.is_a?(Group) ? _('Leave group') : _('Leave project') = link_to link_text, polymorphic_path([:leave, source, :members]), method: :delete, data: { confirm: leave_confirmation_message(source) }, class: 'btn' - - elsif requester = source.requesters.find_by(user_id: current_user.id) +- elsif requester = source.requesters.find_by(user_id: current_user.id) + .project-action-button.inline = link_to _('Withdraw Access Request'), polymorphic_path([:leave, source, :members]), method: :delete, data: { confirm: remove_member_message(requester) }, class: 'btn' - - elsif source.request_access_enabled && can?(current_user, :request_access, source) +- elsif source.request_access_enabled && can?(current_user, :request_access, source) + .project-action-button.inline = link_to _('Request Access'), polymorphic_path([:request_access, source, :members]), method: :post, class: 'btn' diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index 1d5a61cffce..bcdad3c153a 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -14,7 +14,7 @@ %span{ class: ('text-warning' if group_link.expires_soon?) } Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)} .controls.member-controls - = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do + = form_tag project_group_link_path(@project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do = hidden_field_tag "group_link[group_access]", group_link.group_access .member-form-control.dropdown.append-right-5 %button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button", @@ -36,7 +36,7 @@ = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can_admin_member %i.clear-icon.js-clear-input - if can_admin_member - = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), + = link_to project_group_link_path(@project, group_link), method: :delete, data: { confirm: "Are you sure you want to remove #{group.name}?" }, class: 'btn btn-remove prepend-left-10' do diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml index 680e1f3a4ea..ecc8b42979c 100644 --- a/app/views/shared/milestones/_milestone.html.haml +++ b/app/views/shared/milestones/_milestone.html.haml @@ -35,9 +35,9 @@ .col-sm-6= render('shared/milestone_expired', milestone: milestone) .col-sm-6.milestone-actions - if can?(current_user, :admin_milestone, milestone.project) and milestone.active? - = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs btn-grouped" do + = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-xs btn-grouped" do Edit \ - = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close btn-grouped" - = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove btn-grouped" do + = link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close btn-grouped" + = link_to project_milestone_path(milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove btn-grouped" do Delete diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml index 9bb87640319..895fb8247b5 100644 --- a/app/views/shared/milestones/_sidebar.html.haml +++ b/app/views/shared/milestones/_sidebar.html.haml @@ -21,7 +21,7 @@ .title Start date - if @project && can?(current_user, :admin_milestone, @project) - = link_to 'Edit', edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: 'edit-link pull-right' + = link_to 'Edit', edit_project_milestone_path(@project, @milestone), class: 'edit-link pull-right' .value %span.value-content - if milestone.start_date @@ -51,7 +51,7 @@ .title.hide-collapsed Due date - if @project && can?(current_user, :admin_milestone, @project) - = link_to 'Edit', edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: 'edit-link pull-right' + = link_to 'Edit', edit_project_milestone_path(@project, @milestone), class: 'edit-link pull-right' .value.hide-collapsed %span.value-content - if milestone.due_date @@ -73,7 +73,7 @@ Issues %span.badge= milestone.issues_visible_to_user(current_user).count - if project && can?(current_user, :create_issue, project) - = link_to new_namespace_project_issue_path(project.namespace, project, issue: { milestone_id: milestone.id }), class: "pull-right", title: "New Issue" do + = link_to new_project_issue_path(project, issue: { milestone_id: milestone.id }), class: "pull-right", title: "New Issue" do New issue .value.hide-collapsed.bold %span.milestone-stat diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml index 4de8a6cb15f..e2d1695b7c3 100644 --- a/app/views/shared/milestones/_tabs.html.haml +++ b/app/views/shared/milestones/_tabs.html.haml @@ -30,7 +30,7 @@ .tab-content.milestone-content - if milestone.is_a?(GlobalMilestone) || can?(current_user, :read_issue, @project) - .tab-pane.active#tab-issues{ data: { sort_endpoint: (sort_issues_namespace_project_milestone_path(@project.namespace, @project, @milestone) if @project && current_user) } } + .tab-pane.active#tab-issues{ data: { sort_endpoint: (sort_issues_project_milestone_path(@project, @milestone) if @project && current_user) } } = render 'shared/milestones/issues_tab', issues: milestone.sorted_issues(current_user), show_project_name: show_project_name, show_full_project_name: show_full_project_name .tab-pane#tab-merge-requests -# loaded async diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml index 2562f085338..20a12613cfc 100644 --- a/app/views/shared/milestones/_top.html.haml +++ b/app/views/shared/milestones/_top.html.haml @@ -48,7 +48,7 @@ %tr %td - project_name = group ? ms.project.name : ms.project.name_with_namespace - = link_to project_name, namespace_project_milestone_path(ms.project.namespace, ms.project, ms) + = link_to project_name, project_milestone_path(ms.project, ms) %td = ms.issues_visible_to_user(current_user).opened.count %td diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml index 1e34b7c1e76..7174855e176 100644 --- a/app/views/shared/notes/_note.html.haml +++ b/app/views/shared/notes/_note.html.haml @@ -60,6 +60,6 @@ = link_to note.attachment.url, target: '_blank' do = icon('paperclip') = note.attachment_identifier - = link_to delete_attachment_namespace_project_note_path(note.project.namespace, note.project, note), + = link_to delete_attachment_project_note_path(note.project, note), title: 'Delete this attachment', method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: 'danger js-note-attachment-delete' do = icon('trash-o', class: 'cred') diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index 8c3d6351ac2..4bdbc26a4c3 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -31,7 +31,7 @@ - if show_last_commit_as_description .description.prepend-top-5 - = link_to_gfm project.commit.title, namespace_project_commit_path(project.namespace, project, project.commit), + = link_to_gfm project.commit.title, project_commit_path(project, project.commit), class: "commit-row-message" - elsif project.description.present? .description.prepend-top-5 diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml index 8549cb91b03..43322978749 100644 --- a/app/views/shared/snippets/_form.html.haml +++ b/app/views/shared/snippets/_form.html.haml @@ -36,6 +36,6 @@ = f.submit 'Save changes', class: "btn-save btn" - if @snippet.project_id - = link_to "Cancel", namespace_project_snippets_path(@project.namespace, @project), class: "btn btn-cancel" + = link_to "Cancel", project_snippets_path(@project), class: "btn btn-cancel" - else = link_to "Cancel", snippets_path(@project), class: "btn btn-cancel" diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml index 5d2d2317f22..7388f20a9fd 100644 --- a/app/views/shared/snippets/_snippet.html.haml +++ b/app/views/shared/snippets/_snippet.html.haml @@ -30,7 +30,7 @@ - if link_project && snippet.project_id? %span.hidden-xs in - = link_to namespace_project_path(snippet.project.namespace, snippet.project) do + = link_to project_path(snippet.project) do = snippet.project.name_with_namespace .pull-right.snippet-updated-at diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml index d1e88274878..805a346a85e 100644 --- a/app/views/users/calendar_activities.html.haml +++ b/app/views/users/calendar_activities.html.haml @@ -12,7 +12,7 @@ - if event.push? #{event.action_name} #{event.ref_type} %strong - - commits_path = namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) + - commits_path = project_commits_path(event.project, event.ref_name) = link_to_if event.project.repository.branch_exists?(event.ref_name), event.ref_name, commits_path - else = event_action_name(event) diff --git a/app/workers/expire_job_cache_worker.rb b/app/workers/expire_job_cache_worker.rb index 08e281e7350..e383202260d 100644 --- a/app/workers/expire_job_cache_worker.rb +++ b/app/workers/expire_job_cache_worker.rb @@ -18,18 +18,10 @@ class ExpireJobCacheWorker private def project_pipeline_path(project, pipeline) - Gitlab::Routing.url_helpers.namespace_project_pipeline_path( - project.namespace, - project, - pipeline, - format: :json) + Gitlab::Routing.url_helpers.project_pipeline_path(project, pipeline, format: :json) end def project_job_path(project, job) - Gitlab::Routing.url_helpers.namespace_project_build_path( - project.namespace, - project, - job.id, - format: :json) + Gitlab::Routing.url_helpers.project_build_path(project, job.id, format: :json) end end diff --git a/app/workers/expire_pipeline_cache_worker.rb b/app/workers/expire_pipeline_cache_worker.rb index 92e622285de..7c02d6cf892 100644 --- a/app/workers/expire_pipeline_cache_worker.rb +++ b/app/workers/expire_pipeline_cache_worker.rb @@ -23,42 +23,24 @@ class ExpirePipelineCacheWorker private def project_pipelines_path(project) - Gitlab::Routing.url_helpers.namespace_project_pipelines_path( - project.namespace, - project, - format: :json) + Gitlab::Routing.url_helpers.project_pipelines_path(project, format: :json) end def project_pipeline_path(project, pipeline) - Gitlab::Routing.url_helpers.namespace_project_pipeline_path( - project.namespace, - project, - pipeline, - format: :json) + Gitlab::Routing.url_helpers.project_pipeline_path(project, pipeline, format: :json) end def commit_pipelines_path(project, commit) - Gitlab::Routing.url_helpers.pipelines_namespace_project_commit_path( - project.namespace, - project, - commit.id, - format: :json) + Gitlab::Routing.url_helpers.pipelines_project_commit_path(project, commit.id, format: :json) end def new_merge_request_pipelines_path(project) - Gitlab::Routing.url_helpers.namespace_project_new_merge_request_path( - project.namespace, - project, - format: :json) + Gitlab::Routing.url_helpers.project_new_merge_request_path(project, format: :json) end def each_pipelines_merge_request_path(project, pipeline) pipeline.all_merge_requests.each do |merge_request| - path = Gitlab::Routing.url_helpers.pipelines_namespace_project_merge_request_path( - project.namespace, - project, - merge_request, - format: :json) + path = Gitlab::Routing.url_helpers.pipelines_project_merge_request_path(project, merge_request, format: :json) yield(path) end diff --git a/changelogs/unreleased/18000-remember-me-for-oauth-login.yml b/changelogs/unreleased/18000-remember-me-for-oauth-login.yml new file mode 100644 index 00000000000..1ef92756a76 --- /dev/null +++ b/changelogs/unreleased/18000-remember-me-for-oauth-login.yml @@ -0,0 +1,4 @@ +--- +title: Honor the "Remember me" parameter for OAuth-based login +merge_request: 11963 +author: diff --git a/changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml new file mode 100644 index 00000000000..a5f78202c93 --- /dev/null +++ b/changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml @@ -0,0 +1,4 @@ +--- +title: Replace 'dashboard/new-project.feature' spinach with rspec +merge_request: 12550 +author: Alexander Randa (@randaalex) diff --git a/changelogs/unreleased/23036-replace-snippets-spinach.yml b/changelogs/unreleased/23036-replace-snippets-spinach.yml new file mode 100644 index 00000000000..545805b1302 --- /dev/null +++ b/changelogs/unreleased/23036-replace-snippets-spinach.yml @@ -0,0 +1,4 @@ +--- +title: Replace 'snippets/snippets.feature' spinach with rspec +merge_request: 12385 +author: Alexander Randa @randaalex diff --git a/changelogs/unreleased/23162-allow-creation-of-files-and-dirs-with-spaces-in-web-ui.yml b/changelogs/unreleased/23162-allow-creation-of-files-and-dirs-with-spaces-in-web-ui.yml new file mode 100644 index 00000000000..442406c3c04 --- /dev/null +++ b/changelogs/unreleased/23162-allow-creation-of-files-and-dirs-with-spaces-in-web-ui.yml @@ -0,0 +1,4 @@ +--- +title: Allow creation of files and directories with spaces through Web UI +merge_request: 12608 +author: diff --git a/changelogs/unreleased/32048-shared-runners-admin-buttons-have-odd-spacing.yml b/changelogs/unreleased/32048-shared-runners-admin-buttons-have-odd-spacing.yml new file mode 100644 index 00000000000..99e64b9b467 --- /dev/null +++ b/changelogs/unreleased/32048-shared-runners-admin-buttons-have-odd-spacing.yml @@ -0,0 +1,4 @@ +--- +title: Fix spacing on runner buttons. +merge_request: !12535 +author: diff --git a/changelogs/unreleased/32408-enable-disable-all-restricted-visibility-levels.yml b/changelogs/unreleased/32408-enable-disable-all-restricted-visibility-levels.yml new file mode 100644 index 00000000000..ebb27d118d7 --- /dev/null +++ b/changelogs/unreleased/32408-enable-disable-all-restricted-visibility-levels.yml @@ -0,0 +1,4 @@ +--- +title: Allow admins to disable all restricted visibility levels +merge_request: 12649 +author: diff --git a/changelogs/unreleased/32815--Add-Custom-CI-Config-Path.yml b/changelogs/unreleased/32815--Add-Custom-CI-Config-Path.yml new file mode 100644 index 00000000000..7784d7d0ce0 --- /dev/null +++ b/changelogs/unreleased/32815--Add-Custom-CI-Config-Path.yml @@ -0,0 +1,4 @@ +--- +title: Allow customize CI config path +merge_request: 12509 +author: Keith Pope diff --git a/changelogs/unreleased/32838-admin-panel-spacing.yml b/changelogs/unreleased/32838-admin-panel-spacing.yml new file mode 100644 index 00000000000..ccd703fa43f --- /dev/null +++ b/changelogs/unreleased/32838-admin-panel-spacing.yml @@ -0,0 +1,4 @@ +--- +title: Add wells to admin dashboard overview to fix spacing problems +merge_request: +author: diff --git a/changelogs/unreleased/33130-remove-group-modal.yml b/changelogs/unreleased/33130-remove-group-modal.yml new file mode 100644 index 00000000000..4672d41ded5 --- /dev/null +++ b/changelogs/unreleased/33130-remove-group-modal.yml @@ -0,0 +1,4 @@ +--- +title: "Remove group modal like remove project modal (requires typing + confirmation)" +merge_request: 12569 +author: Diego Souza diff --git a/changelogs/unreleased/33360-generate-kubeconfig.yml b/changelogs/unreleased/33360-generate-kubeconfig.yml new file mode 100644 index 00000000000..96f0b1bc93f --- /dev/null +++ b/changelogs/unreleased/33360-generate-kubeconfig.yml @@ -0,0 +1,4 @@ +--- +title: Provide KUBECONFIG from KubernetesService for runners +merge_request: 12223 +author: diff --git a/changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml b/changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml new file mode 100644 index 00000000000..d6b1b2524c6 --- /dev/null +++ b/changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml @@ -0,0 +1,4 @@ +--- +title: Supplement Traditional Chinese in Taiwan translation of Project Page & Repository Page +merge_request: 12514 +author: Huang Tao diff --git a/changelogs/unreleased/33580-fix-api-scoping.yml b/changelogs/unreleased/33580-fix-api-scoping.yml new file mode 100644 index 00000000000..f4ebb13c082 --- /dev/null +++ b/changelogs/unreleased/33580-fix-api-scoping.yml @@ -0,0 +1,4 @@ +--- +title: Fix API Scoping +merge_request: 12300 +author: diff --git a/changelogs/unreleased/33657-user-projects-api.yml b/changelogs/unreleased/33657-user-projects-api.yml new file mode 100644 index 00000000000..a8d485865e9 --- /dev/null +++ b/changelogs/unreleased/33657-user-projects-api.yml @@ -0,0 +1,4 @@ +--- +title: Add user projects API +merge_request: 12596 +author: Ivan Chernov diff --git a/changelogs/unreleased/33772-readonly-gitlab-ci-cache.yml b/changelogs/unreleased/33772-readonly-gitlab-ci-cache.yml new file mode 100644 index 00000000000..c2bce368a58 --- /dev/null +++ b/changelogs/unreleased/33772-readonly-gitlab-ci-cache.yml @@ -0,0 +1,4 @@ +--- +title: Introduce cache policies for CI jobs +merge_request: 12483 +author: diff --git a/changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml b/changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml new file mode 100644 index 00000000000..69d5d34b072 --- /dev/null +++ b/changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml @@ -0,0 +1,4 @@ +--- +title: Allow the feature flags to be enabled/disabled with more granularity +merge_request: 12357 +author: diff --git a/changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml b/changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml new file mode 100644 index 00000000000..8f8b5a96c2b --- /dev/null +++ b/changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml @@ -0,0 +1,4 @@ +--- +title: Change milestone endpoint for groups +merge_request: 12374 +author: Takuya Noguchi diff --git a/changelogs/unreleased/34141-allow-unauthenticated-access-to-the-users-api.yml b/changelogs/unreleased/34141-allow-unauthenticated-access-to-the-users-api.yml new file mode 100644 index 00000000000..a3ade8db214 --- /dev/null +++ b/changelogs/unreleased/34141-allow-unauthenticated-access-to-the-users-api.yml @@ -0,0 +1,4 @@ +--- +title: Allow unauthenticated access to the /api/v4/users API +merge_request: 12445 +author: diff --git a/changelogs/unreleased/34169-add-simplified-chinese-translations-of-commits-page.yml b/changelogs/unreleased/34169-add-simplified-chinese-translations-of-commits-page.yml new file mode 100644 index 00000000000..1a631c3f0a4 --- /dev/null +++ b/changelogs/unreleased/34169-add-simplified-chinese-translations-of-commits-page.yml @@ -0,0 +1,4 @@ +--- +title: Add Simplified Chinese translations of Commits Page +merge_request: 12405 +author: Huang Tao diff --git a/changelogs/unreleased/34171-add-traditional-chinese-in-hongkong-translations-of-commits-page.yml b/changelogs/unreleased/34171-add-traditional-chinese-in-hongkong-translations-of-commits-page.yml new file mode 100644 index 00000000000..3cf7c0b547f --- /dev/null +++ b/changelogs/unreleased/34171-add-traditional-chinese-in-hongkong-translations-of-commits-page.yml @@ -0,0 +1,4 @@ +--- +title: Add Traditional Chinese in HongKong translations of Commits Page +merge_request: 12406 +author: Huang Tao diff --git a/changelogs/unreleased/34172-add-traditional-chinese-in-taiwan-translations-of-commits-page.yml b/changelogs/unreleased/34172-add-traditional-chinese-in-taiwan-translations-of-commits-page.yml new file mode 100644 index 00000000000..224b9e1852f --- /dev/null +++ b/changelogs/unreleased/34172-add-traditional-chinese-in-taiwan-translations-of-commits-page.yml @@ -0,0 +1,4 @@ +--- +title: Add Traditional Chinese in Taiwan translations of Commits Page +merge_request: 12407 +author: Huang Tao diff --git a/changelogs/unreleased/34175-add-esperanto-translations-of-commits-page.yml b/changelogs/unreleased/34175-add-esperanto-translations-of-commits-page.yml new file mode 100644 index 00000000000..b43a38f3794 --- /dev/null +++ b/changelogs/unreleased/34175-add-esperanto-translations-of-commits-page.yml @@ -0,0 +1,4 @@ +--- +title: Add Esperanto translations of Commits Page +merge_request: 12410 +author: Huang Tao diff --git a/changelogs/unreleased/34176-add-bulgarian-translations-of-commits-page.yml b/changelogs/unreleased/34176-add-bulgarian-translations-of-commits-page.yml new file mode 100644 index 00000000000..9177ae3acd1 --- /dev/null +++ b/changelogs/unreleased/34176-add-bulgarian-translations-of-commits-page.yml @@ -0,0 +1,4 @@ +--- +title: Add Bulgarian translations of Commits Page +merge_request: 12411 +author: Huang Tao diff --git a/changelogs/unreleased/34531-remove-scroll.yml b/changelogs/unreleased/34531-remove-scroll.yml new file mode 100644 index 00000000000..c3c5289f66f --- /dev/null +++ b/changelogs/unreleased/34531-remove-scroll.yml @@ -0,0 +1,4 @@ +--- +title: Update jobs page output to have a scrollable page +merge_request: 12587 +author: diff --git a/changelogs/unreleased/34544-add-italian-translation-of-cycle-analytics-page-&-project-page-&-repository-page.yml b/changelogs/unreleased/34544-add-italian-translation-of-cycle-analytics-page-&-project-page-&-repository-page.yml new file mode 100644 index 00000000000..31f4262c9f9 --- /dev/null +++ b/changelogs/unreleased/34544-add-italian-translation-of-cycle-analytics-page-&-project-page-&-repository-page.yml @@ -0,0 +1,4 @@ +--- +title: Add Italian translation of Cycle Analytics Page & Project Page & Repository Page +merge_request: 12578 +author: Huang Tao diff --git a/changelogs/unreleased/34578-sidebar-padding.yml b/changelogs/unreleased/34578-sidebar-padding.yml new file mode 100644 index 00000000000..dc4647298e6 --- /dev/null +++ b/changelogs/unreleased/34578-sidebar-padding.yml @@ -0,0 +1,4 @@ +--- +title: fix left & right padding on sidebar +merge_request: +author: diff --git a/changelogs/unreleased/34653-minor-ux-cleanups-for-performance-dashboard.yml b/changelogs/unreleased/34653-minor-ux-cleanups-for-performance-dashboard.yml new file mode 100644 index 00000000000..736991318d7 --- /dev/null +++ b/changelogs/unreleased/34653-minor-ux-cleanups-for-performance-dashboard.yml @@ -0,0 +1,4 @@ +--- +title: Cleanup minor UX issues in the performance dashboard +merge_request: +author: diff --git a/changelogs/unreleased/34688-add-italian-translations-of-commits-page.yml b/changelogs/unreleased/34688-add-italian-translations-of-commits-page.yml new file mode 100644 index 00000000000..90a1f8c98fe --- /dev/null +++ b/changelogs/unreleased/34688-add-italian-translations-of-commits-page.yml @@ -0,0 +1,4 @@ +--- +title: Add Italian translations of Commits Page +merge_request: 12645 +author: Huang Tao diff --git a/changelogs/unreleased/adam-external-issue-references-spike.yml b/changelogs/unreleased/adam-external-issue-references-spike.yml new file mode 100644 index 00000000000..aeec6688425 --- /dev/null +++ b/changelogs/unreleased/adam-external-issue-references-spike.yml @@ -0,0 +1,4 @@ +--- +title: Improve support for external issue references +merge_request: 12485 +author: diff --git a/changelogs/unreleased/add-ci_variables-environment_scope-mysql.yml b/changelogs/unreleased/add-ci_variables-environment_scope-mysql.yml new file mode 100644 index 00000000000..4948d415bed --- /dev/null +++ b/changelogs/unreleased/add-ci_variables-environment_scope-mysql.yml @@ -0,0 +1,6 @@ +--- +title: Rename duplicated variables with the same key for projects. Add environment_scope + column to variables and add unique constraint to make sure that no variables could + be created with the same key within a project +merge_request: 12363 +author: diff --git a/changelogs/unreleased/dm-dependency-linker-newlines.yml b/changelogs/unreleased/dm-dependency-linker-newlines.yml deleted file mode 100644 index 5631095fcb7..00000000000 --- a/changelogs/unreleased/dm-dependency-linker-newlines.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix diff of requirements.txt file by not matching newlines as part of package - names -merge_request: -author: diff --git a/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml b/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml new file mode 100644 index 00000000000..b359a25053a --- /dev/null +++ b/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml @@ -0,0 +1,4 @@ +--- +title: Improve performance of lookups of issues, merge requests etc by dropping unnecessary ORDER BY clause +merge_request: +author: diff --git a/changelogs/unreleased/dm-empty-state-new-merge-request.yml b/changelogs/unreleased/dm-empty-state-new-merge-request.yml new file mode 100644 index 00000000000..5fad7a0f883 --- /dev/null +++ b/changelogs/unreleased/dm-empty-state-new-merge-request.yml @@ -0,0 +1,5 @@ +--- +title: Fix 'New merge request' button for users who don't have push access to canonical + project +merge_request: +author: diff --git a/changelogs/unreleased/dm-encode-tree-and-blob-paths.yml b/changelogs/unreleased/dm-encode-tree-and-blob-paths.yml new file mode 100644 index 00000000000..c1a026e1f29 --- /dev/null +++ b/changelogs/unreleased/dm-encode-tree-and-blob-paths.yml @@ -0,0 +1,5 @@ +--- +title: Fix issues with non-UTF8 filenames by always fixing the encoding of tree and + blob paths +merge_request: +author: diff --git a/changelogs/unreleased/enable-polling-env.yml b/changelogs/unreleased/enable-polling-env.yml new file mode 100644 index 00000000000..b3f65f02574 --- /dev/null +++ b/changelogs/unreleased/enable-polling-env.yml @@ -0,0 +1,4 @@ +--- +title: Re-enable realtime for environments table +merge_request: +author: diff --git a/changelogs/unreleased/enable-webpack-code-splitting.yml b/changelogs/unreleased/enable-webpack-code-splitting.yml new file mode 100644 index 00000000000..d61c3b97d11 --- /dev/null +++ b/changelogs/unreleased/enable-webpack-code-splitting.yml @@ -0,0 +1,5 @@ +--- +title: Enable support for webpack code-splitting by dynamically setting publicPath + at runtime +merge_request: 12032 +author: diff --git a/changelogs/unreleased/feature-no-hypen-at-end-of-commit-ref-slug.yml b/changelogs/unreleased/feature-no-hypen-at-end-of-commit-ref-slug.yml new file mode 100644 index 00000000000..bbcf2946ea7 --- /dev/null +++ b/changelogs/unreleased/feature-no-hypen-at-end-of-commit-ref-slug.yml @@ -0,0 +1,4 @@ +--- +title: Omit trailing / leading hyphens in CI_COMMIT_REF_SLUG variable to make it usable as a hostname +merge_request: 11218 +author: Stefan Hanreich diff --git a/changelogs/unreleased/fix-34417.yml b/changelogs/unreleased/fix-34417.yml deleted file mode 100644 index 5f012ad0c81..00000000000 --- a/changelogs/unreleased/fix-34417.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Perform housekeeping only when an import of a fresh project is completed -merge_request: -author: diff --git a/changelogs/unreleased/fix-assigned-issuable-lists.yml b/changelogs/unreleased/fix-assigned-issuable-lists.yml new file mode 100644 index 00000000000..fc2cd18ddb6 --- /dev/null +++ b/changelogs/unreleased/fix-assigned-issuable-lists.yml @@ -0,0 +1,5 @@ +--- +title: Add issuable-list class to shared mr/issue lists to fix new responsive layout + design +merge_request: +author: diff --git a/changelogs/unreleased/fix-head-pipeline-for-commit-status.yml b/changelogs/unreleased/fix-head-pipeline-for-commit-status.yml deleted file mode 100644 index f12e7b53790..00000000000 --- a/changelogs/unreleased/fix-head-pipeline-for-commit-status.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix head pipeline stored in merge request for external pipelines -merge_request: 12478 -author: diff --git a/changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml b/changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml new file mode 100644 index 00000000000..856990a6126 --- /dev/null +++ b/changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml @@ -0,0 +1,4 @@ +--- +title: Fixed sidebar not collapsing on merge requests in mobile screens +merge_request: +author: diff --git a/changelogs/unreleased/foreign-keys-for-project-model.yml b/changelogs/unreleased/foreign-keys-for-project-model.yml new file mode 100644 index 00000000000..3648b1c3735 --- /dev/null +++ b/changelogs/unreleased/foreign-keys-for-project-model.yml @@ -0,0 +1,4 @@ +--- +title: Speed up project removals by adding foreign keys with cascading deletes to various tables +merge_request: +author: diff --git a/changelogs/unreleased/hb-hide-archived-labels-from-group-issue-tracker.yml b/changelogs/unreleased/hb-hide-archived-labels-from-group-issue-tracker.yml new file mode 100644 index 00000000000..3b465d84126 --- /dev/null +++ b/changelogs/unreleased/hb-hide-archived-labels-from-group-issue-tracker.yml @@ -0,0 +1,4 @@ +--- +title: Hide archived project labels from group issue tracker +merge_request: 12547 +author: Horacio Bertorello diff --git a/changelogs/unreleased/highest-return-on-diff-investment.yml b/changelogs/unreleased/highest-return-on-diff-investment.yml deleted file mode 100644 index c8be1e0ff8f..00000000000 --- a/changelogs/unreleased/highest-return-on-diff-investment.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Bring back branches badge to main project page -merge_request: 12548 -author: diff --git a/changelogs/unreleased/issue-boards-closed-list-all.yml b/changelogs/unreleased/issue-boards-closed-list-all.yml deleted file mode 100644 index 7643864150d..00000000000 --- a/changelogs/unreleased/issue-boards-closed-list-all.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixed issue boards closed list not showing all closed issues -merge_request: -author: diff --git a/changelogs/unreleased/issue-form-multiple-line-markdown.yml b/changelogs/unreleased/issue-form-multiple-line-markdown.yml deleted file mode 100644 index 23128f346bc..00000000000 --- a/changelogs/unreleased/issue-form-multiple-line-markdown.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixed multi-line markdown tooltip buttons in issue edit form -merge_request: -author: diff --git a/changelogs/unreleased/issueable-list-cleanup.yml b/changelogs/unreleased/issueable-list-cleanup.yml new file mode 100644 index 00000000000..d3d67d04574 --- /dev/null +++ b/changelogs/unreleased/issueable-list-cleanup.yml @@ -0,0 +1,4 @@ +--- +title: Clean up UI of issuable lists and make more responsive +merge_request: +author: diff --git a/changelogs/unreleased/monitoring-dashboard-fine-tuning-ux.yml b/changelogs/unreleased/monitoring-dashboard-fine-tuning-ux.yml new file mode 100644 index 00000000000..f84d41b7929 --- /dev/null +++ b/changelogs/unreleased/monitoring-dashboard-fine-tuning-ux.yml @@ -0,0 +1,4 @@ +--- +title: Improve the overall UX for the new monitoring dashboard +merge_request: +author: diff --git a/changelogs/unreleased/monitoring-dashboard-fix-y-label.yml b/changelogs/unreleased/monitoring-dashboard-fix-y-label.yml new file mode 100644 index 00000000000..8a0e9ca855c --- /dev/null +++ b/changelogs/unreleased/monitoring-dashboard-fix-y-label.yml @@ -0,0 +1,4 @@ +--- +title: Fixed the y_label not setting correctly for each graph on the monitoring dashboard +merge_request: +author: diff --git a/changelogs/unreleased/sh-allow-force-repo-create.yml b/changelogs/unreleased/sh-allow-force-repo-create.yml new file mode 100644 index 00000000000..2a65ba807bb --- /dev/null +++ b/changelogs/unreleased/sh-allow-force-repo-create.yml @@ -0,0 +1,4 @@ +--- +title: Make Project#ensure_repository force create a repo +merge_request: +author: diff --git a/changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml b/changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml new file mode 100644 index 00000000000..9309f961345 --- /dev/null +++ b/changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml @@ -0,0 +1,4 @@ +--- +title: Defer project destroys within a namespace in Groups::DestroyService#async_execute +merge_request: +author: diff --git a/changelogs/unreleased/sh-log-application-controller-exceptions-sentry.yml b/changelogs/unreleased/sh-log-application-controller-exceptions-sentry.yml new file mode 100644 index 00000000000..ec9ceab3d81 --- /dev/null +++ b/changelogs/unreleased/sh-log-application-controller-exceptions-sentry.yml @@ -0,0 +1,4 @@ +--- +title: Log rescued exceptions to Sentry +merge_request: +author: diff --git a/changelogs/unreleased/sh-optimize-project-commit-api.yml b/changelogs/unreleased/sh-optimize-project-commit-api.yml new file mode 100644 index 00000000000..e6a8a80593c --- /dev/null +++ b/changelogs/unreleased/sh-optimize-project-commit-api.yml @@ -0,0 +1,4 @@ +--- +title: Optimize creation of commit API by using Repository#commit instead of Repository#commits +merge_request: +author: diff --git a/changelogs/unreleased/speed-up-issue-counting-for-a-project.yml b/changelogs/unreleased/speed-up-issue-counting-for-a-project.yml new file mode 100644 index 00000000000..6bf03d9a382 --- /dev/null +++ b/changelogs/unreleased/speed-up-issue-counting-for-a-project.yml @@ -0,0 +1,5 @@ +--- +title: Cache open issue and merge request counts for project tabs to speed up project + pages +merge_request: 12457 +author: diff --git a/config/application.rb b/config/application.rb index 3f39170a123..a9a961d7520 100644 --- a/config/application.rb +++ b/config/application.rb @@ -162,5 +162,25 @@ module Gitlab config.generators do |g| g.factory_girl false end + + config.after_initialize do + Rails.application.reload_routes! + + project_url_helpers = Module.new do + Gitlab::Application.routes.named_routes.helper_names.each do |name| + next unless name.include?('namespace_project') + + define_method(name.sub('namespace_project', 'project')) do |project, *args| + send(name, project&.namespace, project, *args) + end + end + end + + Gitlab::Routing.url_helpers.include project_url_helpers + Gitlab::Routing.url_helpers.extend project_url_helpers + + GitlabRoutingHelper.include project_url_helpers + GitlabRoutingHelper.extend project_url_helpers + end end end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 43a8c0078ca..1eb209ac2be 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -543,6 +543,10 @@ production: &base # enabled: true # host: localhost # port: 3808 + prometheus: + # Time between sampling of unicorn socket metrics, in seconds + # unicorn_sampler_interval: 10 + # # 5. Extra customization @@ -615,6 +619,49 @@ test: title: "JIRA" url: https://sample_company.atlassian.net project_key: PROJECT + + omniauth: + enabled: true + allow_single_sign_on: true + external_providers: [] + + providers: + - { name: 'cas3', + label: 'cas3', + args: { url: 'https://sso.example.com', + disable_ssl_verification: false, + login_url: '/cas/login', + service_validate_url: '/cas/p3/serviceValidate', + logout_url: '/cas/logout'} } + - { name: 'github', + app_id: 'YOUR_APP_ID', + app_secret: 'YOUR_APP_SECRET', + url: "https://github.com/", + verify_ssl: false, + args: { scope: 'user:email' } } + - { name: 'bitbucket', + app_id: 'YOUR_APP_ID', + app_secret: 'YOUR_APP_SECRET' } + - { name: 'gitlab', + app_id: 'YOUR_APP_ID', + app_secret: 'YOUR_APP_SECRET', + args: { scope: 'api' } } + - { name: 'google_oauth2', + app_id: 'YOUR_APP_ID', + app_secret: 'YOUR_APP_SECRET', + args: { access_type: 'offline', approval_prompt: '' } } + - { name: 'facebook', + app_id: 'YOUR_APP_ID', + app_secret: 'YOUR_APP_SECRET' } + - { name: 'twitter', + app_id: 'YOUR_APP_ID', + app_secret: 'YOUR_APP_SECRET' } + - { name: 'auth0', + args: { + client_id: 'YOUR_AUTH0_CLIENT_ID', + client_secret: 'YOUR_AUTH0_CLIENT_SECRET', + namespace: 'YOUR_AUTH0_DOMAIN' } } + ldap: enabled: false servers: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 8ddf8e4d2e4..cb11d2c34f4 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -495,6 +495,12 @@ Settings.webpack.dev_server['host'] ||= 'localhost' Settings.webpack.dev_server['port'] ||= 3808 # +# Prometheus metrics settings +# +Settings['prometheus'] ||= Settingslogic.new({}) +Settings.prometheus['unicorn_sampler_interval'] ||= 10 + +# # Testing settings # if Rails.env.test? diff --git a/config/initializers/8_metrics.rb b/config/initializers/8_metrics.rb index a0a63ddf8f0..d56fd7a6cfa 100644 --- a/config/initializers/8_metrics.rb +++ b/config/initializers/8_metrics.rb @@ -119,6 +119,13 @@ def instrument_classes(instrumentation) end # rubocop:enable Metrics/AbcSize +Gitlab::Metrics::UnicornSampler.initialize_instance(Settings.prometheus.unicorn_sampler_interval).start + +Gitlab::Application.configure do |config| + # 0 should be Sentry to catch errors in this middleware + config.middleware.insert(1, Gitlab::Metrics::ConnectionRackMiddleware) +end + if Gitlab::Metrics.enabled? require 'pathname' require 'influxdb' @@ -175,7 +182,7 @@ if Gitlab::Metrics.enabled? GC::Profiler.enable - Gitlab::Metrics::Sampler.new.start + Gitlab::Metrics::InfluxSampler.initialize_instance.start module TrackNewRedisConnections def connect(*args) diff --git a/config/initializers/doorkeeper_openid_connect.rb b/config/initializers/doorkeeper_openid_connect.rb index 4ff9019c43c..c58f425b19b 100644 --- a/config/initializers/doorkeeper_openid_connect.rb +++ b/config/initializers/doorkeeper_openid_connect.rb @@ -29,7 +29,7 @@ Doorkeeper::OpenidConnect.configure do o.claim(:email) { |user| user.public_email } o.claim(:email_verified) { |user| true if user.public_email? } o.claim(:website) { |user| user.full_website_url if user.website_url? } - o.claim(:profile) { |user| Rails.application.routes.url_helpers.user_url user } + o.claim(:profile) { |user| Gitlab::Routing.url_helpers.user_url user } o.claim(:picture) { |user| user.avatar_url(only_path: false) } end end diff --git a/config/initializers/flipper.rb b/config/initializers/flipper.rb index 0fee832788d..8ec9613a4b7 100644 --- a/config/initializers/flipper.rb +++ b/config/initializers/flipper.rb @@ -1,4 +1,6 @@ require 'flipper/middleware/memoizer' -Rails.application.config.middleware.use Flipper::Middleware::Memoizer, - lambda { Feature.flipper } +unless Rails.env.test? + Rails.application.config.middleware.use Flipper::Middleware::Memoizer, + lambda { Feature.flipper } +end diff --git a/config/initializers/relative_naming_ci_namespace.rb b/config/initializers/relative_naming_ci_namespace.rb index 03ac55be0b6..d9d3034150f 100644 --- a/config/initializers/relative_naming_ci_namespace.rb +++ b/config/initializers/relative_naming_ci_namespace.rb @@ -4,10 +4,10 @@ # - [project.namespace, project, build] # # instead of: -# - namespace_project_job_path(project.namespace, project, build) +# - project_job_path(project, build) # # Without that, Ci:: namespace is used for resolving routes: -# - namespace_project_ci_build_path(project.namespace, project, build) +# - project_ci_build_path(project, build) module Ci def self.use_relative_model_naming? diff --git a/config/prometheus/additional_metrics.yml b/config/prometheus/additional_metrics.yml index daecde49570..d33fae4182d 100644 --- a/config/prometheus/additional_metrics.yml +++ b/config/prometheus/additional_metrics.yml @@ -1,32 +1,82 @@ -- group: Kubernetes - priority: 1 +- group: AWS Elastic Load Balancer + priority: 10 metrics: - - title: "Memory usage" - y_label: "Values" + - title: "Throughput" + y_label: "Requests / Sec" required_metrics: - - container_memory_usage_bytes + - aws_elb_request_count_sum weight: 1 queries: - - query_range: 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20' - label: Container memory - unit: MiB - - title: "Current memory usage" + - query_range: 'sum(aws_elb_request_count_sum{%{environment_filter}}) * 60' + label: Total + unit: req / sec + - title: "Latency" + y_label: "Latency (ms)" required_metrics: - - container_memory_usage_bytes + - aws_elb_latency_average weight: 1 queries: - - query: 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20' - display_empty: false - unit: MiB - - title: "CPU usage" + - query_range: 'avg(aws_elb_latency_average{%{environment_filter}}) * 1000' + label: Average + unit: ms + - title: "HTTP Error Rate" + y_label: "Error Rate (%)" required_metrics: - - container_cpu_usage_seconds_total + - aws_elb_request_count_sum + - aws_elb_httpcode_backend_5_xx_sum + weight: 1 + queries: + - query_range: 'sum(aws_elb_httpcode_backend_5_xx_sum{%{environment_filter}}) / sum(aws_elb_request_count_sum{%{environment_filter}})' + label: HTTP Errors + unit: "%" +- group: NGINX + priority: 10 + metrics: + - title: "Throughput" + y_label: "Requests / Sec" + required_metrics: + - nginx_requests_total + weight: 1 + queries: + - query_range: 'sum(rate(nginx_requests_total{server_zone!="*", server_zone!="_", %{environment_filter}}[2m]))' + label: Total + unit: req / sec + - title: "Latency" + y_label: "Latency (ms)" + required_metrics: + - nginx_upstream_response_msecs_avg + weight: 1 + queries: + - query_range: 'avg(nginx_upstream_response_msecs_avg{%{environment_filter}}) * 1000' + label: Upstream + unit: ms + - title: "HTTP Error Rate" + y_label: "Error Rate (%)" + required_metrics: + - nginx_responses_total + weight: 1 + queries: + - query_range: 'sum(nginx_responses_total{status_code="5xx", %{environment_filter}}) / sum(nginx_responses_total{server_zone!="*", server_zone!="_", %{environment_filter}})' + label: HTTP Errors + unit: "%" +- group: Kubernetes + priority: 5 + metrics: + - title: "Memory Usage" + y_label: "Memory Usage (MB)" + required_metrics: + - container_memory_usage_bytes weight: 1 queries: - - query_range: 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100' - - title: "Current CPU usage" + - query_range: '(sum(container_memory_usage_bytes{container_name!="POD",%{environment_filter}}) / count(container_memory_usage_bytes{container_name!="POD",%{environment_filter}})) /1024/1024' + label: Average + unit: MB + - title: "CPU Utilization" + y_label: "CPU Utilization (%)" required_metrics: - container_cpu_usage_seconds_total weight: 1 queries: - - query: 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100' + - query_range: 'sum(rate(container_cpu_usage_seconds_total{container_name!="POD",%{environment_filter}}[2m])) / count(container_cpu_usage_seconds_total{container_name!="POD",%{environment_filter}}) * 100' + label: Average + unit: "%" diff --git a/config/webpack.config.js b/config/webpack.config.js index 90ef6a5448b..cbb0a899638 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -71,6 +71,7 @@ var config = { vue_merge_request_widget: './vue_merge_request_widget/index.js', test: './test.js', peek: './peek.js', + webpack_runtime: './webpack.js', }, output: { @@ -190,7 +191,7 @@ var config = { // create cacheable common library bundles new webpack.optimize.CommonsChunkPlugin({ - names: ['main', 'locale', 'common', 'runtime'], + names: ['main', 'locale', 'common', 'webpack_runtime'], }), ], @@ -245,7 +246,6 @@ if (IS_DEV_SERVER) { hot: DEV_SERVER_LIVERELOAD, inline: DEV_SERVER_LIVERELOAD }; - config.output.publicPath = '//' + DEV_SERVER_HOST + ':' + DEV_SERVER_PORT + config.output.publicPath; config.plugins.push( // watch node_modules for changes if we encounter a missing module compile error new WatchMissingNodeModulesPlugin(path.join(ROOT_PATH, 'node_modules')) diff --git a/db/migrate/20160804142904_add_ci_config_file_to_project.rb b/db/migrate/20160804142904_add_ci_config_file_to_project.rb new file mode 100644 index 00000000000..341ae555c1b --- /dev/null +++ b/db/migrate/20160804142904_add_ci_config_file_to_project.rb @@ -0,0 +1,11 @@ +class AddCiConfigFileToProject < ActiveRecord::Migration + DOWNTIME = false + + def change + add_column :projects, :ci_config_path, :string + end + + def down + remove_column :projects, :ci_config_path + end +end diff --git a/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb b/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb new file mode 100644 index 00000000000..3eaafac321d --- /dev/null +++ b/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb @@ -0,0 +1,187 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class ProjectForeignKeysWithCascadingDeletes < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + CONCURRENCY = 4 + + disable_ddl_transaction! + + # The tables/columns for which to remove orphans and add foreign keys. Order + # matters as some tables/columns should be processed before others. + TABLES = [ + [:boards, :projects, :project_id], + [:lists, :labels, :label_id], + [:lists, :boards, :board_id], + [:services, :projects, :project_id], + [:forked_project_links, :projects, :forked_to_project_id], + [:merge_requests, :projects, :target_project_id], + [:labels, :projects, :project_id], + [:issues, :projects, :project_id], + [:events, :projects, :project_id], + [:milestones, :projects, :project_id], + [:notes, :projects, :project_id], + [:snippets, :projects, :project_id], + [:web_hooks, :projects, :project_id], + [:protected_branch_merge_access_levels, :protected_branches, :protected_branch_id], + [:protected_branch_push_access_levels, :protected_branches, :protected_branch_id], + [:protected_branches, :projects, :project_id], + [:protected_tags, :projects, :project_id], + [:deploy_keys_projects, :projects, :project_id], + [:users_star_projects, :projects, :project_id], + [:releases, :projects, :project_id], + [:project_group_links, :projects, :project_id], + [:pages_domains, :projects, :project_id], + [:todos, :projects, :project_id], + [:project_import_data, :projects, :project_id], + [:project_features, :projects, :project_id], + [:ci_builds, :projects, :project_id], + [:ci_pipelines, :projects, :project_id], + [:ci_runner_projects, :projects, :project_id], + [:ci_triggers, :projects, :project_id], + [:environments, :projects, :project_id], + [:deployments, :projects, :project_id] + ] + + def up + # These existing foreign keys don't have an "ON DELETE CASCADE" clause. + remove_foreign_key_without_error(:boards, :project_id) + remove_foreign_key_without_error(:lists, :label_id) + remove_foreign_key_without_error(:lists, :board_id) + remove_foreign_key_without_error(:protected_branch_merge_access_levels, + :protected_branch_id) + + remove_foreign_key_without_error(:protected_branch_push_access_levels, + :protected_branch_id) + + remove_orphaned_rows + add_foreign_keys + + # These columns are not indexed yet, meaning a cascading delete would take + # forever. + add_concurrent_index(:project_group_links, :project_id) + add_concurrent_index(:pages_domains, :project_id) + end + + def down + TABLES.each do |(source, _, column)| + remove_foreign_key_without_error(source, column) + end + + add_concurrent_foreign_key(:boards, :projects, column: :project_id) + add_concurrent_foreign_key(:lists, :labels, column: :label_id) + add_concurrent_foreign_key(:lists, :boards, column: :board_id) + + add_concurrent_foreign_key(:protected_branch_merge_access_levels, + :protected_branches, + column: :protected_branch_id) + + add_concurrent_foreign_key(:protected_branch_push_access_levels, + :protected_branches, + column: :protected_branch_id) + + remove_index_without_error(:project_group_links, :project_id) + remove_index_without_error(:pages_domains, :project_id) + end + + def add_foreign_keys + TABLES.each do |(source, target, column)| + add_concurrent_foreign_key(source, target, column: column) + end + end + + # Removes orphans from various tables concurrently. + def remove_orphaned_rows + Gitlab::Database.with_connection_pool(CONCURRENCY) do |pool| + queues = queues_for_rows(TABLES) + + threads = queues.map do |queue| + Thread.new do + pool.with_connection do |connection| + Thread.current[:foreign_key_connection] = connection + + # Disables statement timeouts for the current connection. This is + # necessary as removing of orphaned data might otherwise exceed the + # statement timeout. + disable_statement_timeout + + remove_orphans(*queue.pop) until queue.empty? + + steal_from_queues(queues - [queue]) + end + end + end + + threads.each(&:join) + end + end + + def steal_from_queues(queues) + loop do + stolen = false + + queues.each do |queue| + # Stealing is racy so it's possible a pop might be called on an + # already-empty queue. + begin + remove_orphans(*queue.pop(true)) + stolen = true + rescue ThreadError + end + end + + break unless stolen + end + end + + def remove_orphans(source, target, column) + quoted_source = quote_table_name(source) + quoted_target = quote_table_name(target) + quoted_column = quote_column_name(column) + + execute <<-EOF.strip_heredoc + DELETE FROM #{quoted_source} + WHERE NOT EXISTS ( + SELECT true + FROM #{quoted_target} + WHERE #{quoted_target}.id = #{quoted_source}.#{quoted_column} + ) + AND #{quoted_source}.#{quoted_column} IS NOT NULL + EOF + end + + def remove_foreign_key_without_error(table, column) + remove_foreign_key(table, column: column) + rescue ArgumentError + end + + def remove_index_without_error(table, column) + remove_concurrent_index(table, column) + rescue ArgumentError + end + + def connection + # Rails memoizes connection objects, but this causes them to be shared + # amongst threads; we don't want that. + Thread.current[:foreign_key_connection] || ActiveRecord::Base.connection + end + + def queues_for_rows(rows) + queues = Array.new(CONCURRENCY) { Queue.new } + slice_size = rows.length / CONCURRENCY + + # Divide all the tuples as evenly as possible amongst the queues. + rows.each_slice(slice_size).each_with_index do |slice, index| + bucket = index % CONCURRENCY + + slice.each do |row| + queues[bucket] << row + end + end + + queues + end +end diff --git a/db/migrate/20170622130029_correct_protected_branches_foreign_keys.rb b/db/migrate/20170622130029_correct_protected_branches_foreign_keys.rb new file mode 100644 index 00000000000..46497775527 --- /dev/null +++ b/db/migrate/20170622130029_correct_protected_branches_foreign_keys.rb @@ -0,0 +1,40 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class CorrectProtectedBranchesForeignKeys < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + def up + remove_foreign_key_without_error(:protected_branch_push_access_levels, + column: :protected_branch_id) + + execute <<-EOF + DELETE FROM protected_branch_push_access_levels + WHERE NOT EXISTS ( + SELECT true + FROM protected_branches + WHERE protected_branch_push_access_levels.protected_branch_id = protected_branches.id + ) + AND protected_branch_id IS NOT NULL + EOF + + add_concurrent_foreign_key(:protected_branch_push_access_levels, + :protected_branches, + column: :protected_branch_id) + end + + def down + # Previously there was a foreign key without a CASCADING DELETE, so we'll + # just leave the foreign key in place. + end + + def remove_foreign_key_without_error(*args) + remove_foreign_key(*args) + rescue ArgumentError + end +end diff --git a/db/migrate/20170622132212_add_foreign_key_for_merge_request_diffs.rb b/db/migrate/20170622132212_add_foreign_key_for_merge_request_diffs.rb new file mode 100644 index 00000000000..9f524fac8a7 --- /dev/null +++ b/db/migrate/20170622132212_add_foreign_key_for_merge_request_diffs.rb @@ -0,0 +1,30 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddForeignKeyForMergeRequestDiffs < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + def up + execute <<-EOF + DELETE FROM merge_request_diffs + WHERE NOT EXISTS ( + SELECT true + FROM merge_requests + WHERE merge_requests.id = merge_request_diffs.merge_request_id + ) + EOF + + add_concurrent_foreign_key(:merge_request_diffs, + :merge_requests, + column: :merge_request_id) + end + + def down + remove_foreign_key(:merge_request_diffs, column: :merge_request_id) + end +end diff --git a/db/migrate/20170622135451_rename_duplicated_variable_key.rb b/db/migrate/20170622135451_rename_duplicated_variable_key.rb new file mode 100644 index 00000000000..368718ab0ce --- /dev/null +++ b/db/migrate/20170622135451_rename_duplicated_variable_key.rb @@ -0,0 +1,38 @@ +class RenameDuplicatedVariableKey < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + execute(<<~SQL) + UPDATE ci_variables + SET #{key} = CONCAT(#{key}, #{underscore}, id) + WHERE id IN ( + SELECT * + FROM ( -- MySQL requires an extra layer + SELECT dup.id + FROM ci_variables dup + INNER JOIN (SELECT max(id) AS id, #{key}, project_id + FROM ci_variables tmp + GROUP BY #{key}, project_id) var + USING (#{key}, project_id) where dup.id <> var.id + ) dummy + ) + SQL + end + + def down + # noop + end + + def key + # key needs to be quoted in MySQL + quote_column_name('key') + end + + def underscore + quote('_') + end +end diff --git a/db/migrate/20170622135628_add_environment_scope_to_ci_variables.rb b/db/migrate/20170622135628_add_environment_scope_to_ci_variables.rb new file mode 100644 index 00000000000..17fe062d8d5 --- /dev/null +++ b/db/migrate/20170622135628_add_environment_scope_to_ci_variables.rb @@ -0,0 +1,15 @@ +class AddEnvironmentScopeToCiVariables < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column_with_default(:ci_variables, :environment_scope, :string, default: '*') + end + + def down + remove_column(:ci_variables, :environment_scope) + end +end diff --git a/db/migrate/20170622135728_add_unique_constraint_to_ci_variables.rb b/db/migrate/20170622135728_add_unique_constraint_to_ci_variables.rb new file mode 100644 index 00000000000..8b2cc40ee59 --- /dev/null +++ b/db/migrate/20170622135728_add_unique_constraint_to_ci_variables.rb @@ -0,0 +1,38 @@ +class AddUniqueConstraintToCiVariables < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + unless this_index_exists? + add_concurrent_index(:ci_variables, columns, name: index_name, unique: true) + end + end + + def down + if this_index_exists? + if Gitlab::Database.mysql? && !index_exists?(:ci_variables, :project_id) + # Need to add this index for MySQL project_id foreign key constraint + add_concurrent_index(:ci_variables, :project_id) + end + + remove_concurrent_index(:ci_variables, columns, name: index_name) + end + end + + private + + def this_index_exists? + index_exists?(:ci_variables, columns, name: index_name) + end + + def columns + @columns ||= [:project_id, :key, :environment_scope] + end + + def index_name + 'index_ci_variables_on_project_id_and_key_and_environment_scope' + end +end diff --git a/db/migrate/20170623080805_remove_ci_variables_project_id_index.rb b/db/migrate/20170623080805_remove_ci_variables_project_id_index.rb new file mode 100644 index 00000000000..ddcc0292b9d --- /dev/null +++ b/db/migrate/20170623080805_remove_ci_variables_project_id_index.rb @@ -0,0 +1,19 @@ +class RemoveCiVariablesProjectIdIndex < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + if index_exists?(:ci_variables, :project_id) + remove_concurrent_index(:ci_variables, :project_id) + end + end + + def down + unless index_exists?(:ci_variables, :project_id) + add_concurrent_index(:ci_variables, :project_id) + end + end +end diff --git a/db/migrate/20170703102400_add_stage_id_foreign_key_to_builds.rb b/db/migrate/20170703102400_add_stage_id_foreign_key_to_builds.rb new file mode 100644 index 00000000000..68b947583d3 --- /dev/null +++ b/db/migrate/20170703102400_add_stage_id_foreign_key_to_builds.rb @@ -0,0 +1,35 @@ +class AddStageIdForeignKeyToBuilds < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + unless index_exists?(:ci_builds, :stage_id) + add_concurrent_index(:ci_builds, :stage_id) + end + + unless foreign_key_exists?(:ci_builds, :stage_id) + add_concurrent_foreign_key(:ci_builds, :ci_stages, column: :stage_id, on_delete: :cascade) + end + end + + def down + if foreign_key_exists?(:ci_builds, :stage_id) + remove_foreign_key(:ci_builds, column: :stage_id) + end + + if index_exists?(:ci_builds, :stage_id) + remove_concurrent_index(:ci_builds, :stage_id) + end + end + + private + + def foreign_key_exists?(table, column) + foreign_keys(:ci_builds).any? do |key| + key.options[:column] == column.to_s + end + end +end diff --git a/db/post_migrate/20170621102400_add_stage_id_index_to_builds.rb b/db/post_migrate/20170621102400_add_stage_id_index_to_builds.rb index 7d6609b18bf..ac61b5c84a8 100644 --- a/db/post_migrate/20170621102400_add_stage_id_index_to_builds.rb +++ b/db/post_migrate/20170621102400_add_stage_id_index_to_builds.rb @@ -3,19 +3,15 @@ class AddStageIdIndexToBuilds < ActiveRecord::Migration DOWNTIME = false - disable_ddl_transaction! + ## + # Improved in 20170703102400_add_stage_id_foreign_key_to_builds.rb + # def up - unless index_exists?(:ci_builds, :stage_id) - add_concurrent_foreign_key(:ci_builds, :ci_stages, column: :stage_id, on_delete: :cascade) - add_concurrent_index(:ci_builds, :stage_id) - end + # noop end def down - if index_exists?(:ci_builds, :stage_id) - remove_foreign_key(:ci_builds, column: :stage_id) - remove_concurrent_index(:ci_builds, :stage_id) - end + # noop end end diff --git a/db/schema.rb b/db/schema.rb index 8c7440ee610..f12fdf903c5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170622162730) do +ActiveRecord::Schema.define(version: 20170703102400) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -374,9 +374,10 @@ ActiveRecord::Schema.define(version: 20170622162730) do t.string "encrypted_value_iv" t.integer "project_id", null: false t.boolean "protected", default: false, null: false + t.string "environment_scope", default: "*", null: false end - add_index "ci_variables", ["project_id"], name: "index_ci_variables_on_project_id", using: :btree + add_index "ci_variables", ["project_id", "key", "environment_scope"], name: "index_ci_variables_on_project_id_and_key_and_environment_scope", unique: true, using: :btree create_table "container_repositories", force: :cascade do |t| t.integer "project_id", null: false @@ -970,6 +971,7 @@ ActiveRecord::Schema.define(version: 20170622162730) do end add_index "pages_domains", ["domain"], name: "index_pages_domains_on_domain", unique: true, using: :btree + add_index "pages_domains", ["project_id"], name: "index_pages_domains_on_project_id", using: :btree create_table "personal_access_tokens", force: :cascade do |t| t.integer "user_id", null: false @@ -1019,6 +1021,7 @@ ActiveRecord::Schema.define(version: 20170622162730) do end add_index "project_group_links", ["group_id"], name: "index_project_group_links_on_group_id", using: :btree + add_index "project_group_links", ["project_id"], name: "index_project_group_links_on_project_id", using: :btree create_table "project_import_data", force: :cascade do |t| t.integer "project_id" @@ -1085,6 +1088,7 @@ ActiveRecord::Schema.define(version: 20170622162730) do t.string "import_jid" t.integer "cached_markdown_version" t.datetime "last_repository_updated_at" + t.string "ci_config_path" end add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree @@ -1526,48 +1530,75 @@ ActiveRecord::Schema.define(version: 20170622162730) do add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree add_index "web_hooks", ["type"], name: "index_web_hooks_on_type", using: :btree - add_foreign_key "boards", "projects" + add_foreign_key "boards", "projects", name: "fk_f15266b5f9", on_delete: :cascade add_foreign_key "chat_teams", "namespaces", on_delete: :cascade add_foreign_key "ci_builds", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_a2141b1522", on_delete: :nullify add_foreign_key "ci_builds", "ci_stages", column: "stage_id", name: "fk_3a9eaa254d", on_delete: :cascade + add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade add_foreign_key "ci_pipeline_schedules", "projects", name: "fk_8ead60fcc4", on_delete: :cascade add_foreign_key "ci_pipeline_schedules", "users", column: "owner_id", name: "fk_9ea99f58d2", on_delete: :nullify add_foreign_key "ci_pipelines", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_3d34ab2e06", on_delete: :nullify add_foreign_key "ci_pipelines", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_262d4c2d19", on_delete: :nullify + add_foreign_key "ci_pipelines", "projects", name: "fk_86635dbd80", on_delete: :cascade + add_foreign_key "ci_runner_projects", "projects", name: "fk_4478a6f1e4", on_delete: :cascade add_foreign_key "ci_stages", "ci_pipelines", column: "pipeline_id", name: "fk_fb57e6cc56", on_delete: :cascade add_foreign_key "ci_stages", "projects", name: "fk_2360681d1d", on_delete: :cascade add_foreign_key "ci_trigger_requests", "ci_triggers", column: "trigger_id", name: "fk_b8ec8b7245", on_delete: :cascade + add_foreign_key "ci_triggers", "projects", name: "fk_e3e63f966e", on_delete: :cascade add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade add_foreign_key "ci_variables", "projects", name: "fk_ada5eb64b3", on_delete: :cascade add_foreign_key "container_repositories", "projects" + add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade + add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade + add_foreign_key "environments", "projects", name: "fk_d1c8c1da6a", on_delete: :cascade + add_foreign_key "events", "projects", name: "fk_0434b48643", on_delete: :cascade + add_foreign_key "forked_project_links", "projects", column: "forked_to_project_id", name: "fk_434510edb0", on_delete: :cascade add_foreign_key "issue_assignees", "issues", name: "fk_b7d881734a", on_delete: :cascade add_foreign_key "issue_assignees", "users", name: "fk_5e0c8d9154", on_delete: :cascade add_foreign_key "issue_metrics", "issues", on_delete: :cascade + add_foreign_key "issues", "projects", name: "fk_899c8f3231", on_delete: :cascade add_foreign_key "label_priorities", "labels", on_delete: :cascade add_foreign_key "label_priorities", "projects", on_delete: :cascade add_foreign_key "labels", "namespaces", column: "group_id", on_delete: :cascade - add_foreign_key "lists", "boards" - add_foreign_key "lists", "labels" + add_foreign_key "labels", "projects", name: "fk_7de4989a69", on_delete: :cascade + add_foreign_key "lists", "boards", name: "fk_0d3f677137", on_delete: :cascade + add_foreign_key "lists", "labels", name: "fk_7a5553d60f", on_delete: :cascade add_foreign_key "merge_request_diff_files", "merge_request_diffs", on_delete: :cascade + add_foreign_key "merge_request_diffs", "merge_requests", name: "fk_8483f3258f", on_delete: :cascade add_foreign_key "merge_request_metrics", "ci_pipelines", column: "pipeline_id", on_delete: :cascade add_foreign_key "merge_request_metrics", "merge_requests", on_delete: :cascade + add_foreign_key "merge_requests", "projects", column: "target_project_id", name: "fk_a6963e8447", on_delete: :cascade add_foreign_key "merge_requests_closing_issues", "issues", on_delete: :cascade add_foreign_key "merge_requests_closing_issues", "merge_requests", on_delete: :cascade + add_foreign_key "milestones", "projects", name: "fk_9bd0a0c791", on_delete: :cascade + add_foreign_key "notes", "projects", name: "fk_99e097b079", on_delete: :cascade add_foreign_key "oauth_openid_requests", "oauth_access_grants", column: "access_grant_id", name: "fk_oauth_openid_requests_oauth_access_grants_access_grant_id" + add_foreign_key "pages_domains", "projects", name: "fk_ea2f6dfc6f", on_delete: :cascade add_foreign_key "personal_access_tokens", "users" add_foreign_key "project_authorizations", "projects", on_delete: :cascade add_foreign_key "project_authorizations", "users", on_delete: :cascade + add_foreign_key "project_features", "projects", name: "fk_18513d9b92", on_delete: :cascade + add_foreign_key "project_group_links", "projects", name: "fk_daa8cee94c", on_delete: :cascade + add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade add_foreign_key "project_statistics", "projects", on_delete: :cascade - add_foreign_key "protected_branch_merge_access_levels", "protected_branches" - add_foreign_key "protected_branch_push_access_levels", "protected_branches" + add_foreign_key "protected_branch_merge_access_levels", "protected_branches", name: "fk_8a3072ccb3", on_delete: :cascade + add_foreign_key "protected_branch_push_access_levels", "protected_branches", name: "fk_9ffc86a3d9", on_delete: :cascade + add_foreign_key "protected_branches", "projects", name: "fk_7a9c6d93e7", on_delete: :cascade add_foreign_key "protected_tag_create_access_levels", "namespaces", column: "group_id" add_foreign_key "protected_tag_create_access_levels", "protected_tags" add_foreign_key "protected_tag_create_access_levels", "users" + add_foreign_key "protected_tags", "projects", name: "fk_8e4af87648", on_delete: :cascade + add_foreign_key "releases", "projects", name: "fk_47fe2a0596", on_delete: :cascade + add_foreign_key "services", "projects", name: "fk_71cce407f9", on_delete: :cascade + add_foreign_key "snippets", "projects", name: "fk_be41fd4bb7", on_delete: :cascade add_foreign_key "subscriptions", "projects", on_delete: :cascade add_foreign_key "system_note_metadata", "notes", name: "fk_d83a918cb1", on_delete: :cascade add_foreign_key "timelogs", "issues", name: "fk_timelogs_issues_issue_id", on_delete: :cascade add_foreign_key "timelogs", "merge_requests", name: "fk_timelogs_merge_requests_merge_request_id", on_delete: :cascade + add_foreign_key "todos", "projects", name: "fk_45054f9c45", on_delete: :cascade add_foreign_key "trending_projects", "projects", on_delete: :cascade add_foreign_key "u2f_registrations", "users" + add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade add_foreign_key "web_hook_logs", "web_hooks", on_delete: :cascade + add_foreign_key "web_hooks", "projects", name: "fk_0c8ca6d9d1", on_delete: :cascade end diff --git a/doc/api/features.md b/doc/api/features.md index 89b8d3ac948..558869255cc 100644 --- a/doc/api/features.md +++ b/doc/api/features.md @@ -58,6 +58,10 @@ POST /features/:name | --------- | ---- | -------- | ----------- | | `name` | string | yes | Name of the feature to create or update | | `value` | integer/string | yes | `true` or `false` to enable/disable, or an integer for percentage of time | +| `feature_group` | string | no | A Feature group name | +| `user` | string | no | A GitLab username | + +Note that `feature_group` and `user` are mutually exclusive. ```bash curl --data "value=30" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/features/new_library diff --git a/doc/api/projects.md b/doc/api/projects.md index cc1bb3911c8..0d892c74d00 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -173,6 +173,164 @@ Parameters: ] ``` +### List a user's projects + +Get a list of visible projects for the given user. When accessed without authentication, only public projects are returned. + +``` +GET /users/:user_id/projects +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `user_id` | string | yes | The ID or username of the user | +| `archived` | boolean | no | Limit by archived status | +| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` | +| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` | +| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | +| `search` | string | no | Return list of projects matching the search criteria | +| `simple` | boolean | no | Return only the ID, URL, name, and path of each project | +| `owned` | boolean | no | Limit by projects owned by the current user | +| `membership` | boolean | no | Limit by projects that the current user is a member of | +| `starred` | boolean | no | Limit by projects starred by the current user | +| `statistics` | boolean | no | Include project statistics | +| `with_issues_enabled` | boolean | no | Limit by enabled issues feature | +| `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature | + +```json +[ + { + "id": 4, + "description": null, + "default_branch": "master", + "visibility": "private", + "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git", + "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git", + "web_url": "http://example.com/diaspora/diaspora-client", + "tag_list": [ + "example", + "disapora client" + ], + "owner": { + "id": 3, + "name": "Diaspora", + "created_at": "2013-09-30T13:46:02Z" + }, + "name": "Diaspora Client", + "name_with_namespace": "Diaspora / Diaspora Client", + "path": "diaspora-client", + "path_with_namespace": "diaspora/diaspora-client", + "issues_enabled": true, + "open_issues_count": 1, + "merge_requests_enabled": true, + "jobs_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "container_registry_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", + "creator_id": 3, + "namespace": { + "id": 3, + "name": "Diaspora", + "path": "diaspora", + "kind": "group", + "full_path": "diaspora" + }, + "import_status": "none", + "archived": false, + "avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png", + "shared_runners_enabled": true, + "forks_count": 0, + "star_count": 0, + "runners_token": "b8547b1dc37721d05889db52fa2f02", + "public_jobs": true, + "shared_with_groups": [], + "only_allow_merge_if_pipeline_succeeds": false, + "only_allow_merge_if_all_discussions_are_resolved": false, + "request_access_enabled": false, + "statistics": { + "commit_count": 37, + "storage_size": 1038090, + "repository_size": 1038090, + "lfs_objects_size": 0, + "job_artifacts_size": 0 + } + }, + { + "id": 6, + "description": null, + "default_branch": "master", + "visibility": "private", + "ssh_url_to_repo": "git@example.com:brightbox/puppet.git", + "http_url_to_repo": "http://example.com/brightbox/puppet.git", + "web_url": "http://example.com/brightbox/puppet", + "tag_list": [ + "example", + "puppet" + ], + "owner": { + "id": 4, + "name": "Brightbox", + "created_at": "2013-09-30T13:46:02Z" + }, + "name": "Puppet", + "name_with_namespace": "Brightbox / Puppet", + "path": "puppet", + "path_with_namespace": "brightbox/puppet", + "issues_enabled": true, + "open_issues_count": 1, + "merge_requests_enabled": true, + "jobs_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "container_registry_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", + "creator_id": 3, + "namespace": { + "id": 4, + "name": "Brightbox", + "path": "brightbox", + "kind": "group", + "full_path": "brightbox" + }, + "import_status": "none", + "import_error": null, + "permissions": { + "project_access": { + "access_level": 10, + "notification_level": 3 + }, + "group_access": { + "access_level": 50, + "notification_level": 3 + } + }, + "archived": false, + "avatar_url": null, + "shared_runners_enabled": true, + "forks_count": 0, + "star_count": 0, + "runners_token": "b8547b1dc37721d05889db52fa2f02", + "public_jobs": true, + "shared_with_groups": [], + "only_allow_merge_if_pipeline_succeeds": false, + "only_allow_merge_if_all_discussions_are_resolved": false, + "request_access_enabled": false, + "statistics": { + "commit_count": 12, + "storage_size": 2066080, + "repository_size": 2066080, + "lfs_objects_size": 0, + "job_artifacts_size": 0 + } + } +] +``` + ### Get single project Get a specific project. This endpoint can be accessed without authentication if @@ -336,7 +494,7 @@ Parameters: | `snippets_enabled` | boolean | no | Enable snippets for this project | | `container_registry_enabled` | boolean | no | Enable container registry for this project | | `shared_runners_enabled` | boolean | no | Enable shared runners for this project | -| `visibility` | String | no | See [project visibility level](#project-visibility-level) | +| `visibility` | string | no | See [project visibility level](#project-visibility-level) | | `import_url` | string | no | URL to import repository from | | `public_jobs` | boolean | no | If `true`, jobs can be viewed by non-project-members | | `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful jobs | @@ -346,6 +504,7 @@ Parameters: | `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project | | `avatar` | mixed | no | Image file for avatar of the project | | `printing_merge_request_link_enabled` | boolean | no | Show link to create/view merge request when pushing from the command line | +| `ci_config_path` | string | no | The path to CI config file | ### Create project for user @@ -382,6 +541,7 @@ Parameters: | `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project | | `avatar` | mixed | no | Image file for avatar of the project | | `printing_merge_request_link_enabled` | boolean | no | Show link to create/view merge request when pushing from the command line | +| `ci_config_path` | string | no | The path to CI config file | ### Edit project @@ -416,6 +576,7 @@ Parameters: | `request_access_enabled` | boolean | no | Allow users to request member access | | `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project | | `avatar` | mixed | no | Image file for avatar of the project | +| `ci_config_path` | string | no | The path to CI config file | ### Fork project diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md index 18ceb8f779e..1fc577561a0 100644 --- a/doc/api/repository_files.md +++ b/doc/api/repository_files.md @@ -61,7 +61,7 @@ POST /projects/:id/repository/files/:file_path ``` ```bash -curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/app%2Fprojectrb%2E?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20content&commit_message=create%20a%20new%20file' +curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fprojectrb%2E?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20content&commit_message=create%20a%20new%20file' ``` Example response: @@ -90,7 +90,7 @@ PUT /projects/:id/repository/files/:file_path ``` ```bash -curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20other%20content&commit_message=update%20file' +curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20other%20content&commit_message=update%20file' ``` Example response: @@ -129,7 +129,7 @@ DELETE /projects/:id/repository/files/:file_path ``` ```bash -curl --request DELETE --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file' +curl --request DELETE --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file' ``` Example response: diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index be4dea55c20..d3433594eb7 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -1,4 +1,4 @@ -# Using Docker Images +# Using Docker images GitLab CI in conjunction with [GitLab Runner](../runners/README.md) can use [Docker Engine](https://www.docker.com/) to test and build any application. @@ -17,14 +17,16 @@ can also run on your workstation. The added benefit is that you can test all the commands that we will explore later from your shell, rather than having to test them on a dedicated CI server. -## Register docker runner +## Register Docker Runner -To use GitLab Runner with docker you need to register a new runner to use the -`docker` executor: +To use GitLab Runner with Docker you need to [register a new Runner][register] +to use the `docker` executor. + +A one-line example can be seen below: ```bash -gitlab-ci-multi-runner register \ - --url "https://gitlab.com/" \ +sudo gitlab-runner register \ + --url "https://gitlab.example.com/" \ --registration-token "PROJECT_REGISTRATION_TOKEN" \ --description "docker-ruby-2.1" \ --executor "docker" \ @@ -33,26 +35,26 @@ gitlab-ci-multi-runner register \ --docker-mysql latest ``` -The registered runner will use the `ruby:2.1` docker image and will run two +The registered runner will use the `ruby:2.1` Docker image and will run two services, `postgres:latest` and `mysql:latest`, both of which will be accessible during the build process. ## What is an image -The `image` keyword is the name of the docker image the docker executor -will run to perform the CI tasks. +The `image` keyword is the name of the Docker image the Docker executor +will run to perform the CI tasks. -By default the executor will only pull images from [Docker Hub][hub], +By default, the executor will only pull images from [Docker Hub][hub], but this can be configured in the `gitlab-runner/config.toml` by setting -the [docker pull policy][] to allow using local images. +the [Docker pull policy][] to allow using local images. For more information about images and Docker Hub please read the [Docker Fundamentals][] documentation. ## What is a service -The `services` keyword defines just another docker image that is run during -your job and is linked to the docker image that the `image` keyword defines. +The `services` keyword defines just another Docker image that is run during +your job and is linked to the Docker image that the `image` keyword defines. This allows you to access the service image during build time. The service image can run any application, but the most common use case is to @@ -60,6 +62,11 @@ run a database container, eg. `mysql`. It's easier and faster to use an existing image and run it as an additional container than install `mysql` every time the project is built. +You are not limited to have only database services. You can add as many +services you need to `.gitlab-ci.yml` or manually modify `config.toml`. +Any image found at [Docker Hub][hub] or your private Container Registry can be +used as a service. + You can see some widely used services examples in the relevant documentation of [CI services examples](../services/README.md). @@ -73,22 +80,49 @@ then be used to create a container that is linked to the job container. The service container for MySQL will be accessible under the hostname `mysql`. So, in order to access your database service you have to connect to the host -named `mysql` instead of a socket or `localhost`. +named `mysql` instead of a socket or `localhost`. Read more in [accessing the +services](#accessing-the-services). -## Overwrite image and services +### Accessing the services -See [How to use other images as services](#how-to-use-other-images-as-services). +Let's say that you need a Wordpress instance to test some API integration with +your application. -## How to use other images as services +You can then use for example the [tutum/wordpress][] image in your +`.gitlab-ci.yml`: -You are not limited to have only database services. You can add as many -services you need to `.gitlab-ci.yml` or manually modify `config.toml`. -Any image found at [Docker Hub][hub] can be used as a service. +```yaml +services: +- tutum/wordpress:latest +``` + +If you don't [specify a service alias](#available-settings-for-services-entry), +when the job is run, `tutum/wordpress` will be started and you will have +access to it from your build container under two hostnames to choose from: -## Define image and services from `.gitlab-ci.yml` +- `tutum-wordpress` +- `tutum__wordpress` + +>**Note:** +Hostnames with underscores are not RFC valid and may cause problems in 3rd party +applications. + +The default aliases for the service's hostname are created from its image name +following these rules: + +- Everything after the colon (`:`) is stripped +- Slash (`/`) is replaced with double underscores (`__`) and the primary alias + is created +- Slash (`/`) is replaced with a single dash (`-`) and the secondary alias is + created (requires GitLab Runner v1.1.0 or higher) + +To override the default behavior, you can +[specify a service alias](#available-settings-for-services-entry). + +## Define `image` and `services` from `.gitlab-ci.yml` You can simply define an image that will be used for all jobs and a list of -services that you want to use during build time. +services that you want to use during build time: ```yaml image: ruby:2.2 @@ -125,6 +159,203 @@ test:2.2: - bundle exec rake spec ``` +Or you can pass some [extended configuration options](#extended-docker-configuration-options) +for `image` and `services`: + +```yaml +image: + name: ruby:2.2 + entrypoint: ["/bin/bash"] + +services: +- name: my-postgres:9.4 + alias: db-postgres + entrypoint: ["/usr/local/bin/db-postgres"] + command: ["start"] + +before_script: +- bundle install + +test: + script: + - bundle exec rake spec +``` + +## Extended Docker configuration options + +> **Note:** +This feature requires GitLab 9.4 and GitLab Runner 9.4 or higher. + +When configuring the `image` or `services` entries, you can use a string or a map as +options: + +- when using a string as an option, it must be the full name of the image to use + (including the Registry part if you want to download the image from a Registry + other than Docker Hub) +- when using a map as an option, then it must contain at least the `name` + option, which is the same name of the image as used for the string setting + +For example, the following two definitions are equal: + +1. Using a string as an option to `image` and `services`: + + ```yaml + image: "registry.example.com/my/image:latest" + + services: + - postgresql:9.4 + - redis:latest + ``` + +1. Using a map as an option to `image` and `services`. The use of `image:name` is + required: + + ```yaml + image: + name: "registry.example.com/my/image:latest" + + services: + - name: postgresql:9.4 + - name: redis:latest + ``` + +### Available settings for `image` + +> **Note:** +This feature requires GitLab 9.4 and GitLab Runner 9.4 or higher. + +| Setting | Required | Description | +|------------|----------|-------------| +| `name` | yes, when used with any other option | Full name of the image that should be used. It should contain the Registry part if needed. | +| `entrypoint` | no | Command or script that should be executed as the container's entrypoint. It will be translated to Docker's `--entrypoint` option while creating the container. The syntax is similar to [`Dockerfile`'s `ENTRYPOINT`][entrypoint] directive, where each shell token is a separate string in the array. | + +### Available settings for `services` + +> **Note:** +This feature requires GitLab 9.4 and GitLab Runner 9.4 or higher. + +| Setting | Required | Description | +|------------|----------|-------------| +| `name` | yes, when used with any other option | Full name of the image that should be used. It should contain the Registry part if needed. | +| `entrypoint` | no | Command or script that should be executed as the container's entrypoint. It will be translated to Docker's `--entrypoint` option while creating the container. The syntax is similar to [`Dockerfile`'s `ENTRYPOINT`][entrypoint] directive, where each shell token is a separate string in the array. | +| `command` | no | Command or script that should be used as the container's command. It will be translated to arguments passed to Docker after the image's name. The syntax is similar to [`Dockerfile`'s `CMD`][cmd] directive, where each shell token is a separate string in the array. | +| `alias` | no | Additional alias that can be used to access the service from the job's container. Read [Accessing the services](#accessing-the-services) for more information. | + +### Starting multiple services from the same image + +Before the new extended Docker configuration options, the following configuration +would not work properly: + +```yaml +services: +- mysql:latest +- mysql:latest +``` + +The Runner would start two containers using the `mysql:latest` image, but both +of them would be added to the job's container with the `mysql` alias based on +the [default hostname naming](#accessing-the-services). This would end with one +of the services not being accessible. + +After the new extended Docker configuration options, the above example would +look like: + +```yaml +services: +- name: mysql:latest + alias: mysql-1 +- name: mysql:latest + alias: mysql-2 +``` + +The Runner will still start two containers using the `mysql:latest` image, +but now each of them will also be accessible with the alias configured +in `.gitlab-ci.yml` file. + +### Setting a command for the service + +Let's assume you have a `super/sql:latest` image with some SQL database +inside it and you would like to use it as a service for your job. Let's also +assume that this image doesn't start the database process while starting +the container and the user needs to manually use `/usr/bin/super-sql run` as +a command to start the database. + +Before the new extended Docker configuration options, you would need to create +your own image based on the `super/sql:latest` image, add the default command, +and then use it in job's configuration, like: + +```Dockerfile +# my-super-sql:latest image's Dockerfile + +FROM super/sql:latest +CMD ["/usr/bin/super-sql", "run"] +``` + +```yaml +# .gitlab-ci.yml + +services: +- my-super-sql:latest +``` + +After the new extended Docker configuration options, you can now simply +set a `command` in `.gitlab-ci.yml`, like: + +```yaml +# .gitlab-ci.yml + +services: +- name: super/sql:latest + command: ["/usr/bin/super-sql", "run"] +``` + +As you can see, the syntax of `command` is similar to [Dockerfile's `CMD`][cmd]. + +### Overriding the entrypoint of an image + +Let's assume you have a `super/sql:experimental` image with some SQL database +inside it and you would like to use it as a base image for your job because you +want to execute some tests with this database binary. Let's also assume that +this image is configured with `/usr/bin/super-sql run` as an entrypoint. That +means, that when starting the container without additional options, it will run +the database's process, while Runner expects that the image will have no +entrypoint or at least will start with a shell as its entrypoint. + +Previously we would need to create our own image based on the +`super/sql:experimental` image, set the entrypoint to a shell, and then use +it in job's configuration, e.g.: + +Before the new extended Docker configuration options, you would need to create +your own image based on the `super/sql:experimental` image, set the entrypoint +to a shell and then use it in job's configuration, like: + +```Dockerfile +# my-super-sql:experimental image's Dockerfile + +FROM super/sql:experimental +ENTRYPOINT ["/bin/sh"] +``` + +```yaml +# .gitlab-ci.yml + +image: my-super-sql:experimental +``` + +After the new extended Docker configuration options, you can now simply +set an `entrypoint` in `.gitlab-ci.yml`, like: + +```yaml +# .gitlab-ci.yml + +image: + name: super/sql:experimental + entrypoint: ["/bin/sh"] +``` + +As you can see the syntax of `entrypoint` is similar to +[Dockerfile's `ENTRYPOINT`][entrypoint]. + ## Define image and services in `config.toml` Look for the `[runners.docker]` section: @@ -138,7 +369,7 @@ Look for the `[runners.docker]` section: The image and services defined this way will be added to all job run by that runner. -## Define an image from a private Docker registry +## Define an image from a private Container Registry > **Notes:** - This feature requires GitLab Runner **1.8** or higher @@ -193,44 +424,6 @@ To configure access for `registry.example.com`, follow these steps: You can add configuration for as many registries as you want, adding more registries to the `"auths"` hash as described above. -## Accessing the services - -Let's say that you need a Wordpress instance to test some API integration with -your application. - -You can then use for example the [tutum/wordpress][] image in your -`.gitlab-ci.yml`: - -```yaml -services: -- tutum/wordpress:latest -``` - -When the job is run, `tutum/wordpress` will be started and you will have -access to it from your build container under the hostnames `tutum-wordpress` -(requires GitLab Runner v1.1.0 or newer) and `tutum__wordpress`. - -When using a private registry, the image name also includes a hostname and port -of the registry. - -```yaml -services: -- docker.example.com:5000/wordpress:latest -``` - -The service hostname will also include the registry hostname. Service will be -available under hostnames `docker.example.com-wordpress` (requires GitLab Runner v1.1.0 or newer) -and `docker.example.com__wordpress`. - -*Note: hostname with underscores is not RFC valid and may cause problems in 3rd party applications.* - -The alias hostnames for the service are made from the image name following these -rules: - -1. Everything after `:` is stripped -2. Slash (`/`) is replaced with double underscores (`__`) - primary alias -3. Slash (`/`) is replaced with dash (`-`) - secondary alias, requires GitLab Runner v1.1.0 or newer - ## Configuring services Many services accept environment variables which allow you to easily change @@ -257,7 +450,7 @@ See the specific documentation for ## How Docker integration works -Below is a high level overview of the steps performed by docker during job +Below is a high level overview of the steps performed by Docker during job time. 1. Create any service container: `mysql`, `postgresql`, `mongodb`, `redis`. @@ -274,7 +467,7 @@ time. ## How to debug a job locally *Note: The following commands are run without root privileges. You should be -able to run docker with your regular user account.* +able to run Docker with your regular user account.* First start with creating a file named `build_script`: @@ -334,3 +527,6 @@ creation. [mysql-hub]: https://hub.docker.com/r/_/mysql/ [runner-priv-reg]: http://docs.gitlab.com/runner/configuration/advanced-configuration.html#using-a-private-container-registry [secret variable]: ../variables/README.md#secret-variables +[entrypoint]: https://docs.docker.com/engine/reference/builder/#entrypoint +[cmd]: https://docs.docker.com/engine/reference/builder/#cmd +[register]: https://docs.gitlab.com/runner/register/ diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index d1f9881e51b..3501aae75ec 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -37,9 +37,10 @@ future GitLab releases.** |-------------------------------- |--------|--------|-------------| | **CI** | all | 0.4 | Mark that job is executed in CI environment | | **CI_COMMIT_REF_NAME** | 9.0 | all | The branch or tag name for which project is built | -| **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. | +| **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. | | **CI_COMMIT_SHA** | 9.0 | all | The commit revision for which project is built | | **CI_COMMIT_TAG** | 9.0 | 0.5 | The commit tag name. Present only when building tags. | +| **CI_CONFIG_PATH** | 9.4 | 0.5 | The path to CI config file. Defaults to `.gitlab-ci.yml` | | **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled | | **CI_ENVIRONMENT_NAME** | 8.15 | all | The name of the environment for this job | | **CI_ENVIRONMENT_SLUG** | 8.15 | all | A simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, etc. | @@ -159,7 +160,7 @@ Secret variables can be added by going to your project's Once you set them, they will be available for all subsequent pipelines. -## Protected secret variables +### Protected secret variables >**Notes:** This feature requires GitLab 9.3 or higher. @@ -425,10 +426,11 @@ export CI_REGISTRY_PASSWORD="longalfanumstring" ``` [ce-13784]: https://gitlab.com/gitlab-org/gitlab-ce/issues/13784 -[runner]: https://docs.gitlab.com/runner/ -[triggered]: ../triggers/README.md -[triggers]: ../triggers/README.md#pass-job-variables-to-a-trigger +[eep]: https://about.gitlab.com/gitlab-ee/ "Available only in GitLab Enterprise Edition Premium" +[envs]: ../environments.md [protected branches]: ../../user/project/protected_branches.md [protected tags]: ../../user/project/protected_tags.md +[runner]: https://docs.gitlab.com/runner/ [shellexecutors]: https://docs.gitlab.com/runner/executors/ -[eep]: https://about.gitlab.com/gitlab-ee/ "Available only in GitLab Enterprise Edition Premium" +[triggered]: ../triggers/README.md +[triggers]: ../triggers/README.md#pass-job-variables-to-a-trigger diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 8a0662db6fd..724843a4d56 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -306,6 +306,53 @@ cache: untracked: true ``` +### cache:policy + +> Introduced in GitLab 9.4. + +The default behaviour of a caching job is to download the files at the start of +execution, and to re-upload them at the end. This allows any changes made by the +job to be persisted for future runs, and is known as the `pull-push` cache +policy. + +If you know the job doesn't alter the cached files, you can skip the upload step +by setting `policy: pull` in the job specification. Typically, this would be +twinned with an ordinary cache job at an earlier stage to ensure the cache +is updated from time to time: + +```yaml +stages: + - setup + - test + +prepare: + stage: setup + cache: + key: gems + paths: + - vendor/bundle + script: + - bundle install --deployment + +rspec: + stage: test + cache: + key: gems + paths: + - vendor/bundle + policy: pull + script: + - bundle exec rspec ... +``` + +This helps to speed up job execution and reduce load on the cache server, +especially when you have a large number of cache-using jobs executing in +parallel. + +Additionally, if you have a job that unconditionally recreates the cache without +reference to its previous contents, you can use `policy: push` in that job to +skip the download step. + ## Jobs `.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job diff --git a/doc/downgrade_ee_to_ce/README.md b/doc/downgrade_ee_to_ce/README.md index fe4b6d73771..75bae324585 100644 --- a/doc/downgrade_ee_to_ce/README.md +++ b/doc/downgrade_ee_to_ce/README.md @@ -46,6 +46,19 @@ $ sudo gitlab-rails runner "Service.where(type: ['JenkinsService', 'JenkinsDepre $ bundle exec rails runner "Service.where(type: ['JenkinsService', 'JenkinsDeprecatedService']).delete_all" production ``` +### Secret variables environment scopes + +If you're using this feature and there are variables sharing the same +key, but they have different scopes in a project, then you might want to +revisit the environment scope setting for those variables. + +In CE, environment scopes are completely ignored, therefore you could +accidentally get a variable which you're not expecting for a particular +environment. Make sure that you have the right variables in this case. + +Data is completely preserved, so you could always upgrade back to EE and +restore the behavior if you leave it alone. + ## Downgrade to CE After performing the above mentioned steps, you are now ready to downgrade your diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md index 9a171d34671..37e9b3101ca 100644 --- a/doc/install/database_mysql.md +++ b/doc/install/database_mysql.md @@ -43,7 +43,7 @@ mysql> SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda, innodb_ mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_general_ci`; # Grant the GitLab user necessary permissions on the database -mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost'; +mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES, TRIGGER ON `gitlabhq_production`.* TO 'git'@'localhost'; # Quit the database session mysql> \q diff --git a/doc/install/kubernetes/gitlab_runner_chart.md b/doc/install/kubernetes/gitlab_runner_chart.md index b8bc0795f2e..515b2841d08 100644 --- a/doc/install/kubernetes/gitlab_runner_chart.md +++ b/doc/install/kubernetes/gitlab_runner_chart.md @@ -54,6 +54,13 @@ gitlabURL: http://gitlab.your-domain.com/ ## runnerRegistrationToken: "" +## Set the certsSecretName in order to pass custom certficates for GitLab Runner to use +## Provide resource name for a Kubernetes Secret Object in the same namespace, +## this is used to populate the /etc/gitlab-runner/certs directory +## ref: https://docs.gitlab.com/runner/configuration/tls-self-signed.html#supported-options-for-self-signed-certificates +## +#certsSecretName: + ## Configure the maximum number of concurrent jobs ## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section ## @@ -135,6 +142,52 @@ runners: privileged: true ``` +### Providing a custom certificate for accessing GitLab + +You can provide a [Kubernetes Secret](https://kubernetes.io/docs/concepts/configuration/secret/) +to the GitLab Runner Helm Chart, which will be used to populate the container's +`/etc/gitlab-runner/certs` directory. + +Each key name in the Secret will be used as a filename in the directory, with the +file content being the value associated with the key. + +More information on how GitLab Runner uses these certificates can be found in the +[Runner Documentation](https://docs.gitlab.com/runner/configuration/tls-self-signed.html#supported-options-for-self-signed-certificates). + + - The key/file name used should be in the format `<gitlab-hostname>.crt`. For example: `gitlab.your-domain.com.crt`. + - Any intermediate certificates need to be concatenated to your server certificate in the same file. + - The hostname used should be the one the certificate is registered for. + +The GitLab Runner Helm Chart does not create a secret for you. In order to create +the secret, you can prepare your certificate on you local machine, and then run +the `kubectl create secret` command from the directory with the certificate + +```bash +kubectl + --namespace <NAMESPACE> + create secret generic <SECRET_NAME> + --from-file=<CERTFICATE_FILENAME> +``` + +- `<NAMESPACE>` is the Kubernetes namespace where you want to install the GitLab Runner. +- `<SECRET_NAME>` is the Kubernetes Secret resource name. For example: `gitlab-domain-cert` +- `<CERTFICATE_FILENAME>` is the filename for the certificate in your current directory that will be imported into the secret + +You then need to provide the secret's name to the GitLab Runner chart. + +Add the following to your `values.yaml` + +```yaml +## Set the certsSecretName in order to pass custom certficates for GitLab Runner to use +## Provide resource name for a Kubernetes Secret Object in the same namespace, +## this is used to populate the /etc/gitlab-runner/certs directory +## ref: https://docs.gitlab.com/runner/configuration/tls-self-signed.html#supported-options-for-self-signed-certificates +## +certsSecretName: <SECRET NAME> +``` + +- `<SECRET_NAME>` is the Kubernetes Secret resource name. For example: `gitlab-domain-cert` + ## Installing GitLab Runner using the Helm Chart Once you [have configured](#configuration) GitLab Runner in your `values.yml` file, diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md index 265c891cf83..2dd9b33273c 100644 --- a/doc/integration/external-issue-tracker.md +++ b/doc/integration/external-issue-tracker.md @@ -8,6 +8,9 @@ you to do the following: issue index of the external tracker - clicking **New issue** on the project dashboard creates a new issue on the external tracker +- you can reference these external issues inside GitLab interface + (merge requests, commits, comments) and they will be automatically converted + into links ## Configuration diff --git a/doc/update/8.9-to-8.10.md b/doc/update/8.9-to-8.10.md index d6b2f11d49a..42132f690d8 100644 --- a/doc/update/8.9-to-8.10.md +++ b/doc/update/8.9-to-8.10.md @@ -156,7 +156,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/9.1-to-9.2.md b/doc/update/9.1-to-9.2.md index e7d97fde14e..225a4dcc924 100644 --- a/doc/update/9.1-to-9.2.md +++ b/doc/update/9.1-to-9.2.md @@ -70,7 +70,27 @@ curl --location https://yarnpkg.com/install.sh | bash - More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install). -### 5. Get latest code +### 5. Update Go + +NOTE: GitLab 9.2 and higher only supports Go 1.8.3 and dropped support for Go +1.5.x through 1.7.x. Be sure to upgrade your installation if necessary. + +You can check which version you are running with `go version`. + +Download and install Go: + +```bash +# Remove former Go installation folder +sudo rm -rf /usr/local/go + +curl --remote-name --progress https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz +echo '1862f4c3d3907e59b04a757cfda0ea7aa9ef39274af99a784f5be843c80c6772 go1.8.3.linux-amd64.tar.gz' | shasum -a256 -c - && \ + sudo tar -C /usr/local -xzf go1.8.3.linux-amd64.tar.gz +sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/ +rm go1.8.3.linux-amd64.tar.gz +``` + +### 6. Get latest code ```bash cd /home/git/gitlab @@ -97,7 +117,7 @@ cd /home/git/gitlab sudo -u git -H git checkout 9-2-stable-ee ``` -### 6. Update gitlab-shell +### 7. Update gitlab-shell ```bash cd /home/git/gitlab-shell @@ -107,11 +127,10 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION) sudo -u git -H bin/compile ``` -### 7. Update gitlab-workhorse +### 8. Update gitlab-workhorse -Install and compile gitlab-workhorse. This requires -[Go 1.8](https://golang.org/dl). Go (at least 1.5) should already be on your system from -GitLab 8.1 and shall be upgraded if necessary. Please note that starting in Gitlab 9.3, only Go 1.8.3 and above will be supported. GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/). +Install and compile 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. @@ -123,7 +142,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION) sudo -u git -H make ``` -### 8. Update configuration files +### 9. Update configuration files #### New configuration options for `gitlab.yml` @@ -197,7 +216,7 @@ For Ubuntu 16.04.1 LTS: sudo systemctl daemon-reload ``` -### 9. Install libs, migrations, etc. +### 10. Install libs, migrations, etc. ```bash cd /home/git/gitlab @@ -223,7 +242,7 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production **MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md). -### 10. Optional: install Gitaly +### 11. Optional: install Gitaly Gitaly is still an optional component of GitLab. If you want to save time during your 9.2 upgrade **you can skip this step**. @@ -240,14 +259,14 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION) sudo -u git -H make ``` -### 11. Start application +### 12. Start application ```bash sudo service gitlab start sudo service nginx restart ``` -### 12. Check application status +### 13. Check application status Check if GitLab and its environment are configured correctly: diff --git a/doc/update/9.2-to-9.3.md b/doc/update/9.2-to-9.3.md index 8fbcc892fd5..097b996ec31 100644 --- a/doc/update/9.2-to-9.3.md +++ b/doc/update/9.2-to-9.3.md @@ -72,8 +72,8 @@ More information can be found on the [yarn website](https://yarnpkg.com/en/docs/ ### 5. Update Go -NOTE: GitLab 9.3 and higher only supports Go 1.8.3 and dropped support for Go 1.5.x through 1.7.x. Be -sure to upgrade your installation if necessary +NOTE: GitLab 9.2 and higher only supports Go 1.8.3 and dropped support for Go +1.5.x through 1.7.x. Be sure to upgrade your installation if necessary. You can check which version you are running with `go version`. @@ -129,9 +129,8 @@ sudo -u git -H bin/compile ### 8. 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. GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/). +Install and compile 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. @@ -157,7 +156,16 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION) sudo -u git -H make ``` -### 10. Update configuration files +### 10. Update MySQL permissions + +If you are using MySQL you need to grant the GitLab user the necessary +permissions on the database: + +```bash +mysql -u root -p -e "GRANT TRIGGER ON \`gitlabhq_production\`.* TO 'git'@'localhost';" +``` + +### 11. Update configuration files #### New configuration options for `gitlab.yml` @@ -231,7 +239,7 @@ For Ubuntu 16.04.1 LTS: sudo systemctl daemon-reload ``` -### 11. Install libs, migrations, etc. +### 12. Install libs, migrations, etc. ```bash cd /home/git/gitlab @@ -257,14 +265,14 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production **MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md). -### 12. Start application +### 13. Start application ```bash sudo service gitlab start sudo service nginx restart ``` -### 13. Check application status +### 14. Check application status Check if GitLab and its environment are configured correctly: diff --git a/doc/user/project/img/issue_board.png b/doc/user/project/img/issue_board.png Binary files differindex b636cb294b8..cf7f519f783 100644 --- a/doc/user/project/img/issue_board.png +++ b/doc/user/project/img/issue_board.png diff --git a/doc/user/project/img/issue_board_add_list.png b/doc/user/project/img/issue_board_add_list.png Binary files differindex cdfc466d23f..973d9f7cde4 100644 --- a/doc/user/project/img/issue_board_add_list.png +++ b/doc/user/project/img/issue_board_add_list.png diff --git a/doc/user/project/img/issue_board_move_issue_card_list.png b/doc/user/project/img/issue_board_move_issue_card_list.png Binary files differnew file mode 100644 index 00000000000..c6b17ada40e --- /dev/null +++ b/doc/user/project/img/issue_board_move_issue_card_list.png diff --git a/doc/user/project/img/issue_board_welcome_message.png b/doc/user/project/img/issue_board_welcome_message.png Binary files differindex 5318e6ea4a9..127b9b08cc7 100644 --- a/doc/user/project/img/issue_board_welcome_message.png +++ b/doc/user/project/img/issue_board_welcome_message.png diff --git a/doc/user/project/img/issue_boards_add_issues_modal.png b/doc/user/project/img/issue_boards_add_issues_modal.png Binary files differindex 33049dce74f..bedaf724a15 100644 --- a/doc/user/project/img/issue_boards_add_issues_modal.png +++ b/doc/user/project/img/issue_boards_add_issues_modal.png diff --git a/doc/user/project/integrations/bugzilla.md b/doc/user/project/integrations/bugzilla.md index 0b219e84478..6a040516231 100644 --- a/doc/user/project/integrations/bugzilla.md +++ b/doc/user/project/integrations/bugzilla.md @@ -16,3 +16,14 @@ Once you have configured and enabled Bugzilla: - the **Issues** link on the GitLab project pages takes you to the appropriate Bugzilla product page - clicking **New issue** on the project dashboard takes you to Bugzilla for entering a new issue + +## Referencing issues in Bugzilla + +Issues in Bugzilla can be referenced in two alternative ways: +1. `#<ID>` where `<ID>` is a number (example `#143`) +2. `<PROJECT>-<ID>` where `<PROJECT>` starts with a capital letter which is + then followed by capital letters, numbers or underscores, and `<ID>` is + a number (example `API_32-143`). + +Please note that `<PROJECT>` part is ignored and links always point to the +address specified in `issues_url`. diff --git a/doc/user/project/integrations/kubernetes.md b/doc/user/project/integrations/kubernetes.md index 73fa83d72a8..bfe2672e098 100644 --- a/doc/user/project/integrations/kubernetes.md +++ b/doc/user/project/integrations/kubernetes.md @@ -55,6 +55,7 @@ GitLab CI build environment: - `KUBE_CA_PEM_FILE` - only present if a custom CA bundle was specified. Path to a file containing PEM data. - `KUBE_CA_PEM` (deprecated)- only if a custom CA bundle was specified. Raw PEM data. +- `KUBECONFIG` - Path to a file containing kubeconfig for this deployment. CA bundle would be embedded if specified. ## Web terminals diff --git a/doc/user/project/integrations/redmine.md b/doc/user/project/integrations/redmine.md index 89c0312d3c2..8026f1f57bc 100644 --- a/doc/user/project/integrations/redmine.md +++ b/doc/user/project/integrations/redmine.md @@ -21,3 +21,14 @@ Once you have configured and enabled Redmine: As an example, below is a configuration for a project named gitlab-ci. ![Redmine configuration](img/redmine_configuration.png) + +## Referencing issues in Redmine + +Issues in Redmine can be referenced in two alternative ways: +1. `#<ID>` where `<ID>` is a number (example `#143`) +2. `<PROJECT>-<ID>` where `<PROJECT>` starts with a capital letter which is + then followed by capital letters, numbers or underscores, and `<ID>` is + a number (example `API_32-143`). + +Please note that `<PROJECT>` part is ignored and links always point to the +address specified in `issues_url`. diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index ebea7062ecb..e2cc67726e0 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -1,8 +1,7 @@ -# Issue board +# Issue Board ->**Notes:** -- [Introduced][ce-5554] in GitLab 8.11. -- The Backlog column was replaced by the **Add issues** button in GitLab 8.17. +>**Note:** +[Introduced][ce-5554] in [GitLab 8.11](https://about.gitlab.com/2016/08/22/gitlab-8-11-released/#issue-board). The GitLab Issue Board is a software project management tool used to plan, organize, and visualize a workflow for a feature or product release. @@ -15,12 +14,65 @@ Other interesting links: ## Overview -The Issue Board builds on GitLab's existing issue tracking functionality and +The Issue Board builds on GitLab's existing +[issue tracking functionality](issues/index.md#issue-tracker) and leverages the power of [labels] by utilizing them as lists of the scrum board. -With the Issue Board you can have a different view of your issues while also +With the Issue Board you can have a different view of your issues while maintaining the same filtering and sorting abilities you see across the -issue tracker. +issue tracker. An Issue Board is based on its project's label structure, therefore, it +applies the same descriptive labels to indicate placement on the board, keeping +consistency throughout the entire development lifecycle. + +An Issue Board shows you what issues your team is working on, who is assigned to each, +and where in the workflow those issues are. + +You create issues, host code, perform reviews, build, test, +and deploy from one single platform. Issue Boards help you to visualize +and manage the entire process _in_ GitLab. + +With [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards), available +only in [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/), +you go even further, as you can not only keep yourself and your project +organized from a broader perspective with one Issue Board per project, +but also allow your team members to organize their own workflow by creating +multiple Issue Boards within the same project. + +## Use cases + +GitLab Workflow allows you to discuss proposals in issues, categorize them +with labels, and from there organize and prioritize them with Issue Boards. + +For example, let's consider this simplified development workflow: + +1. You have a repository hosting your app's codebase +and your team actively contributing to code +1. Your **backend** team starts working a new +implementation, gathers feedback and approval, and pass it over to **frontend** +1. When frontend is complete, the new feature is deployed to **staging** to be tested +1. When successful, it is deployed to **production** + +If we have the labels "**backend**", "**frontend**", "**staging**", and +"**production**", and an Issue Board with a list for each, we can: + +- Visualize the entire flow of implementations since the +beginning of the development lifecycle until deployed to production +- Prioritize the issues in a list by moving them vertically +- Move issues between lists to organize them according to the labels you've set +- Add multiple issues to lists in the board by selecting one or more existing issues + +![issue card moving](img/issue_board_move_issue_card_list.png) + +> **Notes:** +> +>- For a broader use case, please check the blog post +[GitLab Workflow, an Overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/#gitlab-workflow-use-case-scenario). +> +>- For a real use case, please check why +[Codepen decided to adopt Issue Boards](https://about.gitlab.com/2017/01/27/codepen-welcome-to-gitlab/#project-management-everything-in-one-place) +to improve their workflow with [multiple boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards). + +## Issue Board terminology Below is a table of the definitions used for GitLab's Issue Board. @@ -57,7 +109,7 @@ In short, here's a list of actions you can take in an Issue Board: If you are not able to perform one or more of the things above, make sure you have the right [permissions](#permissions). -## First time using the issue board +## First time using the Issue Board The first time you navigate to your Issue Board, you will be presented with a default list (**Done**) and a welcoming message that gives @@ -98,7 +150,7 @@ list view that is removed. You can always add it back later if you need. ## Adding issues to a list You can add issues to a list by clicking the **Add issues** button that is -present in the upper right corner of the issue board. This will open up a modal +present in the upper right corner of the Issue Board. This will open up a modal window where you can see all the issues that do not belong to any list. Select one or more issues by clicking on the cards and then click **Add issues** diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md index fe87e6f9495..e55e2aea023 100644 --- a/doc/user/project/issues/index.md +++ b/doc/user/project/issues/index.md @@ -1,4 +1,4 @@ -# Issues documentation +# Issues The GitLab Issue Tracker is an advanced and complete tool for tracking the evolution of a new idea or the process diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md index a992a348c0f..3ff5a08d72c 100644 --- a/doc/user/project/pipelines/settings.md +++ b/doc/user/project/pipelines/settings.md @@ -27,6 +27,22 @@ The default value is 60 minutes. Decrease the time limit if you want to impose a hard limit on your jobs' running time or increase it otherwise. In any case, if the job surpasses the threshold, it is marked as failed. +## Custom CI config path + +> - [Introduced][ce-12509] in GitLab 9.4. + +By default we look for the `.gitlab-ci.yml` file in the project's root +directory. If you require a different location **within** the repository, +you can set a custom filepath that will be used to lookup the config file, +this filepath should be **relative** to the root. + +Here are some valid examples: + +> * .gitlab-ci.yml +> * .my-custom-file.yml +> * my/path/.gitlab-ci.yml +> * my/path/.my-custom-file.yml + ## Test coverage parsing If you use test coverage in your code, GitLab can capture its output in the @@ -59,8 +75,8 @@ pipelines** checkbox and save the changes. > [Introduced][ce-9362] in GitLab 9.1. -If you want to auto-cancel all pending non-HEAD pipelines on branch, when -new pipeline will be created (after your git push or manually from UI), +If you want to auto-cancel all pending non-HEAD pipelines on branch, when +new pipeline will be created (after your git push or manually from UI), check **Auto-cancel pending pipelines** checkbox and save the changes. ## Badges @@ -115,3 +131,4 @@ into your `README.md`: [var]: ../../../ci/yaml/README.md#git-strategy [coverage report]: #test-coverage-parsing [ce-9362]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9362 +[ce-12509]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12509 diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md index e10ccc4fc46..ea28968fbb2 100644 --- a/doc/workflow/gitlab_flow.md +++ b/doc/workflow/gitlab_flow.md @@ -300,7 +300,7 @@ If there are no merge conflicts and the feature branches are short lived the ris If there are merge conflicts you merge the master branch into the feature branch and the CI server will rerun the tests. If you have long lived feature branches that last for more than a few days you should make your issues smaller. -## Working wih feature branches +## Working with feature branches ![Shell output showing git pull output](git_pull.png) diff --git a/features/dashboard/new_project.feature b/features/dashboard/new_project.feature deleted file mode 100644 index 046e2815d4e..00000000000 --- a/features/dashboard/new_project.feature +++ /dev/null @@ -1,30 +0,0 @@ -@dashboard -Feature: New Project -Background: - Given I sign in as a user - And I own project "Shop" - And I visit dashboard page - And I click "New project" link - - @javascript - Scenario: I should see New Projects page - Then I see "New Project" page - Then I see all possible import options - - @javascript - Scenario: I should see instructions on how to import from Git URL - Given I see "New Project" page - When I click on "Repo by URL" - Then I see instructions on how to import from Git URL - - @javascript - Scenario: I should see instructions on how to import from GitHub - Given I see "New Project" page - When I click on "Import project from GitHub" - Then I am redirected to the GitHub import page - - @javascript - Scenario: I should see Google Code import page - Given I see "New Project" page - When I click on "Google Code" - Then I redirected to Google Code import page diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index 472ec9544f3..59a625056d6 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -131,16 +131,6 @@ Feature: Project Source Browse Files Then I can see the new text file @javascript - Scenario: If I enter an illegal file name I see an error message - Given I click on "New file" link in repo - And I fill the new file name with an illegal name - And I edit code - And I fill the commit message - And I click on "Commit changes" - Then I am on the new file page - And I see "Path can contain only..." - - @javascript Scenario: I can create file with a directory name Given I click on "New file" link in repo And I fill the new file name with a new directory diff --git a/features/snippets/snippets.feature b/features/snippets/snippets.feature deleted file mode 100644 index 1ad02780229..00000000000 --- a/features/snippets/snippets.feature +++ /dev/null @@ -1,40 +0,0 @@ -@snippets -Feature: Snippets - Background: - Given I sign in as a user - And I have public "Personal snippet one" snippet - And I have private "Personal snippet private" snippet - - @javascript - Scenario: I create new snippet - Given I visit new snippet page - And I submit new snippet "Personal snippet three" - Then I should see snippet "Personal snippet three" - - Scenario: I update "Personal snippet one" - Given I visit snippet page "Personal snippet one" - And I click link "Edit" - And I submit new title "Personal snippet new title" - Then I should see "Personal snippet new title" - - Scenario: Set "Personal snippet one" public - Given I visit snippet page "Personal snippet one" - And I click link "Edit" - And I uncheck "Private" checkbox - Then I should see "Personal snippet one" public - - Scenario: I destroy "Personal snippet one" - Given I visit snippet page "Personal snippet one" - And I click link "Delete" - Then I should not see "Personal snippet one" in snippets - - Scenario: I create new internal snippet - Given I logout directly - And I sign in as an admin - Then I visit new snippet page - And I submit new internal snippet - Then I visit snippet page "Internal personal snippet one" - And I logout directly - Then I sign in as a user - Given I visit new snippet page - Then I visit snippet page "Internal personal snippet one" diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb index 3c06b188f3e..0960f49aad3 100644 --- a/features/steps/dashboard/dashboard.rb +++ b/features/steps/dashboard/dashboard.rb @@ -27,7 +27,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps step 'I see prefilled new Merge Request page' do expect(page).to have_selector('.merge-request-form') - expect(current_path).to eq namespace_project_new_merge_request_path(@project.namespace, @project) + expect(current_path).to eq project_new_merge_request_path(@project) expect(find("#merge_request_target_project_id").value).to eq @project.id.to_s expect(find("input#merge_request_source_branch").value).to eq "fix" expect(find("input#merge_request_target_branch").value).to eq "master" diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb deleted file mode 100644 index 530fd6f7bdb..00000000000 --- a/features/steps/dashboard/new_project.rb +++ /dev/null @@ -1,59 +0,0 @@ -class Spinach::Features::NewProject < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - include SharedProject - - step 'I click "New project" link' do - page.within '#content-body' do - click_link "New project" - end - end - - step 'I click "New project" in top right menu' do - page.within '.header-content' do - click_link "New project" - end - end - - step 'I see "New Project" page' do - expect(page).to have_content('Project path') - expect(page).to have_content('Project name') - end - - step 'I see all possible import options' do - expect(page).to have_link('GitHub') - expect(page).to have_link('Bitbucket') - expect(page).to have_link('GitLab.com') - expect(page).to have_link('Google Code') - expect(page).to have_button('Repo by URL') - expect(page).to have_link('GitLab export') - end - - step 'I click on "Import project from GitHub"' do - first('.import_github').click - end - - step 'I am redirected to the GitHub import page' do - expect(page).to have_content('Import Projects from GitHub') - expect(current_path).to eq new_import_github_path - end - - step 'I click on "Repo by URL"' do - first('.import_git').click - end - - step 'I see instructions on how to import from Git URL' do - git_import_instructions = first('.js-toggle-content') - expect(git_import_instructions).to be_visible - expect(git_import_instructions).to have_content "Git repository URL" - end - - step 'I click on "Google Code"' do - first('.import_google_code').click - end - - step 'I redirected to Google Code import page' do - expect(page).to have_content('Import projects from Google Code') - expect(current_path).to eq new_import_google_code_path - end -end diff --git a/features/steps/dashboard/starred_projects.rb b/features/steps/dashboard/starred_projects.rb deleted file mode 100644 index c33813e550b..00000000000 --- a/features/steps/dashboard/starred_projects.rb +++ /dev/null @@ -1,15 +0,0 @@ -class Spinach::Features::DashboardStarredProjects < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - include SharedProject - - step 'I starred project "Community"' do - current_user.toggle_star(Project.find_by(name: 'Community')) - end - - step 'I should not see project "Shop"' do - page.within '.projects-list' do - expect(page).not_to have_content('Shop') - end - end -end diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb index 1a55f40abb9..f1288c15084 100644 --- a/features/steps/explore/projects.rb +++ b/features/steps/explore/projects.rb @@ -66,7 +66,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps title: "New feature", project: public_project ) - visit namespace_project_issues_path(public_project.namespace, public_project) + visit project_issues_path(public_project) end step 'I should see list of issues for "Community" project' do @@ -84,7 +84,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps title: "New internal feature", project: internal_project ) - visit namespace_project_issues_path(internal_project.namespace, internal_project) + visit project_issues_path(internal_project) end step 'I should see list of issues for "Internal" project' do @@ -94,7 +94,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps end step 'I visit "Community" merge requests page' do - visit namespace_project_merge_requests_path(public_project.namespace, public_project) + visit project_merge_requests_path(public_project) end step 'project "Community" has "Bug fix" open merge request' do @@ -111,7 +111,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps end step 'I visit "Internal" merge requests page' do - visit namespace_project_merge_requests_path(internal_project.namespace, internal_project) + visit project_merge_requests_path(internal_project) end step 'project "Internal" has "Feature implemented" open merge request' do diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb index 0542b06c0ab..7288dc87005 100644 --- a/features/steps/group/milestones.rb +++ b/features/steps/group/milestones.rb @@ -39,7 +39,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps expect(page).to have_content('Milestone GL-113') expect(page).to have_content('Issues 3 Open: 3 Closed: 0') issue = Milestone.find_by(name: 'GL-113').issues.first - expect(page).to have_link(issue.title, href: namespace_project_issue_path(issue.project.namespace, issue.project, issue)) + expect(page).to have_link(issue.title, href: project_issue_path(issue.project, issue)) end step 'I fill milestone name' do diff --git a/features/steps/project/archived.rb b/features/steps/project/archived.rb index b6f1d417e21..e4847180be9 100644 --- a/features/steps/project/archived.rb +++ b/features/steps/project/archived.rb @@ -15,7 +15,7 @@ class Spinach::Features::ProjectArchived < Spinach::FeatureSteps When 'I visit project "Forum" page' do project = Project.find_by(name: "Forum") - visit namespace_project_path(project.namespace, project) + visit project_path(project) end step 'I should not see "Archived"' do diff --git a/features/steps/project/badges/build.rb b/features/steps/project/badges/build.rb index 96c59322f9b..5a9094ee9d3 100644 --- a/features/steps/project/badges/build.rb +++ b/features/steps/project/badges/build.rb @@ -5,7 +5,7 @@ class Spinach::Features::ProjectBadgesBuild < Spinach::FeatureSteps include RepoHelpers step 'I display builds badge for a master branch' do - visit build_namespace_project_badges_path(@project.namespace, @project, ref: :master, format: :svg) + visit build_project_badges_path(@project, ref: :master, format: :svg) end step 'I should see a build success badge' do diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index f19fa1c7600..305fff37c41 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -33,7 +33,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I click on commit link' do - visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id) + visit project_commit_path(@project, sample_commit.id) end step 'I see commit info' do @@ -73,7 +73,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I visit commits list page for feature branch' do - visit namespace_project_commits_path(@project.namespace, @project, 'feature', { limit: 5 }) + visit project_commits_path(@project, 'feature', { limit: 5 }) end step 'I see feature branch commits' do @@ -119,7 +119,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps step 'I should see button to the merge request' do merge_request = MergeRequest.find_by(title: 'Feature') - expect(page).to have_link "View open merge request", href: namespace_project_merge_request_path(@project.namespace, @project, merge_request) + expect(page).to have_link "View open merge request", href: project_merge_request_path(@project, merge_request) end step 'I see breadcrumb links' do @@ -135,7 +135,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I visit a commit with an image that changed' do - visit namespace_project_commit_path(@project.namespace, @project, sample_image_commit.id) + visit project_commit_path(@project, sample_image_commit.id) end step 'The diff links to both the previous and current image' do diff --git a/features/steps/project/commits/revert.rb b/features/steps/project/commits/revert.rb index 114de129d19..ebfa7a878bb 100644 --- a/features/steps/project/commits/revert.rb +++ b/features/steps/project/commits/revert.rb @@ -6,7 +6,7 @@ class Spinach::Features::RevertCommits < Spinach::FeatureSteps include RepoHelpers step 'I click on commit link' do - visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id) + visit project_commit_path(@project, sample_commit.id) end step 'I click on the revert button' do diff --git a/features/steps/project/commits/user_lookup.rb b/features/steps/project/commits/user_lookup.rb index 2d43be5a386..4599e0d032a 100644 --- a/features/steps/project/commits/user_lookup.rb +++ b/features/steps/project/commits/user_lookup.rb @@ -4,11 +4,11 @@ class Spinach::Features::ProjectCommitsUserLookup < Spinach::FeatureSteps include SharedPaths step 'I click on commit link' do - visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id) + visit project_commit_path(@project, sample_commit.id) end step 'I click on another commit link' do - visit namespace_project_commit_path(@project.namespace, @project, sample_commit.parent_id) + visit project_commit_path(@project, sample_commit.parent_id) end step 'I have user with primary email' do diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb index 28be9c6df5b..60fa232672e 100644 --- a/features/steps/project/create.rb +++ b/features/steps/project/create.rb @@ -7,12 +7,12 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps fill_in 'project_path', with: 'Empty' page.within '#content-body' do click_button "Create project" - end + end end step 'I should see project page' do expect(page).to have_content "Empty" - expect(current_path).to eq namespace_project_path(Project.last.namespace, Project.last) + expect(current_path).to eq project_path(Project.last) end step 'I should see empty project instructions' do diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb index 8ad9d4a4741..b58d595c7c6 100644 --- a/features/steps/project/deploy_keys.rb +++ b/features/steps/project/deploy_keys.rb @@ -36,7 +36,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps end step 'I should be on deploy keys page' do - expect(current_path).to eq namespace_project_settings_repository_path(@project.namespace, @project) + expect(current_path).to eq project_settings_repository_path(@project) end step 'I should see newly created deploy key' do diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb index 35df403a85f..dd4dff7f7a9 100644 --- a/features/steps/project/fork.rb +++ b/features/steps/project/fork.rb @@ -53,7 +53,7 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps step 'I visit the forks page of the "Shop" project' do @project = Project.where(name: 'Shop').last - visit namespace_project_forks_path(@project.namespace, @project) + visit project_forks_path(@project) end step 'I should see my fork on the list' do diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb index 2d9d3efd9d4..c6cabace25b 100644 --- a/features/steps/project/forked_merge_requests.rb +++ b/features/steps/project/forked_merge_requests.rb @@ -25,7 +25,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps step 'I should see merge request "Merge Request On Forked Project"' do expect(@project.merge_requests.size).to be >= 1 @merge_request = @project.merge_requests.last - expect(current_path).to eq namespace_project_merge_request_path(@project.namespace, @project, @merge_request) + expect(current_path).to eq project_merge_request_path(@project, @merge_request) expect(@merge_request.title).to eq "Merge Request On Forked Project" expect(@merge_request.source_project).to eq @forked_project expect(@merge_request.source_branch).to eq "fix" @@ -77,7 +77,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps expect(page).to have_content "An Edited Forked Merge Request" expect(@project.merge_requests.size).to be >= 1 @merge_request = @project.merge_requests.last - expect(current_path).to eq namespace_project_merge_request_path(@project.namespace, @project, @merge_request) + expect(current_path).to eq project_merge_request_path(@project, @merge_request) expect(@merge_request.source_project).to eq @forked_project expect(@merge_request.source_branch).to eq "fix" expect(@merge_request.target_branch).to eq "master" @@ -97,7 +97,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps end step 'I see the edit page prefilled for "Merge Request On Forked Project"' do - expect(current_path).to eq edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) + expect(current_path).to eq edit_project_merge_request_path(@project, @merge_request) expect(page).to have_content "Edit merge request #{@merge_request.to_reference}" expect(find("#merge_request_title").value).to eq "Merge Request On Forked Project" end diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb index 176d04d721c..e78e25318a6 100644 --- a/features/steps/project/graph.rb +++ b/features/steps/project/graph.rb @@ -7,19 +7,19 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps end When 'I visit project "Shop" graph page' do - visit namespace_project_graph_path(project.namespace, project, "master") + visit project_graph_path(project, "master") end step 'I visit project "Shop" commits graph page' do - visit commits_namespace_project_graph_path(project.namespace, project, "master") + visit commits_project_graph_path(project, "master") end step 'I visit project "Shop" languages graph page' do - visit languages_namespace_project_graph_path(project.namespace, project, "master") + visit languages_project_graph_path(project, "master") end step 'I visit project "Shop" chart page' do - visit charts_namespace_project_graph_path(project.namespace, project, "master") + visit charts_project_graph_path(project, "master") end step 'page should have languages graphs' do @@ -33,7 +33,7 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps end step 'I visit project "Shop" CI graph page' do - visit ci_namespace_project_graph_path(project.namespace, project, 'master') + visit ci_project_graph_path(project, 'master') end step 'page should have CI graphs' do diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb index 2324edda975..bbd284b4633 100644 --- a/features/steps/project/issues/award_emoji.rb +++ b/features/steps/project/issues/award_emoji.rb @@ -5,7 +5,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps include Select2Helper step 'I visit "Bugfix" issue page' do - visit namespace_project_issue_path(@project.namespace, @project, @issue) + visit project_issue_path(@project, @issue) end step 'I click the thumbsup award Emoji' do diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index e4a559d8ff5..2deef9036d3 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -247,7 +247,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps When 'I visit empty project page' do project = Project.find_by(name: 'Empty Project') - visit namespace_project_path(project.namespace, project) + visit project_path(project) end step 'I see empty project details with ssh clone info' do @@ -259,12 +259,12 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps When "I visit project \"Community\" issues page" do project = Project.find_by(name: 'Community') - visit namespace_project_issues_path(project.namespace, project) + visit project_issues_path(project) end When "I visit empty project's issues page" do project = Project.find_by(name: 'Empty Project') - visit namespace_project_issues_path(project.namespace, project) + visit project_issues_path(project) end step 'I leave a comment with code block' do diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb index 2828e41f731..dac18c537ac 100644 --- a/features/steps/project/issues/labels.rb +++ b/features/steps/project/issues/labels.rb @@ -4,7 +4,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps include SharedPaths step 'I visit \'bug\' label edit page' do - visit edit_namespace_project_label_path(project.namespace, project, bug_label) + visit edit_project_label_path(project, bug_label) end step 'I remove label \'bug\'' do diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 69f5d0f8410..810cd75591b 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -65,7 +65,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'I should not see "master" branch' do - expect(find('.merge-request-info')).not_to have_content "master" + expect(find('.issuable-info')).not_to have_content "master" end step 'I should see "feature_conflict" branch' do @@ -256,7 +256,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'I switch to the merge request\'s comments tab' do - visit namespace_project_merge_request_path(project.namespace, project, merge_request) + visit project_merge_request_path(project, merge_request) end step 'I click on the commit in the merge request' do diff --git a/features/steps/project/merge_requests/acceptance.rb b/features/steps/project/merge_requests/acceptance.rb index 870dc862992..3c640e3512a 100644 --- a/features/steps/project/merge_requests/acceptance.rb +++ b/features/steps/project/merge_requests/acceptance.rb @@ -1,6 +1,5 @@ class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps include LoginHelpers - include GitlabRoutingHelper include WaitForRequests step 'I am on the Merge Request detail page' do diff --git a/features/steps/project/merge_requests/revert.rb b/features/steps/project/merge_requests/revert.rb index 98d990f112f..25ccf5ab180 100644 --- a/features/steps/project/merge_requests/revert.rb +++ b/features/steps/project/merge_requests/revert.rb @@ -1,6 +1,5 @@ class Spinach::Features::RevertMergeRequests < Spinach::FeatureSteps include LoginHelpers - include GitlabRoutingHelper include WaitForRequests step 'I click on the revert button' do diff --git a/features/steps/project/network_graph.rb b/features/steps/project/network_graph.rb index 370e46265c7..ba98d861e7b 100644 --- a/features/steps/project/network_graph.rb +++ b/features/steps/project/network_graph.rb @@ -12,11 +12,11 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps Network::Graph.stub(max_count: 10) @project = Project.find_by(name: "Shop") - visit namespace_project_network_path(@project.namespace, @project, "master") + visit project_network_path(@project, "master") end step "I visit project network page on branch 'test'" do - visit namespace_project_network_path(@project.namespace, @project, "'test'") + visit project_network_path(@project, "'test'") end step 'page should select "master" in select box' do diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb index 4e6830f738b..275fb4fc010 100644 --- a/features/steps/project/pages.rb +++ b/features/steps/project/pages.rb @@ -15,7 +15,7 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps end step 'I visit the Project Pages' do - visit namespace_project_pages_path(@project.namespace, @project) + visit project_pages_path(@project) end step 'I should see the usage of GitLab Pages' do @@ -75,7 +75,7 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps end step 'I visit add a new Pages Domain' do - visit new_namespace_project_pages_domain_path(@project.namespace, @project) + visit new_project_pages_domain_path(@project) end step 'I fill the domain' do diff --git a/features/steps/project/project_group_links.rb b/features/steps/project/project_group_links.rb index 5280a38ce81..47ee31786a6 100644 --- a/features/steps/project/project_group_links.rb +++ b/features/steps/project/project_group_links.rb @@ -42,7 +42,7 @@ class Spinach::Features::ProjectGroupLinks < Spinach::FeatureSteps end step 'I visit project group links page' do - visit namespace_project_group_links_path(project.namespace, project) + visit project_group_links_path(project) end def project diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb index 92936f27c20..b2ceb8dd9a8 100644 --- a/features/steps/project/redirects.rb +++ b/features/steps/project/redirects.rb @@ -13,7 +13,7 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps step 'I visit project "Community" page' do project = Project.find_by(name: 'Community') - visit namespace_project_path(project.namespace, project) + visit project_path(project) end step 'I should see project "Community" home page' do @@ -25,12 +25,12 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps step 'I visit project "Enterprise" page' do project = Project.find_by(name: 'Enterprise') - visit namespace_project_path(project.namespace, project) + visit project_path(project) end step 'I visit project "CommunityDoesNotExist" page' do project = Project.find_by(name: 'Community') - visit namespace_project_path(project.namespace, project) + 'DoesNotExist' + visit project_path(project) + 'DoesNotExist' end step 'I click on "Sign In"' do diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb index 6bac4df16f8..906a81b29b3 100644 --- a/features/steps/project/services.rb +++ b/features/steps/project/services.rb @@ -4,7 +4,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps include SharedPaths step 'I visit project "Shop" services page' do - visit namespace_project_settings_integrations_path(@project.namespace, @project) + visit project_settings_integrations_path(@project) end step 'I should see list of available services' do diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb index dd49701a3d9..b0407d3f07d 100644 --- a/features/steps/project/snippets.rb +++ b/features/steps/project/snippets.rb @@ -91,7 +91,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps end step 'I visit snippet page "Snippet one"' do - visit namespace_project_snippet_path(project.namespace, project, project_snippet) + visit project_snippet_path(project, project_snippet) end def project_snippet diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 02434319a08..621cae5d80d 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -9,7 +9,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps step "I don't have write access" do @project = create(:project, :repository, name: "Other Project", path: "other-project") @project.team << [@user, :reporter] - visit namespace_project_tree_path(@project.namespace, @project, root_ref) + visit project_tree_path(@project, root_ref) end step 'I should see files from repository' do @@ -19,7 +19,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step 'I should see files from repository for "6d39438"' do - expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "6d39438") + expect(current_path).to eq project_tree_path(@project, "6d39438") expect(page).to have_content ".gitignore" expect(page).to have_content "LICENSE" end @@ -92,10 +92,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps fill_in :branch_name, with: 'new_branch_name', visible: true end - step 'I fill the new file name with an illegal name' do - fill_in :file_name, with: 'Spaces Not Allowed' - end - step 'I fill the new file name with a new directory' do fill_in :file_name, with: new_file_name_with_directory end @@ -240,16 +236,16 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step 'I am redirected to the files URL' do - expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, 'master') + expect(current_path).to eq project_tree_path(@project, 'master') end step 'I am redirected to the ".gitignore"' do - expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'master/.gitignore')) + expect(current_path).to eq(project_blob_path(@project, 'master/.gitignore')) end step 'I am redirected to the permalink URL' do expect(current_path).to( - eq(namespace_project_blob_path(@project.namespace, @project, + eq(project_blob_path(@project, @project.repository.commit.sha + '/.gitignore')) ) @@ -257,26 +253,26 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps step 'I am redirected to the new file' do expect(current_path).to eq( - namespace_project_blob_path(@project.namespace, @project, 'master/' + new_file_name)) + project_blob_path(@project, 'master/' + new_file_name)) end step 'I am redirected to the new file with directory' do expect(current_path).to eq( - namespace_project_blob_path(@project.namespace, @project, 'master/' + new_file_name_with_directory)) + project_blob_path(@project, 'master/' + new_file_name_with_directory)) end step 'I am redirected to the new merge request page' do - expect(current_path).to eq(namespace_project_new_merge_request_path(@project.namespace, @project)) + expect(current_path).to eq(project_new_merge_request_path(@project)) end step "I am redirected to the fork's new merge request page" do fork = @user.fork_of(@project) - expect(current_path).to eq(namespace_project_new_merge_request_path(fork.namespace, fork)) + expect(current_path).to eq(project_new_merge_request_path(fork)) end step 'I am redirected to the root directory' do expect(current_path).to eq( - namespace_project_tree_path(@project.namespace, @project, 'master')) + project_tree_path(@project, 'master')) end step "I don't see the permalink link" do @@ -327,11 +323,11 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step "I visit the 'test' tree" do - visit namespace_project_tree_path(@project.namespace, @project, "'test'") + visit project_tree_path(@project, "'test'") end step "I visit the fix tree" do - visit namespace_project_tree_path(@project.namespace, @project, "fix/.testdir") + visit project_tree_path(@project, "fix/.testdir") end step 'I see the commit data' do @@ -346,7 +342,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps step 'I click on "files/lfs/lfs_object.iso" file in repo' do allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true) - visit namespace_project_tree_path(@project.namespace, @project, "lfs") + visit project_tree_path(@project, "lfs") click_link 'files' click_link "lfs" click_link "lfs_object.iso" @@ -389,7 +385,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step 'I visit the SVG file' do - visit namespace_project_blob_path(@project.namespace, @project, 'new_branch_name/logo_sample.svg') + visit project_blob_path(@project, 'new_branch_name/logo_sample.svg') end step 'I can see the new rendered SVG image' do diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb index cf31e61437e..243a0f54f7f 100644 --- a/features/steps/project/source/markdown_render.rb +++ b/features/steps/project/source/markdown_render.rb @@ -14,7 +14,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps end step 'I should see files from repository in markdown' do - expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown") + expect(current_path).to eq project_tree_path(@project, "markdown") expect(page).to have_content "README.md" expect(page).to have_content "CHANGELOG" end @@ -34,7 +34,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps end step 'I should see correct document rendered' do - expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md") + expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/README.md") wait_for_requests expect(page).to have_content "All API requests require authentication" end @@ -44,7 +44,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps end step 'I should see correct directory rendered' do - expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/raketasks") + expect(current_path).to eq project_tree_path(@project, "markdown/doc/raketasks") expect(page).to have_content "backup_restore.md" expect(page).to have_content "maintenance.md" end @@ -54,7 +54,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps end step 'I should see correct doc/api directory rendered' do - expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api") + expect(current_path).to eq project_tree_path(@project, "markdown/doc/api") expect(page).to have_content "README.md" expect(page).to have_content "users.md" end @@ -64,7 +64,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps end step 'I should see correct maintenance file rendered' do - expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/raketasks/maintenance.md") + expect(current_path).to eq project_blob_path(@project, "markdown/doc/raketasks/maintenance.md") wait_for_requests expect(page).to have_content "bundle exec rake gitlab:env:info RAILS_ENV=production" end @@ -98,7 +98,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps end step 'I see correct file rendered' do - expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md") + expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/README.md") wait_for_requests expect(page).to have_content "Contents" expect(page).to have_link "Users" @@ -110,7 +110,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps end step 'I should see the correct document file' do - expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md") + expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/users.md") expect(page).to have_content "Get a list of users." end @@ -121,30 +121,30 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps # Markdown branch When 'I visit markdown branch' do - visit namespace_project_tree_path(@project.namespace, @project, "markdown") + visit project_tree_path(@project, "markdown") wait_for_requests end When 'I visit markdown branch "README.md" blob' do - visit namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + visit project_blob_path(@project, "markdown/README.md") end When 'I visit markdown branch "d" tree' do - visit namespace_project_tree_path(@project.namespace, @project, "markdown/d") + visit project_tree_path(@project, "markdown/d") end When 'I visit markdown branch "d/README.md" blob' do - visit namespace_project_blob_path(@project.namespace, @project, "markdown/d/README.md") + visit project_blob_path(@project, "markdown/d/README.md") end step 'I should see files from repository in markdown branch' do - expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown") + expect(current_path).to eq project_tree_path(@project, "markdown") expect(page).to have_content "README.md" expect(page).to have_content "CHANGELOG" end step 'I see correct file rendered in markdown branch' do - expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md") + expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/README.md") wait_for_requests expect(page).to have_content "Contents" expect(page).to have_link "Users" @@ -152,19 +152,19 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps end step 'I should see correct document rendered for markdown branch' do - expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md") + expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/README.md") wait_for_requests expect(page).to have_content "All API requests require authentication" end step 'I should see correct directory rendered for markdown branch' do - expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/raketasks") + expect(current_path).to eq project_tree_path(@project, "markdown/doc/raketasks") expect(page).to have_content "backup_restore.md" expect(page).to have_content "maintenance.md" end step 'I should see the users document file in markdown branch' do - expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md") + expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/users.md") expect(page).to have_content "Get a list of users." end @@ -172,54 +172,54 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps step 'The link with text "empty" should have url "tree/markdown"' do wait_for_requests - find('a', text: /^empty$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown") + find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown") end step 'The link with text "empty" should have url "blob/markdown/README.md"' do - find('a', text: /^empty$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") end step 'The link with text "empty" should have url "tree/markdown/d"' do - find('a', text: /^empty$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown/d") + find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown/d") end step 'The link with text "empty" should have '\ 'url "blob/markdown/d/README.md"' do - find('a', text: /^empty$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/d/README.md") + find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/d/README.md") end step 'The link with text "ID" should have url "tree/markdownID"' do - find('a', text: /^#id$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown") + '#id' + find('a', text: /^#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id' end step 'The link with text "/ID" should have url "tree/markdownID"' do - find('a', text: /^\/#id$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown") + '#id' + find('a', text: /^\/#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id' end step 'The link with text "README.mdID" '\ 'should have url "blob/markdown/README.mdID"' do - find('a', text: /^README.md#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id' + find('a', text: /^README.md#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id' end step 'The link with text "d/README.mdID" should have '\ 'url "blob/markdown/d/README.mdID"' do - find('a', text: /^d\/README.md#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "d/markdown/README.md") + '#id' + find('a', text: /^d\/README.md#id$/)['href'] == current_host + project_blob_path(@project, "d/markdown/README.md") + '#id' end step 'The link with text "ID" should have url "blob/markdown/README.mdID"' do wait_for_requests - find('a', text: /^#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id' + find('a', text: /^#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id' end step 'The link with text "/ID" should have url "blob/markdown/README.mdID"' do - find('a', text: /^\/#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id' + find('a', text: /^\/#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id' end # Wiki step 'I go to wiki page' do click_link "Wiki" - expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "home") + expect(current_path).to eq project_wiki_path(@project, "home") end step 'I add various links to the wiki page' do @@ -231,7 +231,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps end step 'Wiki page should have added links' do - expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "home") + expect(current_path).to eq project_wiki_path(@project, "home") expect(page).to have_content "test GitLab API doc Rake tasks" end @@ -252,7 +252,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps end step 'I see new wiki page named test' do - expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "test") + expect(current_path).to eq project_wiki_path(@project, "test") page.within(:css, ".nav-text") do expect(page).to have_content "Test" @@ -261,8 +261,8 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps end When 'I go back to wiki page home' do - visit namespace_project_wiki_path(@project.namespace, @project, "home") - expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "home") + visit project_wiki_path(@project, "home") + expect(current_path).to eq project_wiki_path(@project, "home") end step 'I click on GitLab API doc link' do @@ -270,7 +270,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps end step 'I see Gitlab API document' do - expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "api") + expect(current_path).to eq project_wiki_path(@project, "api") page.within(:css, ".nav-text") do expect(page).to have_content "Create" @@ -283,7 +283,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps end step 'I see Rake tasks directory' do - expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "raketasks") + expect(current_path).to eq project_wiki_path(@project, "raketasks") page.within(:css, ".nav-text") do expect(page).to have_content "Create" @@ -292,8 +292,8 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps end step 'I go directory which contains README file' do - visit namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api") - expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api") + visit project_tree_path(@project, "markdown/doc/api") + expect(current_path).to eq project_tree_path(@project, "markdown/doc/api") end step 'I click on a relative link in README' do @@ -301,7 +301,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps end step 'I should see the correct markdown' do - expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md") + expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/users.md") wait_for_requests expect(page).to have_content "List users" end diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb index 517c257d892..a2f5d2e1515 100644 --- a/features/steps/project/wiki.rb +++ b/features/steps/project/wiki.rb @@ -11,7 +11,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps end step 'I should be redirected back to the Edit Home Wiki page' do - expect(current_path).to eq namespace_project_wiki_path(project.namespace, project, :home) + expect(current_path).to eq project_wiki_path(project, :home) end step 'I create the Wiki Home page' do @@ -42,7 +42,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps end step 'I browse to that Wiki page' do - visit namespace_project_wiki_path(project.namespace, project, @page) + visit project_wiki_path(project, @page) end step 'I click on the Edit button' do @@ -59,7 +59,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps end step 'I should be redirected back to that Wiki page' do - expect(current_path).to eq namespace_project_wiki_path(project.namespace, project, @page) + expect(current_path).to eq project_wiki_path(project, @page) end step 'That page has two revisions' do @@ -95,7 +95,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps end step 'I browse to wiki page with images' do - visit namespace_project_wiki_path(project.namespace, project, @wiki_page) + visit project_wiki_path(project, @wiki_page) end step 'I click on existing image link' do diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb index 624f1a7858b..3b4c98ec00d 100644 --- a/features/steps/shared/builds.rb +++ b/features/steps/shared/builds.rb @@ -27,11 +27,11 @@ module SharedBuilds end step 'I visit recent build details page' do - visit namespace_project_job_path(@project.namespace, @project, @build) + visit project_job_path(@project, @build) end step 'I visit project builds page' do - visit namespace_project_jobs_path(@project.namespace, @project) + visit project_jobs_path(@project) end step 'recent build has artifacts available' do @@ -56,7 +56,7 @@ module SharedBuilds end step 'I access artifacts download page' do - visit download_namespace_project_job_artifacts_path(@project.namespace, @project, @build) + visit download_project_job_artifacts_path(@project, @build) end step 'I see details of a build' do diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb index 36fc315599e..2c59ec5bb06 100644 --- a/features/steps/shared/diff_note.rb +++ b/features/steps/shared/diff_note.rb @@ -232,7 +232,7 @@ module SharedDiffNote end def click_parallel_diff_line(code, line_type) - find(".line_content.parallel.#{line_type}[data-line-code='#{code}']").trigger 'mouseover' + find(".line_holder.parallel .diff-line-num[id='#{code}']").trigger 'mouseover' find(".line_holder.parallel button[data-line-code='#{code}']").trigger 'click' end end diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb index 3d9cedf5c2d..7c842ba88fb 100644 --- a/features/steps/shared/issuable.rb +++ b/features/steps/shared/issuable.rb @@ -51,22 +51,22 @@ module SharedIssuable step 'I visit issue page "Enterprise issue"' do issue = Issue.find_by(title: 'Enterprise issue') - visit namespace_project_issue_path(issue.project.namespace, issue.project, issue) + visit project_issue_path(issue.project, issue) end step 'I visit merge request page "Enterprise fix"' do mr = MergeRequest.find_by(title: 'Enterprise fix') - visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr) + visit project_merge_request_path(mr.target_project, mr) end step 'I visit issue page "Community issue"' do issue = Issue.find_by(title: 'Community issue') - visit namespace_project_issue_path(issue.project.namespace, issue.project, issue) + visit project_issue_path(issue.project, issue) end step 'I visit issue page "Community fix"' do mr = MergeRequest.find_by(title: 'Community fix') - visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr) + visit project_merge_request_path(mr.target_project, mr) end step 'I should not see any related merge requests' do diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index 8a5b4112ffe..830263fd038 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -201,67 +201,67 @@ module SharedPaths # ---------------------------------------- step "I visit my project's home page" do - visit namespace_project_path(@project.namespace, @project) + visit project_path(@project) end step "I visit my project's settings page" do - visit edit_namespace_project_path(@project.namespace, @project) + visit edit_project_path(@project) end step "I visit my project's files page" do - visit namespace_project_tree_path(@project.namespace, @project, root_ref) + visit project_tree_path(@project, root_ref) end step 'I visit a binary file in the repo' do - visit namespace_project_blob_path(@project.namespace, @project, + visit project_blob_path(@project, File.join(root_ref, 'files/images/logo-black.png')) end step "I visit my project's commits page" do - visit namespace_project_commits_path(@project.namespace, @project, root_ref, { limit: 5 }) + visit project_commits_path(@project, root_ref, { limit: 5 }) end step "I visit my project's commits page for a specific path" do - visit namespace_project_commits_path(@project.namespace, @project, root_ref + "/app/models/project.rb", { limit: 5 }) + visit project_commits_path(@project, root_ref + "/app/models/project.rb", { limit: 5 }) end step 'I visit my project\'s commits stats page' do - visit stats_namespace_project_repository_path(@project.namespace, @project) + visit stats_project_repository_path(@project) end step "I visit my project's graph page" do # Stub Graph max_size to speed up test (10 commits vs. 650) Network::Graph.stub(max_count: 10) - visit namespace_project_network_path(@project.namespace, @project, root_ref) + visit project_network_path(@project, root_ref) end step "I visit my project's issues page" do - visit namespace_project_issues_path(@project.namespace, @project) + visit project_issues_path(@project) end step "I visit my project's merge requests page" do - visit namespace_project_merge_requests_path(@project.namespace, @project) + visit project_merge_requests_path(@project) end step "I visit my project's members page" do - visit namespace_project_project_members_path(@project.namespace, @project) + visit project_project_members_path(@project) end step "I visit my project's wiki page" do - visit namespace_project_wiki_path(@project.namespace, @project, :home) + visit project_wiki_path(@project, :home) end step 'I visit project hooks page' do - visit namespace_project_settings_integrations_path(@project.namespace, @project) + visit project_settings_integrations_path(@project) end step 'I visit project deploy keys page' do - visit namespace_project_deploy_keys_path(@project.namespace, @project) + visit project_deploy_keys_path(@project) end step 'I visit project find file page' do - visit namespace_project_find_file_path(@project.namespace, @project, root_ref) + visit project_find_file_path(@project, root_ref) end # ---------------------------------------- @@ -269,107 +269,107 @@ module SharedPaths # ---------------------------------------- step 'I visit project "Shop" page' do - visit namespace_project_path(project.namespace, project) + visit project_path(project) end step 'I visit project "Shop" activity page' do - visit activity_namespace_project_path(project.namespace, project) + visit activity_project_path(project) end step 'I visit project "Forked Shop" merge requests page' do - visit namespace_project_merge_requests_path(@forked_project.namespace, @forked_project) + visit project_merge_requests_path(@forked_project) end step 'I visit edit project "Shop" page' do - visit edit_namespace_project_path(project.namespace, project) + visit edit_project_path(project) end step 'I visit project branches page' do - visit namespace_project_branches_path(@project.namespace, @project) + visit project_branches_path(@project) end step 'I visit project protected branches page' do - visit namespace_project_protected_branches_path(@project.namespace, @project) + visit project_protected_branches_path(@project) end step 'I visit compare refs page' do - visit namespace_project_compare_index_path(@project.namespace, @project) + visit project_compare_index_path(@project) end step 'I visit project commits page' do - visit namespace_project_commits_path(@project.namespace, @project, root_ref, { limit: 5 }) + visit project_commits_path(@project, root_ref, { limit: 5 }) end step 'I visit project commits page for stable branch' do - visit namespace_project_commits_path(@project.namespace, @project, 'stable', { limit: 5 }) + visit project_commits_path(@project, 'stable', { limit: 5 }) end step 'I visit project source page' do - visit namespace_project_tree_path(@project.namespace, @project, root_ref) + visit project_tree_path(@project, root_ref) end step 'I visit blob file from repo' do - visit namespace_project_blob_path(@project.namespace, @project, File.join(sample_commit.id, sample_blob.path)) + visit project_blob_path(@project, File.join(sample_commit.id, sample_blob.path)) end step 'I visit ".gitignore" file in repo' do - visit namespace_project_blob_path(@project.namespace, @project, File.join(root_ref, '.gitignore')) + visit project_blob_path(@project, File.join(root_ref, '.gitignore')) end step 'I am on the new file page' do - expect(current_path).to eq(namespace_project_create_blob_path(@project.namespace, @project, root_ref)) + expect(current_path).to eq(project_create_blob_path(@project, root_ref)) end step 'I am on the ".gitignore" edit file page' do expect(current_path).to eq( - namespace_project_edit_blob_path(@project.namespace, @project, File.join(root_ref, '.gitignore'))) + project_edit_blob_path(@project, File.join(root_ref, '.gitignore'))) end step 'I visit project source page for "6d39438"' do - visit namespace_project_tree_path(@project.namespace, @project, "6d39438") + visit project_tree_path(@project, "6d39438") end step 'I visit project source page for' \ ' "6d394385cf567f80a8fd85055db1ab4c5295806f"' do - visit namespace_project_tree_path(@project.namespace, @project, + visit project_tree_path(@project, '6d394385cf567f80a8fd85055db1ab4c5295806f') end step 'I visit project tags page' do - visit namespace_project_tags_path(@project.namespace, @project) + visit project_tags_path(@project) end step 'I visit project commit page' do - visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id) + visit project_commit_path(@project, sample_commit.id) end step 'I visit project "Shop" issues page' do - visit namespace_project_issues_path(project.namespace, project) + visit project_issues_path(project) end step 'I visit issue page "Release 0.4"' do issue = Issue.find_by(title: "Release 0.4") - visit namespace_project_issue_path(issue.project.namespace, issue.project, issue) + visit project_issue_path(issue.project, issue) end step 'I visit project "Shop" labels page' do project = Project.find_by(name: 'Shop') - visit namespace_project_labels_path(project.namespace, project) + visit project_labels_path(project) end step 'I visit project "Forum" labels page' do project = Project.find_by(name: 'Forum') - visit namespace_project_labels_path(project.namespace, project) + visit project_labels_path(project) end step 'I visit project "Shop" new label page' do project = Project.find_by(name: 'Shop') - visit new_namespace_project_label_path(project.namespace, project) + visit new_project_label_path(project) end step 'I visit project "Forum" new label page' do project = Project.find_by(name: 'Forum') - visit new_namespace_project_label_path(project.namespace, project) + visit new_project_label_path(project) end step 'I visit merge request page "Bug NS-04"' do @@ -394,28 +394,28 @@ module SharedPaths step 'I visit merge request page "Bug CO-01"' do mr = MergeRequest.find_by(title: "Bug CO-01") - visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr) + visit project_merge_request_path(mr.target_project, mr) wait_for_requests end step 'I visit project "Shop" merge requests page' do - visit namespace_project_merge_requests_path(project.namespace, project) + visit project_merge_requests_path(project) end step 'I visit forked project "Shop" merge requests page' do - visit namespace_project_merge_requests_path(project.namespace, project) + visit project_merge_requests_path(project) end step 'I visit project "Shop" milestones page' do - visit namespace_project_milestones_path(project.namespace, project) + visit project_milestones_path(project) end step 'I visit project "Shop" team page' do - visit namespace_project_project_members_path(project.namespace, project) + visit project_project_members_path(project) end step 'I visit project wiki page' do - visit namespace_project_wiki_path(@project.namespace, @project, :home) + visit project_wiki_path(@project, :home) end # ---------------------------------------- @@ -424,22 +424,22 @@ module SharedPaths step 'I visit project "Community" page' do project = Project.find_by(name: "Community") - visit namespace_project_path(project.namespace, project) + visit project_path(project) end step 'I visit project "Community" source page' do project = Project.find_by(name: 'Community') - visit namespace_project_tree_path(project.namespace, project, root_ref) + visit project_tree_path(project, root_ref) end step 'I visit project "Internal" page' do project = Project.find_by(name: "Internal") - visit namespace_project_path(project.namespace, project) + visit project_path(project) end step 'I visit project "Enterprise" page' do project = Project.find_by(name: "Enterprise") - visit namespace_project_path(project.namespace, project) + visit project_path(project) end # ---------------------------------------- @@ -448,7 +448,7 @@ module SharedPaths step "I visit empty project page" do project = Project.find_by(name: "Empty Public Project") - visit namespace_project_path(project.namespace, project) + visit project_path(project) end step "I should not see command line instructions" do @@ -480,17 +480,13 @@ module SharedPaths # ---------------------------------------- step 'I visit project "Shop" snippets page' do - visit namespace_project_snippets_path(project.namespace, project) + visit project_snippets_path(project) end step 'I visit snippets page' do visit explore_snippets_path end - step 'I visit new snippet page' do - visit new_snippet_path - end - def root_ref @project.repository.root_ref end @@ -501,7 +497,7 @@ module SharedPaths def merge_request_path(title) mr = MergeRequest.find_by(title: title) - namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr) + project_merge_request_path(mr.target_project, mr) end # ---------------------------------------- diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index c4f1c57836f..729e2b8982c 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -61,12 +61,12 @@ module SharedProject step 'I visit my empty project page' do project = Project.find_by(name: 'Empty Project') - visit namespace_project_path(project.namespace, project) + visit project_path(project) end step 'I visit project "Shop" activity page' do project = Project.find_by(name: 'Shop') - visit namespace_project_path(project.namespace, project) + visit project_path(project) end step 'project "Shop" has push event' do @@ -101,7 +101,7 @@ module SharedProject end step 'I should see project settings' do - expect(current_path).to eq edit_namespace_project_path(@project.namespace, @project) + expect(current_path).to eq edit_project_path(@project) expect(page).to have_content("Project name") expect(page).to have_content("Sharing & Permissions") end diff --git a/features/steps/shared/snippet.rb b/features/steps/shared/snippet.rb deleted file mode 100644 index bb596c1620a..00000000000 --- a/features/steps/shared/snippet.rb +++ /dev/null @@ -1,63 +0,0 @@ -module SharedSnippet - include Spinach::DSL - - step 'I have public "Personal snippet one" snippet' do - create(:personal_snippet, - title: "Personal snippet one", - content: "Test content", - file_name: "snippet.rb", - visibility_level: Snippet::PUBLIC, - author: current_user) - end - - step 'I have private "Personal snippet private" snippet' do - create(:personal_snippet, - title: "Personal snippet private", - content: "Provate content", - file_name: "private_snippet.rb", - visibility_level: Snippet::PRIVATE, - author: current_user) - end - - step 'I have internal "Personal snippet internal" snippet' do - create(:personal_snippet, - title: "Personal snippet internal", - content: "Provate content", - file_name: "internal_snippet.rb", - visibility_level: Snippet::INTERNAL, - author: current_user) - end - - step 'I have a public many lined snippet' do - create(:personal_snippet, - title: 'Many lined snippet', - content: <<-END.gsub(/^\s+\|/, ''), - |line one - |line two - |line three - |line four - |line five - |line six - |line seven - |line eight - |line nine - |line ten - |line eleven - |line twelve - |line thirteen - |line fourteen - END - file_name: 'many_lined_snippet.rb', - visibility_level: Snippet::PUBLIC, - author: current_user) - end - - step 'There is public "Personal snippet one" snippet' do - create(:personal_snippet, - title: "Personal snippet one", - content: "Test content", - file_name: "snippet.rb", - visibility_level: Snippet::PUBLIC, - author: create(:user)) - end -end diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb deleted file mode 100644 index a4fc77746ee..00000000000 --- a/features/steps/snippets/snippets.rb +++ /dev/null @@ -1,86 +0,0 @@ -class Spinach::Features::Snippets < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - include SharedProject - include SharedSnippet - include WaitForRequests - - step 'I click link "Personal snippet one"' do - click_link "Personal snippet one" - end - - step 'I should not see "Personal snippet one" in snippets' do - expect(page).not_to have_content "Personal snippet one" - end - - step 'I click link "Edit"' do - page.within ".detail-page-header" do - first(:link, "Edit").click - end - end - - step 'I click link "Delete"' do - first(:link, "Delete").click - end - - step 'I submit new snippet "Personal snippet three"' do - fill_in "personal_snippet_title", with: "Personal snippet three" - fill_in "personal_snippet_file_name", with: "my_snippet.rb" - page.within('.file-editor') do - find('.ace_editor').native.send_keys 'Content of snippet three' - end - click_button "Create snippet" - wait_for_requests - end - - step 'I submit new internal snippet' do - fill_in "personal_snippet_title", with: "Internal personal snippet one" - fill_in "personal_snippet_file_name", with: "my_snippet.rb" - choose 'personal_snippet_visibility_level_10' - - page.within('.file-editor') do - find(:xpath, "//input[@id='personal_snippet_content']").set 'Content of internal snippet' - end - - click_button "Create snippet" - end - - step 'I should see snippet "Personal snippet three"' do - expect(page).to have_content "Personal snippet three" - expect(page).to have_content "Content of snippet three" - end - - step 'I submit new title "Personal snippet new title"' do - fill_in "personal_snippet_title", with: "Personal snippet new title" - click_button "Save" - end - - step 'I should see "Personal snippet new title"' do - expect(page).to have_content "Personal snippet new title" - end - - step 'I uncheck "Private" checkbox' do - choose "Internal" - click_button "Save" - end - - step 'I should see "Personal snippet one" public' do - expect(page).to have_no_xpath("//i[@class='public-snippet']") - end - - step 'I visit snippet page "Personal snippet one"' do - visit snippet_path(snippet) - end - - step 'I visit snippet page "Internal personal snippet one"' do - visit snippet_path(internal_snippet) - end - - def snippet - @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one") - end - - def internal_snippet - @snippet ||= PersonalSnippet.find_by!(title: "Internal personal snippet one") - end -end diff --git a/features/support/env.rb b/features/support/env.rb index 1690465d9b2..608d988755c 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -28,6 +28,7 @@ Spinach.hooks.before_run do TestEnv.disable_pre_receive include FactoryGirl::Syntax::Methods + include GitlabRoutingHelper end Spinach.hooks.after_scenario do |scenario_data, step_definitions| diff --git a/lib/api/api.rb b/lib/api/api.rb index d767af36e8e..efcf0976a81 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -2,6 +2,8 @@ module API class API < Grape::API include APIGuard + allow_access_with_scope :api + version %w(v3 v4), using: :path version 'v3', using: :path do @@ -44,7 +46,6 @@ module API mount ::API::V3::Variables end - before { allow_access_with_scope :api } before { header['X-Frame-Options'] = 'SAMEORIGIN' } before { Gitlab::I18n.locale = current_user&.preferred_language } diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index 9fcf04efa38..0d2d71e336a 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -23,6 +23,23 @@ module API install_error_responders(base) end + class_methods do + # Set the authorization scope(s) allowed for an API endpoint. + # + # A call to this method maps the given scope(s) to the current API + # endpoint class. If this method is called multiple times on the same class, + # the scopes are all aggregated. + def allow_access_with_scope(scopes, options = {}) + Array(scopes).each do |scope| + allowed_scopes << Scope.new(scope, options) + end + end + + def allowed_scopes + @scopes ||= [] + end + end + # Helper Methods for Grape Endpoint module HelperMethods # Invokes the doorkeeper guard. @@ -47,7 +64,7 @@ module API access_token = find_access_token return nil unless access_token - case AccessTokenValidationService.new(access_token).validate(scopes: scopes) + case AccessTokenValidationService.new(access_token, request: request).validate(scopes: scopes) when AccessTokenValidationService::INSUFFICIENT_SCOPE raise InsufficientScopeError.new(scopes) @@ -74,18 +91,6 @@ module API @current_user end - # Set the authorization scope(s) allowed for the current request. - # - # Note: A call to this method adds to any previous scopes in place. This is done because - # `Grape` callbacks run from the outside-in: the top-level callback (API::API) runs first, then - # the next-level callback (API::API::Users, for example) runs. All these scopes are valid for the - # given endpoint (GET `/api/users` is accessible by the `api` and `read_user` scopes), and so they - # need to be stored. - def allow_access_with_scope(*scopes) - @scopes ||= [] - @scopes.concat(scopes.map(&:to_s)) - end - private def find_user_by_authentication_token(token_string) @@ -96,7 +101,7 @@ module API access_token = PersonalAccessToken.active.find_by_token(token_string) return unless access_token - if AccessTokenValidationService.new(access_token).include_any_scope?(scopes) + if AccessTokenValidationService.new(access_token, request: request).include_any_scope?(scopes) User.find(access_token.user_id) end end diff --git a/lib/api/commits.rb b/lib/api/commits.rb index c6fc17cc391..bcb842b9211 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -67,7 +67,7 @@ module API result = ::Files::MultiService.new(user_project, current_user, attrs).execute if result[:status] == :success - commit_detail = user_project.repository.commits(result[:result], limit: 1).first + commit_detail = user_project.repository.commit(result[:result]) present commit_detail, with: Entities::RepoCommitDetail else render_api_error!(result[:message], 400) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index cef5a0abe12..99eda3b0c4b 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -112,6 +112,7 @@ module API expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) && project.default_issues_tracker? } expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] } expose :public_builds, as: :public_jobs + expose :ci_config_path expose :shared_with_groups do |project, options| SharedGroup.represent(project.project_group_links.all, options) end @@ -434,7 +435,7 @@ module API target_url = "namespace_project_#{target_type}_url" target_anchor = "note_#{todo.note_id}" if todo.note_id? - Gitlab::Application.routes.url_helpers.public_send(target_url, + Gitlab::Routing.url_helpers.public_send(target_url, todo.project.namespace, todo.project, todo.target, anchor: target_anchor) end @@ -831,7 +832,7 @@ module API end class Cache < Grape::Entity - expose :key, :untracked, :paths + expose :key, :untracked, :paths, :policy end class Credentials < Grape::Entity diff --git a/lib/api/features.rb b/lib/api/features.rb index cff0ba2ddff..21745916463 100644 --- a/lib/api/features.rb +++ b/lib/api/features.rb @@ -2,6 +2,29 @@ module API class Features < Grape::API before { authenticated_as_admin! } + helpers do + def gate_value(params) + case params[:value] + when 'true' + true + when '0', 'false' + false + else + params[:value].to_i + end + end + + def gate_target(params) + if params[:feature_group] + Feature.group(params[:feature_group]) + elsif params[:user] + User.find_by_username(params[:user]) + else + gate_value(params) + end + end + end + resource :features do desc 'Get a list of all features' do success Entities::Feature @@ -17,16 +40,22 @@ module API end params do requires :value, type: String, desc: '`true` or `false` to enable/disable, an integer for percentage of time' + optional :feature_group, type: String, desc: 'A Feature group name' + optional :user, type: String, desc: 'A GitLab username' + mutually_exclusive :feature_group, :user end post ':name' do feature = Feature.get(params[:name]) + target = gate_target(params) + value = gate_value(params) - if %w(0 false).include?(params[:value]) - feature.disable - elsif params[:value] == 'true' - feature.enable + case value + when true + feature.enable(target) + when false + feature.disable(target) else - feature.enable_percentage_of_time(params[:value].to_i) + feature.enable_percentage_of_time(value) end present feature, with: Entities::Feature, current_user: current_user diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 2c73a6fdc4e..0f4791841d2 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -268,6 +268,7 @@ module API finder_params[:visibility_level] = Gitlab::VisibilityLevel.level_value(params[:visibility]) if params[:visibility] finder_params[:archived] = params[:archived] finder_params[:search] = params[:search] if params[:search] + finder_params[:user] = params.delete(:user) if params[:user] finder_params end @@ -313,7 +314,7 @@ module API def present_artifacts!(artifacts_file) return not_found! unless artifacts_file.exists? - + if artifacts_file.file_storage? present_file!(artifacts_file.path, artifacts_file.filename) else @@ -342,8 +343,8 @@ module API def initial_current_user return @initial_current_user if defined?(@initial_current_user) Gitlab::Auth::UniqueIpsLimiter.limit_user! do - @initial_current_user ||= find_user_by_private_token(scopes: @scopes) - @initial_current_user ||= doorkeeper_guard(scopes: @scopes) + @initial_current_user ||= find_user_by_private_token(scopes: scopes_registered_for_endpoint) + @initial_current_user ||= doorkeeper_guard(scopes: scopes_registered_for_endpoint) @initial_current_user ||= find_user_from_warden unless @initial_current_user && Gitlab::UserAccess.new(@initial_current_user).allowed? @@ -407,5 +408,22 @@ module API exception.status == 500 end + + # An array of scopes that were registered (using `allow_access_with_scope`) + # for the current endpoint class. It also returns scopes registered on + # `API::API`, since these are meant to apply to all API routes. + def scopes_registered_for_endpoint + @scopes_registered_for_endpoint ||= + begin + endpoint_classes = [options[:for].presence, ::API::API].compact + endpoint_classes.reduce([]) do |memo, endpoint| + if endpoint.respond_to?(:allowed_scopes) + memo.concat(endpoint.allowed_scopes) + else + memo + end + end + end + end end end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 886e97a2638..27d49eae844 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -1,4 +1,4 @@ -require 'declarative_policy' +require_dependency 'declarative_policy' module API # Projects API @@ -10,6 +10,7 @@ module API helpers do params :optional_params_ce do optional :description, type: String, desc: 'The description of the project' + optional :ci_config_path, type: String, desc: 'The path to CI config file. Defaults to `.gitlab-ci.yml`' 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' @@ -35,61 +36,78 @@ module API params :statistics_params do optional :statistics, type: Boolean, default: false, desc: 'Include project statistics' end - end - resource :projects do - helpers do - params :collection_params do - use :sort_params - use :filter_params - use :pagination - - optional :simple, type: Boolean, default: false, - desc: 'Return only the ID, URL, name, and path of each project' - end + params :collection_params do + use :sort_params + use :filter_params + use :pagination - 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 + optional :simple, type: Boolean, default: false, + desc: 'Return only the ID, URL, name, and path of each project' + end - params :filter_params do - optional :archived, type: Boolean, default: false, desc: 'Limit by archived status' - optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, - desc: 'Limit by visibility' - optional :search, type: String, desc: 'Return list of projects matching the search criteria' - optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user' - optional :starred, type: Boolean, default: false, desc: 'Limit by starred status' - optional :membership, type: Boolean, default: false, desc: 'Limit by projects that the current user is a member of' - optional :with_issues_enabled, type: Boolean, default: false, desc: 'Limit by enabled issues feature' - optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature' - end + 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 - 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 + params :filter_params do + optional :archived, type: Boolean, default: false, desc: 'Limit by archived status' + optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, + desc: 'Limit by visibility' + optional :search, type: String, desc: 'Return list of projects matching the search criteria' + optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user' + optional :starred, type: Boolean, default: false, desc: 'Limit by starred status' + optional :membership, type: Boolean, default: false, desc: 'Limit by projects that the current user is a member of' + optional :with_issues_enabled, type: Boolean, default: false, desc: 'Limit by enabled issues feature' + optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature' + end - def present_projects(options = {}) - projects = ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute - projects = reorder_projects(projects) - projects = projects.with_statistics if params[:statistics] - projects = projects.with_issues_enabled if params[:with_issues_enabled] - projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled] - - options = options.reverse_merge( - with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails, - statistics: params[:statistics], - current_user: current_user - ) - options[:with] = Entities::BasicProjectDetails if params[:simple] - - present paginate(projects), options - 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 + + def present_projects(options = {}) + projects = ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute + projects = reorder_projects(projects) + projects = projects.with_statistics if params[:statistics] + projects = projects.with_issues_enabled if params[:with_issues_enabled] + projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled] + + options = options.reverse_merge( + with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails, + statistics: params[:statistics], + current_user: current_user + ) + options[:with] = Entities::BasicProjectDetails if params[:simple] + + present paginate(projects), options end + end + resource :users, requirements: { user_id: %r{[^/]+} } do + desc 'Get a user projects' do + success Entities::BasicProjectDetails + end + params do + requires :user_id, type: String, desc: 'The ID or username of the user' + use :collection_params + use :statistics_params + end + get ":user_id/projects" do + user = find_user(params[:user_id]) + not_found!('User') unless user + + params[:user] = user + + present_projects + end + end + + resource :projects do desc 'Get a list of visible projects for authenticated user' do success Entities::BasicProjectDetails end diff --git a/lib/api/scope.rb b/lib/api/scope.rb new file mode 100644 index 00000000000..d5165b2e482 --- /dev/null +++ b/lib/api/scope.rb @@ -0,0 +1,23 @@ +# Encapsulate a scope used for authorization, such as `api`, or `read_user` +module API + class Scope + attr_reader :name, :if + + def initialize(name, options = {}) + @name = name.to_sym + @if = options[:if] + end + + # Are the `scopes` passed in sufficient to adequately authorize the passed + # request for the scope represented by the current instance of this class? + def sufficient?(scopes, request) + scopes.include?(self.name) && verify_if_condition(request) + end + + private + + def verify_if_condition(request) + self.if.nil? || self.if.call(request) + end + end +end diff --git a/lib/api/users.rb b/lib/api/users.rb index f9555842daf..88bca235692 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -1,13 +1,15 @@ module API class Users < Grape::API include PaginationParams + include APIGuard - before do - allow_access_with_scope :read_user if request.get? - authenticate! - end + allow_access_with_scope :read_user, if: -> (request) { request.get? } resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do + before do + authenticate_non_get! + end + helpers do def find_user(params) id = params[:user_id] || params[:id] @@ -51,15 +53,22 @@ module API use :pagination end get do - unless can?(current_user, :read_users_list) - render_api_error!("Not authorized.", 403) - end - authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?) users = UsersFinder.new(current_user, params).execute - entity = current_user.admin? ? Entities::UserWithAdmin : Entities::UserBasic + authorized = can?(current_user, :read_users_list) + + # When `current_user` is not present, require that the `username` + # parameter is passed, to prevent an unauthenticated user from accessing + # a list of all the users on the GitLab instance. `UsersFinder` performs + # an exact match on the `username` parameter, so we are guaranteed to + # get either 0 or 1 `users` here. + authorized &&= params[:username].present? if current_user.blank? + + forbidden!("Not authorized to access /api/v4/users") unless authorized + + entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic present paginate(users), with: entity end @@ -398,6 +407,10 @@ module API end resource :user do + before do + authenticate! + end + desc 'Get the currently authenticated user' do success Entities::UserPublic end diff --git a/lib/api/v3/users.rb b/lib/api/v3/users.rb index 37020019e07..cf106f2552d 100644 --- a/lib/api/v3/users.rb +++ b/lib/api/v3/users.rb @@ -2,9 +2,11 @@ module API module V3 class Users < Grape::API include PaginationParams + include APIGuard + + allow_access_with_scope :read_user, if: -> (request) { request.get? } before do - allow_access_with_scope :read_user if request.get? authenticate! end diff --git a/lib/api/variables.rb b/lib/api/variables.rb index 10374995497..7fa528fb2d3 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -45,7 +45,9 @@ module API optional :protected, type: String, desc: 'Whether the variable is protected' end post ':id/variables' do - variable = user_project.variables.create(declared_params(include_missing: false)) + variable_params = declared_params(include_missing: false) + + variable = user_project.variables.create(variable_params) if variable.valid? present variable, with: Entities::Variable @@ -67,7 +69,9 @@ module API return not_found!('Variable') unless variable - if variable.update(declared_params(include_missing: false).except(:key)) + variable_params = declared_params(include_missing: false).except(:key) + + if variable.update(variable_params) present variable, with: Entities::Variable else render_validation_error!(variable) diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index 8bc2dd18bda..7a262dd025c 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -216,12 +216,7 @@ module Banzai @references_per_project ||= begin refs = Hash.new { |hash, key| hash[key] = Set.new } - regex = - if uses_reference_pattern? - Regexp.union(object_class.reference_pattern, object_class.link_reference_pattern) - else - object_class.link_reference_pattern - end + regex = Regexp.union(object_class.reference_pattern, object_class.link_reference_pattern) nodes.each do |node| node.to_html.scan(regex) do @@ -323,14 +318,6 @@ module Banzai value end end - - # There might be special cases like filters - # that should ignore reference pattern - # eg: IssueReferenceFilter when using a external issues tracker - # In those cases this method should be overridden on the filter subclass - def uses_reference_pattern? - true - end end end end diff --git a/lib/banzai/filter/commit_range_reference_filter.rb b/lib/banzai/filter/commit_range_reference_filter.rb index eaacb9591b1..21bcb1c5ca8 100644 --- a/lib/banzai/filter/commit_range_reference_filter.rb +++ b/lib/banzai/filter/commit_range_reference_filter.rb @@ -30,7 +30,7 @@ module Banzai def url_for_object(range, project) h = Gitlab::Routing.url_helpers - h.namespace_project_compare_url(project.namespace, project, + h.project_compare_url(project, range.to_param.merge(only_path: context[:only_path])) end diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb index 69c06117eda..714e0319025 100644 --- a/lib/banzai/filter/commit_reference_filter.rb +++ b/lib/banzai/filter/commit_reference_filter.rb @@ -24,7 +24,7 @@ module Banzai def url_for_object(commit, project) h = Gitlab::Routing.url_helpers - h.namespace_project_commit_url(project.namespace, project, commit, + h.project_commit_url(project, commit, only_path: context[:only_path]) end diff --git a/lib/banzai/filter/external_issue_reference_filter.rb b/lib/banzai/filter/external_issue_reference_filter.rb index dce4de3ceaf..53a229256a5 100644 --- a/lib/banzai/filter/external_issue_reference_filter.rb +++ b/lib/banzai/filter/external_issue_reference_filter.rb @@ -3,6 +3,8 @@ module Banzai # HTML filter that replaces external issue tracker references with links. # References are ignored if the project doesn't use an external issue # tracker. + # + # This filter does not support cross-project references. class ExternalIssueReferenceFilter < ReferenceFilter self.reference_type = :external_issue @@ -87,7 +89,7 @@ module Banzai end def issue_reference_pattern - external_issues_cached(:issue_reference_pattern) + external_issues_cached(:external_issue_reference_pattern) end private diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb index 044d18ff824..ba1a5ac84b3 100644 --- a/lib/banzai/filter/issue_reference_filter.rb +++ b/lib/banzai/filter/issue_reference_filter.rb @@ -15,10 +15,6 @@ module Banzai Issue end - def uses_reference_pattern? - context[:project].default_issues_tracker? - end - def find_object(project, iid) issues_per_project[project][iid] end @@ -38,13 +34,7 @@ module Banzai projects_per_reference.each do |path, project| issue_ids = references_per_project[path] - - issues = - if project.default_issues_tracker? - project.issues.where(iid: issue_ids.to_a) - else - issue_ids.map { |id| ExternalIssue.new(id, project) } - end + issues = project.issues.where(iid: issue_ids.to_a) issues.each do |issue| hash[project][issue.iid.to_i] = issue @@ -55,26 +45,6 @@ module Banzai end end - def object_link_title(object) - if object.is_a?(ExternalIssue) - "Issue in #{object.project.external_issue_tracker.title}" - else - super - end - end - - def data_attributes_for(text, project, object, link: false) - if object.is_a?(ExternalIssue) - data_attribute( - project: project.id, - external_issue: object.id, - reference_type: ExternalIssueReferenceFilter.reference_type - ) - else - super - end - end - def projects_relation_for_paths(paths) super(paths).includes(:gitlab_issue_tracker_service) end diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index a605dea149e..5364984c9d3 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -61,8 +61,7 @@ module Banzai def url_for_object(label, project) h = Gitlab::Routing.url_helpers - h.namespace_project_issues_url(project.namespace, project, label_name: label.name, - only_path: context[:only_path]) + h.project_issues_url(project, label_name: label.name, only_path: context[:only_path]) end def object_link_text(object, matches) diff --git a/lib/banzai/filter/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb index 3888acf935e..0eab865ac04 100644 --- a/lib/banzai/filter/merge_request_reference_filter.rb +++ b/lib/banzai/filter/merge_request_reference_filter.rb @@ -17,7 +17,7 @@ module Banzai def url_for_object(mr, project) h = Gitlab::Routing.url_helpers - h.namespace_project_merge_request_url(project.namespace, project, mr, + h.project_merge_request_url(project, mr, only_path: context[:only_path]) end diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb index f12014e191f..45c033d32a8 100644 --- a/lib/banzai/filter/milestone_reference_filter.rb +++ b/lib/banzai/filter/milestone_reference_filter.rb @@ -49,7 +49,7 @@ module Banzai def url_for_object(milestone, project) h = Gitlab::Routing.url_helpers - h.namespace_project_milestone_url(project.namespace, project, milestone, + h.project_milestone_url(project, milestone, only_path: context[:only_path]) end diff --git a/lib/banzai/filter/snippet_reference_filter.rb b/lib/banzai/filter/snippet_reference_filter.rb index 212a0bbf2a0..134a192c22b 100644 --- a/lib/banzai/filter/snippet_reference_filter.rb +++ b/lib/banzai/filter/snippet_reference_filter.rb @@ -17,7 +17,7 @@ module Banzai def url_for_object(snippet, project) h = Gitlab::Routing.url_helpers - h.namespace_project_snippet_url(project.namespace, project, snippet, + h.project_snippet_url(project, snippet, only_path: context[:only_path]) end end diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb index a798927823f..f3356d6c51e 100644 --- a/lib/banzai/filter/user_reference_filter.rb +++ b/lib/banzai/filter/user_reference_filter.rb @@ -107,7 +107,7 @@ module Banzai if author && !project.team.member?(author) link_content else - url = urls.namespace_project_url(project.namespace, project, + url = urls.project_url(project, only_path: context[:only_path]) data = data_attribute(project: project.id, author: author.try(:id)) diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb index 9fd4bd68d43..a65bbe23958 100644 --- a/lib/banzai/reference_parser/issue_parser.rb +++ b/lib/banzai/reference_parser/issue_parser.rb @@ -4,9 +4,6 @@ module Banzai self.reference_type = :issue def nodes_visible_to_user(user, nodes) - # It is not possible to check access rights for external issue trackers - return nodes if project && project.external_issue_tracker - issues = issues_for_nodes(nodes) readable_issues = Ability diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb index dd864eea3fa..721ed97bb6b 100644 --- a/lib/extracts_path.rb +++ b/lib/extracts_path.rb @@ -126,8 +126,7 @@ module ExtractsPath raise InvalidPathError unless @commit @hex_path = Digest::SHA1.hexdigest(@path) - @logs_path = logs_file_namespace_project_ref_path(@project.namespace, - @project, @ref, @path) + @logs_path = logs_file_project_ref_path(@project, @ref, @path) rescue RuntimeError, NoMethodError, InvalidPathError render_404 diff --git a/lib/feature.rb b/lib/feature.rb index d3d972564af..363f66ba60e 100644 --- a/lib/feature.rb +++ b/lib/feature.rb @@ -12,6 +12,8 @@ class Feature end class << self + delegate :group, to: :flipper + def all flipper.features.to_a end @@ -27,16 +29,24 @@ class Feature all.map(&:name).include?(feature.name) end - def enabled?(key) - get(key).enabled? + def enabled?(key, thing = nil) + get(key).enabled?(thing) + end + + def enable(key, thing = true) + get(key).enable(thing) + end + + def disable(key, thing = false) + get(key).disable(thing) end - def enable(key) - get(key).enable + def enable_group(key, group) + get(key).enable_group(group) end - def disable(key) - get(key).disable + def disable_group(key, group) + get(key).disable_group(group) end def flipper diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 3933c3b04dd..ccb5d886bab 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -130,13 +130,13 @@ module Gitlab token = PersonalAccessTokensFinder.new(state: 'active').find_by(token: password) - if token && valid_scoped_token?(token, AVAILABLE_SCOPES.map(&:to_s)) + if token && valid_scoped_token?(token, AVAILABLE_SCOPES) Gitlab::Auth::Result.new(token.user, nil, :personal_token, abilities_for_scope(token.scopes)) end end def valid_oauth_token?(token) - token && token.accessible? && valid_scoped_token?(token, ["api"]) + token && token.accessible? && valid_scoped_token?(token, [:api]) end def valid_scoped_token?(token, scopes) diff --git a/lib/gitlab/badge/build/metadata.rb b/lib/gitlab/badge/build/metadata.rb index f87a7b7942e..2ee35a0d4c1 100644 --- a/lib/gitlab/badge/build/metadata.rb +++ b/lib/gitlab/badge/build/metadata.rb @@ -15,12 +15,11 @@ module Gitlab end def image_url - build_namespace_project_badges_url(@project.namespace, - @project, @ref, format: :svg) + build_project_badges_url(@project, @ref, format: :svg) end def link_url - namespace_project_commits_url(@project.namespace, @project, id: @ref) + project_commits_url(@project, id: @ref) end end end diff --git a/lib/gitlab/badge/coverage/metadata.rb b/lib/gitlab/badge/coverage/metadata.rb index 53588185622..e898f5d790e 100644 --- a/lib/gitlab/badge/coverage/metadata.rb +++ b/lib/gitlab/badge/coverage/metadata.rb @@ -16,13 +16,11 @@ module Gitlab end def image_url - coverage_namespace_project_badges_url(@project.namespace, - @project, @ref, - format: :svg) + coverage_project_badges_url(@project, @ref, format: :svg) end def link_url - namespace_project_commits_url(@project.namespace, @project, id: @ref) + project_commits_url(@project, @ref) end end end diff --git a/lib/gitlab/badge/metadata.rb b/lib/gitlab/badge/metadata.rb index 4a049ef758d..86c193650fb 100644 --- a/lib/gitlab/badge/metadata.rb +++ b/lib/gitlab/badge/metadata.rb @@ -4,7 +4,7 @@ module Gitlab # Abstract class for badge metadata # class Metadata - include Gitlab::Application.routes.url_helpers + include Gitlab::Routing.url_helpers include ActionView::Helpers::AssetTagHelper include ActionView::Helpers::UrlHelper diff --git a/lib/gitlab/ci/config/entry/cache.rb b/lib/gitlab/ci/config/entry/cache.rb index f074df9c7a1..d7e09acbbf3 100644 --- a/lib/gitlab/ci/config/entry/cache.rb +++ b/lib/gitlab/ci/config/entry/cache.rb @@ -7,11 +7,14 @@ module Gitlab # class Cache < Node include Configurable + include Attributable - ALLOWED_KEYS = %i[key untracked paths].freeze + ALLOWED_KEYS = %i[key untracked paths policy].freeze + DEFAULT_POLICY = 'pull-push'.freeze validations do validates :config, allowed_keys: ALLOWED_KEYS + validates :policy, inclusion: { in: %w[pull-push push pull], message: 'should be pull-push, push, or pull' }, allow_blank: true end entry :key, Entry::Key, @@ -25,8 +28,15 @@ module Gitlab helpers :key + attributes :policy + def value - super.merge(key: key_value) + result = super + + result[:key] = key_value + result[:policy] = policy || DEFAULT_POLICY + + result end end end diff --git a/lib/gitlab/ci/config/entry/image.rb b/lib/gitlab/ci/config/entry/image.rb index 897dcff8012..6555c589173 100644 --- a/lib/gitlab/ci/config/entry/image.rb +++ b/lib/gitlab/ci/config/entry/image.rb @@ -15,7 +15,7 @@ module Gitlab validates :config, allowed_keys: ALLOWED_KEYS validates :name, type: String, presence: true - validates :entrypoint, type: String, allow_nil: true + validates :entrypoint, array_of_strings: true, allow_nil: true end def hash? diff --git a/lib/gitlab/ci/config/entry/service.rb b/lib/gitlab/ci/config/entry/service.rb index b52faf48b58..3e2ebcff31a 100644 --- a/lib/gitlab/ci/config/entry/service.rb +++ b/lib/gitlab/ci/config/entry/service.rb @@ -15,8 +15,8 @@ module Gitlab validates :config, allowed_keys: ALLOWED_KEYS validates :name, type: String, presence: true - validates :entrypoint, type: String, allow_nil: true - validates :command, type: String, allow_nil: true + validates :entrypoint, array_of_strings: true, allow_nil: true + validates :command, array_of_strings: true, allow_nil: true validates :alias, type: String, allow_nil: true end diff --git a/lib/gitlab/ci/status/build/cancelable.rb b/lib/gitlab/ci/status/build/cancelable.rb index 439ef0ce015..8ad3e57e59d 100644 --- a/lib/gitlab/ci/status/build/cancelable.rb +++ b/lib/gitlab/ci/status/build/cancelable.rb @@ -12,9 +12,7 @@ module Gitlab end def action_path - cancel_namespace_project_job_path(subject.project.namespace, - subject.project, - subject) + cancel_project_job_path(subject.project, subject) end def action_method diff --git a/lib/gitlab/ci/status/build/common.rb b/lib/gitlab/ci/status/build/common.rb index b173c23fba4..c0c7c7f5b5d 100644 --- a/lib/gitlab/ci/status/build/common.rb +++ b/lib/gitlab/ci/status/build/common.rb @@ -8,9 +8,7 @@ module Gitlab end def details_path - namespace_project_job_path(subject.project.namespace, - subject.project, - subject) + project_job_path(subject.project, subject) end end end diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb index e80f3263794..c7726543599 100644 --- a/lib/gitlab/ci/status/build/play.rb +++ b/lib/gitlab/ci/status/build/play.rb @@ -20,9 +20,7 @@ module Gitlab end def action_path - play_namespace_project_job_path(subject.project.namespace, - subject.project, - subject) + play_project_job_path(subject.project, subject) end def action_method diff --git a/lib/gitlab/ci/status/build/retryable.rb b/lib/gitlab/ci/status/build/retryable.rb index 56303e4cb17..8c8fdc56d75 100644 --- a/lib/gitlab/ci/status/build/retryable.rb +++ b/lib/gitlab/ci/status/build/retryable.rb @@ -16,9 +16,7 @@ module Gitlab end def action_path - retry_namespace_project_job_path(subject.project.namespace, - subject.project, - subject) + retry_project_job_path(subject.project, subject) end def action_method diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb index 2778d6f3b52..d464738deaf 100644 --- a/lib/gitlab/ci/status/build/stop.rb +++ b/lib/gitlab/ci/status/build/stop.rb @@ -20,9 +20,7 @@ module Gitlab end def action_path - play_namespace_project_job_path(subject.project.namespace, - subject.project, - subject) + play_project_job_path(subject.project, subject) end def action_method diff --git a/lib/gitlab/ci/status/pipeline/common.rb b/lib/gitlab/ci/status/pipeline/common.rb index 76bfd18bf40..61bb07beb0f 100644 --- a/lib/gitlab/ci/status/pipeline/common.rb +++ b/lib/gitlab/ci/status/pipeline/common.rb @@ -8,9 +8,7 @@ module Gitlab end def details_path - namespace_project_pipeline_path(subject.project.namespace, - subject.project, - subject) + project_pipeline_path(subject.project, subject) end def has_action? diff --git a/lib/gitlab/ci/status/stage/common.rb b/lib/gitlab/ci/status/stage/common.rb index 7852f492e1d..bc99d925347 100644 --- a/lib/gitlab/ci/status/stage/common.rb +++ b/lib/gitlab/ci/status/stage/common.rb @@ -8,10 +8,7 @@ module Gitlab end def details_path - namespace_project_pipeline_path(subject.project.namespace, - subject.project, - subject.pipeline, - anchor: subject.name) + project_pipeline_path(subject.project, subject.pipeline, anchor: subject.name) end def has_action? diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 75a213ef752..d2b4e6e209e 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -205,9 +205,7 @@ module Gitlab old_path: their_path, new_path: our_path, blob_icon: file_type_icon_class('file', our_mode, our_path), - blob_path: namespace_project_blob_path(merge_request.project.namespace, - merge_request.project, - ::File.join(merge_request.diff_refs.head_sha, our_path)) + blob_path: project_blob_path(merge_request.project, ::File.join(merge_request.diff_refs.head_sha, our_path)) } json_hash.tap do |json_hash| @@ -223,11 +221,10 @@ module Gitlab end def content_path - conflict_for_path_namespace_project_merge_request_path(merge_request.project.namespace, - merge_request.project, - merge_request, - old_path: their_path, - new_path: our_path) + conflict_for_path_project_merge_request_path(merge_request.project, + merge_request, + old_path: their_path, + new_path: our_path) end # Don't try to print merge_request or repository. diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb index ea035e33eff..42fc2a4ea19 100644 --- a/lib/gitlab/email/message/repository_push.rb +++ b/lib/gitlab/email/message/repository_push.rb @@ -96,20 +96,13 @@ module Gitlab def target_url if @action == :push && commits if commits.length > 1 - namespace_project_compare_url(project_namespace, - project, - from: compare.start_commit, - to: compare.head_commit) + project_compare_url(project, from: compare.start_commit, to: compare.head_commit) else - namespace_project_commit_url(project_namespace, - project, - commits.first) + project_commit_url(project, commits.first) end else unless @action == :delete - namespace_project_tree_url(project_namespace, - project, - ref_name) + project_tree_url(project, ref_name) end end end diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index a7aceab4c14..ffe4f3ca95f 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -175,6 +175,10 @@ module Gitlab encode! @name end + def path + encode! @path + end + def truncated? size && (size > loaded_size) end diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb index bd90d24a2ec..5042916343b 100644 --- a/lib/gitlab/git/hook.rb +++ b/lib/gitlab/git/hook.rb @@ -4,9 +4,10 @@ module Gitlab GL_PROTOCOL = 'web'.freeze attr_reader :name, :repo_path, :path - def initialize(name, repo_path) + def initialize(name, project) @name = name - @repo_path = repo_path + @project = project + @repo_path = project.repository.path @path = File.join(repo_path.strip, 'hooks', name) end @@ -38,7 +39,8 @@ module Gitlab vars = { 'GL_ID' => gl_id, 'PWD' => repo_path, - 'GL_PROTOCOL' => GL_PROTOCOL + 'GL_PROTOCOL' => GL_PROTOCOL, + 'GL_REPOSITORY' => Gitlab::GlRepository.gl_repository(@project, false) } options = { diff --git a/lib/gitlab/git/index.rb b/lib/gitlab/git/index.rb index 1add037fa5f..666743006e5 100644 --- a/lib/gitlab/git/index.rb +++ b/lib/gitlab/git/index.rb @@ -110,10 +110,6 @@ module Gitlab if segment == '..' raise IndexError, 'Path cannot include directory traversal' end - - unless segment =~ Gitlab::Regex.file_name_regex - raise IndexError, "Path #{Gitlab::Regex.file_name_regex_message}" - end end pathname.to_s diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 23d0c8a9bdb..dd5a4d5ad55 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -554,11 +554,14 @@ module Gitlab # # => git@localhost:rack.git # def submodule_url_for(ref, path) - if submodules(ref).any? - submodule = submodules(ref)[path] - - if submodule - submodule['url'] + Gitlab::GitalyClient.migrate(:submodule_url_for) do |is_enabled| + if is_enabled + gitaly_submodule_url_for(ref, path) + else + if submodules(ref).any? + submodule = submodules(ref)[path] + submodule['url'] if submodule + end end end end @@ -915,6 +918,18 @@ module Gitlab fill_submodule_ids(commit, parser.parse) end + def gitaly_submodule_url_for(ref, path) + # We don't care about the contents so 1 byte is enough. Can't request 0 bytes, 0 means unlimited. + commit_object = gitaly_commit_client.tree_entry(ref, path, 1) + + return unless commit_object && commit_object.type == :COMMIT + + gitmodules = gitaly_commit_client.tree_entry(ref, '.gitmodules', Blob::MAX_DATA_DISPLAY_SIZE) + found_module = GitmodulesParser.new(gitmodules.data).parse[path] + + found_module && found_module['url'] + end + def alternate_object_directories Gitlab::Git::Env.all.values_at(*ALLOWED_OBJECT_DIRECTORIES_VARIABLES).compact end diff --git a/lib/gitlab/git/tree.rb b/lib/gitlab/git/tree.rb index b9afa05c819..b6d4e6cfe46 100644 --- a/lib/gitlab/git/tree.rb +++ b/lib/gitlab/git/tree.rb @@ -80,6 +80,10 @@ module Gitlab encode! @name end + def path + encode! @path + end + def dir? type == :tree end diff --git a/lib/gitlab/gitaly_client/ref.rb b/lib/gitlab/gitaly_client/ref.rb index 2d61992f595..6edc69de078 100644 --- a/lib/gitlab/gitaly_client/ref.rb +++ b/lib/gitlab/gitaly_client/ref.rb @@ -34,7 +34,7 @@ module Gitlab commit_id: commit_id, prefix: ref_prefix ) - GitalyClient.call(@storage, :ref, :find_ref_name, request).name + encode!(GitalyClient.call(@storage, :ref, :find_ref_name, request).name.dup) end def count_tag_names diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index 319633656ff..2d1ae6a5925 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -2,11 +2,14 @@ module Gitlab module GonHelper + include WebpackHelper + def add_gon_variables gon.api_version = 'v4' gon.default_avatar_url = URI.join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s gon.max_file_size = current_application_settings.max_attachment_size gon.asset_host = ActionController::Base.asset_host + gon.webpack_public_path = webpack_public_path gon.relative_url_root = Gitlab.config.gitlab.relative_url_root gon.shortcuts_path = help_page_path('shortcuts') gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class diff --git a/lib/gitlab/health_checks/fs_shards_check.rb b/lib/gitlab/health_checks/fs_shards_check.rb index e78b7f22e03..70da4080cae 100644 --- a/lib/gitlab/health_checks/fs_shards_check.rb +++ b/lib/gitlab/health_checks/fs_shards_check.rb @@ -52,7 +52,7 @@ module Gitlab ] end rescue RuntimeError => ex - Rails.logger("unexpected error #{ex} when checking #{ok_metric}") + Rails.logger.error("unexpected error #{ex} when checking #{ok_metric}") [metric(ok_metric, 0, **labels)] end diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index db7cdf4b5c7..f3d489aad0d 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -12,7 +12,8 @@ module Gitlab 'zh_HK' => 'ç¹é«”ä¸æ–‡(香港)', 'zh_TW' => 'ç¹é«”ä¸æ–‡(臺ç£)', 'bg' => 'българÑки', - 'eo' => 'Esperanto' + 'eo' => 'Esperanto', + 'it' => 'Italiano' }.freeze def available_locales diff --git a/lib/gitlab/kubernetes.rb b/lib/gitlab/kubernetes.rb index c56c1a4322f..cdbdfa10d0e 100644 --- a/lib/gitlab/kubernetes.rb +++ b/lib/gitlab/kubernetes.rb @@ -76,5 +76,44 @@ module Gitlab url.to_s end + + def to_kubeconfig(url:, namespace:, token:, ca_pem: nil) + config = { + apiVersion: 'v1', + clusters: [ + name: 'gitlab-deploy', + cluster: { + server: url + } + ], + contexts: [ + name: 'gitlab-deploy', + context: { + cluster: 'gitlab-deploy', + namespace: namespace, + user: 'gitlab-deploy' + } + ], + 'current-context': 'gitlab-deploy', + kind: 'Config', + users: [ + { + name: 'gitlab-deploy', + user: { token: token } + } + ] + } + + kubeconfig_embed_ca_pem(config, ca_pem) if ca_pem + + config.deep_stringify_keys + end + + private + + def kubeconfig_embed_ca_pem(config, ca_pem) + cluster = config.dig(:clusters, 0, :cluster) + cluster[:'certificate-authority-data'] = Base64.encode64(ca_pem) + end end end diff --git a/lib/gitlab/metrics/base_sampler.rb b/lib/gitlab/metrics/base_sampler.rb new file mode 100644 index 00000000000..219accfc029 --- /dev/null +++ b/lib/gitlab/metrics/base_sampler.rb @@ -0,0 +1,94 @@ +require 'logger' +module Gitlab + module Metrics + class BaseSampler + def self.initialize_instance(*args) + raise "#{name} singleton instance already initialized" if @instance + @instance = new(*args) + at_exit(&@instance.method(:stop)) + @instance + end + + def self.instance + @instance + end + + attr_reader :running + + # interval - The sampling interval in seconds. + def initialize(interval) + interval_half = interval.to_f / 2 + + @interval = interval + @interval_steps = (-interval_half..interval_half).step(0.1).to_a + + @mutex = Mutex.new + end + + def enabled? + true + end + + def start + return unless enabled? + + @mutex.synchronize do + return if running + @running = true + + @thread = Thread.new do + sleep(sleep_interval) + + while running + safe_sample + + sleep(sleep_interval) + end + end + end + end + + def stop + @mutex.synchronize do + return unless running + + @running = false + + if @thread + @thread.wakeup if @thread.alive? + @thread.join + @thread = nil + end + end + end + + def safe_sample + sample + rescue => e + Rails.logger.warn("#{self.class}: #{e}, stopping") + stop + end + + def sample + raise NotImplementedError + end + + # Returns the sleep interval with a random adjustment. + # + # The random adjustment is put in place to ensure we: + # + # 1. Don't generate samples at the exact same interval every time (thus + # potentially missing anything that happens in between samples). + # 2. Don't sample data at the same interval two times in a row. + def sleep_interval + while step = @interval_steps.sample + if step != @last_step + @last_step = step + + return @interval + @last_step + end + end + end + end + end +end diff --git a/lib/gitlab/metrics/connection_rack_middleware.rb b/lib/gitlab/metrics/connection_rack_middleware.rb new file mode 100644 index 00000000000..b3da360be8f --- /dev/null +++ b/lib/gitlab/metrics/connection_rack_middleware.rb @@ -0,0 +1,45 @@ +module Gitlab + module Metrics + class ConnectionRackMiddleware + def initialize(app) + @app = app + end + + def self.rack_request_count + @rack_request_count ||= Gitlab::Metrics.counter(:rack_request, 'Rack request count') + end + + def self.rack_response_count + @rack_response_count ||= Gitlab::Metrics.counter(:rack_response, 'Rack response count') + end + + def self.rack_uncaught_errors_count + @rack_uncaught_errors_count ||= Gitlab::Metrics.counter(:rack_uncaught_errors, 'Rack connections handling uncaught errors count') + end + + def self.rack_execution_time + @rack_execution_time ||= Gitlab::Metrics.histogram(:rack_execution_time, 'Rack connection handling execution time', + {}, [0.05, 0.1, 0.25, 0.5, 0.7, 1, 1.5, 2, 2.5, 3, 5, 7, 10]) + end + + def call(env) + method = env['REQUEST_METHOD'].downcase + started = Time.now.to_f + begin + ConnectionRackMiddleware.rack_request_count.increment(method: method) + + status, headers, body = @app.call(env) + + ConnectionRackMiddleware.rack_response_count.increment(method: method, status: status) + [status, headers, body] + rescue + ConnectionRackMiddleware.rack_uncaught_errors_count.increment + raise + ensure + elapsed = Time.now.to_f - started + ConnectionRackMiddleware.rack_execution_time.observe({}, elapsed) + end + end + end + end +end diff --git a/lib/gitlab/metrics/sampler.rb b/lib/gitlab/metrics/influx_sampler.rb index 0000450d9bb..6db1dd755b7 100644 --- a/lib/gitlab/metrics/sampler.rb +++ b/lib/gitlab/metrics/influx_sampler.rb @@ -5,14 +5,11 @@ module Gitlab # This class is used to gather statistics that can't be directly associated # with a transaction such as system memory usage, garbage collection # statistics, etc. - class Sampler + class InfluxSampler < BaseSampler # interval - The sampling interval in seconds. def initialize(interval = Metrics.settings[:sample_interval]) - interval_half = interval.to_f / 2 - - @interval = interval - @interval_steps = (-interval_half..interval_half).step(0.1).to_a - @last_step = nil + super(interval) + @last_step = nil @metrics = [] @@ -26,18 +23,6 @@ module Gitlab end end - def start - Thread.new do - Thread.current.abort_on_exception = true - - loop do - sleep(sleep_interval) - - sample - end - end - end - def sample sample_memory_usage sample_file_descriptors @@ -86,7 +71,7 @@ module Gitlab end def sample_gc - time = GC::Profiler.total_time * 1000.0 + time = GC::Profiler.total_time * 1000.0 stats = GC.stat.merge(total_time: time) # We want the difference of GC runs compared to the last sample, not the @@ -111,23 +96,6 @@ module Gitlab def sidekiq? Sidekiq.server? end - - # Returns the sleep interval with a random adjustment. - # - # The random adjustment is put in place to ensure we: - # - # 1. Don't generate samples at the exact same interval every time (thus - # potentially missing anything that happens in between samples). - # 2. Don't sample data at the same interval two times in a row. - def sleep_interval - while step = @interval_steps.sample - if step != @last_step - @last_step = step - - return @interval + @last_step - end - end - end end end end diff --git a/lib/gitlab/metrics/prometheus.rb b/lib/gitlab/metrics/prometheus.rb index 9d314a56e58..fb7bbc7cfc7 100644 --- a/lib/gitlab/metrics/prometheus.rb +++ b/lib/gitlab/metrics/prometheus.rb @@ -29,8 +29,8 @@ module Gitlab provide_metric(name) || registry.summary(name, docstring, base_labels) end - def gauge(name, docstring, base_labels = {}) - provide_metric(name) || registry.gauge(name, docstring, base_labels) + def gauge(name, docstring, base_labels = {}, multiprocess_mode = :all) + provide_metric(name) || registry.gauge(name, docstring, base_labels, multiprocess_mode) end def histogram(name, docstring, base_labels = {}, buckets = ::Prometheus::Client::Histogram::DEFAULT_BUCKETS) diff --git a/lib/gitlab/metrics/unicorn_sampler.rb b/lib/gitlab/metrics/unicorn_sampler.rb new file mode 100644 index 00000000000..f6987252039 --- /dev/null +++ b/lib/gitlab/metrics/unicorn_sampler.rb @@ -0,0 +1,48 @@ +module Gitlab + module Metrics + class UnicornSampler < BaseSampler + def initialize(interval) + super(interval) + end + + def unicorn_active_connections + @unicorn_active_connections ||= Gitlab::Metrics.gauge(:unicorn_active_connections, 'Unicorn active connections', {}, :max) + end + + def unicorn_queued_connections + @unicorn_queued_connections ||= Gitlab::Metrics.gauge(:unicorn_queued_connections, 'Unicorn queued connections', {}, :max) + end + + def enabled? + # Raindrops::Linux.tcp_listener_stats is only present on Linux + unicorn_with_listeners? && Raindrops::Linux.respond_to?(:tcp_listener_stats) + end + + def sample + Raindrops::Linux.tcp_listener_stats(tcp_listeners).each do |addr, stats| + unicorn_active_connections.set({ type: 'tcp', address: addr }, stats.active) + unicorn_queued_connections.set({ type: 'tcp', address: addr }, stats.queued) + end + + Raindrops::Linux.unix_listener_stats(unix_listeners).each do |addr, stats| + unicorn_active_connections.set({ type: 'unix', address: addr }, stats.active) + unicorn_queued_connections.set({ type: 'unix', address: addr }, stats.queued) + end + end + + private + + def tcp_listeners + @tcp_listeners ||= Unicorn.listener_names.grep(%r{\A[^/]+:\d+\z}) + end + + def unix_listeners + @unix_listeners ||= Unicorn.listener_names - tcp_listeners + end + + def unicorn_with_listeners? + defined?(Unicorn) && Unicorn.listener_names.any? + end + end + end +end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index b706434217d..c1ee20b6977 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -19,14 +19,6 @@ module Gitlab "It must start with letter, digit, emoji or '_'." end - def file_name_regex - @file_name_regex ||= /\A[[[:alnum:]]_\-\.\@\+]*\z/.freeze - end - - def file_name_regex_message - "can contain only letters, digits, '_', '-', '@', '+' and '.'." - end - def container_registry_reference_regex Gitlab::PathRegex.git_reference_regex end @@ -38,8 +30,12 @@ module Gitlab @container_repository_regex ||= %r{\A[a-z0-9]+(?:[-._/][a-z0-9]+)*\Z} end + def environment_name_regex_chars + 'a-zA-Z0-9_/\\$\\{\\}\\. -' + end + def environment_name_regex - @environment_name_regex ||= /\A[a-zA-Z0-9_\\\/\${}. -]+\z/.freeze + @environment_name_regex ||= /\A[#{environment_name_regex_chars}]+\z/.freeze end def environment_name_regex_message diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 22554236c38..0baea092e6a 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -2,6 +2,8 @@ require 'securerandom' module Gitlab class Shell + GITLAB_SHELL_ENV_VARS = %w(GIT_TERMINAL_PROMPT).freeze + Error = Class.new(StandardError) KeyAdder = Struct.new(:io) do @@ -67,8 +69,8 @@ module Gitlab # add_repository("/path/to/storage", "gitlab/gitlab-ci") # def add_repository(storage, name) - Gitlab::Utils.system_silent([gitlab_shell_projects_path, - 'add-project', storage, "#{name}.git"]) + gitlab_shell_fast_execute([gitlab_shell_projects_path, + 'add-project', storage, "#{name}.git"]) end # Import repository @@ -82,10 +84,9 @@ module Gitlab def import_repository(storage, name, url) # Timeout should be less than 900 ideally, to prevent the memory killer # to silently kill the process without knowing we are timing out here. - output, status = Popen.popen([gitlab_shell_projects_path, 'import-project', - storage, "#{name}.git", url, "#{Gitlab.config.gitlab_shell.git_timeout}"]) - raise Error, output unless status.zero? - true + cmd = [gitlab_shell_projects_path, 'import-project', + storage, "#{name}.git", url, "#{Gitlab.config.gitlab_shell.git_timeout}"] + gitlab_shell_fast_execute_raise_error(cmd) end # Fetch remote for repository @@ -103,9 +104,7 @@ module Gitlab args << '--force' if forced args << '--no-tags' if no_tags - output, status = Popen.popen(args) - raise Error, output unless status.zero? - true + gitlab_shell_fast_execute_raise_error(args) end # Move repository @@ -117,8 +116,8 @@ module Gitlab # mv_repository("/path/to/storage", "gitlab/gitlab-ci", "randx/gitlab-ci-new") # def mv_repository(storage, path, new_path) - Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project', - storage, "#{path}.git", "#{new_path}.git"]) + gitlab_shell_fast_execute([gitlab_shell_projects_path, 'mv-project', + storage, "#{path}.git", "#{new_path}.git"]) end # Fork repository to new namespace @@ -131,9 +130,9 @@ module Gitlab # fork_repository("/path/to/forked_from/storage", "gitlab/gitlab-ci", "/path/to/forked_to/storage", "randx") # def fork_repository(forked_from_storage, path, forked_to_storage, fork_namespace) - Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'fork-project', - forked_from_storage, "#{path}.git", forked_to_storage, - fork_namespace]) + gitlab_shell_fast_execute([gitlab_shell_projects_path, 'fork-project', + forked_from_storage, "#{path}.git", forked_to_storage, + fork_namespace]) end # Remove repository from file system @@ -145,8 +144,8 @@ module Gitlab # remove_repository("/path/to/storage", "gitlab/gitlab-ci") # def remove_repository(storage, name) - Gitlab::Utils.system_silent([gitlab_shell_projects_path, - 'rm-project', storage, "#{name}.git"]) + gitlab_shell_fast_execute([gitlab_shell_projects_path, + 'rm-project', storage, "#{name}.git"]) end # Add new key to gitlab-shell @@ -155,8 +154,8 @@ module Gitlab # add_key("key-42", "sha-rsa ...") # def add_key(key_id, key_content) - Gitlab::Utils.system_silent([gitlab_shell_keys_path, - 'add-key', key_id, self.class.strip_key(key_content)]) + gitlab_shell_fast_execute([gitlab_shell_keys_path, + 'add-key', key_id, self.class.strip_key(key_content)]) end # Batch-add keys to authorized_keys @@ -175,8 +174,10 @@ module Gitlab # remove_key("key-342", "sha-rsa ...") # def remove_key(key_id, key_content) - Gitlab::Utils.system_silent([gitlab_shell_keys_path, - 'rm-key', key_id, key_content]) + args = [gitlab_shell_keys_path, 'rm-key', key_id] + args << key_content if key_content + + gitlab_shell_fast_execute(args) end # Remove all ssh keys from gitlab shell @@ -185,7 +186,7 @@ module Gitlab # remove_all_keys # def remove_all_keys - Gitlab::Utils.system_silent([gitlab_shell_keys_path, 'clear']) + gitlab_shell_fast_execute([gitlab_shell_keys_path, 'clear']) end # Add empty directory for storing repositories @@ -267,5 +268,31 @@ module Gitlab def gitlab_shell_keys_path File.join(gitlab_shell_path, 'bin', 'gitlab-keys') end + + private + + def gitlab_shell_fast_execute(cmd) + output, status = gitlab_shell_fast_execute_helper(cmd) + + return true if status.zero? + + Rails.logger.error("gitlab-shell failed with error #{status}: #{output}") + false + end + + def gitlab_shell_fast_execute_raise_error(cmd) + output, status = gitlab_shell_fast_execute_helper(cmd) + + raise Error, output unless status.zero? + true + end + + def gitlab_shell_fast_execute_helper(cmd) + vars = ENV.to_h.slice(*GITLAB_SHELL_ENV_VARS) + + # Don't pass along the entire parent environment to prevent gitlab-shell + # from wasting I/O by searching through GEM_PATH + Bundler.with_original_env { Popen.popen(cmd, nil, vars) } + end end end diff --git a/lib/gitlab/sql/glob.rb b/lib/gitlab/sql/glob.rb new file mode 100644 index 00000000000..5e89e12b2b1 --- /dev/null +++ b/lib/gitlab/sql/glob.rb @@ -0,0 +1,22 @@ +module Gitlab + module SQL + module Glob + extend self + + # Convert a simple glob pattern with wildcard (*) to SQL LIKE pattern + # with SQL expression + def to_like(pattern) + <<~SQL + REPLACE(REPLACE(REPLACE(#{pattern}, + #{q('%')}, #{q('\\%')}), + #{q('_')}, #{q('\\_')}), + #{q('*')}, #{q('%')}) + SQL + end + + def q(string) + ActiveRecord::Base.connection.quote(string) + end + end + end +end diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb index 23af9318d1a..073af685a09 100644 --- a/lib/gitlab/url_builder.rb +++ b/lib/gitlab/url_builder.rb @@ -23,9 +23,9 @@ module Gitlab when WikiPage wiki_page_url when ProjectSnippet - project_snippet_url(object) + project_snippet_url(object.project, object) when Snippet - personal_snippet_url(object) + snippet_url(object) else raise NotImplementedError.new("No URL builder defined for #{object.class}") end @@ -65,13 +65,13 @@ module Gitlab if snippet.is_a?(PersonalSnippet) snippet_url(snippet, anchor: dom_id(object)) else - project_snippet_url(snippet, anchor: dom_id(object)) + project_snippet_url(snippet.project, snippet, anchor: dom_id(object)) end end end def wiki_page_url - namespace_project_wiki_url(object.wiki.project.namespace, object.wiki.project, object.slug) + project_wiki_url(object.wiki.project, object.slug) end end end diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake index e3883278886..e9fb6a008b0 100644 --- a/lib/tasks/gitlab/info.rake +++ b/lib/tasks/gitlab/info.rake @@ -42,8 +42,7 @@ namespace :gitlab do http_clone_url = project.http_url_to_repo ssh_clone_url = project.ssh_url_to_repo - omniauth_providers = Gitlab.config.omniauth.providers - omniauth_providers.map! { |provider| provider['name'] } + omniauth_providers = Gitlab.config.omniauth.providers.map { |provider| provider['name'] } puts "" puts "GitLab information".color(:yellow) diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index dd1430700f8..db4dc9a02da 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -4,17 +4,28 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-15 21:59-0500\n" +"POT-Creation-Date: 2017-06-19 15:50-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-20 06:26-0400\n" +"PO-Revision-Date: 2017-06-23 04:07-0400\n" "Last-Translator: Lyubomir Vasilev <lyubomirv@abv.bg>\n" "Language-Team: Bulgarian (https://translate.zanata.org/project/view/GitLab)\n" "Language: bg\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" +msgid "%d additional commit has been omitted to prevent performance issues." +msgid_plural "" +"%d additional commits have been omitted to prevent performance issues." +msgstr[0] "%d подаване беше пропуÑнато, за да не Ñе натоварва ÑиÑтемата." +msgstr[1] "%d Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ Ð±Ñха пропуÑнати, за да не Ñе натоварва ÑиÑтемата." + +msgid "%d commit" +msgid_plural "%d commits" +msgstr[0] "%d подаване" +msgstr[1] "%d подаваниÑ" + msgid "%{commit_author_link} committed %{commit_timeago}" msgstr "%{commit_author_link} подаде %{commit_timeago}" @@ -67,9 +78,24 @@ msgstr "" "автоматичното внедрÑване, изберете Yaml шаблон за GitLab CI и подайте " "промените Ñи. %{link_to_autodeploy_doc}" +msgid "BranchSwitcherPlaceholder|Search branches" +msgstr "ТърÑете в клоновете" + +msgid "BranchSwitcherTitle|Switch branch" +msgstr "Превключване на клона" + msgid "Branches" msgstr "Клонове" +msgid "Browse Directory" +msgstr "Преглед на папката" + +msgid "Browse File" +msgstr "Преглед на файла" + +msgid "Browse Files" +msgstr "Преглед на файловете" + msgid "Browse files" msgstr "Разглеждане на файловете" @@ -177,6 +203,9 @@ msgstr "ДобавÑне на „%{file_name}“" msgid "Commits" msgstr "ПодаваниÑ" +msgid "Commits feed" +msgstr "Поток от подаваниÑ" + msgid "Commits|History" msgstr "ИÑториÑ" @@ -339,6 +368,9 @@ msgstr "Планът за Ñхема не може да бъде премахнРmsgid "Files" msgstr "Файлове" +msgid "Filter by commit message" +msgstr "Филтриране по Ñъобщение" + msgid "Find by path" msgstr "ТърÑене по път" @@ -711,10 +743,10 @@ msgstr "Изберете чаÑова зона" msgid "Select target branch" msgstr "Изберете целеви клон" -msgid "Set a password on your account to pull or push via %{protocol}" +msgid "Set a password on your account to pull or push via %{protocol}." msgstr "" "Задайте парола на профила Ñи, за да можете да изтеглÑте и изпращате промени " -"чрез %{protocol}" +"чрез %{protocol}." msgid "Set up CI" msgstr "ÐаÑтройка на ÐИ" @@ -1032,9 +1064,15 @@ msgstr "Качване на нов файл" msgid "Upload file" msgstr "Качване на файл" +msgid "UploadLink|click to upload" +msgstr "щракнете за качване" + msgid "Use your global notification setting" msgstr "Използване на глобалната Ви наÑтройка за извеÑтиÑта" +msgid "View open merge request" +msgstr "Преглед на отворената заÑвка за Ñливане" + msgid "VisibilityLevel|Internal" msgstr "Вътрешен" diff --git a/locale/en/gitlab.po b/locale/en/gitlab.po index afb8fb3176f..bda3fc09e85 100644 --- a/locale/en/gitlab.po +++ b/locale/en/gitlab.po @@ -17,9 +17,27 @@ msgstr "" "Plural-Forms: nplurals=2; plural=n != 1;\n" "\n" +msgid "%d additional commit has been omitted to prevent performance issues." +msgid_plural "%d additional commits have been omitted to prevent performance issues." +msgstr[0] "" +msgstr[1] "" + +msgid "%d commit" +msgid_plural "%d commits" +msgstr[0] "" +msgstr[1] "" + msgid "%{commit_author_link} committed %{commit_timeago}" msgstr "" +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "" +msgstr[1] "" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "" + msgid "About auto deploy" msgstr "" @@ -61,9 +79,24 @@ msgstr[1] "" msgid "Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" msgstr "" +msgid "BranchSwitcherPlaceholder|Search branches" +msgstr "" + +msgid "BranchSwitcherTitle|Switch branch" +msgstr "" + msgid "Branches" msgstr "" +msgid "Browse Directory" +msgstr "" + +msgid "Browse File" +msgstr "" + +msgid "Browse Files" +msgstr "" + msgid "Browse files" msgstr "" @@ -159,6 +192,9 @@ msgid_plural "Commits" msgstr[0] "" msgstr[1] "" +msgid "Commit duration in minutes for last 30 commits" +msgstr "" + msgid "Commit message" msgstr "" @@ -171,6 +207,9 @@ msgstr "" msgid "Commits" msgstr "" +msgid "Commits feed" +msgstr "" + msgid "Commits|History" msgstr "" @@ -195,6 +234,9 @@ msgstr "" msgid "Create New Directory" msgstr "" +msgid "Create a personal access token on your account to pull or push via %{protocol}." +msgstr "" + msgid "Create directory" msgstr "" @@ -213,6 +255,9 @@ msgstr "" msgid "CreateTag|Tag" msgstr "" +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "" + msgid "Cron Timezone" msgstr "" @@ -323,6 +368,9 @@ msgstr "" msgid "Files" msgstr "" +msgid "Filter by commit message" +msgstr "" + msgid "Find by path" msgstr "" @@ -370,6 +418,15 @@ msgstr "" msgid "Introducing Cycle Analytics" msgstr "" +msgid "Jobs for last month" +msgstr "" + +msgid "Jobs for last week" +msgstr "" + +msgid "Jobs for last year" +msgstr "" + msgid "LFSStatus|Disabled" msgstr "" @@ -535,6 +592,21 @@ msgstr "" msgid "Pipeline Schedules" msgstr "" +msgid "PipelineCharts|Failed:" +msgstr "" + +msgid "PipelineCharts|Overall statistics" +msgstr "" + +msgid "PipelineCharts|Success ratio:" +msgstr "" + +msgid "PipelineCharts|Successful:" +msgstr "" + +msgid "PipelineCharts|Total:" +msgstr "" + msgid "PipelineSchedules|Activated" msgstr "" @@ -565,6 +637,18 @@ msgstr "" msgid "PipelineSheduleIntervalPattern|Custom" msgstr "" +msgid "Pipelines" +msgstr "" + +msgid "Pipelines charts" +msgstr "" + +msgid "Pipeline|all" +msgstr "" + +msgid "Pipeline|success" +msgstr "" + msgid "Pipeline|with stage" msgstr "" @@ -688,7 +772,7 @@ msgstr "" msgid "Select target branch" msgstr "" -msgid "Set a password on your account to pull or push via %{protocol}" +msgid "Set a password on your account to pull or push via %{protocol}." msgstr "" msgid "Set up CI" @@ -714,10 +798,7 @@ msgstr "" msgid "StarProject|Star" msgstr "" -msgid "Start a %{new_merge_request} with these changes" -msgstr "" - -msgid "Start a <strong>new merge request</strong> with these changes" +msgid "Start a %{new_merge_request} with these changes" msgstr "" msgid "Switch branch/tag" @@ -948,9 +1029,15 @@ msgstr "" msgid "Upload file" msgstr "" +msgid "UploadLink|click to upload" +msgstr "" + msgid "Use your global notification setting" msgstr "" +msgid "View open merge request" +msgstr "" + msgid "VisibilityLevel|Internal" msgstr "" diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po index 3ef9e19067d..0ca8dfca266 100644 --- a/locale/eo/gitlab.po +++ b/locale/eo/gitlab.po @@ -4,17 +4,28 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-15 21:59-0500\n" +"POT-Creation-Date: 2017-06-19 15:50-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-20 06:24-0400\n" +"PO-Revision-Date: 2017-07-05 02:56-0400\n" "Last-Translator: Lyubomir Vasilev <lyubomirv@abv.bg>\n" "Language-Team: Esperanto (https://translate.zanata.org/project/view/GitLab)\n" "Language: eo\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" +msgid "%d additional commit has been omitted to prevent performance issues." +msgid_plural "" +"%d additional commits have been omitted to prevent performance issues." +msgstr[0] "%d enmetado estis transsaltita, por ne troÅarÄi la sistemon." +msgstr[1] "%d enmetadoj estis transsaltitaj, por ne troÅarÄi la sistemon." + +msgid "%d commit" +msgid_plural "%d commits" +msgstr[0] "%d enmetado" +msgstr[1] "%d enmetadoj" + msgid "%{commit_author_link} committed %{commit_timeago}" msgstr "%{commit_author_link} enmetis %{commit_timeago}" @@ -67,9 +78,24 @@ msgstr "" "disponigadon, bonvolu elekti Yaml-Åablonon por GitLab CI kaj enmeti viajn " "ÅanÄojn. %{link_to_autodeploy_doc}" +msgid "BranchSwitcherPlaceholder|Search branches" +msgstr "Serĉu branĉon" + +msgid "BranchSwitcherTitle|Switch branch" +msgstr "Iri al branĉo" + msgid "Branches" msgstr "Branĉoj" +msgid "Browse Directory" +msgstr "Foliumi dosierujon" + +msgid "Browse File" +msgstr "Foliumi dosieron" + +msgid "Browse Files" +msgstr "Foliumi dosierojn" + msgid "Browse files" msgstr "Elekti dosierojn" @@ -177,6 +203,9 @@ msgstr "Aldoni „%{file_name}“" msgid "Commits" msgstr "Enmetadoj" +msgid "Commits feed" +msgstr "Fluo de enmetadoj" + msgid "Commits|History" msgstr "Historio" @@ -339,6 +368,9 @@ msgstr "Ne eblas forigi la ĉenstablan planon" msgid "Files" msgstr "Dosieroj" +msgid "Filter by commit message" +msgstr "Filtri per mesaÄo" + msgid "Find by path" msgstr "Trovi per dosierindiko" @@ -712,10 +744,10 @@ msgstr "Elektu horzonon" msgid "Select target branch" msgstr "Elektu celan branĉon" -msgid "Set a password on your account to pull or push via %{protocol}" +msgid "Set a password on your account to pull or push via %{protocol}." msgstr "" "Kreu pasvorton por via konto por ebligi al vi eltiri kaj alpuÅi per " -"%{protocol}" +"%{protocol}." msgid "Set up CI" msgstr "Agordi SI" @@ -1033,9 +1065,15 @@ msgstr "AlÅuti novan dosieron" msgid "Upload file" msgstr "AlÅuti dosieron" +msgid "UploadLink|click to upload" +msgstr "alklaku por alÅuti" + msgid "Use your global notification setting" msgstr "Uzi vian Äeneralan agordon pri la sciigoj" +msgid "View open merge request" +msgstr "Vidi la malfermitan peton pri kunfando" + msgid "VisibilityLevel|Internal" msgstr "Interna" diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po index cc44a06cbc5..cec086b871c 100644 --- a/locale/es/gitlab.po +++ b/locale/es/gitlab.po @@ -719,8 +719,8 @@ msgstr "Selecciona una zona horaria" msgid "Select target branch" msgstr "Selecciona una rama de destino" -msgid "Set a password on your account to pull or push via %{protocol}" -msgstr "Establezca una contraseña en su cuenta para actualizar o enviar a través de %{protocol}" +msgid "Set a password on your account to pull or push via %{protocol}." +msgstr "Establezca una contraseña en su cuenta para actualizar o enviar a través de %{protocol}." msgid "Set up CI" msgstr "Configurar CI" diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 07f9efeb495..9f1caeddaa7 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-19 15:50-0500\n" -"PO-Revision-Date: 2017-06-19 15:50-0500\n" +"POT-Creation-Date: 2017-06-28 13:32+0200\n" +"PO-Revision-Date: 2017-06-28 13:32+0200\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: \n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" -msgid "%d additional commit have been omitted to prevent performance issues." +msgid "%d additional commit has been omitted to prevent performance issues." msgid_plural "%d additional commits have been omitted to prevent performance issues." msgstr[0] "" msgstr[1] "" @@ -31,6 +31,14 @@ msgstr[1] "" msgid "%{commit_author_link} committed %{commit_timeago}" msgstr "" +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "" +msgstr[1] "" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "" + msgid "About auto deploy" msgstr "" @@ -185,6 +193,9 @@ msgid_plural "Commits" msgstr[0] "" msgstr[1] "" +msgid "Commit duration in minutes for last 30 commits" +msgstr "" + msgid "Commit message" msgstr "" @@ -224,6 +235,9 @@ msgstr "" msgid "Create New Directory" msgstr "" +msgid "Create a personal access token on your account to pull or push via %{protocol}." +msgstr "" + msgid "Create directory" msgstr "" @@ -242,6 +256,9 @@ msgstr "" msgid "CreateTag|Tag" msgstr "" +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "" + msgid "Cron Timezone" msgstr "" @@ -402,6 +419,15 @@ msgstr "" msgid "Introducing Cycle Analytics" msgstr "" +msgid "Jobs for last month" +msgstr "" + +msgid "Jobs for last week" +msgstr "" + +msgid "Jobs for last year" +msgstr "" + msgid "LFSStatus|Disabled" msgstr "" @@ -567,6 +593,21 @@ msgstr "" msgid "Pipeline Schedules" msgstr "" +msgid "PipelineCharts|Failed:" +msgstr "" + +msgid "PipelineCharts|Overall statistics" +msgstr "" + +msgid "PipelineCharts|Success ratio:" +msgstr "" + +msgid "PipelineCharts|Successful:" +msgstr "" + +msgid "PipelineCharts|Total:" +msgstr "" + msgid "PipelineSchedules|Activated" msgstr "" @@ -597,6 +638,18 @@ msgstr "" msgid "PipelineSheduleIntervalPattern|Custom" msgstr "" +msgid "Pipelines" +msgstr "" + +msgid "Pipelines charts" +msgstr "" + +msgid "Pipeline|all" +msgstr "" + +msgid "Pipeline|success" +msgstr "" + msgid "Pipeline|with stage" msgstr "" @@ -720,7 +773,7 @@ msgstr "" msgid "Select target branch" msgstr "" -msgid "Set a password on your account to pull or push via %{protocol}" +msgid "Set a password on your account to pull or push via %{protocol}." msgstr "" msgid "Set up CI" diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po new file mode 100644 index 00000000000..db992021403 --- /dev/null +++ b/locale/it/gitlab.po @@ -0,0 +1,1185 @@ +# Huang Tao <htve@outlook.com>, 2017. #zanata +# Paolo Falomo <info@paolofalomo.it>, 2017. #zanata +msgid "" +msgstr "" +"Project-Id-Version: gitlab 1.0.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-06-19 15:50-0500\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"PO-Revision-Date: 2017-07-02 10:32-0400\n" +"Last-Translator: Paolo Falomo <info@paolofalomo.it>\n" +"Language-Team: Italian\n" +"Language: it\n" +"X-Generator: Zanata 3.9.6\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" + +msgid "%d additional commit has been omitted to prevent performance issues." +msgid_plural "" +"%d additional commits have been omitted to prevent performance issues." +msgstr[0] "" +"%d commit aggiuntivo è stato omesso per evitare degradi di prestazioni negli " +"issues." +msgstr[1] "" +"%d commit aggiuntivi sono stati omessi per evitare degradi di prestazioni " +"negli issues." + +msgid "%d commit" +msgid_plural "%d commits" +msgstr[0] "%d commit" +msgstr[1] "%d commit" + +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "%{commit_author_link} ha committato %{commit_timeago}" + +msgid "About auto deploy" +msgstr "Riguardo il rilascio automatico" + +msgid "Active" +msgstr "Attivo" + +msgid "Activity" +msgstr "Attività " + +msgid "Add Changelog" +msgstr "Aggiungi Changelog" + +msgid "Add Contribution guide" +msgstr "Aggiungi Guida per contribuire" + +msgid "Add License" +msgstr "Aggiungi Licenza" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "" +"Aggiungi una chiave SSH al tuo profilo per eseguire pull o push tramite SSH" + +msgid "Add new directory" +msgstr "Aggiungi una directory (cartella)" + +msgid "Archived project! Repository is read-only" +msgstr "Progetto archiviato! La Repository è sola-lettura" + +msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "Sei sicuro di voler cancellare questa pipeline programmata?" + +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "" +"Aggiungi un file tramite trascina & rilascia ( drag & drop) o " +"%{upload_link}" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "Branch" +msgstr[1] "Branches" + +msgid "" +"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, " +"choose a GitLab CI Yaml template and commit your changes. " +"%{link_to_autodeploy_doc}" +msgstr "" +"La branch <strong>%{branch_name}</strong> è stata creata. Per impostare un " +"rilascio automatico scegli un template CI di Gitlab e committa le tue " +"modifiche %{link_to_autodeploy_doc}" + +msgid "BranchSwitcherPlaceholder|Search branches" +msgstr "Cerca branches" + +msgid "BranchSwitcherTitle|Switch branch" +msgstr "Cambia branch" + +msgid "Branches" +msgstr "Branches" + +msgid "Browse Directory" +msgstr "Naviga direttori" + +msgid "Browse File" +msgstr "Esplora File" + +msgid "Browse Files" +msgstr "Esplora Files" + +msgid "Browse files" +msgstr "Guarda i files" + +msgid "ByAuthor|by" +msgstr "per" + +msgid "CI configuration" +msgstr "Configurazione CI (Integrazione Continua)" + +msgid "Cancel" +msgstr "Cancella" + +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "Preleva nella branch" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "Ripristina nella branch" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "Cherry-pick" + +msgid "ChangeTypeAction|Revert" +msgstr "Ripristina" + +msgid "Changelog" +msgstr "Changelog" + +msgid "Charts" +msgstr "Grafici" + +msgid "Cherry-pick this commit" +msgstr "Cherry-pick this commit" + +msgid "Cherry-pick this merge request" +msgstr "Cherry-pick questa richiesta di merge" + +msgid "CiStatusLabel|canceled" +msgstr "cancellato" + +msgid "CiStatusLabel|created" +msgstr "creato" + +msgid "CiStatusLabel|failed" +msgstr "fallito" + +msgid "CiStatusLabel|manual action" +msgstr "azione manuale" + +msgid "CiStatusLabel|passed" +msgstr "superata" + +msgid "CiStatusLabel|passed with warnings" +msgstr "superata con avvisi" + +msgid "CiStatusLabel|pending" +msgstr "in coda" + +msgid "CiStatusLabel|skipped" +msgstr "saltata" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "in attesa di azione manuale" + +msgid "CiStatusText|blocked" +msgstr "bloccata" + +msgid "CiStatusText|canceled" +msgstr "cancellata" + +msgid "CiStatusText|created" +msgstr "creata" + +msgid "CiStatusText|failed" +msgstr "fallita" + +msgid "CiStatusText|manual" +msgstr "manuale" + +msgid "CiStatusText|passed" +msgstr "superata" + +msgid "CiStatusText|pending" +msgstr "in coda" + +msgid "CiStatusText|skipped" +msgstr "saltata" + +msgid "CiStatus|running" +msgstr "in corso" + +msgid "Commit" +msgid_plural "Commits" +msgstr[0] "Commit" +msgstr[1] "Commits" + +msgid "Commit message" +msgstr "Messaggio del commit" + +msgid "CommitBoxTitle|Commit" +msgstr "Commit" + +msgid "CommitMessage|Add %{file_name}" +msgstr "Aggiungi %{file_name}" + +msgid "Commits" +msgstr "Commits" + +msgid "Commits feed" +msgstr "Feed dei Commits" + +msgid "Commits|History" +msgstr "Cronologia" + +msgid "Committed by" +msgstr "Committato da " + +msgid "Compare" +msgstr "Confronta" + +msgid "Contribution guide" +msgstr "Guida per contribuire" + +msgid "Contributors" +msgstr "Collaboratori" + +msgid "Copy URL to clipboard" +msgstr "Copia URL negli appunti" + +msgid "Copy commit SHA to clipboard" +msgstr "Copia l'SHA del commit negli appunti" + +msgid "Create New Directory" +msgstr "Crea una nuova cartella" + +msgid "Create directory" +msgstr "Crea cartella" + +msgid "Create empty bare repository" +msgstr "Crea una repository vuota" + +msgid "Create merge request" +msgstr "Crea una richiesta di merge" + +msgid "Create new..." +msgstr "Crea nuovo..." + +msgid "CreateNewFork|Fork" +msgstr "Fork" + +msgid "CreateTag|Tag" +msgstr "Tag" + +msgid "Cron Timezone" +msgstr "Cron Timezone" + +msgid "Cron syntax" +msgstr "Sintassi Cron" + +msgid "Custom notification events" +msgstr "Eventi-Notifica personalizzati" + +msgid "" +"Custom notification levels are the same as participating levels. With custom " +"notification levels you will also receive notifications for select events. " +"To find out more, check out %{notification_link}." +msgstr "" +"I livelli di notifica personalizzati sono uguali a quelli di partecipazione. " +"Con i livelli di notifica personalizzati riceverai anche notifiche per gli " +"eventi da te scelti %{notification_link}." + +msgid "Cycle Analytics" +msgstr "Statistiche Cicliche" + +msgid "" +"Cycle Analytics gives an overview of how much time it takes to go from idea " +"to production in your project." +msgstr "" +"L'Analisi Ciclica fornisce una panoramica sul tempo che trascorre tra l'idea " +"ed il rilascio in produzione del tuo progetto" + +msgid "CycleAnalyticsStage|Code" +msgstr "Codice" + +msgid "CycleAnalyticsStage|Issue" +msgstr "Issue" + +msgid "CycleAnalyticsStage|Plan" +msgstr "Pianificazione" + +msgid "CycleAnalyticsStage|Production" +msgstr "Produzione" + +msgid "CycleAnalyticsStage|Review" +msgstr "Revisione" + +msgid "CycleAnalyticsStage|Staging" +msgstr "Pre-rilascio" + +msgid "CycleAnalyticsStage|Test" +msgstr "Test" + +msgid "Define a custom pattern with cron syntax" +msgstr "Definisci un patter personalizzato mediante la sintassi cron" + +msgid "Delete" +msgstr "Elimina" + +msgid "Deploy" +msgid_plural "Deploys" +msgstr[0] "Rilascio" +msgstr[1] "Rilasci" + +msgid "Description" +msgstr "Descrizione" + +msgid "Directory name" +msgstr "Nome cartella" + +msgid "Don't show again" +msgstr "Non mostrare più" + +msgid "Download" +msgstr "Scarica" + +msgid "Download tar" +msgstr "Scarica tar" + +msgid "Download tar.bz2" +msgstr "Scarica tar.bz2" + +msgid "Download tar.gz" +msgstr "Scarica tar.gz" + +msgid "Download zip" +msgstr "Scarica zip" + +msgid "DownloadArtifacts|Download" +msgstr "Scarica" + +msgid "DownloadCommit|Email Patches" +msgstr "Email Patches" + +msgid "DownloadCommit|Plain Diff" +msgstr "Differenze" + +msgid "DownloadSource|Download" +msgstr "Scarica" + +msgid "Edit" +msgstr "Modifica" + +msgid "Edit Pipeline Schedule %{id}" +msgstr "Cambia programmazione della pipeline %{id}" + +msgid "Every day (at 4:00am)" +msgstr "Ogni giorno (alle 4 del mattino)" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "Ogni primo giorno del mese (alle 4 del mattino)" + +msgid "Every week (Sundays at 4:00am)" +msgstr "Ogni settimana (Di domenica alle 4 del mattino)" + +msgid "Failed to change the owner" +msgstr "Impossibile cambiare owner" + +msgid "Failed to remove the pipeline schedule" +msgstr "Impossibile rimuovere la pipeline pianificata" + +msgid "Files" +msgstr "Files" + +msgid "Filter by commit message" +msgstr "Filtra per messaggio di commit" + +msgid "Find by path" +msgstr "Trova in percorso" + +msgid "Find file" +msgstr "Trova file" + +msgid "FirstPushedBy|First" +msgstr "Primo" + +msgid "FirstPushedBy|pushed by" +msgstr "Push di" + +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "Fork" +msgstr[1] "Forks" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "Fork da" + +msgid "From issue creation until deploy to production" +msgstr "Dalla creazione di un issue fino al rilascio in produzione" + +msgid "From merge request merge until deploy to production" +msgstr "" +"Dalla richiesta di merge fino effettua il merge fino al rilascio in " +"produzione" + +msgid "Go to your fork" +msgstr "Vai il tuo fork" + +msgid "GoToYourFork|Fork" +msgstr "Fork" + +msgid "Home" +msgstr "Home" + +msgid "Housekeeping successfully started" +msgstr "Housekeeping iniziato con successo" + +msgid "Import repository" +msgstr "Importa repository" + +msgid "Interval Pattern" +msgstr "Intervallo di Pattern" + +msgid "Introducing Cycle Analytics" +msgstr "Introduzione delle Analisi Cicliche" + +msgid "LFSStatus|Disabled" +msgstr "Disabilitato" + +msgid "LFSStatus|Enabled" +msgstr "Abilitato" + +msgid "Last %d day" +msgid_plural "Last %d days" +msgstr[0] "L'ultimo %d giorno" +msgstr[1] "Gli ultimi %d giorni" + +msgid "Last Pipeline" +msgstr "Ultima Pipeline" + +msgid "Last Update" +msgstr "Ultimo Aggiornamento" + +msgid "Last commit" +msgstr "Ultimo Commit" + +msgid "Learn more in the" +msgstr "Leggi di più su" + +msgid "Learn more in the|pipeline schedules documentation" +msgstr "documentazione sulla pianificazione delle pipelines" + +msgid "Leave group" +msgstr "Abbandona il gruppo" + +msgid "Leave project" +msgstr "Abbandona il progetto" + +msgid "Limited to showing %d event at most" +msgid_plural "Limited to showing %d events at most" +msgstr[0] "Limita visualizzazione %d d'evento" +msgstr[1] "Limita visualizzazione %d di eventi" + +msgid "Median" +msgstr "Mediano" + +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "aggiungi una chiave SSH" + +msgid "New Issue" +msgid_plural "New Issues" +msgstr[0] "Nuovo Issue" +msgstr[1] "Nuovi Issues" + +msgid "New Pipeline Schedule" +msgstr "Nuova pianificazione Pipeline" + +msgid "New branch" +msgstr "Nuova Branch" + +msgid "New directory" +msgstr "Nuova directory" + +msgid "New file" +msgstr "Nuovo file" + +msgid "New issue" +msgstr "Nuovo Issue" + +msgid "New merge request" +msgstr "Nuova richiesta di merge" + +msgid "New schedule" +msgstr "Nuova pianficazione" + +msgid "New snippet" +msgstr "Nuovo snippet" + +msgid "New tag" +msgstr "Nuovo tag" + +msgid "No repository" +msgstr "Nessuna Repository" + +msgid "No schedules" +msgstr "Nessuna pianificazione" + +msgid "Not available" +msgstr "Non disponibile" + +msgid "Not enough data" +msgstr "Dati insufficienti " + +msgid "Notification events" +msgstr "Notifica eventi" + +msgid "NotificationEvent|Close issue" +msgstr "Chiudi issue" + +msgid "NotificationEvent|Close merge request" +msgstr "Chiudi richiesta di merge" + +msgid "NotificationEvent|Failed pipeline" +msgstr "Pipeline fallita" + +msgid "NotificationEvent|Merge merge request" +msgstr "Completa la richiesta di merge" + +msgid "NotificationEvent|New issue" +msgstr "Nuovo issue" + +msgid "NotificationEvent|New merge request" +msgstr "Nuova richiesta di merge" + +msgid "NotificationEvent|New note" +msgstr "Nuova nota" + +msgid "NotificationEvent|Reassign issue" +msgstr "Riassegna issue" + +msgid "NotificationEvent|Reassign merge request" +msgstr "Riassegna richiesta di Merge" + +msgid "NotificationEvent|Reopen issue" +msgstr "Riapri issue" + +msgid "NotificationEvent|Successful pipeline" +msgstr "Pipeline Completata" + +msgid "NotificationLevel|Custom" +msgstr "Personalizzato" + +msgid "NotificationLevel|Disabled" +msgstr "Disabilitato" + +msgid "NotificationLevel|Global" +msgstr "Globale" + +msgid "NotificationLevel|On mention" +msgstr "Se menzionato" + +msgid "NotificationLevel|Participate" +msgstr "Partecipa" + +msgid "NotificationLevel|Watch" +msgstr "Osserva" + +msgid "OfSearchInADropdown|Filter" +msgstr "Filtra" + +msgid "OpenedNDaysAgo|Opened" +msgstr "Aperto" + +msgid "Options" +msgstr "Opzioni" + +msgid "Owner" +msgstr "Owner" + +msgid "Pipeline" +msgstr "Pipeline" + +msgid "Pipeline Health" +msgstr "Stato della Pipeline" + +msgid "Pipeline Schedule" +msgstr "Pianificazione Pipeline" + +msgid "Pipeline Schedules" +msgstr "Pianificazione multipla Pipeline" + +msgid "PipelineSchedules|Activated" +msgstr "Attivata" + +msgid "PipelineSchedules|Active" +msgstr "Attiva" + +msgid "PipelineSchedules|All" +msgstr "Tutto" + +msgid "PipelineSchedules|Inactive" +msgstr "Inattiva" + +msgid "PipelineSchedules|Next Run" +msgstr "Prossima esecuzione" + +msgid "PipelineSchedules|None" +msgstr "Nessuna" + +msgid "PipelineSchedules|Provide a short description for this pipeline" +msgstr "Fornisci una breve descrizione per questa pipeline" + +msgid "PipelineSchedules|Take ownership" +msgstr "Prendi possesso" + +msgid "PipelineSchedules|Target" +msgstr "Target" + +msgid "PipelineSheduleIntervalPattern|Custom" +msgstr "Personalizzato" + +msgid "Pipeline|with stage" +msgstr "con stadio" + +msgid "Pipeline|with stages" +msgstr "con più stadi" + +msgid "Project '%{project_name}' queued for deletion." +msgstr "Il Progetto '%{project_name}' in coda di eliminazione." + +msgid "Project '%{project_name}' was successfully created." +msgstr "Il Progetto '%{project_name}' è stato creato con successo." + +msgid "Project '%{project_name}' was successfully updated." +msgstr "Il Progetto '%{project_name}' è stato aggiornato con successo." + +msgid "Project '%{project_name}' will be deleted." +msgstr "Il Progetto '%{project_name}' verrà eliminato" + +msgid "Project access must be granted explicitly to each user." +msgstr "L'accesso al progetto dev'esser fornito esplicitamente ad ogni utente" + +msgid "Project export could not be deleted." +msgstr "L'esportazione del progetto non può essere eliminata." + +msgid "Project export has been deleted." +msgstr "L'esportazione del progetto è stata eliminata." + +msgid "" +"Project export link has expired. Please generate a new export from your " +"project settings." +msgstr "" +"Il link d'esportazione del progetto è scaduto. Genera una nuova esportazione " +"dalle impostazioni del tuo progetto." + +msgid "Project export started. A download link will be sent by email." +msgstr "" +"Esportazione del progetto iniziata. Un link di download sarà inviato via " +"email." + +msgid "Project home" +msgstr "Home di progetto" + +msgid "ProjectFeature|Disabled" +msgstr "Disabilitato" + +msgid "ProjectFeature|Everyone with access" +msgstr "Chiunque con accesso" + +msgid "ProjectFeature|Only team members" +msgstr "Solo i membri del team" + +msgid "ProjectFileTree|Name" +msgstr "Nome" + +msgid "ProjectLastActivity|Never" +msgstr "Mai" + +msgid "ProjectLifecycle|Stage" +msgstr "Stadio" + +msgid "ProjectNetworkGraph|Graph" +msgstr "Grafico" + +msgid "Read more" +msgstr "Continua..." + +msgid "Readme" +msgstr "Leggimi" + +msgid "RefSwitcher|Branches" +msgstr "Branches" + +msgid "RefSwitcher|Tags" +msgstr "Tags" + +msgid "Related Commits" +msgstr "Commit correlati" + +msgid "Related Deployed Jobs" +msgstr "Attività di Rilascio Correlate" + +msgid "Related Issues" +msgstr "Issues Correlati" + +msgid "Related Jobs" +msgstr "Attività Correlate" + +msgid "Related Merge Requests" +msgstr "Richieste di Merge Correlate" + +msgid "Related Merged Requests" +msgstr "Richieste di Merge Completate Correlate" + +msgid "Remind later" +msgstr "Ricordamelo più tardi" + +msgid "Remove project" +msgstr "Rimuovi progetto" + +msgid "Request Access" +msgstr "Richiedi accesso" + +msgid "Revert this commit" +msgstr "Ripristina questo commit" + +msgid "Revert this merge request" +msgstr "Ripristina questa richiesta di merge" + +msgid "Save pipeline schedule" +msgstr "Salva pianificazione pipeline" + +msgid "Schedule a new pipeline" +msgstr "Pianifica una nuova Pipeline" + +msgid "Scheduling Pipelines" +msgstr "Pianificazione pipelines" + +msgid "Search branches and tags" +msgstr "Ricerca branches e tags" + +msgid "Select Archive Format" +msgstr "Seleziona formato d'archivio" + +msgid "Select a timezone" +msgstr "Seleziona una timezone" + +msgid "Select target branch" +msgstr "Seleziona una branch di destinazione" + +msgid "Set a password on your account to pull or push via %{protocol}." +msgstr "" +"Imposta una password sul tuo account per eseguire pull o push tramite " +"%{protocol}." + +msgid "Set up CI" +msgstr "Configura CI" + +msgid "Set up Koding" +msgstr "Configura Koding" + +msgid "Set up auto deploy" +msgstr "Configura il rilascio automatico" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "imposta una password" + +msgid "Showing %d event" +msgid_plural "Showing %d events" +msgstr[0] "Visualizza %d evento" +msgstr[1] "Visualizza %d eventi" + +msgid "Source code" +msgstr "Codice Sorgente" + +msgid "StarProject|Star" +msgstr "Star" + +msgid "Start a %{new_merge_request} with these changes" +msgstr "inizia una %{new_merge_request} con queste modifiche" + +msgid "Switch branch/tag" +msgstr "Cambia branch/tag" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "Tag" +msgstr[1] "Tags" + +msgid "Tags" +msgstr "Tags" + +msgid "Target Branch" +msgstr "Branch di destinazione" + +msgid "" +"The coding stage shows the time from the first commit to creating the merge " +"request. The data will automatically be added here once you create your " +"first merge request." +msgstr "" +"Lo stadio di programmazione mostra il tempo trascorso dal primo commit alla " +"creazione di una richiesta di merge (MR). I dati saranno aggiunti una volta " +"che avrai creato la prima richiesta di merge." + +msgid "The collection of events added to the data gathered for that stage." +msgstr "L'insieme di eventi aggiunti ai dati raccolti per quello stadio." + +msgid "The fork relationship has been removed." +msgstr "La relazione del fork è stata rimossa" + +msgid "" +"The issue stage shows the time it takes from creating an issue to assigning " +"the issue to a milestone, or add the issue to a list on your Issue Board. " +"Begin creating issues to see data for this stage." +msgstr "" +"Questo stadio di issue mostra il tempo che ci vuole dal creare un issue " +"all'assegnarli una milestone, o ad aggiungere un issue alla tua board. Crea " +"un issue per vedere questo stadio." + +msgid "The phase of the development lifecycle." +msgstr "Il ciclo vitale della fase di sviluppo." + +msgid "" +"The pipelines schedule runs pipelines in the future, repeatedly, for " +"specific branches or tags. Those scheduled pipelines will inherit limited " +"project access based on their associated user." +msgstr "" +"Le pipelines pianificate vengono eseguite nel futuro, ripetitivamente, per " +"specifici tag o branch ed ereditano restrizioni di progetto basate " +"sull'utente ad esse associato." + +msgid "" +"The planning stage shows the time from the previous step to pushing your " +"first commit. This time will be added automatically once you push your first " +"commit." +msgstr "" +"Lo stadio di pianificazione mostra il tempo trascorso dal primo commit al " +"suo step precedente. Questo periodo sarà disponibile automaticamente nel " +"momento in cui farai il primo commit." + +msgid "" +"The production stage shows the total time it takes between creating an issue " +"and deploying the code to production. The data will be automatically added " +"once you have completed the full idea to production cycle." +msgstr "" +"Lo stadio di produzione mostra il tempo totale che trascorre tra la " +"creazione di un issue il suo rilascio (inteso come codice) in produzione. " +"Questo dato sarà disponibile automaticamente nel momento in cui avrai " +"completato l'intero processo ideale del ciclo di produzione" + +msgid "The project can be accessed by any logged in user." +msgstr "Qualunque utente autenticato può accedere a questo progetto." + +msgid "The project can be accessed without any authentication." +msgstr "" +"Chiunque può accedere a questo progetto (senza alcuna autenticazione)." + +msgid "The repository for this project does not exist." +msgstr "La repository di questo progetto non esiste." + +msgid "" +"The review stage shows the time from creating the merge request to merging " +"it. The data will automatically be added after you merge your first merge " +"request." +msgstr "" +"Lo stadio di revisione mostra il tempo tra una richiesta di merge al suo " +"svolgimento effettivo. Questo dato sarà disponibile appena avrai completato " +"una MR (Merger Request)" + +msgid "" +"The staging stage shows the time between merging the MR and deploying code " +"to the production environment. The data will be automatically added once you " +"deploy to production for the first time." +msgstr "" +"Lo stadio di pre-rilascio mostra il tempo che trascorre da una MR (Richiesta " +"di Merge) completata al suo rilascio in ambiente di produzione. Questa " +"informazione sarà disponibile dal tuo primo rilascio in produzione" + +msgid "" +"The testing stage shows the time GitLab CI takes to run every pipeline for " +"the related merge request. The data will automatically be added after your " +"first pipeline finishes running." +msgstr "" +"Lo stadio di test mostra il tempo che ogni Pipeline impiega per essere " +"eseguita in ogni Richiesta di Merge correlata. L'informazione sarà " +"disponibile automaticamente quando la tua prima Pipeline avrà finito d'esser " +"eseguita." + +msgid "The time taken by each data entry gathered by that stage." +msgstr "" +"Il tempo aggregato relativo eventi/data entry raccolto in quello stadio." + +msgid "" +"The value lying at the midpoint of a series of observed values. E.g., " +"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 =" +" 6." +msgstr "" +"Il valore falsato nel mezzo di una serie di dati osservati. ES: tra 3,5,9 il " +"mediano è 5. Tra 3,5,7,8 il mediano è (5+7)/2 quindi 6." + +msgid "" +"This means you can not push code until you create an empty repository or " +"import existing one." +msgstr "" +"Questo significa che non è possibile effettuare push di codice fino a che " +"non crei una repository vuota o ne importi una esistente" + +msgid "Time before an issue gets scheduled" +msgstr "Il tempo che impiega un issue per esser pianificato" + +msgid "Time before an issue starts implementation" +msgstr "Il tempo che impiega un issue per esser implementato" + +msgid "Time between merge request creation and merge/close" +msgstr "Il tempo tra la creazione di una richiesta di merge ed il merge/close" + +msgid "Time until first merge request" +msgstr "Il tempo fino alla prima richiesta di merge" + +msgid "Timeago|%s days ago" +msgstr "%s giorni fa" + +msgid "Timeago|%s days remaining" +msgstr "%s giorni rimanenti" + +msgid "Timeago|%s hours remaining" +msgstr "%s ore rimanenti" + +msgid "Timeago|%s minutes ago" +msgstr "%s minuti fa" + +msgid "Timeago|%s minutes remaining" +msgstr "%s minuti rimanenti" + +msgid "Timeago|%s months ago" +msgstr "%s minuti fa" + +msgid "Timeago|%s months remaining" +msgstr "%s mesi rimanenti" + +msgid "Timeago|%s seconds remaining" +msgstr "%s secondi rimanenti" + +msgid "Timeago|%s weeks ago" +msgstr "%s settimane fa" + +msgid "Timeago|%s weeks remaining" +msgstr "%s settimane rimanenti" + +msgid "Timeago|%s years ago" +msgstr "%s anni fa" + +msgid "Timeago|%s years remaining" +msgstr "%s anni rimanenti" + +msgid "Timeago|1 day remaining" +msgstr "1 giorno rimanente" + +msgid "Timeago|1 hour remaining" +msgstr "1 ora rimanente" + +msgid "Timeago|1 minute remaining" +msgstr "1 minuto rimanente" + +msgid "Timeago|1 month remaining" +msgstr "1 mese rimanente" + +msgid "Timeago|1 week remaining" +msgstr "1 settimana rimanente" + +msgid "Timeago|1 year remaining" +msgstr "1 anno rimanente" + +msgid "Timeago|Past due" +msgstr "Entro" + +msgid "Timeago|a day ago" +msgstr "un giorno fa" + +msgid "Timeago|a month ago" +msgstr "un mese fa" + +msgid "Timeago|a week ago" +msgstr "una settimana fa" + +msgid "Timeago|a while" +msgstr "poco fa" + +msgid "Timeago|a year ago" +msgstr "un anno fa" + +msgid "Timeago|about %s hours ago" +msgstr "circa %s ore fa" + +msgid "Timeago|about a minute ago" +msgstr "circa un minuto fa" + +msgid "Timeago|about an hour ago" +msgstr "circa un ora fa" + +msgid "Timeago|in %s days" +msgstr "in %s giorni" + +msgid "Timeago|in %s hours" +msgstr "in %s ore" + +msgid "Timeago|in %s minutes" +msgstr "in %s minuti" + +msgid "Timeago|in %s months" +msgstr "in %s mesi" + +msgid "Timeago|in %s seconds" +msgstr "in %s secondi" + +msgid "Timeago|in %s weeks" +msgstr "in %s settimane" + +msgid "Timeago|in %s years" +msgstr "in %s anni" + +msgid "Timeago|in 1 day" +msgstr "in 1 giorno" + +msgid "Timeago|in 1 hour" +msgstr "in 1 ora" + +msgid "Timeago|in 1 minute" +msgstr "in 1 minuto" + +msgid "Timeago|in 1 month" +msgstr "in 1 mese" + +msgid "Timeago|in 1 week" +msgstr "in 1 settimana" + +msgid "Timeago|in 1 year" +msgstr "in 1 anno" + +msgid "Timeago|less than a minute ago" +msgstr "meno di un minuto fa" + +msgid "Time|hr" +msgid_plural "Time|hrs" +msgstr[0] "hr" +msgstr[1] "hr" + +msgid "Time|min" +msgid_plural "Time|mins" +msgstr[0] "min" +msgstr[1] "mins" + +msgid "Time|s" +msgstr "s" + +msgid "Total Time" +msgstr "Tempo Totale" + +msgid "Total test time for all commits/merges" +msgstr "Tempo totale di test per tutti i commits/merges" + +msgid "Unstar" +msgstr "Unstar" + +msgid "Upload New File" +msgstr "Carica un nuovo file" + +msgid "Upload file" +msgstr "Carica file" + +msgid "UploadLink|click to upload" +msgstr "clicca per caricare" + +msgid "Use your global notification setting" +msgstr "Usa le tue impostazioni globali " + +msgid "View open merge request" +msgstr "Mostra la richieste di merge aperte" + +msgid "VisibilityLevel|Internal" +msgstr "Interno" + +msgid "VisibilityLevel|Private" +msgstr "Privato" + +msgid "VisibilityLevel|Public" +msgstr "Pubblico" + +msgid "Want to see the data? Please ask an administrator for access." +msgstr "" +"Vuoi visualizzare i dati? Richiedi l'accesso ad un amministratore, grazie." + +msgid "We don't have enough data to show this stage." +msgstr "Non ci sono sufficienti dati da mostrare su questo stadio" + +msgid "Withdraw Access Request" +msgstr "Ritira richiesta d'accesso" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" +"Stai per rimuovere %{project_name_with_namespace}.\n" +"I progetti rimossi NON POSSONO essere ripristinati\n" +"Sei assolutamente sicuro?" + +msgid "" +"You are going to remove the fork relationship to source project " +"%{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "" +"Stai per rimuovere la relazione con il progetto sorgente " +"%{forked_from_project}. Sei ASSOLUTAMENTE sicuro?" + +msgid "" +"You are going to transfer %{project_name_with_namespace} to another owner. " +"Are you ABSOLUTELY sure?" +msgstr "" +"Stai per trasferire %{project_name_with_namespace} ad un altro owner. Sei " +"ASSOLUTAMENTE sicuro?" + +msgid "You can only add files when you are on a branch" +msgstr "Puoi aggiungere files solo quando sei in una branch" + +msgid "You have reached your project limit" +msgstr "Hai raggiunto il tuo limite di progetto" + +msgid "You must sign in to star a project" +msgstr "Devi accedere per porre una star al progetto" + +msgid "You need permission." +msgstr "Necessiti del permesso." + +msgid "You will not get any notifications via email" +msgstr "Non riceverai alcuna notifica via email" + +msgid "You will only receive notifications for the events you choose" +msgstr "Riceverai notifiche solo per gli eventi che hai scelto" + +msgid "" +"You will only receive notifications for threads you have participated in" +msgstr "Riceverai notifiche solo per i threads a cui hai partecipato" + +msgid "You will receive notifications for any activity" +msgstr "Riceverai notifiche per ogni attività " + +msgid "" +"You will receive notifications only for comments in which you were " +"@mentioned" +msgstr "Riceverai notifiche solo per i commenti ai quale sei stato menzionato" + +msgid "" +"You won't be able to pull or push project code via %{protocol} until you " +"%{set_password_link} on your account" +msgstr "" +"Non sarai in grado di eseguire pull o push di codice tramite %{protocol} " +"fino a che %{set_password_link} nel tuo account." + +msgid "" +"You won't be able to pull or push project code via SSH until you " +"%{add_ssh_key_link} to your profile" +msgstr "" +"Non sarai in grado di effettuare push o pull tramite SSH fino a che " +"%{add_ssh_key_link} al tuo profilo" + +msgid "Your name" +msgstr "Il tuo nome" + +msgid "day" +msgid_plural "days" +msgstr[0] "giorno" +msgstr[1] "giorni" + +msgid "new merge request" +msgstr "Nuova richiesta di merge" + +msgid "notification emails" +msgstr "Notifiche via email" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "parent" +msgstr[1] "parents" + diff --git a/locale/it/gitlab.po.time_stamp b/locale/it/gitlab.po.time_stamp new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/locale/it/gitlab.po.time_stamp diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po index 8ba95093b82..2f21aae2899 100644 --- a/locale/zh_CN/gitlab.po +++ b/locale/zh_CN/gitlab.po @@ -4,17 +4,26 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-15 21:59-0500\n" +"POT-Creation-Date: 2017-06-19 15:50-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-19 09:57-0400\n" +"PO-Revision-Date: 2017-06-27 03:18-0400\n" "Last-Translator: Huang Tao <htve@outlook.com>\n" "Language-Team: Chinese (China) (https://translate.zanata.org/project/view/GitLab)\n" "Language: zh-CN\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=1; plural=0\n" +msgid "%d additional commit has been omitted to prevent performance issues." +msgid_plural "" +"%d additional commits have been omitted to prevent performance issues." +msgstr[0] "为æ高页é¢åŠ 载速度åŠæ€§èƒ½ï¼Œå·²çœç•¥äº† %d 次æ交。" + +msgid "%d commit" +msgid_plural "%d commits" +msgstr[0] "%d 次æ交" + msgid "%{commit_author_link} committed %{commit_timeago}" msgstr "ç”± %{commit_author_link} æ交于 %{commit_timeago}" @@ -63,9 +72,24 @@ msgstr "" "已创建分支 <strong>%{branch_name}</strong> 。如需设置自动部署, 请选择åˆé€‚çš„ GitLab CI Yaml " "模æ¿å¹¶æ交更改。%{link_to_autodeploy_doc}" +msgid "BranchSwitcherPlaceholder|Search branches" +msgstr "æœç´¢åˆ†æ”¯" + +msgid "BranchSwitcherTitle|Switch branch" +msgstr "切æ¢åˆ†æ”¯" + msgid "Branches" msgstr "分支" +msgid "Browse Directory" +msgstr "æµè§ˆç›®å½•" + +msgid "Browse File" +msgstr "æµè§ˆæ–‡ä»¶" + +msgid "Browse Files" +msgstr "æµè§ˆæ–‡ä»¶" + msgid "Browse files" msgstr "æµè§ˆæ–‡ä»¶" @@ -172,6 +196,9 @@ msgstr "æ·»åŠ %{file_name}" msgid "Commits" msgstr "æ交" +msgid "Commits feed" +msgstr "æ交动æ€" + msgid "Commits|History" msgstr "历å²" @@ -329,6 +356,9 @@ msgstr "æ— æ³•åˆ é™¤æµæ°´çº¿è®¡åˆ’" msgid "Files" msgstr "文件" +msgid "Filter by commit message" +msgstr "按æ交消æ¯è¿‡æ»¤" + msgid "Find by path" msgstr "按路径查找" @@ -692,7 +722,7 @@ msgstr "选择时区" msgid "Select target branch" msgstr "é€‰æ‹©ç›®æ ‡åˆ†æ”¯" -msgid "Set a password on your account to pull or push via %{protocol}" +msgid "Set a password on your account to pull or push via %{protocol}." msgstr "为账å·åˆ›å»ºä¸€ä¸ªç”¨äºŽæŽ¨é€æˆ–拉å–çš„ %{protocol} 密ç 。" msgid "Set up CI" @@ -974,9 +1004,15 @@ msgstr "ä¸Šä¼ æ–°æ–‡ä»¶" msgid "Upload file" msgstr "ä¸Šä¼ æ–‡ä»¶" +msgid "UploadLink|click to upload" +msgstr "ç‚¹å‡»ä¸Šä¼ " + msgid "Use your global notification setting" msgstr "使用全局通知设置" +msgid "View open merge request" +msgstr "查看待处ç†çš„åˆå¹¶è¯·æ±‚" + msgid "VisibilityLevel|Internal" msgstr "内部" diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index 4d545d27185..afdbd01b7d7 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -9,13 +9,22 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-19 09:57-0400\n" +"PO-Revision-Date: 2017-06-23 01:23-0400\n" "Last-Translator: Huang Tao <htve@outlook.com>\n" "Language-Team: Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)\n" "Language: zh-HK\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=1; plural=0\n" +msgid "%d additional commit has been omitted to prevent performance issues." +msgid_plural "" +"%d additional commits have been omitted to prevent performance issues." +msgstr[0] "為æ高é é¢åŠ 載速度åŠæ€§èƒ½ï¼Œå·²çœç•¥äº† %d 次æ交。" + +msgid "%d commit" +msgid_plural "%d commits" +msgstr[0] " %d 次æ交" + msgid "%{commit_author_link} committed %{commit_timeago}" msgstr "ç”± %{commit_author_link} æ交於 %{commit_timeago}" @@ -64,9 +73,24 @@ msgstr "" "分支 <strong>%{branch_name}</strong> 已創建。如需è¨ç½®è‡ªå‹•éƒ¨ç½²ï¼Œ è«‹é¸æ“‡åˆé©çš„ GitLab CI Yaml " "模æ¿ä½µæ交更改。%{link_to_autodeploy_doc}" +msgid "BranchSwitcherPlaceholder|Search branches" +msgstr "æœç´¢åˆ†æ”¯" + +msgid "BranchSwitcherTitle|Switch branch" +msgstr "切æ›åˆ†æ”¯" + msgid "Branches" msgstr "分支" +msgid "Browse Directory" +msgstr "ç€è¦½ç›®éŒ„" + +msgid "Browse File" +msgstr "ç€è¦½æ–‡ä»¶" + +msgid "Browse Files" +msgstr "ç€è¦½æ–‡ä»¶" + msgid "Browse files" msgstr "ç€è¦½æ–‡ä»¶" @@ -173,6 +197,9 @@ msgstr "æ·»åŠ %{file_name}" msgid "Commits" msgstr "æ交" +msgid "Commits feed" +msgstr "æ交動態" + msgid "Commits|History" msgstr "æ·å²" @@ -330,6 +357,9 @@ msgstr "無法刪除æµæ°´ç·šè¨ˆåŠƒ" msgid "Files" msgstr "文件" +msgid "Filter by commit message" +msgstr "按æ交消æ¯éŽæ¿¾" + msgid "Find by path" msgstr "按路徑查找" @@ -693,7 +723,7 @@ msgstr "é¸æ“‡æ™‚å€" msgid "Select target branch" msgstr "é¸æ“‡ç›®æ¨™åˆ†æ”¯" -msgid "Set a password on your account to pull or push via %{protocol}" +msgid "Set a password on your account to pull or push via %{protocol}." msgstr "ç‚ºè³¬è™Ÿæ·»åŠ å£¹å€‹ç”¨æ–¼æŽ¨é€æˆ–拉å–çš„ %{protocol} 密碼。" msgid "Set up CI" @@ -975,9 +1005,15 @@ msgstr "上傳新文件" msgid "Upload file" msgstr "上傳文件" +msgid "UploadLink|click to upload" +msgstr "點擊上傳" + msgid "Use your global notification setting" msgstr "使用全局通知è¨ç½®" +msgid "View open merge request" +msgstr "查看開啟的åˆä¸¦è«‹æ±‚" + msgid "VisibilityLevel|Internal" msgstr "內部" diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po index 5130572d7ed..fa0b3b339fa 100644 --- a/locale/zh_TW/gitlab.po +++ b/locale/zh_TW/gitlab.po @@ -1,128 +1,490 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the gitlab package. -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. -# +# Huang Tao <htve@outlook.com>, 2017. #zanata +# Lin Jen-Shin <anonymous@domain.com>, 2017. +# Hazel Yang <anonymous@domain.com>, 2017. +# TzeKei Lee <anonymous@domain.com>, 2017. +# Jerry Ho <a29988122@gmail.com>, 2017. msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2017-05-04 19:24-0500\n" -"Last-Translator: HuangTao <htve@outlook.com>, 2017\n" -"Language-Team: Chinese (Taiwan) (https://www.transifex.com/gitlab-zh/teams/751" -"77/zh_TW/)\n" +"POT-Creation-Date: 2017-06-19 15:50-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: zh_TW\n" -"Plural-Forms: nplurals=1; plural=0;\n" +"PO-Revision-Date: 2017-06-28 11:13-0400\n" +"Last-Translator: Huang Tao <htve@outlook.com>\n" +"Language-Team: Chinese (Taiwan) (https://translate.zanata.org/project/view/GitLab)\n" +"Language: zh-TW\n" +"X-Generator: Zanata 3.9.6\n" +"Plural-Forms: nplurals=1; plural=0\n" + +msgid "%d additional commit has been omitted to prevent performance issues." +msgid_plural "" +"%d additional commits have been omitted to prevent performance issues." +msgstr[0] "å› æ•ˆèƒ½è€ƒé‡ï¼Œä¸é¡¯ç¤º %d 個更動 (commit)。" + +msgid "%d commit" +msgid_plural "%d commits" +msgstr[0] "%d 個更動 (commit)" + +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "%{commit_author_link} 在 %{commit_timeago} é€äº¤" + +msgid "About auto deploy" +msgstr "關於自動部署" + +msgid "Active" +msgstr "啟用" + +msgid "Activity" +msgstr "活動" + +msgid "Add Changelog" +msgstr "新增更新日誌" + +msgid "Add Contribution guide" +msgstr "新增å”作指å—" + +msgid "Add License" +msgstr "新增授權æ¢æ¬¾" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "請先新增 SSH 金鑰到您的個人帳號,æ‰èƒ½ä½¿ç”¨ SSH 來上傳 (push) 或下載 (pull) 。" + +msgid "Add new directory" +msgstr "新增目錄" + +msgid "Archived project! Repository is read-only" +msgstr "æ¤å°ˆæ¡ˆå·²å°å˜ï¼æª”案庫 (repository) 為唯讀狀態" msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "確定è¦åˆªé™¤æ¤æµæ°´ç·š (pipeline) 排程嗎?" + +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "拖放檔案到æ¤è™•æˆ–者 %{upload_link}" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "分支 (branch) " + +msgid "" +"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, " +"choose a GitLab CI Yaml template and commit your changes. " +"%{link_to_autodeploy_doc}" msgstr "" +"已建立分支 (branch) <strong>%{branch_name}</strong> 。如è¦è¨å®šè‡ªå‹•éƒ¨ç½²ï¼Œ è«‹é¸æ“‡åˆé©çš„ GitLab CI " +"Yaml 模æ¿ï¼Œç„¶å¾Œè¨˜å¾—è¦é€äº¤ (commit) 您的編輯內容。%{link_to_autodeploy_doc}\n" + +msgid "BranchSwitcherPlaceholder|Search branches" +msgstr "æœå°‹åˆ†æ”¯ (branches)" + +msgid "BranchSwitcherTitle|Switch branch" +msgstr "切æ›åˆ†æ”¯ (branch)" + +msgid "Branches" +msgstr "分支 (branch) " + +msgid "Browse Directory" +msgstr "ç€è¦½ç›®éŒ„" + +msgid "Browse File" +msgstr "ç€è¦½æª”案" + +msgid "Browse Files" +msgstr "ç€è¦½æª”案" + +msgid "Browse files" +msgstr "ç€è¦½æª”案" msgid "ByAuthor|by" -msgstr "作者:" +msgstr "作者:" + +msgid "CI configuration" +msgstr "CI 組態" msgid "Cancel" -msgstr "" +msgstr "å–消" + +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "挑é¸åˆ°åˆ†æ”¯ (branch) " + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "還原分支 (branch) " + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "挑é¸" + +msgid "ChangeTypeAction|Revert" +msgstr "還原" + +msgid "Changelog" +msgstr "更新日誌" + +msgid "Charts" +msgstr "統計圖" + +msgid "Cherry-pick this commit" +msgstr "挑é¸æ¤æ›´å‹•è¨˜éŒ„ (commit) " + +msgid "Cherry-pick this merge request" +msgstr "挑é¸æ¤åˆä½µè«‹æ±‚ (merge request) " + +msgid "CiStatusLabel|canceled" +msgstr "å·²å–消" + +msgid "CiStatusLabel|created" +msgstr "已建立" + +msgid "CiStatusLabel|failed" +msgstr "失敗" + +msgid "CiStatusLabel|manual action" +msgstr "手動æ“作" + +msgid "CiStatusLabel|passed" +msgstr "已通éŽ" + +msgid "CiStatusLabel|passed with warnings" +msgstr "通éŽï¼Œä½†æœ‰è¦å‘Šè¨Šæ¯" + +msgid "CiStatusLabel|pending" +msgstr "ç‰å¾…ä¸" + +msgid "CiStatusLabel|skipped" +msgstr "已跳éŽ" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "ç‰å¾…手動æ“作" + +msgid "CiStatusText|blocked" +msgstr "已阻擋" + +msgid "CiStatusText|canceled" +msgstr "å·²å–消" + +msgid "CiStatusText|created" +msgstr "已建立" + +msgid "CiStatusText|failed" +msgstr "失敗" + +msgid "CiStatusText|manual" +msgstr "手動æ“作" + +msgid "CiStatusText|passed" +msgstr "已通éŽ" + +msgid "CiStatusText|pending" +msgstr "ç‰å¾…ä¸" + +msgid "CiStatusText|skipped" +msgstr "已跳éŽ" + +msgid "CiStatus|running" +msgstr "執行ä¸" msgid "Commit" msgid_plural "Commits" -msgstr[0] "é€äº¤" +msgstr[0] "更動記錄 (commit) " + +msgid "Commit message" +msgstr "更動說明 (commit) " + +msgid "CommitBoxTitle|Commit" +msgstr "é€äº¤" + +msgid "CommitMessage|Add %{file_name}" +msgstr "建立 %{file_name}" + +msgid "Commits" +msgstr "更動記錄 (commit) " + +msgid "Commits feed" +msgstr "æ›´å‹•æ‘˜è¦ (commit feed)" + +msgid "Commits|History" +msgstr "éŽåŽ»æ›´å‹• (commit) " + +msgid "Committed by" +msgstr "é€äº¤è€…為 " + +msgid "Compare" +msgstr "比較" + +msgid "Contribution guide" +msgstr "å”作指å—" + +msgid "Contributors" +msgstr "å”作者" + +msgid "Copy URL to clipboard" +msgstr "複製網å€åˆ°å‰ªè²¼ç°¿" + +msgid "Copy commit SHA to clipboard" +msgstr "複製更動記錄 (commit) çš„ SHA 值到剪貼簿" + +msgid "Create New Directory" +msgstr "建立新目錄" + +msgid "Create directory" +msgstr "建立目錄" + +msgid "Create empty bare repository" +msgstr "建立一個新的 bare repository" + +msgid "Create merge request" +msgstr "發出åˆä½µè«‹æ±‚ (merge request) " + +msgid "Create new..." +msgstr "建立..." + +msgid "CreateNewFork|Fork" +msgstr "分支 (fork) " + +msgid "CreateTag|Tag" +msgstr "建立標籤" msgid "Cron Timezone" +msgstr "Cron 時å€" + +msgid "Cron syntax" +msgstr "Cron 語法" + +msgid "Custom notification events" +msgstr "自訂事件通知" + +msgid "" +"Custom notification levels are the same as participating levels. With custom " +"notification levels you will also receive notifications for select events. " +"To find out more, check out %{notification_link}." msgstr "" +"自訂通知層級相當於åƒèˆ‡åº¦è¨å®šã€‚使用自訂通知層級,您å¯ä»¥åªæ”¶åˆ°ç‰¹å®šçš„事件通知。請åƒç…§ %{notification_link} 以ç²å¾—更多訊æ¯ã€‚" -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." -msgstr "週期分æžæ¦‚è¿°äº†ä½ çš„å°ˆæ¡ˆå¾žæƒ³æ³•åˆ°ç”¢å“實ç¾ï¼Œå„階段所需的時間。" +msgid "Cycle Analytics" +msgstr "週期分æž" + +msgid "" +"Cycle Analytics gives an overview of how much time it takes to go from idea " +"to production in your project." +msgstr "週期分æžè®“您å¯ä»¥æœ‰æ•ˆçš„é‡æ¸…專案從發想到產å“推出所花的時間長çŸã€‚" msgid "CycleAnalyticsStage|Code" msgstr "程å¼é–‹ç™¼" msgid "CycleAnalyticsStage|Issue" -msgstr "è°é¡Œ" +msgstr "è°é¡Œ (issue) " msgid "CycleAnalyticsStage|Plan" msgstr "計劃" msgid "CycleAnalyticsStage|Production" -msgstr "上線" +msgstr "營é‹" msgid "CycleAnalyticsStage|Review" msgstr "複閱" msgid "CycleAnalyticsStage|Staging" -msgstr "é å‚™" +msgstr "試營é‹" msgid "CycleAnalyticsStage|Test" msgstr "測試" +msgid "Define a custom pattern with cron syntax" +msgstr "使用 Cron 語法自訂排程" + msgid "Delete" -msgstr "" +msgstr "刪除" msgid "Deploy" msgid_plural "Deploys" msgstr[0] "部署" msgid "Description" -msgstr "" +msgstr "æè¿°" + +msgid "Directory name" +msgstr "目錄å稱" + +msgid "Don't show again" +msgstr "ä¸å†é¡¯ç¤º" + +msgid "Download" +msgstr "下載" + +msgid "Download tar" +msgstr "下載 tar" + +msgid "Download tar.bz2" +msgstr "下載 tar.bz2" + +msgid "Download tar.gz" +msgstr "下載 tar.gz" + +msgid "Download zip" +msgstr "下載 zip" + +msgid "DownloadArtifacts|Download" +msgstr "下載" + +msgid "DownloadCommit|Email Patches" +msgstr "é›»å郵件修補檔案 (patch)" + +msgid "DownloadCommit|Plain Diff" +msgstr "差異檔 (diff)" + +msgid "DownloadSource|Download" +msgstr "下載原始碼" msgid "Edit" -msgstr "" +msgstr "編輯" msgid "Edit Pipeline Schedule %{id}" -msgstr "" +msgstr "編輯 %{id} æµæ°´ç·š (pipeline) 排程" + +msgid "Every day (at 4:00am)" +msgstr "æ¯æ—¥åŸ·è¡Œï¼ˆæ·©æ™¨å››é»žï¼‰" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "æ¯æœˆåŸ·è¡Œï¼ˆæ¯æœˆä¸€æ—¥æ·©æ™¨å››é»žï¼‰" + +msgid "Every week (Sundays at 4:00am)" +msgstr "æ¯é€±åŸ·è¡Œï¼ˆé€±æ—¥æ·©æ™¨ 四點)" msgid "Failed to change the owner" -msgstr "" +msgstr "無法變更所有權" msgid "Failed to remove the pipeline schedule" -msgstr "" +msgstr "無法刪除æµæ°´ç·š (pipeline) 排程" -msgid "Filter" -msgstr "" +msgid "Files" +msgstr "檔案" + +msgid "Filter by commit message" +msgstr "以更動說明篩é¸" + +msgid "Find by path" +msgstr "以路徑æœå°‹" + +msgid "Find file" +msgstr "æœå°‹æª”案" msgid "FirstPushedBy|First" -msgstr "首次推é€" +msgstr "é¦–æ¬¡æŽ¨é€ (push) " msgid "FirstPushedBy|pushed by" -msgstr "推é€è€…:" +msgstr "推é€è€… (push) :" + +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "分支 (fork) " + +msgid "ForkedFromProjectPath|Forked from" +msgstr "分支 (fork) 自" msgid "From issue creation until deploy to production" -msgstr "從è°é¡Œå»ºç«‹è‡³ç·šä¸Šéƒ¨ç½²" +msgstr "從è°é¡Œ (issue) 建立直到部署至營é‹ç’°å¢ƒ" msgid "From merge request merge until deploy to production" -msgstr "從請求被åˆä½µå¾Œè‡³ç·šä¸Šéƒ¨ç½²" +msgstr "從請求被åˆä½µå¾Œ (merge request merged) 直到部署至營é‹ç’°å¢ƒ" + +msgid "Go to your fork" +msgstr "å‰å¾€æ‚¨çš„分支 (fork) " + +msgid "GoToYourFork|Fork" +msgstr "å‰å¾€æ‚¨çš„分支 (fork) " + +msgid "Home" +msgstr "首é " + +msgid "Housekeeping successfully started" +msgstr "已開始ç¶è·" + +msgid "Import repository" +msgstr "匯入檔案庫 (repository)" msgid "Interval Pattern" -msgstr "" +msgstr "循環週期" msgid "Introducing Cycle Analytics" msgstr "週期分æžç°¡ä»‹" +msgid "LFSStatus|Disabled" +msgstr "åœç”¨" + +msgid "LFSStatus|Enabled" +msgstr "啟用" + msgid "Last %d day" msgid_plural "Last %d days" -msgstr[0] "最後 %d 天" +msgstr[0] "最近 %d 天" msgid "Last Pipeline" -msgstr "" +msgstr "最新æµæ°´ç·š (pipeline) " + +msgid "Last Update" +msgstr "最後更新" + +msgid "Last commit" +msgstr "最後更動記錄 (commit) " + +msgid "Learn more in the" +msgstr "了解更多" + +msgid "Learn more in the|pipeline schedules documentation" +msgstr "æµæ°´ç·š (pipeline) 排程說明文件" + +msgid "Leave group" +msgstr "退出群組" + +msgid "Leave project" +msgstr "退出專案" msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" -msgstr[0] "最多顯示 %d 個事件" +msgstr[0] "é™åˆ¶æœ€å¤šé¡¯ç¤º %d 個事件" msgid "Median" msgstr "ä¸ä½æ•¸" +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "新增 SSH 金鑰" + msgid "New Issue" msgid_plural "New Issues" -msgstr[0] "æ–°è°é¡Œ" +msgstr[0] "建立è°é¡Œ (issue) " msgid "New Pipeline Schedule" -msgstr "" +msgstr "建立æµæ°´ç·š (pipeline) 排程" + +msgid "New branch" +msgstr "新分支 (branch) " + +msgid "New directory" +msgstr "新增目錄" + +msgid "New file" +msgstr "新增檔案" + +msgid "New issue" +msgstr "新增è°é¡Œ (issue) " + +msgid "New merge request" +msgstr "新增åˆä½µè«‹æ±‚ (merge request) " + +msgid "New schedule" +msgstr "新增排程" + +msgid "New snippet" +msgstr "æ–°æ–‡å—片段" + +msgid "New tag" +msgstr "新增標籤" + +msgid "No repository" +msgstr "找ä¸åˆ°æª”案庫 (repository)" msgid "No schedules" -msgstr "" +msgstr "沒有排程" msgid "Not available" msgstr "無法使用" @@ -130,135 +492,502 @@ msgstr "無法使用" msgid "Not enough data" msgstr "資料ä¸è¶³" +msgid "Notification events" +msgstr "事件通知" + +msgid "NotificationEvent|Close issue" +msgstr "關閉è°é¡Œ (issue) " + +msgid "NotificationEvent|Close merge request" +msgstr "關閉åˆä½µè«‹æ±‚ (merge request) " + +msgid "NotificationEvent|Failed pipeline" +msgstr "æµæ°´ç·š (pipeline) 失敗" + +msgid "NotificationEvent|Merge merge request" +msgstr "åˆä½µè«‹æ±‚ (merge request) 被åˆä½µ" + +msgid "NotificationEvent|New issue" +msgstr "新增è°é¡Œ (issue) " + +msgid "NotificationEvent|New merge request" +msgstr "新增åˆä½µè«‹æ±‚ (merge request) " + +msgid "NotificationEvent|New note" +msgstr "新增評論" + +msgid "NotificationEvent|Reassign issue" +msgstr "é‡æ–°æŒ‡æ´¾è°é¡Œ (issue) " + +msgid "NotificationEvent|Reassign merge request" +msgstr "é‡æ–°æŒ‡æ´¾åˆä½µè«‹æ±‚ (merge request) " + +msgid "NotificationEvent|Reopen issue" +msgstr "é‡å•Ÿè°é¡Œ (issue)" + +msgid "NotificationEvent|Successful pipeline" +msgstr "æµæ°´ç·š (pipeline) æˆåŠŸå®Œæˆ" + +msgid "NotificationLevel|Custom" +msgstr "自訂" + +msgid "NotificationLevel|Disabled" +msgstr "åœç”¨" + +msgid "NotificationLevel|Global" +msgstr "全域" + +msgid "NotificationLevel|On mention" +msgstr "æåŠ" + +msgid "NotificationLevel|Participate" +msgstr "åƒèˆ‡" + +msgid "NotificationLevel|Watch" +msgstr "關注" + +msgid "OfSearchInADropdown|Filter" +msgstr "篩é¸" + msgid "OpenedNDaysAgo|Opened" msgstr "開始於" +msgid "Options" +msgstr "é¸é …" + msgid "Owner" -msgstr "" +msgstr "所有權" + +msgid "Pipeline" +msgstr "æµæ°´ç·š (pipeline) " msgid "Pipeline Health" -msgstr "æµæ°´ç·šå¥åº·æŒ‡æ¨™" +msgstr "æµæ°´ç·š (pipeline) å¥åº·æŒ‡æ•¸" msgid "Pipeline Schedule" -msgstr "" +msgstr "æµæ°´ç·š (pipeline) 排程" msgid "Pipeline Schedules" -msgstr "" +msgstr "æµæ°´ç·š (pipeline) 排程" msgid "PipelineSchedules|Activated" -msgstr "" +msgstr "是å¦å•Ÿç”¨" msgid "PipelineSchedules|Active" -msgstr "" +msgstr "已啟用" msgid "PipelineSchedules|All" -msgstr "" +msgstr "所有" msgid "PipelineSchedules|Inactive" -msgstr "" +msgstr "未啟用" msgid "PipelineSchedules|Next Run" -msgstr "" +msgstr "下次執行時間" msgid "PipelineSchedules|None" -msgstr "" +msgstr "ç„¡" msgid "PipelineSchedules|Provide a short description for this pipeline" -msgstr "" +msgstr "請簡單說明æ¤æµæ°´ç·š (pipeline) " msgid "PipelineSchedules|Take ownership" -msgstr "" +msgstr "å–得所有權" msgid "PipelineSchedules|Target" -msgstr "" +msgstr "目標" + +msgid "PipelineSheduleIntervalPattern|Custom" +msgstr "自訂" + +msgid "Pipeline|with stage" +msgstr "於階段" + +msgid "Pipeline|with stages" +msgstr "於階段" + +msgid "Project '%{project_name}' queued for deletion." +msgstr "專案 '%{project_name}' å·²åŠ å…¥åˆªé™¤ä½‡åˆ—ã€‚" + +msgid "Project '%{project_name}' was successfully created." +msgstr "專案 '%{project_name}' 建立完æˆã€‚" + +msgid "Project '%{project_name}' was successfully updated." +msgstr "專案 '%{project_name}' 更新完æˆã€‚" + +msgid "Project '%{project_name}' will be deleted." +msgstr "專案 '%{project_name}' 將被刪除。" + +msgid "Project access must be granted explicitly to each user." +msgstr "專案權é™å¿…é ˆä¸€ä¸€æŒ‡æ´¾çµ¦æ¯å€‹ä½¿ç”¨è€…。" + +msgid "Project export could not be deleted." +msgstr "匯出的專案無法被刪除。" + +msgid "Project export has been deleted." +msgstr "匯出的專案已被刪除。" + +msgid "" +"Project export link has expired. Please generate a new export from your " +"project settings." +msgstr "專案的匯出連çµå·²å¤±æ•ˆã€‚請到專案è¨å®šä¸ç”¢ç”Ÿæ–°çš„連çµã€‚" + +msgid "Project export started. A download link will be sent by email." +msgstr "專案導出已開始。完æˆå¾Œä¸‹è¼‰é€£çµæœƒé€åˆ°æ‚¨çš„信箱。" + +msgid "Project home" +msgstr "專案首é " + +msgid "ProjectFeature|Disabled" +msgstr "åœç”¨" + +msgid "ProjectFeature|Everyone with access" +msgstr "任何人都å¯å˜å–" + +msgid "ProjectFeature|Only team members" +msgstr "åªæœ‰åœ˜éšŠæˆå“¡å¯ä»¥å˜å–" + +msgid "ProjectFileTree|Name" +msgstr "å稱" + +msgid "ProjectLastActivity|Never" +msgstr "從未" msgid "ProjectLifecycle|Stage" -msgstr "專案生命週期" +msgstr "階段" + +msgid "ProjectNetworkGraph|Graph" +msgstr "分支圖" msgid "Read more" -msgstr "了解更多" +msgstr "çžè§£æ›´å¤š" + +msgid "Readme" +msgstr "說明檔" + +msgid "RefSwitcher|Branches" +msgstr "分支 (branch) " + +msgid "RefSwitcher|Tags" +msgstr "標籤" msgid "Related Commits" -msgstr "相關的é€äº¤" +msgstr "相關的更動記錄 (commit) " msgid "Related Deployed Jobs" msgstr "相關的部署作æ¥" msgid "Related Issues" -msgstr "相關的è°é¡Œ" +msgstr "相關的è°é¡Œ (issue) " msgid "Related Jobs" msgstr "相關的作æ¥" msgid "Related Merge Requests" -msgstr "相關的åˆä½µè«‹æ±‚" +msgstr "相關的åˆä½µè«‹æ±‚ (merge request) " msgid "Related Merged Requests" msgstr "相關已åˆä½µçš„請求" +msgid "Remind later" +msgstr "ç¨å¾Œæ醒" + +msgid "Remove project" +msgstr "刪除專案" + +msgid "Request Access" +msgstr "申請權é™" + +msgid "Revert this commit" +msgstr "還原æ¤æ›´å‹•è¨˜éŒ„ (commit)" + +msgid "Revert this merge request" +msgstr "還原æ¤åˆä½µè«‹æ±‚ (merge request) " + msgid "Save pipeline schedule" -msgstr "" +msgstr "ä¿å˜æµæ°´ç·š (pipeline) 排程" msgid "Schedule a new pipeline" -msgstr "" +msgstr "建立æµæ°´ç·š (pipeline) 排程" + +msgid "Scheduling Pipelines" +msgstr "æµæ°´ç·š (pipeline) 計劃" + +msgid "Search branches and tags" +msgstr "æœå°‹åˆ†æ”¯ (branch) 和標籤" + +msgid "Select Archive Format" +msgstr "é¸æ“‡ä¸‹è¼‰æ ¼å¼" msgid "Select a timezone" -msgstr "" +msgstr "é¸æ“‡æ™‚å€" msgid "Select target branch" -msgstr "" +msgstr "é¸æ“‡ç›®æ¨™åˆ†æ”¯ (branch) " + +msgid "Set a password on your account to pull or push via %{protocol}." +msgstr "è«‹å…ˆè¨å®šå¯†ç¢¼ï¼Œæ‰èƒ½ä½¿ç”¨ %{protocol} 來上傳 (push) 或下載 (pull) 。" + +msgid "Set up CI" +msgstr "è¨å®š CI" + +msgid "Set up Koding" +msgstr "è¨å®š Koding" + +msgid "Set up auto deploy" +msgstr "è¨å®šè‡ªå‹•éƒ¨ç½²" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "è¨å®šå¯†ç¢¼" msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "顯示 %d 個事件" +msgid "Source code" +msgstr "原始碼" + +msgid "StarProject|Star" +msgstr "收è—" + +msgid "Start a %{new_merge_request} with these changes" +msgstr "以這些改動建立一個新的 %{new_merge_request} " + +msgid "Switch branch/tag" +msgstr "切æ›åˆ†æ”¯ (branch) 或標籤" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "標籤" + +msgid "Tags" +msgstr "標籤" + msgid "Target Branch" -msgstr "" +msgstr "目標分支 (branch) " -msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." -msgstr "程å¼é–‹ç™¼éšŽæ®µé¡¯ç¤ºå¾žç¬¬ä¸€æ¬¡é€äº¤åˆ°å»ºç«‹åˆä½µè«‹æ±‚的時間。建立第一個åˆä½µè«‹æ±‚後,資料將自動填入。" +msgid "" +"The coding stage shows the time from the first commit to creating the merge " +"request. The data will automatically be added here once you create your " +"first merge request." +msgstr "" +"程å¼é–‹ç™¼éšŽæ®µé¡¯ç¤ºå¾žç¬¬ä¸€æ¬¡æ›´å‹•è¨˜éŒ„ (commit) 到建立åˆä½µè«‹æ±‚ (merge request) 的時間。建立第一個åˆä½µè«‹æ±‚後,資料將自動填入。" msgid "The collection of events added to the data gathered for that stage." -msgstr "與該階段相關的事件。" +msgstr "該階段ä¸çš„相關事件集åˆã€‚" -msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." -msgstr "è°é¡ŒéšŽæ®µé¡¯ç¤ºå¾žè°é¡Œå»ºç«‹åˆ°è¨ç½®é‡Œç¨‹ç¢‘ã€æˆ–將該è°é¡ŒåŠ 至è°é¡Œçœ‹æ¿çš„時間。建立第一個è°é¡Œå¾Œï¼Œè³‡æ–™å°‡è‡ªå‹•å¡«å…¥ã€‚" +msgid "The fork relationship has been removed." +msgstr "åˆ†æ”¯èˆ‡ä¸»å¹¹é–“çš„é—œè¯ (fork relationship) 已被刪除。" + +msgid "" +"The issue stage shows the time it takes from creating an issue to assigning " +"the issue to a milestone, or add the issue to a list on your Issue Board. " +"Begin creating issues to see data for this stage." +msgstr "" +"è°é¡Œ (issue) 階段顯示從è°é¡Œå»ºç«‹åˆ°è¨å®šé‡Œç¨‹ç¢‘所花的時間,或是è°é¡Œè¢«åˆ†é¡žåˆ°è°é¡Œçœ‹æ¿ (issue board) " +"ä¸æ‰€èŠ±çš„時間。建立第一個è°é¡Œå¾Œï¼Œè³‡æ–™å°‡è‡ªå‹•å¡«å…¥ã€‚" msgid "The phase of the development lifecycle." -msgstr "專案開發生命週期的å„個階段。" +msgstr "專案開發週期的å„個階段。" + +msgid "" +"The pipelines schedule runs pipelines in the future, repeatedly, for " +"specific branches or tags. Those scheduled pipelines will inherit limited " +"project access based on their associated user." +msgstr "" +"在指定了特定分支 (branch) 或標籤後,æ¤è™•çš„æµæ°´ç·š (pipeline) 排程會ä¸æ–·åœ°é‡è¤‡åŸ·è¡Œã€‚\n" +"æµæ°´ç·šæŽ’程的å˜å–權é™èˆ‡å°ˆæ¡ˆæœ¬èº«ç›¸åŒã€‚" -msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." -msgstr "計劃階段所顯示的是è°é¡Œè¢«æŽ’程後至第一個é€äº¤è¢«æŽ¨é€çš„時間。一旦完æˆï¼ˆæˆ–執行)首次的推é€ï¼Œè³‡æ–™å°‡è‡ªå‹•å¡«å…¥ã€‚" +msgid "" +"The planning stage shows the time from the previous step to pushing your " +"first commit. This time will be added automatically once you push your first " +"commit." +msgstr "計劃階段顯示從更動記錄 (commit) 被排程至第一個推é€çš„時間。第一次推é€ä¹‹å¾Œï¼Œè³‡æ–™å°‡è‡ªå‹•å¡«å…¥ã€‚" + +msgid "" +"The production stage shows the total time it takes between creating an issue " +"and deploying the code to production. The data will be automatically added " +"once you have completed the full idea to production cycle." +msgstr "營é‹éšŽæ®µé¡¯ç¤ºå¾žå»ºç«‹è°é¡Œ (issue) 到部署程å¼ä¸Šç·šæ‰€èŠ±çš„時間。完æˆå¾žç™¼æƒ³åˆ°ä¸Šç·šçš„完整開發週期後,資料將自動填入。" -msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle." -msgstr "上線階段顯示從建立一個è°é¡Œåˆ°éƒ¨ç½²ç¨‹å¼è‡³ç·šä¸Šçš„總時間。當完æˆå¾žæƒ³æ³•åˆ°ç”¢å“實ç¾çš„循環後,資料將自動填入。" +msgid "The project can be accessed by any logged in user." +msgstr "本專案å¯è®“任何已登入的使用者å˜å–" -msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." -msgstr "複閱階段顯示從åˆä½µè«‹æ±‚建立後至被åˆä½µçš„時間。當建立第一個åˆä½µè«‹æ±‚後,資料將自動填入。" +msgid "The project can be accessed without any authentication." +msgstr "本專案å¯è®“任何人å˜å–" -msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." -msgstr "é 備階段顯示從åˆä½µè«‹æ±‚被åˆä½µå¾Œè‡³éƒ¨ç½²ä¸Šç·šçš„時間。當第一次部署上線後,資料將自動填入。" +msgid "The repository for this project does not exist." +msgstr "本專案沒有檔案庫 (repository) " -msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running." -msgstr "測試階段顯示相關åˆä½µè«‹æ±‚çš„æµæ°´ç·šæ‰€èŠ±çš„時間。當第一個æµæ°´ç·šé‹ä½œå®Œç•¢å¾Œï¼Œè³‡æ–™å°‡è‡ªå‹•å¡«å…¥ã€‚" +msgid "" +"The review stage shows the time from creating the merge request to merging " +"it. The data will automatically be added after you merge your first merge " +"request." +msgstr "" +"複閱階段顯示從åˆä½µè«‹æ±‚ (merge request) 建立後至被åˆä½µçš„時間。當建立第一個åˆä½µè«‹æ±‚ (merge request) 後,資料將自動填入。" + +msgid "" +"The staging stage shows the time between merging the MR and deploying code " +"to the production environment. The data will be automatically added once you " +"deploy to production for the first time." +msgstr "試營é‹æ®µé¡¯ç¤ºå¾žåˆä½µè«‹æ±‚ (merge request) 被åˆä½µå¾Œè‡³éƒ¨ç½²ç‡Ÿé‹çš„時間。當第一次部署營é‹å¾Œï¼Œè³‡æ–™å°‡è‡ªå‹•å¡«å…¥" + +msgid "" +"The testing stage shows the time GitLab CI takes to run every pipeline for " +"the related merge request. The data will automatically be added after your " +"first pipeline finishes running." +msgstr "" +"測試階段顯示相關åˆä½µè«‹æ±‚ (merge request) çš„æµæ°´ç·š (pipeline) 所花的時間。當第一個æµæ°´ç·š (pipeline) " +"執行完畢後,資料將自動填入。" msgid "The time taken by each data entry gathered by that stage." -msgstr "æ¯ç†è©²éšŽæ®µç›¸é—œè³‡æ–™æ‰€èŠ±çš„時間。" +msgstr "該階段ä¸æ¯ä¸€å€‹è³‡æ–™é …目所花的時間。" -msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." +msgid "" +"The value lying at the midpoint of a series of observed values. E.g., " +"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 =" +" 6." msgstr "ä¸ä½æ•¸æ˜¯ä¸€å€‹æ•¸åˆ—ä¸æœ€ä¸é–“的值。例如在 3ã€5ã€9 之間,ä¸ä½æ•¸æ˜¯ 5。在 3ã€5ã€7ã€8 之間,ä¸ä½æ•¸æ˜¯ (5 + 7)/ 2 = 6。" +msgid "" +"This means you can not push code until you create an empty repository or " +"import existing one." +msgstr "這代表在您建立一個空的檔案庫 (repository) 或是匯入一個ç¾å˜çš„檔案庫之å‰ï¼Œæ‚¨å°‡ç„¡æ³•ä¸Šå‚³æ›´æ–° (push) 。" + msgid "Time before an issue gets scheduled" -msgstr "è°é¡Œç‰å¾…排程的時間" +msgstr "è°é¡Œ (issue) 被列入日程表的時間" msgid "Time before an issue starts implementation" -msgstr "è°é¡Œç‰å¾…開始實作的時間" +msgstr "è°é¡Œ (issue) ç‰å¾…開始實作的時間" msgid "Time between merge request creation and merge/close" -msgstr "åˆä½µè«‹æ±‚被åˆä½µæˆ–是關閉的時間" +msgstr "åˆä½µè«‹æ±‚ (merge request) 從建立到被åˆä½µæˆ–是關閉的時間" msgid "Time until first merge request" -msgstr "第一個åˆä½µè«‹æ±‚被建立å‰çš„時間" +msgstr "第一個åˆä½µè«‹æ±‚ (merge request) 被建立å‰çš„時間" + +msgid "Timeago|%s days ago" +msgstr " %s 天å‰" + +msgid "Timeago|%s days remaining" +msgstr "剩下 %s 天" + +msgid "Timeago|%s hours remaining" +msgstr "剩下 %s å°æ™‚" + +msgid "Timeago|%s minutes ago" +msgstr " %s 分é˜å‰" + +msgid "Timeago|%s minutes remaining" +msgstr "剩下 %s 分é˜" + +msgid "Timeago|%s months ago" +msgstr " %s 個月å‰" + +msgid "Timeago|%s months remaining" +msgstr "剩下 %s 月" + +msgid "Timeago|%s seconds remaining" +msgstr "剩下 %s 秒" + +msgid "Timeago|%s weeks ago" +msgstr " %s 週å‰" + +msgid "Timeago|%s weeks remaining" +msgstr "剩下 %s 週" + +msgid "Timeago|%s years ago" +msgstr " %s å¹´å‰" + +msgid "Timeago|%s years remaining" +msgstr "剩下 %s å¹´" + +msgid "Timeago|1 day remaining" +msgstr "剩下 1 天" + +msgid "Timeago|1 hour remaining" +msgstr "剩下 1 å°æ™‚" + +msgid "Timeago|1 minute remaining" +msgstr "剩下 1 分é˜" + +msgid "Timeago|1 month remaining" +msgstr "剩下 1 個月" + +msgid "Timeago|1 week remaining" +msgstr "剩下 1 週" + +msgid "Timeago|1 year remaining" +msgstr "剩下 1 å¹´" + +msgid "Timeago|Past due" +msgstr "逾期" + +msgid "Timeago|a day ago" +msgstr " 1 天å‰" + +msgid "Timeago|a month ago" +msgstr " 1 個月å‰" + +msgid "Timeago|a week ago" +msgstr " 1 週å‰" + +msgid "Timeago|a while" +msgstr "剛剛" + +msgid "Timeago|a year ago" +msgstr " 1 å¹´å‰" + +msgid "Timeago|about %s hours ago" +msgstr "ç´„ %s å°æ™‚å‰" + +msgid "Timeago|about a minute ago" +msgstr "ç´„ 1 分é˜å‰" + +msgid "Timeago|about an hour ago" +msgstr "ç´„ 1 å°æ™‚å‰" + +msgid "Timeago|in %s days" +msgstr " %s 天後" + +msgid "Timeago|in %s hours" +msgstr " %s å°æ™‚後" + +msgid "Timeago|in %s minutes" +msgstr " %s 分é˜å¾Œ" + +msgid "Timeago|in %s months" +msgstr " %s 個月後" + +msgid "Timeago|in %s seconds" +msgstr " %s 秒後" + +msgid "Timeago|in %s weeks" +msgstr " %s 週後" + +msgid "Timeago|in %s years" +msgstr " %s 年後" + +msgid "Timeago|in 1 day" +msgstr " 1 天後" + +msgid "Timeago|in 1 hour" +msgstr " 1 å°æ™‚後" + +msgid "Timeago|in 1 minute" +msgstr " 1 分é˜å¾Œ" + +msgid "Timeago|in 1 month" +msgstr " 1 個月後" + +msgid "Timeago|in 1 week" +msgstr " 1 週後" + +msgid "Timeago|in 1 year" +msgstr " 1 年後" + +msgid "Timeago|less than a minute ago" +msgstr "ä¸åˆ° 1 分é˜å‰" msgid "Time|hr" msgid_plural "Time|hrs" @@ -275,7 +1004,34 @@ msgid "Total Time" msgstr "總時間" msgid "Total test time for all commits/merges" -msgstr "所有é€äº¤å’Œåˆä½µçš„總測試時間" +msgstr "åˆä½µ (merge) 與更動記錄 (commit) 的總測試時間" + +msgid "Unstar" +msgstr "å–消收è—" + +msgid "Upload New File" +msgstr "上傳新檔案" + +msgid "Upload file" +msgstr "上傳檔案" + +msgid "UploadLink|click to upload" +msgstr "點擊上傳" + +msgid "Use your global notification setting" +msgstr "使用全域通知è¨å®š" + +msgid "View open merge request" +msgstr "查看æ¤åˆ†æ”¯çš„åˆä½µè«‹æ±‚ (merge request)" + +msgid "VisibilityLevel|Internal" +msgstr "內部" + +msgid "VisibilityLevel|Private" +msgstr "ç§æœ‰" + +msgid "VisibilityLevel|Public" +msgstr "公開" msgid "Want to see the data? Please ask an administrator for access." msgstr "權é™ä¸è¶³ã€‚如需查看相關資料,請å‘管ç†å“¡ç”³è«‹æ¬Šé™ã€‚" @@ -283,12 +1039,85 @@ msgstr "權é™ä¸è¶³ã€‚如需查看相關資料,請å‘管ç†å“¡ç”³è«‹æ¬Šé™ã€‚ msgid "We don't have enough data to show this stage." msgstr "å› è©²éšŽæ®µçš„è³‡æ–™ä¸è¶³è€Œç„¡æ³•é¡¯ç¤ºç›¸é—œè³‡è¨Š" -msgid "You have reached your project limit" +msgid "Withdraw Access Request" +msgstr "å–消權é™ç”³è«‹" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" +"å³å°‡è¦åˆªé™¤ %{project_name_with_namespace}。\n" +"被刪除的專案完全無法救回來喔ï¼\n" +"真的「100%確定ã€è¦é€™éº¼åšå—Žï¼Ÿ" + +msgid "" +"You are going to remove the fork relationship to source project " +"%{forked_from_project}. Are you ABSOLUTELY sure?" msgstr "" +"å°‡è¦åˆªé™¤æœ¬åˆ†æ”¯å°ˆæ¡ˆèˆ‡ä¸»å¹¹çš„æ‰€æœ‰é—œè¯ (fork relationship) 。 %{forked_from_project} " +"真的「100%確定ã€è¦é€™éº¼åšå—Žï¼Ÿ" + +msgid "" +"You are going to transfer %{project_name_with_namespace} to another owner. " +"Are you ABSOLUTELY sure?" +msgstr "å°‡è¦æŠŠ %{project_name_with_namespace} 的所有權轉移給å¦ä¸€å€‹äººã€‚真的「100%確定ã€è¦é€™éº¼åšå—Žï¼Ÿ" + +msgid "You can only add files when you are on a branch" +msgstr "åªèƒ½åœ¨åˆ†æ”¯ (branch) 上建立檔案" + +msgid "You have reached your project limit" +msgstr "您已é”到專案數é‡é™åˆ¶" + +msgid "You must sign in to star a project" +msgstr "å¿…é ˆç™»å…¥æ‰èƒ½æ”¶è—專案" msgid "You need permission." -msgstr "您需è¦ç›¸é—œçš„權é™ã€‚" +msgstr "需è¦æ¬Šé™æ‰èƒ½é€™éº¼åšã€‚" + +msgid "You will not get any notifications via email" +msgstr "ä¸æœƒæ”¶åˆ°ä»»ä½•é€šçŸ¥éƒµä»¶" + +msgid "You will only receive notifications for the events you choose" +msgstr "åªæŽ¥æ”¶æ‚¨é¸æ“‡çš„事件通知" + +msgid "" +"You will only receive notifications for threads you have participated in" +msgstr "åªæŽ¥æ”¶åƒèˆ‡ä¸»é¡Œçš„通知" + +msgid "You will receive notifications for any activity" +msgstr "接收所有活動的通知" + +msgid "" +"You will receive notifications only for comments in which you were " +"@mentioned" +msgstr "åªæŽ¥æ”¶è©•è«–ä¸æåŠ(@)您的通知" + +msgid "" +"You won't be able to pull or push project code via %{protocol} until you " +"%{set_password_link} on your account" +msgstr "" +"在帳號上 %{set_password_link} 之å‰ï¼Œ 將無法使用 %{protocol} 上傳 (push) 或下載 (pull) 程å¼ç¢¼ã€‚" + +msgid "" +"You won't be able to pull or push project code via SSH until you " +"%{add_ssh_key_link} to your profile" +msgstr "åœ¨å€‹äººå¸³è™Ÿä¸ %{add_ssh_key_link} 之å‰ï¼Œ 將無法使用 SSH 上傳 (push) 或下載 (pull) 程å¼ç¢¼ã€‚" + +msgid "Your name" +msgstr "您的åå—" msgid "day" msgid_plural "days" msgstr[0] "天" + +msgid "new merge request" +msgstr "建立åˆä½µè«‹æ±‚" + +msgid "notification emails" +msgstr "通知信" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "上層" + diff --git a/package.json b/package.json index 045f07ee2f9..5a997e813f8 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "babel-core": "^6.22.1", + "babel-eslint": "^7.2.1", "babel-loader": "^6.2.10", "babel-plugin-transform-define": "^1.2.0", "babel-preset-latest": "^6.24.0", diff --git a/rubocop/cop/active_record_dependent.rb b/rubocop/cop/active_record_dependent.rb new file mode 100644 index 00000000000..8d15f150885 --- /dev/null +++ b/rubocop/cop/active_record_dependent.rb @@ -0,0 +1,26 @@ +require_relative '../model_helpers' + +module RuboCop + module Cop + # Cop that prevents the use of `dependent: ...` in ActiveRecord models. + class ActiveRecordDependent < RuboCop::Cop::Cop + include ModelHelpers + + MSG = 'Do not use `dependent: to remove associated data, ' \ + 'use foreign keys with cascading deletes instead'.freeze + + METHOD_NAMES = [:has_many, :has_one, :belongs_to].freeze + + def on_send(node) + return unless in_model?(node) + return unless METHOD_NAMES.include?(node.children[1]) + + node.children.last.each_node(:pair) do |pair| + key_name = pair.children[0].children[0] + + add_offense(pair, :expression) if key_name == :dependent + end + end + end + end +end diff --git a/rubocop/cop/activerecord_serialize.rb b/rubocop/cop/active_record_serialize.rb index 9bdcc3b4c34..204caf37f8b 100644 --- a/rubocop/cop/activerecord_serialize.rb +++ b/rubocop/cop/active_record_serialize.rb @@ -3,7 +3,7 @@ require_relative '../model_helpers' module RuboCop module Cop # Cop that prevents the use of `serialize` in ActiveRecord models. - class ActiverecordSerialize < RuboCop::Cop::Cop + class ActiveRecordSerialize < RuboCop::Cop::Cop include ModelHelpers MSG = 'Do not store serialized data in the database, use separate columns and/or tables instead'.freeze diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb index 69b4b29507c..1e70314598a 100644 --- a/rubocop/rubocop.rb +++ b/rubocop/rubocop.rb @@ -1,9 +1,10 @@ require_relative 'cop/custom_error_class' require_relative 'cop/gem_fetcher' -require_relative 'cop/activerecord_serialize' +require_relative 'cop/active_record_serialize' require_relative 'cop/redirect_with_status' require_relative 'cop/polymorphic_associations' require_relative 'cop/project_path_helper' +require_relative 'cop/active_record_dependent' require_relative 'cop/migration/add_column' require_relative 'cop/migration/add_column_with_default_to_large_table' require_relative 'cop/migration/add_concurrent_foreign_key' diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index f3263bc177d..c6e5fb61cf9 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -23,6 +23,21 @@ describe Groups::MilestonesController do project.team << [user, :master] end + describe "#index" do + it 'shows group milestones page' do + get :index, group_id: group.to_param + + expect(response).to have_http_status(200) + end + + it 'shows group milestones JSON' do + get :index, group_id: group.to_param, format: :json + + expect(response).to have_http_status(200) + expect(response.content_type).to eq 'application/json' + end + end + it_behaves_like 'milestone tabs' describe "#create" do diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb index 428bc45b842..d2c613a2423 100644 --- a/spec/controllers/projects/artifacts_controller_spec.rb +++ b/spec/controllers/projects/artifacts_controller_spec.rb @@ -134,10 +134,7 @@ describe Projects::ArtifactsController do context 'found the job and redirect' do shared_examples 'redirect to the job' do it 'redirects' do - path = browse_namespace_project_job_artifacts_path( - project.namespace, - project, - job) + path = browse_project_job_artifacts_path(project, job) expect(response).to redirect_to(path) end @@ -174,11 +171,7 @@ describe Projects::ArtifactsController do end it 'redirects' do - path = file_namespace_project_job_artifacts_path( - project.namespace, - project, - job, - 'README.md') + path = file_project_job_artifacts_path(project, job, 'README.md') expect(response).to redirect_to(path) end diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 561bc219bb4..02bbc48dc59 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -117,7 +117,7 @@ describe Projects::BlobController do end it 'redirects to blob show' do - expect(response).to redirect_to(namespace_project_blob_path(project.namespace, project, 'master/CHANGELOG')) + expect(response).to redirect_to(project_blob_path(project, 'master/CHANGELOG')) end end @@ -164,7 +164,7 @@ describe Projects::BlobController do end def blob_after_edit_path - namespace_project_blob_path(project.namespace, project, 'master/CHANGELOG') + project_blob_path(project, 'master/CHANGELOG') end before do @@ -186,7 +186,7 @@ describe Projects::BlobController do it 'redirects to MR diff' do put :update, mr_params - after_edit_path = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) + after_edit_path = diffs_project_merge_request_path(project, merge_request) file_anchor = "##{Digest::SHA1.hexdigest('CHANGELOG')}" expect(response).to redirect_to(after_edit_path + file_anchor) end @@ -223,7 +223,7 @@ describe Projects::BlobController do it 'redirects to blob' do put :update, default_params - expect(response).to redirect_to(namespace_project_blob_path(forked_project.namespace, forked_project, 'master/CHANGELOG')) + expect(response).to redirect_to(project_blob_path(forked_project, 'master/CHANGELOG')) end end @@ -235,8 +235,7 @@ describe Projects::BlobController do put :update, default_params expect(response).to redirect_to( - namespace_project_new_merge_request_path( - forked_project.namespace, + project_new_merge_request_path( forked_project, merge_request: { source_project_id: forked_project.id, diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index 14426b09c73..9cd4e9dbf84 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -110,7 +110,7 @@ describe Projects::BranchesController do branch_name: branch, issue_iid: issue.iid - expect(response).to redirect_to namespace_project_tree_path(project.namespace, project, branch) + expect(response).to redirect_to project_tree_path(project, branch) end it 'redirects to autodeploy setup page' do @@ -127,7 +127,7 @@ describe Projects::BranchesController do branch_name: branch, issue_iid: issue.iid - expect(response.location).to include(namespace_project_new_blob_path(project.namespace, project, branch)) + expect(response.location).to include(project_new_blob_path(project, branch)) expect(response).to have_http_status(302) end end @@ -303,7 +303,7 @@ describe Projects::BranchesController do it 'redirects to branches path' do expect(response) - .to redirect_to(namespace_project_branches_path(project.namespace, project)) + .to redirect_to(project_branches_path(project)) end end end @@ -323,7 +323,7 @@ describe Projects::BranchesController do it 'redirects to branches' do destroy_all_merged - expect(response).to redirect_to namespace_project_branches_path(project.namespace, project) + expect(response).to redirect_to project_branches_path(project) end it 'starts worker to delete merged branches' do diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index e10da40eaab..eb61a0c080c 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -169,7 +169,7 @@ describe Projects::CommitController do start_branch: 'master', id: commit.id) - expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master') + expect(response).to redirect_to project_commits_path(project, 'master') expect(flash[:notice]).to eq('The commit has been successfully reverted.') end end @@ -191,7 +191,7 @@ describe Projects::CommitController do start_branch: 'master', id: commit.id) - expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, commit.id) + expect(response).to redirect_to project_commit_path(project, commit.id) expect(flash[:alert]).to match('Sorry, we cannot revert this commit automatically.') end end @@ -218,7 +218,7 @@ describe Projects::CommitController do start_branch: 'master', id: master_pickable_commit.id) - expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master') + expect(response).to redirect_to project_commits_path(project, 'master') expect(flash[:notice]).to eq('The commit has been successfully cherry-picked.') end end @@ -240,7 +240,7 @@ describe Projects::CommitController do start_branch: 'master', id: master_pickable_commit.id) - expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, master_pickable_commit.id) + expect(response).to redirect_to project_commit_path(project, master_pickable_commit.id) expect(flash[:alert]).to match('Sorry, we cannot cherry-pick this commit automatically.') end end diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index 8f4694c9854..b4f9fd9b7a2 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -72,7 +72,7 @@ describe Projects::CompareController do from: '', to: 'master') - expect(response).to redirect_to(namespace_project_compare_index_path(project.namespace, project, to: 'master')) + expect(response).to redirect_to(project_compare_index_path(project, to: 'master')) end it 'redirects back to index when params[:to] is empty and preserves params[:from]' do @@ -82,7 +82,7 @@ describe Projects::CompareController do from: 'master', to: '') - expect(response).to redirect_to(namespace_project_compare_index_path(project.namespace, project, from: 'master')) + expect(response).to redirect_to(project_compare_index_path(project, from: 'master')) end it 'redirects back to index when params[:from] and params[:to] are empty' do diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index ad0b046742d..f88f50c3cc6 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -58,11 +58,9 @@ describe Projects::EnvironmentsController do expect(json_response['stopped_count']).to eq 1 end - it 'does not set the polling interval header' do - # TODO, this is a temporary fix, see follow up issue: - # https://gitlab.com/gitlab-org/gitlab-ee/issues/2677 + it 'sets the polling interval header' do expect(response).to have_http_status(:ok) - expect(response.headers['Poll-Interval']).to be_nil + expect(response.headers['Poll-Interval']).to eq("3000") end end @@ -184,7 +182,7 @@ describe Projects::EnvironmentsController do expect(response).to have_http_status(200) expect(json_response).to eq( { 'redirect_url' => - namespace_project_job_url(project.namespace, project, action) }) + project_job_url(project, action) }) end end @@ -198,7 +196,7 @@ describe Projects::EnvironmentsController do expect(response).to have_http_status(200) expect(json_response).to eq( { 'redirect_url' => - namespace_project_environment_url(project.namespace, project, environment) }) + project_environment_url(project, environment) }) end end end diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb index b5435357f53..48a2994cbc0 100644 --- a/spec/controllers/projects/group_links_controller_spec.rb +++ b/spec/controllers/projects/group_links_controller_spec.rb @@ -34,7 +34,7 @@ describe Projects::GroupLinksController do it 'redirects to project group links page' do expect(response).to redirect_to( - namespace_project_settings_members_path(project.namespace, project) + project_settings_members_path(project) ) end end @@ -65,7 +65,7 @@ describe Projects::GroupLinksController do it 'redirects to project group links page' do expect(response).to redirect_to( - namespace_project_settings_members_path(project.namespace, project) + project_settings_members_path(project) ) end end @@ -79,7 +79,7 @@ describe Projects::GroupLinksController do it 'redirects to project group links page' do expect(response).to redirect_to( - namespace_project_settings_members_path(project.namespace, project) + project_settings_members_path(project) ) expect(flash[:alert]).to eq('Please select a group.') end diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb index 6724b474179..9be61342616 100644 --- a/spec/controllers/projects/imports_controller_spec.rb +++ b/spec/controllers/projects/imports_controller_spec.rb @@ -59,7 +59,7 @@ describe Projects::ImportsController do it 'redirects to new_namespace_project_import_path' do get :show, namespace_id: project.namespace.to_param, project_id: project - expect(response).to redirect_to new_namespace_project_import_path(project.namespace, project) + expect(response).to redirect_to new_project_import_path(project) end end @@ -75,7 +75,7 @@ describe Projects::ImportsController do get :show, namespace_id: project.namespace.to_param, project_id: project expect(flash[:notice]).to eq 'The project was successfully forked.' - expect(response).to redirect_to namespace_project_path(project.namespace, project) + expect(response).to redirect_to project_path(project) end end @@ -84,14 +84,14 @@ describe Projects::ImportsController do get :show, namespace_id: project.namespace.to_param, project_id: project expect(flash[:notice]).to eq 'The project was successfully imported.' - expect(response).to redirect_to namespace_project_path(project.namespace, project) + expect(response).to redirect_to project_path(project) end end context 'when continue params is present' do let(:params) do { - to: namespace_project_path(project.namespace, project), + to: project_path(project), notice: 'Finished' } end @@ -120,7 +120,7 @@ describe Projects::ImportsController do it 'redirects to namespace_project_path' do get :show, namespace_id: project.namespace.to_param, project_id: project - expect(response).to redirect_to namespace_project_path(project.namespace, project) + expect(response).to redirect_to project_path(project) end end end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 9f98427a86b..22aad0b3225 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -35,7 +35,7 @@ describe Projects::IssuesController do it "returns 301 if request path doesn't match project path" do get :index, namespace_id: project.namespace, project_id: project.path.upcase - expect(response).to redirect_to(namespace_project_issues_path(project.namespace, project)) + expect(response).to redirect_to(project_issues_path(project)) end it "returns 404 when issues are disabled" do @@ -329,7 +329,7 @@ describe Projects::IssuesController do update_verified_issue expect(response) - .to redirect_to(namespace_project_issue_path(project.namespace, project, issue)) + .to redirect_to(project_issue_path(project, issue)) end it 'accepts an issue after recaptcha is verified' do diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index bf1776eb320..f19ad4c2c81 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -178,7 +178,7 @@ describe Projects::LabelsController do it 'redirects to the correct casing' do get :index, namespace_id: project.namespace, project_id: project.to_param.upcase - expect(response).to redirect_to(namespace_project_labels_path(project.namespace, project)) + expect(response).to redirect_to(project_labels_path(project)) expect(controller).not_to set_flash[:notice] end end @@ -191,7 +191,7 @@ describe Projects::LabelsController do it 'redirects to the canonical path' do get :index, namespace_id: project.namespace, project_id: project.to_param + 'old' - expect(response).to redirect_to(namespace_project_labels_path(project.namespace, project)) + expect(response).to redirect_to(project_labels_path(project)) expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, project)) end end diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb index 422a8b6fac0..12e413db902 100644 --- a/spec/controllers/projects/mattermosts_controller_spec.rb +++ b/spec/controllers/projects/mattermosts_controller_spec.rb @@ -38,7 +38,7 @@ describe Projects::MattermostsController do it 'shows the error' do allow_any_instance_of(MattermostSlashCommandsService).to receive(:configure).and_return([false, "error message"]) - expect(subject).to redirect_to(new_namespace_project_mattermost_url(project.namespace, project)) + expect(subject).to redirect_to(new_project_mattermost_url(project)) end end @@ -51,7 +51,7 @@ describe Projects::MattermostsController do subject service = project.services.last - expect(subject).to redirect_to(edit_namespace_project_service_url(project.namespace, project, service)) + expect(subject).to redirect_to(edit_project_service_url(project, service)) end end end diff --git a/spec/controllers/projects/pages_domains_controller_spec.rb b/spec/controllers/projects/pages_domains_controller_spec.rb index 33853c4b9d0..920189be381 100644 --- a/spec/controllers/projects/pages_domains_controller_spec.rb +++ b/spec/controllers/projects/pages_domains_controller_spec.rb @@ -46,7 +46,7 @@ describe Projects::PagesDomainsController do post(:create, request_params.merge(pages_domain: pages_domain_params)) end.to change { PagesDomain.count }.by(1) - expect(response).to redirect_to(namespace_project_pages_path(project.namespace, project)) + expect(response).to redirect_to(project_pages_path(project)) end end @@ -56,7 +56,7 @@ describe Projects::PagesDomainsController do delete(:destroy, request_params.merge(id: pages_domain.domain)) end.to change { PagesDomain.count }.by(-1) - expect(response).to redirect_to(namespace_project_pages_path(project.namespace, project)) + expect(response).to redirect_to(project_pages_path(project)) end end diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index f2b59ba82ca..548ec8f487f 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -9,7 +9,7 @@ describe Projects::ProjectMembersController do get :index, namespace_id: project.namespace, project_id: project expect(response).to have_http_status(302) - expect(response.location).to include namespace_project_settings_members_path(project.namespace, project) + expect(response.location).to include project_settings_members_path(project) end end @@ -50,7 +50,7 @@ describe Projects::ProjectMembersController do access_level: Gitlab::Access::GUEST expect(response).to set_flash.to 'Users were successfully added.' - expect(response).to redirect_to(namespace_project_settings_members_path(project.namespace, project)) + expect(response).to redirect_to(project_settings_members_path(project)) end it 'adds no user to members' do @@ -62,7 +62,7 @@ describe Projects::ProjectMembersController do access_level: Gitlab::Access::GUEST expect(response).to set_flash.to 'Message' - expect(response).to redirect_to(namespace_project_settings_members_path(project.namespace, project)) + expect(response).to redirect_to(project_settings_members_path(project)) end end end @@ -111,7 +111,7 @@ describe Projects::ProjectMembersController do id: member expect(response).to redirect_to( - namespace_project_settings_members_path(project.namespace, project) + project_settings_members_path(project) ) expect(project.members).not_to include member end @@ -183,7 +183,7 @@ describe Projects::ProjectMembersController do project_id: project expect(response).to set_flash.to 'Your access request to the project has been withdrawn.' - expect(response).to redirect_to(namespace_project_path(project.namespace, project)) + expect(response).to redirect_to(project_path(project)) expect(project.requesters).to be_empty expect(project.users).not_to include user end @@ -202,7 +202,7 @@ describe Projects::ProjectMembersController do expect(response).to set_flash.to 'Your request for access has been queued for review.' expect(response).to redirect_to( - namespace_project_path(project.namespace, project) + project_path(project) ) expect(project.requesters.exists?(user_id: user)).to be_truthy expect(project.users).not_to include user @@ -253,7 +253,7 @@ describe Projects::ProjectMembersController do id: member expect(response).to redirect_to( - namespace_project_settings_members_path(project.namespace, project) + project_settings_members_path(project) ) expect(project.members).to include member end @@ -290,7 +290,7 @@ describe Projects::ProjectMembersController do expect(project.team_members).to include member expect(response).to set_flash.to 'Successfully imported' expect(response).to redirect_to( - namespace_project_settings_members_path(project.namespace, project) + project_settings_members_path(project) ) end end diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index 4dc227a36d4..5a9d8a75f3e 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -79,7 +79,7 @@ describe Projects::ServicesController do put :update, namespace_id: project.namespace.id, project_id: project.id, id: service.id, service: { active: true } - expect(response).to redirect_to(namespace_project_settings_integrations_path(project.namespace, project)) + expect(response).to redirect_to(project_settings_integrations_path(project)) expect(flash[:notice]).to eq 'HipChat activated.' end end diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb index ec0b7f8c967..cc444f31797 100644 --- a/spec/controllers/projects/snippets_controller_spec.rb +++ b/spec/controllers/projects/snippets_controller_spec.rb @@ -148,7 +148,7 @@ describe Projects::SnippetsController do { spam_log_id: spam_logs.last.id, recaptcha_verification: true }) - expect(response).to redirect_to(Snippet.last) + expect(response).to redirect_to(project_snippet_path(project, Snippet.last)) end end end @@ -228,7 +228,7 @@ describe Projects::SnippetsController do { spam_log_id: spam_logs.last.id, recaptcha_verification: true }) - expect(response).to redirect_to(snippet) + expect(response).to redirect_to(project_snippet_path(project, snippet)) end end end @@ -273,7 +273,7 @@ describe Projects::SnippetsController do { spam_log_id: spam_logs.last.id, recaptcha_verification: true }) - expect(response).to redirect_to(snippet) + expect(response).to redirect_to(project_snippet_path(project, snippet)) end end end diff --git a/spec/controllers/projects/variables_controller_spec.rb b/spec/controllers/projects/variables_controller_spec.rb index 1ecfe48475c..a0ecc756653 100644 --- a/spec/controllers/projects/variables_controller_spec.rb +++ b/spec/controllers/projects/variables_controller_spec.rb @@ -16,7 +16,7 @@ describe Projects::VariablesController do variable: { key: "one", value: "two" } expect(flash[:notice]).to include 'Variables were successfully updated.' - expect(response).to redirect_to(namespace_project_settings_ci_cd_path(project.namespace, project)) + expect(response).to redirect_to(project_settings_ci_cd_path(project)) end end @@ -44,7 +44,7 @@ describe Projects::VariablesController do id: variable.id, variable: { key: variable.key, value: 'two' } expect(flash[:notice]).to include 'Variable was successfully updated.' - expect(response).to redirect_to(namespace_project_variables_path(project.namespace, project)) + expect(response).to redirect_to(project_variables_path(project)) end it 'renders the action #show if the variable key is invalid' do diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 240a81367d0..f96fe7ad5cb 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -482,7 +482,7 @@ describe ProjectsController do it 'redirects to the canonical path (testing non-show action)' do get :refs, namespace_id: 'foo', id: 'bar' - expect(response).to redirect_to(refs_namespace_project_path(namespace_id: public_project.namespace, id: public_project)) + expect(response).to redirect_to(refs_project_path(public_project)) expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, public_project)) end end diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb index 917bd44c91b..7340a4e16c0 100644 --- a/spec/controllers/sent_notifications_controller_spec.rb +++ b/spec/controllers/sent_notifications_controller_spec.rb @@ -88,7 +88,7 @@ describe SentNotificationsController, type: :controller do it 'redirects to the issue page' do expect(response) - .to redirect_to(namespace_project_issue_path(project.namespace, project, issue)) + .to redirect_to(project_issue_path(project, issue)) end end @@ -114,7 +114,7 @@ describe SentNotificationsController, type: :controller do it 'redirects to the merge request page' do expect(response) - .to redirect_to(namespace_project_merge_request_path(project.namespace, project, merge_request)) + .to redirect_to(project_merge_request_path(project, merge_request)) end end end diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index 430d1208cd1..15416a89017 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -341,7 +341,7 @@ describe SnippetsController do { spam_log_id: spam_logs.last.id, recaptcha_verification: true }) - expect(response).to redirect_to(snippet) + expect(response).to redirect_to(snippet_path(snippet)) end end end diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 0cc498f0ce9..a77f01ecb00 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -207,7 +207,8 @@ FactoryGirl.define do cache: { key: 'cache_key', untracked: false, - paths: ['vendor/*'] + paths: ['vendor/*'], + policy: 'pull-push' } } end diff --git a/spec/factories/ci/runner_projects.rb b/spec/factories/ci/runner_projects.rb index 6712dd5d82e..33a17cf7ed5 100644 --- a/spec/factories/ci/runner_projects.rb +++ b/spec/factories/ci/runner_projects.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :ci_runner_project, class: Ci::RunnerProject do - runner_id 1 - project_id 1 + runner factory: :ci_runner + project factory: :empty_project end end diff --git a/spec/factories/personal_snippets.rb b/spec/factories/personal_snippets.rb deleted file mode 100644 index 0f13b2c1020..00000000000 --- a/spec/factories/personal_snippets.rb +++ /dev/null @@ -1,4 +0,0 @@ -FactoryGirl.define do - factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do - end -end diff --git a/spec/factories/project_snippets.rb b/spec/factories/project_snippets.rb deleted file mode 100644 index e0fe1b36fd3..00000000000 --- a/spec/factories/project_snippets.rb +++ /dev/null @@ -1,5 +0,0 @@ -FactoryGirl.define do - factory :project_snippet, parent: :snippet, class: :ProjectSnippet do - project factory: :empty_project - end -end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index aef1c17a239..1bb2db11e7f 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -220,7 +220,7 @@ FactoryGirl.define do active: true, properties: { 'project_url' => 'http://redmine/projects/project_name_in_redmine', - 'issues_url' => "http://redmine/#{project.id}/project_name_in_redmine/:id", + 'issues_url' => 'http://redmine/projects/project_name_in_redmine/issues/:id', 'new_issue_url' => 'http://redmine/projects/project_name_in_redmine/issues/new' } ) diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb index 388f662e6e5..f6ce99d50f9 100644 --- a/spec/factories/snippets.rb +++ b/spec/factories/snippets.rb @@ -18,4 +18,11 @@ FactoryGirl.define do visibility_level Snippet::PRIVATE end end + + factory :project_snippet, parent: :snippet, class: :ProjectSnippet do + project factory: :empty_project + end + + factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do + end end diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index e38c4263060..a44fa0b86d5 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -16,6 +16,19 @@ feature 'Admin updates settings', feature: true do expect(page).to have_content "Application settings saved successfully" end + scenario 'Uncheck all restricted visibility levels' do + find('#application_setting_visibility_level_0').set(false) + find('#application_setting_visibility_level_10').set(false) + find('#application_setting_visibility_level_20').set(false) + + click_button 'Save' + + expect(page).to have_content "Application settings saved successfully" + expect(find('#application_setting_visibility_level_0')).not_to be_checked + expect(find('#application_setting_visibility_level_10')).not_to be_checked + expect(find('#application_setting_visibility_level_20')).not_to be_checked + end + scenario 'Change application settings' do uncheck 'Gravatar enabled' fill_in 'Home page URL', with: 'https://about.gitlab.com/' diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb index 22a0ebd3531..011fdce21d8 100644 --- a/spec/features/atom/issues_spec.rb +++ b/spec/features/atom/issues_spec.rb @@ -30,7 +30,8 @@ describe 'Issues Feed', feature: true do context 'when authenticated via private token' do it 'renders atom feed' do - visit project_issues_path(project, :atom, private_token: user.private_token) + visit project_issues_path(project, :atom, + private_token: user.private_token) expect(response_headers['Content-Type']) .to have_content('application/atom+xml') @@ -44,7 +45,8 @@ describe 'Issues Feed', feature: true do context 'when authenticated via RSS token' do it 'renders atom feed' do - visit project_issues_path(project, :atom, rss_token: user.rss_token) + visit project_issues_path(project, :atom, + rss_token: user.rss_token) expect(response_headers['Content-Type']) .to have_content('application/atom+xml') @@ -57,7 +59,8 @@ describe 'Issues Feed', feature: true do end it "renders atom feed with url parameters for project issues" do - visit project_issues_path(project, :atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id) + visit project_issues_path(project, + :atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id) link = find('link[type="application/atom+xml"]') params = CGI.parse(URI.parse(link[:href]).query) diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index 6cf2246f67d..fa17ef92bbb 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -79,6 +79,22 @@ describe 'Issue Boards', feature: true, js: true do end end + it 'does not show remove button for backlog or closed issues' do + create(:issue, project: project) + create(:issue, :closed, project: project) + + visit project_board_path(project, board) + wait_for_requests + + click_card(find('.board:nth-child(1)').first('.card')) + + expect(find('.issue-boards-sidebar')).not_to have_button 'Remove from board' + + click_card(find('.board:nth-child(3)').first('.card')) + + expect(find('.issue-boards-sidebar')).not_to have_button 'Remove from board' + end + context 'assignee' do it 'updates the issues assignee' do click_card(card) diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index e264a7c989f..7ca002fc821 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' -RSpec.describe 'Dashboard Projects', feature: true do +feature 'Dashboard Projects' do let(:user) { create(:user) } - let(:project) { create(:project, name: "awesome stuff") } + let(:project) { create(:project, name: 'awesome stuff') } let(:project2) { create(:project, :public, name: 'Community project') } before do @@ -21,6 +21,14 @@ RSpec.describe 'Dashboard Projects', feature: true do expect(page).to have_content('awesome stuff') end + it 'shows "New project" button' do + visit dashboard_projects_path + + page.within '#content-body' do + expect(page).to have_link('New project') + end + end + context 'when last_repository_updated_at, last_activity_at and update_at are present' do it 'shows the last_repository_updated_at attribute as the update date' do project.update_attributes!(last_repository_updated_at: Time.now, last_activity_at: 1.hour.ago) @@ -53,8 +61,8 @@ RSpec.describe 'Dashboard Projects', feature: true do end end - describe "with a pipeline", redis: true do - let!(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha) } + describe 'with a pipeline', redis: true do + let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha) } before do # Since the cache isn't updated when a new pipeline is created diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb index c42f4c0a95c..18c06a48111 100644 --- a/spec/features/expand_collapse_diffs_spec.rb +++ b/spec/features/expand_collapse_diffs_spec.rb @@ -129,7 +129,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do before do large_diff.find('.diff-line-num', match: :prefer_exact).hover - large_diff.find('.add-diff-note').click + large_diff.find('.add-diff-note', match: :prefer_exact).click large_diff.find('.note-textarea').send_keys comment_text large_diff.find_button('Comment').click wait_for_requests diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index 597da41bc95..6f8c8999f98 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -135,7 +135,7 @@ feature 'Group', feature: true do expect(page).not_to have_content('secret-group') end - describe 'group edit' do + describe 'group edit', js: true do let(:group) { create(:group) } let(:path) { edit_group_path(group) } let(:new_name) { 'new-name' } @@ -157,8 +157,8 @@ feature 'Group', feature: true do end it 'removes group' do - click_link 'Remove group' - + expect { remove_with_confirm('Remove group', group.path) }.to change {Group.count}.by(-1) + expect(group.members.all.count).to be_zero expect(page).to have_content "scheduled for deletion" end end @@ -212,4 +212,10 @@ feature 'Group', feature: true do expect(page).to have_content(nested_group.name) end end + + def remove_with_confirm(button_text, confirm_with) + click_button button_text + fill_in 'confirm_name_input', with: confirm_with + click_button 'Confirm' + end end diff --git a/spec/features/issuables/user_sees_sidebar_spec.rb b/spec/features/issuables/user_sees_sidebar_spec.rb new file mode 100644 index 00000000000..948d151a517 --- /dev/null +++ b/spec/features/issuables/user_sees_sidebar_spec.rb @@ -0,0 +1,30 @@ +require 'rails_helper' + +describe 'Issue Sidebar on Mobile' do + include MobileHelpers + + let(:project) { create(:project, :public) } + let(:merge_request) { create(:merge_request, source_project: project) } + let(:issue) { create(:issue, project: project) } + let!(:user) { create(:user)} + + before do + sign_in(user) + end + + context 'mobile sidebar on merge requests', js: true do + before do + visit project_merge_request_path(merge_request.project, merge_request) + end + + it_behaves_like "issue sidebar stays collapsed on mobile" + end + + context 'mobile sidebar on issues', js: true do + before do + visit project_issue_path(project, issue) + end + + it_behaves_like "issue sidebar stays collapsed on mobile" + end +end diff --git a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb index 41854ebdd21..5c291f7b817 100644 --- a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb +++ b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb @@ -68,7 +68,7 @@ feature 'Resolve an open discussion in a merge request by creating an issue', fe project.team << [user, :reporter] sign_in user visit new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid, - discussion_to_resolve: discussion.id) + discussion_to_resolve: discussion.id) end it 'Shows a notice to ask someone else to resolve the discussions' do diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index 329071dcbc8..9fc6391fa98 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -459,7 +459,7 @@ describe 'Filter issues', js: true, feature: true do context 'issue label clicked' do before do - find('.issues-list .issue .issue-info a .label', text: multiple_words_label.title).click + find('.issues-list .issue .issue-main-info .issuable-info a .label', text: multiple_words_label.title).click end it 'filters' do diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb index f909ef97d5a..60e6e431c9e 100644 --- a/spec/features/issues/form_spec.rb +++ b/spec/features/issues/form_spec.rb @@ -1,7 +1,6 @@ require 'rails_helper' describe 'New/edit issue', :feature, :js do - include GitlabRoutingHelper include ActionView::Helpers::JavaScriptHelper include FormHelper @@ -31,8 +30,8 @@ describe 'New/edit issue', :feature, :js do # the original method, resulting in infinite recurison when called. # This is likely a bug with helper modules included into dynamically generated view classes. # To work around this, we have to hold on to and call to the original implementation manually. - original_issue_dropdown_options = FormHelper.instance_method(:issue_dropdown_options) - allow_any_instance_of(FormHelper).to receive(:issue_dropdown_options).and_wrap_original do |original, *args| + original_issue_dropdown_options = FormHelper.instance_method(:issue_assignees_dropdown_options) + allow_any_instance_of(FormHelper).to receive(:issue_assignees_dropdown_options).and_wrap_original do |original, *args| options = original_issue_dropdown_options.bind(original.receiver).call(*args) options[:data][:per_page] = 2 diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb index e350bb9dbb6..f75d2c72672 100644 --- a/spec/features/issues/issue_sidebar_spec.rb +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -154,20 +154,6 @@ feature 'Issue Sidebar', feature: true do end end - context 'as a allowed mobile user', js: true do - before do - project.team << [user, :developer] - resize_screen_xs - visit_issue(project, issue) - end - - context 'mobile sidebar' do - it 'collapses the sidebar for small screens' do - expect(page).not_to have_css('aside.right-sidebar.right-sidebar-collapsed') - end - end - end - context 'as a guest' do before do project.team << [user, :guest] diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index 45858e9eead..833eb47efb2 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -41,13 +41,10 @@ feature 'issue move to another project' do find('#issuable-move', visible: false).set(new_project.id) click_button('Save changes') - wait_for_requests - - expect(current_url).to include project_path(new_project) - expect(page).to have_content("Text with #{cross_reference}#{mr.to_reference}") expect(page).to have_content("moved from #{cross_reference}#{issue.to_reference}") expect(page).to have_content(issue.title) + expect(page.current_path).to include project_path(new_project) end scenario 'searching project dropdown', js: true do @@ -100,8 +97,4 @@ feature 'issue move to another project' do def issue_path(issue) project_issue_path(issue.project, issue) end - - def project_path(project) - project_path(new_project) - end end diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb index 62987457e75..5a7c4f54cb6 100644 --- a/spec/features/issues/update_issues_spec.rb +++ b/spec/features/issues/update_issues_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'Multiple issue updating from issues#index', feature: true do +feature 'Multiple issue updating from issues#index', :js do let!(:project) { create(:project) } let!(:issue) { create(:issue, project: project) } let!(:user) { create(:user)} @@ -10,7 +10,7 @@ feature 'Multiple issue updating from issues#index', feature: true do sign_in(user) end - context 'status', js: true do + context 'status' do it 'sets to closed' do visit project_issues_path(project) @@ -37,7 +37,7 @@ feature 'Multiple issue updating from issues#index', feature: true do end end - context 'assignee', js: true do + context 'assignee' do it 'updates to current user' do visit project_issues_path(project) @@ -67,8 +67,8 @@ feature 'Multiple issue updating from issues#index', feature: true do end end - context 'milestone', js: true do - let(:milestone) { create(:milestone, project: project) } + context 'milestone' do + let!(:milestone) { create(:milestone, project: project) } it 'updates milestone' do visit project_issues_path(project) diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 4955e115b1c..0016fa10f67 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -355,7 +355,8 @@ describe 'Issues', feature: true do end it 'sorts with a filter applied' do - visit project_issues_path(project, sort: sort_value_oldest_created, + visit project_issues_path(project, + sort: sort_value_oldest_created, assignee_id: user2.id) expect(first_issue).to include('bar') diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index 235d8588e1e..e0d97dec586 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -138,7 +138,9 @@ feature 'Create New Merge Request', feature: true, js: true do end it 'shows pipelines for a new merge request' do - visit project_new_merge_request_path(project, merge_request: { target_branch: 'master', source_branch: 'fix' }) + visit project_new_merge_request_path( + project, + merge_request: { target_branch: 'master', source_branch: 'fix' }) page.within('.merge-request') do click_link 'Pipelines' diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb index 14148633451..171386e16ad 100644 --- a/spec/features/merge_requests/form_spec.rb +++ b/spec/features/merge_requests/form_spec.rb @@ -1,8 +1,6 @@ require 'rails_helper' describe 'New/edit merge request', feature: true, js: true do - include GitlabRoutingHelper - let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } let(:fork_project) { create(:project, forked_from_project: project) } let!(:user) { create(:user)} @@ -23,7 +21,9 @@ describe 'New/edit merge request', feature: true, js: true do context 'new merge request' do before do - visit project_new_merge_request_path(project, merge_request: { + visit project_new_merge_request_path( + project, + merge_request: { source_project_id: project.id, target_project_id: project.id, source_branch: 'fix', @@ -179,7 +179,9 @@ describe 'New/edit merge request', feature: true, js: true do context 'new merge request' do before do - visit project_new_merge_request_path(fork_project, merge_request: { + visit project_new_merge_request_path( + fork_project, + merge_request: { source_project_id: fork_project.id, target_project_id: project.id, source_branch: 'fix', diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb index c50cfa78218..f541f495995 100644 --- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb +++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb @@ -136,7 +136,8 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true end it 'sorts by recently due milestone' do - visit project_merge_requests_path(project, label_name: [label.name, label2.name], + visit project_merge_requests_path(project, + label_name: [label.name, label2.name], assignee_id: user.id, sort: sort_value_milestone_soon) diff --git a/spec/features/merge_requests/versions_spec.rb b/spec/features/merge_requests/versions_spec.rb index f6f2c46758f..218d57b49e3 100644 --- a/spec/features/merge_requests/versions_spec.rb +++ b/spec/features/merge_requests/versions_spec.rb @@ -96,8 +96,12 @@ feature 'Merge Request versions', js: true, feature: true do end it 'has a path with comparison context' do - expect(page).to have_current_path diffs_project_merge_request_path(project, merge_request.iid, diff_id: merge_request_diff3.id, - start_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') + expect(page).to have_current_path diffs_project_merge_request_path( + project, + merge_request.iid, + diff_id: merge_request_diff3.id, + start_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9' + ) end it 'should have correct value in the compare dropdown' do diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb index 3dcb0ea64de..46c558659c7 100644 --- a/spec/features/merge_requests/widget_spec.rb +++ b/spec/features/merge_requests/widget_spec.rb @@ -12,7 +12,9 @@ describe 'Merge request', :feature, :js do context 'new merge request' do before do - visit project_new_merge_request_path(project, merge_request: { + visit project_new_merge_request_path( + project, + merge_request: { source_project_id: project.id, target_project_id: project.id, source_branch: 'feature', diff --git a/spec/features/merge_requests/wip_message_spec.rb b/spec/features/merge_requests/wip_message_spec.rb index f555306d6a3..91cf8fc7218 100644 --- a/spec/features/merge_requests/wip_message_spec.rb +++ b/spec/features/merge_requests/wip_message_spec.rb @@ -11,7 +11,9 @@ feature 'Work In Progress help message', feature: true do context 'with WIP commits' do it 'shows a specific WIP hint' do - visit project_new_merge_request_path(project, merge_request: { + visit project_new_merge_request_path( + project, + merge_request: { source_project_id: project.id, target_project_id: project.id, source_branch: 'wip', @@ -28,7 +30,9 @@ feature 'Work In Progress help message', feature: true do context 'without WIP commits' do it 'shows the regular WIP message' do - visit project_new_merge_request_path(project, merge_request: { + visit project_new_merge_request_path( + project, + merge_request: { source_project_id: project.id, target_project_id: project.id, source_branch: 'fix', diff --git a/spec/features/oauth_login_spec.rb b/spec/features/oauth_login_spec.rb new file mode 100644 index 00000000000..1b6d1f3415f --- /dev/null +++ b/spec/features/oauth_login_spec.rb @@ -0,0 +1,112 @@ +require 'spec_helper' + +feature 'OAuth Login', js: true do + def enter_code(code) + fill_in 'user_otp_attempt', with: code + click_button 'Verify code' + end + + def stub_omniauth_config(provider) + OmniAuth.config.add_mock(provider, OmniAuth::AuthHash.new(provider: provider.to_s, uid: "12345")) + Rails.application.env_config['devise.mapping'] = Devise.mappings[:user] + Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider] + end + + providers = [:github, :twitter, :bitbucket, :gitlab, :google_oauth2, + :facebook, :cas3, :auth0] + + before(:all) do + # The OmniAuth `full_host` parameter doesn't get set correctly (it gets set to something like `http://localhost` + # here), and causes integration tests to fail with 404s. We set the `full_host` by removing the request path (and + # anything after it) from the request URI. + @omniauth_config_full_host = OmniAuth.config.full_host + OmniAuth.config.full_host = ->(request) { request['REQUEST_URI'].sub(/#{request['REQUEST_PATH']}.*/, '') } + end + + after(:all) do + OmniAuth.config.full_host = @omniauth_config_full_host + end + + providers.each do |provider| + context "when the user logs in using the #{provider} provider" do + context 'when two-factor authentication is disabled' do + it 'logs the user in' do + stub_omniauth_config(provider) + user = create(:omniauth_user, extern_uid: 'my-uid', provider: provider.to_s) + login_via(provider.to_s, user, 'my-uid') + + expect(current_path).to eq root_path + end + end + + context 'when two-factor authentication is enabled' do + it 'logs the user in' do + stub_omniauth_config(provider) + user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: provider.to_s) + login_via(provider.to_s, user, 'my-uid') + + enter_code(user.current_otp) + expect(current_path).to eq root_path + end + end + + context 'when "remember me" is checked' do + context 'when two-factor authentication is disabled' do + it 'remembers the user after a browser restart' do + stub_omniauth_config(provider) + user = create(:omniauth_user, extern_uid: 'my-uid', provider: provider.to_s) + login_via(provider.to_s, user, 'my-uid', remember_me: true) + + clear_browser_session + + visit(root_path) + expect(current_path).to eq root_path + end + end + + context 'when two-factor authentication is enabled' do + it 'remembers the user after a browser restart' do + stub_omniauth_config(provider) + user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: provider.to_s) + login_via(provider.to_s, user, 'my-uid', remember_me: true) + enter_code(user.current_otp) + + clear_browser_session + + visit(root_path) + expect(current_path).to eq root_path + end + end + end + + context 'when "remember me" is not checked' do + context 'when two-factor authentication is disabled' do + it 'does not remember the user after a browser restart' do + stub_omniauth_config(provider) + user = create(:omniauth_user, extern_uid: 'my-uid', provider: provider.to_s) + login_via(provider.to_s, user, 'my-uid', remember_me: false) + + clear_browser_session + + visit(root_path) + expect(current_path).to eq new_user_session_path + end + end + + context 'when two-factor authentication is enabled' do + it 'does not remember the user after a browser restart' do + stub_omniauth_config(provider) + user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: provider.to_s) + login_via(provider.to_s, user, 'my-uid', remember_me: false) + enter_code(user.current_otp) + + clear_browser_session + + visit(root_path) + expect(current_path).to eq new_user_session_path + end + end + end + end + end +end diff --git a/spec/features/projects/files/creating_a_file_spec.rb b/spec/features/projects/files/creating_a_file_spec.rb index 1367077c4cf..55350db4c34 100644 --- a/spec/features/projects/files/creating_a_file_spec.rb +++ b/spec/features/projects/files/creating_a_file_spec.rb @@ -30,11 +30,6 @@ feature 'User wants to create a file', feature: true do expect(page).to have_content 'The file has been successfully created' end - scenario 'file name contains invalid characters' do - submit_new_file(file_name: '\\') - expect(page).to have_content 'Path can contain only' - end - scenario 'file name contains directory traversal' do submit_new_file(file_name: '../README.md') expect(page).to have_content 'Path cannot include directory traversal' diff --git a/spec/features/projects/files/editing_a_file_spec.rb b/spec/features/projects/files/editing_a_file_spec.rb index f93647b2d7e..c295380dfc9 100644 --- a/spec/features/projects/files/editing_a_file_spec.rb +++ b/spec/features/projects/files/editing_a_file_spec.rb @@ -18,7 +18,8 @@ feature 'User wants to edit a file', feature: true do background do project.team << [user, :master] sign_in user - visit project_edit_blob_path(project, File.join(project.default_branch, '.gitignore')) + visit project_edit_blob_path(project, + File.join(project.default_branch, '.gitignore')) end scenario 'file has been updated since the user opened the edit page' do diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb index abee3194066..533ff4612ff 100644 --- a/spec/features/projects/import_export/import_file_spec.rb +++ b/spec/features/projects/import_export/import_file_spec.rb @@ -97,6 +97,6 @@ feature 'Import/Export - project import integration test', feature: true, js: tr end def project_hook_exists?(project) - Gitlab::Git::Hook.new('post-receive', project.repository.path).exists? + Gitlab::Git::Hook.new('post-receive', project).exists? end end diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb index dfda7560486..88bb678362b 100644 --- a/spec/features/projects/issuable_templates_spec.rb +++ b/spec/features/projects/issuable_templates_spec.rb @@ -28,7 +28,7 @@ feature 'issuable templates', feature: true, js: true do longtemplate_content, message: 'added issue template', branch_name: 'master') - visit edit_project_issue_path(project, issue) + visit edit_project_issue_path project, issue fill_in :'issue[title]', with: 'test issue title' end @@ -81,7 +81,7 @@ feature 'issuable templates', feature: true, js: true do template_content, message: 'added issue template', branch_name: 'master') - visit edit_project_issue_path(project, issue) + visit edit_project_issue_path project, issue fill_in :'issue[title]', with: 'test issue title' fill_in :'issue[description]', with: prior_description end @@ -105,7 +105,7 @@ feature 'issuable templates', feature: true, js: true do template_content, message: 'added merge request template', branch_name: 'master') - visit edit_project_merge_request_path(project, merge_request) + visit edit_project_merge_request_path project, merge_request fill_in :'merge_request[title]', with: 'test merge request title' end @@ -138,8 +138,7 @@ feature 'issuable templates', feature: true, js: true do template_content, message: 'added merge request template', branch_name: 'master') - - visit edit_project_merge_request_path(project, merge_request) + visit edit_project_merge_request_path project, merge_request fill_in :'merge_request[title]', with: 'test merge request title' end diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index ad8b6220a13..411987573fa 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -5,7 +5,6 @@ feature 'Jobs', :feature do let(:user) { create(:user) } let(:user_access_level) { :developer } let(:project) { create(:project) } - let(:namespace) { project.namespace } let(:pipeline) { create(:ci_pipeline, project: project) } let(:job) { create(:ci_build, :trace, pipeline: pipeline) } @@ -157,7 +156,7 @@ feature 'Jobs', :feature do let(:job) { create(:ci_build, :failed, pipeline: pipeline) } before do - visit namespace_project_job_path(namespace, project, job) + visit project_job_path(project, job) end it 'shows New issue button' do @@ -166,10 +165,10 @@ feature 'Jobs', :feature do it 'links to issues/new with the title and description filled in' do button_title = "Build Failed ##{job.id}" - job_path = namespace_project_job_path(namespace, project, job) + job_path = project_job_path(project, job) options = { issue: { title: button_title, description: job_path } } - href = new_namespace_project_issue_path(namespace, project, options) + href = new_project_issue_path(project, options) page.within('.header-action-buttons') do expect(find('.js-new-issue')['href']).to include(href) @@ -467,7 +466,7 @@ feature 'Jobs', :feature do .to receive(:paths) .and_return([existing_file]) - visit namespace_project_job_path(namespace, project, job) + visit project_job_path(project, job) find('.js-raw-link-controller').click end @@ -485,7 +484,7 @@ feature 'Jobs', :feature do .to receive(:paths) .and_return([]) - visit namespace_project_job_path(namespace, project, job) + visit project_job_path(project, job) end it 'sends the right headers' do diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb index d832437cc32..afb613f034e 100644 --- a/spec/features/projects/members/sorting_spec.rb +++ b/spec/features/projects/members/sorting_spec.rb @@ -84,7 +84,7 @@ feature 'Projects > Members > Sorting', feature: true do end def visit_members_list(sort:) - visit namespace_project_project_members_path(project.namespace.to_param, project, sort: sort) + visit project_project_members_path(project, sort: sort) end def first_member diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb index 8bf6dcdb3b9..12b4747602d 100644 --- a/spec/features/projects/merge_request_button_spec.rb +++ b/spec/features/projects/merge_request_button_spec.rb @@ -23,8 +23,9 @@ feature 'Merge Request button', feature: true do end it 'shows Create merge request button' do - href = project_new_merge_request_path(project, merge_request: { source_branch: 'feature', - target_branch: 'master' }) + href = project_new_merge_request_path(project, + merge_request: { source_branch: 'feature', + target_branch: 'master' }) visit url @@ -65,8 +66,9 @@ feature 'Merge Request button', feature: true do let(:user) { forked_project.owner } it 'shows Create merge request button' do - href = project_new_merge_request_path(forked_project, merge_request: { source_branch: 'feature', - target_branch: 'master' }) + href = project_new_merge_request_path(forked_project, + merge_request: { source_branch: 'feature', + target_branch: 'master' }) visit fork_url diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index c1ad1551e1f..22fb1223739 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -1,13 +1,27 @@ -require "spec_helper" +require 'spec_helper' -feature "New project", feature: true do +feature 'New project' do let(:user) { create(:admin) } before do sign_in(user) end - context "Visibility level selector" do + it 'shows "New project" page' do + visit new_project_path + + expect(page).to have_content('Project path') + expect(page).to have_content('Project name') + + expect(page).to have_link('GitHub') + expect(page).to have_link('Bitbucket') + expect(page).to have_link('GitLab.com') + expect(page).to have_link('Google Code') + expect(page).to have_button('Repo by URL') + expect(page).to have_link('GitLab export') + end + + context 'Visibility level selector' do Gitlab::VisibilityLevel.options.each do |key, level| it "sets selector to #{key}" do stub_application_setting(default_project_visibility: level) @@ -28,20 +42,20 @@ feature "New project", feature: true do end end - context "Namespace selector" do - context "with user namespace" do + context 'Namespace selector' do + context 'with user namespace' do before do visit new_project_path end - it "selects the user namespace" do - namespace = find("#project_namespace_id") + it 'selects the user namespace' do + namespace = find('#project_namespace_id') expect(namespace.text).to eq user.username end end - context "with group namespace" do + context 'with group namespace' do let(:group) { create(:group, :private, owner: user) } before do @@ -49,13 +63,13 @@ feature "New project", feature: true do visit new_project_path(namespace_id: group.id) end - it "selects the group namespace" do - namespace = find("#project_namespace_id option[selected]") + it 'selects the group namespace' do + namespace = find('#project_namespace_id option[selected]') expect(namespace.text).to eq group.name end - context "on validation error" do + context 'on validation error' do before do fill_in('project_path', with: 'private-group-project') choose('Internal') @@ -64,15 +78,15 @@ feature "New project", feature: true do expect(page).to have_css '.project-edit-errors .alert.alert-danger' end - it "selects the group namespace" do - namespace = find("#project_namespace_id option[selected]") + it 'selects the group namespace' do + namespace = find('#project_namespace_id option[selected]') expect(namespace.text).to eq group.name end end end - context "with subgroup namespace" do + context 'with subgroup namespace' do let(:group) { create(:group, :private, owner: user) } let(:subgroup) { create(:group, parent: group) } @@ -81,8 +95,8 @@ feature "New project", feature: true do visit new_project_path(namespace_id: subgroup.id) end - it "selects the group namespace" do - namespace = find("#project_namespace_id option[selected]") + it 'selects the group namespace' do + namespace = find('#project_namespace_id option[selected]') expect(namespace.text).to eq subgroup.full_path end @@ -94,10 +108,45 @@ feature "New project", feature: true do visit new_project_path end - it 'does not autocomplete sensitive git repo URL' do - autocomplete = find('#project_import_url')['autocomplete'] + context 'from git repository url' do + before do + first('.import_git').click + end + + it 'does not autocomplete sensitive git repo URL' do + autocomplete = find('#project_import_url')['autocomplete'] + + expect(autocomplete).to eq('off') + end + + it 'shows import instructions' do + git_import_instructions = first('.js-toggle-content') - expect(autocomplete).to eq('off') + expect(git_import_instructions).to be_visible + expect(git_import_instructions).to have_content 'Git repository URL' + end + end + + context 'from GitHub' do + before do + first('.import_github').click + end + + it 'shows import instructions' do + expect(page).to have_content('Import Projects from GitHub') + expect(current_path).to eq new_import_github_path + end + end + + context 'from Google Code' do + before do + first('.import_google_code').click + end + + it 'shows import instructions' do + expect(page).to have_content('Import projects from Google Code') + expect(current_path).to eq new_import_google_code_path + end end end end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 21d4c3d49f4..4a08d9088aa 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe 'Pipeline', :feature, :js do - include GitlabRoutingHelper - let(:project) { create(:empty_project) } let(:user) { create(:user) } diff --git a/spec/features/projects/settings/merge_requests_settings_spec.rb b/spec/features/projects/settings/merge_requests_settings_spec.rb index 2689bcedd74..ecaf65c4ad9 100644 --- a/spec/features/projects/settings/merge_requests_settings_spec.rb +++ b/spec/features/projects/settings/merge_requests_settings_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' feature 'Project settings > Merge Requests', feature: true, js: true do - include GitlabRoutingHelper - let(:project) { create(:empty_project, :public) } let(:user) { create(:user) } diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb index 7fcab9d4d19..724cfa10e72 100644 --- a/spec/features/projects/settings/pipelines_settings_spec.rb +++ b/spec/features/projects/settings/pipelines_settings_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' feature "Pipelines settings", feature: true do - include GitlabRoutingHelper - let(:project) { create(:empty_project) } let(:user) { create(:user) } let(:role) { :developer } diff --git a/spec/features/projects/sub_group_issuables_spec.rb b/spec/features/projects/sub_group_issuables_spec.rb index be087995a83..007910bb931 100644 --- a/spec/features/projects/sub_group_issuables_spec.rb +++ b/spec/features/projects/sub_group_issuables_spec.rb @@ -12,13 +12,13 @@ describe 'Subgroup Issuables', :feature, :js, :nested_groups do end it 'shows the full subgroup title when issues index page is empty' do - visit namespace_project_issues_path(project.namespace.to_param, project.to_param) + visit project_issues_path(project) expect_to_have_full_subgroup_title end it 'shows the full subgroup title when merge requests index page is empty' do - visit namespace_project_merge_requests_path(project.namespace.to_param, project.to_param) + visit project_merge_requests_path(project) expect_to_have_full_subgroup_title end 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 fc25abcb7df..9d66f482c8d 100644 --- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do +feature 'Projects > Wiki > User creates wiki page', :js do let(:user) { create(:user) } background do @@ -8,13 +8,16 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do sign_in(user) visit project_path(project) - find('.shortcuts-wiki').trigger('click') end context 'in the user namespace' do let(:project) { create(:project, namespace: user.namespace) } context 'when wiki is empty' do + before do + find('.shortcuts-wiki').trigger('click') + end + scenario 'commit message field has value "Create home"' do expect(page).to have_field('wiki[message]', with: 'Create home') end @@ -67,10 +70,11 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do context 'when wiki is not empty' do before do WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute + find('.shortcuts-wiki').trigger('click') end context 'via the "new wiki page" page' do - scenario 'when the wiki page has a single word name', js: true do + scenario 'when the wiki page has a single word name' do click_link 'New page' page.within '#modal-new-wiki' do @@ -91,7 +95,7 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do expect(page).to have_content('My awesome wiki!') end - scenario 'when the wiki page has spaces in the name', js: true do + scenario 'when the wiki page has spaces in the name' do click_link 'New page' page.within '#modal-new-wiki' do @@ -112,7 +116,7 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do expect(page).to have_content('My awesome wiki!') end - scenario 'when the wiki page has hyphens in the name', js: true do + scenario 'when the wiki page has hyphens in the name' do click_link 'New page' page.within '#modal-new-wiki' do @@ -134,7 +138,7 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do end end - scenario 'content has autocomplete', :js do + scenario 'content has autocomplete' do click_link 'New page' page.within '#modal-new-wiki' do @@ -156,6 +160,10 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do let(:project) { create(:project, namespace: create(:group, :public)) } context 'when wiki is empty' do + before do + find('.shortcuts-wiki').trigger('click') + end + scenario 'commit message field has value "Create home"' do expect(page).to have_field('wiki[message]', with: 'Create home') end @@ -175,9 +183,10 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do context 'when wiki is not empty' do before do WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute + find('.shortcuts-wiki').trigger('click') end - scenario 'via the "new wiki page" page', js: true do + scenario 'via the "new wiki page" page' do click_link 'New page' page.within '#modal-new-wiki' do diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index bab77796dfb..1725b70acf3 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe "Runners" do - include GitlabRoutingHelper - let(:user) { create(:user) } before do diff --git a/spec/features/snippets/create_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb index dd0dd5934c2..57dec14b480 100644 --- a/spec/features/snippets/create_snippet_spec.rb +++ b/spec/features/snippets/user_creates_snippet_spec.rb @@ -1,10 +1,12 @@ require 'rails_helper' -feature 'Create Snippet', :js, feature: true do +feature 'User creates snippet', :js, feature: true do include DropzoneHelper + let(:user) { create(:user) } + before do - sign_in(create(:user)) + sign_in(user) visit new_snippet_path end diff --git a/spec/features/snippets/user_deletes_snippet_spec.rb b/spec/features/snippets/user_deletes_snippet_spec.rb new file mode 100644 index 00000000000..162c2c9e730 --- /dev/null +++ b/spec/features/snippets/user_deletes_snippet_spec.rb @@ -0,0 +1,19 @@ +require 'rails_helper' + +feature 'User deletes snippet', feature: true do + let(:user) { create(:user) } + let(:content) { 'puts "test"' } + let(:snippet) { create(:personal_snippet, :public, content: content, author: user) } + + before do + sign_in(user) + + visit snippet_path(snippet) + end + + it 'deletes the snippet' do + first(:link, 'Delete').click + + expect(page).not_to have_content(snippet.title) + end +end diff --git a/spec/features/snippets/edit_snippet_spec.rb b/spec/features/snippets/user_edits_snippet_spec.rb index b4b94b8be5e..cff64423873 100644 --- a/spec/features/snippets/edit_snippet_spec.rb +++ b/spec/features/snippets/user_edits_snippet_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'Edit Snippet', :js, feature: true do +feature 'User edits snippet', :js, feature: true do include DropzoneHelper let(:file_name) { 'test.rb' } @@ -27,7 +27,7 @@ feature 'Edit Snippet', :js, feature: true do it 'updates the snippet with files attached' do dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') - expect(page.find_field("personal_snippet_description").value).to have_content('banana_sample') + expect(page.find_field('personal_snippet_description').value).to have_content('banana_sample') click_button('Save changes') wait_for_requests @@ -35,4 +35,24 @@ feature 'Edit Snippet', :js, feature: true do link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] expect(link).to match(%r{/uploads/personal_snippet/#{snippet.id}/\h{32}/banana_sample\.gif\z}) end + + it 'updates the snippet to make it internal' do + choose 'Internal' + + click_button 'Save changes' + wait_for_requests + + expect(page).to have_no_xpath("//i[@class='fa fa-lock']") + expect(page).to have_xpath("//i[@class='fa fa-shield']") + end + + it 'updates the snippet to make it public' do + choose 'Public' + + click_button 'Save changes' + wait_for_requests + + expect(page).to have_no_xpath("//i[@class='fa fa-lock']") + expect(page).to have_xpath("//i[@class='fa fa-globe']") + end end diff --git a/spec/features/user_can_display_performance_bar_spec.rb b/spec/features/user_can_display_performance_bar_spec.rb index 4182882bb45..8464bf50cac 100644 --- a/spec/features/user_can_display_performance_bar_spec.rb +++ b/spec/features/user_can_display_performance_bar_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe 'User can display performacne bar', :js do +describe 'User can display performance bar', :js do shared_examples 'performance bar is disabled' do it 'does not show the performance bar by default' do expect(page).not_to have_css('#peek') @@ -27,8 +27,8 @@ describe 'User can display performacne bar', :js do find('body').native.send_keys('pb') end - it 'does not show the performance bar by default' do - expect(page).not_to have_css('#peek') + it 'shows the performance bar' do + expect(page).to have_css('#peek') end end end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 8ace1fb5751..4a52f0d5c58 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -295,22 +295,121 @@ describe IssuesFinder do end end - describe '.not_restricted_by_confidentiality' do - let(:authorized_user) { create(:user) } - let(:project) { create(:empty_project, namespace: authorized_user.namespace) } - let!(:public_issue) { create(:issue, project: project) } - let!(:confidential_issue) { create(:issue, project: project, confidential: true) } - - it 'returns non confidential issues for nil user' do - expect(described_class.send(:not_restricted_by_confidentiality, nil)).to include(public_issue) - end + describe '#with_confidentiality_access_check' do + let(:guest) { create(:user) } + set(:authorized_user) { create(:user) } + set(:project) { create(:empty_project, namespace: authorized_user.namespace) } + set(:public_issue) { create(:issue, project: project) } + set(:confidential_issue) { create(:issue, project: project, confidential: true) } + + context 'when no project filter is given' do + let(:params) { {} } + + context 'for an anonymous user' do + subject { described_class.new(nil, params).with_confidentiality_access_check } + + it 'returns only public issues' do + expect(subject).to include(public_issue) + expect(subject).not_to include(confidential_issue) + end + end + + context 'for a user without project membership' do + subject { described_class.new(user, params).with_confidentiality_access_check } + + it 'returns only public issues' do + expect(subject).to include(public_issue) + expect(subject).not_to include(confidential_issue) + end + end + + context 'for a guest user' do + subject { described_class.new(guest, params).with_confidentiality_access_check } + + before do + project.add_guest(guest) + end + + it 'returns only public issues' do + expect(subject).to include(public_issue) + expect(subject).not_to include(confidential_issue) + end + end + + context 'for a project member with access to view confidential issues' do + subject { described_class.new(authorized_user, params).with_confidentiality_access_check } - it 'returns non confidential issues for user not authorized for the issues projects' do - expect(described_class.send(:not_restricted_by_confidentiality, user)).to include(public_issue) + it 'returns all issues' do + expect(subject).to include(public_issue, confidential_issue) + end + end end - it 'returns all issues for user authorized for the issues projects' do - expect(described_class.send(:not_restricted_by_confidentiality, authorized_user)).to include(public_issue, confidential_issue) + context 'when searching within a specific project' do + let(:params) { { project_id: project.id } } + + context 'for an anonymous user' do + subject { described_class.new(nil, params).with_confidentiality_access_check } + + it 'returns only public issues' do + expect(subject).to include(public_issue) + expect(subject).not_to include(confidential_issue) + end + + it 'does not filter by confidentiality' do + expect(Issue).not_to receive(:where).with(a_string_matching('confidential'), anything) + + subject + end + end + + context 'for a user without project membership' do + subject { described_class.new(user, params).with_confidentiality_access_check } + + it 'returns only public issues' do + expect(subject).to include(public_issue) + expect(subject).not_to include(confidential_issue) + end + + it 'filters by confidentiality' do + expect(Issue).to receive(:where).with(a_string_matching('confidential'), anything) + + subject + end + end + + context 'for a guest user' do + subject { described_class.new(guest, params).with_confidentiality_access_check } + + before do + project.add_guest(guest) + end + + it 'returns only public issues' do + expect(subject).to include(public_issue) + expect(subject).not_to include(confidential_issue) + end + + it 'filters by confidentiality' do + expect(Issue).to receive(:where).with(a_string_matching('confidential'), anything) + + subject + end + end + + context 'for a project member with access to view confidential issues' do + subject { described_class.new(authorized_user, params).with_confidentiality_access_check } + + it 'returns all issues' do + expect(subject).to include(public_issue, confidential_issue) + end + + it 'does not filter by confidentiality' do + expect(Issue).not_to receive(:where).with(a_string_matching('confidential'), anything) + + subject + end + end end end end diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb index 1724cdba830..95d96354b77 100644 --- a/spec/finders/labels_finder_spec.rb +++ b/spec/finders/labels_finder_spec.rb @@ -49,12 +49,12 @@ describe LabelsFinder do end context 'filtering by group_id' do - it 'returns labels available for any project within the group' do + it 'returns labels available for any non-archived project within the group' do group_1.add_developer(user) - + project_1.archive! finder = described_class.new(user, group_id: group_1.id) - expect(finder.execute).to eq [group_label_2, project_label_1, group_label_1, project_label_5] + expect(finder.execute).to eq [group_label_2, group_label_1, project_label_5] end end diff --git a/spec/fixtures/config/kubeconfig-without-ca.yml b/spec/fixtures/config/kubeconfig-without-ca.yml new file mode 100644 index 00000000000..b2cb989d548 --- /dev/null +++ b/spec/fixtures/config/kubeconfig-without-ca.yml @@ -0,0 +1,18 @@ +--- +apiVersion: v1 +clusters: +- name: gitlab-deploy + cluster: + server: https://kube.domain.com +contexts: +- name: gitlab-deploy + context: + cluster: gitlab-deploy + namespace: NAMESPACE + user: gitlab-deploy +current-context: gitlab-deploy +kind: Config +users: +- name: gitlab-deploy + user: + token: TOKEN diff --git a/spec/fixtures/config/kubeconfig.yml b/spec/fixtures/config/kubeconfig.yml new file mode 100644 index 00000000000..c4e8e573c32 --- /dev/null +++ b/spec/fixtures/config/kubeconfig.yml @@ -0,0 +1,19 @@ +--- +apiVersion: v1 +clusters: +- name: gitlab-deploy + cluster: + server: https://kube.domain.com + certificate-authority-data: "UEVN\n" +contexts: +- name: gitlab-deploy + context: + cluster: gitlab-deploy + namespace: NAMESPACE + user: gitlab-deploy +current-context: gitlab-deploy +kind: Config +users: +- name: gitlab-deploy + user: + token: TOKEN diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb index 51a3e91d201..58b43805705 100644 --- a/spec/fixtures/markdown.md.erb +++ b/spec/fixtures/markdown.md.erb @@ -166,9 +166,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Issue in another project: <%= xissue.to_reference(project) %> - Ignored in code: `<%= issue.to_reference %>` - Ignored in links: [Link to <%= issue.to_reference %>](#issue-link) -- Issue by URL: <%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %> +- Issue by URL: <%= urls.project_issue_url(issue.project, issue) %> - Link to issue by reference: [Issue](<%= issue.to_reference %>) -- Link to issue by URL: [Issue](<%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %>) +- Link to issue by URL: [Issue](<%= urls.project_issue_url(issue.project, issue) %>) #### MergeRequestReferenceFilter @@ -176,9 +176,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Merge request in another project: <%= xmerge_request.to_reference(project) %> - Ignored in code: `<%= merge_request.to_reference %>` - Ignored in links: [Link to <%= merge_request.to_reference %>](#merge-request-link) -- Merge request by URL: <%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %> +- Merge request by URL: <%= urls.project_merge_request_url(merge_request.project, merge_request) %> - Link to merge request by reference: [Merge request](<%= merge_request.to_reference %>) -- Link to merge request by URL: [Merge request](<%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %>) +- Link to merge request by URL: [Merge request](<%= urls.project_merge_request_url(merge_request.project, merge_request) %>) #### SnippetReferenceFilter @@ -186,9 +186,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Snippet in another project: <%= xsnippet.to_reference(project) %> - Ignored in code: `<%= snippet.to_reference %>` - Ignored in links: [Link to <%= snippet.to_reference %>](#snippet-link) -- Snippet by URL: <%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %> +- Snippet by URL: <%= urls.project_snippet_url(snippet.project, snippet) %> - Link to snippet by reference: [Snippet](<%= snippet.to_reference %>) -- Link to snippet by URL: [Snippet](<%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %>) +- Link to snippet by URL: [Snippet](<%= urls.project_snippet_url(snippet.project, snippet) %>) #### CommitRangeReferenceFilter @@ -196,9 +196,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Range in another project: <%= xcommit_range.to_reference(project) %> - Ignored in code: `<%= commit_range.to_reference %>` - Ignored in links: [Link to <%= commit_range.to_reference %>](#commit-range-link) -- Range by URL: <%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %> +- Range by URL: <%= urls.project_compare_url(commit_range.project, commit_range.to_param) %> - Link to range by reference: [Range](<%= commit_range.to_reference %>) -- Link to range by URL: [Range](<%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %>) +- Link to range by URL: [Range](<%= urls.project_compare_url(commit_range.project, commit_range.to_param) %>) #### CommitReferenceFilter @@ -206,9 +206,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Commit in another project: <%= xcommit.to_reference(project) %> - Ignored in code: `<%= commit.to_reference %>` - Ignored in links: [Link to <%= commit.to_reference %>](#commit-link) -- Commit by URL: <%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %> +- Commit by URL: <%= urls.project_commit_url(commit.project, commit) %> - Link to commit by reference: [Commit](<%= commit.to_reference %>) -- Link to commit by URL: [Commit](<%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %>) +- Link to commit by URL: [Commit](<%= urls.project_commit_url(commit.project, commit) %>) #### LabelReferenceFilter @@ -227,7 +227,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Milestone in another project: <%= xmilestone.to_reference(project) %> - Ignored in code: `<%= simple_milestone.to_reference %>` - Ignored in links: [Link to <%= simple_milestone.to_reference %>](#milestone-link) -- Milestone by URL: <%= urls.namespace_project_milestone_url(milestone.project.namespace, milestone.project, milestone) %> +- Milestone by URL: <%= urls.project_milestone_url(milestone.project, milestone) %> - Link to milestone by URL: [Milestone](<%= milestone.to_reference %>) ### Task Lists diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 56daeffde27..e0cad1da86a 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -76,7 +76,7 @@ describe ApplicationHelper do allow_any_instance_of(Project).to receive(:avatar_in_git).and_return(true) - avatar_url = "#{gitlab_host}#{namespace_project_avatar_path(project.namespace, project)}" + avatar_url = "#{gitlab_host}#{project_avatar_path(project)}" expect(helper.project_icon(project.full_path).to_s).to match(image_tag(avatar_url)) end end @@ -292,7 +292,7 @@ describe ApplicationHelper do let(:alternate_url) { 'http://company.example.com/getting-help' } before do - allow(current_application_settings).to receive(:help_page_support_url) { alternate_url } + stub_application_setting(help_page_support_url: alternate_url) end it 'returns the alternate support url' do diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb index 14847d0a49e..7a522487a74 100644 --- a/spec/helpers/gitlab_routing_helper_spec.rb +++ b/spec/helpers/gitlab_routing_helper_spec.rb @@ -5,37 +5,37 @@ describe GitlabRoutingHelper do describe '#project_members_url' do let(:project) { build_stubbed(:empty_project) } - it { expect(project_members_url(project)).to eq namespace_project_project_members_url(project.namespace, project) } + it { expect(project_members_url(project)).to eq project_project_members_url(project) } end describe '#project_member_path' do let(:project_member) { create(:project_member) } - it { expect(project_member_path(project_member)).to eq namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) } + it { expect(project_member_path(project_member)).to eq project_project_member_path(project_member.source, project_member) } end describe '#request_access_project_members_path' do let(:project) { build_stubbed(:empty_project) } - it { expect(request_access_project_members_path(project)).to eq request_access_namespace_project_project_members_path(project.namespace, project) } + it { expect(request_access_project_members_path(project)).to eq request_access_project_project_members_path(project) } end describe '#leave_project_members_path' do let(:project) { build_stubbed(:empty_project) } - it { expect(leave_project_members_path(project)).to eq leave_namespace_project_project_members_path(project.namespace, project) } + it { expect(leave_project_members_path(project)).to eq leave_project_project_members_path(project) } end describe '#approve_access_request_project_member_path' do let(:project_member) { create(:project_member) } - it { expect(approve_access_request_project_member_path(project_member)).to eq approve_access_request_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) } + it { expect(approve_access_request_project_member_path(project_member)).to eq approve_access_request_project_project_member_path(project_member.source, project_member) } end describe '#resend_invite_project_member_path' do let(:project_member) { create(:project_member) } - it { expect(resend_invite_project_member_path(project_member)).to eq resend_invite_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) } + it { expect(resend_invite_project_member_path(project_member)).to eq resend_invite_project_project_member_path(project_member.source, project_member) } end end diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 8da22dc78fa..e3f9d9db9eb 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -91,7 +91,7 @@ describe GroupsHelper do let!(:very_deep_nested_group) { create(:group, parent: deep_nested_group) } it 'outputs the groups in the correct order' do - expect(group_title(very_deep_nested_group)).to match(/>#{group.name}<\/a>.*>#{nested_group.name}<\/a>.*>#{deep_nested_group.name}<\/a>/) + expect(helper.group_title(very_deep_nested_group)).to match(/>#{group.name}<\/a>.*>#{nested_group.name}<\/a>.*>#{deep_nested_group.name}<\/a>/) end end end diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index 15cb620199d..d2e918ef014 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -77,54 +77,89 @@ describe IssuablesHelper do }.with_indifferent_access end + let(:issues_finder) { IssuesFinder.new(nil, params) } + let(:merge_requests_finder) { MergeRequestsFinder.new(nil, params) } + + before do + allow(helper).to receive(:issues_finder).and_return(issues_finder) + allow(helper).to receive(:merge_requests_finder).and_return(merge_requests_finder) + end + it 'returns the cached value when called for the same issuable type & with the same params' do - expect(helper).to receive(:params).twice.and_return(params) - expect(helper).to receive(:issuables_count_for_state).with(:issues, :opened).and_return(42) + expect(issues_finder).to receive(:count_by_state).and_return(opened: 42) expect(helper.issuables_state_counter_text(:issues, :opened)) .to eq('<span>Open</span> <span class="badge">42</span>') - expect(helper).not_to receive(:issuables_count_for_state) + expect(issues_finder).not_to receive(:count_by_state) expect(helper.issuables_state_counter_text(:issues, :opened)) .to eq('<span>Open</span> <span class="badge">42</span>') end + it 'takes confidential status into account when searching for issues' do + expect(issues_finder).to receive(:count_by_state).and_return(opened: 42) + + expect(helper.issuables_state_counter_text(:issues, :opened)) + .to include('42') + + expect(issues_finder).to receive(:user_cannot_see_confidential_issues?).twice.and_return(false) + expect(issues_finder).to receive(:count_by_state).and_return(opened: 40) + + expect(helper.issuables_state_counter_text(:issues, :opened)) + .to include('40') + + expect(issues_finder).to receive(:user_can_see_all_confidential_issues?).and_return(true) + expect(issues_finder).to receive(:count_by_state).and_return(opened: 45) + + expect(helper.issuables_state_counter_text(:issues, :opened)) + .to include('45') + end + + it 'does not take confidential status into account when searching for merge requests' do + expect(merge_requests_finder).to receive(:count_by_state).and_return(opened: 42) + expect(merge_requests_finder).not_to receive(:user_cannot_see_confidential_issues?) + expect(merge_requests_finder).not_to receive(:user_can_see_all_confidential_issues?) + + expect(helper.issuables_state_counter_text(:merge_requests, :opened)) + .to include('42') + end + it 'does not take some keys into account in the cache key' do - expect(helper).to receive(:params).and_return({ + expect(issues_finder).to receive(:count_by_state).and_return(opened: 42) + expect(issues_finder).to receive(:params).and_return({ author_id: '11', state: 'foo', sort: 'foo', utf8: 'foo', page: 'foo' }.with_indifferent_access) - expect(helper).to receive(:issuables_count_for_state).with(:issues, :opened).and_return(42) expect(helper.issuables_state_counter_text(:issues, :opened)) .to eq('<span>Open</span> <span class="badge">42</span>') - expect(helper).to receive(:params).and_return({ + expect(issues_finder).not_to receive(:count_by_state) + expect(issues_finder).to receive(:params).and_return({ author_id: '11', state: 'bar', sort: 'bar', utf8: 'bar', page: 'bar' }.with_indifferent_access) - expect(helper).not_to receive(:issuables_count_for_state) expect(helper.issuables_state_counter_text(:issues, :opened)) .to eq('<span>Open</span> <span class="badge">42</span>') end it 'does not take params order into account in the cache key' do - expect(helper).to receive(:params).and_return('author_id' => '11', 'state' => 'opened') - expect(helper).to receive(:issuables_count_for_state).with(:issues, :opened).and_return(42) + expect(issues_finder).to receive(:params).and_return('author_id' => '11', 'state' => 'opened') + expect(issues_finder).to receive(:count_by_state).and_return(opened: 42) expect(helper.issuables_state_counter_text(:issues, :opened)) .to eq('<span>Open</span> <span class="badge">42</span>') - expect(helper).to receive(:params).and_return('state' => 'opened', 'author_id' => '11') - expect(helper).not_to receive(:issuables_count_for_state) + expect(issues_finder).to receive(:params).and_return('state' => 'opened', 'author_id' => '11') + expect(issues_finder).not_to receive(:count_by_state) expect(helper.issuables_state_counter_text(:issues, :opened)) .to eq('<span>Open</span> <span class="badge">42</span>') diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 00db98fd9d2..8f7f17a484f 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -137,7 +137,7 @@ describe IssuesHelper do let(:merge_request) { create(:merge_request) } it "links just the merge request" do - expected_path = namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request) + expected_path = project_merge_request_path(merge_request.project, merge_request) expect(link_to_discussions_to_resolve(merge_request, nil)).to include(expected_path) end diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index b4226f96a04..4b6a351cf70 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -25,17 +25,17 @@ describe MarkupHelper do let(:actual) { "#{merge_request.to_reference} -> #{commit.to_reference} -> #{issue.to_reference}" } it "links to the merge request" do - expected = namespace_project_merge_request_path(project.namespace, project, merge_request) + expected = project_merge_request_path(project, merge_request) expect(helper.markdown(actual)).to match(expected) end it "links to the commit" do - expected = namespace_project_commit_path(project.namespace, project, commit) + expected = project_commit_path(project, commit) expect(helper.markdown(actual)).to match(expected) end it "links to the issue" do - expected = namespace_project_issue_path(project.namespace, project, issue) + expected = project_issue_path(project, issue) expect(helper.markdown(actual)).to match(expected) end end @@ -46,7 +46,7 @@ describe MarkupHelper do let(:second_issue) { create(:issue, project: second_project) } it 'links to the issue' do - expected = namespace_project_issue_path(second_project.namespace, second_project, second_issue) + expected = project_issue_path(second_project, second_issue) expect(markdown(actual, project: second_project)).to match(expected) end end @@ -69,7 +69,7 @@ describe MarkupHelper do # First issue link expect(doc.css('a')[1].attr('href')) - .to eq namespace_project_issue_path(project.namespace, project, issues[0]) + .to eq project_issue_path(project, issues[0]) expect(doc.css('a')[1].text).to eq issues[0].to_reference # Internal commit link @@ -78,7 +78,7 @@ describe MarkupHelper do # Second issue link expect(doc.css('a')[3].attr('href')) - .to eq namespace_project_issue_path(project.namespace, project, issues[1]) + .to eq project_issue_path(project, issues[1]) expect(doc.css('a')[3].text).to eq issues[1].to_reference # Trailing commit link diff --git a/spec/helpers/milestones_helper_spec.rb b/spec/helpers/milestones_helper_spec.rb index 3cb809d42b5..b8f9c02a486 100644 --- a/spec/helpers/milestones_helper_spec.rb +++ b/spec/helpers/milestones_helper_spec.rb @@ -1,6 +1,42 @@ require 'spec_helper' describe MilestonesHelper do + describe '#milestones_filter_dropdown_path' do + let(:project) { create(:empty_project) } + let(:project2) { create(:empty_project) } + let(:group) { create(:group) } + + context 'when @project present' do + it 'returns project milestones JSON URL' do + assign(:project, project) + + expect(helper.milestones_filter_dropdown_path).to eq(project_milestones_path(project, :json)) + end + end + + context 'when @target_project present' do + it 'returns targeted project milestones JSON URL' do + assign(:target_project, project2) + + expect(helper.milestones_filter_dropdown_path).to eq(project_milestones_path(project2, :json)) + end + end + + context 'when @group present' do + it 'returns group milestones JSON URL' do + assign(:group, group) + + expect(helper.milestones_filter_dropdown_path).to eq(group_milestones_path(group, :json)) + end + end + + context 'when neither of @project/@target_project/@group present' do + it 'returns dashboard milestones JSON URL' do + expect(helper.milestones_filter_dropdown_path).to eq(dashboard_milestones_path(:json)) + end + end + end + describe "#milestone_date_range" do def result_for(*args) milestone_date_range(build(:milestone, *args)) diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb index cc861af8533..56f252ba273 100644 --- a/spec/helpers/notes_helper_spec.rb +++ b/spec/helpers/notes_helper_spec.rb @@ -53,7 +53,7 @@ describe NotesHelper do let(:discussion) { create(:diff_note_on_merge_request, noteable: merge_request, project: project).to_discussion } it 'returns the diff path with the line code' do - expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: discussion.line_code)) + expect(helper.discussion_path(discussion)).to eq(diffs_project_merge_request_path(project, merge_request, anchor: discussion.line_code)) end end @@ -77,7 +77,7 @@ describe NotesHelper do end it 'returns the diff version path with the line code' do - expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: merge_request_diff1, anchor: discussion.line_code)) + expect(helper.discussion_path(discussion)).to eq(diffs_project_merge_request_path(project, merge_request, diff_id: merge_request_diff1, anchor: discussion.line_code)) end end @@ -101,7 +101,7 @@ describe NotesHelper do end it 'returns the diff version comparison path with the line code' do - expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: merge_request_diff3, start_sha: merge_request_diff1.head_commit_sha, anchor: discussion.line_code)) + expect(helper.discussion_path(discussion)).to eq(diffs_project_merge_request_path(project, merge_request, diff_id: merge_request_diff3, start_sha: merge_request_diff1.head_commit_sha, anchor: discussion.line_code)) end end @@ -129,7 +129,7 @@ describe NotesHelper do end it 'returns the diff path with the line code' do - expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: discussion.line_code)) + expect(helper.discussion_path(discussion)).to eq(diffs_project_merge_request_path(project, merge_request, anchor: discussion.line_code)) end end @@ -160,7 +160,7 @@ describe NotesHelper do let(:discussion) { create(:diff_note_on_commit, project: project).to_discussion } it 'returns the commit path with the line code' do - expect(helper.discussion_path(discussion)).to eq(namespace_project_commit_path(project.namespace, project, commit, anchor: discussion.line_code)) + expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit, anchor: discussion.line_code)) end end @@ -168,7 +168,7 @@ describe NotesHelper do let(:discussion) { create(:legacy_diff_note_on_commit, project: project).to_discussion } it 'returns the commit path with the line code' do - expect(helper.discussion_path(discussion)).to eq(namespace_project_commit_path(project.namespace, project, commit, anchor: discussion.line_code)) + expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit, anchor: discussion.line_code)) end end @@ -176,7 +176,7 @@ describe NotesHelper do let(:discussion) { create(:discussion_note_on_commit, project: project).to_discussion } it 'returns the commit path' do - expect(helper.discussion_path(discussion)).to eq(namespace_project_commit_path(project.namespace, project, commit)) + expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit)) end end end diff --git a/spec/initializers/8_metrics_spec.rb b/spec/initializers/8_metrics_spec.rb index a507d7f7f2b..d4189f902fd 100644 --- a/spec/initializers/8_metrics_spec.rb +++ b/spec/initializers/8_metrics_spec.rb @@ -1,17 +1,25 @@ require 'spec_helper' -require_relative '../../config/initializers/8_metrics' describe 'instrument_classes', lib: true do let(:config) { double(:config) } + let(:unicorn_sampler) { double(:unicorn_sampler) } + let(:influx_sampler) { double(:influx_sampler) } + before do allow(config).to receive(:instrument_method) allow(config).to receive(:instrument_methods) allow(config).to receive(:instrument_instance_method) allow(config).to receive(:instrument_instance_methods) + allow(Gitlab::Metrics::UnicornSampler).to receive(:initialize_instance).and_return(unicorn_sampler) + allow(Gitlab::Metrics::InfluxSampler).to receive(:initialize_instance).and_return(influx_sampler) + allow(unicorn_sampler).to receive(:start) + allow(influx_sampler).to receive(:start) + allow(Gitlab::Application).to receive(:configure) end it 'can autoload and instrument all files' do + require_relative '../../config/initializers/8_metrics' expect { instrument_classes(config) }.not_to raise_error end end diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index 3fc03324d16..8e056882108 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -1,7 +1,7 @@ /* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, comma-dangle, new-parens, no-unused-vars, quotes, jasmine/no-spec-dupes, prefer-template, max-len */ import Cookies from 'js-cookie'; -import AwardsHandler from '~/awards_handler'; +import loadAwardsHandler from '~/awards_handler'; import '~/lib/utils/common_utils'; @@ -26,14 +26,13 @@ import '~/lib/utils/common_utils'; describe('AwardsHandler', function() { preloadFixtures('issues/issue_with_comment.html.raw'); - beforeEach(function() { + beforeEach(function(done) { loadFixtures('issues/issue_with_comment.html.raw'); - awardsHandler = new AwardsHandler; - spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) { - return function(button, url, emoji, cb) { - return cb(); - }; - })(this)); + loadAwardsHandler(true).then((obj) => { + awardsHandler = obj; + spyOn(awardsHandler, 'postEmoji').and.callFake((button, url, emoji, cb) => cb()); + done(); + }).catch(fail); let isEmojiMenuBuilt = false; openAndWaitForEmojiMenu = function() { diff --git a/spec/javascripts/environments/environments_store_spec.js b/spec/javascripts/environments/environments_store_spec.js index 6e855530b21..f2c6ec24dd7 100644 --- a/spec/javascripts/environments/environments_store_spec.js +++ b/spec/javascripts/environments/environments_store_spec.js @@ -86,6 +86,16 @@ describe('Store', () => { store.toggleFolder(store.state.environments[1]); expect(store.state.environments[1].isOpen).toEqual(false); }); + + it('should keep folder open when environments are updated', () => { + store.storeEnvironments(serverData); + + store.toggleFolder(store.state.environments[1]); + expect(store.state.environments[1].isOpen).toEqual(true); + + store.storeEnvironments(serverData); + expect(store.state.environments[1].isOpen).toEqual(true); + }); }); describe('setfolderContent', () => { @@ -97,6 +107,17 @@ describe('Store', () => { expect(store.state.environments[1].children.length).toEqual(serverData.length); expect(store.state.environments[1].children[0].isChildren).toEqual(true); }); + + it('should keep folder content when environments are updated', () => { + store.storeEnvironments(serverData); + + store.setfolderContent(store.state.environments[1], serverData); + + expect(store.state.environments[1].children.length).toEqual(serverData.length); + // poll + store.storeEnvironments(serverData); + expect(store.state.environments[1].children.length).toEqual(serverData.length); + }); }); describe('store pagination', () => { diff --git a/spec/javascripts/fixtures/oauth_remember_me.html.haml b/spec/javascripts/fixtures/oauth_remember_me.html.haml new file mode 100644 index 00000000000..7886e995e57 --- /dev/null +++ b/spec/javascripts/fixtures/oauth_remember_me.html.haml @@ -0,0 +1,5 @@ +#oauth-container + %input#remember_me{ type: "checkbox" } + + %a.oauth-login.twitter{ href: "http://example.com/" } + %a.oauth-login.github{ href: "http://example.com/" } diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js index 9df92318864..bc13373a27e 100644 --- a/spec/javascripts/issue_show/components/app_spec.js +++ b/spec/javascripts/issue_show/components/app_spec.js @@ -42,9 +42,6 @@ describe('Issuable output', () => { }).$mount(); }); - afterEach(() => { - }); - it('should render a title/description/edited and update title/description/edited on update', (done) => { vm.poll.options.successCallback({ json() { diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js index 6f4cb989847..56d938e1fbe 100644 --- a/spec/javascripts/monitoring/mock_data.js +++ b/spec/javascripts/monitoring/mock_data.js @@ -13,7 +13,7 @@ const metricsGroupsAPIResponse = { 'queries': [ { 'query_range': 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20', - 'label': 'Container memory', + 'y_label': 'Memory', 'unit': 'MiB', 'result': [ { @@ -2477,7 +2477,7 @@ export const singleRowMetrics = [ { 'title': 'CPU usage', 'weight': 1, - 'y_label': 'Values', + 'y_label': 'Memory', 'queries': [ { 'query_range': 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100', diff --git a/spec/javascripts/monitoring/monitoring_column_spec.js b/spec/javascripts/monitoring/monitoring_column_spec.js index c8787f9708c..b3bc97adc9f 100644 --- a/spec/javascripts/monitoring/monitoring_column_spec.js +++ b/spec/javascripts/monitoring/monitoring_column_spec.js @@ -94,4 +94,15 @@ describe('MonitoringColumn', () => { done(); }); }); + + it('has a title for the y-axis that comes from the backend', () => { + const component = createComponent({ + columnData: singleRowMetrics[0], + classType: 'col-md-6', + updateAspectRatio: false, + deploymentData, + }); + + expect(component.yAxisLabel).toEqual(component.columnData.y_label); + }); }); diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 5ece4ed080b..2c096ed08a8 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -523,6 +523,51 @@ import '~/notes'; }); }); + describe('postComment with Slash commands', () => { + const sampleComment = '/assign @root\n/award :100:'; + const note = { + commands_changes: { + assignee_id: 1, + emoji_award: '100' + }, + errors: { + commands_only: ['Commands applied'] + }, + valid: false + }; + let $form; + let $notesContainer; + + beforeEach(() => { + this.notes = new Notes('', []); + window.gon.current_username = 'root'; + window.gon.current_user_fullname = 'Administrator'; + gl.awardsHandler = { + addAwardToEmojiBar: () => {}, + scrollToAwards: () => {} + }; + gl.GfmAutoComplete = { + dataSources: { + commands: '/root/test-project/autocomplete_sources/commands' + } + }; + $form = $('form.js-main-target-form'); + $notesContainer = $('ul.main-notes-list'); + $form.find('textarea.js-note-text').val(sampleComment); + }); + + it('should remove slash command placeholder when comment with slash commands is done posting', () => { + const deferred = $.Deferred(); + spyOn($, 'ajax').and.returnValue(deferred.promise()); + spyOn(gl.awardsHandler, 'addAwardToEmojiBar').and.callThrough(); + $('.js-comment-button').click(); + + expect($notesContainer.find('.system-note.being-posted').length).toEqual(1); // Placeholder shown + deferred.resolve(note); + expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed + }); + }); + describe('update comment with script tags', () => { const sampleComment = '<script></script>'; const updatedComment = '<script></script>'; diff --git a/spec/javascripts/oauth_remember_me_spec.js b/spec/javascripts/oauth_remember_me_spec.js new file mode 100644 index 00000000000..f90e0093d25 --- /dev/null +++ b/spec/javascripts/oauth_remember_me_spec.js @@ -0,0 +1,26 @@ +import OAuthRememberMe from '~/oauth_remember_me'; + +describe('OAuthRememberMe', () => { + preloadFixtures('static/oauth_remember_me.html.raw'); + + beforeEach(() => { + loadFixtures('static/oauth_remember_me.html.raw'); + + new OAuthRememberMe({ container: $('#oauth-container') }).bindEvents(); + }); + + it('adds the "remember_me" query parameter to all OAuth login buttons', () => { + $('#oauth-container #remember_me').click(); + + expect($('#oauth-container .oauth-login.twitter').attr('href')).toBe('http://example.com/?remember_me=1'); + expect($('#oauth-container .oauth-login.github').attr('href')).toBe('http://example.com/?remember_me=1'); + }); + + it('removes the "remember_me" query parameter from all OAuth login buttons', () => { + $('#oauth-container #remember_me').click(); + $('#oauth-container #remember_me').click(); + + expect($('#oauth-container .oauth-login.twitter').attr('href')).toBe('http://example.com/'); + expect($('#oauth-container .oauth-login.github').attr('href')).toBe('http://example.com/'); + }); +}); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js index 4b6f171c8d6..647b59520f8 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js @@ -76,28 +76,6 @@ describe('MRWidgetPipeline', () => { el = vm.$el; }); - afterEach(() => { - vm.$destroy(); - }); - - describe('without a pipeline', () => { - beforeEach(() => { - vm.mr = { pipeline: null }; - }); - - it('should render message with spinner', (done) => { - Vue.nextTick() - .then(() => { - expect(el.querySelector('.pipeline-id')).toBe(null); - expect(el.innerText.trim()).toBe('Waiting for pipeline...'); - expect(el.querySelectorAll('i.fa.fa-spinner.fa-spin').length).toBe(1); - done(); - }) - .then(done) - .catch(done.fail); - }); - }); - it('should render template elements correctly', () => { expect(el.classList.contains('mr-widget-heading')).toBeTruthy(); expect(el.querySelectorAll('.ci-status-icon.ci-status-icon-success').length).toEqual(1); @@ -115,47 +93,39 @@ describe('MRWidgetPipeline', () => { it('should list single stage', (done) => { pipeline.details.stages.splice(0, 1); - Vue.nextTick() - .then(() => { - expect(el.querySelectorAll('.stage-container button').length).toEqual(1); - expect(el.innerText).toContain('with stage'); - }) - .then(done) - .catch(done.fail); + Vue.nextTick(() => { + expect(el.querySelectorAll('.stage-container button').length).toEqual(1); + expect(el.innerText).toContain('with stage'); + done(); + }); }); it('should not have stages when there is no stage', (done) => { vm.mr.pipeline.details.stages = []; - Vue.nextTick() - .then(() => { - expect(el.querySelectorAll('.stage-container button').length).toEqual(0); - }) - .then(done) - .catch(done.fail); + Vue.nextTick(() => { + expect(el.querySelectorAll('.stage-container button').length).toEqual(0); + done(); + }); }); it('should not have coverage text when pipeline has no coverage info', (done) => { vm.mr.pipeline.coverage = null; - Vue.nextTick() - .then(() => { - expect(el.querySelector('.js-mr-coverage')).toEqual(null); - }) - .then(done) - .catch(done.fail); + Vue.nextTick(() => { + expect(el.querySelector('.js-mr-coverage')).toEqual(null); + done(); + }); }); it('should show CI error when there is a CI error', (done) => { vm.mr.ciStatus = null; - Vue.nextTick() - .then(() => { - expect(el.querySelectorAll('.js-ci-error').length).toEqual(1); - expect(el.innerText).toContain('Could not connect to the CI server'); - }) - .then(done) - .catch(done.fail); + Vue.nextTick(() => { + expect(el.querySelectorAll('.js-ci-error').length).toEqual(1); + expect(el.innerText).toContain('Could not connect to the CI server'); + done(); + }); }); }); }); diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb index fc67c7ec3c4..60c27bc0d3c 100644 --- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb @@ -29,14 +29,14 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do doc = reference_filter("See #{reference2}") expect(doc.css('a').first.attr('href')) - .to eq urls.namespace_project_compare_url(project.namespace, project, range2.to_param) + .to eq urls.project_compare_url(project, range2.to_param) end it 'links to a valid three-dot reference' do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')) - .to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param) + .to eq urls.project_compare_url(project, range.to_param) end it 'links to a valid short ID' do @@ -94,7 +94,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do link = doc.css('a').first.attr('href') expect(link).not_to match %r(https?://) - expect(link).to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id, only_path: true) + expect(link).to eq urls.project_compare_url(project, from: commit1.id, to: commit2.id, only_path: true) end end @@ -106,7 +106,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')) - .to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param) + .to eq urls.project_compare_url(project2, range.to_param) end it 'link has valid text' do @@ -141,7 +141,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')) - .to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param) + .to eq urls.project_compare_url(project2, range.to_param) end it 'link has valid text' do @@ -176,7 +176,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')) - .to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param) + .to eq urls.project_compare_url(project2, range.to_param) end it 'link has valid text' do @@ -205,7 +205,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do let(:namespace) { create(:namespace) } let(:project2) { create(:project, :public, :repository, namespace: namespace) } let(:range) { CommitRange.new("#{commit1.id}...master", project) } - let(:reference) { urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: 'master') } + let(:reference) { urls.project_compare_url(project2, from: commit1.id, to: 'master') } before do range.project = project2 diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb index c4d8d3b6682..f6893641481 100644 --- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb @@ -27,7 +27,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do expect(doc.css('a').first.text).to eq commit.short_id expect(doc.css('a').first.attr('href')) - .to eq urls.namespace_project_commit_url(project.namespace, project, reference) + .to eq urls.project_commit_url(project, reference) end end @@ -90,7 +90,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do link = doc.css('a').first.attr('href') expect(link).not_to match %r(https?://) - expect(link).to eq urls.namespace_project_commit_url(project.namespace, project, reference, only_path: true) + expect(link).to eq urls.project_commit_url(project, reference, only_path: true) end end @@ -175,13 +175,13 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do let(:namespace) { create(:namespace) } let(:project2) { create(:project, :public, :repository, namespace: namespace) } let(:commit) { project2.commit } - let(:reference) { urls.namespace_project_commit_url(project2.namespace, project2, commit.id) } + let(:reference) { urls.project_commit_url(project2, commit.id) } it 'links to a valid reference' do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')) - .to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id) + .to eq urls.project_commit_url(project2, commit.id) end it 'links with adjacent text' do diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb index a4bb043f8f1..b7d82c36ddd 100644 --- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb @@ -88,12 +88,12 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do it 'queries the collection on the first call' do expect_any_instance_of(Project).to receive(:default_issues_tracker?).once.and_call_original - expect_any_instance_of(Project).to receive(:issue_reference_pattern).once.and_call_original + expect_any_instance_of(Project).to receive(:external_issue_reference_pattern).once.and_call_original not_cached = reference_filter.call("look for #{reference}", { project: project }) expect_any_instance_of(Project).not_to receive(:default_issues_tracker?) - expect_any_instance_of(Project).not_to receive(:issue_reference_pattern) + expect_any_instance_of(Project).not_to receive(:external_issue_reference_pattern) cached = reference_filter.call("look for #{reference}", { project: project }) diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb index e5c1deb338b..a79d365d6c5 100644 --- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb @@ -39,13 +39,6 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do let(:reference) { "##{issue.iid}" } - it 'ignores valid references when using non-default tracker' do - allow(project).to receive(:default_issues_tracker?).and_return(false) - - exp = act = "Issue #{reference}" - expect(reference_filter(act).to_html).to eq exp - end - it 'links to a valid reference' do doc = reference_filter("Fixed #{reference}") @@ -340,24 +333,6 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do .to eq({ project => { issue.iid => issue } }) end end - - context 'using an external issue tracker' do - it 'returns a Hash containing the issues per project' do - doc = Nokogiri::HTML.fragment('') - filter = described_class.new(doc, project: project) - - expect(project).to receive(:default_issues_tracker?).and_return(false) - - expect(filter).to receive(:projects_per_reference) - .and_return({ project.path_with_namespace => project }) - - expect(filter).to receive(:references_per_project) - .and_return({ project.path_with_namespace => Set.new([1]) }) - - expect(filter.issues_per_project[project][1]) - .to be_an_instance_of(ExternalIssue) - end - end end describe '.references_in' do diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index cb3cf982351..8daef3ca691 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -45,7 +45,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do link = doc.css('a').first.attr('href') expect(link).not_to match %r(https?://) - expect(link).to eq urls.namespace_project_issues_path(project.namespace, project, label_name: label.name) + expect(link).to eq urls.project_issues_path(project, label_name: label.name) end context 'project that does not exist referenced' do @@ -73,7 +73,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls - .namespace_project_issues_url(project.namespace, project, label_name: label.name) + .project_issues_url(project, label_name: label.name) end it 'links with adjacent text' do @@ -96,7 +96,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls - .namespace_project_issues_url(project.namespace, project, label_name: label.name) + .project_issues_url(project, label_name: label.name) expect(doc.text).to eq 'See gfm' end @@ -120,7 +120,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls - .namespace_project_issues_url(project.namespace, project, label_name: label.name) + .project_issues_url(project, label_name: label.name) expect(doc.text).to eq 'See 2fa' end @@ -144,7 +144,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls - .namespace_project_issues_url(project.namespace, project, label_name: label.name) + .project_issues_url(project, label_name: label.name) expect(doc.text).to eq 'See ?g.fm&' end @@ -169,7 +169,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls - .namespace_project_issues_url(project.namespace, project, label_name: label.name) + .project_issues_url(project, label_name: label.name) expect(doc.text).to eq 'See gfm references' end @@ -193,7 +193,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls - .namespace_project_issues_url(project.namespace, project, label_name: label.name) + .project_issues_url(project, label_name: label.name) expect(doc.text).to eq 'See 2 factor authentication' end @@ -217,7 +217,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls - .namespace_project_issues_url(project.namespace, project, label_name: label.name) + .project_issues_url(project, label_name: label.name) expect(doc.text).to eq 'See g.fm & references?' end @@ -250,9 +250,9 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do doc = reference_filter("See #{references}") expect(doc.css('a').map { |a| a.attr('href') }).to match_array([ - urls.namespace_project_issues_url(project.namespace, project, label_name: bug.name), - urls.namespace_project_issues_url(project.namespace, project, label_name: feature_proposal.name), - urls.namespace_project_issues_url(project.namespace, project, label_name: technical_debt.name) + urls.project_issues_url(project, label_name: bug.name), + urls.project_issues_url(project, label_name: feature_proposal.name), + urls.project_issues_url(project, label_name: technical_debt.name) ]) expect(doc.text).to eq 'See bug, feature proposal, technical debt' end @@ -265,9 +265,9 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do doc = reference_filter("See #{references}") expect(doc.css('a').map { |a| a.attr('href') }).to match_array([ - urls.namespace_project_issues_url(project.namespace, project, label_name: bug.name), - urls.namespace_project_issues_url(project.namespace, project, label_name: feature_proposal.name), - urls.namespace_project_issues_url(project.namespace, project, label_name: technical_debt.name) + urls.project_issues_url(project, label_name: bug.name), + urls.project_issues_url(project, label_name: feature_proposal.name), + urls.project_issues_url(project, label_name: technical_debt.name) ]) expect(doc.text).to eq 'See bug feature proposal technical debt' end @@ -288,7 +288,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls - .namespace_project_issues_url(project.namespace, project, label_name: label.name) + .project_issues_url(project, label_name: label.name) end it 'links with adjacent text' do @@ -325,7 +325,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do doc = reference_filter("See #{reference}", project: project) expect(doc.css('a').first.attr('href')).to eq urls - .namespace_project_issues_url(project.namespace, project, label_name: group_label.name) + .project_issues_url(project, label_name: group_label.name) expect(doc.text).to eq 'See gfm references' end @@ -348,7 +348,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do doc = reference_filter("See #{reference}", project: project) expect(doc.css('a').first.attr('href')).to eq urls - .namespace_project_issues_url(project.namespace, project, label_name: group_label.name) + .project_issues_url(project, label_name: group_label.name) expect(doc.text).to eq "See gfm references" end @@ -373,9 +373,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do it 'links to a valid reference' do expect(result.css('a').first.attr('href')) - .to eq urls.namespace_project_issues_url(project2.namespace, - project2, - label_name: label.name) + .to eq urls.project_issues_url(project2, label_name: label.name) end it 'has valid color' do @@ -407,9 +405,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do it 'links to a valid reference' do expect(result.css('a').first.attr('href')) - .to eq urls.namespace_project_issues_url(project2.namespace, - project2, - label_name: label.name) + .to eq urls.project_issues_url(project2, label_name: label.name) end it 'has valid color' do @@ -441,9 +437,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do it 'links to a valid reference' do expect(result.css('a').first.attr('href')) - .to eq urls.namespace_project_issues_url(project2.namespace, - project2, - label_name: label.name) + .to eq urls.project_issues_url(project2, label_name: label.name) end it 'has valid color' do @@ -477,9 +471,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do it 'points to referenced project issues page' do expect(result.css('a').first.attr('href')) - .to eq urls.namespace_project_issues_url(another_project.namespace, - another_project, - label_name: group_label.name) + .to eq urls.project_issues_url(another_project, label_name: group_label.name) end it 'has valid color' do @@ -514,9 +506,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do it 'points to referenced project issues page' do expect(result.css('a').first.attr('href')) - .to eq urls.namespace_project_issues_url(another_project.namespace, - another_project, - label_name: group_label.name) + .to eq urls.project_issues_url(another_project, label_name: group_label.name) end it 'has valid color' do @@ -550,9 +540,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do it 'points to referenced project issues page' do expect(result.css('a').first.attr('href')) - .to eq urls.namespace_project_issues_url(project.namespace, - project, - label_name: group_label.name) + .to eq urls.project_issues_url(project, label_name: group_label.name) end it 'has valid color' do @@ -584,9 +572,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do it 'points to referenced project issues page' do expect(result.css('a').first.attr('href')) - .to eq urls.namespace_project_issues_url(project.namespace, - project, - label_name: group_label.name) + .to eq urls.project_issues_url(project, label_name: group_label.name) end it 'has valid color' do diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb index cd91681551e..1ad329b6452 100644 --- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb @@ -37,7 +37,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls - .namespace_project_merge_request_url(project.namespace, project, merge) + .project_merge_request_url(project, merge) end it 'links with adjacent text' do @@ -95,7 +95,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do link = doc.css('a').first.attr('href') expect(link).not_to match %r(https?://) - expect(link).to eq urls.namespace_project_merge_request_url(project.namespace, project, merge, only_path: true) + expect(link).to eq urls.project_merge_request_url(project, merge, only_path: true) end end @@ -108,8 +108,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')) - .to eq urls.namespace_project_merge_request_url(project2.namespace, - project2, merge) + .to eq urls.project_merge_request_url(project2, merge) end it 'link has valid text' do @@ -142,8 +141,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')) - .to eq urls.namespace_project_merge_request_url(project2.namespace, - project2, merge) + .to eq urls.project_merge_request_url(project2, merge) end it 'link has valid text' do @@ -176,8 +174,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')) - .to eq urls.namespace_project_merge_request_url(project2.namespace, - project2, merge) + .to eq urls.project_merge_request_url(project2, merge) end it 'link has valid text' do @@ -203,7 +200,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:empty_project, :public, namespace: namespace) } let(:merge) { create(:merge_request, source_project: project2, target_project: project2) } - let(:reference) { urls.namespace_project_merge_request_url(project2.namespace, project2, merge) + '/diffs#note_123' } + let(:reference) { urls.project_merge_request_url(project2, merge) + '/diffs#note_123' } it 'links to a valid reference' do doc = reference_filter("See #{reference}") diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index fe88b9cb73e..7fab5613afc 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -45,7 +45,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do expect(link).not_to match %r(https?://) expect(link).to eq urls - .namespace_project_milestone_path(project.namespace, project, milestone) + .project_milestone_path(project, milestone) end context 'Integer-based references' do @@ -53,7 +53,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls - .namespace_project_milestone_url(project.namespace, project, milestone) + .project_milestone_url(project, milestone) end it 'links with adjacent text' do @@ -76,7 +76,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls - .namespace_project_milestone_url(project.namespace, project, milestone) + .project_milestone_url(project, milestone) expect(doc.text).to eq 'See gfm' end @@ -100,7 +100,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls - .namespace_project_milestone_url(project.namespace, project, milestone) + .project_milestone_url(project, milestone) expect(doc.text).to eq 'See gfm references' end @@ -123,7 +123,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls - .namespace_project_milestone_url(project.namespace, project, milestone) + .project_milestone_url(project, milestone) end it 'links with adjacent text' do @@ -157,9 +157,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do it 'points to referenced project milestone page' do expect(result.css('a').first.attr('href')).to eq urls - .namespace_project_milestone_url(another_project.namespace, - another_project, - milestone) + .project_milestone_url(another_project, milestone) end it 'link has valid text' do @@ -196,9 +194,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do it 'points to referenced project milestone page' do expect(result.css('a').first.attr('href')).to eq urls - .namespace_project_milestone_url(another_project.namespace, - another_project, - milestone) + .project_milestone_url(another_project, milestone) end it 'link has valid text' do @@ -235,9 +231,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do it 'points to referenced project milestone page' do expect(result.css('a').first.attr('href')).to eq urls - .namespace_project_milestone_url(another_project.namespace, - another_project, - milestone) + .project_milestone_url(another_project, milestone) end it 'link has valid text' do diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb index e851120bc3a..9704db0c221 100644 --- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb @@ -23,7 +23,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls - .namespace_project_snippet_url(project.namespace, project, snippet) + .project_snippet_url(project, snippet) end it 'links with adjacent text' do @@ -75,7 +75,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do link = doc.css('a').first.attr('href') expect(link).not_to match %r(https?://) - expect(link).to eq urls.namespace_project_snippet_url(project.namespace, project, snippet, only_path: true) + expect(link).to eq urls.project_snippet_url(project, snippet, only_path: true) end end @@ -89,7 +89,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')) - .to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet) + .to eq urls.project_snippet_url(project2, snippet) end it 'link has valid text' do @@ -122,7 +122,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')) - .to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet) + .to eq urls.project_snippet_url(project2, snippet) end it 'link has valid text' do @@ -155,7 +155,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')) - .to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet) + .to eq urls.project_snippet_url(project2, snippet) end it 'link has valid text' do @@ -181,13 +181,13 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:empty_project, :public, namespace: namespace) } let(:snippet) { create(:project_snippet, project: project2) } - let(:reference) { urls.namespace_project_snippet_url(project2.namespace, project2, snippet) } + let(:reference) { urls.project_snippet_url(project2, snippet) } it 'links to a valid reference' do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')) - .to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet) + .to eq urls.project_snippet_url(project2, snippet) end it 'links with adjacent text' do diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index edf3846b742..77561e00573 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -43,7 +43,7 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do expect(doc.css('a').length).to eq 1 expect(doc.css('a').first.attr('href')) - .to eq urls.namespace_project_url(project.namespace, project) + .to eq urls.project_url(project) end it 'includes a data-author attribute when there is an author' do diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb new file mode 100644 index 00000000000..1eb90dc1847 --- /dev/null +++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb @@ -0,0 +1,29 @@ +require 'rails_helper' + +describe Banzai::Pipeline::GfmPipeline do + describe 'integration between parsing regular and external issue references' do + let(:project) { create(:redmine_project, :public) } + + it 'allows to use shorthand external reference syntax for Redmine' do + markdown = '#12' + + result = described_class.call(markdown, project: project)[:output] + link = result.css('a').first + + expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12' + end + + it 'parses cross-project references to regular issues' do + other_project = create(:empty_project, :public) + issue = create(:issue, project: other_project) + markdown = issue.to_reference(project, full: true) + + result = described_class.call(markdown, project: project)[:output] + link = result.css('a').first + + expect(link['href']).to eq( + Gitlab::Routing.url_helpers.project_issue_path(other_project, issue) + ) + end + end +end diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb index 58e1a0c1bc1..acdd23f81f3 100644 --- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb @@ -39,16 +39,6 @@ describe Banzai::ReferenceParser::IssueParser, lib: true do expect(subject.nodes_visible_to_user(user, [link])).to eq([]) end end - - context 'when the project uses an external issue tracker' do - it 'returns all nodes' do - link = double(:link) - - expect(project).to receive(:external_issue_tracker).and_return(true) - - expect(subject.nodes_visible_to_user(user, [link])).to eq([link]) - end - end end describe '#referenced_by' do diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index af0e7855a9b..ef58ef1b0cd 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -598,8 +598,10 @@ module Ci describe "Image and service handling" do context "when extended docker configuration is used" do it "returns image and service when defined" do - config = YAML.dump({ image: { name: "ruby:2.1" }, - services: ["mysql", { name: "docker:dind", alias: "docker" }], + config = YAML.dump({ image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] }, + services: ["mysql", { name: "docker:dind", alias: "docker", + entrypoint: ["/usr/local/bin/init", "run"], + command: ["/usr/local/bin/init", "run"] }], before_script: ["pwd"], rspec: { script: "rspec" } }) @@ -614,8 +616,10 @@ module Ci coverage_regex: nil, tag_list: [], options: { - image: { name: "ruby:2.1" }, - services: [{ name: "mysql" }, { name: "docker:dind", alias: "docker" }] + image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] }, + services: [{ name: "mysql" }, + { name: "docker:dind", alias: "docker", entrypoint: ["/usr/local/bin/init", "run"], + command: ["/usr/local/bin/init", "run"] }] }, allow_failure: false, when: "on_success", @@ -628,8 +632,11 @@ module Ci config = YAML.dump({ image: "ruby:2.1", services: ["mysql"], before_script: ["pwd"], - rspec: { image: { name: "ruby:2.5" }, - services: [{ name: "postgresql", alias: "db-pg" }, "docker:dind"], script: "rspec" } }) + rspec: { image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] }, + services: [{ name: "postgresql", alias: "db-pg", + entrypoint: ["/usr/local/bin/init", "run"], + command: ["/usr/local/bin/init", "run"] }, "docker:dind"], + script: "rspec" } }) config_processor = GitlabCiYamlProcessor.new(config, path) @@ -642,8 +649,10 @@ module Ci coverage_regex: nil, tag_list: [], options: { - image: { name: "ruby:2.5" }, - services: [{ name: "postgresql", alias: "db-pg" }, { name: "docker:dind" }] + image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] }, + services: [{ name: "postgresql", alias: "db-pg", entrypoint: ["/usr/local/bin/init", "run"], + command: ["/usr/local/bin/init", "run"] }, + { name: "docker:dind" }] }, allow_failure: false, when: "on_success", @@ -869,7 +878,8 @@ module Ci expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( paths: ["logs/", "binaries/"], untracked: true, - key: 'key' + key: 'key', + policy: 'pull-push' ) end @@ -887,7 +897,8 @@ module Ci expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( paths: ["logs/", "binaries/"], untracked: true, - key: 'key' + key: 'key', + policy: 'pull-push' ) end @@ -906,7 +917,8 @@ module Ci expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( paths: ["test/"], untracked: false, - key: 'local' + key: 'local', + policy: 'pull-push' ) end end diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb index 878b1d6b862..8f711e02f9b 100644 --- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Ci::Config::Entry::Cache do - let(:entry) { described_class.new(config) } + subject(:entry) { described_class.new(config) } describe 'validations' do before do @@ -9,22 +9,44 @@ describe Gitlab::Ci::Config::Entry::Cache do end context 'when entry config value is correct' do + let(:policy) { nil } + let(:config) do { key: 'some key', untracked: true, - paths: ['some/path/'] } + paths: ['some/path/'], + policy: policy } end describe '#value' do it 'returns hash value' do - expect(entry.value).to eq config + expect(entry.value).to eq(key: 'some key', untracked: true, paths: ['some/path/'], policy: 'pull-push') end end describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end + it { is_expected.to be_valid } + end + + context 'policy is pull-push' do + let(:policy) { 'pull-push' } + + it { is_expected.to be_valid } + it { expect(entry.value).to include(policy: 'pull-push') } + end + + context 'policy is push' do + let(:policy) { 'push' } + + it { is_expected.to be_valid } + it { expect(entry.value).to include(policy: 'push') } + end + + context 'policy is pull' do + let(:policy) { 'pull' } + + it { is_expected.to be_valid } + it { expect(entry.value).to include(policy: 'pull') } end context 'when key is missing' do @@ -44,12 +66,20 @@ describe Gitlab::Ci::Config::Entry::Cache do context 'when entry value is not correct' do describe '#errors' do + subject { entry.errors } context 'when is not a hash' do let(:config) { 'ls' } it 'reports errors with config value' do - expect(entry.errors) - .to include 'cache config should be a hash' + is_expected.to include 'cache config should be a hash' + end + end + + context 'when policy is unknown' do + let(:config) { { policy: "unknown" } } + + it 'reports error' do + is_expected.to include('cache policy should be pull-push, push, or pull') end end @@ -57,8 +87,7 @@ describe Gitlab::Ci::Config::Entry::Cache do let(:config) { { key: 1 } } it 'reports error with descendants' do - expect(entry.errors) - .to include 'key config should be a string or symbol' + is_expected.to include 'key config should be a string or symbol' end end @@ -66,8 +95,7 @@ describe Gitlab::Ci::Config::Entry::Cache do let(:config) { { invalid: true } } it 'reports error with descendants' do - expect(entry.errors) - .to include 'cache config contains unknown keys: invalid' + is_expected.to include 'cache config contains unknown keys: invalid' end end end diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb index 293f112b2b0..1860ed79bfd 100644 --- a/spec/lib/gitlab/ci/config/entry/global_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb @@ -143,7 +143,7 @@ describe Gitlab::Ci::Config::Entry::Global do describe '#cache_value' do it 'returns cache configuration' do expect(global.cache_value) - .to eq(key: 'k', untracked: true, paths: ['public/']) + .to eq(key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push') end end @@ -157,7 +157,7 @@ describe Gitlab::Ci::Config::Entry::Global do image: { name: 'ruby:2.2' }, services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', - cache: { key: 'k', untracked: true, paths: ['public/'] }, + cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' }, variables: { 'VAR' => 'value' }, ignore: false, after_script: ['make clean'] }, @@ -168,7 +168,7 @@ describe Gitlab::Ci::Config::Entry::Global do image: { name: 'ruby:2.2' }, services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', - cache: { key: 'k', untracked: true, paths: ['public/'] }, + cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' }, variables: {}, ignore: false, after_script: ['make clean'] } @@ -212,7 +212,7 @@ describe Gitlab::Ci::Config::Entry::Global do describe '#cache_value' do it 'returns correct cache definition' do - expect(global.cache_value).to eq(key: 'a') + expect(global.cache_value).to eq(key: 'a', policy: 'pull-push') end end end diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb index bca22e39500..1a4d9ed5517 100644 --- a/spec/lib/gitlab/ci/config/entry/image_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb @@ -38,7 +38,7 @@ describe Gitlab::Ci::Config::Entry::Image do end context 'when configuration is a hash' do - let(:config) { { name: 'ruby:2.2', entrypoint: '/bin/sh' } } + let(:config) { { name: 'ruby:2.2', entrypoint: %w(/bin/sh run) } } describe '#value' do it 'returns image hash' do @@ -66,7 +66,7 @@ describe Gitlab::Ci::Config::Entry::Image do describe '#entrypoint' do it "returns image's entrypoint" do - expect(entry.entrypoint).to eq '/bin/sh' + expect(entry.entrypoint).to eq %w(/bin/sh run) end end end diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index 92cba689f47..c5cad887b64 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -109,7 +109,7 @@ describe Gitlab::Ci::Config::Entry::Job do it 'overrides global config' do expect(entry[:image].value).to eq(name: 'some_image') - expect(entry[:cache].value).to eq(key: 'test') + expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push') end end @@ -123,7 +123,7 @@ describe Gitlab::Ci::Config::Entry::Job do it 'uses config from global entry' do expect(entry[:image].value).to eq 'specified' - expect(entry[:cache].value).to eq(key: 'test') + expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push') end end end diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb index 7202fe525e4..9ebf947a751 100644 --- a/spec/lib/gitlab/ci/config/entry/service_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb @@ -43,7 +43,7 @@ describe Gitlab::Ci::Config::Entry::Service do context 'when configuration is a hash' do let(:config) do - { name: 'postgresql:9.5', alias: 'db', command: 'cmd', entrypoint: '/bin/sh' } + { name: 'postgresql:9.5', alias: 'db', command: %w(cmd run), entrypoint: %w(/bin/sh run) } end describe '#valid?' do @@ -72,13 +72,13 @@ describe Gitlab::Ci::Config::Entry::Service do describe '#command' do it "returns service's command" do - expect(entry.command).to eq 'cmd' + expect(entry.command).to eq %w(cmd run) end end describe '#entrypoint' do it "returns service's entrypoint" do - expect(entry.entrypoint).to eq '/bin/sh' + expect(entry.entrypoint).to eq %w(/bin/sh run) end end end diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index ca68010cb89..fe988266ae3 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -276,7 +276,7 @@ describe Gitlab::ClosingIssueExtractor, lib: true do context "with a cross-project URL" do it do - message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)}" + message = "Closes #{urls.project_issue_url(issue2.project, issue2)}" expect(subject.closed_by_message(message)).to eq([issue2]) end end @@ -292,7 +292,7 @@ describe Gitlab::ClosingIssueExtractor, lib: true do context "with an invalid URL" do it do - message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)}" + message = "Closes https://google.com#{urls.project_issue_path(issue2.project, issue2)}" expect(subject.closed_by_message(message)).to eq([]) end end @@ -347,14 +347,14 @@ describe Gitlab::ClosingIssueExtractor, lib: true do end it "fetches cross-project URL references" do - message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)} and #{reference}" + message = "Closes #{urls.project_issue_url(issue2.project, issue2)} and #{reference}" expect(subject.closed_by_message(message)) .to match_array([issue, issue2]) end it "ignores invalid cross-project URL references" do - message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)} and #{reference}" + message = "Closes https://google.com#{urls.project_issue_path(issue2.project, issue2)} and #{reference}" expect(subject.closed_by_message(message)) .to match_array([issue]) diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb index 8b041ac69b1..66c016d14b3 100644 --- a/spec/lib/gitlab/git/blame_spec.rb +++ b/spec/lib/gitlab/git/blame_spec.rb @@ -20,6 +20,7 @@ describe Gitlab::Git::Blame, seed_helper: true do expect(data.size).to eq(95) expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) expect(data.first[:line]).to eq("# Contribute to GitLab") + expect(data.first[:line]).to be_utf8 end end @@ -40,6 +41,7 @@ describe Gitlab::Git::Blame, seed_helper: true do expect(data.size).to eq(1) expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) expect(data.first[:line]).to eq("Ä ü") + expect(data.first[:line]).to be_utf8 end end @@ -61,6 +63,7 @@ describe Gitlab::Git::Blame, seed_helper: true do expect(data.size).to eq(1) expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) expect(data.first[:line]).to eq(" ") + expect(data.first[:line]).to be_utf8 end end end diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb index 9dba4397e79..d1d7ed1d02a 100644 --- a/spec/lib/gitlab/git/branch_spec.rb +++ b/spec/lib/gitlab/git/branch_spec.rb @@ -48,7 +48,7 @@ describe Gitlab::Git::Branch, seed_helper: true do expect(Gitlab::Git::Commit).to receive(:decorate) .with(hash_including(attributes)).and_call_original - expect(branch.dereferenced_target.message.encoding).to be(Encoding::UTF_8) + expect(branch.dereferenced_target.message).to be_utf8 end end diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb index d50ccb0df30..d97e85364c2 100644 --- a/spec/lib/gitlab/git/diff_spec.rb +++ b/spec/lib/gitlab/git/diff_spec.rb @@ -180,7 +180,7 @@ EOT let(:raw_patch) { @raw_diff_hash[:diff].encode(Encoding::ASCII_8BIT) } it 'encodes diff patch to UTF-8' do - expect(diff.diff.encoding).to eq(Encoding::UTF_8) + expect(diff.diff).to be_utf8 end end end diff --git a/spec/lib/gitlab/git/hook_spec.rb b/spec/lib/gitlab/git/hook_spec.rb index 3f279c21865..73518656bde 100644 --- a/spec/lib/gitlab/git/hook_spec.rb +++ b/spec/lib/gitlab/git/hook_spec.rb @@ -4,18 +4,20 @@ require 'fileutils' describe Gitlab::Git::Hook, lib: true do describe "#trigger" do let(:project) { create(:project, :repository) } + let(:repo_path) { project.repository.path } let(:user) { create(:user) } + let(:gl_id) { Gitlab::GlId.gl_id(user) } def create_hook(name) - FileUtils.mkdir_p(File.join(project.repository.path, 'hooks')) - File.open(File.join(project.repository.path, 'hooks', name), 'w', 0755) do |f| + FileUtils.mkdir_p(File.join(repo_path, 'hooks')) + File.open(File.join(repo_path, 'hooks', name), 'w', 0755) do |f| f.write('exit 0') end end def create_failing_hook(name) - FileUtils.mkdir_p(File.join(project.repository.path, 'hooks')) - File.open(File.join(project.repository.path, 'hooks', name), 'w', 0755) do |f| + FileUtils.mkdir_p(File.join(repo_path, 'hooks')) + File.open(File.join(repo_path, 'hooks', name), 'w', 0755) do |f| f.write(<<-HOOK) echo 'regular message from the hook' echo 'error message from the hook' 1>&2 @@ -27,13 +29,29 @@ describe Gitlab::Git::Hook, lib: true do ['pre-receive', 'post-receive', 'update'].each do |hook_name| context "when triggering a #{hook_name} hook" do context "when the hook is successful" do + let(:hook_path) { File.join(repo_path, 'hooks', hook_name) } + let(:gl_repository) { Gitlab::GlRepository.gl_repository(project, false) } + let(:env) do + { + 'GL_ID' => gl_id, + 'PWD' => repo_path, + 'GL_PROTOCOL' => 'web', + 'GL_REPOSITORY' => gl_repository + } + end + it "returns success with no errors" do create_hook(hook_name) - hook = Gitlab::Git::Hook.new(hook_name, project.repository.path) + hook = Gitlab::Git::Hook.new(hook_name, project) blank = Gitlab::Git::BLANK_SHA ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch' - status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref) + if hook_name != 'update' + expect(Open3).to receive(:popen3) + .with(env, hook_path, chdir: repo_path).and_call_original + end + + status, errors = hook.trigger(gl_id, blank, blank, ref) expect(status).to be true expect(errors).to be_blank end @@ -42,11 +60,11 @@ describe Gitlab::Git::Hook, lib: true do context "when the hook is unsuccessful" do it "returns failure with errors" do create_failing_hook(hook_name) - hook = Gitlab::Git::Hook.new(hook_name, project.repository.path) + hook = Gitlab::Git::Hook.new(hook_name, project) blank = Gitlab::Git::BLANK_SHA ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch' - status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref) + status, errors = hook.trigger(gl_id, blank, blank, ref) expect(status).to be false expect(errors).to eq("error message from the hook\n") end @@ -56,11 +74,11 @@ describe Gitlab::Git::Hook, lib: true do context "when the hook doesn't exist" do it "returns success with no errors" do - hook = Gitlab::Git::Hook.new('unknown_hook', project.repository.path) + hook = Gitlab::Git::Hook.new('unknown_hook', project) blank = Gitlab::Git::BLANK_SHA ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch' - status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref) + status, errors = hook.trigger(gl_id, blank, blank, ref) expect(status).to be true expect(errors).to be_nil end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 0cd458bf933..6aca181194a 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -27,34 +27,24 @@ describe Gitlab::Git::Repository, seed_helper: true do end it 'returns UTF-8' do - expect(repository.root_ref.encoding).to eq(Encoding.find('UTF-8')) + expect(repository.root_ref).to be_utf8 end - context 'with gitaly enabled' do - before do - stub_gitaly - end - - after do - Gitlab::GitalyClient.clear_stubs! - end - - it 'gets the branch name from GitalyClient' do - expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name) - repository.root_ref - end + it 'gets the branch name from GitalyClient' do + expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name) + repository.root_ref + end - it 'wraps GRPC not found' do - expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name) - .and_raise(GRPC::NotFound) - expect { repository.root_ref }.to raise_error(Gitlab::Git::Repository::NoRepository) - end + it 'wraps GRPC not found' do + expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name) + .and_raise(GRPC::NotFound) + expect { repository.root_ref }.to raise_error(Gitlab::Git::Repository::NoRepository) + end - it 'wraps GRPC exceptions' do - expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name) - .and_raise(GRPC::Unknown) - expect { repository.root_ref }.to raise_error(Gitlab::Git::CommandError) - end + it 'wraps GRPC exceptions' do + expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name) + .and_raise(GRPC::Unknown) + expect { repository.root_ref }.to raise_error(Gitlab::Git::CommandError) end end @@ -129,37 +119,27 @@ describe Gitlab::Git::Repository, seed_helper: true do end it 'returns UTF-8' do - expect(subject.first.encoding).to eq(Encoding.find('UTF-8')) + expect(subject.first).to be_utf8 end it { is_expected.to include("master") } it { is_expected.not_to include("branch-from-space") } - context 'with gitaly enabled' do - before do - stub_gitaly - end - - after do - Gitlab::GitalyClient.clear_stubs! - end - - it 'gets the branch names from GitalyClient' do - expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names) - subject - end + it 'gets the branch names from GitalyClient' do + expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names) + subject + end - it 'wraps GRPC not found' do - expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names) - .and_raise(GRPC::NotFound) - expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository) - end + it 'wraps GRPC not found' do + expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names) + .and_raise(GRPC::NotFound) + expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository) + end - it 'wraps GRPC other exceptions' do - expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names) - .and_raise(GRPC::Unknown) - expect { subject }.to raise_error(Gitlab::Git::CommandError) - end + it 'wraps GRPC other exceptions' do + expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names) + .and_raise(GRPC::Unknown) + expect { subject }.to raise_error(Gitlab::Git::CommandError) end end @@ -173,7 +153,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end it 'returns UTF-8' do - expect(subject.first.encoding).to eq(Encoding.find('UTF-8')) + expect(subject.first).to be_utf8 end describe '#last' do @@ -183,31 +163,21 @@ describe Gitlab::Git::Repository, seed_helper: true do it { is_expected.to include("v1.0.0") } it { is_expected.not_to include("v5.0.0") } - context 'with gitaly enabled' do - before do - stub_gitaly - end - - after do - Gitlab::GitalyClient.clear_stubs! - end - - it 'gets the tag names from GitalyClient' do - expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names) - subject - end + it 'gets the tag names from GitalyClient' do + expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names) + subject + end - it 'wraps GRPC not found' do - expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names) - .and_raise(GRPC::NotFound) - expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository) - end + it 'wraps GRPC not found' do + expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names) + .and_raise(GRPC::NotFound) + expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository) + end - it 'wraps GRPC exceptions' do - expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names) - .and_raise(GRPC::Unknown) - expect { subject }.to raise_error(Gitlab::Git::CommandError) - end + it 'wraps GRPC exceptions' do + expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names) + .and_raise(GRPC::Unknown) + expect { subject }.to raise_error(Gitlab::Git::CommandError) end end @@ -358,6 +328,38 @@ describe Gitlab::Git::Repository, seed_helper: true do end end + describe '#submodule_url_for' do + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } + let(:ref) { 'master' } + + def submodule_url(path) + repository.submodule_url_for(ref, path) + end + + it { expect(submodule_url('six')).to eq('git://github.com/randx/six.git') } + it { expect(submodule_url('nested/six')).to eq('git://github.com/randx/six.git') } + it { expect(submodule_url('deeper/nested/six')).to eq('git://github.com/randx/six.git') } + it { expect(submodule_url('invalid/path')).to eq(nil) } + + context 'uncommitted submodule dir' do + let(:ref) { 'fix-existing-submodule-dir' } + + it { expect(submodule_url('submodule-existing-dir')).to eq(nil) } + end + + context 'tags' do + let(:ref) { 'v1.2.1' } + + it { expect(submodule_url('six')).to eq('git://github.com/randx/six.git') } + end + + context 'no submodules at commit' do + let(:ref) { '6d39438' } + + it { expect(submodule_url('six')).to eq(nil) } + end + end + context '#submodules' do let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } @@ -1281,42 +1283,31 @@ describe Gitlab::Git::Repository, seed_helper: true do expect(@repo.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true) end - context 'with gitaly enabled' do - before do - stub_gitaly - end - - after do - Gitlab::GitalyClient.clear_stubs! - end - - it 'returns a Branch with UTF-8 fields' do - branches = @repo.local_branches.to_a - expect(branches.size).to be > 0 - utf_8 = Encoding.find('utf-8') - branches.each do |branch| - expect(branch.name.encoding).to eq(utf_8) - expect(branch.target.encoding).to eq(utf_8) unless branch.target.nil? - end + it 'returns a Branch with UTF-8 fields' do + branches = @repo.local_branches.to_a + expect(branches.size).to be > 0 + branches.each do |branch| + expect(branch.name).to be_utf8 + expect(branch.target).to be_utf8 unless branch.target.nil? end + end - it 'gets the branches from GitalyClient' do - expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches) - .and_return([]) - @repo.local_branches - end + it 'gets the branches from GitalyClient' do + expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches) + .and_return([]) + @repo.local_branches + end - it 'wraps GRPC not found' do - expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches) - .and_raise(GRPC::NotFound) - expect { @repo.local_branches }.to raise_error(Gitlab::Git::Repository::NoRepository) - end + it 'wraps GRPC not found' do + expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches) + .and_raise(GRPC::NotFound) + expect { @repo.local_branches }.to raise_error(Gitlab::Git::Repository::NoRepository) + end - it 'wraps GRPC exceptions' do - expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches) - .and_raise(GRPC::Unknown) - expect { @repo.local_branches }.to raise_error(Gitlab::Git::CommandError) - end + it 'wraps GRPC exceptions' do + expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches) + .and_raise(GRPC::Unknown) + expect { @repo.local_branches }.to raise_error(Gitlab::Git::CommandError) end end @@ -1395,11 +1386,4 @@ describe Gitlab::Git::Repository, seed_helper: true do sha = Rugged::Commit.create(repo, options) repo.lookup(sha) end - - def stub_gitaly - allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(true) - - stub = double(:stub) - allow(Gitaly::Ref::Stub).to receive(:new).and_return(stub) - end end diff --git a/spec/lib/gitlab/gitaly_client/ref_spec.rb b/spec/lib/gitlab/gitaly_client/ref_spec.rb index 8ad39a02b93..7c090460764 100644 --- a/spec/lib/gitlab/gitaly_client/ref_spec.rb +++ b/spec/lib/gitlab/gitaly_client/ref_spec.rb @@ -6,17 +6,6 @@ describe Gitlab::GitalyClient::Ref do let(:relative_path) { project.path_with_namespace + '.git' } let(:client) { described_class.new(project.repository) } - before do - allow(Gitlab.config.gitaly).to receive(:enabled).and_return(true) - end - - after do - # When we say `expect_any_instance_of(Gitaly::Ref::Stub)` a double is created, - # and because GitalyClient shares stubs these will get passed from example to - # example, which will cause an error, so we clean the stubs after each example. - Gitlab::GitalyClient.clear_stubs! - end - describe '#branch_names' do it 'sends a find_all_branch_names message' do expect_any_instance_of(Gitaly::Ref::Stub) @@ -82,4 +71,13 @@ describe Gitlab::GitalyClient::Ref do expect { client.local_branches(sort_by: 'invalid_sort') }.to raise_error(ArgumentError) end end + + describe '#find_ref_name', seed_helper: true do + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } + let(:client) { described_class.new(repository) } + subject { client.find_ref_name(SeedRepo::Commit::ID, 'refs/heads/master') } + + it { is_expected.to be_utf8 } + it { is_expected.to eq('refs/heads/master') } + end end diff --git a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb index 61c10d47434..b333e162909 100644 --- a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb +++ b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb @@ -97,30 +97,40 @@ describe Gitlab::HealthChecks::FsShardsCheck do }.with_indifferent_access end - it { is_expected.to all(have_attributes(labels: { shard: :default })) } + # Unsolved intermittent failure in CI https://gitlab.com/gitlab-org/gitlab-ce/issues/31128 + around(:each) do |example| # rubocop:disable RSpec/AroundBlock + times_to_try = ENV['CI'] ? 4 : 1 + example.run_with_retry retry: times_to_try + end - it { is_expected.to include(an_object_having_attributes(name: :filesystem_accessible, value: 0)) } - it { is_expected.to include(an_object_having_attributes(name: :filesystem_readable, value: 0)) } - it { is_expected.to include(an_object_having_attributes(name: :filesystem_writable, value: 0)) } + it 'provides metrics' do + expect(subject).to all(have_attributes(labels: { shard: :default })) + expect(subject).to include(an_object_having_attributes(name: :filesystem_accessible, value: 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 0)) - it { is_expected.to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) } - it { is_expected.to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) } - it { is_expected.to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) } + expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) + end end context 'storage points to directory that has both read and write rights' do before do FileUtils.chmod_R(0755, tmp_dir) end - it { is_expected.to all(have_attributes(labels: { shard: :default })) } - it { is_expected.to include(an_object_having_attributes(name: :filesystem_accessible, value: 1)) } - it { is_expected.to include(an_object_having_attributes(name: :filesystem_readable, value: 1)) } - it { is_expected.to include(an_object_having_attributes(name: :filesystem_writable, value: 1)) } + it 'provides metrics' do + expect(subject).to all(have_attributes(labels: { shard: :default })) - it { is_expected.to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) } - it { is_expected.to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) } - it { is_expected.to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) } + expect(subject).to include(an_object_having_attributes(name: :filesystem_accessible, value: 1)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 1)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 1)) + + expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) + end end end end diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb index 168a59e5139..30b6a0d8845 100644 --- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb @@ -34,7 +34,7 @@ describe Gitlab::ImportExport::RepoRestorer, services: true do it 'has the webhooks' do restorer.restore - expect(Gitlab::Git::Hook.new('post-receive', project.repository.path_to_repo)).to exist + expect(Gitlab::Git::Hook.new('post-receive', project)).to exist end end end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index fadd3ad1330..697ddf52af9 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -383,6 +383,7 @@ Project: - printing_merge_request_link_enabled - build_allow_git_fetch - last_repository_updated_at +- ci_config_path Author: - name ProjectFeature: diff --git a/spec/lib/gitlab/kubernetes_spec.rb b/spec/lib/gitlab/kubernetes_spec.rb index e8c599a95ee..34b33772578 100644 --- a/spec/lib/gitlab/kubernetes_spec.rb +++ b/spec/lib/gitlab/kubernetes_spec.rb @@ -46,4 +46,28 @@ describe Gitlab::Kubernetes do expect(filter_by_label(items, app: 'foo')).to eq(matching_items) end end + + describe '#to_kubeconfig' do + subject do + to_kubeconfig( + url: 'https://kube.domain.com', + namespace: 'NAMESPACE', + token: 'TOKEN', + ca_pem: ca_pem) + end + + context 'when CA PEM is provided' do + let(:ca_pem) { 'PEM' } + let(:path) { expand_fixture_path('config/kubeconfig.yml') } + + it { is_expected.to eq(YAML.load_file(path)) } + end + + context 'when CA PEM is not provided' do + let(:ca_pem) { nil } + let(:path) { expand_fixture_path('config/kubeconfig-without-ca.yml') } + + it { is_expected.to eq(YAML.load_file(path)) } + end + end end diff --git a/spec/lib/gitlab/metrics/connection_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/connection_rack_middleware_spec.rb new file mode 100644 index 00000000000..94251af305f --- /dev/null +++ b/spec/lib/gitlab/metrics/connection_rack_middleware_spec.rb @@ -0,0 +1,88 @@ +require 'spec_helper' + +describe Gitlab::Metrics::ConnectionRackMiddleware do + let(:app) { double('app') } + subject { described_class.new(app) } + + around do |example| + Timecop.freeze { example.run } + end + + describe '#call' do + let(:status) { 100 } + let(:env) { { 'REQUEST_METHOD' => 'GET' } } + let(:stack_result) { [status, {}, 'body'] } + + before do + allow(app).to receive(:call).and_return(stack_result) + end + + context '@app.call succeeds with 200' do + before do + allow(app).to receive(:call).and_return([200, nil, nil]) + end + + it 'increments response count with status label' do + expect(described_class).to receive_message_chain(:rack_response_count, :increment).with(include(status: 200, method: 'get')) + + subject.call(env) + end + + it 'increments requests count' do + expect(described_class).to receive_message_chain(:rack_request_count, :increment).with(method: 'get') + + subject.call(env) + end + + it 'measures execution time' do + execution_time = 10 + allow(app).to receive(:call) do |*args| + Timecop.freeze(execution_time.seconds) + end + + expect(described_class).to receive_message_chain(:rack_execution_time, :observe).with({}, execution_time) + + subject.call(env) + end + end + + context '@app.call throws exception' do + let(:rack_response_count) { double('rack_response_count') } + + before do + allow(app).to receive(:call).and_raise(StandardError) + allow(described_class).to receive(:rack_response_count).and_return(rack_response_count) + end + + it 'increments exceptions count' do + expect(described_class).to receive_message_chain(:rack_uncaught_errors_count, :increment) + + expect { subject.call(env) }.to raise_error(StandardError) + end + + it 'increments requests count' do + expect(described_class).to receive_message_chain(:rack_request_count, :increment).with(method: 'get') + + expect { subject.call(env) }.to raise_error(StandardError) + end + + it "does't increment response count" do + expect(described_class.rack_response_count).not_to receive(:increment) + + expect { subject.call(env) }.to raise_error(StandardError) + end + + it 'measures execution time' do + execution_time = 10 + allow(app).to receive(:call) do |*args| + Timecop.freeze(execution_time.seconds) + raise StandardError + end + + expect(described_class).to receive_message_chain(:rack_execution_time, :observe).with({}, execution_time) + + expect { subject.call(env) }.to raise_error(StandardError) + end + end + end +end diff --git a/spec/lib/gitlab/metrics/sampler_spec.rb b/spec/lib/gitlab/metrics/influx_sampler_spec.rb index d07ce6f81af..0bc68d64276 100644 --- a/spec/lib/gitlab/metrics/sampler_spec.rb +++ b/spec/lib/gitlab/metrics/influx_sampler_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Metrics::Sampler do +describe Gitlab::Metrics::InfluxSampler do let(:sampler) { described_class.new(5) } after do @@ -8,10 +8,10 @@ describe Gitlab::Metrics::Sampler do end describe '#start' do - it 'gathers a sample at a given interval' do - expect(sampler).to receive(:sleep).with(a_kind_of(Numeric)) - expect(sampler).to receive(:sample) - expect(sampler).to receive(:loop).and_yield + it 'runs once and gathers a sample at a given interval' do + expect(sampler).to receive(:sleep).with(a_kind_of(Numeric)).twice + expect(sampler).to receive(:sample).once + expect(sampler).to receive(:running).and_return(false, true, false) sampler.start.join end diff --git a/spec/lib/gitlab/metrics/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/unicorn_sampler_spec.rb new file mode 100644 index 00000000000..dc0d1f2e940 --- /dev/null +++ b/spec/lib/gitlab/metrics/unicorn_sampler_spec.rb @@ -0,0 +1,108 @@ +require 'spec_helper' + +describe Gitlab::Metrics::UnicornSampler do + subject { described_class.new(1.second) } + + describe '#sample' do + let(:unicorn) { double('unicorn') } + let(:raindrops) { double('raindrops') } + let(:stats) { double('stats') } + + before do + stub_const('Unicorn', unicorn) + stub_const('Raindrops::Linux', raindrops) + allow(raindrops).to receive(:unix_listener_stats).and_return({}) + allow(raindrops).to receive(:tcp_listener_stats).and_return({}) + end + + context 'unicorn listens on unix sockets' do + let(:socket_address) { '/some/sock' } + let(:sockets) { [socket_address] } + + before do + allow(unicorn).to receive(:listener_names).and_return(sockets) + end + + it 'samples socket data' do + expect(raindrops).to receive(:unix_listener_stats).with(sockets) + + subject.sample + end + + context 'stats collected' do + before do + allow(stats).to receive(:active).and_return('active') + allow(stats).to receive(:queued).and_return('queued') + allow(raindrops).to receive(:unix_listener_stats).and_return({ socket_address => stats }) + end + + it 'updates metrics type unix and with addr' do + labels = { type: 'unix', address: socket_address } + + expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active') + expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued') + + subject.sample + end + end + end + + context 'unicorn listens on tcp sockets' do + let(:tcp_socket_address) { '0.0.0.0:8080' } + let(:tcp_sockets) { [tcp_socket_address] } + + before do + allow(unicorn).to receive(:listener_names).and_return(tcp_sockets) + end + + it 'samples socket data' do + expect(raindrops).to receive(:tcp_listener_stats).with(tcp_sockets) + + subject.sample + end + + context 'stats collected' do + before do + allow(stats).to receive(:active).and_return('active') + allow(stats).to receive(:queued).and_return('queued') + allow(raindrops).to receive(:tcp_listener_stats).and_return({ tcp_socket_address => stats }) + end + + it 'updates metrics type unix and with addr' do + labels = { type: 'tcp', address: tcp_socket_address } + + expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active') + expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued') + + subject.sample + end + end + end + end + + describe '#start' do + context 'when enabled' do + before do + allow(subject).to receive(:enabled?).and_return(true) + end + + it 'creates new thread' do + expect(Thread).to receive(:new) + + subject.start + end + end + + context 'when disabled' do + before do + allow(subject).to receive(:enabled?).and_return(false) + end + + it "doesn't create new thread" do + expect(Thread).not_to receive(:new) + + subject.start + end + end + end +end diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb index 4ae216d55b0..af50ecdb2ab 100644 --- a/spec/lib/gitlab/popen_spec.rb +++ b/spec/lib/gitlab/popen_spec.rb @@ -32,6 +32,17 @@ describe 'Gitlab::Popen', lib: true, no_db: true do end end + context 'with custom options' do + let(:vars) { { 'foobar' => 123, 'PWD' => path } } + let(:options) { { chdir: path } } + + it 'calls popen3 with the provided environment variables' do + expect(Open3).to receive(:popen3).with(vars, 'ls', options) + + @output, @status = @klass.new.popen(%w(ls), path, { 'foobar' => 123 }) + end + end + context 'without a directory argument' do before do @output, @status = @klass.new.popen(%w(ls)) @@ -45,7 +56,7 @@ describe 'Gitlab::Popen', lib: true, no_db: true do before do @output, @status = @klass.new.popen(%w[cat]) { |stdin| stdin.write 'hello' } end - + it { expect(@status).to be_zero } it { expect(@output).to eq('hello') } end diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index 979f4fefcb6..51e2c3c38c6 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -14,12 +14,6 @@ describe Gitlab::Regex, lib: true do it { is_expected.not_to match('?gitlab') } end - describe '.file_name_regex' do - subject { described_class.file_name_regex } - - it { is_expected.to match('foo@bar') } - end - describe '.environment_slug_regex' do subject { described_class.environment_name_regex } diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index a97a0f8452b..5b1b8f9516a 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -4,6 +4,7 @@ require 'stringio' describe Gitlab::Shell, lib: true do let(:project) { double('Project', id: 7, path: 'diaspora') } let(:gitlab_shell) { Gitlab::Shell.new } + let(:popen_vars) { { 'GIT_TERMINAL_PROMPT' => ENV['GIT_TERMINAL_PROMPT'] } } before do allow(Project).to receive(:find).and_return(project) @@ -50,7 +51,7 @@ describe Gitlab::Shell, lib: true do describe '#add_key' do it 'removes trailing garbage' do allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) - expect(Gitlab::Utils).to receive(:system_silent).with( + expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with( [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar'] ) @@ -100,17 +101,91 @@ describe Gitlab::Shell, lib: true do allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800) end + describe '#add_repository' do + it 'returns true when the command succeeds' do + expect(Gitlab::Popen).to receive(:popen) + .with([projects_path, 'add-project', 'current/storage', 'project/path.git'], + nil, popen_vars).and_return([nil, 0]) + + expect(gitlab_shell.add_repository('current/storage', 'project/path')).to be true + end + + it 'returns false when the command fails' do + expect(Gitlab::Popen).to receive(:popen) + .with([projects_path, 'add-project', 'current/storage', 'project/path.git'], + nil, popen_vars).and_return(["error", 1]) + + expect(gitlab_shell.add_repository('current/storage', 'project/path')).to be false + end + end + + describe '#remove_repository' do + it 'returns true when the command succeeds' do + expect(Gitlab::Popen).to receive(:popen) + .with([projects_path, 'rm-project', 'current/storage', 'project/path.git'], + nil, popen_vars).and_return([nil, 0]) + + expect(gitlab_shell.remove_repository('current/storage', 'project/path')).to be true + end + + it 'returns false when the command fails' do + expect(Gitlab::Popen).to receive(:popen) + .with([projects_path, 'rm-project', 'current/storage', 'project/path.git'], + nil, popen_vars).and_return(["error", 1]) + + expect(gitlab_shell.remove_repository('current/storage', 'project/path')).to be false + end + end + + describe '#mv_repository' do + it 'returns true when the command succeeds' do + expect(Gitlab::Popen).to receive(:popen) + .with([projects_path, 'mv-project', 'current/storage', 'project/path.git', 'project/newpath.git'], + nil, popen_vars).and_return([nil, 0]) + + expect(gitlab_shell.mv_repository('current/storage', 'project/path', 'project/newpath')).to be true + end + + it 'returns false when the command fails' do + expect(Gitlab::Popen).to receive(:popen) + .with([projects_path, 'mv-project', 'current/storage', 'project/path.git', 'project/newpath.git'], + nil, popen_vars).and_return(["error", 1]) + + expect(gitlab_shell.mv_repository('current/storage', 'project/path', 'project/newpath')).to be false + end + end + + describe '#fork_repository' do + it 'returns true when the command succeeds' do + expect(Gitlab::Popen).to receive(:popen) + .with([projects_path, 'fork-project', 'current/storage', 'project/path.git', 'new/storage', 'new-namespace'], + nil, popen_vars).and_return([nil, 0]) + + expect(gitlab_shell.fork_repository('current/storage', 'project/path', 'new/storage', 'new-namespace')).to be true + end + + it 'return false when the command fails' do + expect(Gitlab::Popen).to receive(:popen) + .with([projects_path, 'fork-project', 'current/storage', 'project/path.git', 'new/storage', 'new-namespace'], + nil, popen_vars).and_return(["error", 1]) + + expect(gitlab_shell.fork_repository('current/storage', 'project/path', 'new/storage', 'new-namespace')).to be false + end + end + describe '#fetch_remote' do it 'returns true when the command succeeds' do expect(Gitlab::Popen).to receive(:popen) - .with([projects_path, 'fetch-remote', 'current/storage', 'project/path.git', 'new/storage', '800']).and_return([nil, 0]) + .with([projects_path, 'fetch-remote', 'current/storage', 'project/path.git', 'new/storage', '800'], + nil, popen_vars).and_return([nil, 0]) expect(gitlab_shell.fetch_remote('current/storage', 'project/path', 'new/storage')).to be true end it 'raises an exception when the command fails' do expect(Gitlab::Popen).to receive(:popen) - .with([projects_path, 'fetch-remote', 'current/storage', 'project/path.git', 'new/storage', '800']).and_return(["error", 1]) + .with([projects_path, 'fetch-remote', 'current/storage', 'project/path.git', 'new/storage', '800'], + nil, popen_vars).and_return(["error", 1]) expect { gitlab_shell.fetch_remote('current/storage', 'project/path', 'new/storage') }.to raise_error(Gitlab::Shell::Error, "error") end @@ -119,14 +194,16 @@ describe Gitlab::Shell, lib: true do describe '#import_repository' do it 'returns true when the command succeeds' do expect(Gitlab::Popen).to receive(:popen) - .with([projects_path, 'import-project', 'current/storage', 'project/path.git', 'https://gitlab.com/gitlab-org/gitlab-ce.git', "800"]).and_return([nil, 0]) + .with([projects_path, 'import-project', 'current/storage', 'project/path.git', 'https://gitlab.com/gitlab-org/gitlab-ce.git', "800"], + nil, popen_vars).and_return([nil, 0]) expect(gitlab_shell.import_repository('current/storage', 'project/path', 'https://gitlab.com/gitlab-org/gitlab-ce.git')).to be true end it 'raises an exception when the command fails' do expect(Gitlab::Popen).to receive(:popen) - .with([projects_path, 'import-project', 'current/storage', 'project/path.git', 'https://gitlab.com/gitlab-org/gitlab-ce.git', "800"]).and_return(["error", 1]) + .with([projects_path, 'import-project', 'current/storage', 'project/path.git', 'https://gitlab.com/gitlab-org/gitlab-ce.git', "800"], + nil, popen_vars).and_return(["error", 1]) expect { gitlab_shell.import_repository('current/storage', 'project/path', 'https://gitlab.com/gitlab-org/gitlab-ce.git') }.to raise_error(Gitlab::Shell::Error, "error") end diff --git a/spec/lib/gitlab/sql/glob_spec.rb b/spec/lib/gitlab/sql/glob_spec.rb new file mode 100644 index 00000000000..451c583310d --- /dev/null +++ b/spec/lib/gitlab/sql/glob_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe Gitlab::SQL::Glob, lib: true do + describe '.to_like' do + it 'matches * as %' do + expect(glob('apple', '*')).to be(true) + expect(glob('apple', 'app*')).to be(true) + expect(glob('apple', 'apple*')).to be(true) + expect(glob('apple', '*pple')).to be(true) + expect(glob('apple', 'ap*le')).to be(true) + + expect(glob('apple', '*a')).to be(false) + expect(glob('apple', 'app*a')).to be(false) + expect(glob('apple', 'ap*l')).to be(false) + end + + it 'matches % literally' do + expect(glob('100%', '100%')).to be(true) + + expect(glob('100%', '%')).to be(false) + end + + it 'matches _ literally' do + expect(glob('^_^', '^_^')).to be(true) + + expect(glob('^A^', '^_^')).to be(false) + end + end + + def glob(string, pattern) + match(string, subject.to_like(quote(pattern))) + end + + def match(string, pattern) + value = query("SELECT #{quote(string)} LIKE #{pattern}") + .rows.flatten.first + + case value + when 't', 1 + true + else + false + end + end + + def query(sql) + ActiveRecord::Base.connection.select_all(sql) + end + + def quote(string) + ActiveRecord::Base.connection.quote(string) + end +end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 980b24370d0..683e893968b 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -52,7 +52,7 @@ describe Notify do it 'has the correct subject and body' do aggregate_failures do is_expected.to have_referable_subject(issue) - is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue)) + is_expected.to have_body_text(project_issue_path(project, issue)) end end @@ -99,7 +99,7 @@ describe Notify do is_expected.to have_referable_subject(issue, reply: true) is_expected.to have_html_escaped_body_text(previous_assignee.name) is_expected.to have_html_escaped_body_text(assignee.name) - is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue)) + is_expected.to have_body_text(project_issue_path(project, issue)) end end end @@ -125,7 +125,7 @@ describe Notify do aggregate_failures do is_expected.to have_referable_subject(issue, reply: true) is_expected.to have_body_text('foo, bar, and baz') - is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue)) + is_expected.to have_body_text(project_issue_path(project, issue)) end end @@ -165,7 +165,7 @@ describe Notify do is_expected.to have_referable_subject(issue, reply: true) is_expected.to have_body_text(status) is_expected.to have_html_escaped_body_text(current_user.name) - is_expected.to have_body_text(namespace_project_issue_path project.namespace, project, issue) + is_expected.to have_body_text(project_issue_path project, issue) end end end @@ -185,13 +185,12 @@ describe Notify do end it 'has the correct subject and body' do - new_issue_url = namespace_project_issue_path(new_issue.project.namespace, - new_issue.project, new_issue) + new_issue_url = project_issue_path(new_issue.project, new_issue) aggregate_failures do is_expected.to have_referable_subject(issue, reply: true) is_expected.to have_body_text(new_issue_url) - is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue)) + is_expected.to have_body_text(project_issue_path(project, issue)) end end end @@ -216,7 +215,7 @@ describe Notify do it 'has the correct subject and body' do aggregate_failures do is_expected.to have_referable_subject(merge_request) - is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request)) + is_expected.to have_body_text(project_merge_request_path(project, merge_request)) is_expected.to have_body_text(merge_request.source_branch) is_expected.to have_body_text(merge_request.target_branch) end @@ -265,7 +264,7 @@ describe Notify do aggregate_failures do is_expected.to have_referable_subject(merge_request, reply: true) is_expected.to have_html_escaped_body_text(previous_assignee.name) - is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request)) + is_expected.to have_body_text(project_merge_request_path(project, merge_request)) is_expected.to have_html_escaped_body_text(assignee.name) end end @@ -291,7 +290,7 @@ describe Notify do it 'has the correct subject and body' do is_expected.to have_referable_subject(merge_request, reply: true) is_expected.to have_body_text('foo, bar, and baz') - is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request)) + is_expected.to have_body_text(project_merge_request_path(project, merge_request)) end end @@ -316,7 +315,7 @@ describe Notify do is_expected.to have_referable_subject(merge_request, reply: true) is_expected.to have_body_text(status) is_expected.to have_html_escaped_body_text(current_user.name) - is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request)) + is_expected.to have_body_text(project_merge_request_path(project, merge_request)) end end end @@ -341,7 +340,7 @@ describe Notify do aggregate_failures do is_expected.to have_referable_subject(merge_request, reply: true) is_expected.to have_body_text('merged') - is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request)) + is_expected.to have_body_text(project_merge_request_path(project, merge_request)) end end end @@ -390,7 +389,7 @@ describe Notify do is_expected.to have_subject "Request to join the #{project.name_with_namespace} project" is_expected.to have_html_escaped_body_text project.name_with_namespace - is_expected.to have_body_text namespace_project_project_members_url(project.namespace, project) + is_expected.to have_body_text project_project_members_url(project) is_expected.to have_body_text project_member.human_access end end @@ -417,7 +416,7 @@ describe Notify do is_expected.to have_subject "Request to join the #{project.name_with_namespace} project" is_expected.to have_html_escaped_body_text project.name_with_namespace - is_expected.to have_body_text namespace_project_project_members_url(project.namespace, project) + is_expected.to have_body_text project_project_members_url(project) is_expected.to have_body_text project_member.human_access end end @@ -609,7 +608,7 @@ describe Notify do describe 'on a merge request' do let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } - let(:note_on_merge_request_path) { namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: "note_#{note.id}") } + let(:note_on_merge_request_path) { project_merge_request_path(project, merge_request, anchor: "note_#{note.id}") } before do allow(note).to receive(:noteable).and_return(merge_request) @@ -634,7 +633,7 @@ describe Notify do describe 'on an issue' do let(:issue) { create(:issue, project: project) } - let(:note_on_issue_path) { namespace_project_issue_path(project.namespace, project, issue, anchor: "note_#{note.id}") } + let(:note_on_issue_path) { project_issue_path(project, issue, anchor: "note_#{note.id}") } before do allow(note).to receive(:noteable).and_return(issue) @@ -725,7 +724,7 @@ describe Notify do describe 'on a merge request' do let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:note) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project, author: note_author) } - let(:note_on_merge_request_path) { namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: "note_#{note.id}") } + let(:note_on_merge_request_path) { project_merge_request_path(project, merge_request, anchor: "note_#{note.id}") } before do allow(note).to receive(:noteable).and_return(merge_request) @@ -752,7 +751,7 @@ describe Notify do describe 'on an issue' do let(:issue) { create(:issue, project: project) } let(:note) { create(:discussion_note_on_issue, noteable: issue, project: project, author: note_author) } - let(:note_on_issue_path) { namespace_project_issue_path(project.namespace, project, issue, anchor: "note_#{note.id}") } + let(:note_on_issue_path) { project_issue_path(project, issue, anchor: "note_#{note.id}") } before do allow(note).to receive(:noteable).and_return(issue) @@ -1022,7 +1021,7 @@ describe Notify do describe 'email on push for a created branch' do let(:example_site_path) { root_path } let(:user) { create(:user) } - let(:tree_path) { namespace_project_tree_path(project.namespace, project, "empty-branch") } + let(:tree_path) { project_tree_path(project, "empty-branch") } subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/empty-branch', action: :create) } @@ -1048,7 +1047,7 @@ describe Notify do describe 'email on push for a created tag' do let(:example_site_path) { root_path } let(:user) { create(:user) } - let(:tree_path) { namespace_project_tree_path(project.namespace, project, "v1.0") } + let(:tree_path) { project_tree_path(project, "v1.0") } subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/tags/v1.0', action: :create) } @@ -1122,7 +1121,7 @@ describe Notify do let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_image_commit.id, sample_commit.id) } let(:compare) { Compare.decorate(raw_compare, project) } let(:commits) { compare.commits } - let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: Commit.new(compare.base, project), to: Commit.new(compare.head, project)) } + let(:diff_path) { project_compare_path(project, from: Commit.new(compare.base, project), to: Commit.new(compare.head, project)) } let(:send_from_committer_email) { false } let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) } @@ -1216,7 +1215,7 @@ describe Notify do let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_commit.parent_id, sample_commit.id) } let(:compare) { Compare.decorate(raw_compare, project) } let(:commits) { compare.commits } - let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) } + let(:diff_path) { project_commit_path(project, commits.first) } let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) } subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, diff_refs: diff_refs) } diff --git a/spec/migrations/rename_duplicated_variable_key_spec.rb b/spec/migrations/rename_duplicated_variable_key_spec.rb new file mode 100644 index 00000000000..11096564dfa --- /dev/null +++ b/spec/migrations/rename_duplicated_variable_key_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20170622135451_rename_duplicated_variable_key.rb') + +describe RenameDuplicatedVariableKey, :migration do + let(:variables) { table(:ci_variables) } + let(:projects) { table(:projects) } + + before do + projects.create!(id: 1) + variables.create!(id: 1, key: 'key1', project_id: 1) + variables.create!(id: 2, key: 'key2', project_id: 1) + variables.create!(id: 3, key: 'keyX', project_id: 1) + variables.create!(id: 4, key: 'keyX', project_id: 1) + variables.create!(id: 5, key: 'keyY', project_id: 1) + variables.create!(id: 6, key: 'keyX', project_id: 1) + variables.create!(id: 7, key: 'key7', project_id: 1) + variables.create!(id: 8, key: 'keyY', project_id: 1) + end + + it 'correctly remove duplicated records with smaller id' do + migrate! + + expect(variables.pluck(:id, :key)).to contain_exactly( + [1, 'key1'], + [2, 'key2'], + [3, 'keyX_3'], + [4, 'keyX_4'], + [5, 'keyY_5'], + [6, 'keyX'], + [7, 'key7'], + [8, 'keyY'] + ) + end +end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 488697f74eb..2b10791ad6d 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -998,13 +998,17 @@ describe Ci::Build, :models do describe '#ref_slug' do { - 'master' => 'master', - '1-foo' => '1-foo', - 'fix/1-foo' => 'fix-1-foo', - 'fix-1-foo' => 'fix-1-foo', - 'a' * 63 => 'a' * 63, - 'a' * 64 => 'a' * 63, - 'FOO' => 'foo' + 'master' => 'master', + '1-foo' => '1-foo', + 'fix/1-foo' => 'fix-1-foo', + 'fix-1-foo' => 'fix-1-foo', + 'a' * 63 => 'a' * 63, + 'a' * 64 => 'a' * 63, + 'FOO' => 'foo', + '-' + 'a' * 61 + '-' => 'a' * 61, + '-' + 'a' * 62 + '-' => 'a' * 62, + '-' + 'a' * 63 + '-' => 'a' * 62, + 'a' * 62 + ' ' => 'a' * 62 }.each do |ref, slug| it "transforms #{ref} to #{slug}" do build.ref = ref @@ -1179,6 +1183,7 @@ describe Ci::Build, :models do { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true }, { key: 'CI_PROJECT_URL', value: project.web_url, public: true }, { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }, + { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true }, { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true }, { key: 'CI_REGISTRY_PASSWORD', value: build.token, public: false }, { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false } @@ -1469,6 +1474,16 @@ describe Ci::Build, :models do it { is_expected.to include(deployment_variable) } end + context 'when project has custom CI config path' do + let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true } } + + before do + project.update(ci_config_path: 'custom') + end + + it { is_expected.to include(ci_config_path) } + end + context 'returns variables in valid order' do let(:build_pre_var) { { key: 'build', value: 'value' } } let(:project_pre_var) { { key: 'project', value: 'value' } } @@ -1481,9 +1496,10 @@ describe Ci::Build, :models do allow(pipeline).to receive(:predefined_variables) { [pipeline_pre_var] } allow(build).to receive(:yaml_variables) { [build_yaml_var] } - allow(project).to receive(:secret_variables_for).with(build.ref) do - [create(:ci_variable, key: 'secret', value: 'value')] - end + allow(project).to receive(:secret_variables_for) + .with(ref: 'master', environment: nil) do + [create(:ci_variable, key: 'secret', value: 'value')] + end end it do diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 55d85a6e228..ba0696fa210 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -748,6 +748,39 @@ describe Ci::Pipeline, models: true do end end + describe '#ci_yaml_file_path' do + subject { pipeline.ci_yaml_file_path } + + it 'returns the path from project' do + allow(pipeline.project).to receive(:ci_config_path) { 'custom/path' } + + is_expected.to eq('custom/path') + end + + it 'returns default when custom path is nil' do + allow(pipeline.project).to receive(:ci_config_path) { nil } + + is_expected.to eq('.gitlab-ci.yml') + end + + it 'returns default when custom path is empty' do + allow(pipeline.project).to receive(:ci_config_path) { '' } + + is_expected.to eq('.gitlab-ci.yml') + end + end + + describe '#ci_yaml_file' do + it 'reports error if the file is not found' do + allow(pipeline.project).to receive(:ci_config_path) { 'custom' } + + pipeline.ci_yaml_file + + expect(pipeline.yaml_errors) + .to eq('Failed to load CI/CD config file at custom') + end + end + describe '#detailed_status' do subject { pipeline.detailed_status(user) } diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index 329682a0771..4ffbfa6c130 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -3,8 +3,12 @@ require 'spec_helper' describe Ci::Variable, models: true do subject { build(:ci_variable) } - it { is_expected.to include_module(HasVariable) } - it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id) } + let(:secret_value) { 'secret' } + + describe 'validations' do + it { is_expected.to include_module(HasVariable) } + it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id, :environment_scope) } + end describe '.unprotected' do subject { described_class.unprotected } diff --git a/spec/models/concerns/feature_gate_spec.rb b/spec/models/concerns/feature_gate_spec.rb new file mode 100644 index 00000000000..3f601243245 --- /dev/null +++ b/spec/models/concerns/feature_gate_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe FeatureGate do + describe 'User' do + describe '#flipper_id' do + context 'when user is not persisted' do + let(:user) { build(:user) } + + it { expect(user.flipper_id).to be_nil } + end + + context 'when user is persisted' do + let(:user) { create(:user) } + + it { expect(user.flipper_id).to eq "User:#{user.id}" } + end + end + end +end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index ac9303370ab..505039c9d88 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -155,7 +155,7 @@ describe Issuable do end describe "#sort" do - let(:project) { build_stubbed(:empty_project) } + let(:project) { create(:empty_project) } context "by milestone due date" do # Correct order is: diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb index 65f05121b40..36aedd2f701 100644 --- a/spec/models/concerns/routable_spec.rb +++ b/spec/models/concerns/routable_spec.rb @@ -132,6 +132,19 @@ describe Group, 'Routable' do end end + describe '#expires_full_path_cache' do + context 'with RequestStore active', :request_store do + it 'expires the full_path cache' do + expect(group.full_path).to eq('foo') + + group.route.update(path: 'bar', name: 'bar') + group.expires_full_path_cache + + expect(group.full_path).to eq('bar') + end + end + end + describe '#full_name' do let(:group) { create(:group) } let(:nested_group) { create(:group, parent: group) } diff --git a/spec/models/concerns/sortable_spec.rb b/spec/models/concerns/sortable_spec.rb new file mode 100644 index 00000000000..d1e17c4f684 --- /dev/null +++ b/spec/models/concerns/sortable_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Sortable do + let(:relation) { Issue.all } + + describe '#where' do + it 'orders by id, descending' do + order_node = relation.where(iid: 1).order_values.first + expect(order_node).to be_a(Arel::Nodes::Descending) + expect(order_node.expr.name).to eq(:id) + end + end + + describe '#find_by' do + it 'does not order' do + expect(relation).to receive(:unscope).with(:order).and_call_original + + relation.find_by(iid: 1) + end + end +end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index b0635c6a90a..0a2cd8c2957 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -120,28 +120,17 @@ describe Environment, models: true do let(:head_commit) { project.commit } let(:commit) { project.commit.parent } - context 'Gitaly find_ref_name feature disabled' do - it 'returns deployment id for the environment' do - expect(environment.first_deployment_for(commit)).to eq deployment1 - end + it 'returns deployment id for the environment' do + expect(environment.first_deployment_for(commit)).to eq deployment1 + end - it 'return nil when no deployment is found' do - expect(environment.first_deployment_for(head_commit)).to eq nil - end + it 'return nil when no deployment is found' do + expect(environment.first_deployment_for(head_commit)).to eq nil end - # TODO: Uncomment when feature is reenabled - # context 'Gitaly find_ref_name feature enabled' do - # before do - # allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:find_ref_name).and_return(true) - # end - # - # it 'calls GitalyClient' do - # expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:find_ref_name) - # - # environment.first_deployment_for(commit) - # end - # end + it 'returns a UTF-8 ref' do + expect(environment.first_deployment_for(commit).ref).to be_utf8 + end end describe '#environment_type' do diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb index 6e8d43f988c..38fbdd2536a 100644 --- a/spec/models/forked_project_link_spec.rb +++ b/spec/models/forked_project_link_spec.rb @@ -2,53 +2,75 @@ require 'spec_helper' describe ForkedProjectLink, "add link on fork" do let(:project_from) { create(:project, :repository) } + let(:project_to) { fork_project(project_from, user) } let(:user) { create(:user) } let(:namespace) { user.namespace } before do - create(:project_member, :reporter, user: user, project: project_from) - @project_to = fork_project(project_from, user) + project_from.add_reporter(user) + end + + it 'project_from knows its forks' do + _ = project_to + + expect(project_from.forks.count).to eq(1) end it "project_to knows it is forked" do - expect(@project_to.forked?).to be_truthy + expect(project_to.forked?).to be_truthy end it "project knows who it is forked from" do - expect(@project_to.forked_from_project).to eq(project_from) + expect(project_to.forked_from_project).to eq(project_from) end -end -describe '#forked?' do - let(:forked_project_link) { build(:forked_project_link) } - let(:project_from) { create(:project, :repository) } - let(:project_to) { create(:project, forked_project_link: forked_project_link) } + context 'project_to is pending_delete' do + before do + project_to.update!(pending_delete: true) + end - before :each do - forked_project_link.forked_from_project = project_from - forked_project_link.forked_to_project = project_to - forked_project_link.save! + it { expect(project_from.forks.count).to eq(0) } end - it "project_to knows it is forked" do - expect(project_to.forked?).to be_truthy - end + context 'project_from is pending_delete' do + before do + project_from.update!(pending_delete: true) + end - it "project_from is not forked" do - expect(project_from.forked?).to be_falsey + it { expect(project_to.forked_from_project).to be_nil } end - it "project_to.destroy destroys fork_link" do - expect(forked_project_link).to receive(:destroy) - project_to.destroy + describe '#forked?' do + let(:project_to) { create(:project, forked_project_link: forked_project_link) } + let(:forked_project_link) { create(:forked_project_link) } + + before do + forked_project_link.forked_from_project = project_from + forked_project_link.forked_to_project = project_to + forked_project_link.save! + end + + it "project_to knows it is forked" do + expect(project_to.forked?).to be_truthy + end + + it "project_from is not forked" do + expect(project_from.forked?).to be_falsey + end + + it "project_to.destroy destroys fork_link" do + project_to.destroy + + expect(ForkedProjectLink.exists?(id: forked_project_link.id)).to eq(false) + end end -end -def fork_project(from_project, user) - shell = double('gitlab_shell', fork_repository: true) + def fork_project(from_project, user) + service = Projects::ForkService.new(from_project, user) + shell = double('gitlab_shell', fork_repository: true) - service = Projects::ForkService.new(from_project, user) - allow(service).to receive(:gitlab_shell).and_return(shell) + allow(service).to receive(:gitlab_shell).and_return(shell) - service.execute + service.execute + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index bb5273074a2..d91f1f1a11c 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -10,7 +10,7 @@ describe MergeRequest, models: true do it { is_expected.to belong_to(:source_project).class_name('Project') } it { is_expected.to belong_to(:merge_user).class_name("User") } it { is_expected.to belong_to(:assignee) } - it { is_expected.to have_many(:merge_request_diffs).dependent(:destroy) } + it { is_expected.to have_many(:merge_request_diffs) } end describe 'modules' do @@ -105,6 +105,22 @@ describe MergeRequest, models: true do end end + describe '#assignee_ids' do + it 'returns an array of the assigned user id' do + subject.assignee_id = 123 + + expect(subject.assignee_ids).to eq([123]) + end + end + + describe '#assignee_ids=' do + it 'sets assignee_id to the last id in the array' do + subject.assignee_ids = [123, 456] + + expect(subject.assignee_id).to eq(456) + end + end + describe '#assignee_or_author?' do let(:user) { create(:user) } diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index d4f898f6d9f..62c4ea01ce1 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -342,6 +342,17 @@ describe Namespace, models: true do end end + describe '#soft_delete_without_removing_associations' do + let(:project1) { create(:project_empty_repo, namespace: namespace) } + + it 'updates the deleted_at timestamp but preserves projects' do + namespace.soft_delete_without_removing_associations + + expect(Project.all).to include(project1) + expect(namespace.deleted_at).not_to be_nil + end + end + describe '#user_ids_for_project_authorizations' do it 'returns the user IDs for which to refresh authorizations' do expect(namespace.user_ids_for_project_authorizations) diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb index 4161b9158b1..d68d8b719cd 100644 --- a/spec/models/project_group_link_spec.rb +++ b/spec/models/project_group_link_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe ProjectGroupLink do describe "Associations" do - it { should belong_to(:group) } - it { should belong_to(:project) } + it { is_expected.to belong_to(:group) } + it { is_expected.to belong_to(:project) } end describe "Validation" do @@ -12,10 +12,10 @@ describe ProjectGroupLink do let(:project) { create(:project, group: group) } let!(:project_group_link) { create(:project_group_link, project: project) } - it { should validate_presence_of(:project_id) } - it { should validate_uniqueness_of(:group_id).scoped_to(:project_id).with_message(/already shared/) } - it { should validate_presence_of(:group) } - it { should validate_presence_of(:group_access) } + it { is_expected.to validate_presence_of(:project_id) } + it { is_expected.to validate_uniqueness_of(:group_id).scoped_to(:project_id).with_message(/already shared/) } + it { is_expected.to validate_presence_of(:group) } + it { is_expected.to validate_presence_of(:group_access) } it "doesn't allow a project to be shared with the group it is in" do project_group_link.group = group diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb index 291fc645a1c..ef10df9e092 100644 --- a/spec/models/project_services/external_wiki_service_spec.rb +++ b/spec/models/project_services/external_wiki_service_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe ExternalWikiService, models: true do include ExternalWikiHelper describe "Associations" do - it { should belong_to :project } - it { should have_one :service_hook } + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } end describe 'Validations' do diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index c86f56c55eb..4a1de76f099 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -64,12 +64,12 @@ describe JiraService, models: true do end end - describe '#reference_pattern' do + describe '.reference_pattern' do it_behaves_like 'allows project key on reference pattern' it 'does not allow # on the code' do - expect(subject.reference_pattern.match('#123')).to be_nil - expect(subject.reference_pattern.match('1#23#12')).to be_nil + expect(described_class.reference_pattern.match('#123')).to be_nil + expect(described_class.reference_pattern.match('1#23#12')).to be_nil end end diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index 858ad595dbf..5ba523a478a 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -129,7 +129,7 @@ describe KubernetesService, models: true, caching: true do it "returns the default namespace" do is_expected.to eq(service.send(:default_namespace)) end - + context 'when namespace is specified' do before do service.namespace = 'my-namespace' @@ -201,6 +201,22 @@ describe KubernetesService, models: true, caching: true do end describe '#predefined_variables' do + let(:kubeconfig) do + config = + YAML.load(File.read(expand_fixture_path('config/kubeconfig.yml'))) + + config.dig('users', 0, 'user')['token'] = + 'token' + + config.dig('clusters', 0, 'cluster')['certificate-authority-data'] = + Base64.encode64('CA PEM DATA') + + config.dig('contexts', 0, 'context')['namespace'] = + namespace + + YAML.dump(config) + end + before do subject.api_url = 'https://kube.domain.com' subject.token = 'token' @@ -208,32 +224,34 @@ describe KubernetesService, models: true, caching: true do subject.project = project end - context 'namespace is provided' do - before do - subject.namespace = 'my-project' - end - + shared_examples 'setting variables' do it 'sets the variables' do expect(subject.predefined_variables).to include( { key: 'KUBE_URL', value: 'https://kube.domain.com', public: true }, { key: 'KUBE_TOKEN', value: 'token', public: false }, - { key: 'KUBE_NAMESPACE', value: 'my-project', public: true }, + { key: 'KUBE_NAMESPACE', value: namespace, public: true }, + { key: 'KUBECONFIG', value: kubeconfig, public: false, file: true }, { key: 'KUBE_CA_PEM', value: 'CA PEM DATA', public: true }, { key: 'KUBE_CA_PEM_FILE', value: 'CA PEM DATA', public: true, file: true } ) end end - context 'no namespace provided' do - it 'sets the variables' do - expect(subject.predefined_variables).to include( - { key: 'KUBE_URL', value: 'https://kube.domain.com', public: true }, - { key: 'KUBE_TOKEN', value: 'token', public: false }, - { key: 'KUBE_CA_PEM', value: 'CA PEM DATA', public: true }, - { key: 'KUBE_CA_PEM_FILE', value: 'CA PEM DATA', public: true, file: true } - ) + context 'namespace is provided' do + let(:namespace) { 'my-project' } + + before do + subject.namespace = namespace end + it_behaves_like 'setting variables' + end + + context 'no namespace provided' do + let(:namespace) { subject.actual_namespace } + + it_behaves_like 'setting variables' + it 'sets the KUBE_NAMESPACE' do kube_namespace = subject.predefined_variables.find { |h| h[:key] == 'KUBE_NAMESPACE' } diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb index 6631d9040b1..441b3f896ca 100644 --- a/spec/models/project_services/redmine_service_spec.rb +++ b/spec/models/project_services/redmine_service_spec.rb @@ -31,11 +31,11 @@ describe RedmineService, models: true do end end - describe '#reference_pattern' do + describe '.reference_pattern' do it_behaves_like 'allows project key on reference pattern' it 'does allow # on the reference' do - expect(subject.reference_pattern.match('#123')[:issue]).to eq('123') + expect(described_class.reference_pattern.match('#123')[:issue]).to eq('123') end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 1390848ff4a..99bfab70088 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -7,50 +7,50 @@ describe Project, models: true do it { is_expected.to belong_to(:creator).class_name('User') } it { is_expected.to have_many(:users) } it { is_expected.to have_many(:services) } - it { is_expected.to have_many(:events).dependent(:destroy) } - it { is_expected.to have_many(:merge_requests).dependent(:destroy) } - it { is_expected.to have_many(:issues).dependent(:destroy) } - it { is_expected.to have_many(:milestones).dependent(:destroy) } - it { is_expected.to have_many(:project_members).dependent(:destroy) } + it { is_expected.to have_many(:events) } + it { is_expected.to have_many(:merge_requests) } + it { is_expected.to have_many(:issues) } + it { is_expected.to have_many(:milestones) } + it { is_expected.to have_many(:project_members).dependent(:delete_all) } it { is_expected.to have_many(:users).through(:project_members) } - it { is_expected.to have_many(:requesters).dependent(:destroy) } - it { is_expected.to have_many(:notes).dependent(:destroy) } - it { is_expected.to have_many(:snippets).class_name('ProjectSnippet').dependent(:destroy) } - it { is_expected.to have_many(:deploy_keys_projects).dependent(:destroy) } + it { is_expected.to have_many(:requesters).dependent(:delete_all) } + it { is_expected.to have_many(:notes) } + it { is_expected.to have_many(:snippets).class_name('ProjectSnippet') } + it { is_expected.to have_many(:deploy_keys_projects) } it { is_expected.to have_many(:deploy_keys) } - it { is_expected.to have_many(:hooks).dependent(:destroy) } - it { is_expected.to have_many(:protected_branches).dependent(:destroy) } - it { is_expected.to have_one(:forked_project_link).dependent(:destroy) } - it { is_expected.to have_one(:slack_service).dependent(:destroy) } - it { is_expected.to have_one(:microsoft_teams_service).dependent(:destroy) } - it { is_expected.to have_one(:mattermost_service).dependent(:destroy) } - it { is_expected.to have_one(:pushover_service).dependent(:destroy) } - it { is_expected.to have_one(:asana_service).dependent(:destroy) } - it { is_expected.to have_many(:boards).dependent(:destroy) } - it { is_expected.to have_one(:campfire_service).dependent(:destroy) } - it { is_expected.to have_one(:drone_ci_service).dependent(:destroy) } - it { is_expected.to have_one(:emails_on_push_service).dependent(:destroy) } - it { is_expected.to have_one(:pipelines_email_service).dependent(:destroy) } - it { is_expected.to have_one(:irker_service).dependent(:destroy) } - it { is_expected.to have_one(:pivotaltracker_service).dependent(:destroy) } - it { is_expected.to have_one(:hipchat_service).dependent(:destroy) } - it { is_expected.to have_one(:flowdock_service).dependent(:destroy) } - it { is_expected.to have_one(:assembla_service).dependent(:destroy) } - it { is_expected.to have_one(:slack_slash_commands_service).dependent(:destroy) } - it { is_expected.to have_one(:mattermost_slash_commands_service).dependent(:destroy) } - it { is_expected.to have_one(:gemnasium_service).dependent(:destroy) } - it { is_expected.to have_one(:buildkite_service).dependent(:destroy) } - it { is_expected.to have_one(:bamboo_service).dependent(:destroy) } - it { is_expected.to have_one(:teamcity_service).dependent(:destroy) } - it { is_expected.to have_one(:jira_service).dependent(:destroy) } - it { is_expected.to have_one(:redmine_service).dependent(:destroy) } - it { is_expected.to have_one(:custom_issue_tracker_service).dependent(:destroy) } - it { is_expected.to have_one(:bugzilla_service).dependent(:destroy) } - it { is_expected.to have_one(:gitlab_issue_tracker_service).dependent(:destroy) } - it { is_expected.to have_one(:external_wiki_service).dependent(:destroy) } - it { is_expected.to have_one(:project_feature).dependent(:destroy) } - it { is_expected.to have_one(:statistics).class_name('ProjectStatistics').dependent(:delete) } - it { is_expected.to have_one(:import_data).class_name('ProjectImportData').dependent(:delete) } + it { is_expected.to have_many(:hooks) } + it { is_expected.to have_many(:protected_branches) } + it { is_expected.to have_one(:forked_project_link) } + it { is_expected.to have_one(:slack_service) } + it { is_expected.to have_one(:microsoft_teams_service) } + it { is_expected.to have_one(:mattermost_service) } + it { is_expected.to have_one(:pushover_service) } + it { is_expected.to have_one(:asana_service) } + it { is_expected.to have_many(:boards) } + it { is_expected.to have_one(:campfire_service) } + it { is_expected.to have_one(:drone_ci_service) } + it { is_expected.to have_one(:emails_on_push_service) } + it { is_expected.to have_one(:pipelines_email_service) } + it { is_expected.to have_one(:irker_service) } + it { is_expected.to have_one(:pivotaltracker_service) } + it { is_expected.to have_one(:hipchat_service) } + it { is_expected.to have_one(:flowdock_service) } + it { is_expected.to have_one(:assembla_service) } + it { is_expected.to have_one(:slack_slash_commands_service) } + it { is_expected.to have_one(:mattermost_slash_commands_service) } + it { is_expected.to have_one(:gemnasium_service) } + it { is_expected.to have_one(:buildkite_service) } + it { is_expected.to have_one(:bamboo_service) } + it { is_expected.to have_one(:teamcity_service) } + it { is_expected.to have_one(:jira_service) } + it { is_expected.to have_one(:redmine_service) } + it { is_expected.to have_one(:custom_issue_tracker_service) } + it { is_expected.to have_one(:bugzilla_service) } + it { is_expected.to have_one(:gitlab_issue_tracker_service) } + it { is_expected.to have_one(:external_wiki_service) } + it { is_expected.to have_one(:project_feature) } + it { is_expected.to have_one(:statistics).class_name('ProjectStatistics') } + it { is_expected.to have_one(:import_data).class_name('ProjectImportData') } it { is_expected.to have_one(:last_event).class_name('Event') } it { is_expected.to have_one(:forked_from_project).through(:forked_project_link) } it { is_expected.to have_many(:commit_statuses) } @@ -62,18 +62,18 @@ describe Project, models: true do it { is_expected.to have_many(:variables) } it { is_expected.to have_many(:triggers) } it { is_expected.to have_many(:pages_domains) } - it { is_expected.to have_many(:labels).class_name('ProjectLabel').dependent(:destroy) } - it { is_expected.to have_many(:users_star_projects).dependent(:destroy) } - it { is_expected.to have_many(:environments).dependent(:destroy) } - it { is_expected.to have_many(:deployments).dependent(:destroy) } - it { is_expected.to have_many(:todos).dependent(:destroy) } - it { is_expected.to have_many(:releases).dependent(:destroy) } - it { is_expected.to have_many(:lfs_objects_projects).dependent(:destroy) } - it { is_expected.to have_many(:project_group_links).dependent(:destroy) } - it { is_expected.to have_many(:notification_settings).dependent(:destroy) } + it { is_expected.to have_many(:labels).class_name('ProjectLabel') } + it { is_expected.to have_many(:users_star_projects) } + it { is_expected.to have_many(:environments) } + it { is_expected.to have_many(:deployments) } + it { is_expected.to have_many(:todos) } + it { is_expected.to have_many(:releases) } + it { is_expected.to have_many(:lfs_objects_projects) } + it { is_expected.to have_many(:project_group_links) } + it { is_expected.to have_many(:notification_settings).dependent(:delete_all) } it { is_expected.to have_many(:forks).through(:forked_project_links) } it { is_expected.to have_many(:uploads).dependent(:destroy) } - it { is_expected.to have_many(:pipeline_schedules).dependent(:destroy) } + it { is_expected.to have_many(:pipeline_schedules) } context 'after initialized' do it "has a project_feature" do @@ -143,6 +143,10 @@ describe Project, models: true do it { is_expected.to validate_length_of(:description).is_at_most(2000) } + it { is_expected.to validate_length_of(:ci_config_path).is_at_most(255) } + it { is_expected.to allow_value('').for(:ci_config_path) } + it { is_expected.not_to allow_value('test/../foo').for(:ci_config_path) } + it { is_expected.to validate_presence_of(:creator) } it { is_expected.to validate_presence_of(:namespace) } @@ -823,13 +827,13 @@ describe Project, models: true do let(:avatar_path) { "/#{project.full_path}/avatar" } - it { should eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" } + it { is_expected.to eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" } end context 'when git repo is empty' do let(:project) { create(:empty_project) } - it { should eq nil } + it { is_expected.to eq nil } end end @@ -1216,6 +1220,8 @@ describe Project, models: true do expect(project).to receive(:expire_caches_before_rename) + expect(project).to receive(:expires_full_path_cache) + project.rename_repo end @@ -1344,7 +1350,7 @@ describe Project, models: true do .with(project.repository_storage_path, project.path_with_namespace) .and_return(true) - expect(project).to receive(:create_repository) + expect(project).to receive(:create_repository).with(force: true) project.ensure_repository end @@ -1357,6 +1363,19 @@ describe Project, models: true do project.ensure_repository end + + it 'creates the repository if it is a fork' do + expect(project).to receive(:forked?).and_return(true) + + allow(project).to receive(:repository_exists?) + .and_return(false) + + expect(shell).to receive(:add_repository) + .with(project.repository_storage_path, project.path_with_namespace) + .and_return(true) + + project.ensure_repository + end end describe '#user_can_push_to_empty_repo?' do @@ -1489,6 +1508,28 @@ describe Project, models: true do end end + describe '#ci_config_path=' do + let(:project) { create(:empty_project) } + + it 'sets nil' do + project.update!(ci_config_path: nil) + + expect(project.ci_config_path).to be_nil + end + + it 'sets a string' do + project.update!(ci_config_path: 'foo/.gitlab_ci.yml') + + expect(project.ci_config_path).to eq('foo/.gitlab_ci.yml') + end + + it 'sets a string but removes all leading slashes and null characters' do + project.update!(ci_config_path: "///f\0oo/\0/.gitlab_ci.yml") + + expect(project.ci_config_path).to eq('foo//.gitlab_ci.yml') + end + end + describe 'Project import job' do let(:project) { create(:empty_project, import_url: generate(:url)) } @@ -1834,7 +1875,12 @@ describe Project, models: true do create(:ci_variable, :protected, value: 'protected', project: project) end - subject { project.secret_variables_for('ref') } + subject { project.secret_variables_for(ref: 'ref') } + + before do + stub_application_setting( + default_branch_protection: Gitlab::Access::PROTECTION_NONE) + end shared_examples 'ref is protected' do it 'contains all the variables' do @@ -1843,11 +1889,6 @@ describe Project, models: true do end context 'when the ref is not protected' do - before do - stub_application_setting( - default_branch_protection: Gitlab::Access::PROTECTION_NONE) - end - it 'contains only the secret variables' do is_expected.to contain_exactly(secret_variable) end @@ -2158,4 +2199,21 @@ describe Project, models: true do end end end + + describe '#remove_private_deploy_keys' do + it 'removes the private deploy keys of a project' do + project = create(:empty_project) + + private_key = create(:deploy_key, public: false) + public_key = create(:deploy_key, public: true) + + create(:deploy_keys_project, deploy_key: private_key, project: project) + create(:deploy_keys_project, deploy_key: public_key, project: project) + + project.remove_private_deploy_keys + + expect(project.deploy_keys.where(public: false).any?).to eq(false) + expect(project.deploy_keys.where(public: true).any?).to eq(true) + end + end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 3e984ec7588..af305e9b234 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -347,6 +347,17 @@ describe Repository, models: true do expect(blob.data).to eq('Changelog!') end + it 'creates new file and dir when file_path has a forward slash' do + expect do + repository.create_file(user, 'new_dir/new_file.txt', 'File!', + message: 'Create new_file with new_dir', + branch_name: 'master') + end.to change { repository.commits('master').count }.by(1) + + expect(repository.tree('master', 'new_dir').path).to eq('new_dir') + expect(repository.blob_at('master', 'new_dir/new_file.txt').data).to eq('File!') + end + it 'respects the autocrlf setting' do repository.create_file(user, 'hello.txt', "Hello,\r\nWorld", message: 'Add hello world', @@ -780,7 +791,7 @@ describe Repository, models: true do context 'when pre hooks were successful' do it 'runs without errors' do expect_any_instance_of(GitHooksService).to receive(:execute) - .with(user, project.repository.path_to_repo, old_rev, blank_sha, 'refs/heads/feature') + .with(user, project, old_rev, blank_sha, 'refs/heads/feature') expect { repository.rm_branch(user, 'feature') }.not_to raise_error end @@ -823,12 +834,7 @@ describe Repository, models: true do service = GitHooksService.new expect(GitHooksService).to receive(:new).and_return(service) expect(service).to receive(:execute) - .with( - user, - repository.path_to_repo, - old_rev, - new_rev, - 'refs/heads/feature') + .with(user, project, old_rev, new_rev, 'refs/heads/feature') .and_yield(service).and_return(true) end @@ -1474,9 +1480,9 @@ describe Repository, models: true do it 'passes commit SHA to pre-receive and update hooks,\ and tag SHA to post-receive hook' do - pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', repository.path_to_repo) - update_hook = Gitlab::Git::Hook.new('update', repository.path_to_repo) - post_receive_hook = Gitlab::Git::Hook.new('post-receive', repository.path_to_repo) + pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', project) + update_hook = Gitlab::Git::Hook.new('update', project) + post_receive_hook = Gitlab::Git::Hook.new('post-receive', project) allow(Gitlab::Git::Hook).to receive(:new) .and_return(pre_receive_hook, update_hook, post_receive_hook) diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb new file mode 100644 index 00000000000..bb0fa0c0e9c --- /dev/null +++ b/spec/policies/global_policy_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe GlobalPolicy, models: true do + let(:current_user) { create(:user) } + let(:user) { create(:user) } + + subject { GlobalPolicy.new(current_user, [user]) } + + describe "reading the list of users" do + context "for a logged in user" do + it { is_expected.to be_allowed(:read_users_list) } + end + + context "for an anonymous user" do + let(:current_user) { nil } + + context "when the public level is restricted" do + before do + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) + end + + it { is_expected.not_to be_allowed(:read_users_list) } + end + + context "when the public level is not restricted" do + before do + stub_application_setting(restricted_visibility_levels: []) + end + + it { is_expected.to be_allowed(:read_users_list) } + end + end + end +end diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb index 518e97d17a1..f05d5c7fce5 100644 --- a/spec/presenters/ci/build_presenter_spec.rb +++ b/spec/presenters/ci/build_presenter_spec.rb @@ -85,7 +85,7 @@ describe Ci::BuildPresenter do describe 'quack like a Ci::Build permission-wise' do context 'user is not allowed' do - let(:project) { build_stubbed(:empty_project, public_builds: false) } + let(:project) { create(:empty_project, public_builds: false) } it 'returns false' do expect(presenter.can?(nil, :read_build)).to be_falsy @@ -93,7 +93,7 @@ describe Ci::BuildPresenter do end context 'user is allowed' do - let(:project) { build_stubbed(:empty_project, :public) } + let(:project) { create(:empty_project, :public) } it 'returns true' do expect(presenter.can?(nil, :read_build)).to be_truthy diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index cdb60fc0d1a..8b62aa268d9 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -237,6 +237,28 @@ describe API::CommitStatuses do end end + context 'when retrying a commit status' do + before do + post api(post_url, developer), + { state: 'failed', name: 'test', ref: 'master' } + + post api(post_url, developer), + { state: 'success', name: 'test', ref: 'master' } + end + + it 'correctly posts a new commit status' do + expect(response).to have_http_status(201) + expect(json_response['sha']).to eq(commit.id) + expect(json_response['status']).to eq('success') + end + + it 'retries a commit status' do + expect(CommitStatus.count).to eq 2 + expect(CommitStatus.first).to be_retried + expect(CommitStatus.last.pipeline).to be_success + end + end + context 'when status is invalid' do before do post api(post_url, developer), state: 'invalid' diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb index f169e6661d1..1d8aaeea8f2 100644 --- a/spec/requests/api/features_spec.rb +++ b/spec/requests/api/features_spec.rb @@ -4,6 +4,13 @@ describe API::Features do let(:user) { create(:user) } let(:admin) { create(:admin) } + before do + Flipper.unregister_groups + Flipper.register(:perf_team) do |actor| + actor.respond_to?(:admin) && actor.admin? + end + end + describe 'GET /features' do let(:expected_features) do [ @@ -16,6 +23,14 @@ describe API::Features do 'name' => 'feature_2', 'state' => 'off', 'gates' => [{ 'key' => 'boolean', 'value' => false }] + }, + { + 'name' => 'feature_3', + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'groups', 'value' => ['perf_team'] } + ] } ] end @@ -23,6 +38,7 @@ describe API::Features do before do Feature.get('feature_1').enable Feature.get('feature_2').disable + Feature.get('feature_3').enable Feature.group(:perf_team) end it 'returns a 401 for anonymous users' do @@ -47,30 +63,70 @@ describe API::Features do describe 'POST /feature' do let(:feature_name) { 'my_feature' } - it 'returns a 401 for anonymous users' do - post api("/features/#{feature_name}") - expect(response).to have_http_status(401) - end + context 'when the feature does not exist' do + it 'returns a 401 for anonymous users' do + post api("/features/#{feature_name}") - it 'returns a 403 for users' do - post api("/features/#{feature_name}", user) + expect(response).to have_http_status(401) + end - expect(response).to have_http_status(403) - end + it 'returns a 403 for users' do + post api("/features/#{feature_name}", user) - it 'creates an enabled feature if passed true' do - post api("/features/#{feature_name}", admin), value: 'true' + expect(response).to have_http_status(403) + end - expect(response).to have_http_status(201) - expect(Feature.get(feature_name)).to be_enabled - end + context 'when passed value=true' do + it 'creates an enabled feature' do + post api("/features/#{feature_name}", admin), value: 'true' - it 'creates a feature with the given percentage if passed an integer' do - post api("/features/#{feature_name}", admin), value: '50' + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'on', + 'gates' => [{ 'key' => 'boolean', 'value' => true }]) + end + + it 'creates an enabled feature for the given Flipper group when passed feature_group=perf_team' do + post api("/features/#{feature_name}", admin), value: 'true', feature_group: 'perf_team' + + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'groups', 'value' => ['perf_team'] } + ]) + end + + it 'creates an enabled feature for the given user when passed user=username' do + post api("/features/#{feature_name}", admin), value: 'true', user: user.username - expect(response).to have_http_status(201) - expect(Feature.get(feature_name).percentage_of_time_value).to be(50) + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'actors', 'value' => ["User:#{user.id}"] } + ]) + end + end + + it 'creates a feature with the given percentage if passed an integer' do + post api("/features/#{feature_name}", admin), value: '50' + + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'percentage_of_time', 'value' => 50 } + ]) + end end context 'when the feature exists' do @@ -80,11 +136,83 @@ describe API::Features do feature.disable # This also persists the feature on the DB end - it 'enables the feature if passed true' do - post api("/features/#{feature_name}", admin), value: 'true' + context 'when passed value=true' do + it 'enables the feature' do + post api("/features/#{feature_name}", admin), value: 'true' - expect(response).to have_http_status(201) - expect(feature).to be_enabled + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'on', + 'gates' => [{ 'key' => 'boolean', 'value' => true }]) + end + + it 'enables the feature for the given Flipper group when passed feature_group=perf_team' do + post api("/features/#{feature_name}", admin), value: 'true', feature_group: 'perf_team' + + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'groups', 'value' => ['perf_team'] } + ]) + end + + it 'enables the feature for the given user when passed user=username' do + post api("/features/#{feature_name}", admin), value: 'true', user: user.username + + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'actors', 'value' => ["User:#{user.id}"] } + ]) + end + end + + context 'when feature is enabled and value=false is passed' do + it 'disables the feature' do + feature.enable + expect(feature).to be_enabled + + post api("/features/#{feature_name}", admin), value: 'false' + + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'off', + 'gates' => [{ 'key' => 'boolean', 'value' => false }]) + end + + it 'disables the feature for the given Flipper group when passed feature_group=perf_team' do + feature.enable(Feature.group(:perf_team)) + expect(Feature.get(feature_name).enabled?(admin)).to be_truthy + + post api("/features/#{feature_name}", admin), value: 'false', feature_group: 'perf_team' + + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'off', + 'gates' => [{ 'key' => 'boolean', 'value' => false }]) + end + + it 'disables the feature for the given user when passed user=username' do + feature.enable(user) + expect(Feature.get(feature_name).enabled?(user)).to be_truthy + + post api("/features/#{feature_name}", admin), value: 'false', user: user.username + + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'off', + 'gates' => [{ 'key' => 'boolean', 'value' => false }]) + end end context 'with a pre-existing percentage value' do @@ -96,7 +224,13 @@ describe API::Features do post api("/features/#{feature_name}", admin), value: '30' expect(response).to have_http_status(201) - expect(Feature.get(feature_name).percentage_of_time_value).to be(30) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'percentage_of_time', 'value' => 30 } + ]) end end end diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index 191c60aba31..25ec44fa036 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -14,6 +14,10 @@ describe API::Helpers do let(:request) { Rack::Request.new(env) } let(:header) { } + before do + allow_any_instance_of(self.class).to receive(:options).and_return({}) + end + def set_env(user_or_token, identifier) clear_env clear_param @@ -167,7 +171,6 @@ describe API::Helpers do it "returns nil for a token without the appropriate scope" do personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user']) env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token - allow_access_with_scope('write_user') expect(current_user).to be_nil end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 14dec3d45b1..ee25bd1deb1 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -347,7 +347,8 @@ describe API::Projects do wiki_enabled: false, only_allow_merge_if_pipeline_succeeds: false, request_access_enabled: true, - only_allow_merge_if_all_discussions_are_resolved: false + only_allow_merge_if_all_discussions_are_resolved: false, + ci_config_path: 'a/custom/path' }) post api('/projects', user), project @@ -475,6 +476,26 @@ describe API::Projects do end end + describe 'GET /users/:user_id/projects/' do + let!(:public_project) { create(:empty_project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) } + + it 'returns error when user not found' do + get api('/users/9999/projects/') + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 User Not Found') + end + + it 'returns projects filtered by user' do + get api("/users/#{user4.id}/projects/", user) + + expect(response).to have_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id) + end + end + describe 'POST /projects/user/:id' do before do expect(project).to be_persisted @@ -653,6 +674,7 @@ describe API::Projects do expect(json_response['star_count']).to be_present expect(json_response['forks_count']).to be_present expect(json_response['public_jobs']).to be_present + expect(json_response['ci_config_path']).to be_nil 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) diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 339a57a1f20..ca5d98c78ef 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -351,7 +351,8 @@ describe API::Runner do let(:expected_cache) do [{ 'key' => 'cache_key', 'untracked' => false, - 'paths' => ['vendor/*'] }] + 'paths' => ['vendor/*'], + 'policy' => 'pull-push' }] end it 'picks a job' do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index c0174b304c8..70b94a09e6b 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -13,9 +13,40 @@ describe API::Users do describe 'GET /users' do context "when unauthenticated" do - it "returns authentication error" do + it "returns authorization error when the `username` parameter is not passed" do get api("/users") - expect(response).to have_http_status(401) + + expect(response).to have_http_status(403) + end + + it "returns the user when a valid `username` parameter is passed" do + user = create(:user) + + get api("/users"), username: user.username + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(1) + expect(json_response[0]['id']).to eq(user.id) + expect(json_response[0]['username']).to eq(user.username) + end + + it "returns authorization error when the `username` parameter refers to an inaccessible user" do + user = create(:user) + + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) + + get api("/users"), username: user.username + + expect(response).to have_http_status(403) + end + + it "returns an empty response when an invalid `username` parameter is passed" do + get api("/users"), username: 'invalid' + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(0) end end @@ -138,6 +169,7 @@ describe API::Users do describe "GET /users/:id" do it "returns a user by id" do get api("/users/#{user.id}", user) + expect(response).to have_http_status(200) expect(json_response['username']).to eq(user.username) end @@ -148,9 +180,22 @@ describe API::Users do expect(json_response['is_admin']).to be_nil end - it "returns a 401 if unauthenticated" do - get api("/users/9998") - expect(response).to have_http_status(401) + context 'for an anonymous user' do + it "returns a user by id" do + get api("/users/#{user.id}") + + expect(response).to have_http_status(200) + expect(json_response['username']).to eq(user.username) + end + + it "returns a 404 if the target user is present but inaccessible" do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(nil, :read_user, user).and_return(false) + + get api("/users/#{user.id}") + + expect(response).to have_http_status(404) + end end it "returns a 404 error if user id not found" do @@ -345,6 +390,14 @@ describe API::Users do expect(json_response['identities'].first['provider']).to eq('github') end end + + context "scopes" do + let(:user) { admin } + let(:path) { '/users' } + let(:api_call) { method(:api) } + + include_examples 'does not allow the "read_user" scope' + end end describe "GET /users/sign_up" do @@ -842,6 +895,13 @@ describe API::Users do expect(response).to match_response_schema('public_api/v4/user/public') expect(json_response['id']).to eq(user.id) end + + context "scopes" do + let(:path) { "/user" } + let(:api_call) { method(:api) } + + include_examples 'allows the "read_user" scope' + end end context 'with admin' do @@ -911,6 +971,13 @@ describe API::Users do expect(json_response).to be_an Array expect(json_response.first["title"]).to eq(key.title) end + + context "scopes" do + let(:path) { "/user/keys" } + let(:api_call) { method(:api) } + + include_examples 'allows the "read_user" scope' + end end end @@ -944,6 +1011,13 @@ describe API::Users do expect(response).to have_http_status(404) end + + context "scopes" do + let(:path) { "/user/keys/#{key.id}" } + let(:api_call) { method(:api) } + + include_examples 'allows the "read_user" scope' + end end describe "POST /user/keys" do @@ -1033,6 +1107,13 @@ describe API::Users do expect(json_response).to be_an Array expect(json_response.first["email"]).to eq(email.email) end + + context "scopes" do + let(:path) { "/user/emails" } + let(:api_call) { method(:api) } + + include_examples 'allows the "read_user" scope' + end end end @@ -1065,6 +1146,13 @@ describe API::Users do expect(response).to have_http_status(404) end + + context "scopes" do + let(:path) { "/user/emails/#{email.id}" } + let(:api_call) { method(:api) } + + include_examples 'allows the "read_user" scope' + end end describe "POST /user/emails" do diff --git a/spec/requests/api/v3/users_spec.rb b/spec/requests/api/v3/users_spec.rb index 6d7401f9764..de7499a4e43 100644 --- a/spec/requests/api/v3/users_spec.rb +++ b/spec/requests/api/v3/users_spec.rb @@ -67,6 +67,19 @@ describe API::V3::Users do expect(json_response.first['title']).to eq(key.title) end end + + context "scopes" do + let(:user) { admin } + let(:path) { "/users/#{user.id}/keys" } + let(:api_call) { method(:v3_api) } + + before do + user.keys << key + user.save + end + + include_examples 'allows the "read_user" scope' + end end describe 'GET /user/:id/emails' do @@ -287,7 +300,7 @@ describe API::V3::Users do end it 'returns a 404 error if not found' do - get v3_api('/users/42/events', user) + get v3_api('/users/420/events', user) expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 User Not Found') @@ -312,5 +325,13 @@ describe API::V3::Users do expect(json_response['is_admin']).to be_nil end + + context "scopes" do + let(:user) { admin } + let(:path) { '/users' } + let(:api_call) { method(:v3_api) } + + include_examples 'does not allow the "read_user" scope' + end end end diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb index d4d3c9478a0..e78d2cfdb33 100644 --- a/spec/requests/projects/cycle_analytics_events_spec.rb +++ b/spec/requests/projects/cycle_analytics_events_spec.rb @@ -21,7 +21,7 @@ describe 'cycle analytics events', api: true do end it 'lists the issue events' do - get namespace_project_cycle_analytics_issue_path(project.namespace, project, format: :json) + get project_cycle_analytics_issue_path(project, format: :json) first_issue_iid = project.issues.sort(:created_desc).pluck(:iid).first.to_s @@ -30,7 +30,7 @@ describe 'cycle analytics events', api: true do end it 'lists the plan events' do - get namespace_project_cycle_analytics_plan_path(project.namespace, project, format: :json) + get project_cycle_analytics_plan_path(project, format: :json) first_mr_short_sha = project.merge_requests.sort(:created_asc).first.commits.first.short_id @@ -39,7 +39,7 @@ describe 'cycle analytics events', api: true do end it 'lists the code events' do - get namespace_project_cycle_analytics_code_path(project.namespace, project, format: :json) + get project_cycle_analytics_code_path(project, format: :json) expect(json_response['events']).not_to be_empty @@ -49,14 +49,14 @@ describe 'cycle analytics events', api: true do end it 'lists the test events' do - get namespace_project_cycle_analytics_test_path(project.namespace, project, format: :json) + get project_cycle_analytics_test_path(project, format: :json) expect(json_response['events']).not_to be_empty expect(json_response['events'].first['date']).not_to be_empty end it 'lists the review events' do - get namespace_project_cycle_analytics_review_path(project.namespace, project, format: :json) + get project_cycle_analytics_review_path(project, format: :json) first_mr_iid = project.merge_requests.sort(:created_desc).pluck(:iid).first.to_s @@ -65,14 +65,14 @@ describe 'cycle analytics events', api: true do end it 'lists the staging events' do - get namespace_project_cycle_analytics_staging_path(project.namespace, project, format: :json) + get project_cycle_analytics_staging_path(project, format: :json) expect(json_response['events']).not_to be_empty expect(json_response['events'].first['date']).not_to be_empty end it 'lists the production events' do - get namespace_project_cycle_analytics_production_path(project.namespace, project, format: :json) + get project_cycle_analytics_production_path(project, format: :json) first_issue_iid = project.issues.sort(:created_desc).pluck(:iid).first.to_s @@ -84,7 +84,7 @@ describe 'cycle analytics events', api: true do it 'lists the test events' do branch = project.merge_requests.first.source_branch - get namespace_project_cycle_analytics_test_path(project.namespace, project, format: :json, branch: branch) + get project_cycle_analytics_test_path(project, format: :json, branch: branch) expect(json_response['events']).not_to be_empty expect(json_response['events'].first['date']).not_to be_empty @@ -97,19 +97,19 @@ describe 'cycle analytics events', api: true do end it 'does not list the test events' do - get namespace_project_cycle_analytics_test_path(project.namespace, project, format: :json) + get project_cycle_analytics_test_path(project, format: :json) expect(response).to have_http_status(:not_found) end it 'does not list the staging events' do - get namespace_project_cycle_analytics_staging_path(project.namespace, project, format: :json) + get project_cycle_analytics_staging_path(project, format: :json) expect(response).to have_http_status(:not_found) end it 'lists the issue events' do - get namespace_project_cycle_analytics_issue_path(project.namespace, project, format: :json) + get project_cycle_analytics_issue_path(project, format: :json) expect(response).to have_http_status(:ok) end diff --git a/spec/rubocop/cop/active_record_dependent_spec.rb b/spec/rubocop/cop/active_record_dependent_spec.rb new file mode 100644 index 00000000000..599a032bfc5 --- /dev/null +++ b/spec/rubocop/cop/active_record_dependent_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' +require 'rubocop' +require 'rubocop/rspec/support' +require_relative '../../../rubocop/cop/active_record_dependent' + +describe RuboCop::Cop::ActiveRecordDependent do + include CopHelper + + subject(:cop) { described_class.new } + + context 'inside the app/models directory' do + it 'registers an offense when dependent: is used' do + allow(cop).to receive(:in_model?).and_return(true) + + inspect_source(cop, 'belongs_to :foo, dependent: :destroy') + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([1]) + end + end + end + + context 'outside the app/models directory' do + it 'does nothing' do + allow(cop).to receive(:in_model?).and_return(false) + + inspect_source(cop, 'belongs_to :foo, dependent: :destroy') + + expect(cop.offenses).to be_empty + end + end +end diff --git a/spec/rubocop/cop/activerecord_serialize_spec.rb b/spec/rubocop/cop/active_record_serialize_spec.rb index 5bd7e5fa926..b94b25cecd0 100644 --- a/spec/rubocop/cop/activerecord_serialize_spec.rb +++ b/spec/rubocop/cop/active_record_serialize_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' require 'rubocop' require 'rubocop/rspec/support' -require_relative '../../../rubocop/cop/activerecord_serialize' +require_relative '../../../rubocop/cop/active_record_serialize' -describe RuboCop::Cop::ActiverecordSerialize do +describe RuboCop::Cop::ActiveRecordSerialize do include CopHelper subject(:cop) { described_class.new } diff --git a/spec/serializers/deploy_key_entity_spec.rb b/spec/serializers/deploy_key_entity_spec.rb index ed89fccc3d0..9620f9665cf 100644 --- a/spec/serializers/deploy_key_entity_spec.rb +++ b/spec/serializers/deploy_key_entity_spec.rb @@ -29,7 +29,7 @@ describe DeployKeyEntity do { id: project.id, name: project.name, - full_path: namespace_project_path(project.namespace, project), + full_path: project_path(project), full_name: project.full_name } ] diff --git a/spec/services/access_token_validation_service_spec.rb b/spec/services/access_token_validation_service_spec.rb index 87f093ee8ce..11225fad18a 100644 --- a/spec/services/access_token_validation_service_spec.rb +++ b/spec/services/access_token_validation_service_spec.rb @@ -2,40 +2,71 @@ require 'spec_helper' describe AccessTokenValidationService, services: true do describe ".include_any_scope?" do + let(:request) { double("request") } + it "returns true if the required scope is present in the token's scopes" do token = double("token", scopes: [:api, :read_user]) + scopes = [:api] - expect(described_class.new(token).include_any_scope?([:api])).to be(true) + expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true) end it "returns true if more than one of the required scopes is present in the token's scopes" do token = double("token", scopes: [:api, :read_user, :other_scope]) + scopes = [:api, :other_scope] - expect(described_class.new(token).include_any_scope?([:api, :other_scope])).to be(true) + expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true) end it "returns true if the list of required scopes is an exact match for the token's scopes" do token = double("token", scopes: [:api, :read_user, :other_scope]) + scopes = [:api, :read_user, :other_scope] - expect(described_class.new(token).include_any_scope?([:api, :read_user, :other_scope])).to be(true) + expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true) end it "returns true if the list of required scopes contains all of the token's scopes, in addition to others" do token = double("token", scopes: [:api, :read_user]) + scopes = [:api, :read_user, :other_scope] - expect(described_class.new(token).include_any_scope?([:api, :read_user, :other_scope])).to be(true) + expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true) end it 'returns true if the list of required scopes is blank' do token = double("token", scopes: []) + scopes = [] - expect(described_class.new(token).include_any_scope?([])).to be(true) + expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true) end it "returns false if there are no scopes in common between the required scopes and the token scopes" do token = double("token", scopes: [:api, :read_user]) + scopes = [:other_scope] + + expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(false) + end + + context "conditions" do + it "ignores any scopes whose `if` condition returns false" do + token = double("token", scopes: [:api, :read_user]) + scopes = [API::Scope.new(:api, if: ->(_) { false })] + + expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(false) + end + + it "does not ignore scopes whose `if` condition is not set" do + token = double("token", scopes: [:api, :read_user]) + scopes = [API::Scope.new(:api, if: ->(_) { false }), :read_user] + + expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true) + end + + it "does not ignore scopes whose `if` condition returns true" do + token = double("token", scopes: [:api, :read_user]) + scopes = [API::Scope.new(:api, if: ->(_) { true }), API::Scope.new(:read_user, if: ->(_) { false })] - expect(described_class.new(token).include_any_scope?([:other_scope])).to be(false) + expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true) + end end end end diff --git a/spec/services/delete_merged_branches_service_spec.rb b/spec/services/delete_merged_branches_service_spec.rb index cae74df9c90..fe21ca0b3cb 100644 --- a/spec/services/delete_merged_branches_service_spec.rb +++ b/spec/services/delete_merged_branches_service_spec.rb @@ -24,6 +24,14 @@ describe DeleteMergedBranchesService, services: true do expect(project.repository.branch_names).to include('master') end + it 'keeps protected branches' do + create(:protected_branch, project: project, name: 'improve/awesome') + + service.execute + + expect(project.repository.branch_names).to include('improve/awesome') + end + context 'user without rights' do let(:user) { create(:user) } diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb index ac7ccfbaab0..213678c27f5 100644 --- a/spec/services/git_hooks_service_spec.rb +++ b/spec/services/git_hooks_service_spec.rb @@ -12,7 +12,6 @@ describe GitHooksService, services: true do @oldrev = sample_commit.parent_id @newrev = sample_commit.id @ref = 'refs/heads/feature' - @repo_path = project.repository.path_to_repo end describe '#execute' do @@ -21,7 +20,7 @@ describe GitHooksService, services: true do hook = double(trigger: [true, nil]) expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook) - service.execute(user, @repo_path, @blankrev, @newrev, @ref) { } + service.execute(user, project, @blankrev, @newrev, @ref) { } end end @@ -31,7 +30,7 @@ describe GitHooksService, services: true do expect(service).not_to receive(:run_hook).with('post-receive') expect do - service.execute(user, @repo_path, @blankrev, @newrev, @ref) + service.execute(user, project, @blankrev, @newrev, @ref) end.to raise_error(GitHooksService::PreReceiveError) end end @@ -43,7 +42,7 @@ describe GitHooksService, services: true do expect(service).not_to receive(:run_hook).with('post-receive') expect do - service.execute(user, @repo_path, @blankrev, @newrev, @ref) + service.execute(user, project, @blankrev, @newrev, @ref) end.to raise_error(GitHooksService::PreReceiveError) end end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index ca827fc0f39..8e8816870e1 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -401,18 +401,6 @@ describe GitPushService, services: true do expect(SystemNoteService).not_to receive(:cross_reference) execute_service(project, commit_author, @oldrev, @newrev, @ref ) end - - it "doesn't close issues when external issue tracker is in use" do - allow_any_instance_of(Project).to receive(:default_issues_tracker?) - .and_return(false) - external_issue_tracker = double(title: 'My Tracker', issue_path: issue.iid, reference_pattern: project.issue_reference_pattern) - allow_any_instance_of(Project).to receive(:external_issue_tracker).and_return(external_issue_tracker) - - # The push still shouldn't create cross-reference notes. - expect do - execute_service(project, commit_author, @oldrev, @newrev, 'refs/heads/hurf' ) - end.not_to change { Note.where(project_id: project.id, system: true).count } - end end context "to non-default branches" do diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb index a37257d1bf4..d59b37bee36 100644 --- a/spec/services/groups/destroy_service_spec.rb +++ b/spec/services/groups/destroy_service_spec.rb @@ -15,6 +15,14 @@ describe Groups::DestroyService, services: true do group.add_user(user, Gitlab::Access::OWNER) end + def destroy_group(group, user, async) + if async + Groups::DestroyService.new(group, user).async_execute + else + Groups::DestroyService.new(group, user).execute + end + end + shared_examples 'group destruction' do |async| context 'database records' do before do @@ -30,30 +38,14 @@ describe Groups::DestroyService, services: true do context 'file system' do context 'Sidekiq inline' do before do - # Run sidekiq immediatly to check that renamed dir will be removed + # Run sidekiq immediately to check that renamed dir will be removed Sidekiq::Testing.inline! { destroy_group(group, user, async) } end - it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey } - it { expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey } - end - - context 'Sidekiq fake' do - before do - # Don't run sidekiq to check if renamed repository exists - Sidekiq::Testing.fake! { destroy_group(group, user, async) } + it 'verifies that paths have been deleted' do + expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey + expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey end - - it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey } - it { expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_truthy } - end - end - - def destroy_group(group, user, async) - if async - Groups::DestroyService.new(group, user).async_execute - else - Groups::DestroyService.new(group, user).execute end end end @@ -61,6 +53,26 @@ describe Groups::DestroyService, services: true do describe 'asynchronous delete' do it_behaves_like 'group destruction', true + context 'Sidekiq fake' do + before do + # Don't run Sidekiq to verify that group and projects are not actually destroyed + Sidekiq::Testing.fake! { destroy_group(group, user, true) } + end + + after do + # Clean up stale directories + gitlab_shell.rm_namespace(project.repository_storage_path, group.path) + gitlab_shell.rm_namespace(project.repository_storage_path, remove_path) + end + + it 'verifies original paths and projects still exist' do + expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_truthy + expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey + expect(Project.unscoped.count).to eq(1) + expect(Group.unscoped.count).to eq(2) + end + end + context 'potential race conditions' do context "when the `GroupDestroyWorker` task runs immediately" do it "deletes the group" do diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index 711059208c1..19d9e4049fe 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe MergeRequests::MergeService, services: true do let(:user) { create(:user) } let(:user2) { create(:user) } - let(:merge_request) { create(:merge_request, assignee: user2) } + let(:merge_request) { create(:merge_request, :simple, author: user2, assignee: user2) } let(:project) { merge_request.project } before do @@ -133,18 +133,65 @@ describe MergeRequests::MergeService, services: true do it { expect(todo).to be_done } end - context 'remove source branch by author' do - let(:service) do - merge_request.merge_params['force_remove_source_branch'] = '1' - merge_request.save! - MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') + context 'source branch removal' do + context 'when the source branch is protected' do + let(:service) do + MergeRequests::MergeService.new(project, user, should_remove_source_branch: '1') + end + + before do + create(:protected_branch, project: project, name: merge_request.source_branch) + end + + it 'does not delete the source branch' do + expect(DeleteBranchService).not_to receive(:new) + service.execute(merge_request) + end end - it 'removes the source branch' do - expect(DeleteBranchService).to receive(:new) - .with(merge_request.source_project, merge_request.author) - .and_call_original - service.execute(merge_request) + context 'when the source branch is the default branch' do + let(:service) do + MergeRequests::MergeService.new(project, user, should_remove_source_branch: '1') + end + + before do + allow(project).to receive(:root_ref?).with(merge_request.source_branch).and_return(true) + end + + it 'does not delete the source branch' do + expect(DeleteBranchService).not_to receive(:new) + service.execute(merge_request) + end + end + + context 'when the source branch can be removed' do + context 'when MR author set the source branch to be removed' do + let(:service) do + merge_request.merge_params['force_remove_source_branch'] = '1' + merge_request.save! + MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') + end + + it 'removes the source branch using the author user' do + expect(DeleteBranchService).to receive(:new) + .with(merge_request.source_project, merge_request.author) + .and_call_original + service.execute(merge_request) + end + end + + context 'when MR merger set the source branch to be removed' do + let(:service) do + MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message', should_remove_source_branch: '1') + end + + it 'removes the source branch using the current user' do + expect(DeleteBranchService).to receive(:new) + .with(merge_request.source_project, user) + .and_call_original + service.execute(merge_request) + end + end end end diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 76c52d55ae5..441a5276c56 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -30,6 +30,12 @@ describe Projects::TransferService, services: true do transfer_project(project, user, group) end + it 'expires full_path cache' do + expect(project).to receive(:expires_full_path_cache) + + transfer_project(project, user, group) + end + it 'executes system hooks' do expect_any_instance_of(Projects::TransferService).to receive(:execute_system_hooks) diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index c9e63efbc14..35373675894 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -359,18 +359,18 @@ describe QuickActions::InterpretService, services: true do let(:content) { "/assign @#{developer.username}" } context 'Issue' do - it 'fetches assignee and populates assignee_id if content contains /assign' do + it 'fetches assignee and populates assignee_ids if content contains /assign' do _, updates = service.execute(content, issue) - expect(updates).to eq(assignee_ids: [developer.id]) + expect(updates[:assignee_ids]).to match_array([developer.id]) end end context 'Merge Request' do - it 'fetches assignee and populates assignee_id if content contains /assign' do + it 'fetches assignee and populates assignee_ids if content contains /assign' do _, updates = service.execute(content, merge_request) - expect(updates).to eq(assignee_id: developer.id) + expect(updates).to eq(assignee_ids: [developer.id]) end end end @@ -383,7 +383,7 @@ describe QuickActions::InterpretService, services: true do end context 'Issue' do - it 'fetches assignee and populates assignee_id if content contains /assign' do + it 'fetches assignee and populates assignee_ids if content contains /assign' do _, updates = service.execute(content, issue) expect(updates[:assignee_ids]).to match_array([developer.id]) @@ -391,10 +391,10 @@ describe QuickActions::InterpretService, services: true do end context 'Merge Request' do - it 'fetches assignee and populates assignee_id if content contains /assign' do + it 'fetches assignee and populates assignee_ids if content contains /assign' do _, updates = service.execute(content, merge_request) - expect(updates).to eq(assignee_id: developer.id) + expect(updates).to eq(assignee_ids: [developer.id]) end end end @@ -422,11 +422,27 @@ describe QuickActions::InterpretService, services: true do end context 'Merge Request' do - it 'populates assignee_id: nil if content contains /unassign' do - merge_request.update(assignee_id: developer.id) + it 'populates assignee_ids: [] if content contains /unassign' do + merge_request.update(assignee_ids: [developer.id]) _, updates = service.execute(content, merge_request) - expect(updates).to eq(assignee_id: nil) + expect(updates).to eq(assignee_ids: []) + end + end + end + + context 'reassign command' do + let(:content) { '/reassign' } + + context 'Issue' do + it 'reassigns user if content contains /reassign @user' do + user = create(:user) + + issue.update(assignee_ids: [developer.id]) + + _, updates = service.execute("/reassign @#{user.username}", issue) + + expect(updates).to eq(assignee_ids: [user.id]) end end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 8d3dafafab2..e35e4c1d631 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -807,7 +807,7 @@ describe SystemNoteService, services: true do body: hash_including( GlobalID: "GitLab", object: { - url: namespace_project_commit_url(project.namespace, project, commit), + url: project_commit_url(project, commit), title: "GitLab: Mentioned on commit - #{commit.title}", icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" }, status: { resolved: false } @@ -833,7 +833,7 @@ describe SystemNoteService, services: true do body: hash_including( GlobalID: "GitLab", object: { - url: namespace_project_issue_url(project.namespace, project, issue), + url: project_issue_url(project, issue), title: "GitLab: Mentioned on issue - #{issue.title}", icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" }, status: { resolved: false } @@ -859,7 +859,7 @@ describe SystemNoteService, services: true do body: hash_including( GlobalID: "GitLab", object: { - url: namespace_project_snippet_url(project.namespace, project, snippet), + url: project_snippet_url(project, snippet), title: "GitLab: Mentioned on snippet - #{snippet.title}", icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" }, status: { resolved: false } @@ -1098,7 +1098,7 @@ describe SystemNoteService, services: true do diff_id = merge_request.merge_request_diff.id line_code = change_position.line_code(project.repository) - expect(subject.note).to include(diffs_namespace_project_merge_request_url(project.namespace, project, merge_request, diff_id: diff_id, anchor: line_code)) + expect(subject.note).to include(diffs_project_merge_request_url(project, merge_request, diff_id: diff_id, anchor: line_code)) end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index fdef6fd5221..3e90a642d56 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -57,7 +57,7 @@ RSpec.configure do |config| config.include StubGitlabCalls config.include StubGitlabData config.include ApiHelpers, :api - config.include Rails.application.routes.url_helpers, type: :routing + config.include Gitlab::Routing.url_helpers, type: :routing config.include MigrationsHelpers, :migration config.infer_spec_type_from_file_location! diff --git a/spec/support/api/scopes/read_user_shared_examples.rb b/spec/support/api/scopes/read_user_shared_examples.rb new file mode 100644 index 00000000000..3bd589d64b9 --- /dev/null +++ b/spec/support/api/scopes/read_user_shared_examples.rb @@ -0,0 +1,79 @@ +shared_examples_for 'allows the "read_user" scope' do + context 'for personal access tokens' do + context 'when the requesting token has the "api" scope' do + let(:token) { create(:personal_access_token, scopes: ['api'], user: user) } + + it 'returns a "200" response' do + get api_call.call(path, user, personal_access_token: token) + + expect(response).to have_http_status(200) + end + end + + context 'when the requesting token has the "read_user" scope' do + let(:token) { create(:personal_access_token, scopes: ['read_user'], user: user) } + + it 'returns a "200" response' do + get api_call.call(path, user, personal_access_token: token) + + expect(response).to have_http_status(200) + end + end + + context 'when the requesting token does not have any required scope' do + let(:token) { create(:personal_access_token, scopes: ['read_registry'], user: user) } + + it 'returns a "401" response' do + get api_call.call(path, user, personal_access_token: token) + + expect(response).to have_http_status(401) + end + end + end + + context 'for doorkeeper (OAuth) tokens' do + let!(:application) { Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) } + + context 'when the requesting token has the "api" scope' do + let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "api" } + + it 'returns a "200" response' do + get api_call.call(path, user, oauth_access_token: token) + + expect(response).to have_http_status(200) + end + end + + context 'when the requesting token has the "read_user" scope' do + let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "read_user" } + + it 'returns a "200" response' do + get api_call.call(path, user, oauth_access_token: token) + + expect(response).to have_http_status(200) + end + end + + context 'when the requesting token does not have any required scope' do + let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "invalid" } + + it 'returns a "403" response' do + get api_call.call(path, user, oauth_access_token: token) + + expect(response).to have_http_status(403) + end + end + end +end + +shared_examples_for 'does not allow the "read_user" scope' do + context 'when the requesting token has the "read_user" scope' do + let(:token) { create(:personal_access_token, scopes: ['read_user'], user: user) } + + it 'returns a "401" response' do + post api_call.call(path, user, personal_access_token: token), attributes_for(:user, projects_limit: 3) + + expect(response).to have_http_status(401) + end + end +end diff --git a/spec/support/api_helpers.rb b/spec/support/api_helpers.rb index 35d1e1cfc7d..ac0aaa524b7 100644 --- a/spec/support/api_helpers.rb +++ b/spec/support/api_helpers.rb @@ -17,14 +17,18 @@ module ApiHelpers # => "/api/v2/issues?foo=bar&private_token=..." # # Returns the relative path to the requested API resource - def api(path, user = nil, version: API::API.version) + def api(path, user = nil, version: API::API.version, personal_access_token: nil, oauth_access_token: nil) "/api/#{version}#{path}" + # Normalize query string (path.index('?') ? '' : '?') + + if personal_access_token.present? + "&private_token=#{personal_access_token.token}" + elsif oauth_access_token.present? + "&access_token=#{oauth_access_token.token}" # Append private_token if given a User object - if user.respond_to?(:private_token) + elsif user.respond_to?(:private_token) "&private_token=#{user.private_token}" else '' @@ -32,8 +36,14 @@ module ApiHelpers end # Temporary helper method for simplifying V3 exclusive API specs - def v3_api(path, user = nil) - api(path, user, version: 'v3') + def v3_api(path, user = nil, personal_access_token: nil, oauth_access_token: nil) + api( + path, + user, + version: 'v3', + personal_access_token: personal_access_token, + oauth_access_token: oauth_access_token + ) end def ci_api(path, user = nil) diff --git a/spec/support/capybara_helpers.rb b/spec/support/capybara_helpers.rb index b57a3493aff..3eb7bea3227 100644 --- a/spec/support/capybara_helpers.rb +++ b/spec/support/capybara_helpers.rb @@ -35,6 +35,11 @@ module CapybaraHelpers visit 'about:blank' visit url end + + # Simulate a browser restart by clearing the session cookie. + def clear_browser_session + page.driver.remove_cookie('_gitlab_session') + end end RSpec.configure do |config| diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb index 6e1eb5c678d..c0a5491a430 100644 --- a/spec/support/cycle_analytics_helpers.rb +++ b/spec/support/cycle_analytics_helpers.rb @@ -74,7 +74,9 @@ module CycleAnalyticsHelpers def dummy_pipeline @dummy_pipeline ||= - Ci::Pipeline.new(sha: project.repository.commit('master').sha) + Ci::Pipeline.new( + sha: project.repository.commit('master').sha, + project: project) end def new_dummy_job(environment) diff --git a/spec/support/issue_helpers.rb b/spec/support/issue_helpers.rb index 85241793743..ffd72515f37 100644 --- a/spec/support/issue_helpers.rb +++ b/spec/support/issue_helpers.rb @@ -1,6 +1,6 @@ module IssueHelpers def visit_issues(project, opts = {}) - visit namespace_project_issues_path project.namespace, project, opts + visit project_issues_path project, opts end def first_issue diff --git a/spec/support/issue_tracker_service_shared_example.rb b/spec/support/issue_tracker_service_shared_example.rb index e70b3963d9d..a6ab03cb808 100644 --- a/spec/support/issue_tracker_service_shared_example.rb +++ b/spec/support/issue_tracker_service_shared_example.rb @@ -8,15 +8,15 @@ end RSpec.shared_examples 'allows project key on reference pattern' do |url_attr| it 'allows underscores in the project name' do - expect(subject.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234' + expect(described_class.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234' end it 'allows numbers in the project name' do - expect(subject.reference_pattern.match('EXT3_EXT-1234')[0]).to eq 'EXT3_EXT-1234' + expect(described_class.reference_pattern.match('EXT3_EXT-1234')[0]).to eq 'EXT3_EXT-1234' end it 'requires the project name to begin with A-Z' do - expect(subject.reference_pattern.match('3EXT_EXT-1234')).to eq nil - expect(subject.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234' + expect(described_class.reference_pattern.match('3EXT_EXT-1234')).to eq nil + expect(described_class.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234' end end diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index 47278595958..b410a652126 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -57,6 +57,16 @@ module LoginHelpers click_button "Sign in" end + def login_via(provider, user, uid, remember_me: false) + mock_auth_hash(provider, uid, user.email) + visit new_user_session_path + expect(page).to have_content('Sign in with') + + check 'Remember Me' if remember_me + + click_link "oauth-login-#{provider}" + end + def mock_auth_hash(provider, uid, email) # The mock_auth configuration allows you to set per-provider (or default) # authentication hashes to return during integration testing. @@ -103,6 +113,7 @@ module LoginHelpers end allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: mock_saml_config) stub_omniauth_setting(messages) - expect_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml') + allow_any_instance_of(Object).to receive(:user_saml_omniauth_authorize_path).and_return('/users/auth/saml') + allow_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml') end end diff --git a/spec/support/matchers/be_utf8.rb b/spec/support/matchers/be_utf8.rb new file mode 100644 index 00000000000..ea806352422 --- /dev/null +++ b/spec/support/matchers/be_utf8.rb @@ -0,0 +1,9 @@ +RSpec::Matchers.define :be_utf8 do |_| + match do |actual| + actual.is_a?(String) && actual.encoding == Encoding.find('UTF-8') + end + + description do + "be a String with encoding UTF-8" + end +end diff --git a/spec/support/merge_request_helpers.rb b/spec/support/merge_request_helpers.rb index 326b85eabd0..772adff4626 100644 --- a/spec/support/merge_request_helpers.rb +++ b/spec/support/merge_request_helpers.rb @@ -1,6 +1,6 @@ module MergeRequestHelpers def visit_merge_requests(project, opts = {}) - visit namespace_project_merge_requests_path project.namespace, project, opts + visit project_merge_requests_path project, opts end def first_merge_request diff --git a/spec/support/protected_tags/access_control_ce_shared_examples.rb b/spec/support/protected_tags/access_control_ce_shared_examples.rb index 1d11512ef82..421a51fc336 100644 --- a/spec/support/protected_tags/access_control_ce_shared_examples.rb +++ b/spec/support/protected_tags/access_control_ce_shared_examples.rb @@ -1,7 +1,7 @@ RSpec.shared_examples "protected tags > access control > CE" do ProtectedTag::CreateAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| it "allows creating protected tags that #{access_type_name} can create" do - visit namespace_project_protected_tags_path(project.namespace, project) + visit project_protected_tags_path(project) set_protected_tag_name('master') @@ -22,7 +22,7 @@ RSpec.shared_examples "protected tags > access control > CE" do end it "allows updating protected tags so that #{access_type_name} can create them" do - visit namespace_project_protected_tags_path(project.namespace, project) + visit project_protected_tags_path(project) set_protected_tag_name('master') diff --git a/spec/support/routing_helpers.rb b/spec/support/routing_helpers.rb new file mode 100644 index 00000000000..af1f4760804 --- /dev/null +++ b/spec/support/routing_helpers.rb @@ -0,0 +1,3 @@ +RSpec.configure do |config| + config.include GitlabRoutingHelper +end diff --git a/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb b/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb new file mode 100644 index 00000000000..96c821b26f7 --- /dev/null +++ b/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb @@ -0,0 +1,9 @@ +shared_examples 'issue sidebar stays collapsed on mobile' do + before do + resize_screen_xs + end + + it 'keeps the sidebar collapsed' do + expect(page).not_to have_css('.right-sidebar.right-sidebar-collapsed') + end +end diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb index b6341127a76..66e598e2691 100644 --- a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb +++ b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb @@ -1,7 +1,7 @@ shared_examples "protected branches > access control > CE" do ProtectedBranch::PushAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| it "allows creating protected branches that #{access_type_name} can push to" do - visit namespace_project_protected_branches_path(project.namespace, project) + visit project_protected_branches_path(project) set_protected_branch_name('master') @@ -21,7 +21,7 @@ shared_examples "protected branches > access control > CE" do end it "allows updating protected branches so that #{access_type_name} can push to them" do - visit namespace_project_protected_branches_path(project.namespace, project) + visit project_protected_branches_path(project) set_protected_branch_name('master') @@ -46,7 +46,7 @@ shared_examples "protected branches > access control > CE" do ProtectedBranch::MergeAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| it "allows creating protected branches that #{access_type_name} can merge to" do - visit namespace_project_protected_branches_path(project.namespace, project) + visit project_protected_branches_path(project) set_protected_branch_name('master') @@ -66,7 +66,7 @@ shared_examples "protected branches > access control > CE" do end it "allows updating protected branches so that #{access_type_name} can merge to them" do - visit namespace_project_protected_branches_path(project.namespace, project) + visit project_protected_branches_path(project) set_protected_branch_name('master') diff --git a/spec/views/ci/status/_badge.html.haml_spec.rb b/spec/views/ci/status/_badge.html.haml_spec.rb index 72323da2838..6a4738ba443 100644 --- a/spec/views/ci/status/_badge.html.haml_spec.rb +++ b/spec/views/ci/status/_badge.html.haml_spec.rb @@ -16,8 +16,7 @@ describe 'ci/status/_badge', :view do end it 'has link to build details page' do - details_path = namespace_project_job_path( - project.namespace, project, build) + details_path = project_job_path(project, build) render_status(build) diff --git a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb index 3e17fe2104b..98c7de9b709 100644 --- a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb @@ -25,10 +25,7 @@ describe 'projects/merge_requests/_commits.html.haml' do render commit = source_project.commit(merge_request.source_branch) - href = namespace_project_commit_path( - source_project.namespace, - source_project, - commit) + href = project_commit_path(source_project, commit) expect(rendered).to have_link(Commit.truncate_sha(commit.sha), href: href) end diff --git a/spec/workers/expire_build_instance_artifacts_worker_spec.rb b/spec/workers/expire_build_instance_artifacts_worker_spec.rb index 1d8da68883b..bed5c5e2ecb 100644 --- a/spec/workers/expire_build_instance_artifacts_worker_spec.rb +++ b/spec/workers/expire_build_instance_artifacts_worker_spec.rb @@ -30,20 +30,6 @@ describe ExpireBuildInstanceArtifactsWorker do expect(build.reload.artifacts_file_identifier).to be_nil end end - - context 'when associated project was removed' do - let(:build) do - create(:ci_build, :artifacts, artifacts_expiry) do |build| - build.project.pending_delete = true - end - end - - it 'does not remove artifacts' do - expect do - build.reload.artifacts_file - end.not_to raise_error - end - end end context 'with not yet expired artifacts' do diff --git a/yarn.lock b/yarn.lock index b902d5235d0..b04eebe60af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -265,6 +265,15 @@ babel-core@^6.22.1, babel-core@^6.23.0: slash "^1.0.0" source-map "^0.5.0" +babel-eslint@^7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-7.2.1.tgz#079422eb73ba811e3ca0865ce87af29327f8c52f" + dependencies: + babel-code-frame "^6.22.0" + babel-traverse "^6.23.1" + babel-types "^6.23.0" + babylon "^6.16.1" + babel-generator@^6.18.0, babel-generator@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.23.0.tgz#6b8edab956ef3116f79d8c84c5a3c05f32a74bc5" @@ -816,10 +825,14 @@ babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.23 lodash "^4.2.0" to-fast-properties "^1.0.1" -babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0: +babylon@^6.11.0: version "6.15.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.15.0.tgz#ba65cfa1a80e1759b0e89fb562e27dccae70348e" +babylon@^6.13.0, babylon@^6.15.0, babylon@^6.16.1: + version "6.16.1" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3" + backo2@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" |