diff options
578 files changed, 4936 insertions, 1884 deletions
diff --git a/.eslintrc b/.eslintrc index e13f76b213c..9ab0145820d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -15,6 +15,7 @@ "filenames" ], "rules": { - "filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"] + "filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"], + "no-multiple-empty-lines": ["error", { "max": 1 }] } } diff --git a/.haml-lint.yml b/.haml-lint.yml index 7c8a9c4fd17..528f99d08d2 100644 --- a/.haml-lint.yml +++ b/.haml-lint.yml @@ -46,7 +46,7 @@ linters: max: 80 MultilinePipe: - enabled: false + enabled: true MultilineScript: enabled: true @@ -77,7 +77,7 @@ linters: - Style/WhileUntilModifier RubyComments: - enabled: false + enabled: true SpaceBeforeScript: enabled: true @@ -97,7 +97,7 @@ linters: enabled: true UnnecessaryInterpolation: - enabled: false + enabled: true UnnecessaryStringOutput: - enabled: false + enabled: true diff --git a/CHANGELOG.md b/CHANGELOG.md index aecacbee2f5..9712b32232e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 8.16.2 (2017-01-25) + +- allow issue filter bar to be operated with mouse only. !8681 +- Fix CI requests concurrency for newer runners that prevents from picking pending builds (from 1.9.0-rc5). !8760 +- Add some basic fixes for IE11/Edge. +- Remove blue border from comment box hover. +- Fixed bug where links in merge dropdown wouldn't work. + +## 8.16.1 (2017-01-23) + +- Ensure export files are removed after a namespace is deleted. +- Don't allow project guests to subscribe to merge requests through the API. (Robert Schilling) +- Prevent users from creating notes on resources they can't access. +- Prevent users from deleting system deploy keys via the project deploy key API. +- Upgrade omniauth gem to 1.3.2. + ## 8.16.0 (2017-02-22) - Add LDAP Rake task to rename a provider. !2181 @@ -395,6 +411,14 @@ entry. - Whitelist next project names: help, ci, admin, search. !8227 - Adds back CSS for progress-bars. !8237 +## 8.14.8 (2017-01-25) + +- Accept environment variables from the `pre-receive` script. !7967 +- Milestoneish SQL performance partially improved and memoized. !8146 +- Fix N+1 queries on milestone show pages. !8185 +- Speed up group milestone index by passing group_id to IssuesFinder. !8363 +- Ensure issuable state changes only fire webhooks once. + ## 8.14.6 (2017-01-10) - Update the gitlab-markup gem to the version 1.5.1. !8509 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b68c4a67826..d404f1b91df 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -80,11 +80,9 @@ the remaining issues on the GitHub issue tracker. ## I want to contribute! If you want to contribute to GitLab, but are not sure where to start, -look for [issues with the label `up-for-grabs`][up-for-grabs]. These issues -will be of reasonable size and challenge, for anyone to start contributing to -GitLab. - -This was inspired by [an article by Kent C. Dodds][medium-up-for-grabs]. +look for [issues with the label `Accepting Merge Requests` and weight < 5][accepting-mrs-weight]. +These issues will be of reasonable size and challenge, for anyone to start +contributing to GitLab. ## Implement design & UI elements @@ -214,16 +212,19 @@ associated with in the description of the issue. ## Merge requests We welcome merge requests with fixes and improvements to GitLab code, tests, -and/or documentation. The features we would really like a merge request for are -listed with the label [`Accepting Merge Requests` on our issue tracker for CE][accepting-mrs-ce] -and [EE][accepting-mrs-ee] but other improvements are also welcome. Please note -that if an issue is marked for the current milestone either before or while you -are working on it, a team member may take over the merge request in order to -ensure the work is finished before the release date. +and/or documentation. The issues that are specifically suitable for +community contributions are listed with the label +[`Accepting Merge Requests` on our issue tracker for CE][accepting-mrs-ce] +and [EE][accepting-mrs-ee], but you are free to contribute to any other issue +you want. + +Please note that if an issue is marked for the current milestone either before +or while you are working on it, a team member may take over the merge request +in order to ensure the work is finished before the release date. If you want to add a new feature that is not labeled it is best to first create a feedback issue (if there isn't one already) and leave a comment asking for it -to be marked as `Accepting merge requests`. Please include screenshots or +to be marked as `Accepting Merge Requests`. Please include screenshots or wireframes if the feature will also change the UI. Merge requests should be opened at [GitLab.com][gitlab-mr-tracker]. @@ -285,14 +286,6 @@ request is as follows: 1. For tests that use Capybara or PhantomJS, see this [article on how to write reliable asynchronous tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara). -The **official merge window** is in the beginning of the month from the 1st to -the 7th day of the month. This is the best time to submit an MR and get -feedback fast. Before this time the GitLab Inc. team is still dealing with work -that is created by the monthly release such as regressions requiring patch -releases. After the 7th it is already getting closer to the release date of the -next version. This means there is less time to fix the issues created by -merging large new features. - Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split the functionality? Can you only submit the backend/API code? Can @@ -450,8 +443,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor [core team]: https://about.gitlab.com/core-team/ [getting-help]: https://about.gitlab.com/getting-help/ [codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq -[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up-for-grabs -[medium-up-for-grabs]: https://medium.com/@kentcdodds/first-timers-only-78281ea47455 +[accepting-mrs-weight]: https://gitlab.com/gitlab-org/gitlab-ce/issues?assignee_id=0&label_name[]=Accepting%20Merge%20Requests&sort=weight_asc [ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues [ee-tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues [google-group]: https://groups.google.com/forum/#!forum/gitlabhq @@ -322,7 +322,7 @@ group :test do gem 'email_spec', '~> 1.6.0' gem 'json-schema', '~> 2.6.2' gem 'webmock', '~> 1.21.0' - gem 'test_after_commit', '~> 0.4.2' + gem 'test_after_commit', '~> 1.1' gem 'sham_rack', '~> 1.3.6' gem 'timecop', '~> 0.8.0' end diff --git a/Gemfile.lock b/Gemfile.lock index c9115982838..133e47e1ea4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -760,7 +760,7 @@ GEM teaspoon-jasmine (2.2.0) teaspoon (>= 1.0.0) temple (0.7.7) - test_after_commit (0.4.2) + test_after_commit (1.1.0) activerecord (>= 3.2) thin (1.7.0) daemons (~> 1.0, >= 1.0.9) @@ -997,7 +997,7 @@ DEPENDENCIES sys-filesystem (~> 1.1.6) teaspoon (~> 1.1.0) teaspoon-jasmine (~> 2.2.0) - test_after_commit (~> 0.4.2) + test_after_commit (~> 1.1) thin (~> 1.7.0) timecop (~> 0.8.0) truncato (~> 0.7.8) diff --git a/app/assets/javascripts/copy_as_gfm.js.es6 b/app/assets/javascripts/copy_as_gfm.js.es6 new file mode 100644 index 00000000000..b94125a4210 --- /dev/null +++ b/app/assets/javascripts/copy_as_gfm.js.es6 @@ -0,0 +1,355 @@ +/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */ +/* jshint esversion: 6 */ + +/*= require lib/utils/common_utils */ + +(() => { + const gfmRules = { + // The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert + // GitLab Flavored Markdown (GFM) to HTML. + // These handlers consequently convert that same HTML to GFM to be copied to the clipboard. + // Every filter in lib/banzai/pipeline/gfm_pipeline.rb that generates HTML + // from GFM should have a handler here, in reverse order. + // The GFM-to-HTML-to-GFM cycle is tested in spec/features/copy_as_gfm_spec.rb. + InlineDiffFilter: { + 'span.idiff.addition'(el, text) { + return `{+${text}+}`; + }, + 'span.idiff.deletion'(el, text) { + return `{-${text}-}`; + }, + }, + TaskListFilter: { + 'input[type=checkbox].task-list-item-checkbox'(el, text) { + return `[${el.checked ? 'x' : ' '}]`; + }, + }, + ReferenceFilter: { + 'a.gfm:not([data-link=true])'(el, text) { + return el.dataset.original || text; + }, + }, + AutolinkFilter: { + 'a'(el, text) { + // Fallback on the regular MarkdownFilter's `a` handler. + if (text !== el.getAttribute('href')) return false; + + return text; + }, + }, + TableOfContentsFilter: { + 'ul.section-nav'(el, text) { + return '[[_TOC_]]'; + }, + }, + EmojiFilter: { + 'img.emoji'(el, text) { + return el.getAttribute('alt'); + }, + }, + ImageLinkFilter: { + 'a.no-attachment-icon'(el, text) { + return text; + }, + }, + VideoLinkFilter: { + '.video-container'(el, text) { + const videoEl = el.querySelector('video'); + if (!videoEl) return false; + + return CopyAsGFM.nodeToGFM(videoEl); + }, + 'video'(el, text) { + return `![${el.dataset.title}](${el.getAttribute('src')})`; + }, + }, + MathFilter: { + 'pre.code.math[data-math-style=display]'(el, text) { + return `\`\`\`math\n${text.trim()}\n\`\`\``; + }, + 'code.code.math[data-math-style=inline]'(el, text) { + return `$\`${text}\`$`; + }, + 'span.katex-display span.katex-mathml'(el, text) { + const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]'); + if (!mathAnnotation) return false; + + return `\`\`\`math\n${CopyAsGFM.nodeToGFM(mathAnnotation)}\n\`\`\``; + }, + 'span.katex-mathml'(el, text) { + const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]'); + if (!mathAnnotation) return false; + + return `$\`${CopyAsGFM.nodeToGFM(mathAnnotation)}\`$`; + }, + 'span.katex-html'(el, text) { + // We don't want to include the content of this element in the copied text. + return ''; + }, + 'annotation[encoding="application/x-tex"]'(el, text) { + return text.trim(); + }, + }, + SanitizationFilter: { + 'dl'(el, text) { + let lines = text.trim().split('\n'); + // Add two spaces to the front of subsequent list items lines, + // or leave the line entirely blank. + lines = lines.map((l) => { + const line = l.trim(); + if (line.length === 0) return ''; + + return ` ${line}`; + }); + + return `<dl>\n${lines.join('\n')}\n</dl>`; + }, + 'sub, dt, dd, kbd, q, samp, var, ruby, rt, rp, abbr'(el, text) { + const tag = el.nodeName.toLowerCase(); + return `<${tag}>${text}</${tag}>`; + }, + }, + SyntaxHighlightFilter: { + 'pre.code.highlight'(el, t) { + const text = t.trim(); + + let lang = el.getAttribute('lang'); + if (lang === 'plaintext') { + lang = ''; + } + + // Prefixes lines with 4 spaces if the code contains triple backticks + if (lang === '' && text.match(/^```/gm)) { + return text.split('\n').map((l) => { + const line = l.trim(); + if (line.length === 0) return ''; + + return ` ${line}`; + }).join('\n'); + } + + return `\`\`\`${lang}\n${text}\n\`\`\``; + }, + 'pre > code'(el, text) { + // Don't wrap code blocks in `` + return text; + }, + }, + MarkdownFilter: { + 'br'(el, text) { + // Two spaces at the end of a line are turned into a BR + return ' '; + }, + 'code'(el, text) { + let backtickCount = 1; + const backtickMatch = text.match(/`+/); + if (backtickMatch) { + backtickCount = backtickMatch[0].length + 1; + } + + const backticks = Array(backtickCount + 1).join('`'); + const spaceOrNoSpace = backtickCount > 1 ? ' ' : ''; + + return backticks + spaceOrNoSpace + text + spaceOrNoSpace + backticks; + }, + 'blockquote'(el, text) { + return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n'); + }, + 'img'(el, text) { + return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`; + }, + 'a.anchor'(el, text) { + // Don't render a Markdown link for the anchor link inside a heading + return text; + }, + 'a'(el, text) { + return `[${text}](${el.getAttribute('href')})`; + }, + 'li'(el, text) { + const lines = text.trim().split('\n'); + const firstLine = `- ${lines.shift()}`; + // Add four spaces to the front of subsequent list items lines, + // or leave the line entirely blank. + const nextLines = lines.map((s) => { + if (s.trim().length === 0) return ''; + + return ` ${s}`; + }); + + return `${firstLine}\n${nextLines.join('\n')}`; + }, + 'ul'(el, text) { + return text; + }, + 'ol'(el, text) { + // LIs get a `- ` prefix by default, which we replace by `1. ` for ordered lists. + return text.replace(/^- /mg, '1. '); + }, + 'h1'(el, text) { + return `# ${text.trim()}`; + }, + 'h2'(el, text) { + return `## ${text.trim()}`; + }, + 'h3'(el, text) { + return `### ${text.trim()}`; + }, + 'h4'(el, text) { + return `#### ${text.trim()}`; + }, + 'h5'(el, text) { + return `##### ${text.trim()}`; + }, + 'h6'(el, text) { + return `###### ${text.trim()}`; + }, + 'strong'(el, text) { + return `**${text}**`; + }, + 'em'(el, text) { + return `_${text}_`; + }, + 'del'(el, text) { + return `~~${text}~~`; + }, + 'sup'(el, text) { + return `^${text}`; + }, + 'hr'(el, text) { + return '-----'; + }, + 'table'(el, text) { + const theadEl = el.querySelector('thead'); + const tbodyEl = el.querySelector('tbody'); + if (!theadEl || !tbodyEl) return false; + + const theadText = CopyAsGFM.nodeToGFM(theadEl); + const tbodyText = CopyAsGFM.nodeToGFM(tbodyEl); + + return theadText + tbodyText; + }, + 'thead'(el, text) { + const cells = _.map(el.querySelectorAll('th'), (cell) => { + let chars = CopyAsGFM.nodeToGFM(cell).trim().length + 2; + + let before = ''; + let after = ''; + switch (cell.style.textAlign) { + case 'center': + before = ':'; + after = ':'; + chars -= 2; + break; + case 'right': + after = ':'; + chars -= 1; + break; + default: + break; + } + + chars = Math.max(chars, 3); + + const middle = Array(chars + 1).join('-'); + + return before + middle + after; + }); + + return `${text}|${cells.join('|')}|`; + }, + 'tr'(el, text) { + const cells = _.map(el.querySelectorAll('td, th'), cell => CopyAsGFM.nodeToGFM(cell).trim()); + return `| ${cells.join(' | ')} |`; + }, + }, + }; + + class CopyAsGFM { + constructor() { + $(document).on('copy', '.md, .wiki', this.handleCopy); + $(document).on('paste', '.js-gfm-input', this.handlePaste); + } + + handleCopy(e) { + const clipboardData = e.originalEvent.clipboardData; + if (!clipboardData) return; + + const documentFragment = window.gl.utils.getSelectedFragment(); + if (!documentFragment) return; + + // If the documentFragment contains more than just Markdown, don't copy as GFM. + if (documentFragment.querySelector('.md, .wiki')) return; + + e.preventDefault(); + clipboardData.setData('text/plain', documentFragment.textContent); + + const gfm = CopyAsGFM.nodeToGFM(documentFragment); + clipboardData.setData('text/x-gfm', gfm); + } + + handlePaste(e) { + const clipboardData = e.originalEvent.clipboardData; + if (!clipboardData) return; + + const gfm = clipboardData.getData('text/x-gfm'); + if (!gfm) return; + + e.preventDefault(); + + window.gl.utils.insertText(e.target, gfm); + } + + static nodeToGFM(node) { + if (node.nodeType === Node.TEXT_NODE) { + return node.textContent; + } + + const text = this.innerGFM(node); + + if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { + return text; + } + + for (const filter in gfmRules) { + const rules = gfmRules[filter]; + + for (const selector in rules) { + const func = rules[selector]; + + if (!window.gl.utils.nodeMatchesSelector(node, selector)) continue; + + const result = func(node, text); + if (result === false) continue; + + return result; + } + } + + return text; + } + + static innerGFM(parentNode) { + const nodes = parentNode.childNodes; + + const clonedParentNode = parentNode.cloneNode(true); + const clonedNodes = Array.prototype.slice.call(clonedParentNode.childNodes, 0); + + for (let i = 0; i < nodes.length; i += 1) { + const node = nodes[i]; + const clonedNode = clonedNodes[i]; + + const text = this.nodeToGFM(node); + + // `clonedNode.replaceWith(text)` is not yet widely supported + clonedNode.parentNode.replaceChild(document.createTextNode(text), clonedNode); + } + + return clonedParentNode.innerText || clonedParentNode.textContent; + } + } + + window.gl = window.gl || {}; + window.gl.CopyAsGFM = CopyAsGFM; + + new CopyAsGFM(); +})(); diff --git a/app/assets/javascripts/diff.js.es6 b/app/assets/javascripts/diff.js.es6 index 5e1a4c948aa..35a029194d0 100644 --- a/app/assets/javascripts/diff.js.es6 +++ b/app/assets/javascripts/diff.js.es6 @@ -1,5 +1,7 @@ /* eslint-disable class-methods-use-this */ +//= require lib/utils/url_utility */ + (() => { const UNFOLD_COUNT = 20; @@ -104,11 +106,11 @@ } highlighSelectedLine() { + const hash = gl.utils.getLocationHash(); const $diffFiles = $('.diff-file'); $diffFiles.find('.hll').removeClass('hll'); - if (window.location.hash !== '') { - const hash = window.location.hash.replace('#', ''); + if (hash) { $diffFiles .find(`tr#${hash}:not(.match) td, td#${hash}, td[data-line-code="${hash}"]`) .addClass('hll'); diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index dcf67a8fd68..529d476ca4e 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -261,6 +261,9 @@ case 'projects:artifacts:browse': new BuildArtifacts(); break; + case 'help:index': + gl.VersionCheckImage.bindErrorEvent($('img.js-version-status-badge')); + break; case 'search:show': new Search(); break; diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js index c79f0230951..8b14191395b 100644 --- a/app/assets/javascripts/droplab/droplab.js +++ b/app/assets/javascripts/droplab/droplab.js @@ -58,6 +58,7 @@ var CustomEvent = require('./custom_event_polyfill'); var utils = require('./utils'); var DropDown = function(list) { + this.currentIndex = 0; this.hidden = true; this.list = list; this.items = []; @@ -164,15 +165,21 @@ Object.assign(DropDown.prototype, { }, show: function() { - // debugger - this.list.style.display = 'block'; - this.hidden = false; + if (this.hidden) { + // debugger + this.list.style.display = 'block'; + this.currentIndex = 0; + this.hidden = false; + } }, hide: function() { - // debugger - this.list.style.display = 'none'; - this.hidden = true; + if (!this.hidden) { + // debugger + this.list.style.display = 'none'; + this.currentIndex = 0; + this.hidden = true; + } }, destroy: function() { @@ -478,6 +485,8 @@ Object.assign(HookInput.prototype, { this.input = function input(e) { if(self.hasRemovedEvents) return; + self.list.show(); + var inputEvent = new CustomEvent('input.dl', { detail: { hook: self, @@ -487,7 +496,6 @@ Object.assign(HookInput.prototype, { cancelable: true }); e.target.dispatchEvent(inputEvent); - self.list.show(); } this.keyup = function keyup(e) { @@ -503,6 +511,8 @@ Object.assign(HookInput.prototype, { } function keyEvent(e, keyEventName){ + self.list.show(); + var keyEvent = new CustomEvent(keyEventName, { detail: { hook: self, @@ -514,7 +524,6 @@ Object.assign(HookInput.prototype, { cancelable: true }); e.target.dispatchEvent(keyEvent); - self.list.show(); } this.events = this.events || {}; @@ -572,24 +581,43 @@ require('./window')(function(w){ module.exports = function(){ var currentKey; var currentFocus; - var currentIndex = 0; var isUpArrow = false; var isDownArrow = false; var removeHighlight = function removeHighlight(list) { - var listItems = list.list.querySelectorAll('li'); + var listItems = Array.prototype.slice.call(list.list.querySelectorAll('li:not(.divider)'), 0); + var listItemsTmp = []; for(var i = 0; i < listItems.length; i++) { - listItems[i].classList.remove('dropdown-active'); + var listItem = listItems[i]; + listItem.classList.remove('dropdown-active'); + + if (listItem.style.display !== 'none') { + listItemsTmp.push(listItem); + } } - return listItems; + return listItemsTmp; }; var setMenuForArrows = function setMenuForArrows(list) { var listItems = removeHighlight(list); - if(currentIndex>0){ - if(!listItems[currentIndex-1]){ - currentIndex = currentIndex-1; + if(list.currentIndex>0){ + if(!listItems[list.currentIndex-1]){ + list.currentIndex = list.currentIndex-1; + } + + if (listItems[list.currentIndex-1]) { + var el = listItems[list.currentIndex-1]; + var filterDropdownEl = el.closest('.filter-dropdown'); + el.classList.add('dropdown-active'); + + if (filterDropdownEl) { + var filterDropdownBottom = filterDropdownEl.offsetHeight; + var elOffsetTop = el.offsetTop - 30; + + if (elOffsetTop > filterDropdownBottom) { + filterDropdownEl.scrollTop = elOffsetTop - filterDropdownBottom; + } + } } - listItems[currentIndex-1].classList.add('dropdown-active'); } }; @@ -597,13 +625,13 @@ require('./window')(function(w){ var list = e.detail.hook.list; removeHighlight(list); list.show(); - currentIndex = 0; + list.currentIndex = 0; isUpArrow = false; isDownArrow = false; }; var selectItem = function selectItem(list) { var listItems = removeHighlight(list); - var currentItem = listItems[currentIndex-1]; + var currentItem = listItems[list.currentIndex-1]; var listEvent = new CustomEvent('click.dl', { detail: { list: list, @@ -617,6 +645,8 @@ require('./window')(function(w){ var keydown = function keydown(e){ var typedOn = e.target; + var list = e.detail.hook.list; + var currentIndex = list.currentIndex; isUpArrow = false; isDownArrow = false; @@ -648,6 +678,7 @@ require('./window')(function(w){ if(isUpArrow){ currentIndex--; } if(isDownArrow){ currentIndex++; } if(currentIndex < 0){ currentIndex = 0; } + list.currentIndex = currentIndex; setMenuForArrows(e.detail.hook.list); }; diff --git a/app/assets/javascripts/droplab/droplab_ajax.js b/app/assets/javascripts/droplab/droplab_ajax.js index f20610b3811..c290e1a8355 100644 --- a/app/assets/javascripts/droplab/droplab_ajax.js +++ b/app/assets/javascripts/droplab/droplab_ajax.js @@ -9,6 +9,7 @@ require('../window')(function(w){ w.droplabAjax = { _loadUrlData: function _loadUrlData(url) { + var self = this; return new Promise(function(resolve, reject) { var xhr = new XMLHttpRequest; xhr.open('GET', url, true); @@ -16,6 +17,7 @@ require('../window')(function(w){ if(xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { var data = JSON.parse(xhr.responseText); + self.cache[url] = data; return resolve(data); } else { return reject([xhr.responseText, xhr.status]); @@ -26,9 +28,23 @@ require('../window')(function(w){ }); }, + _loadData: function _loadData(data, config, self) { + if (config.loadingTemplate) { + var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]'); + + if (dataLoadingTemplate) { + dataLoadingTemplate.outerHTML = self.listTemplate; + } + } + + self.hook.list[config.method].call(self.hook.list, data); + }, + init: function init(hook) { var self = this; + self.cache = self.cache || {}; var config = hook.config.droplabAjax; + this.hook = hook; if (!config || !config.endpoint || !config.method) { return; @@ -49,22 +65,23 @@ require('../window')(function(w){ dynamicList.outerHTML = loadingTemplate.outerHTML; } - this._loadUrlData(config.endpoint) - .then(function(d) { - if (config.loadingTemplate) { - var dataLoadingTemplate = hook.list.list.querySelector('[data-loading-template]'); - - if (dataLoadingTemplate) { - dataLoadingTemplate.outerHTML = self.listTemplate; - } - } - hook.list[config.method].call(hook.list, d); - }).catch(function(e) { - throw new droplabAjaxException(e.message || e); - }); + if (self.cache[config.endpoint]) { + self._loadData(self.cache[config.endpoint], config, self); + } else { + this._loadUrlData(config.endpoint) + .then(function(d) { + self._loadData(d, config, self); + }).catch(function(e) { + throw new droplabAjaxException(e.message || e); + }); + } }, destroy: function() { + if (this.listTemplate) { + var dynamicList = this.hook.list.list.querySelector('[data-dynamic]'); + dynamicList.outerHTML = this.listTemplate; + } } }; }); @@ -76,4 +93,4 @@ module.exports = function(callback) { }; },{}]},{},[1])(1) -});
\ No newline at end of file +}); diff --git a/app/assets/javascripts/droplab/droplab_ajax_filter.js b/app/assets/javascripts/droplab/droplab_ajax_filter.js index af163f76851..b63d73066cb 100644 --- a/app/assets/javascripts/droplab/droplab_ajax_filter.js +++ b/app/assets/javascripts/droplab/droplab_ajax_filter.js @@ -72,31 +72,22 @@ require('../window')(function(w){ var params = config.params || {}; params[config.searchKey] = searchValue; var self = this; - this._loadUrlData(config.endpoint + this.buildParams(params)).then(function(data) { - if (config.loadingTemplate && self.hook.list.data === undefined || - self.hook.list.data.length === 0) { - const dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]'); - - if (dataLoadingTemplate) { - dataLoadingTemplate.outerHTML = self.listTemplate; - } - } - - if (!self.destroyed) { - var hookListChildren = self.hook.list.list.children; - var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic'); - - if (onlyDynamicList && data.length === 0) { - self.hook.list.hide(); - } - - self.hook.list.setData.call(self.hook.list, data); - } - self.notLoading(); - }); + self.cache = self.cache || {}; + var url = config.endpoint + this.buildParams(params); + var urlCachedData = self.cache[url]; + + if (urlCachedData) { + self._loadData(urlCachedData, config, self); + } else { + this._loadUrlData(url) + .then(function(data) { + self._loadData(data, config, self); + }); + } }, _loadUrlData: function _loadUrlData(url) { + var self = this; return new Promise(function(resolve, reject) { var xhr = new XMLHttpRequest; xhr.open('GET', url, true); @@ -104,6 +95,7 @@ require('../window')(function(w){ if(xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { var data = JSON.parse(xhr.responseText); + self.cache[url] = data; return resolve(data); } else { return reject([xhr.responseText, xhr.status]); @@ -114,6 +106,30 @@ require('../window')(function(w){ }); }, + _loadData: function _loadData(data, config, self) { + if (config.loadingTemplate && self.hook.list.data === undefined || + self.hook.list.data.length === 0) { + const dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]'); + + if (dataLoadingTemplate) { + dataLoadingTemplate.outerHTML = self.listTemplate; + } + } + + if (!self.destroyed) { + var hookListChildren = self.hook.list.list.children; + var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic'); + + if (onlyDynamicList && data.length === 0) { + self.hook.list.hide(); + } + + self.hook.list.setData.call(self.hook.list, data); + } + self.notLoading(); + self.hook.list.currentIndex = 0; + }, + buildParams: function(params) { if (!params) return ''; var paramsArray = Object.keys(params).map(function(param) { @@ -142,4 +158,4 @@ module.exports = function(callback) { }; },{}]},{},[1])(1) -});
\ No newline at end of file +}); diff --git a/app/assets/javascripts/droplab/droplab_filter.js b/app/assets/javascripts/droplab/droplab_filter.js index 41a220831f9..9b40a3f20a4 100644 --- a/app/assets/javascripts/droplab/droplab_filter.js +++ b/app/assets/javascripts/droplab/droplab_filter.js @@ -6,6 +6,8 @@ require('../window')(function(w){ w.droplabFilter = { keydownWrapper: function(e){ + var hiddenCount = 0; + var dataHiddenCount = 0; var list = e.detail.hook.list; var data = list.data; var value = e.detail.hook.trigger.value.toLowerCase(); @@ -27,10 +29,22 @@ require('../window')(function(w){ }; } + dataHiddenCount = data.filter(function(o) { + return !o.droplab_hidden; + }).length; + matches = data.map(function(o) { return filterFunction(o, value); }); - list.render(matches); + + hiddenCount = matches.filter(function(o) { + return !o.droplab_hidden; + }).length; + + if (dataHiddenCount !== hiddenCount) { + list.render(matches); + list.currentIndex = 0; + } }, init: function init(hookInput) { @@ -57,4 +71,4 @@ module.exports = function(callback) { }; },{}]},{},[1])(1) -});
\ No newline at end of file +}); diff --git a/app/assets/javascripts/environments/environments_bundle.js.es6 b/app/assets/javascripts/environments/environments_bundle.js.es6 index 9f24a6a4f88..3b003f6f661 100644 --- a/app/assets/javascripts/environments/environments_bundle.js.es6 +++ b/app/assets/javascripts/environments/environments_bundle.js.es6 @@ -3,7 +3,6 @@ //= require ./components/environment //= require ./vue_resource_interceptor - $(() => { window.gl = window.gl || {}; diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 index 443ac222f70..eeab10fba17 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 @@ -84,7 +84,7 @@ let inputValue = input.value; // Replace all spaces inside quote marks with underscores // This helps with matching the beginning & end of a token:key - inputValue = inputValue.replace(/"(.*?)"/g, str => str.replace(/\s/g, '_')); + inputValue = inputValue.replace(/("(.*?)"|:\s+)/g, str => str.replace(/\s/g, '_')); // Get the right position for the word selected // Regex matches first space diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index ae19bb68360..8d62324b79f 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -64,13 +64,26 @@ } checkForEnter(e) { + if (e.keyCode === 38 || e.keyCode === 40) { + const selectionStart = this.filteredSearchInput.selectionStart; + + e.preventDefault(); + this.filteredSearchInput.setSelectionRange(selectionStart, selectionStart); + } + if (e.keyCode === 13) { + const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown]; + const dropdownEl = dropdown.element; + const activeElements = dropdownEl.querySelectorAll('.dropdown-active'); + e.preventDefault(); - // Prevent droplab from opening dropdown - this.dropdownManager.destroyDroplab(); + if (!activeElements.length) { + // Prevent droplab from opening dropdown + this.dropdownManager.destroyDroplab(); - this.search(); + this.search(); + } } } diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 index e46373024b6..e6b53cd4b55 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 @@ -21,6 +21,15 @@ symbol: '~', }]; + const alternativeTokenKeys = [{ + key: 'label', + type: 'string', + param: 'name', + symbol: '~', + }]; + + const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys); + const conditions = [{ url: 'assignee_id=0', tokenKey: 'assignee', @@ -44,6 +53,10 @@ return tokenKeys; } + static getAlternatives() { + return alternativeTokenKeys; + } + static getConditions() { return conditions; } @@ -57,7 +70,7 @@ } static searchByKeyParam(keyParam) { - return tokenKeys.find((tokenKey) => { + return tokenKeysWithAlternative.find((tokenKey) => { let tokenKeyParam = tokenKey.key; if (tokenKey.param) { diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6 index a1b7b442882..3f23095dad9 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.es6 +++ b/app/assets/javascripts/gfm_auto_complete.js.es6 @@ -367,9 +367,14 @@ return $input.trigger('keyup'); }, isLoading(data) { - if (!data || !data.length) return false; - if (Array.isArray(data)) data = data[0]; - return data === this.defaultLoadingData[0] || data.name === this.defaultLoadingData[0]; + var dataToInspect = data; + if (data && data.length > 0) { + dataToInspect = data[0]; + } + + var loadingState = this.defaultLoadingData[0]; + return dataToInspect && + (dataToInspect === loadingState || dataToInspect.name === loadingState); } }; }).call(this); diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 7746535d9ed..cc1c0877cdf 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -651,18 +651,14 @@ isMarking = false; el.removeClass(ACTIVE_CLASS); if (field && field.length) { - if (isInput) { - field.val(''); - } else { - field.remove(); - } + this.clearField(field, isInput); } } else if (el.hasClass(INDETERMINATE_CLASS)) { isMarking = true; el.addClass(ACTIVE_CLASS); el.removeClass(INDETERMINATE_CLASS); if (field && field.length && value == null) { - field.remove(); + this.clearField(field, isInput); } if ((!field || !field.length) && fieldName) { this.addInput(fieldName, value, selectedObject); @@ -676,7 +672,7 @@ } } if (field && field.length && value == null) { - field.remove(); + this.clearField(field, isInput); } // Toggle active class for the tick mark el.addClass(ACTIVE_CLASS); @@ -826,6 +822,10 @@ return $(this.el).find(".dropdown-toggle-text").text(this.options.toggleLabel(selected, el, instance)); }; + GitLabDropdown.prototype.clearField = function(field, isInput) { + return isInput ? field.val('') : field.remove(); + }; + return GitLabDropdown; })(); diff --git a/app/assets/javascripts/issues_bulk_assignment.js.es6 b/app/assets/javascripts/issues_bulk_assignment.js.es6 index c260ad03d47..e0ebd36a65c 100644 --- a/app/assets/javascripts/issues_bulk_assignment.js.es6 +++ b/app/assets/javascripts/issues_bulk_assignment.js.es6 @@ -61,7 +61,6 @@ return labels; } - /** * Will return only labels that were marked previously and the user has unmarked * @return {Array} Label IDs @@ -80,7 +79,6 @@ return result; } - /** * Simple form serialization, it will return just what we need * Returns key/value pairs from form data diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index fd1e229e30a..70dc0d06b7b 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -336,7 +336,11 @@ .removeClass('is-active'); } - if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) { + if ($dropdown.hasClass('js-issuable-form-dropdown')) { + return; + } + + if ($dropdown.hasClass('js-filter-bulk-update')) { _this.enableBulkLabelDropdown(); _this.setDropdownData($dropdown, isMarking, this.id(label)); return; diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js.es6 index 6d57d31f380..51993bb3420 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js.es6 +++ b/app/assets/javascripts/lib/utils/common_utils.js.es6 @@ -159,5 +159,75 @@ if (!results[2]) return ''; return decodeURIComponent(results[2].replace(/\+/g, ' ')); }; + + w.gl.utils.getSelectedFragment = () => { + const selection = window.getSelection(); + const documentFragment = selection.getRangeAt(0).cloneContents(); + if (documentFragment.textContent.length === 0) return null; + + return documentFragment; + }; + + w.gl.utils.insertText = (target, text) => { + // Firefox doesn't support `document.execCommand('insertText', false, text)` on textareas + + const selectionStart = target.selectionStart; + const selectionEnd = target.selectionEnd; + const value = target.value; + + const textBefore = value.substring(0, selectionStart); + const textAfter = value.substring(selectionEnd, value.length); + const newText = textBefore + text + textAfter; + + target.value = newText; + target.selectionStart = target.selectionEnd = selectionStart + text.length; + + // Trigger autosave + $(target).trigger('input'); + + // Trigger autosize + var event = document.createEvent('Event'); + event.initEvent('autosize:update', true, false); + target.dispatchEvent(event); + }; + + w.gl.utils.nodeMatchesSelector = (node, selector) => { + const matches = Element.prototype.matches || + Element.prototype.matchesSelector || + Element.prototype.mozMatchesSelector || + Element.prototype.msMatchesSelector || + Element.prototype.oMatchesSelector || + Element.prototype.webkitMatchesSelector; + + if (matches) { + return matches.call(node, selector); + } + + // IE11 doesn't support `node.matches(selector)` + + let parentNode = node.parentNode; + if (!parentNode) { + parentNode = document.createElement('div'); + node = node.cloneNode(true); + parentNode.appendChild(node); + } + + const matchingNodes = parentNode.querySelectorAll(selector); + return Array.prototype.indexOf.call(matchingNodes, node) !== -1; + }; + + /** + this will take in the headers from an API response and normalize them + this way we don't run into production issues when nginx gives us lowercased header keys + */ + w.gl.utils.normalizeHeaders = (headers) => { + const upperCaseHeaders = {}; + + Object.keys(headers).forEach((e) => { + upperCaseHeaders[e.toUpperCase()] = headers[e]; + }); + + return upperCaseHeaders; + }; })(window); }).call(this); diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js index 4620715a521..2f147704c22 100644 --- a/app/assets/javascripts/line_highlighter.js +++ b/app/assets/javascripts/line_highlighter.js @@ -74,8 +74,9 @@ // If not done this way, the line number anchor will sometimes keep its // active state even when the event is cancelled, resulting in an ugly border // around the link and/or a persisted underline text decoration. - return $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) { - return event.preventDefault(); + $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) { + event.preventDefault(); + event.stopPropagation(); }); }; diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 06a72efa21d..9db830a7ada 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -220,7 +220,6 @@ })(this)); }; - /* Increase @pollingInterval up to 120 seconds on every function call, if `shouldReset` has a truthy value, 'null' or 'undefined' the variable @@ -244,7 +243,6 @@ return this.initRefresh(); }; - Notes.prototype.handleCreateChanges = function(note) { if (typeof note === 'undefined') { return; @@ -294,7 +292,6 @@ } }; - /* Check if note does not exists on page */ @@ -307,7 +304,6 @@ return this.view === 'parallel'; }; - /* Render note in discussion area. @@ -358,7 +354,6 @@ return this.updateNotesCount(1); }; - /* Called in response the main target form has been successfully submitted. @@ -390,7 +385,6 @@ return form.find(".js-note-text").trigger("input"); }; - /* Shows the main form and does some setup on it. @@ -415,7 +409,6 @@ return this.parentTimeline = form.parents('.timeline'); }; - /* General note form setup. @@ -432,7 +425,6 @@ return new Autosave(textarea, ["Note", form.find("#note_noteable_type").val(), form.find("#note_noteable_id").val(), form.find("#note_commit_id").val(), form.find("#note_type").val(), form.find("#note_line_code").val(), form.find("#note_position").val()]); }; - /* Called in response to the new note form being submitted @@ -448,7 +440,6 @@ return new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', this.parentTimeline); }; - /* Called in response to the new note form being submitted @@ -473,7 +464,6 @@ this.removeDiscussionNoteForm($form); }; - /* Called in response to the edit note form being submitted @@ -498,7 +488,6 @@ } }; - Notes.prototype.checkContentToAllowEditing = function($el) { var initialContent = $el.find('.original-note-content').text().trim(); var currentContent = $el.find('.note-textarea').val(); @@ -522,7 +511,6 @@ return isAllowed; }; - /* Called in response to clicking the edit note link @@ -551,7 +539,6 @@ this.putEditFormInPlace($target); }; - /* Called in response to clicking the edit note link @@ -596,7 +583,6 @@ return form.find('.js-note-text').val(form.find('form.edit-note').data('original-note')); }; - /* Called in response to deleting a note of any kind. @@ -636,7 +622,6 @@ return this.updateNotesCount(-1); }; - /* Called in response to clicking the delete attachment link @@ -653,7 +638,6 @@ return note.find(".current-note-edit-form").remove(); }; - /* Called when clicking on the "reply" button for a diff line. @@ -673,7 +657,6 @@ return this.setupDiscussionNoteForm(replyLink, form); }; - /* Shows the diff or discussion form and does some setup on it. @@ -715,7 +698,6 @@ .addClass("discussion-form js-discussion-note-form"); }; - /* Called when clicking on the "add a comment" button on the side of a diff line. @@ -772,7 +754,6 @@ } }; - /* Called in response to "cancel" on a diff note form. @@ -806,7 +787,6 @@ return this.removeDiscussionNoteForm(form); }; - /* Called after an attachment file has been selected. @@ -821,7 +801,6 @@ return form.find(".js-attachment-filename").text(filename); }; - /* Called when the tab visibility changes */ diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6 index 6dbaae25f2a..5aec9c813fe 100644 --- a/app/assets/javascripts/profile/profile.js.es6 +++ b/app/assets/javascripts/profile/profile.js.es6 @@ -36,6 +36,7 @@ } onSubmitForm(e) { + e.preventDefault(); return this.saveForm(); } diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6 index 480755899fb..6250e75d407 100644 --- a/app/assets/javascripts/search_autocomplete.js.es6 +++ b/app/assets/javascripts/search_autocomplete.js.es6 @@ -69,12 +69,17 @@ search: { fields: ['text'] }, + id: this.getSearchText, data: this.getData.bind(this), selectable: true, clicked: this.onClick.bind(this) }); } + getSearchText(selectedObject, el) { + return selectedObject.id ? selectedObject.text : ''; + } + getData(term, callback) { var _this, contents, jqXHR; _this = this; @@ -364,7 +369,7 @@ onClick(item, $el, e) { if (location.pathname.indexOf(item.url) !== -1) { - e.preventDefault(); + if (!e.metaKey) e.preventDefault(); if (!this.badgePresent) { if (item.category === 'Projects') { this.projectInputEl.val(item.id); diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js index 6f919de3da7..4ef516af8c8 100644 --- a/app/assets/javascripts/shortcuts_issuable.js +++ b/app/assets/javascripts/shortcuts_issuable.js @@ -39,29 +39,39 @@ } ShortcutsIssuable.prototype.replyWithSelectedText = function() { - var quote, replyField, selected, separator; - if (window.getSelection) { - selected = window.getSelection().toString(); - replyField = $('.js-main-target-form #note_note'); - if (selected.trim() === "") { - return; - } - // Put a '>' character before each non-empty line in the selection - quote = _.map(selected.split("\n"), function(val) { - if (val.trim() !== '') { - return "> " + val + "\n"; - } - }); - // If replyField already has some content, add a newline before our quote - separator = replyField.val().trim() !== "" && "\n" || ''; - replyField.val(function(_, current) { - return current + separator + quote.join('') + "\n"; - }); - // Trigger autosave for the added text - replyField.trigger('input'); - // Focus the input field - return replyField.focus(); + var quote, replyField, documentFragment, selected, separator; + + documentFragment = window.gl.utils.getSelectedFragment(); + if (!documentFragment) return; + + // If the documentFragment contains more than just Markdown, don't copy as GFM. + if (documentFragment.querySelector('.md, .wiki')) return; + + selected = window.gl.CopyAsGFM.nodeToGFM(documentFragment); + + replyField = $('.js-main-target-form #note_note'); + if (selected.trim() === "") { + return; } + quote = _.map(selected.split("\n"), function(val) { + return ("> " + val).trim() + "\n"; + }); + // If replyField already has some content, add a newline before our quote + separator = replyField.val().trim() !== "" && "\n\n" || ''; + replyField.val(function(_, current) { + return current + separator + quote.join('') + "\n"; + }); + + // Trigger autosave + replyField.trigger('input'); + + // Trigger autosize + var event = document.createEvent('Event'); + event.initEvent('autosize:update', true, false); + replyField.get(0).dispatchEvent(event); + + // Focus the input field + return replyField.focus(); }; ShortcutsIssuable.prototype.editIssue = function() { diff --git a/app/assets/javascripts/version_check_image.js.es6 b/app/assets/javascripts/version_check_image.js.es6 new file mode 100644 index 00000000000..1fa2b5ac399 --- /dev/null +++ b/app/assets/javascripts/version_check_image.js.es6 @@ -0,0 +1,10 @@ +(() => { + class VersionCheckImage { + static bindErrorEvent(imageElement) { + imageElement.off('error').on('error', () => imageElement.hide()); + } + } + + window.gl = window.gl || {}; + gl.VersionCheckImage = VersionCheckImage; +})(); diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index ad5cb30cc42..b195b0ef3ba 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -22,47 +22,51 @@ <div class="controls pull-right"> <div class="btn-group inline"> <div class="btn-group"> - <a + <button v-if='actions' - class="dropdown-toggle btn btn-default js-pipeline-dropdown-manual-actions" + class="dropdown-toggle btn btn-default has-tooltip js-pipeline-dropdown-manual-actions" data-toggle="dropdown" title="Manual build" - alt="Manual Build" + data-placement="top" + data-toggle="dropdown" + aria-label="Manual build" > - <span v-html='svgs.iconPlay'></span> - <i class="fa fa-caret-down"></i> - </a> + <span v-html='svgs.iconPlay' aria-hidden="true"></span> + <i class="fa fa-caret-down" aria-hidden="true"></i> + </button> <ul class="dropdown-menu dropdown-menu-align-right"> <li v-for='action in pipeline.details.manual_actions'> <a rel="nofollow" data-method="post" :href='action.path' - title="Manual build" > - <span v-html='svgs.iconPlay'></span> - <span title="Manual build">{{action.name}}</span> + <span v-html='svgs.iconPlay' aria-hidden="true"></span> + <span>{{action.name}}</span> </a> </li> </ul> </div> <div class="btn-group"> - <a + <button v-if='artifacts' - class="dropdown-toggle btn btn-default build-artifacts js-pipeline-dropdown-download" + class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download" + data-toggle="dropdown" + title="Artifacts" + data-placement="top" data-toggle="dropdown" - type="button" + aria-label="Artifacts" > - <i class="fa fa-download"></i> - <i class="fa fa-caret-down"></i> - </a> + <i class="fa fa-download" aria-hidden="true"></i> + <i class="fa fa-caret-down" aria-hidden="true"></i> + </button> <ul class="dropdown-menu dropdown-menu-align-right"> <li v-for='artifact in pipeline.details.artifacts'> <a rel="nofollow" :href='artifact.path' > - <i class="fa fa-download"></i> + <i class="fa fa-download" aria-hidden="true"></i> <span>{{download(artifact.name)}}</span> </a> </li> @@ -76,9 +80,12 @@ title="Retry" rel="nofollow" data-method="post" + data-placement="top" + data-toggle="dropdown" :href='pipeline.retry_path' + aria-label="Retry" > - <i class="fa fa-repeat"></i> + <i class="fa fa-repeat" aria-hidden="true"></i> </a> <a v-if='pipeline.flags.cancelable' @@ -86,10 +93,12 @@ title="Cancel" rel="nofollow" data-method="post" + data-placement="top" + data-toggle="dropdown" :href='pipeline.cancel_path' - data-original-title="Cancel" + aria-label="Cancel" > - <i class="fa fa-remove"></i> + <i class="fa fa-remove" aria-hidden="true"></i> </a> </div> </div> diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index 4e85f16ebc5..496df9aaced 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -82,12 +82,13 @@ data-placement="top" data-toggle="dropdown" type="button" + :aria-label='stage.title' > - <span v-html="svg"></span> - <i class="fa fa-caret-down "></i> + <span v-html="svg" aria-hidden="true"></span> + <i class="fa fa-caret-down" aria-hidden="true"></i> </button> <ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"> - <div class="arrow-up"></div> + <div class="arrow-up" aria-hidden="true"></div> <div @click='keepGraph($event)' :class="dropdownClass" diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 1982142853a..9e19b1564dc 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -4,19 +4,15 @@ ((gl) => { const pageValues = (headers) => { - const normalizedHeaders = {}; - - Object.keys(headers).forEach((e) => { - normalizedHeaders[e.toUpperCase()] = headers[e]; - }); + const normalized = gl.utils.normalizeHeaders(headers); const paginationInfo = { - perPage: +normalizedHeaders['X-PER-PAGE'], - page: +normalizedHeaders['X-PAGE'], - total: +normalizedHeaders['X-TOTAL'], - totalPages: +normalizedHeaders['X-TOTAL-PAGES'], - nextPage: +normalizedHeaders['X-NEXT-PAGE'], - previousPage: +normalizedHeaders['X-PREV-PAGE'], + perPage: +normalized['X-PER-PAGE'], + page: +normalized['X-PAGE'], + total: +normalized['X-TOTAL'], + totalPages: +normalized['X-TOTAL-PAGES'], + nextPage: +normalized['X-NEXT-PAGE'], + previousPage: +normalized['X-PREV-PAGE'], }; return paginationInfo; diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index 8392b98f0a7..1d59700543c 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -37,6 +37,8 @@ display: inline-block; margin-left: 4px; margin-bottom: 2px; + flex-shrink: 0; + -webkit-flex-shrink: 0; &.s16 { margin-right: 4px; } &.s24 { margin-right: 4px; } diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 407c800feb7..592ef0d647f 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -82,7 +82,12 @@ } .block-controls { - float: right; + display: -webkit-flex; + display: flex; + -webkit-justify-content: flex-end; + justify-content: flex-end; + -webkit-flex: 1; + flex: 1; .control { float: left; @@ -282,3 +287,8 @@ } } } + +.flex-container-block { + display: -webkit-flex; + display: flex; +} diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 755eddefa42..6bfb9a6d1cb 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -125,7 +125,8 @@ top: 100%; left: 0; z-index: 9; - width: 240px; + max-width: 280px; + min-width: 240px; margin-top: 2px; margin-bottom: 0; font-size: 14px; diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index d957ec64654..e3da467a27c 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -79,6 +79,16 @@ overflow: auto; } +%filter-dropdown-item-btn-hover { + background-color: $dropdown-hover-color; + color: $white-light; + text-decoration: none; + + .avatar { + border-color: $white-light; + } +} + .filter-dropdown-item { .btn { border: none; @@ -103,13 +113,7 @@ &:hover, &:focus { - background-color: $dropdown-hover-color; - color: $white-light; - text-decoration: none; - - .avatar { - border-color: $white-light; - } + @extend %filter-dropdown-item-btn-hover; } } @@ -128,11 +132,18 @@ display: flex; -webkit-flex-direction: column; flex-direction: column; + + &> span { + white-space: normal; + word-break: break-all; + } } } -.hint-dropdown { - width: 250px; +.filter-dropdown-item.dropdown-active { + .btn { + @extend %filter-dropdown-item-btn-hover; + } } .filter-dropdown-loading { diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 1c6698ad0c6..426596027de 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -155,7 +155,8 @@ ul.content-list { } > .btn, - > .btn-group { + > .btn-group, + > .dropdown.inline { margin-right: $gl-padding-top; display: inline-block; margin-top: 3px; diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index cb923166b25..6f2e746d4b0 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -13,6 +13,8 @@ $dark-main-bg: #1d1f21; $dark-main-color: #1d1f21; $dark-line-color: #c5c8c6; $dark-line-num-color: rgba(255, 255, 255, 0.3); +$dark-line-num-color-new: #627165; +$dark-line-num-color-old: #806565; $dark-diff-not-empty-bg: #557; $dark-highlight-bg: #ffe792; $dark-highlight-color: $black; @@ -89,7 +91,6 @@ $dark-il: #de935f; .diff-line-num, .diff-line-num a { - color: $dark-main-color; color: $dark-line-num-color; } @@ -121,11 +122,21 @@ $dark-il: #de935f; .diff-line-num.new, .line_content.new { @include diff_background($dark-new-bg, $dark-new-idiff, $dark-border); + + &::before, + a { + color: $dark-line-num-color-new; + } } .diff-line-num.old, .line_content.old { @include diff_background($dark-old-bg, $dark-old-idiff, $dark-border); + + &::before, + a { + color: $dark-line-num-color-old; + } } .line_content.match { diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index d8510baad8a..2144a5f7466 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -7,6 +7,8 @@ $monokai-bg: #272822; $monokai-border: #555; $monokai-text-color: #f8f8f2; $monokai-line-num-color: rgba(255, 255, 255, 0.3); +$monokai-line-num-color-new: #707565; +$monokai-line-num-color-old: #7e736f; $monokai-line-empty-bg: #49483e; $monokai-line-empty-border: darken($monokai-line-empty-bg, 15%); $monokai-diff-border: #808080; @@ -120,11 +122,21 @@ $monokai-gi: #a6e22e; .diff-line-num.new, .line_content.new { @include diff_background($monokai-new-bg, $monokai-new-idiff, $monokai-diff-border); + + &::before, + a { + color: $monokai-line-num-color-new; + } } .diff-line-num.old, .line_content.old { @include diff_background($monokai-old-bg, $monokai-old-idiff, $monokai-diff-border); + + &::before, + a { + color: $monokai-line-num-color-old; + } } .line_content.match { diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index 874aecb5e16..2cb1d18f12f 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -13,6 +13,8 @@ $solarized-dark-pre-color: #93a1a1; $solarized-dark-pre-border: #113b46; $solarized-dark-line-bg: #002b36; $solarized-dark-line-color: rgba(255, 255, 255, 0.3); +$solarized-dark-line-color-new: #5a766c; +$solarized-dark-line-color-old: #7a6c71; $solarized-dark-highlight: #094554; $solarized-dark-hll-bg: #174652; $solarized-dark-c: #586e75; @@ -124,11 +126,21 @@ $solarized-dark-il: #2aa198; .diff-line-num.new, .line_content.new { @include diff_background($solarized-dark-new-bg, $solarized-dark-new-idiff, $solarized-dark-border); + + &::before, + a { + color: $solarized-dark-line-color-new; + } } .diff-line-num.old, .line_content.old { @include diff_background($solarized-dark-old-bg, $solarized-dark-old-idiff, $solarized-dark-border); + + &::before, + a { + color: $solarized-dark-line-color-old; + } } .line_content.match { diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index 499a1c108b8..b72c4326730 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -13,6 +13,9 @@ $solarized-light-pre-bg: #002b36; $solarized-light-pre-bg: #fdf6e3; $solarized-light-pre-color: #586e75; $solarized-light-line-bg: #fdf6e3; +$solarized-light-line-color: rgba(0, 0, 0, 0.3); +$solarized-light-line-color-new: #a1a080; +$solarized-light-line-color-old: #ad9186; $solarized-light-highlight: #eee8d5; $solarized-light-hll-bg: #ddd8c5; $solarized-light-c: #93a1a1; @@ -98,7 +101,7 @@ $solarized-light-il: #2aa198; .diff-line-num, .diff-line-num a { - color: $black-transparent; + color: $solarized-light-line-color; } // Code itself @@ -130,11 +133,21 @@ $solarized-light-il: #2aa198; .line_content.new { @include diff_background($solarized-light-new-bg, $solarized-light-new-idiff, $solarized-light-border); + + &::before, + a { + color: $solarized-light-line-color-new; + } } .diff-line-num.old, .line_content.old { @include diff_background($solarized-light-old-bg, $solarized-light-old-idiff, $solarized-light-border); + + &::before, + a { + color: $solarized-light-line-color-old; + } } .line_content.match { diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index b425c78e0d5..398fbfd3b18 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -108,11 +108,19 @@ $white-gc-bg: #eaf2f5; &.old { background-color: $line-number-old; border-color: $line-removed-dark; + + a { + color: scale-color($line-number-old,$red: -30%, $green: -30%, $blue: -30%); + } } &.new { background-color: $line-number-new; border-color: $line-added-dark; + + a { + color: scale-color($line-number-new,$red: -30%, $green: -30%, $blue: -30%); + } } &.hll:not(.empty-cell) { @@ -125,6 +133,10 @@ $white-gc-bg: #eaf2f5; &.old { background-color: $line-removed; + &::before { + color: scale-color($line-number-old,$red: -30%, $green: -30%, $blue: -30%); + } + span.idiff { background-color: $line-removed-dark; } @@ -133,6 +145,10 @@ $white-gc-bg: #eaf2f5; &.new { background-color: $line-added; + &::before { + color: scale-color($line-number-new,$red: -30%, $green: -30%, $blue: -30%); + } + span.idiff { background-color: $line-added-dark; } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 93cc5a8cf0a..4ef95d27f4f 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -26,10 +26,6 @@ border: 0; } } - - .container-fluid { - @extend .fixed-width-container; - } } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 45ff9f7ff5f..ab68b360f93 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -420,10 +420,6 @@ .merge-request-tabs-holder { background-color: $white-light; - .container-limited { - max-width: $limited-layout-width; - } - &.affix { top: 100px; left: 0; @@ -433,10 +429,26 @@ @media (max-width: $screen-xs-max) { right: 0; } + + .merge-request-tabs-container { + padding-left: $gl-padding; + padding-right: $gl-padding; + } } +} - &:not(.affix) .container-fluid { - padding-left: 0; - padding-right: 0; +.limit-container-width { + .merge-request-tabs-container { + max-width: $limited-layout-width; + margin-left: auto; + margin-right: auto; + } +} + +.limit-container-width:not(.container-limited) { + .merge-request-tabs-holder:not(.affix) { + .merge-request-tabs-container { + max-width: $limited-layout-width - ($gl-padding * 2); + } } } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 8dff22e32bd..47dfc22d533 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -203,6 +203,10 @@ position: relative; margin-right: 6px; + .tooltip { + white-space: nowrap; + } + .tooltip-inner { padding: 3px 4px; } @@ -210,9 +214,9 @@ &:not(:last-child) { &::after { content: ''; - width: 8px; + width: 7px; position: absolute; - right: -8px; + right: -7px; top: 10px; border-bottom: 2px solid $border-color; } @@ -288,6 +292,10 @@ } } } + + .tooltip { + white-space: nowrap; + } } .build-link { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index cd0839e58ea..1b0bf4554e6 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -929,8 +929,32 @@ pre.light-well { .variables-table { table-layout: fixed; + &.table-responsive { + border: none; + } + .variable-key { - width: 30%; + width: 300px; + max-width: 300px; + overflow: hidden; + word-wrap: break-word; + + // override bootstrap + white-space: normal!important; + + @media (max-width: $screen-sm-max) { + width: 150px; + max-width: 150px; + } + } + + .variable-value { + @media(max-width: $screen-xs-max) { + width: 150px; + max-width: 150px; + overflow: hidden; + word-wrap: break-word; + } } } diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 4cce1c363eb..948921efc0b 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -32,6 +32,10 @@ .last-commit { @include str-truncated(506px); + .fa-angle-right { + margin-left: 5px; + } + @media (min-width: $screen-md-min) and (max-width: $screen-md-max) { @include str-truncated(450px); } diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 1b4987dd738..543d5eac504 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -5,7 +5,11 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController end def update - if @application_setting.update_attributes(application_setting_params) + successful = ApplicationSettings::UpdateService + .new(@application_setting, current_user, application_setting_params) + .execute + + if successful redirect_to admin_application_settings_path, notice: 'Application settings saved successfully' else diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index a62c6211372..26e17a7553e 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -22,6 +22,7 @@ class Explore::ProjectsController < Explore::ApplicationController def trending @projects = filter_projects(Project.trending) + @projects = @projects.sort(@sort = params[:sort]) @projects = @projects.page(params[:page]) respond_to do |format| diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 9b45ed6b6af..886934a3f67 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -94,7 +94,7 @@ class Projects::BuildsController < Projects::ApplicationController private def build - @build ||= project.builds.find_by!(id: params[:id]).present(user: current_user) + @build ||= project.builds.find_by!(id: params[:id]).present(current_user: current_user) end def build_path(build) diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 8714349e27f..70845617d3c 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -109,12 +109,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController end def repository + wiki? ? project.wiki.repository : project.repository + end + + def wiki? + return @wiki if defined?(@wiki) + _, suffix = project_id_with_suffix - if suffix == '.wiki.git' - project.wiki.repository - else - project.repository - end + @wiki = suffix == '.wiki.git' end def render_not_found diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 9184dcccac5..278098fcc58 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -84,7 +84,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController end def access - @access ||= Gitlab::GitAccess.new(user, project, 'http', authentication_abilities: authentication_abilities) + @access ||= access_klass.new(user, project, 'http', authentication_abilities: authentication_abilities) end def access_check @@ -102,4 +102,8 @@ class Projects::GitHttpController < Projects::GitHttpClientController access_check.allowed? end + + def access_klass + @access_klass ||= wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess + end end diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 3602b3d5e58..667f4870c7a 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -32,12 +32,6 @@ class Projects::RefsController < Projects::ApplicationController redirect_to new_path end - format.js do - @ref = params[:ref] - define_tree_vars - tree - render "tree" - end end end diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb index aa54ee07bdc..2aa0449c46e 100644 --- a/app/helpers/compare_helper.rb +++ b/app/helpers/compare_helper.rb @@ -3,7 +3,7 @@ module CompareHelper from.present? && to.present? && from != to && - project.feature_available?(:merge_requests, current_user) && + can?(current_user, :create_merge_request, project) && project.repository.branch_names.include?(from) && project.repository.branch_names.include?(to) end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 77dc9e7d538..926c9703628 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -14,7 +14,7 @@ module GroupsHelper def group_title(group, name = nil, url = nil) full_title = '' - group.parents.each do |parent| + group.ancestors.each do |parent| full_title += link_to(simple_sanitize(parent.name), group_path(parent)) full_title += ' / '.html_safe end diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb index a674564c4ec..456598b4c28 100644 --- a/app/helpers/version_check_helper.rb +++ b/app/helpers/version_check_helper.rb @@ -1,7 +1,8 @@ module VersionCheckHelper def version_status_badge if Rails.env.production? && current_application_settings.version_check_enabled - image_tag VersionCheck.new.url + image_url = VersionCheck.new.url + image_tag image_url, class: 'js-version-status-badge' end end end diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index 0d20c9092c4..46fa6fd9f6d 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -38,6 +38,14 @@ module Emails mail_answer_thread(@snippet, note_thread_options(recipient_id)) end + def note_personal_snippet_email(recipient_id, note_id) + setup_note_mail(note_id, recipient_id) + + @snippet = @note.noteable + @target_url = snippet_url(@note.noteable) + mail_answer_thread(@snippet, note_thread_options(recipient_id)) + end + private def note_target_url_options diff --git a/app/models/ability.rb b/app/models/ability.rb index fa8f8bc3a5f..ad6c588202e 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -22,6 +22,17 @@ class Ability end end + # Given a list of users and a snippet this method returns the users that can + # read the given snippet. + def users_that_can_read_personal_snippet(users, snippet) + case snippet.visibility_level + when Snippet::INTERNAL, Snippet::PUBLIC + users + when Snippet::PRIVATE + users.include?(snippet.author) ? [snippet.author] : [] + end + end + # Returns an Array of Issues that can be read by the given user. # # issues - The issues to reduce down to those readable by the user. diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 8fab77cda0a..e33a58d3771 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -13,6 +13,49 @@ class ApplicationSetting < ActiveRecord::Base [\r\n] # any number of newline characters }x + DEFAULTS_CE = { + after_sign_up_text: nil, + akismet_enabled: false, + container_registry_token_expire_delay: 5, + default_branch_protection: Settings.gitlab['default_branch_protection'], + default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], + default_projects_limit: Settings.gitlab['default_projects_limit'], + default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], + disabled_oauth_sign_in_sources: [], + domain_whitelist: Settings.gitlab['domain_whitelist'], + gravatar_enabled: Settings.gravatar['enabled'], + help_page_text: nil, + housekeeping_bitmaps_enabled: true, + housekeeping_enabled: true, + housekeeping_full_repack_period: 50, + housekeeping_gc_period: 200, + housekeeping_incremental_repack_period: 10, + import_sources: Gitlab::ImportSources.values, + koding_enabled: false, + koding_url: nil, + max_artifacts_size: Settings.artifacts['max_size'], + max_attachment_size: Settings.gitlab['max_attachment_size'], + plantuml_enabled: false, + plantuml_url: nil, + recaptcha_enabled: false, + repository_checks_enabled: true, + repository_storages: ['default'], + require_two_factor_authentication: false, + restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], + session_expire_delay: Settings.gitlab['session_expire_delay'], + send_user_confirmation_email: false, + shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], + shared_runners_text: nil, + sidekiq_throttling_enabled: false, + sign_in_text: nil, + signin_enabled: Settings.gitlab['signin_enabled'], + signup_enabled: Settings.gitlab['signup_enabled'], + two_factor_grace_period: 48, + user_default_external: false + } + + DEFAULTS = DEFAULTS_CE + serialize :restricted_visibility_levels serialize :import_sources serialize :disabled_oauth_sign_in_sources, Array @@ -163,46 +206,7 @@ class ApplicationSetting < ActiveRecord::Base end def self.create_from_defaults - create( - default_projects_limit: Settings.gitlab['default_projects_limit'], - default_branch_protection: Settings.gitlab['default_branch_protection'], - signup_enabled: Settings.gitlab['signup_enabled'], - signin_enabled: Settings.gitlab['signin_enabled'], - gravatar_enabled: Settings.gravatar['enabled'], - sign_in_text: nil, - after_sign_up_text: nil, - help_page_text: nil, - shared_runners_text: nil, - restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], - max_attachment_size: Settings.gitlab['max_attachment_size'], - session_expire_delay: Settings.gitlab['session_expire_delay'], - default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], - default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], - domain_whitelist: Settings.gitlab['domain_whitelist'], - import_sources: Gitlab::ImportSources.values, - shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], - max_artifacts_size: Settings.artifacts['max_size'], - require_two_factor_authentication: false, - two_factor_grace_period: 48, - recaptcha_enabled: false, - akismet_enabled: false, - koding_enabled: false, - koding_url: nil, - plantuml_enabled: false, - plantuml_url: nil, - repository_checks_enabled: true, - disabled_oauth_sign_in_sources: [], - send_user_confirmation_email: false, - container_registry_token_expire_delay: 5, - repository_storages: ['default'], - user_default_external: false, - sidekiq_throttling_enabled: false, - housekeeping_enabled: true, - housekeeping_bitmaps_enabled: true, - housekeeping_incremental_repack_period: 10, - housekeeping_full_repack_period: 50, - housekeeping_gc_period: 200, - ) + create(DEFAULTS) end def home_page_url_column_exist diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb index 90bd6490a02..a600f9c14c5 100644 --- a/app/models/concerns/cache_markdown_field.rb +++ b/app/models/concerns/cache_markdown_field.rb @@ -51,6 +51,10 @@ module CacheMarkdownField CACHING_CLASSES.map(&:constantize) end + def skip_project_check? + false + end + extend ActiveSupport::Concern included do @@ -112,7 +116,8 @@ module CacheMarkdownField invalidation_method = "#{html_field}_invalidated?".to_sym define_method(cache_method) do - html = Banzai::Renderer.cacheless_render_field(self, markdown_field) + options = { skip_project_check: skip_project_check? } + html = Banzai::Renderer.cacheless_render_field(self, markdown_field, options) __send__("#{html_field}=", html) true end diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 8ab0401d288..ef2c1e5d414 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -49,7 +49,11 @@ module Mentionable self.class.mentionable_attrs.each do |attr, options| text = __send__(attr) - options = options.merge(cache_key: [self, attr], author: author) + options = options.merge( + cache_key: [self, attr], + author: author, + skip_project_check: skip_project_check? + ) extractor.analyze(text, options) end @@ -121,4 +125,8 @@ module Mentionable def cross_reference_exists?(target) SystemNoteService.cross_reference_exists?(target, local_reference) end + + def skip_project_check? + false + end end diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb index 70740c76e43..4865c0a14b1 100644 --- a/app/models/concerns/participable.rb +++ b/app/models/concerns/participable.rb @@ -96,6 +96,11 @@ module Participable participants.merge(ext.users) - Ability.users_that_can_read_project(participants.to_a, project) + case self + when PersonalSnippet + Ability.users_that_can_read_personal_snippet(participants.to_a, self) + else + Ability.users_that_can_read_project(participants.to_a, project) + end end end diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb index 1108a64c59e..2b93aa30c0f 100644 --- a/app/models/concerns/routable.rb +++ b/app/models/concerns/routable.rb @@ -60,6 +60,21 @@ module Routable joins(:route).where(wheres.join(' OR ')) end end + + # Builds a relation to find multiple objects that are nested under user membership + # + # Usage: + # + # Klass.member_descendants(1) + # + # Returns an ActiveRecord::Relation. + def member_descendants(user_id) + joins(:route). + joins("INNER JOIN routes r2 ON routes.path LIKE CONCAT(r2.path, '/%') + INNER JOIN members ON members.source_id = r2.source_id + AND members.source_type = r2.source_type"). + where('members.user_id = ?', user_id) + end end private diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb index ebc75100a54..25e2d8ea24e 100644 --- a/app/models/concerns/taskable.rb +++ b/app/models/concerns/taskable.rb @@ -11,10 +11,10 @@ module Taskable INCOMPLETE = 'incomplete'.freeze ITEM_PATTERN = / ^ - (?:\s*[-+*]|(?:\d+\.))? # optional list prefix - \s* # optional whitespace prefix - (\[\s\]|\[[xX]\]) # checkbox - (\s.+) # followed by whitespace and some text. + \s*(?:[-+*]|(?:\d+\.)) # list prefix required - task item has to be always in a list + \s+ # whitespace prefix has to be always presented for a list item + (\[\s\]|\[[xX]\]) # checkbox + (\s.+) # followed by whitespace and some text. /x def self.get_tasks(content) diff --git a/app/models/group.rb b/app/models/group.rb index 99675ddb366..4cdfd022094 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -201,7 +201,7 @@ class Group < Namespace end def members_with_parents - GroupMember.where(requested_at: nil, source_id: parents.map(&:id).push(id)) + GroupMember.where(requested_at: nil, source_id: ancestors.map(&:id).push(id)) end def users_with_parents diff --git a/app/models/member.rb b/app/models/member.rb index c585e0b450e..26a6054e00d 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -68,9 +68,9 @@ class Member < ActiveRecord::Base after_create :send_request, if: :request?, unless: :importing? after_create :create_notification_setting, unless: [:pending?, :importing?] after_create :post_create_hook, unless: [:pending?, :importing?] - after_create :refresh_member_authorized_projects, if: :importing? after_update :post_update_hook, unless: [:pending?, :importing?] after_destroy :post_destroy_hook, unless: :pending? + after_commit :refresh_member_authorized_projects delegate :name, :username, :email, to: :user, prefix: true @@ -147,8 +147,6 @@ class Member < ActiveRecord::Base member.save end - UserProjectAccessChangedService.new(user.id).execute if user.is_a?(User) - member end @@ -275,23 +273,27 @@ class Member < ActiveRecord::Base end def post_create_hook - UserProjectAccessChangedService.new(user.id).execute system_hook_service.execute_hooks_for(self, :create) end def post_update_hook - UserProjectAccessChangedService.new(user.id).execute if access_level_changed? + # override in sub class end def post_destroy_hook - refresh_member_authorized_projects system_hook_service.execute_hooks_for(self, :destroy) end + # Refreshes authorizations of the current member. + # + # This method schedules a job using Sidekiq and as such **must not** be called + # in a transaction. Doing so can lead to the job running before the + # transaction has been committed, resulting in the job either throwing an + # error or not doing any meaningful work. def refresh_member_authorized_projects - # If user/source is being destroyed, project access are gonna be destroyed eventually - # because of DB foreign keys, so we shouldn't bother with refreshing after each - # member is destroyed through association + # If user/source is being destroyed, project access are going to be + # destroyed eventually because of DB foreign keys, so we shouldn't bother + # with refreshing after each member is destroyed through association return if destroyed_by_association.present? UserProjectAccessChangedService.new(user_id).execute diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index cd5b345bae5..6753504acff 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -865,9 +865,11 @@ class MergeRequest < ActiveRecord::Base paths: paths ) - active_diff_notes.each do |note| - service.execute(note) - Gitlab::Timeless.timeless(note, &:save) + transaction do + active_diff_notes.each do |note| + service.execute(note) + Gitlab::Timeless.timeless(note, &:save) + end end end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index dd33975731f..67d8c1c2e4c 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -4,6 +4,7 @@ class Namespace < ActiveRecord::Base include CacheMarkdownField include Sortable include Gitlab::ShellAdapter + include Gitlab::CurrentSettings include Routable cache_markdown_field :description, pipeline: :description @@ -176,6 +177,10 @@ class Namespace < ActiveRecord::Base end end + def shared_runners_enabled? + projects.with_shared_runners.any? + end + def full_name @full_name ||= if parent @@ -185,8 +190,26 @@ class Namespace < ActiveRecord::Base end end - def parents - @parents ||= parent ? parent.parents + [parent] : [] + # Scopes the model on ancestors of the record + def ancestors + if parent_id + path = route.path + paths = [] + + until path.blank? + path = path.rpartition('/').first + paths << path + end + + self.class.joins(:route).where('routes.path IN (?)', paths).reorder('routes.path ASC') + else + self.class.none + end + end + + # Scopes the model on direct and indirect children of the record + def descendants + self.class.joins(:route).where('routes.path LIKE ?', "#{route.path}/%").reorder('routes.path ASC') end private diff --git a/app/models/note.rb b/app/models/note.rb index 0c1b05dabf2..bf090a0438c 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -43,7 +43,8 @@ class Note < ActiveRecord::Base delegate :name, :email, to: :author, prefix: true delegate :title, to: :noteable, allow_nil: true - validates :note, :project, presence: true + validates :note, presence: true + validates :project, presence: true, unless: :for_personal_snippet? # Attachments are deprecated and are handled by Markdown uploader validates :attachment, file_size: { maximum: :max_attachment_size } @@ -53,7 +54,7 @@ class Note < ActiveRecord::Base validates :commit_id, presence: true, if: :for_commit? validates :author, presence: true - validate unless: [:for_commit?, :importing?] do |note| + validate unless: [:for_commit?, :importing?, :for_personal_snippet?] do |note| unless note.noteable.try(:project) == note.project errors.add(:invalid_project, 'Note and noteable project mismatch') end @@ -83,7 +84,7 @@ class Note < ActiveRecord::Base after_initialize :ensure_discussion_id before_validation :nullify_blank_type, :nullify_blank_line_code before_validation :set_discussion_id - after_save :keep_around_commit + after_save :keep_around_commit, unless: :for_personal_snippet? class << self def model_name @@ -165,6 +166,14 @@ class Note < ActiveRecord::Base noteable_type == "Snippet" end + def for_personal_snippet? + noteable.is_a?(PersonalSnippet) + end + + def skip_project_check? + for_personal_snippet? + end + # override to return commits, which are not active record def noteable if for_commit? @@ -220,6 +229,10 @@ class Note < ActiveRecord::Base note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1] end + def to_ability_name + for_personal_snippet? ? 'personal_snippet' : noteable_type.underscore + end + private def keep_around_commit diff --git a/app/models/project.rb b/app/models/project.rb index cd35601d76b..59faf35e051 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -224,6 +224,7 @@ class Project < ActiveRecord::Base scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') } scope :with_statistics, -> { includes(:statistics) } + scope :with_shared_runners, -> { where(shared_runners_enabled: true) } # "enabled" here means "not disabled". It includes private features! scope :with_feature_enabled, ->(feature) { @@ -1096,12 +1097,20 @@ class Project < ActiveRecord::Base project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED) end + def shared_runners_available? + shared_runners_enabled? + end + + def shared_runners + shared_runners_available? ? Ci::Runner.shared : Ci::Runner.none + end + def any_runners?(&block) if runners.active.any?(&block) return true end - shared_runners_enabled? && Ci::Runner.shared.active.any?(&block) + shared_runners.active.any?(&block) end def valid_runners_token?(token) diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb index 6149c35cc61..5cb6b0c527d 100644 --- a/app/models/project_group_link.rb +++ b/app/models/project_group_link.rb @@ -16,8 +16,7 @@ class ProjectGroupLink < ActiveRecord::Base validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true validate :different_group - after_create :refresh_group_members_authorized_projects - after_destroy :refresh_group_members_authorized_projects + after_commit :refresh_group_members_authorized_projects def self.access_options Gitlab::Access.options diff --git a/app/models/repository.rb b/app/models/repository.rb index 43dba86e5ed..d77b7692d75 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1188,7 +1188,18 @@ class Repository end def tags_sorted_by_committed_date - tags.sort_by { |tag| tag.dereferenced_target.committed_date } + tags.sort_by do |tag| + # Annotated tags can point to any object (e.g. a blob), but generally + # tags point to a commit. If we don't have a commit, then just default + # to putting the tag at the end of the list. + target = tag.dereferenced_target + + if target + target.committed_date + else + Time.now + end + end end def keep_around_ref_name(sha) diff --git a/app/models/route.rb b/app/models/route.rb index caf596efa79..dd171fdb069 100644 --- a/app/models/route.rb +++ b/app/models/route.rb @@ -8,15 +8,16 @@ class Route < ActiveRecord::Base presence: true, uniqueness: { case_sensitive: false } - after_update :rename_children, if: :path_changed? + after_update :rename_descendants, if: :path_changed? - def rename_children + def rename_descendants # We update each row separately because MySQL does not have regexp_replace. # rubocop:disable Rails/FindEach Route.where('path LIKE ?', "#{path_was}/%").each do |route| # Note that update column skips validation and callbacks. - # We need this to avoid recursive call of rename_children method + # We need this to avoid recursive call of rename_descendants method route.update_column(:path, route.path.sub(path_was, path)) end + # rubocop:enable Rails/FindEach end end diff --git a/app/models/user.rb b/app/models/user.rb index 06dd98a3188..54f5388eb2c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -179,8 +179,8 @@ class User < ActiveRecord::Base scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members WHERE user_id IS NOT NULL AND requested_at IS NULL)') } scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) } - scope :order_recent_sign_in, -> { reorder(last_sign_in_at: :desc) } - scope :order_oldest_sign_in, -> { reorder(last_sign_in_at: :asc) } + scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('last_sign_in_at', 'DESC')) } + scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('last_sign_in_at', 'ASC')) } def self.with_two_factor joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id"). @@ -439,6 +439,15 @@ class User < ActiveRecord::Base Group.where("namespaces.id IN (#{union.to_sql})") end + def nested_groups + Group.member_descendants(id) + end + + def nested_projects + Project.joins(:namespace).where('namespaces.parent_id IS NOT NULL'). + member_descendants(id) + end + def refresh_authorized_projects Users::RefreshAuthorizedProjectsService.new(self).execute end diff --git a/app/presenters/README.md b/app/presenters/README.md index 3edd63451e7..a4d592b54d6 100644 --- a/app/presenters/README.md +++ b/app/presenters/README.md @@ -113,7 +113,7 @@ detects the presenter based on the presented subject's class. class Projects::LabelsController < Projects::ApplicationController def edit @label = Gitlab::View::Presenter::Factory - .new(@label, user: current_user) + .new(@label, current_user: current_user) .fabricate! end end @@ -132,7 +132,7 @@ and then in the controller: ```ruby class Projects::LabelsController < Projects::ApplicationController def edit - @label = @label.present(user: current_user) + @label = @label.present(current_user: current_user) end end ``` @@ -147,7 +147,7 @@ end You can also present the model in the view: ```ruby -- label = @label.present(current_user) +- label = @label.present(current_user: current_user) %div{ class: label.text_color } = render partial: label, label: label diff --git a/app/services/application_settings/base_service.rb b/app/services/application_settings/base_service.rb new file mode 100644 index 00000000000..2bcc7d7c08b --- /dev/null +++ b/app/services/application_settings/base_service.rb @@ -0,0 +1,7 @@ +module ApplicationSettings + class BaseService < ::BaseService + def initialize(application_setting, user, params = {}) + @application_setting, @current_user, @params = application_setting, user, params.dup + end + end +end diff --git a/app/services/application_settings/update_service.rb b/app/services/application_settings/update_service.rb new file mode 100644 index 00000000000..61589a07250 --- /dev/null +++ b/app/services/application_settings/update_service.rb @@ -0,0 +1,7 @@ +module ApplicationSettings + class UpdateService < ApplicationSettings::BaseService + def execute + @application_setting.update(@params) + end + end +end diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb index 74b5ebf372b..6f03bf2be13 100644 --- a/app/services/ci/register_build_service.rb +++ b/app/services/ci/register_build_service.rb @@ -2,48 +2,72 @@ module Ci # This class responsible for assigning # proper pending build to runner on runner API request class RegisterBuildService - def execute(current_runner) - builds = Ci::Build.pending.unstarted + include Gitlab::CurrentSettings + attr_reader :runner + + Result = Struct.new(:build, :valid?) + + def initialize(runner) + @runner = runner + end + + def execute builds = - if current_runner.shared? - builds. - # don't run projects which have not enabled shared runners and builds - joins(:project).where(projects: { shared_runners_enabled: true }). - joins('LEFT JOIN project_features ON ci_builds.gl_project_id = project_features.project_id'). - - # this returns builds that are ordered by number of running builds - # we prefer projects that don't use shared runners at all - joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id"). - where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0'). - order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC') + if runner.shared? + builds_for_shared_runner else - # do run projects which are only assigned to this runner (FIFO) - builds.where(project: current_runner.projects.with_builds_enabled).order('created_at ASC') + builds_for_specific_runner end build = builds.find do |build| - current_runner.can_pick?(build) + runner.can_pick?(build) end if build # In case when 2 runners try to assign the same build, second runner will be declined # with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method. - build.runner_id = current_runner.id + build.runner_id = runner.id build.run! end - build + Result.new(build, true) rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError - nil + Result.new(build, false) end private + def builds_for_shared_runner + new_builds. + # don't run projects which have not enabled shared runners and builds + joins(:project).where(projects: { shared_runners_enabled: true }). + joins('LEFT JOIN project_features ON ci_builds.gl_project_id = project_features.project_id'). + where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0'). + + # Implement fair scheduling + # this returns builds that are ordered by number of running builds + # we prefer projects that don't use shared runners at all + joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id"). + order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC') + end + + def builds_for_specific_runner + new_builds.where(project: runner.projects.with_builds_enabled).order('created_at ASC') + end + def running_builds_for_shared_runners Ci::Build.running.where(runner: Ci::Runner.shared). group(:gl_project_id).select(:gl_project_id, 'count(*) AS running_builds') end + + def new_builds + Ci::Build.pending.unstarted + end + + def shared_runner_build_limits_feature_enabled? + ENV['DISABLE_SHARED_RUNNER_BUILD_MINUTES_LIMIT'].to_s != 'true' + end end end diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index cdd765c85eb..b4f8b33d564 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -3,9 +3,10 @@ module Notes def execute merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha) - note = project.notes.new(params) - note.author = current_user - note.system = false + note = Note.new(params) + note.project = project + note.author = current_user + note.system = false if note.award_emoji? noteable = note.noteable diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb index e4cd3fc7833..6a10e172483 100644 --- a/app/services/notes/post_process_service.rb +++ b/app/services/notes/post_process_service.rb @@ -10,6 +10,9 @@ module Notes # Skip system notes, like status changes and cross-references and awards unless @note.system? EventCreateService.new.leave_note(@note, @note.author) + + return if @note.for_personal_snippet? + @note.create_cross_references! execute_note_hooks end diff --git a/app/services/notes/slash_commands_service.rb b/app/services/notes/slash_commands_service.rb index aaea9717fc4..56913568cae 100644 --- a/app/services/notes/slash_commands_service.rb +++ b/app/services/notes/slash_commands_service.rb @@ -12,7 +12,7 @@ module Notes def self.supported?(note, current_user) noteable_update_service(note) && current_user && - current_user.can?(:"update_#{note.noteable_type.underscore}", note.noteable) + current_user.can?(:"update_#{note.to_ability_name}", note.noteable) end def supported?(note) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index c3b61e68eab..f74e6cac174 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -178,8 +178,15 @@ class NotificationService recipients = [] mentioned_users = note.mentioned_users + + ability, subject = if note.for_personal_snippet? + [:read_personal_snippet, note.noteable] + else + [:read_project, note.project] + end + mentioned_users.select! do |user| - user.can?(:read_project, note.project) + user.can?(ability, subject) end # Add all users participating in the thread (author, assignee, comment authors) @@ -192,11 +199,13 @@ class NotificationService recipients = recipients.concat(participants) - # Merge project watchers - recipients = add_project_watchers(recipients, note.project) + unless note.for_personal_snippet? + # Merge project watchers + recipients = add_project_watchers(recipients, note.project) - # Merge project with custom notification - recipients = add_custom_notifications(recipients, note.project, :new_note) + # Merge project with custom notification + recipients = add_custom_notifications(recipients, note.project, :new_note) + end # Reject users with Mention notification level, except those mentioned in _this_ note. recipients = reject_mention_users(recipients - mentioned_users, note.project) @@ -211,8 +220,7 @@ class NotificationService recipients.delete(note.author) recipients = recipients.uniq - # build notify method like 'note_commit_email' - notify_method = "note_#{note.noteable_type.underscore}_email".to_sym + notify_method = "note_#{note.to_ability_name}_email".to_sym recipients.each do |recipient| mailer.send(notify_method, recipient.id, note.id).deliver_later diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 159f46cd465..c7cce0c55b9 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -22,17 +22,7 @@ module Projects return @project end - # Set project name from path - if @project.name.present? && @project.path.present? - # if both name and path set - everything is ok - elsif @project.path.present? - # Set project name from path - @project.name = @project.path.dup - elsif @project.name.present? - # For compatibility - set path from name - # TODO: remove this in 8.0 - @project.path = @project.name.dup.parameterize - end + set_project_name_from_path # get namespace id namespace_id = params[:namespace_id] @@ -144,5 +134,19 @@ module Projects service.save! end end + + def set_project_name_from_path + # Set project name from path + if @project.name.present? && @project.path.present? + # if both name and path set - everything is ok + elsif @project.path.present? + # Set project name from path + @project.name = @project.path.dup + elsif @project.name.present? + # For compatibility - set path from name + # TODO: remove this in 8.0 + @project.path = @project.name.dup.parameterize + end + end end end diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 842e23eb6b6..55d9cb13ae4 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -22,6 +22,8 @@ module Projects if project.update_attributes(params.except(:default_branch)) if project.previous_changes.include?('path') project.rename_repo + else + system_hook_service.execute_hooks_for(project, :update) end success diff --git a/app/services/user_project_access_changed_service.rb b/app/services/user_project_access_changed_service.rb index 2469b4f0d7c..d7a6804ee88 100644 --- a/app/services/user_project_access_changed_service.rb +++ b/app/services/user_project_access_changed_service.rb @@ -4,6 +4,6 @@ class UserProjectAccessChangedService end def execute - AuthorizedProjectsWorker.bulk_perform_async(@user_ids.map { |id| [id] }) + AuthorizedProjectsWorker.bulk_perform_and_wait(@user_ids.map { |id| [id] }) end end diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb index 2d211d5ebbe..fad741531ea 100644 --- a/app/services/users/refresh_authorized_projects_service.rb +++ b/app/services/users/refresh_authorized_projects_service.rb @@ -118,7 +118,8 @@ module Users user.personal_projects.select("#{user.id} AS user_id, projects.id AS project_id, #{Gitlab::Access::MASTER} AS access_level"), user.groups_projects.select_for_project_authorization, user.projects.select_for_project_authorization, - user.groups.joins(:shared_projects).select_for_project_authorization + user.groups.joins(:shared_projects).select_for_project_authorization, + user.nested_projects.select_for_project_authorization ] Gitlab::SQL::Union.new(relations) diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml index 3132d157f29..2269fb1fd8c 100644 --- a/app/views/admin/broadcast_messages/_form.html.haml +++ b/app/views/admin/broadcast_messages/_form.html.haml @@ -4,7 +4,7 @@ - if @broadcast_message.message.present? = render_broadcast_message(@broadcast_message) - else - = "Your message here" + Your message here = form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-quick-submit js-requires-input'} do |f| = form_errors(@broadcast_message) diff --git a/app/views/admin/identities/_identity.html.haml b/app/views/admin/identities/_identity.html.haml index 7362d904b94..8c658905bd6 100644 --- a/app/views/admin/identities/_identity.html.haml +++ b/app/views/admin/identities/_identity.html.haml @@ -1,6 +1,6 @@ %tr %td - = "#{Gitlab::OAuth::Provider.label_for(identity.provider)} (#{identity.provider})" + #{Gitlab::OAuth::Provider.label_for(identity.provider)} (#{identity.provider}) %td = identity.extern_uid %td diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index 37bb6a3b0e0..124f970524e 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -11,7 +11,7 @@ that for future communication. %br Registration token is - %code{ id: 'runners-token' } #{current_application_settings.runners_registration_token} + %code#runners-token= current_application_settings.runners_registration_token .bs-callout.clearfix .pull-left diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index ca503e35623..39e103e3062 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -100,7 +100,7 @@ %td.build-link - if project = link_to ci_status_path(build.pipeline) do - %strong #{build.pipeline.short_sha} + %strong= build.pipeline.short_sha %td.timestamp - if build.finished_at diff --git a/app/views/admin/system_info/show.html.haml b/app/views/admin/system_info/show.html.haml index bfc6142067a..2e5f120c4e4 100644 --- a/app/views/admin/system_info/show.html.haml +++ b/app/views/admin/system_info/show.html.haml @@ -10,7 +10,7 @@ %h4 CPU .data - if @cpus - %h1= "#{@cpus.length} cores" + %h1 #{@cpus.length} cores - else = icon('warning', class: 'text-warning') Unable to collect CPU info @@ -19,7 +19,7 @@ %h4 Memory .data - if @memory - %h1= "#{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)}" + %h1 #{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)} - else = icon('warning', class: 'text-warning') Unable to collect memory info @@ -28,6 +28,6 @@ %h4 Disks .data - @disks.each do |disk| - %h1= "#{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])}" - %p= "#{disk[:disk_name]}" - %p= "#{disk[:mount_path]}" + %h1 #{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])} + %p= disk[:disk_name] + %p= disk[:mount_path] diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index a71240986c9..76b1291fe10 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -186,6 +186,6 @@ - if @user.solo_owned_groups.present? %p This user is currently an owner in these groups: - %strong #{@user.solo_owned_groups.map(&:name).join(', ')} + %strong= @user.solo_owned_groups.map(&:name).join(', ') %p You must transfer ownership or delete these groups before you can delete this user. diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml index 95eb9a57152..b0bee1c6204 100644 --- a/app/views/ci/lints/show.html.haml +++ b/app/views/ci/lints/show.html.haml @@ -13,7 +13,7 @@ .file-holder .file-title.clearfix Content of .gitlab-ci.yml - #ci-editor.ci-editor #{@content} + #ci-editor.ci-editor= @content = text_area_tag(:content, @content, class: 'hidden form-control span1', rows: 7, require: true) .col-sm-12 .pull-left.prepend-top-10 diff --git a/app/views/ci/status/_badge.html.haml b/app/views/ci/status/_badge.html.haml index 601fb7f0f3f..c00c7f7407e 100644 --- a/app/views/ci/status/_badge.html.haml +++ b/app/views/ci/status/_badge.html.haml @@ -1,7 +1,8 @@ - status = local_assigns.fetch(:status) +- link = local_assigns.fetch(:link, true) - css_classes = "ci-status ci-#{status.group}" -- if status.has_details? +- if link && status.has_details? = link_to status.details_path, class: css_classes do = custom_icon(status.icon) = status.text diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml index 2bce2780484..6f5d4bf2a2f 100644 --- a/app/views/discussions/_discussion.html.haml +++ b/app/views/discussions/_discussion.html.haml @@ -1,6 +1,9 @@ - expanded = discussion.expanded? %li.note.note-discussion.timeline-entry .timeline-entry-inner + .timeline-icon + = link_to user_path(discussion.author) do + = image_tag avatar_icon(discussion.author), class: "avatar s40" .timeline-content .discussion.js-toggle-container{ class: discussion.id, data: { discussion_id: discussion.id } } .discussion-header diff --git a/app/views/discussions/_parallel_diff_discussion.html.haml b/app/views/discussions/_parallel_diff_discussion.html.haml index ef16b516e2c..3a19e021643 100644 --- a/app/views/discussions/_parallel_diff_discussion.html.haml +++ b/app/views/discussions/_parallel_diff_discussion.html.haml @@ -6,7 +6,7 @@ .content{ class: ('hide' unless discussion_left.expanded?) } = render "discussions/notes", discussion: discussion_left, line_type: 'old' - else - %td.notes_line.old= "" + %td.notes_line.old= ("") %td.notes_content.parallel.old .content @@ -16,6 +16,6 @@ .content{ class: ('hide' unless discussion_right.expanded?) } = render "discussions/notes", discussion: discussion_right, line_type: 'new' - else - %td.notes_line.new= "" + %td.notes_line.new= ("") %td.notes_content.parallel.new .content diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml index 2a0e301c8dd..a196561f381 100644 --- a/app/views/doorkeeper/authorizations/new.html.haml +++ b/app/views/doorkeeper/authorizations/new.html.haml @@ -10,7 +10,7 @@ %p = icon("exclamation-triangle fw") You are an admin, which means granting access to - %strong #{@pre_auth.client.name} + %strong= @pre_auth.client.name will allow them to interact with GitLab as an admin as well. Proceed with caution. - if @pre_auth.scopes diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index bc5d3c797ac..f4c432a095a 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -25,7 +25,7 @@ .panel.panel-default .panel-heading Users with access to - %strong #{@group.name} + %strong= @group.name %span.badge= @members.total_count %ul.content-list = render partial: 'shared/members/member', collection: @members, as: :member diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index b4aa4f24d9e..6ad03a60b3a 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -18,7 +18,7 @@ .row-content-block.second-block Only issues from the - %strong #{@group.name} + %strong= @group.name group are listed here. - if current_user To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page. diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index dbbdb583a24..af73554086b 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -10,7 +10,7 @@ .row-content-block.second-block Only merge requests from - %strong #{@group.name} + %strong= @group.name group are listed here. - if current_user To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index a8fdbd8c426..cd5388fe402 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -10,7 +10,7 @@ .row-content-block Only milestones from - %strong #{@group.name} + %strong= @group.name group are listed here. .milestones diff --git a/app/views/import/_githubish_status.html.haml b/app/views/import/_githubish_status.html.haml index 864c5c0ff95..0e7f0b5ed4f 100644 --- a/app/views/import/_githubish_status.html.haml +++ b/app/views/import/_githubish_status.html.haml @@ -16,7 +16,7 @@ %colgroup.import-jobs-status-col %thead %tr - %th= "From #{provider_title}" + %th From #{provider_title} %th To GitLab %th Status %tbody diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml index 07338736bac..9999a4362c6 100644 --- a/app/views/import/fogbugz/new_user_map.html.haml +++ b/app/views/import/fogbugz/new_user_map.html.haml @@ -37,7 +37,7 @@ %tbody - @user_map.each do |id, user| %tr - %td= id + %td= (id) %td= text_field_tag "users[#{id}][name]", user[:name], class: 'form-control' %td= text_field_tag "users[#{id}][email]", user[:email], class: 'form-control' %td diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml index 97e5e51abe0..5de5da5e6a2 100644 --- a/app/views/import/fogbugz/status.html.haml +++ b/app/views/import/fogbugz/status.html.haml @@ -50,7 +50,7 @@ %td = repo.name %td.import-target - = "#{current_user.username}/#{repo.name}" + #{current_user.username}/#{repo.name} %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do Import diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml index 9f1507cade6..5e01af008be 100644 --- a/app/views/import/google_code/status.html.haml +++ b/app/views/import/google_code/status.html.haml @@ -55,7 +55,7 @@ %td = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank" %td.import-target - = "#{current_user.username}/#{repo.name}" + #{current_user.username}/#{repo.name} %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do Import diff --git a/app/views/notify/_reassigned_issuable_email.html.haml b/app/views/notify/_reassigned_issuable_email.html.haml index 56d81b2ed2e..fd35713f79c 100644 --- a/app/views/notify/_reassigned_issuable_email.html.haml +++ b/app/views/notify/_reassigned_issuable_email.html.haml @@ -2,9 +2,9 @@ Assignee changed - if @previous_assignee from - %strong #{@previous_assignee.name} + %strong= @previous_assignee.name to - if issuable.assignee_id - %strong #{issuable.assignee_name} + %strong= issuable.assignee_name - else %strong Unassigned diff --git a/app/views/notify/closed_issue_email.html.haml b/app/views/notify/closed_issue_email.html.haml index 56c18cd83cd..b7284dd819b 100644 --- a/app/views/notify/closed_issue_email.html.haml +++ b/app/views/notify/closed_issue_email.html.haml @@ -1,2 +1,2 @@ %p - = "Issue was closed by #{@updated_by.name}" + Issue was closed by #{@updated_by.name} diff --git a/app/views/notify/closed_issue_email.text.haml b/app/views/notify/closed_issue_email.text.haml index ac703b31edd..bc12e38675f 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 was closed by #{@updated_by.name} Issue ##{@issue.iid}: #{namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)} diff --git a/app/views/notify/closed_merge_request_email.html.haml b/app/views/notify/closed_merge_request_email.html.haml index 81c7c88fc96..44e018304e1 100644 --- a/app/views/notify/closed_merge_request_email.html.haml +++ b/app/views/notify/closed_merge_request_email.html.haml @@ -1,2 +1,2 @@ %p - = "Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}" + Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name} diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml index b435067d5a6..d0c96b83976 100644 --- a/app/views/notify/closed_merge_request_email.text.haml +++ b/app/views/notify/closed_merge_request_email.text.haml @@ -1,4 +1,4 @@ -= "Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}" +Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name} Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} diff --git a/app/views/notify/issue_status_changed_email.html.haml b/app/views/notify/issue_status_changed_email.html.haml index 482c884a9db..b6051b11cea 100644 --- a/app/views/notify/issue_status_changed_email.html.haml +++ b/app/views/notify/issue_status_changed_email.html.haml @@ -1,2 +1,2 @@ %p - = "Issue was #{@issue_status} by #{@updated_by.name}" + Issue was #{@issue_status} by #{@updated_by.name} diff --git a/app/views/notify/merge_request_status_email.html.haml b/app/views/notify/merge_request_status_email.html.haml index 41a320d6bd8..b487e26b122 100644 --- a/app/views/notify/merge_request_status_email.html.haml +++ b/app/views/notify/merge_request_status_email.html.haml @@ -1,2 +1,2 @@ %p - = "Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}" + Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name} diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml index 7a5074a1dc3..4c9719ba732 100644 --- a/app/views/notify/merge_request_status_email.text.haml +++ b/app/views/notify/merge_request_status_email.text.haml @@ -1,4 +1,4 @@ -= "Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}" +Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name} Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} diff --git a/app/views/notify/merged_merge_request_email.html.haml b/app/views/notify/merged_merge_request_email.html.haml index fbe506d4f4d..0fe54e73313 100644 --- a/app/views/notify/merged_merge_request_email.html.haml +++ b/app/views/notify/merged_merge_request_email.html.haml @@ -1,2 +1,2 @@ %p - = "Merge Request #{@merge_request.to_reference} was merged" + Merge Request #{@merge_request.to_reference} was merged diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml index bfbae01094f..46c1c9dee0b 100644 --- a/app/views/notify/merged_merge_request_email.text.haml +++ b/app/views/notify/merged_merge_request_email.text.haml @@ -1,4 +1,4 @@ -= "Merge Request #{@merge_request.to_reference} was merged" +Merge Request #{@merge_request.to_reference} was merged Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} diff --git a/app/views/notify/note_personal_snippet_email.html.haml b/app/views/notify/note_personal_snippet_email.html.haml new file mode 100644 index 00000000000..2fa2f784661 --- /dev/null +++ b/app/views/notify/note_personal_snippet_email.html.haml @@ -0,0 +1 @@ += render 'note_message' diff --git a/app/views/notify/note_personal_snippet_email.text.erb b/app/views/notify/note_personal_snippet_email.text.erb new file mode 100644 index 00000000000..b2a8809a23b --- /dev/null +++ b/app/views/notify/note_personal_snippet_email.text.erb @@ -0,0 +1,8 @@ +New comment for Snippet <%= @snippet.id %> + +<%= url_for(snippet_url(@snippet, anchor: "note_#{@note.id}")) %> + + +Author: <%= @note.author_name %> + +<%= @note.note %> diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index 82c7fe229b8..d9ebbaa2704 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -139,7 +139,7 @@ had = failed.size failed - = "#{'build'.pluralize(failed.size)}." + #{'build'.pluralize(failed.size)}. %tr.warning %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;border:1px solid #ededed;border-bottom:0;border-radius:3px 3px 0 0;overflow:hidden;background-color:#fdf4f6;color:#d22852;font-size:14px;line-height:1.4;text-align:center;padding:8px 15px;" } Logs may contain sensitive data. Please consider before forwarding this email. diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index 6dddb3b6373..8add2e18206 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -138,9 +138,9 @@ %a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } = "\##{@pipeline.id}" successfully completed - = "#{build_count} #{'build'.pluralize(build_count)}" + #{build_count} #{'build'.pluralize(build_count)} in - = "#{stage_count} #{'stage'.pluralize(stage_count)}." + #{stage_count} #{'stage'.pluralize(stage_count)}. %tr.footer %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" } %img{ alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/ diff --git a/app/views/notify/project_was_not_exported_email.text.haml b/app/views/notify/project_was_not_exported_email.text.haml index 27785165c2d..6c6902994a1 100644 --- a/app/views/notify/project_was_not_exported_email.text.haml +++ b/app/views/notify/project_was_not_exported_email.text.haml @@ -1,6 +1,6 @@ -= "Project #{@project.name} couldn't be exported." +Project #{@project.name} couldn't be exported. -= "The errors we encountered were:" +The errors we encountered were: - @errors.each do |error| #{error} diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index 36858fa6f34..c6b1db17f91 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -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, namespace_project_commit_url(@message.project_namespace, @message.project, commit)) %div %span by #{commit.author_name} %i at #{commit.committed_date.to_s(:iso8601)} diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 72f658d1b68..14b330d16ad 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -102,7 +102,7 @@ = f.text_field :username, required: true, class: 'form-control' .help-block Current path: - = "#{root_url}#{current_user.username}" + #{root_url}#{current_user.username} .prepend-top-default = f.button class: "btn btn-warning", type: "submit" do = icon "spinner spin", class: "hidden loading-username" @@ -128,7 +128,7 @@ - if @user.solo_owned_groups.present? %p Your account is currently an owner in these groups: - %strong #{@user.solo_owned_groups.map(&:name).join(', ')} + %strong= @user.solo_owned_groups.map(&:name).join(', ') %p You must transfer ownership or delete these groups before you can delete your account. .append-bottom-default diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index c0c82cde2f6..d551754a2e5 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -62,7 +62,7 @@ %span.help-block Please click the link in the confirmation email before continuing. It was sent to = succeed "." do - %strong #{@user.unconfirmed_email} + %strong= @user.unconfirmed_email %p = link_to "Resend confirmation e-mail", user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index 085f79de785..23e27c1105c 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -23,7 +23,7 @@ = markdown_toolbar_button({ icon: "list-ol fw", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" }) = markdown_toolbar_button({ icon: "check-square-o fw", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" }) .toolbar-group - %button.toolbar-btn.js-zen-enter.has-tooltip.hidden-xs{ type: "button", tabindex: -1, aria: { label: "Go full screen" }, title: "Go full screen", data: { container: "body" } } + %button.toolbar-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, aria: { label: "Go full screen" }, title: "Go full screen", data: { container: "body" } } = icon("arrows-alt fw") .md-write-holder diff --git a/app/views/projects/_merge_request_merge_settings.html.haml b/app/views/projects/_merge_request_merge_settings.html.haml index afe2fd7fd7b..1a1327fb53c 100644 --- a/app/views/projects/_merge_request_merge_settings.html.haml +++ b/app/views/projects/_merge_request_merge_settings.html.haml @@ -8,7 +8,7 @@ %br %span.descr Builds need to be configured to enable this feature. - = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds') + = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_pipeline_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds') .checkbox = form.label :only_allow_merge_if_all_discussions_are_resolved do = form.check_box :only_allow_merge_if_all_discussions_are_resolved diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index 1d058daa094..228ac61fc8c 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -34,7 +34,7 @@ = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' .file-editor.code - %pre.js-edit-mode-pane#editor #{params[:content] || local_assigns[:blob_data]} + %pre.js-edit-mode-pane#editor= params[:content] || local_assigns[:blob_data] - if local_assigns[:path] .js-edit-mode-pane#preview.hide .center diff --git a/app/views/projects/blob/_image.html.haml b/app/views/projects/blob/_image.html.haml index a486b2fe491..f864702d862 100644 --- a/app/views/projects/blob/_image.html.haml +++ b/app/views/projects/blob/_image.html.haml @@ -1,8 +1,8 @@ .file-content.image_file - if blob.svg? - if blob.size_within_svg_limits? - - # We need to scrub SVG but we cannot do so in the RawController: it would - - # be wrong/strange if RawController modified the data. + -# We need to scrub SVG but we cannot do so in the RawController: it would + -# be wrong/strange if RawController modified the data. - blob.load_all_data!(@repository) - blob = sanitize_svg(blob) %img{ src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}", alt: "#{blob.name}" } diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml index 61a7ffdd0ab..4924c73cf8e 100644 --- a/app/views/projects/blob/_upload.html.haml +++ b/app/views/projects/blob/_upload.html.haml @@ -3,7 +3,7 @@ .modal-content .modal-header %a.close{ href: "#", "data-dismiss" => "modal" } × - %h3.page-title #{title} + %h3.page-title= title .modal-body = form_tag form_path, method: method, class: 'js-quick-submit js-upload-blob-form form-horizontal' do .dropzone diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 04efc2e996c..19ffe73a08d 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -18,7 +18,7 @@ - if @project.protected_branch? branch.name %span.label.label-success protected - .controls.hidden-xs + .controls.hidden-xs< - if merge_project && create_mr_button?(@repository.root_ref, branch.name) = link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-default' do Merge Request diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 5f8f56150f9..bd1f2d96f56 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -12,7 +12,7 @@ = form_tag(filter_branches_path, method: :get) do = search_field_tag :search, params[:search], { placeholder: 'Filter by branch name', id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false } - .dropdown.inline + .dropdown.inline> %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } %span.light = projects_sort_options_hash[@sort] diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml index b15be0d861d..736b485bf06 100644 --- a/app/views/projects/builds/_header.html.haml +++ b/app/views/projects/builds/_header.html.haml @@ -1,8 +1,8 @@ .content-block.build-header .header-content - = render 'ci/status/badge', status: @build.detailed_status(current_user) + = render 'ci/status/badge', status: @build.detailed_status(current_user), link: false Build - %strong ##{@build.id} + %strong.js-build-id ##{@build.id} in pipeline = link_to pipeline_path(@build.pipeline) do %strong ##{@build.pipeline.id} diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 324a7f8cd3f..762ff34a9ec 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -1,5 +1,5 @@ - if !project.empty_repo? && can?(current_user, :download_code, project) - .project-action-button.dropdown.inline + .project-action-button.dropdown.inline> %button.btn{ 'data-toggle' => 'dropdown' } = icon('download') = icon("caret-down") diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 520113639b7..c1e496455d1 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -85,7 +85,7 @@ - if build.finished_at %p.finished-at = icon("calendar") - %span #{time_ago_with_tooltip(build.finished_at)} + %span= time_ago_with_tooltip(build.finished_at) %td.coverage - if coverage && build.try(:coverage) diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 990bfbcf951..818a70f38f1 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -78,7 +78,7 @@ .btn-group.inline - if actions.any? .btn-group - %button.dropdown-toggle.btn.btn-default.js-pipeline-dropdown-manual-actions{ type: 'button', 'data-toggle' => 'dropdown' } + %button.dropdown-toggle.btn.btn-default.has-tooltip.js-pipeline-dropdown-manual-actions{ type: 'button', title: 'Manual build', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Manual build' } = custom_icon('icon_play') = icon('caret-down', 'aria-hidden' => 'true') %ul.dropdown-menu.dropdown-menu-align-right @@ -89,7 +89,7 @@ %span= build.name - if artifacts.present? .btn-group - %button.dropdown-toggle.btn.btn-default.build-artifacts.js-pipeline-dropdown-download{ type: 'button', 'data-toggle' => 'dropdown' } + %button.dropdown-toggle.btn.btn-default.build-artifacts.has-tooltip.js-pipeline-dropdown-download{ type: 'button', title: 'Artifacts', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Artifacts' } = icon("download") = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right @@ -102,8 +102,8 @@ - if can?(current_user, :update_pipeline, pipeline.project) .cancel-retry-btns.inline - if pipeline.retryable? - = link_to retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn has-tooltip', title: "Retry", method: :post do + = link_to retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn has-tooltip', title: 'Retry', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Retry' , method: :post do = icon("repeat") - if pipeline.cancelable? - = link_to cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do + = link_to cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-remove has-tooltip', title: 'Cancel', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Cancel' , method: :post do = icon("remove") diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 08eb0c57f66..6dba42d5226 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -1,8 +1,7 @@ .page-content-header .header-main-content - %strong + %strong Commit #{@commit.short_id} = clipboard_button(clipboard_text: @commit.id, title: "Copy commit SHA to clipboard") - = @commit.short_id %span.hidden-xs authored #{time_ago_with_tooltip(@commit.authored_date)} %span by diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml index fcc367951ad..904cdb5767f 100644 --- a/app/views/projects/commits/_commits.html.haml +++ b/app/views/projects/commits/_commits.html.haml @@ -2,7 +2,7 @@ - commits, hidden = limited_commits(@commits) - commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits| - %li.commit-header= "#{day.strftime('%d %b, %Y')} #{pluralize(commits.count, 'commit')}" + %li.commit-header #{day.strftime('%d %b, %Y')} #{pluralize(commits.count, 'commit')} %li.commits-row %ul.content-list.commit-list.table-list.table-wide = render commits, project: project, ref: ref diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index e77f23c7fd8..d94f23f5a38 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -9,10 +9,13 @@ = render "head" %div{ class: container_class } - .row-content-block.second-block.content-component-block + .row-content-block.second-block.content-component-block.flex-container-block .tree-ref-holder = render 'shared/ref_switcher', destination: 'commits' + %ul.breadcrumb.repo-breadcrumb + = commits_breadcrumbs + .block-controls.hidden-xs.hidden-sm - if @merge_request.present? .control @@ -30,8 +33,6 @@ .control = link_to namespace_project_commits_path(@project.namespace, @project, @ref, { format: :atom, private_token: current_user.private_token }), title: "Commits Feed", class: 'btn' do = icon("rss") - %ul.breadcrumb.repo-breadcrumb - = commits_breadcrumbs %div{ id: dom_id(@project) } %ol#commits-list.list-unstyled.content_list diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index 819e9bc15ae..9c8f58d4aea 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -16,9 +16,9 @@ There isn't anything to compare. %p.slead - if params[:to] == params[:from] - %span.label-branch #{params[:from]} + %span.label-branch= params[:from] and - %span.label-branch #{params[:to]} + %span.label-branch= params[:to] are the same. - else You'll need to use different branch names to get a valid comparison. diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml index 9238f232c7e..c468202569f 100644 --- a/app/views/projects/deployments/_deployment.html.haml +++ b/app/views/projects/deployments/_deployment.html.haml @@ -1,6 +1,6 @@ %tr.deployment %td - %strong= "##{deployment.iid}" + %strong ##{deployment.iid} %td = render 'projects/deployments/commit', deployment: deployment @@ -8,7 +8,7 @@ %td.build-column - if deployment.deployable = link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: 'build-link' do - = "#{deployment.deployable.name} (##{deployment.deployable.id})" + #{deployment.deployable.name} (##{deployment.deployable.id}) - if deployment.user by = user_avatar(user: deployment.user, size: 20) diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml index 52a1ece7d60..b87b79b170e 100644 --- a/app/views/projects/diffs/_content.html.haml +++ b/app/views/projects/diffs/_content.html.haml @@ -1,5 +1,5 @@ .diff-content.diff-wrap-lines - - # Skip all non non-supported blobs + -# Skip all non non-supported blobs - return unless blob.respond_to?(:text?) - if diff_file.too_large? .nothing-here-block This diff could not be displayed because it is too large. diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml index 90c9a0c6c2b..ddec775b789 100644 --- a/app/views/projects/diffs/_file_header.html.haml +++ b/app/views/projects/diffs/_file_header.html.haml @@ -25,4 +25,4 @@ - if diff_file.mode_changed? %small - = "#{diff_file.a_mode} → #{diff_file.b_mode}" + #{diff_file.a_mode} → #{diff_file.b_mode} diff --git a/app/views/projects/diffs/_image.html.haml b/app/views/projects/diffs/_image.html.haml index 1bccaaf5273..ca10921c5e2 100644 --- a/app/views/projects/diffs/_image.html.haml +++ b/app/views/projects/diffs/_image.html.haml @@ -9,7 +9,7 @@ %span.wrap .frame{ class: image_diff_class(diff) } %img{ src: diff.deleted_file ? old_file_raw_path : file_raw_path, alt: diff.new_path } - %p.image-info= "#{number_to_human_size file.size}" + %p.image-info= number_to_human_size(file.size) - else .image .two-up.view @@ -18,7 +18,7 @@ %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path)) } %img{ src: old_file_raw_path, alt: diff.old_path } %p.image-info.hide - %span.meta-filesize= "#{number_to_human_size old_file.size}" + %span.meta-filesize= number_to_human_size(old_file.size) | %b W: %span.meta-width @@ -30,7 +30,7 @@ %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path)) } %img{ src: file_raw_path, alt: diff.new_path } %p.image-info.hide - %span.meta-filesize= "#{number_to_human_size file.size}" + %span.meta-filesize= number_to_human_size(file.size) | %b W: %span.meta-width diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml index b087485aa17..f361204ecac 100644 --- a/app/views/projects/diffs/_parallel_view.html.haml +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -14,7 +14,7 @@ - left_line_code = diff_file.line_code(left) - left_position = diff_file.position(left) %td.old_line.diff-line-num{ id: left_line_code, class: left.type, data: { linenumber: left.old_pos } } - %a{ href: "##{left_line_code}" }= raw(left.old_pos) + %a{ href: "##{left_line_code}", data: { linenumber: left.old_pos } } %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) - else %td.old_line.diff-line-num.empty-cell @@ -27,7 +27,7 @@ - right_line_code = diff_file.line_code(right) - right_position = diff_file.position(right) %td.new_line.diff-line-num{ id: right_line_code, class: right.type, data: { linenumber: right.new_pos } } - %a{ href: "##{right_line_code}" }= raw(right.new_pos) + %a{ href: "##{right_line_code}", data: { linenumber: right.new_pos } } %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) - else %td.old_line.diff-line-num.empty-cell diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml index 290f696d582..8e24e28765f 100644 --- a/app/views/projects/diffs/_stats.html.haml +++ b/app/views/projects/diffs/_stats.html.haml @@ -2,7 +2,7 @@ .commit-stat-summary Showing = link_to '#', class: 'js-toggle-button' do - %strong #{pluralize(diff_files.size, "changed file")} + %strong= pluralize(diff_files.size, "changed file") with %strong.cgreen #{diff_files.sum(&:added_lines)} additions and diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 1d7fdf68cb3..114865935d6 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -7,10 +7,17 @@ .project-edit-errors = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f| %fieldset.append-bottom-0 - .form-group - = f.label :name, class: 'label-light' do - Project name - = f.text_field :name, class: "form-control", id: "project_name_edit" + .row + .form-group.col-md-9 + = f.label :name, class: 'label-light' do + Project name + = f.text_field :name, class: "form-control", id: "project_name_edit" + + .form-group.col-md-3 + = f.label :id, class: 'label-light' do + Project ID + = f.text_field :id, class: 'form-control', readonly: true + .form-group = f.label :description, class: 'label-light' do Project description diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml index 6c8a6f051a9..f4aa523b32d 100644 --- a/app/views/projects/forks/index.html.haml +++ b/app/views/projects/forks/index.html.haml @@ -1,7 +1,7 @@ .top-area .nav-text - full_count_title = "#{@public_forks_count} public and #{@private_forks_count} private" - = "#{pluralize(@total_forks_count, 'fork')}: #{full_count_title}" + #{pluralize(@total_forks_count, 'fork')}: #{full_count_title} .nav-controls = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| 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 6d7af1685fd..07fb80750d6 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 @@ -77,7 +77,7 @@ - if generic_commit_status.finished_at %p.finished-at = icon("calendar") - %span #{time_ago_with_tooltip(generic_commit_status.finished_at)} + %span= time_ago_with_tooltip(generic_commit_status.finished_at) %td.coverage - if coverage && generic_commit_status.try(:coverage) diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml index 7e34a89f9ae..c8a82f7bca3 100644 --- a/app/views/projects/graphs/commits.html.haml +++ b/app/views/projects/graphs/commits.html.haml @@ -11,7 +11,7 @@ %p.lead Commit statistics for - %strong #{@ref} + %strong= @ref #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')} .row @@ -19,19 +19,19 @@ %ul %li %p.lead - %strong #{@commits_graph.commits.size} + %strong= @commits_graph.commits.size commits during - %strong #{@commits_graph.duration} + %strong= @commits_graph.duration days %li %p.lead Average - %strong #{@commits_graph.commit_per_day} + %strong= @commits_graph.commit_per_day commits per day %li %p.lead Contributed by - %strong #{@commits_graph.authors} + %strong= @commits_graph.authors authors .col-md-6 %div diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 36c6e7a8dad..d3c013b3f21 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -3,9 +3,9 @@ %p.slead - source_title, target_title = format_mr_branch_names(@merge_request) From - %strong.label-branch #{source_title} + %strong.label-branch= source_title %span into - %strong.label-branch #{target_title} + %strong.label-branch= target_title %span.pull-right = link_to 'Change branches', mr_change_branches_path(@merge_request) @@ -43,7 +43,7 @@ #commits.commits.tab-pane.active = render "projects/merge_requests/show/commits" #diffs.diffs.tab-pane - - # This tab is always loaded via AJAX + -# This tab is always loaded via AJAX - if @pipelines.any? #pipelines.pipelines.tab-pane = render "projects/merge_requests/show/pipelines" diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 2a7cd3a19d0..9585a9a3ad4 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -52,7 +52,7 @@ = render 'award_emoji/awards_block', awardable: @merge_request, inline: true .merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') } - %div{ class: container_class } + .merge-request-tabs-container %ul.merge-request-tabs.nav-links.no-top.no-bottom %li.notes-tab = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do @@ -92,11 +92,11 @@ = render "projects/merge_requests/discussion" #commits.commits.tab-pane - - # This tab is always loaded via AJAX + -# This tab is always loaded via AJAX #pipelines.pipelines.tab-pane - - # This tab is always loaded via AJAX + -# This tab is always loaded via AJAX #diffs.diffs.tab-pane - - # This tab is always loaded via AJAX + -# This tab is always loaded via AJAX .mr-loading-status = spinner @@ -115,4 +115,3 @@ }); var mrRefreshWidgetUrl = "#{mr_widget_refresh_url(@merge_request)}"; - diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index b0f3c86fd21..74a7b1dc498 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -25,7 +25,7 @@ latest version - else version #{version_index(merge_request_diff)} - .monospace #{short_sha(merge_request_diff.head_commit_sha)} + .monospace= short_sha(merge_request_diff.head_commit_sha) %small #{number_with_delimiter(merge_request_diff.commits_count)} #{'commit'.pluralize(merge_request_diff.commits_count)}, = time_ago_with_tooltip(merge_request_diff.created_at) @@ -55,14 +55,14 @@ latest version - else version #{version_index(merge_request_diff)} - .monospace #{short_sha(merge_request_diff.head_commit_sha)} + .monospace= short_sha(merge_request_diff.head_commit_sha) %small = time_ago_with_tooltip(merge_request_diff.created_at) %li = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do %strong #{@merge_request.target_branch} (base) - .monospace #{short_sha(@merge_request_diff.base_commit_sha)} + .monospace= short_sha(@merge_request_diff.base_commit_sha) - if different_base?(@start_version, @merge_request_diff) .content-block @@ -72,7 +72,7 @@ = link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do new commits from - %code #{@merge_request.target_branch} + %code= @merge_request.target_branch - unless @merge_request_diff.latest? && !@start_sha .comments-disabled-notif.content-block diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index c80dc33058d..5faa6c43f9f 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -13,8 +13,8 @@ %span.ci-coverage - elsif @merge_request.has_ci? - - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX - - # TODO, remove in later versions when services like Jenkins will set CI status via Commit status API + -# Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX + -# TODO, remove in later versions when services like Jenkins will set CI status via Commit status API .mr-widget-heading - %w[success skipped canceled failed running pending].each do |status| .ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: "display:none" } diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 36c388c3318..09339e520dd 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -69,7 +69,7 @@ - if note_editable .original-note-content.hidden{ data: { post_url: namespace_project_note_path(@project.namespace, @project, note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } } #{note.note} - %textarea.hidden.js-task-list-field.original-task-list #{note.note} + %textarea.hidden.js-task-list-field.original-task-list= note.note .note-awards = render 'award_emoji/awards_block', awardable: note, inline: false - if note.system diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml index 9738f369a35..c7996077bc7 100644 --- a/app/views/projects/project_members/_group_members.html.haml +++ b/app/views/projects/project_members/_group_members.html.haml @@ -1,7 +1,7 @@ .panel.panel-default .panel-heading Group members with access to - %strong #{@group.name} + %strong= @group.name %span.badge= members.size - if can?(current_user, :admin_group_member, @group) .controls diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml index d7f5fa96527..fdeb5f21fbe 100644 --- a/app/views/projects/project_members/_groups.html.haml +++ b/app/views/projects/project_members/_groups.html.haml @@ -1,7 +1,7 @@ .panel.panel-default.project-members-groups .panel-heading Groups with access to - %strong #{@project.name} + %strong= @project.name %span.badge= group_links.size %ul.content-list = render partial: 'shared/members/group', collection: group_links, as: :group_link diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml index 77370c14def..7902ddb1ae9 100644 --- a/app/views/projects/project_members/_shared_group_members.html.haml +++ b/app/views/projects/project_members/_shared_group_members.html.haml @@ -5,9 +5,9 @@ .panel.panel-default .panel-heading Shared with - %strong #{shared_group.name} + %strong= shared_group.name group, members with - %strong #{group_links.human_access} + %strong= group_links.human_access role (#{shared_group_users_count}) - if can?(current_user, :admin_group, shared_group) .panel-head-actions diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 5292e73be7a..81d57c77edf 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -1,7 +1,7 @@ .panel.panel-default .panel-heading Members with access to - %strong #{@project.name} + %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' do .form-group diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml index 33d5cbff420..79d8d721aa9 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/releases/edit.html.haml @@ -7,7 +7,7 @@ .oneline .title Release notes for tag - %strong #{@tag.name} + %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| diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml index 51b0939564e..dcff675eafc 100644 --- a/app/views/projects/runners/_specific_runners.html.haml +++ b/app/views/projects/runners/_specific_runners.html.haml @@ -9,10 +9,10 @@ (checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} for information on how to install it). %li Specify the following URL during the Runner setup: - %code #{ci_root_url(only_path: false)} + %code= ci_root_url(only_path: false) %li Use the following registration token during setup: - %code #{@project.runners_token} + %code= @project.runners_token %li Start the Runner! 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 c929eee3bb9..fcc91be11cd 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 @@ -4,4 +4,4 @@ .col-sm-9.col-sm-offset-3 = link_to new_namespace_project_mattermost_path(@project.namespace, @project), class: 'btn btn-lg' do = custom_icon('mattermost_logo', size: 15) - = 'Add to Mattermost' + Add to Mattermost diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml index 2c08221565b..6855c463c6d 100644 --- a/app/views/projects/tree/_tree_content.html.haml +++ b/app/views/projects/tree/_tree_content.html.haml @@ -7,10 +7,12 @@ %th.hidden-xs .pull-left Last commit .last-commit.hidden-sm.pull-left + %i.fa.fa-angle-right %small.light - = clipboard_button(clipboard_text: @commit.id, title: "Copy commit SHA to clipboard") = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" + = clipboard_button(clipboard_text: @commit.id, title: "Copy commit SHA to clipboard") = time_ago_with_tooltip(@commit.committed_date) + \- = @commit.full_title %small.commit-history-link-spacer | = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'commit-history-link' diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml index e25d6a48573..fb0efd85dcd 100644 --- a/app/views/projects/wikis/git_access.html.haml +++ b/app/views/projects/wikis/git_access.html.haml @@ -17,6 +17,13 @@ %pre.dark :preserve gem install gollum + %p + It is recommended to install + %code github-markdown + so that GFM features render locally: + %pre.dark + :preserve + gem install github-markdown %h3 Clone your wiki %pre.dark diff --git a/app/views/search/results/_empty.html.haml b/app/views/search/results/_empty.html.haml index 05a63016c09..821a39d61f5 100644 --- a/app/views/search/results/_empty.html.haml +++ b/app/views/search/results/_empty.html.haml @@ -3,4 +3,4 @@ %h4 = icon('search') We couldn't find any results matching - %code #{@search_term} + %code= @search_term diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml index 07b17bc69c0..2e6adf3027c 100644 --- a/app/views/search/results/_merge_request.html.haml +++ b/app/views/search/results/_merge_request.html.haml @@ -2,7 +2,7 @@ %h4 = link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do %span.term.str-truncated= merge_request.title - .pull-right #{merge_request.to_reference} + .pull-right= merge_request.to_reference - if merge_request.description.present? .description.term = preserve do diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml index c9b7bd154af..23ca6479414 100644 --- a/app/views/search/results/_snippet_blob.html.haml +++ b/app/views/search/results/_snippet_blob.html.haml @@ -9,7 +9,7 @@ = link_to user_snippets_path(snippet.author) do = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: '' = snippet.author_name - %span.light #{time_ago_with_tooltip(snippet.created_at)} + %span.light= time_ago_with_tooltip(snippet.created_at) %h4.snippet-title - snippet_path = reliable_snippet_path(snippet) = link_to snippet_path do diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml index 027d42396b4..704d1d01a81 100644 --- a/app/views/search/results/_snippet_title.html.haml +++ b/app/views/search/results/_snippet_title.html.haml @@ -20,4 +20,4 @@ = link_to user_snippets_path(snippet_title.author) do = image_tag avatar_icon(snippet_title.author_email), class: "avatar avatar-inline s16", alt: '' = snippet_title.author_name - %span.light #{time_ago_with_tooltip(snippet_title.created_at)} + %span.light= time_ago_with_tooltip(snippet_title.created_at) diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml index e7cb93b17a7..f94f8ffc604 100644 --- a/app/views/shared/_confirm_modal.html.haml +++ b/app/views/shared/_confirm_modal.html.haml @@ -14,7 +14,7 @@ To prevent accidental actions we ask you to confirm your intention. %br Please type - %code.js-confirm-danger-match #{phrase} + %code.js-confirm-danger-match= phrase to proceed or close this modal to cancel. .form-group diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml index 39294fe1a09..704893b4d5b 100644 --- a/app/views/shared/_milestones_filter.html.haml +++ b/app/views/shared/_milestones_filter.html.haml @@ -6,14 +6,14 @@ = link_to milestones_filter_path(state: 'opened') do Open - if @project - %span.badge #{counts[:opened]} + %span.badge= counts[:opened] %li{ class: milestone_class_for_state(params[:state], 'closed') }> = link_to milestones_filter_path(state: 'closed') do Closed - if @project - %span.badge #{counts[:closed]} + %span.badge= counts[:closed] %li{ class: milestone_class_for_state(params[:state], 'all') }> = link_to milestones_filter_path(state: 'all') do All - if @project - %span.badge #{counts[:all]} + %span.badge= counts[:all] diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml index f9a7aa4e29b..dd9e433491b 100644 --- a/app/views/shared/groups/_group.html.haml +++ b/app/views/shared/groups/_group.html.haml @@ -32,7 +32,7 @@ - if group_member as - %span #{group_member.human_access} + %span= group_member.human_access - if group.description.present? .description diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index c0e8a498316..0a4de709fcd 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -68,7 +68,7 @@ - if !issuable.persisted? && !issuable.project.empty_repo? && (guide_url = contribution_guide_path(issuable.project)) .inline.prepend-left-10 Please review the - %strong #{link_to 'contribution guidelines', guide_url} + %strong= link_to('contribution guidelines', guide_url) for this project. - if issuable.new_record? diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index e9644ca0f12..55360dadbc4 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -47,10 +47,6 @@ %li.filter-dropdown-item{ 'data-value' => 'none' } %button.btn.btn-link No Assignee - - if current_user - %li.filter-dropdown-item{ 'data-value' => current_user.to_reference } - %button.btn.btn-link - Assigned to me %li.divider %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item diff --git a/app/views/shared/tokens/_scopes_form.html.haml b/app/views/shared/tokens/_scopes_form.html.haml index 5074afb63a1..8bbaf431536 100644 --- a/app/views/shared/tokens/_scopes_form.html.haml +++ b/app/views/shared/tokens/_scopes_form.html.haml @@ -5,5 +5,5 @@ - scopes.each do |scope| %fieldset = check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}" - = label_tag "#{prefix}_scopes_#{scope}", scope - %span= "(#{t(scope, scope: [:doorkeeper, :scopes])})" + = label_tag ("#{prefix}_scopes_#{scope}"), scope + %span= t(scope, scope: [:doorkeeper, :scopes]) diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml index f51599212db..b09782749f5 100644 --- a/app/views/users/calendar_activities.html.haml +++ b/app/views/users/calendar_activities.html.haml @@ -1,6 +1,6 @@ %h4.prepend-top-20 Contributions for - %strong #{@calendar_date.to_s(:short)} + %strong= @calendar_date.to_s(:short) - if @events.any? %ul.bordered-list diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index fb25eed4f37..c3d33d49c1e 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -110,16 +110,16 @@ = spinner #groups.tab-pane - - # This tab is always loaded via AJAX + -# This tab is always loaded via AJAX #contributed.tab-pane - - # This tab is always loaded via AJAX + -# This tab is always loaded via AJAX #projects.tab-pane - - # This tab is always loaded via AJAX + -# This tab is always loaded via AJAX #snippets.tab-pane - - # This tab is always loaded via AJAX + -# This tab is always loaded via AJAX .loading-status = spinner diff --git a/app/workers/authorized_projects_worker.rb b/app/workers/authorized_projects_worker.rb index 2badd0680fb..6abbb5a5250 100644 --- a/app/workers/authorized_projects_worker.rb +++ b/app/workers/authorized_projects_worker.rb @@ -2,6 +2,13 @@ class AuthorizedProjectsWorker include Sidekiq::Worker include DedicatedSidekiqQueue + # Schedules multiple jobs and waits for them to be completed. + def self.bulk_perform_and_wait(args_list) + job_ids = bulk_perform_async(args_list) + + Gitlab::JobWaiter.new(job_ids).wait + end + def self.bulk_perform_async(args_list) Sidekiq::Client.push_bulk('class' => self, 'args' => args_list) end diff --git a/changelogs/unreleased/24923_nested_tasks.yml b/changelogs/unreleased/24923_nested_tasks.yml new file mode 100644 index 00000000000..de35cad3dd6 --- /dev/null +++ b/changelogs/unreleased/24923_nested_tasks.yml @@ -0,0 +1,4 @@ +--- +title: Fix nested tasks in ordered list +merge_request: 8626 +author: diff --git a/changelogs/unreleased/25312-search-input-cmd-click-issue.yml b/changelogs/unreleased/25312-search-input-cmd-click-issue.yml new file mode 100644 index 00000000000..56e03a48692 --- /dev/null +++ b/changelogs/unreleased/25312-search-input-cmd-click-issue.yml @@ -0,0 +1,4 @@ +--- +title: Prevent removal of input fields if it is the parent dropdown element +merge_request: 8397 +author: diff --git a/changelogs/unreleased/26068_tasklist_issue.yml b/changelogs/unreleased/26068_tasklist_issue.yml new file mode 100644 index 00000000000..c938351b8a7 --- /dev/null +++ b/changelogs/unreleased/26068_tasklist_issue.yml @@ -0,0 +1,4 @@ +--- +title: Don’t count tasks that are not defined as list items correctly +merge_request: 8526 +author: diff --git a/changelogs/unreleased/26186-diff-view-plus-and-minus-signs-as-part-of-line-number.yml b/changelogs/unreleased/26186-diff-view-plus-and-minus-signs-as-part-of-line-number.yml new file mode 100644 index 00000000000..565672917b2 --- /dev/null +++ b/changelogs/unreleased/26186-diff-view-plus-and-minus-signs-as-part-of-line-number.yml @@ -0,0 +1,4 @@ +--- +title: Color + and - signs in diffs to increase code legibility +merge_request: +author: diff --git a/changelogs/unreleased/26445-accessible-piplelines-buttons.yml b/changelogs/unreleased/26445-accessible-piplelines-buttons.yml new file mode 100644 index 00000000000..fb5274e5253 --- /dev/null +++ b/changelogs/unreleased/26445-accessible-piplelines-buttons.yml @@ -0,0 +1,4 @@ +--- +title: Improve button accessibility on pipelines page +merge_request: 8561 +author: diff --git a/changelogs/unreleased/26447-fix-tab-list-order.yml b/changelogs/unreleased/26447-fix-tab-list-order.yml new file mode 100644 index 00000000000..351c53bd076 --- /dev/null +++ b/changelogs/unreleased/26447-fix-tab-list-order.yml @@ -0,0 +1,4 @@ +--- +title: Fix tab index order on branch commits list page +merge_request: +author: Ryan Harris diff --git a/changelogs/unreleased/26468-fix-users-sort-in-admin-area.yml b/changelogs/unreleased/26468-fix-users-sort-in-admin-area.yml new file mode 100644 index 00000000000..87ae8233c4a --- /dev/null +++ b/changelogs/unreleased/26468-fix-users-sort-in-admin-area.yml @@ -0,0 +1,4 @@ +--- +title: Fix Sort by Recent Sign-in in Admin Area +merge_request: 8637 +author: Poornima M diff --git a/changelogs/unreleased/26775-fix-auto-complete-initial-loading.yml b/changelogs/unreleased/26775-fix-auto-complete-initial-loading.yml new file mode 100644 index 00000000000..2d4ec482ee0 --- /dev/null +++ b/changelogs/unreleased/26775-fix-auto-complete-initial-loading.yml @@ -0,0 +1,4 @@ +--- +title: Fix autocomplete initial undefined state +merge_request: +author: diff --git a/changelogs/unreleased/26785-fix-droplab-in-ie-11-v1.yml b/changelogs/unreleased/26785-fix-droplab-in-ie-11-v1.yml deleted file mode 100644 index 76e9b19b828..00000000000 --- a/changelogs/unreleased/26785-fix-droplab-in-ie-11-v1.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add some basic fixes for IE11/Edge -merge_request: -author: diff --git a/changelogs/unreleased/26844-new-search-bar-performs-a-new-request-for-each-tag.yml b/changelogs/unreleased/26844-new-search-bar-performs-a-new-request-for-each-tag.yml new file mode 100644 index 00000000000..4678297cfd4 --- /dev/null +++ b/changelogs/unreleased/26844-new-search-bar-performs-a-new-request-for-each-tag.yml @@ -0,0 +1,4 @@ +--- +title: Add caching of droplab ajax requests +merge_request: 8725 +author: diff --git a/changelogs/unreleased/26947-build-status-self-link.yml b/changelogs/unreleased/26947-build-status-self-link.yml new file mode 100644 index 00000000000..15c5821874e --- /dev/null +++ b/changelogs/unreleased/26947-build-status-self-link.yml @@ -0,0 +1,4 @@ +--- +title: Add link verification to badge partial in order to render a badge without a link +merge_request: 8740 +author: diff --git a/changelogs/unreleased/27013-regression-in-commit-title-bar.yml b/changelogs/unreleased/27013-regression-in-commit-title-bar.yml new file mode 100644 index 00000000000..7cb5e4b273d --- /dev/null +++ b/changelogs/unreleased/27013-regression-in-commit-title-bar.yml @@ -0,0 +1,4 @@ +--- +title: Fix commit title bar and repository view copy clipboard button order on last commit in repository view +merge_request: +author: diff --git a/changelogs/unreleased/27014-fix-pipeline-tooltip-wrapping.yml b/changelogs/unreleased/27014-fix-pipeline-tooltip-wrapping.yml new file mode 100644 index 00000000000..f0301c849b6 --- /dev/null +++ b/changelogs/unreleased/27014-fix-pipeline-tooltip-wrapping.yml @@ -0,0 +1,4 @@ +--- +title: Fix mini-pipeline stage tooltip text wrapping +merge_request: +author: diff --git a/changelogs/unreleased/27021-line-numbers-now-in-copy-pasta-data.yml b/changelogs/unreleased/27021-line-numbers-now-in-copy-pasta-data.yml new file mode 100644 index 00000000000..b5584749098 --- /dev/null +++ b/changelogs/unreleased/27021-line-numbers-now-in-copy-pasta-data.yml @@ -0,0 +1,4 @@ +--- +title: Prevent copying of line numbers in parallel diff view +merge_request: 8706 +author: diff --git a/changelogs/unreleased/27044-fix-explore-sorting-on-trending.yml b/changelogs/unreleased/27044-fix-explore-sorting-on-trending.yml new file mode 100644 index 00000000000..0f0a8940f72 --- /dev/null +++ b/changelogs/unreleased/27044-fix-explore-sorting-on-trending.yml @@ -0,0 +1,4 @@ +--- +title: Fix /explore sorting +merge_request: +author: diff --git a/changelogs/unreleased/27066-textarea-border.yml b/changelogs/unreleased/27066-textarea-border.yml deleted file mode 100644 index e45cb3aced5..00000000000 --- a/changelogs/unreleased/27066-textarea-border.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove blue border from comment box hover -merge_request: -author: diff --git a/changelogs/unreleased/27178-update-builds-link-in-project-settings.yml b/changelogs/unreleased/27178-update-builds-link-in-project-settings.yml new file mode 100644 index 00000000000..52406bba464 --- /dev/null +++ b/changelogs/unreleased/27178-update-builds-link-in-project-settings.yml @@ -0,0 +1,4 @@ +--- +title: Updated builds info link on the project settings page +merge_request: +author: Ryan Harris diff --git a/changelogs/unreleased/27277-small-mini-pipeline-graph-glitch-upon-hover.yml b/changelogs/unreleased/27277-small-mini-pipeline-graph-glitch-upon-hover.yml new file mode 100644 index 00000000000..9456251025b --- /dev/null +++ b/changelogs/unreleased/27277-small-mini-pipeline-graph-glitch-upon-hover.yml @@ -0,0 +1,4 @@ +--- +title: fixed small mini pipeline graph line glitch +merge_request: 8804 +author: diff --git a/changelogs/unreleased/add_project_update_hook.yml b/changelogs/unreleased/add_project_update_hook.yml new file mode 100644 index 00000000000..915c9538843 --- /dev/null +++ b/changelogs/unreleased/add_project_update_hook.yml @@ -0,0 +1,4 @@ +--- +title: Add system hook for when a project is updated (other than rename/transfer) +merge_request: 5711 +author: Tommy Beadle diff --git a/changelogs/unreleased/broken_iamge_when_doing_offline_update_check-help_page-.yml b/changelogs/unreleased/broken_iamge_when_doing_offline_update_check-help_page-.yml new file mode 100644 index 00000000000..77750b55e7e --- /dev/null +++ b/changelogs/unreleased/broken_iamge_when_doing_offline_update_check-help_page-.yml @@ -0,0 +1,4 @@ +--- +title: Hide version check image if there is no internet connection +merge_request: 8355 +author: Ken Ding diff --git a/changelogs/unreleased/copy-as-md.yml b/changelogs/unreleased/copy-as-md.yml new file mode 100644 index 00000000000..637e9dc36e2 --- /dev/null +++ b/changelogs/unreleased/copy-as-md.yml @@ -0,0 +1,4 @@ +--- +title: Copying a rendered issue/comment will paste into GFM textareas as actual GFM +merge_request: +author: diff --git a/changelogs/unreleased/display-project-id.yml b/changelogs/unreleased/display-project-id.yml new file mode 100644 index 00000000000..8705ed28400 --- /dev/null +++ b/changelogs/unreleased/display-project-id.yml @@ -0,0 +1,4 @@ +--- +title: Display project ID in project settings +merge_request: 8572 +author: winniehell diff --git a/changelogs/unreleased/fix-26518.yml b/changelogs/unreleased/fix-26518.yml new file mode 100644 index 00000000000..961ac2642fb --- /dev/null +++ b/changelogs/unreleased/fix-26518.yml @@ -0,0 +1,4 @@ +--- +title: Fix access to the wiki code via HTTP when repository feature disabled +merge_request: 8758 +author: diff --git a/changelogs/unreleased/fix_broken_diff_discussions.yml b/changelogs/unreleased/fix_broken_diff_discussions.yml new file mode 100644 index 00000000000..4551212759f --- /dev/null +++ b/changelogs/unreleased/fix_broken_diff_discussions.yml @@ -0,0 +1,4 @@ +--- +title: Make MR-review-discussions more reliable +merge_request: +author: diff --git a/changelogs/unreleased/get-rid-of-water-from-notification_service_spec-to-make-it-DRY.yml b/changelogs/unreleased/get-rid-of-water-from-notification_service_spec-to-make-it-DRY.yml new file mode 100644 index 00000000000..f60417d185e --- /dev/null +++ b/changelogs/unreleased/get-rid-of-water-from-notification_service_spec-to-make-it-DRY.yml @@ -0,0 +1,4 @@ +--- +title: Make notification_service spec DRYer by making test reusable +merge_request: +author: YarNayar diff --git a/changelogs/unreleased/issue-filter-click-to-search.yml b/changelogs/unreleased/issue-filter-click-to-search.yml deleted file mode 100644 index c024ea48dc7..00000000000 --- a/changelogs/unreleased/issue-filter-click-to-search.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: allow issue filter bar to be operated with mouse only -merge_request: 8681 -author: diff --git a/changelogs/unreleased/issue_27211.yml b/changelogs/unreleased/issue_27211.yml new file mode 100644 index 00000000000..ad48fec5d85 --- /dev/null +++ b/changelogs/unreleased/issue_27211.yml @@ -0,0 +1,4 @@ +--- +title: Remove unused js response from refs controller +merge_request: +author: diff --git a/changelogs/unreleased/label-select-toggle.yml b/changelogs/unreleased/label-select-toggle.yml new file mode 100644 index 00000000000..af5b4246521 --- /dev/null +++ b/changelogs/unreleased/label-select-toggle.yml @@ -0,0 +1,4 @@ +--- +title: Fixed label dropdown toggle text not correctly updating +merge_request: +author: diff --git a/changelogs/unreleased/merge-dropdown-this-context.yml b/changelogs/unreleased/merge-dropdown-this-context.yml deleted file mode 100644 index 5c4890fcaa2..00000000000 --- a/changelogs/unreleased/merge-dropdown-this-context.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixed bug where links in merge dropdown wouldn't work -merge_request: -author: diff --git a/changelogs/unreleased/misalinged-discussion-with-no-avatar-26854.yml b/changelogs/unreleased/misalinged-discussion-with-no-avatar-26854.yml new file mode 100644 index 00000000000..f32b3aea3c8 --- /dev/null +++ b/changelogs/unreleased/misalinged-discussion-with-no-avatar-26854.yml @@ -0,0 +1,4 @@ +--- +title: adds avatar for discussion note +merge_request: 8734 +author: diff --git a/changelogs/unreleased/mr-tabs-container-offset.yml b/changelogs/unreleased/mr-tabs-container-offset.yml new file mode 100644 index 00000000000..c5df8abfcf2 --- /dev/null +++ b/changelogs/unreleased/mr-tabs-container-offset.yml @@ -0,0 +1,4 @@ +--- +title: Fixed merge requests tab extra margin when fixed to window +merge_request: +author: diff --git a/changelogs/unreleased/newline-eslint-rule.yml b/changelogs/unreleased/newline-eslint-rule.yml new file mode 100644 index 00000000000..5ce080b6912 --- /dev/null +++ b/changelogs/unreleased/newline-eslint-rule.yml @@ -0,0 +1,4 @@ +--- +title: Flag multiple empty lines in eslint, fix offenses. +merge_request: 8137 +author: diff --git a/changelogs/unreleased/no_project_notes.yml b/changelogs/unreleased/no_project_notes.yml new file mode 100644 index 00000000000..6106c027360 --- /dev/null +++ b/changelogs/unreleased/no_project_notes.yml @@ -0,0 +1,4 @@ +--- +title: Support notes when a project is not specified (personal snippet notes) +merge_request: 8468 +author: diff --git a/changelogs/unreleased/refresh-authorizations-fork-join.yml b/changelogs/unreleased/refresh-authorizations-fork-join.yml new file mode 100644 index 00000000000..b1349b9447d --- /dev/null +++ b/changelogs/unreleased/refresh-authorizations-fork-join.yml @@ -0,0 +1,4 @@ +--- +title: Fix race conditions for AuthorizedProjectsWorker +merge_request: +author: diff --git a/changelogs/unreleased/revert-filter-assigned-to-me.yml b/changelogs/unreleased/revert-filter-assigned-to-me.yml new file mode 100644 index 00000000000..37f9d2f5fc4 --- /dev/null +++ b/changelogs/unreleased/revert-filter-assigned-to-me.yml @@ -0,0 +1,4 @@ +--- +title: Revert 3f17f29a +merge_request: 8785 +author: diff --git a/changelogs/unreleased/sh-fix-annotated-tags-pointing-to-blob.yml b/changelogs/unreleased/sh-fix-annotated-tags-pointing-to-blob.yml new file mode 100644 index 00000000000..ff2b38f21f2 --- /dev/null +++ b/changelogs/unreleased/sh-fix-annotated-tags-pointing-to-blob.yml @@ -0,0 +1,4 @@ +--- +title: Fix Error 500 when repositories contain annotated tags pointing to blobs +merge_request: +author: diff --git a/changelogs/unreleased/small-screen-fullscreen-button.yml b/changelogs/unreleased/small-screen-fullscreen-button.yml new file mode 100644 index 00000000000..f4c269bc473 --- /dev/null +++ b/changelogs/unreleased/small-screen-fullscreen-button.yml @@ -0,0 +1,4 @@ +--- +title: Display fullscreen button on small screens +merge_request: 5302 +author: winniehell diff --git a/changelogs/unreleased/tc-only-mr-button-if-allowed.yml b/changelogs/unreleased/tc-only-mr-button-if-allowed.yml new file mode 100644 index 00000000000..a7f5dcb560c --- /dev/null +++ b/changelogs/unreleased/tc-only-mr-button-if-allowed.yml @@ -0,0 +1,4 @@ +--- +title: Only show Merge Request button when user can create a MR +merge_request: 8639 +author: diff --git a/config/database.yml.mysql b/config/database.yml.mysql index d9702870249..a33e40e8eb3 100644 --- a/config/database.yml.mysql +++ b/config/database.yml.mysql @@ -3,8 +3,8 @@ # production: adapter: mysql2 - encoding: utf8mb4 - collation: utf8mb4_general_ci + encoding: utf8 + collation: utf8_general_ci reconnect: false database: gitlabhq_production pool: 10 @@ -18,8 +18,8 @@ production: # development: adapter: mysql2 - encoding: utf8mb4 - collation: utf8mb4_general_ci + encoding: utf8 + collation: utf8_general_ci reconnect: false database: gitlabhq_development pool: 5 diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index 3b8771543e4..e0702e06cc9 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -1,3 +1,117 @@ +# Autoload all classes that we want to instrument, and instrument the methods we +# need. This takes the Gitlab::Metrics::Instrumentation module as an argument so +# that we can stub it for testing, as it is only called when metrics are +# enabled. +# +# rubocop:disable Metrics/AbcSize +def instrument_classes(instrumentation) + instrumentation.instrument_instance_methods(Gitlab::Shell) + + instrumentation.instrument_methods(Gitlab::Git) + + Gitlab::Git.constants.each do |name| + const = Gitlab::Git.const_get(name) + + next unless const.is_a?(Module) + + instrumentation.instrument_methods(const) + instrumentation.instrument_instance_methods(const) + end + + # Path to search => prefix to strip from constant + paths_to_instrument = { + ['app', 'finders'] => ['app', 'finders'], + ['app', 'mailers', 'emails'] => ['app', 'mailers'], + ['app', 'services', '**'] => ['app', 'services'], + ['lib', 'gitlab', 'conflicts'] => ['lib'], + ['lib', 'gitlab', 'diff'] => ['lib'], + ['lib', 'gitlab', 'email', 'message'] => ['lib'], + ['lib', 'gitlab', 'checks'] => ['lib'] + } + + paths_to_instrument.each do |(path, prefix)| + prefix = Rails.root.join(*prefix) + + Dir[Rails.root.join(*path + ['*.rb'])].each do |file_path| + path = Pathname.new(file_path).relative_path_from(prefix) + const = path.to_s.sub('.rb', '').camelize.constantize + + instrumentation.instrument_methods(const) + instrumentation.instrument_instance_methods(const) + end + end + + instrumentation.instrument_methods(Premailer::Adapter::Nokogiri) + instrumentation.instrument_instance_methods(Premailer::Adapter::Nokogiri) + + [ + :Blame, :Branch, :BranchCollection, :Blob, :Commit, :Diff, :Repository, + :Tag, :TagCollection, :Tree + ].each do |name| + const = Rugged.const_get(name) + + instrumentation.instrument_methods(const) + instrumentation.instrument_instance_methods(const) + end + + # Instruments all Banzai filters and reference parsers + { + Filter: Rails.root.join('lib', 'banzai', 'filter', '*.rb'), + ReferenceParser: Rails.root.join('lib', 'banzai', 'reference_parser', '*.rb') + }.each do |const_name, path| + Dir[path].each do |file| + klass = File.basename(file, File.extname(file)).camelize + const = Banzai.const_get(const_name).const_get(klass) + + instrumentation.instrument_methods(const) + instrumentation.instrument_instance_methods(const) + end + end + + instrumentation.instrument_methods(Banzai::Renderer) + instrumentation.instrument_methods(Banzai::Querying) + + instrumentation.instrument_instance_methods(Banzai::ObjectRenderer) + instrumentation.instrument_instance_methods(Banzai::Redactor) + instrumentation.instrument_methods(Banzai::NoteRenderer) + + [Issuable, Mentionable, Participable].each do |klass| + instrumentation.instrument_instance_methods(klass) + instrumentation.instrument_instance_methods(klass::ClassMethods) + end + + instrumentation.instrument_methods(Gitlab::ReferenceExtractor) + instrumentation.instrument_instance_methods(Gitlab::ReferenceExtractor) + + # Instrument the classes used for checking if somebody has push access. + instrumentation.instrument_instance_methods(Gitlab::GitAccess) + instrumentation.instrument_instance_methods(Gitlab::GitAccessWiki) + + instrumentation.instrument_instance_methods(API::Helpers) + + instrumentation.instrument_instance_methods(RepositoryCheck::SingleRepositoryWorker) + + instrumentation.instrument_instance_methods(Rouge::Plugins::Redcarpet) + instrumentation.instrument_instance_methods(Rouge::Formatters::HTMLGitlab) + + [:XML, :HTML].each do |namespace| + namespace_mod = Nokogiri.const_get(namespace) + + instrumentation.instrument_methods(namespace_mod) + instrumentation.instrument_methods(namespace_mod::Document) + end + + instrumentation.instrument_methods(Rinku) + instrumentation.instrument_instance_methods(Repository) + + instrumentation.instrument_methods(Gitlab::Highlight) + instrumentation.instrument_instance_methods(Gitlab::Highlight) + + # This is a Rails scope so we have to instrument it manually. + instrumentation.instrument_method(Project, :visible_to_user) +end +# rubocop:enable Metrics/AbcSize + if Gitlab::Metrics.enabled? require 'pathname' require 'influxdb' @@ -49,110 +163,7 @@ if Gitlab::Metrics.enabled? end Gitlab::Metrics::Instrumentation.configure do |config| - config.instrument_instance_methods(Gitlab::Shell) - - config.instrument_methods(Gitlab::Git) - - Gitlab::Git.constants.each do |name| - const = Gitlab::Git.const_get(name) - - next unless const.is_a?(Module) - - config.instrument_methods(const) - config.instrument_instance_methods(const) - end - - # Path to search => prefix to strip from constant - paths_to_instrument = { - ['app', 'finders'] => ['app', 'finders'], - ['app', 'mailers', 'emails'] => ['app', 'mailers'], - ['app', 'services', '**'] => ['app', 'services'], - ['lib', 'gitlab', 'conflicts'] => ['lib'], - ['lib', 'gitlab', 'diff'] => ['lib'], - ['lib', 'gitlab', 'email', 'message'] => ['lib'], - ['lib', 'gitlab', 'checks'] => ['lib'] - } - - paths_to_instrument.each do |(path, prefix)| - prefix = Rails.root.join(*prefix) - - Dir[Rails.root.join(*path + ['*.rb'])].each do |file_path| - path = Pathname.new(file_path).relative_path_from(prefix) - const = path.to_s.sub('.rb', '').camelize.constantize - - config.instrument_methods(const) - config.instrument_instance_methods(const) - end - end - - config.instrument_methods(Premailer::Adapter::Nokogiri) - config.instrument_instance_methods(Premailer::Adapter::Nokogiri) - - [ - :Blame, :Branch, :BranchCollection, :Blob, :Commit, :Diff, :Repository, - :Tag, :TagCollection, :Tree - ].each do |name| - const = Rugged.const_get(name) - - config.instrument_methods(const) - config.instrument_instance_methods(const) - end - - # Instruments all Banzai filters and reference parsers - { - Filter: Rails.root.join('lib', 'banzai', 'filter', '*.rb'), - ReferenceParser: Rails.root.join('lib', 'banzai', 'reference_parser', '*.rb') - }.each do |const_name, path| - Dir[path].each do |file| - klass = File.basename(file, File.extname(file)).camelize - const = Banzai.const_get(const_name).const_get(klass) - - config.instrument_methods(const) - config.instrument_instance_methods(const) - end - end - - config.instrument_methods(Banzai::Renderer) - config.instrument_methods(Banzai::Querying) - - config.instrument_instance_methods(Banzai::ObjectRenderer) - config.instrument_instance_methods(Banzai::Redactor) - config.instrument_methods(Banzai::NoteRenderer) - - [Issuable, Mentionable, Participable].each do |klass| - config.instrument_instance_methods(klass) - config.instrument_instance_methods(klass::ClassMethods) - end - - config.instrument_methods(Gitlab::ReferenceExtractor) - config.instrument_instance_methods(Gitlab::ReferenceExtractor) - - # Instrument the classes used for checking if somebody has push access. - config.instrument_instance_methods(Gitlab::GitAccess) - config.instrument_instance_methods(Gitlab::GitAccessWiki) - - config.instrument_instance_methods(API::Helpers) - - config.instrument_instance_methods(RepositoryCheck::SingleRepositoryWorker) - - config.instrument_instance_methods(Rouge::Plugins::Redcarpet) - config.instrument_instance_methods(Rouge::Formatters::HTMLGitlab) - - [:XML, :HTML].each do |namespace| - namespace_mod = Nokogiri.const_get(namespace) - - config.instrument_methods(namespace_mod) - config.instrument_methods(namespace_mod::Document) - end - - config.instrument_methods(Rinku) - config.instrument_instance_methods(Repository) - - config.instrument_methods(Gitlab::Highlight) - config.instrument_instance_methods(Gitlab::Highlight) - - # This is a Rails scope so we have to instrument it manually. - config.instrument_method(Project, :visible_to_user) + instrument_classes(config) end GC::Profiler.enable diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 5a7365bb0f6..fa318384405 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -12,6 +12,11 @@ Sidekiq.configure_server do |config| chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS'] chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS'] chain.add Gitlab::SidekiqMiddleware::RequestStoreMiddleware unless ENV['SIDEKIQ_REQUEST_STORE'] == '0' + chain.add Gitlab::SidekiqStatus::ServerMiddleware + end + + config.client_middleware do |chain| + chain.add Gitlab::SidekiqStatus::ClientMiddleware end # Sidekiq-cron: load recurring jobs from gitlab.yml @@ -46,6 +51,10 @@ end Sidekiq.configure_client do |config| config.redis = redis_config_hash + + config.client_middleware do |chain| + chain.add Gitlab::SidekiqStatus::ClientMiddleware + end end # The Sidekiq client API always adds the queue to the Sidekiq queue diff --git a/config/routes/group.rb b/config/routes/group.rb index 776c31c9dac..60a1175fe80 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -19,13 +19,11 @@ end scope(path: 'groups/*id', controller: :groups, - constraints: { id: Gitlab::Regex.namespace_route_regex }) do + constraints: { id: Gitlab::Regex.namespace_route_regex, format: /(html|json|atom)/ }) do get :edit, as: :edit_group get :issues, as: :issues_group get :merge_requests, as: :merge_requests_group get :projects, as: :projects_group get :activity, as: :activity_group + get '/', action: :show, as: :group_canonical end - -# Must be last route in this file -get 'groups/*id' => 'groups#show', as: :group_canonical, constraints: { id: Gitlab::Regex.namespace_route_regex } diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb index bba2fc4b186..6f241f6fa4a 100644 --- a/db/fixtures/development/01_admin.rb +++ b/db/fixtures/development/01_admin.rb @@ -1,3 +1,5 @@ +require './spec/support/sidekiq' + Gitlab::Seeder.quiet do User.seed do |s| s.id = 1 diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb index a984eda5ab5..c2b8f7ba819 100644 --- a/db/fixtures/development/04_project.rb +++ b/db/fixtures/development/04_project.rb @@ -1,4 +1,4 @@ -require 'sidekiq/testing' +require './spec/support/sidekiq' Sidekiq::Testing.inline! do Gitlab::Seeder.quiet do diff --git a/db/fixtures/development/05_users.rb b/db/fixtures/development/05_users.rb index 03da29c4c68..101ff3a1209 100644 --- a/db/fixtures/development/05_users.rb +++ b/db/fixtures/development/05_users.rb @@ -1,3 +1,5 @@ +require './spec/support/sidekiq' + Gitlab::Seeder.quiet do 20.times do |i| begin diff --git a/db/fixtures/development/06_teams.rb b/db/fixtures/development/06_teams.rb index 5c2a03fec3f..86e0a38aae1 100644 --- a/db/fixtures/development/06_teams.rb +++ b/db/fixtures/development/06_teams.rb @@ -1,4 +1,4 @@ -require 'sidekiq/testing' +require './spec/support/sidekiq' Sidekiq::Testing.inline! do Gitlab::Seeder.quiet do diff --git a/db/fixtures/development/07_milestones.rb b/db/fixtures/development/07_milestones.rb index 540e4e68259..271bfbc97e0 100644 --- a/db/fixtures/development/07_milestones.rb +++ b/db/fixtures/development/07_milestones.rb @@ -1,3 +1,5 @@ +require './spec/support/sidekiq' + Gitlab::Seeder.quiet do Project.all.each do |project| 5.times do |i| diff --git a/db/fixtures/development/09_issues.rb b/db/fixtures/development/09_issues.rb index 4fa572fca9b..d93d133d157 100644 --- a/db/fixtures/development/09_issues.rb +++ b/db/fixtures/development/09_issues.rb @@ -1,3 +1,5 @@ +require './spec/support/sidekiq' + Gitlab::Seeder.quiet do Project.all.each do |project| 10.times do diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb index 87fb8e3300d..c04afe97277 100644 --- a/db/fixtures/development/10_merge_requests.rb +++ b/db/fixtures/development/10_merge_requests.rb @@ -1,3 +1,5 @@ +require './spec/support/sidekiq' + Gitlab::Seeder.quiet do # Limit the number of merge requests per project to avoid long seeds MAX_NUM_MERGE_REQUESTS = 10 diff --git a/db/fixtures/development/11_keys.rb b/db/fixtures/development/11_keys.rb index 8b4bee384e1..51e22137d6f 100644 --- a/db/fixtures/development/11_keys.rb +++ b/db/fixtures/development/11_keys.rb @@ -1,12 +1,18 @@ -Gitlab::Seeder.quiet do - User.first(10).each do |user| - key = "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt#{user.id + 100}6k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" +require './spec/support/sidekiq' - user.keys.create( - title: "Sample key #{user.id}", - key: key - ) +# Creating keys runs a gitlab-shell worker. Since we may not have the right +# gitlab-shell path set (yet) we need to disable this for these fixtures. +Sidekiq::Testing.disable! do + Gitlab::Seeder.quiet do + User.first(10).each do |user| + key = "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt#{user.id + 100}6k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" - print '.' + user.keys.create( + title: "Sample key #{user.id}", + key: key + ) + + print '.' + end end end diff --git a/db/fixtures/development/12_snippets.rb b/db/fixtures/development/12_snippets.rb index 74898544a69..4f3bdba043d 100644 --- a/db/fixtures/development/12_snippets.rb +++ b/db/fixtures/development/12_snippets.rb @@ -1,3 +1,5 @@ +require './spec/support/sidekiq' + Gitlab::Seeder.quiet do content =<<eos class Member < ActiveRecord::Base diff --git a/db/fixtures/development/13_comments.rb b/db/fixtures/development/13_comments.rb index 566c0705638..29b8081055d 100644 --- a/db/fixtures/development/13_comments.rb +++ b/db/fixtures/development/13_comments.rb @@ -1,3 +1,5 @@ +require './spec/support/sidekiq' + Gitlab::Seeder.quiet do Issue.all.each do |issue| project = issue.project diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb index be95d788850..534847a7107 100644 --- a/db/fixtures/development/14_pipelines.rb +++ b/db/fixtures/development/14_pipelines.rb @@ -1,3 +1,5 @@ +require './spec/support/sidekiq' + class Gitlab::Seeder::Pipelines STAGES = %w[build test deploy notify] BUILDS = [ diff --git a/db/fixtures/development/15_award_emoji.rb b/db/fixtures/development/15_award_emoji.rb index baac32f2d10..ea343c26b69 100644 --- a/db/fixtures/development/15_award_emoji.rb +++ b/db/fixtures/development/15_award_emoji.rb @@ -1,3 +1,5 @@ +require './spec/support/sidekiq' + Gitlab::Seeder.quiet do emoji = Gitlab::AwardEmoji.emojis.keys diff --git a/db/fixtures/development/16_protected_branches.rb b/db/fixtures/development/16_protected_branches.rb index 103c7f9445c..39d466fb43f 100644 --- a/db/fixtures/development/16_protected_branches.rb +++ b/db/fixtures/development/16_protected_branches.rb @@ -1,3 +1,5 @@ +require './spec/support/sidekiq' + Gitlab::Seeder.quiet do admin_user = User.find(1) diff --git a/db/fixtures/development/17_cycle_analytics.rb b/db/fixtures/development/17_cycle_analytics.rb index 916ee8dbac8..747901dd634 100644 --- a/db/fixtures/development/17_cycle_analytics.rb +++ b/db/fixtures/development/17_cycle_analytics.rb @@ -1,4 +1,4 @@ -require 'sidekiq/testing' +require './spec/support/sidekiq' require './spec/support/test_env' class Gitlab::Seeder::CycleAnalytics diff --git a/db/migrate/20161223034433_add_estimate_to_issuables_ce.rb b/db/migrate/20161223034433_add_estimate_to_issuables_ce.rb index 2cbe626d752..d5116dfab49 100644 --- a/db/migrate/20161223034433_add_estimate_to_issuables_ce.rb +++ b/db/migrate/20161223034433_add_estimate_to_issuables_ce.rb @@ -3,7 +3,7 @@ class AddEstimateToIssuablesCe < ActiveRecord::Migration DOWNTIME = false - def change + def up unless column_exists?(:issues, :time_estimate) add_column :issues, :time_estimate, :integer end @@ -12,4 +12,14 @@ class AddEstimateToIssuablesCe < ActiveRecord::Migration add_column :merge_requests, :time_estimate, :integer end end + + def down + if column_exists?(:issues, :time_estimate) + remove_column :issues, :time_estimate + end + + if column_exists?(:merge_requests, :time_estimate) + remove_column :merge_requests, :time_estimate + end + end end diff --git a/db/migrate/20161223034646_create_timelogs_ce.rb b/db/migrate/20161223034646_create_timelogs_ce.rb index e8a4b406012..66d9cd823fb 100644 --- a/db/migrate/20161223034646_create_timelogs_ce.rb +++ b/db/migrate/20161223034646_create_timelogs_ce.rb @@ -3,7 +3,7 @@ class CreateTimelogsCe < ActiveRecord::Migration DOWNTIME = false - def change + def up unless table_exists?(:timelogs) create_table :timelogs do |t| t.integer :time_spent, null: false @@ -17,4 +17,8 @@ class CreateTimelogsCe < ActiveRecord::Migration add_index :timelogs, :user_id end end + + def down + drop_table :timelogs if table_exists?(:timelogs) + end end diff --git a/doc/README.md b/doc/README.md index e329131b8ee..909740211a6 100644 --- a/doc/README.md +++ b/doc/README.md @@ -6,7 +6,7 @@ ## User documentation -- [Account Security](user/account/security.md) Securing your account via two-factor authentication, etc. +- [Account Security](user/profile/account/two_factor_authentication.md) Securing your account via two-factor authentication, etc. - [API](api/README.md) Automate GitLab via a simple and powerful API. - [CI/CD](ci/README.md) GitLab Continuous Integration (CI) and Continuous Delivery (CD) getting started, `.gitlab-ci.yml` options, and examples. - [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab. @@ -44,7 +44,7 @@ - [Operations](administration/operations.md) Keeping GitLab up and running. - [Raketasks](raketasks/README.md) Backups, maintenance, automatic webhook setup and the importing of projects. - [Repository checks](administration/repository_checks.md) Periodic Git repository checks. -- [Repository storages](administration/repository_storages.md) Manage the paths used to store repositories. +- [Repository storage paths](administration/repository_storage_paths.md) Manage the paths used to store repositories. - [Security](security/README.md) Learn what you can do to further secure your GitLab instance. - [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed. - [Update](update/README.md) Update guides to upgrade your installation. @@ -61,7 +61,6 @@ - [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs. - [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability. - [Container Registry](administration/container_registry.md) Configure Docker Registry with GitLab. -- [Multiple mountpoints for the repositories storage](administration/repository_storages.md) Define multiple repository storage paths to distribute the storage load. ## Contributor documentation diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md index 04723365277..f6027b2f99e 100644 --- a/doc/administration/auth/ldap.md +++ b/doc/administration/auth/ldap.md @@ -65,11 +65,15 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server # # Example: 'Paris' or 'Acme, Ltd.' label: 'LDAP' - + + # Example: 'ldap.mydomain.com' host: '_your_ldap_server' + # This port is an example, it is sometimes different but it is always an integer and not a string port: 389 uid: 'sAMAccountName' method: 'plain' # "tls" or "ssl" or "plain" + + # Examples: 'america\\momo' or 'CN=Gitlab Git,CN=Users,DC=mydomain,DC=com' bind_dn: '_the_full_dn_of_the_user_you_will_bind_with' password: '_the_password_of_the_bind_user' @@ -101,7 +105,7 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server # Base where we can search for users # - # Ex. ou=People,dc=gitlab,dc=example + # Ex. 'ou=People,dc=gitlab,dc=example' or 'DC=mydomain,DC=com' # base: '' @@ -112,6 +116,9 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server # # Note: GitLab does not support omniauth-ldap's custom filter syntax. # + # Below an example for get only specific users + # Example: '(&(objectclass=user)(|(samaccountname=momo)(samaccountname=toto)))' + # user_filter: '' # LDAP attributes that GitLab will use to create an account for the LDAP user. diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md index f3c2e72341f..33b9b28433a 100644 --- a/doc/administration/raketasks/maintenance.md +++ b/doc/administration/raketasks/maintenance.md @@ -27,6 +27,7 @@ Ruby Version: 2.1.5p273 Gem Version: 2.4.3 Bundler Version: 1.7.6 Rake Version: 10.3.2 +Redis Version: 3.2.5 Sidekiq Version: 2.17.8 GitLab information diff --git a/doc/administration/repository_checks.md b/doc/administration/repository_checks.md index bc2b1f20ed3..ee37ea49874 100644 --- a/doc/administration/repository_checks.md +++ b/doc/administration/repository_checks.md @@ -13,12 +13,12 @@ checks failed you can see their output on the admin log page under ## Periodic checks -GitLab periodically runs a repository check on all project repositories and -wiki repositories in order to detect data corruption problems. A -project will be checked no more than once per week. If any projects +When enabled, GitLab periodically runs a repository check on all project +repositories and wiki repositories in order to detect data corruption problems. +A project will be checked no more than once per month. If any projects fail their repository checks all GitLab administrators will receive an email -notification of the situation. This notification is sent out no more -than once a day. +notification of the situation. This notification is sent out once a week on +Sunday, by default. ## Disabling periodic checks diff --git a/doc/administration/repository_storage_paths.md b/doc/administration/repository_storage_paths.md new file mode 100644 index 00000000000..d6aa6101026 --- /dev/null +++ b/doc/administration/repository_storage_paths.md @@ -0,0 +1,102 @@ +# Repository storage paths + +> [Introduced][ce-4578] in GitLab 8.10. + +GitLab allows you to define multiple repository storage paths to distribute the +storage load between several mount points. + +>**Notes:** +> +- You must have at least one storage path called `default`. +- The paths are defined in key-value pairs. The key is an arbitrary name you + can pick to name the file path. +- The target directories and any of its subpaths must not be a symlink. + +## Configure GitLab + +>**Warning:** +In order for [backups] to work correctly, the storage path must **not** be a +mount point and the GitLab user should have correct permissions for the parent +directory of the path. In Omnibus GitLab this is taken care of automatically, +but for source installations you should be extra careful. +> +The thing is that for compatibility reasons `gitlab.yml` has a different +structure than Omnibus. In `gitlab.yml` you indicate the path for the +repositories, for example `/home/git/repositories`, while in Omnibus you +indicate `git_data_dirs`, which for the example above would be `/home/git`. +Then, Omnibus will create a `repositories` directory under that path to use with +`gitlab.yml`. +> +This little detail matters because while restoring a backup, the current +contents of `/home/git/repositories` [are moved to][raketask] `/home/git/repositories.old`, +so if `/home/git/repositories` is the mount point, then `mv` would be moving +things between mount points, and bad things could happen. Ideally, +`/home/git` would be the mount point, so then things would be moving within the +same mount point. This is guaranteed with Omnibus installations (because they +don't specify the full repository path but the parent path), but not for source +installations. + +--- + +Now that you've read that big fat warning above, let's edit the configuration +files and add the full paths of the alternative repository storage paths. In +the example below, we add two more mountpoints that are named `nfs` and `cephfs` +respectively. + +**For installations from source** + +1. Edit `gitlab.yml` and add the storage paths: + + ```yaml + repositories: + # Paths where repositories can be stored. Give the canonicalized absolute pathname. + # NOTE: REPOS PATHS MUST NOT CONTAIN ANY SYMLINK!!! + storages: # You must have at least a 'default' storage path. + default: /home/git/repositories + nfs: /mnt/nfs/repositories + cephfs: /mnt/cephfs/repositories + ``` + +1. [Restart GitLab] for the changes to take effect. + +>**Note:** +The [`gitlab_shell: repos_path` entry][repospath] in `gitlab.yml` will be +deprecated and replaced by `repositories: storages` in the future, so if you +are upgrading from a version prior to 8.10, make sure to add the configuration +as described in the step above. After you make the changes and confirm they are +working, you can remove the `repos_path` line. + +--- + +**For Omnibus installations** + +1. Edit `/etc/gitlab/gitlab.rb` by appending the rest of the paths to the + default one: + + ```ruby + git_data_dirs({ + "default" => "/var/opt/gitlab/git-data", + "nfs" => "/mnt/nfs/git-data", + "cephfs" => "/mnt/cephfs/git-data" + }) + ``` + + Note that Omnibus stores the repositories in a `repositories` subdirectory + of the `git-data` directory. + +## Choose where new project repositories will be stored + +Once you set the multiple storage paths, you can choose where new projects will +be stored via the **Application Settings** in the Admin area. + +![Choose repository storage path in Admin area](img/repository_storages_admin_ui.png) + +Beginning with GitLab 8.13.4, multiple paths can be chosen. New projects will be +randomly placed on one of the selected paths. + +[ce-4578]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4578 +[restart gitlab]: restart_gitlab.md#installations-from-source +[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure +[backups]: ../raketasks/backup_restore.md +[raketask]: https://gitlab.com/gitlab-org/gitlab-ce/blob/033e5423a2594e08a7ebcd2379bd2331f4c39032/lib/backup/repository.rb#L54-56 +[repospath]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-9-stable/config/gitlab.yml.example#L457 diff --git a/doc/administration/repository_storages.md b/doc/administration/repository_storages.md index ab70557b69a..9d41ba77f34 100644 --- a/doc/administration/repository_storages.md +++ b/doc/administration/repository_storages.md @@ -1,102 +1,3 @@ # Repository storages -> [Introduced][ce-4578] in GitLab 8.10. - -GitLab allows you to define multiple repository storage paths to distribute the -storage load between several mount points. - ->**Notes:** -> -- You must have at least one storage path called `default`. -- The paths are defined in key-value pairs. The key is an arbitrary name you - can pick to name the file path. -- The target directories and any of its subpaths must not be a symlink. - -## Configure GitLab - ->**Warning:** -In order for [backups] to work correctly, the storage path must **not** be a -mount point and the GitLab user should have correct permissions for the parent -directory of the path. In Omnibus GitLab this is taken care of automatically, -but for source installations you should be extra careful. -> -The thing is that for compatibility reasons `gitlab.yml` has a different -structure than Omnibus. In `gitlab.yml` you indicate the path for the -repositories, for example `/home/git/repositories`, while in Omnibus you -indicate `git_data_dirs`, which for the example above would be `/home/git`. -Then, Omnibus will create a `repositories` directory under that path to use with -`gitlab.yml`. -> -This little detail matters because while restoring a backup, the current -contents of `/home/git/repositories` [are moved to][raketask] `/home/git/repositories.old`, -so if `/home/git/repositories` is the mount point, then `mv` would be moving -things between mount points, and bad things could happen. Ideally, -`/home/git` would be the mount point, so then things would be moving within the -same mount point. This is guaranteed with Omnibus installations (because they -don't specify the full repository path but the parent path), but not for source -installations. - ---- - -Now that you've read that big fat warning above, let's edit the configuration -files and add the full paths of the alternative repository storage paths. In -the example below, we add two more mountpoints that are named `nfs` and `cephfs` -respectively. - -**For installations from source** - -1. Edit `gitlab.yml` and add the storage paths: - - ```yaml - repositories: - # Paths where repositories can be stored. Give the canonicalized absolute pathname. - # NOTE: REPOS PATHS MUST NOT CONTAIN ANY SYMLINK!!! - storages: # You must have at least a 'default' storage path. - default: /home/git/repositories - nfs: /mnt/nfs/repositories - cephfs: /mnt/cephfs/repositories - ``` - -1. [Restart GitLab] for the changes to take effect. - ->**Note:** -The [`gitlab_shell: repos_path` entry][repospath] in `gitlab.yml` will be -deprecated and replaced by `repositories: storages` in the future, so if you -are upgrading from a version prior to 8.10, make sure to add the configuration -as described in the step above. After you make the changes and confirm they are -working, you can remove the `repos_path` line. - ---- - -**For Omnibus installations** - -1. Edit `/etc/gitlab/gitlab.rb` by appending the rest of the paths to the - default one: - - ```ruby - git_data_dirs({ - "default" => "/var/opt/gitlab/git-data", - "nfs" => "/mnt/nfs/git-data", - "cephfs" => "/mnt/cephfs/git-data" - }) - ``` - - Note that Omnibus stores the repositories in a `repositories` subdirectory - of the `git-data` directory. - -## Choose where new project repositories will be stored - -Once you set the multiple storage paths, you can choose where new projects will -be stored via the **Application Settings** in the Admin area. - -![Choose repository storage path in Admin area](img/repository_storages_admin_ui.png) - -Beginning with GitLab 8.13.4, multiple paths can be chosen. New projects will be -randomly placed on one of the selected paths. - -[ce-4578]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4578 -[restart gitlab]: restart_gitlab.md#installations-from-source -[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure -[backups]: ../raketasks/backup_restore.md -[raketask]: https://gitlab.com/gitlab-org/gitlab-ce/blob/033e5423a2594e08a7ebcd2379bd2331f4c39032/lib/backup/repository.rb#L54-56 -[repospath]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-9-stable/config/gitlab.yml.example#L457 +This document was moved to a [new location](repository_storage_paths.md). diff --git a/doc/api/enviroments.md b/doc/api/enviroments.md index 1299aca8c45..e0ee20d9610 100644 --- a/doc/api/enviroments.md +++ b/doc/api/enviroments.md @@ -78,7 +78,7 @@ PUT /projects/:id/environments/:environments_id | `external_url` | string | no | The new external_url | ```bash -curl --request PUT --data "name=staging&external_url=https://staging.example.gitlab.com" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environment/1" +curl --request PUT --data "name=staging&external_url=https://staging.example.gitlab.com" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environments/1" ``` Example response: @@ -106,7 +106,7 @@ DELETE /projects/:id/environments/:environment_id | `environment_id` | integer | yes | The ID of the environment | ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environment/1" +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environments/1" ``` Example response: diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index ffc310ec8c7..5377bf9ee80 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -14,6 +14,12 @@ Apart from those, here is an collection of tutorials and guides on setting up yo - [Test a Phoenix application](test-phoenix-application.md) - [Using `dpl` as deployment tool](deployment/README.md) - [Example project that shows how to use Review Apps](https://gitlab.com/gitlab-examples/review-apps-nginx/) +- [Run PHP Composer & NPM scripts then deploy them to a staging server](deployment/composer-npm-deploy.md) +- Help your favorite programming language and GitLab by sending a merge request + with a guide for that language. + +## Outside the documentation + - [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) - [Repositories with examples for various languages](https://gitlab.com/groups/gitlab-examples) - [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) diff --git a/doc/ci/examples/deployment/composer-npm-deploy.md b/doc/ci/examples/deployment/composer-npm-deploy.md new file mode 100644 index 00000000000..5334a73e1f5 --- /dev/null +++ b/doc/ci/examples/deployment/composer-npm-deploy.md @@ -0,0 +1,156 @@ +## Running Composer and NPM scripts with deployment via SCP + +This guide covers the building dependencies of a PHP project while compiling assets via an NPM script. + +While is possible to create your own image with custom PHP and Node JS versions, for brevity, we will use an existing [Docker image](https://hub.docker.com/r/tetraweb/php/) that contains both PHP and NodeJS installed. + + +```yaml +image: tetraweb/php +``` + +The next step is to install zip/unzip packages and make composer available. We will place these in the `before_script` section: + +```yaml +before_script: + - apt-get update + - apt-get install zip unzip + - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" + - php composer-setup.php + - php -r "unlink('composer-setup.php');" +``` + +This will make sure we have all requirements ready. Next, we want to run `composer update` to fetch all PHP dependencies and `npm install` to load node packages, then run the `npm` script. We need to append them into `before_script` section: + +```yaml +before_script: + # ... + - php composer.phar update + - npm install + - npm run deploy +``` + +In this particular case, the `npm deploy` script is a Gulp script that does the following: + +1. Compile CSS & JS +2. Create sprites +3. Copy various assets (images, fonts) around +4. Replace some strings + +All these operations will put all files into a `build` folder, which is ready to be deployed to a live server. + +### How to transfer files to a live server? + +You have multiple options: rsync, scp, sftp and so on. For now, we will use scp. + +To make this work, you need to add a GitLab Secret Variable (accessible on _gitlab.example/your-project-name/variables_). That variable will be called `STAGING_PRIVATE_KEY` and it's the **private** ssh key of your server. + +#### Security tip + +Create a user that has access **only** to the folder that needs to be updated! + +After you create that variable, you need to make sure that key will be added to the docker container on run: + +```yaml +before_script: + # - .... + - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' + - mkdir -p ~/.ssh + - eval $(ssh-agent -s) + - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' +``` + +In order, this means that: + +1. We check if the `ssh-agent` is available and we install it if it's not; +2. We create the `~/.ssh` folder; +3. We make sure we're running bash; +4. We disable host checking (we don't ask for user accept when we first connect to a server; and since every build will equal a first connect, we kind of need this) + +And this is basically all you need in the `before_script` section. + +## How to deploy things? + +As we stated above, we need to deploy the `build` folder from the docker image to our server. To do so, we create a new job: + +```yaml +stage_deploy: + artifacts: + paths: + - build/ + only: + - dev + script: + - ssh-add <(echo "$STAGING_PRIVATE_KEY") + - ssh -p22 server_user@server_host "mkdir htdocs/wp-content/themes/_tmp" + - scp -P22 -r build/* server_user@server_host:htdocs/wp-content/themes/_tmp + - ssh -p22 server_user@server_host "mv htdocs/wp-content/themes/live htdocs/wp-content/themes/_old && mv htdocs/wp-content/themes/_tmp htdocs/wp-content/themes/live" + - ssh -p22 server_user@server_host "rm -rf htdocs/wp-content/themes/_old" +``` + +### What's going on here? + +1. `only:dev` means that this build will run only when something is pushed to the `dev` branch. You can remove this block completely and have everything be ran on every push (but probably this is something you don't want) +2. `ssh-add ...` we will add that private key you added on the web UI to the docker container +3. We will connect via `ssh` and create a new `_tmp` folder +4. We will connect via `scp` and upload the `build` folder (which was generated by a `npm` script) to our previously created `_tmp` folder +5. We will connect again to `ssh` and move the `live` folder to an `_old` folder, then move `_tmp` to `live`. +6. We connect to ssh and remove the `_old` folder + +What's the deal with the artifacts? We just tell GitLab CI to keep the `build` directory (later on, you can download that as needed). + +#### Why we do it this way? + +If you're using this only for stage server, you could do this in two steps: + +```yaml +- ssh -p22 server_user@server_host "rm -rf htdocs/wp-content/themes/live/*" +- scp -P22 -r build/* server_user@server_host:htdocs/wp-content/themes/live +``` + +The problem is that there will be a small period of time when you won't have the app on your server. + +So we use so many steps because we want to make sure that at any given time we have a functional app in place. + +## Where to go next? + +Since this was a WordPress project, I gave real life code snippets. Some ideas you can pursuit: + +- Having a slightly different script for `master` branch will allow you to deploy to a production server from that branch and to a stage server from any other branches; +- Instead of pushing it live, you can push it to WordPress official repo (with creating a SVN commit & stuff); +- You could generate i18n text domains on the fly. + +--- + +Our final `.gitlab-ci.yml` will look like this: + +```yaml +image: tetraweb/php + +before_script: + - apt-get update + - apt-get install zip unzip + - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" + - php composer-setup.php + - php -r "unlink('composer-setup.php');" + - php composer.phar update + - npm install + - npm run deploy + - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' + - mkdir -p ~/.ssh + - eval $(ssh-agent -s) + - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' + +stage_deploy: + artifacts: + paths: + - build/ + only: + - dev + script: + - ssh-add <(echo "$STAGING_PRIVATE_KEY") + - ssh -p22 server_user@server_host "mkdir htdocs/wp-content/themes/_tmp" + - scp -P22 -r build/* server_user@server_host:htdocs/wp-content/themes/_tmp + - ssh -p22 server_user@server_host "mv htdocs/wp-content/themes/live htdocs/wp-content/themes/_old && mv htdocs/wp-content/themes/_tmp htdocs/wp-content/themes/live" + - ssh -p22 server_user@server_host "rm -rf htdocs/wp-content/themes/_old" +```
\ No newline at end of file diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 75a0897eb15..f11257be5c3 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -86,7 +86,7 @@ used for time of the build. The configuration of this feature is covered in ### before_script `before_script` is used to define the command that should be run before all -builds, including deploy builds. This can be an array or a multi-line string. +builds, including deploy builds, but after the restoration of artifacts. This can be an array or a multi-line string. ### after_script diff --git a/doc/development/README.md b/doc/development/README.md index 6f2ca7b8590..265df98fb87 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -18,6 +18,7 @@ - [Frontend guidelines](frontend.md) - [SQL guidelines](sql.md) for working with SQL queries - [Sidekiq guidelines](sidekiq_style_guide.md) for working with Sidekiq workers +- [`Gemfile` guidelines](gemfile.md) ## Process diff --git a/doc/development/gemfile.md b/doc/development/gemfile.md new file mode 100644 index 00000000000..ec9718cea71 --- /dev/null +++ b/doc/development/gemfile.md @@ -0,0 +1,14 @@ +# `Gemfile` guidelines + +When adding a new entry to `Gemfile` or upgrading an existing dependency pay +attention to the following rules. + +## No gems fetched from git repositories + +We do not allow gems that are fetched from git repositories. All gems have +to be available in the RubyGems index. We want to minimize external build +dependencies and build times. + +## License compliance + +Refer to [licensing guidelines](licensing.md) for ensuring license compliance. diff --git a/doc/gitlab-basics/add-image.md b/doc/gitlab-basics/add-image.md index 476b48a217c..1a44123aa81 100644 --- a/doc/gitlab-basics/add-image.md +++ b/doc/gitlab-basics/add-image.md @@ -1,62 +1,56 @@ # How to add an image -The following are the steps to add images to your repository in -GitLab: +Using your standard tool for copying files (e.g. Finder in Mac OS, or Explorer +in Windows, or...), put the image file into the GitLab project. You can find the +project as a regular folder in your files. -Find the image that you’d like to add. +Go to your [shell](command-line-commands.md), and move into the folder of your +Gitlab project. This usually means running the following command until you get +to the desired destination: -In your computer files, find the GitLab project to which you'd like to add the image -(you'll find it as a regular file). Click on every file until you find exactly where you'd -like to add the image. There, paste the image. - -Go to your [shell](command-line-commands.md), and add the following commands: - -Add this command for every directory that you'd like to open: ``` -cd NAME-OF-FILE-YOU'D-LIKE-TO-OPEN +cd NAME-OF-FOLDER-YOU'D-LIKE-TO-OPEN ``` -Create a new branch: -``` -git checkout -b NAME-OF-BRANCH -``` +Check if your image is actually present in the directory (if you are in Windows, +use `dir` instead): -Check if your image was correctly added to the directory: ``` ls ``` You should see the name of the image in the list shown. -Move up the hierarchy through directories: -``` -cd ../ -``` +Check the status: -Check the status and you should see your image’s name in red: ``` git status ``` -Add your changes: +Your image's name should appear in red, so `git` took notice of it! Now add it +to the repository: + ``` git add NAME-OF-YOUR-IMAGE ``` -Check the status and you should see your image’s name in green: +Check the status again, your image's name should have turned green: + ``` git status ``` -Add the commit: +Commit: + ``` -git commit -m “DESCRIBE COMMIT IN A FEW WORDS” +git commit -m "DESCRIBE COMMIT IN A FEW WORDS" ``` -Now you can push (send) your changes (in the branch NAME-OF-BRANCH) to GitLab (the git remote named 'origin'): +Now you can push (send) your changes (in the branch NAME-OF-BRANCH) to GitLab +(the git remote named 'origin'): + ``` git push origin NAME-OF-BRANCH ``` -Your image will be added to your branch in your repository in GitLab. Create a [Merge Request](add-merge-request.md) -to integrate your changes to your project. +Your image will be added to your branch in your repository in GitLab. diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md index 322680f0cf4..65bfb0f7d6e 100644 --- a/doc/install/database_mysql.md +++ b/doc/install/database_mysql.md @@ -4,7 +4,7 @@ We do not recommend using MySQL due to various issues. For example, case [(in)sensitivity](https://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html) and [problems](https://bugs.mysql.com/bug.php?id=65830) that [suggested](https://bugs.mysql.com/bug.php?id=50909) [fixes](https://bugs.mysql.com/bug.php?id=65830) [have](https://bugs.mysql.com/bug.php?id=63164). -## MySQL +## Initial database setup # Install the database packages sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev @@ -32,8 +32,11 @@ We do not recommend using MySQL due to various issues. For example, case [(in)se # If this fails, check your MySQL config files (e.g. `/etc/mysql/*.cnf`, `/etc/mysql/conf.d/*`) for the setting "innodb = off" mysql> SET storage_engine=INNODB; + # If you have MySQL < 5.7.7 and want to enable utf8mb4 character set support with your GitLab install, you must set the following NOW: + mysql> SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda, innodb_large_prefix=1; + # Create the GitLab production database - mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`; + 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'; @@ -51,7 +54,203 @@ We do not recommend using MySQL due to various issues. For example, case [(in)se # Quit the database session mysql> \q - # You are done installing the database and can go back to the rest of the installation. + # You are done installing the database for now and can go back to the rest of the installation. + +Please proceed to the rest of the installation before running through the utf8mb4 support section. + + +### MySQL utf8mb4 support + +After installation or upgrade, remember to [convert any new tables](#convert) to `utf8mb4`/`utf8mb4_general_ci`. + +--- + +GitLab 8.14 has introduced [a feature](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7420) requiring `utf8mb4` encoding to be supported in your GitLab MySQL Database, which is not the case if you have setup your database before GitLab 8.16. + +Follow the below instructions to ensure you use the most up to date requirements for your GitLab MySQL Database. + +**We are about to do the following:** +- Ensure you can enable `utf8mb4` encoding and `utf8mb4_general_ci` collation for your GitLab DB, tables and data. +- Convert your GitLab tables and data from `utf8`/`utf8_general_ci` to `utf8mb4`/`utf8mb4_general_ci` + +### Check for utf8mb4 support + +#### Check for InnoDB File-Per-Table Tablespaces + +We need to check, enable and maybe convert your existing GitLab DB tables to the [InnoDB File-Per-Table Tablespaces](http://dev.mysql.com/doc/refman/5.7/en/innodb-multiple-tablespaces.html) as a prerequise for supporting **utfb8mb4 with long indexes** required by recent GitLab databases. + + # Login to MySQL + mysql -u root -p + + # Type the MySQL root password + mysql > use gitlabhq_production; + + # Check your MySQL version is >= 5.5.3 (GitLab requires 5.5.14+) + mysql > SHOW VARIABLES LIKE 'version'; + +---------------+-----------------+ + | Variable_name | Value | + +---------------+-----------------+ + | version | 5.5.53-0+deb8u1 | + +---------------+-----------------+ + + # Note where is your MySQL data dir for later: + mysql > select @@datadir; + +----------------+ + | @@datadir | + +----------------+ + | /var/lib/mysql | + +----------------+ + + # Note whether your MySQL server runs with innodb_file_per_table ON or OFF: + mysql> SELECT @@innodb_file_per_table; + +-------------------------+ + | @@innodb_file_per_table | + +-------------------------+ + | 1 | + +-------------------------+ + + # You can now quit the database session + mysql> \q + +> You need **MySQL 5.5.3 or later** to perform this update. + +Whatever the results of your checks above, we now need to check if your GitLab database has been created using [InnoDB File-Per-Table Tablespaces](http://dev.mysql.com/doc/refman/5.7/en/innodb-multiple-tablespaces.html) (i.e. `innodb_file_per_table` was set to **1** at initial setup time). + +> Note: This setting is [enabled by default](http://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_file_per_table) since MySQL 5.6.6. + + # Run this command with root privileges, replace the data dir if different: + sudo ls -lh /var/lib/mysql/gitlabhq_production/*.ibd | wc -l + + # Run this command with root privileges, replace the data dir if different: + sudo ls -lh /var/lib/mysql/gitlabhq_production/*.frm | wc -l + + +- **Case 1: a result > 0 for both commands** + +Congrats, your GitLab database uses the right InnoDB tablespace format. + +However, you must still ensure that any **future tables** created by GitLab will still use the right format: + +- If `SELECT @@innodb_file_per_table` returned **1** previously, your server is running correctly. +> It's however a requirement to check *now* that this setting is indeed persisted in your [my.cnf](https://dev.mysql.com/doc/refman/5.7/en/tablespace-enabling.html) file! + +- If `SELECT @@innodb_file_per_table` returned **0** previously, your server is not running correctly. +> [Enable innodb_file_per_table](https://dev.mysql.com/doc/refman/5.7/en/tablespace-enabling.html) by running in a MySQL session as root the command `SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda;` and persist the two settings in your [my.cnf](https://dev.mysql.com/doc/refman/5.7/en/tablespace-enabling.html) file + +Now, if you have a **different result** returned by the 2 commands above, it means you have a **mix of tables format** uses in your GitLab database. This can happen if your MySQL server had different values for `innodb_file_per_table` in its life and you updated GitLab at different moments with those inconsistent values. So keep reading. + +- **Case 2: a result equals to "0" OR not the same result for both commands** + +Unfortunately, none or only some of your GitLab database tables use the GitLab requirement of [InnoDB File-Per-Table Tablespaces](http://dev.mysql.com/doc/refman/5.7/en/innodb-multiple-tablespaces.html). + +Let's enable what we need on the running server: + + # Login to MySQL + mysql -u root -p + + # Type the MySQL root password + + # Enable innodb_file_per_table and set innodb_file_format on the running server: + mysql > SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda; + + # You can now quit the database session + mysql> \q + +> Now, **persist** [innodb_file_per_table](https://dev.mysql.com/doc/refman/5.6/en/tablespace-enabling.html) and [innodb_file_format](https://dev.mysql.com/doc/refman/5.6/en/innodb-file-format-enabling.html) in your `my.cnf` file. + +Ensure at this stage that your GitLab instance is indeed **stopped**. + +Now, let's convert all the GitLab database tables to the new tablespace format: + + # Login to MySQL + mysql -u root -p + + # Type the MySQL root password + mysql > use gitlabhq_production; + + # Safety check: you should still have those values set as follow: + mysql> SELECT @@innodb_file_per_table, @@innodb_file_format; + +-------------------------+----------------------+ + | @@innodb_file_per_table | @@innodb_file_format | + +-------------------------+----------------------+ + | 1 | Barracuda | + +-------------------------+----------------------+ + + mysql > SELECT CONCAT('ALTER TABLE `', TABLE_NAME,'` ENGINE=InnoDB;') AS 'Copy & run these SQL statements:' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA="gitlabhq_production" AND TABLE_TYPE="BASE TABLE"; + + # If previous query returned results, copy & run all shown SQL statements + + # You can now quit the database session + mysql> \q + +--- + +#### Check for proper InnoDB File Format, Row Format, Large Prefix and tables conversion + +We need to check, enable and probably convert your existing GitLab DB tables to use the [Barracuda InnoDB file format](https://dev.mysql.com/doc/refman/5.6/en/innodb-file-format.html), the [DYNAMIC row format](https://dev.mysql.com/doc/refman/5.6/en/glossary.html#glos_dynamic_row_format) and [innodb_large_prefix](http://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_large_prefix) as a second prerequisite for supporting **utfb8mb4 with long indexes** used by recent GitLab databases. + + # Login to MySQL + mysql -u root -p + + # Type the MySQL root password + mysql > use gitlabhq_production; + + # Set innodb_file_format and innodb_large_prefix on the running server: + # Note: These are the default settings only for MySQL 5.7.7 and later. + + mysql > SET GLOBAL innodb_file_format=Barracuda, innodb_large_prefix=1; + + # Your DB must be (still) using utf8/utf8_general_ci as default encoding and collation. + # We will NOT change the default encoding and collation on the DB in order to support future GitLab migrations creating tables + # that require "long indexes support" on installations using MySQL <= 5.7.9. + # However, when such migrations occur, you will have to follow this guide again to convert the newly created tables to the proper encoding/collation. + + # This should return the following: + mysql> SELECT @@character_set_database, @@collation_database; + +--------------------------+----------------------+ + | @@character_set_database | @@collation_database | + +--------------------------+----------------------+ + | utf8 | utf8_general_ci | + +--------------------------+----------------------+ + +> Now, ensure that [innodb_file_format](https://dev.mysql.com/doc/refman/5.6/en/tablespace-enabling.html) and [innodb_large_prefix](http://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_large_prefix) are **persisted** in your `my.cnf` file. + +#### Tables and data conversion to utf8mb4 +<a name="convert"></a> + +Now that you have a persistent MySQL setup, you can safely upgrade tables after setup or upgrade time: + + # Convert tables not using ROW_FORMAT DYNAMIC: + + mysql> SELECT CONCAT('ALTER TABLE `', TABLE_NAME,'` ROW_FORMAT=DYNAMIC;') AS 'Copy & run these SQL statements:' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA="gitlabhq_production" AND TABLE_TYPE="BASE TABLE" AND ROW_FORMAT!="Dynamic"; + + # !! If previous query returned results, copy & run all shown SQL statements + + # Convert tables/columns not using utf8mb4/utf8mb4_general_ci as encoding/collation: + + mysql > SET foreign_key_checks = 0; + + mysql > SELECT CONCAT('ALTER TABLE `', TABLE_NAME,'` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;') AS 'Copy & run these SQL statements:' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA="gitlabhq_production" AND TABLE_COLLATION != "utf8mb4_general_ci" AND TABLE_TYPE="BASE TABLE"; + + # !! If previous query returned results, copy & run all shown SQL statements + + # Turn foreign key checks back on + mysql > SET foreign_key_checks = 1; + + # You can now quit the database session + mysql> \q + +Ensure your GitLab database configuration file uses a proper connection encoding and collation: + +```sudo -u git -H editor config/database.yml``` + + production: + adapter: mysql2 + encoding: utf8mb4 + collation: utf8mb4_general_ci + +[Restart your GitLab instance](../administration/restart_gitlab.md). + ## MySQL strings limits diff --git a/doc/profile/2fa_u2f_authenticate.png b/doc/profile/2fa_u2f_authenticate.png Binary files differdeleted file mode 100644 index b224ab14195..00000000000 --- a/doc/profile/2fa_u2f_authenticate.png +++ /dev/null diff --git a/doc/profile/two_factor_authentication.md b/doc/profile/two_factor_authentication.md index 3f6dfe03d14..60918a0339c 100644 --- a/doc/profile/two_factor_authentication.md +++ b/doc/profile/two_factor_authentication.md @@ -1,143 +1 @@ -# Two-factor Authentication (2FA) - -Two-factor Authentication (2FA) provides an additional level of security to your -GitLab account. Once enabled, in addition to supplying your username and -password to login, you'll be prompted for a code generated by an application on -your phone. - -By enabling 2FA, the only way someone other than you can log into your account -is to know your username and password *and* have access to your phone. - -> **Note:** -When you enable 2FA, don't forget to back up your recovery codes. For your safety, if you -lose your codes for GitLab.com, we can't disable or recover them. - -In addition to a phone application, GitLab supports U2F (universal 2nd factor) devices as -the second factor of authentication. Once enabled, in addition to supplying your username and -password to login, you'll be prompted to activate your U2F device (usually by pressing -a button on it), and it will perform secure authentication on your behalf. - -> **Note:** Support for U2F devices was added in version 8.8 - -The U2F workflow is only supported by Google Chrome at this point, so we _strongly_ recommend -that you set up both methods of two-factor authentication, so you can still access your account -from other browsers. - -> **Note:** GitLab officially only supports [Yubikey] U2F devices. - -## Enabling 2FA - -### Enable 2FA via mobile application - -**In GitLab:** - -1. Log in to your GitLab account. -1. Go to your **Profile Settings**. -1. Go to **Account**. -1. Click **Enable Two-factor Authentication**. - -![Two-factor setup](2fa.png) - -**On your phone:** - -1. Install a compatible application. We recommend [Google Authenticator] -\(proprietary\) or [FreeOTP] \(open source\). -1. In the application, add a new entry in one of two ways: - * Scan the code with your phone's camera to add the entry automatically. - * Enter the details provided to add the entry manually. - -**In GitLab:** - -1. Enter the six-digit pin number from the entry on your phone into the **Pin - code** field. -1. Click **Submit**. - -If the pin you entered was correct, you'll see a message indicating that -Two-Factor Authentication has been enabled, and you'll be presented with a list -of recovery codes. - -### Enable 2FA via U2F device - -**In GitLab:** - -1. Log in to your GitLab account. -1. Go to your **Profile Settings**. -1. Go to **Account**. -1. Click **Enable Two-Factor Authentication**. -1. Plug in your U2F device. -1. Click on **Setup New U2F Device**. -1. A light will start blinking on your device. Activate it by pressing its button. - -You will see a message indicating that your device was successfully set up. -Click on **Register U2F Device** to complete the process. - -![Two-Factor U2F Setup](2fa_u2f_register.png) - -## Recovery Codes - -Should you ever lose access to your phone, you can use one of the ten provided -backup codes to login to your account. We suggest copying or printing them for -storage in a safe place. **Each code can be used only once** to log in to your -account. - -If you lose the recovery codes or just want to generate new ones, you can do so -from the **Profile Settings** > **Account** page where you first enabled 2FA. - -> **Note:** Recovery codes are not generated for U2F devices. - -## Logging in with 2FA Enabled - -Logging in with 2FA enabled is only slightly different than a normal login. -Enter your username and password credentials as you normally would, and you'll -be presented with a second prompt, depending on which type of 2FA you've enabled. - -### Log in via mobile application - -Enter the pin from your phone's application or a recovery code to log in. - -![Two-Factor Authentication on sign in via OTP](2fa_auth.png) - -### Log in via U2F device - -1. Click **Login via U2F Device** -1. A light will start blinking on your device. Activate it by pressing its button. - -You will see a message indicating that your device responded to the authentication request. -Click on **Authenticate via U2F Device** to complete the process. - -![Two-Factor Authentication on sign in via U2F device](2fa_u2f_authenticate.png) - -## Disabling 2FA - -1. Log in to your GitLab account. -1. Go to your **Profile Settings**. -1. Go to **Account**. -1. Click **Disable**, under **Two-Factor Authentication**. - -This will clear all your two-factor authentication registrations, including mobile -applications and U2F devices. - -## Personal access tokens - -When 2FA is enabled, you can no longer use your normal account password to -authenticate with Git over HTTPS on the command line, you must use a personal -access token instead. - -1. Log in to your GitLab account. -1. Go to your **Profile Settings**. -1. Go to **Access Tokens**. -1. Choose a name and expiry date for the token. -1. Click on **Create Personal Access Token**. -1. Save the personal access token somewhere safe. - -When using git over HTTPS on the command line, enter the personal access token -into the password field. - -## Note to GitLab administrators - -You need to take special care to that 2FA keeps working after -[restoring a GitLab backup](../raketasks/backup_restore.md). - -[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en -[FreeOTP]: https://fedorahosted.org/freeotp/ -[YubiKey]: https://www.yubico.com/products/yubikey-hardware/ +This document was moved to [user/profile/account](../user/profile/account/two_factor_authentication.md). diff --git a/doc/project_services/kubernetes.md b/doc/project_services/kubernetes.md index 59d5da702f8..99aa9e44bdb 100644 --- a/doc/project_services/kubernetes.md +++ b/doc/project_services/kubernetes.md @@ -8,7 +8,7 @@ the [configuration](#configuration) section. If you have a single cluster that you want to use for all your projects, you can pre-fill the settings page with a default template. To configure the -template, see the [Services Templates](services-templates.md) document. +template, see the [Services Templates](services_templates.md) document. ## Configuration diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md index c44930a4ceb..ec13c2446ef 100644 --- a/doc/system_hooks/system_hooks.md +++ b/doc/system_hooks/system_hooks.md @@ -1,6 +1,6 @@ # System hooks -Your GitLab instance can perform HTTP POST requests on the following events: `project_create`, `project_destroy`, `project_rename`, `project_transfer`, `user_add_to_team`, `user_remove_from_team`, `user_create`, `user_destroy`, `key_create`, `key_destroy`, `group_create`, `group_destroy`, `user_add_to_group` and `user_remove_from_group`. +Your GitLab instance can perform HTTP POST requests on the following events: `project_create`, `project_destroy`, `project_rename`, `project_transfer`, `project_update`, `user_add_to_team`, `user_remove_from_team`, `user_create`, `user_destroy`, `key_create`, `key_destroy`, `group_create`, `group_destroy`, `user_add_to_group` and `user_remove_from_group`. System hooks can be used, e.g. for logging or changing information in a LDAP server. @@ -88,6 +88,23 @@ X-Gitlab-Event: System Hook } ``` +**Project updated:** + +```json +{ + "created_at": "2012-07-21T07:30:54Z", + "updated_at": "2012-07-21T07:38:22Z", + "event_name": "project_update", + "name": "StoreCloud", + "owner_email": "johnsmith@gmail.com", + "owner_name": "John Smith", + "path": "storecloud", + "path_with_namespace": "jsmith/storecloud", + "project_id": 74, + "project_visibility": "private", +} +``` + **New Team Member:** ```json diff --git a/doc/update/8.15-to-8.16.md b/doc/update/8.15-to-8.16.md index 63f3c3fdda9..2695a16ac0b 100644 --- a/doc/update/8.15-to-8.16.md +++ b/doc/update/8.15-to-8.16.md @@ -97,6 +97,8 @@ sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production sudo -u git -H bundle exec rake assets:clean assets:precompile 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). + ### 6. Update gitlab-workhorse Install and compile gitlab-workhorse. This requires diff --git a/doc/user/account/security.md b/doc/user/account/security.md index 816094bf8d2..9336dee7451 100644 --- a/doc/user/account/security.md +++ b/doc/user/account/security.md @@ -1,3 +1 @@ -# Account Security - -- [Two-Factor Authentication](two_factor_authentication.md) +This document was moved to [profile](../profile/index.md#security). diff --git a/doc/user/account/two_factor_authentication.md b/doc/user/account/two_factor_authentication.md index 881358ed94d..ea2c8307860 100644 --- a/doc/user/account/two_factor_authentication.md +++ b/doc/user/account/two_factor_authentication.md @@ -1,68 +1 @@ -# Two-Factor Authentication - -## Recovery options - -If you lose your code generation device (such as your mobile phone) and you need -to disable two-factor authentication on your account, you have several options. - -### Use a saved recovery code - -When you enabled two-factor authentication for your account, a series of -recovery codes were generated. If you saved those codes somewhere safe, you -may use one to sign in. - -First, enter your username/email and password on the GitLab sign in page. When -prompted for a two-factor code, enter one of the recovery codes you saved -previously. - -> **Note:** Once a particular recovery code has been used, it cannot be used again. - You may still use the other saved recovery codes at a later time. - -### Generate new recovery codes using SSH - -It's not uncommon for users to forget to save the recovery codes when enabling -two-factor authentication. If you have an SSH key added to your GitLab account, -you can generate a new set of recovery codes using SSH. - -Run `ssh git@gitlab.example.com 2fa_recovery_codes`. You will be prompted to -confirm that you wish to generate new codes. If you choose to continue, any -previously saved codes will be invalidated. - -```bash -$ ssh git@gitlab.example.com 2fa_recovery_codes -Are you sure you want to generate new two-factor recovery codes? -Any existing recovery codes you saved will be invalidated. (yes/no) -yes - -Your two-factor authentication recovery codes are: - -119135e5a3ebce8e -11f6v2a498810dcd -3924c7ab2089c902 -e79a3398bfe4f224 -34bd7b74adbc8861 -f061691d5107df1a -169bf32a18e63e7f -b510e7422e81c947 -20dbed24c5e74663 -df9d3b9403b9c9f0 - -During sign in, use one of the codes above when prompted for -your two-factor code. Then, visit your Profile Settings and add -a new device so you do not lose access to your account again. -``` - -Next, go to the GitLab sign in page and enter your username/email and password. -When prompted for a two-factor code, enter one of the recovery codes obtained -from the command line output. - -> **Note:** After signing in, you should immediately visit your **Profile Settings - -> Account** to set up two-factor authentication with a new device. - -### Ask a GitLab administrator to disable two-factor on your account - -If the above two methods are not possible, you may ask a GitLab global -administrator to disable two-factor authentication for your account. Please -be aware that this will temporarily leave your account in a less secure state. -You should sign in and re-enable two-factor authentication as soon as possible -after the administrator disables it. +This document was moved to [profile/account/two_factor_authentication](../profile/account/two_factor_authentication.md). diff --git a/doc/user/markdown.md b/doc/user/markdown.md index f6484688721..008872b59a7 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -300,6 +300,20 @@ You can add task lists to issues, merge requests and comments. To create a task - [x] Sub-task 2 - [ ] Sub-task 3 +Tasks formatted as ordered lists are supported as well: + +```no-highlight +1. [x] Completed task +1. [ ] Incomplete task + 1. [ ] Sub-task 1 + 1. [x] Sub-task 2 +``` + +1. [x] Completed task +1. [ ] Incomplete task + 1. [ ] Sub-task 1 + 1. [x] Sub-task 2 + Task lists can only be created in descriptions, not in titles. Task item state can be managed by editing the description's Markdown or by toggling the rendered check boxes. ### Videos @@ -650,7 +664,7 @@ This line is separated from the one above by two newlines, so it will be a *sepa This line is also a separate paragraph, but... This line is only separated by a single newline, so it's a separate line in the *same paragraph*. -This line is also a separate paragraph, and... +This line is also a separate paragraph, and... This line is on its own line, because the previous line ends with two spaces. ``` @@ -662,7 +676,7 @@ This line is separated from the one above by two newlines, so it will be a *sepa This line is also begins a separate paragraph, but... This line is only separated by a single newline, so it's a separate line in the *same paragraph*. -This line is also a separate paragraph, and... +This line is also a separate paragraph, and... This line is on its own line, because the previous line ends with two spaces. @@ -800,4 +814,4 @@ A link starting with a `/` is relative to the wiki root. [redcarpet]: https://github.com/vmg/redcarpet "Redcarpet website" [katex]: https://github.com/Khan/KaTeX "KaTeX website" [katex-subset]: https://github.com/Khan/KaTeX/wiki/Function-Support-in-KaTeX "Macros supported by KaTeX" -[asciidoctor-manual]: http://asciidoctor.org/docs/user-manual/#activating-stem-support "Asciidoctor user manual"
\ No newline at end of file +[asciidoctor-manual]: http://asciidoctor.org/docs/user-manual/#activating-stem-support "Asciidoctor user manual" diff --git a/doc/profile/2fa.png b/doc/user/profile/account/img/2fa.png Binary files differindex bb464efa685..bb464efa685 100644 --- a/doc/profile/2fa.png +++ b/doc/user/profile/account/img/2fa.png diff --git a/doc/profile/2fa_auth.png b/doc/user/profile/account/img/2fa_auth.png Binary files differindex 0caaed10805..0caaed10805 100644 --- a/doc/profile/2fa_auth.png +++ b/doc/user/profile/account/img/2fa_auth.png diff --git a/doc/user/profile/account/img/2fa_u2f_authenticate.png b/doc/user/profile/account/img/2fa_u2f_authenticate.png Binary files differnew file mode 100644 index 00000000000..ff2e936764d --- /dev/null +++ b/doc/user/profile/account/img/2fa_u2f_authenticate.png diff --git a/doc/profile/2fa_u2f_register.png b/doc/user/profile/account/img/2fa_u2f_register.png Binary files differindex 1cc142aa851..1cc142aa851 100644 --- a/doc/profile/2fa_u2f_register.png +++ b/doc/user/profile/account/img/2fa_u2f_register.png diff --git a/doc/user/profile/account/index.md b/doc/user/profile/account/index.md new file mode 100644 index 00000000000..764354e1e96 --- /dev/null +++ b/doc/user/profile/account/index.md @@ -0,0 +1,5 @@ +# Profile settings + +## Account + +Set up [two-factor authentication](two_factor_authentication.md). diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md new file mode 100644 index 00000000000..cc688a7f99c --- /dev/null +++ b/doc/user/profile/account/two_factor_authentication.md @@ -0,0 +1,217 @@ +# Two-Factor Authentication + +Two-factor Authentication (2FA) provides an additional level of security to your +GitLab account. Once enabled, in addition to supplying your username and +password to login, you'll be prompted for a code generated by an application on +your phone. + +By enabling 2FA, the only way someone other than you can log into your account +is to know your username and password *and* have access to your phone. + +## Overview + +> **Note:** +When you enable 2FA, don't forget to back up your recovery codes. + +In addition to a phone application, GitLab supports U2F (universal 2nd factor) devices as +the second factor of authentication. Once enabled, in addition to supplying your username and +password to login, you'll be prompted to activate your U2F device (usually by pressing +a button on it), and it will perform secure authentication on your behalf. + +The U2F workflow is only supported by Google Chrome at this point, so we _strongly_ recommend +that you set up both methods of two-factor authentication, so you can still access your account +from other browsers. + +## Enabling 2FA + +There are two ways to enable two-factor authentication: via a mobile application +or a U2F device. + +### Enable 2FA via mobile application + +**In GitLab:** + +1. Log in to your GitLab account. +1. Go to your **Profile Settings**. +1. Go to **Account**. +1. Click **Enable Two-factor Authentication**. + +![Two-factor setup](img/2fa.png) + +**On your phone:** + +1. Install a compatible application. We recommend [Google Authenticator] +\(proprietary\) or [FreeOTP] \(open source\). +1. In the application, add a new entry in one of two ways: + * Scan the code with your phone's camera to add the entry automatically. + * Enter the details provided to add the entry manually. + +**In GitLab:** + +1. Enter the six-digit pin number from the entry on your phone into the **Pin + code** field. +1. Click **Submit**. + +If the pin you entered was correct, you'll see a message indicating that +Two-Factor Authentication has been enabled, and you'll be presented with a list +of recovery codes. + +### Enable 2FA via U2F device + +> **Notes:** +- GitLab officially only supports [Yubikey] U2F devices. +- Support for U2F devices was added in GitLab 8.8. + +**In GitLab:** + +1. Log in to your GitLab account. +1. Go to your **Profile Settings**. +1. Go to **Account**. +1. Click **Enable Two-Factor Authentication**. +1. Plug in your U2F device. +1. Click on **Setup New U2F Device**. +1. A light will start blinking on your device. Activate it by pressing its button. + +You will see a message indicating that your device was successfully set up. +Click on **Register U2F Device** to complete the process. + +![Two-Factor U2F Setup](img/2fa_u2f_register.png) + +## Recovery Codes + +> **Note:** +Recovery codes are not generated for U2F devices. + +Should you ever lose access to your phone, you can use one of the ten provided +backup codes to login to your account. We suggest copying or printing them for +storage in a safe place. **Each code can be used only once** to log in to your +account. + +If you lose the recovery codes or just want to generate new ones, you can do so +from the **Profile settings ➔ Account** page where you first enabled 2FA. + +## Logging in with 2FA Enabled + +Logging in with 2FA enabled is only slightly different than a normal login. +Enter your username and password credentials as you normally would, and you'll +be presented with a second prompt, depending on which type of 2FA you've enabled. + +### Log in via mobile application + +Enter the pin from your phone's application or a recovery code to log in. + +![Two-Factor Authentication on sign in via OTP](img/2fa_auth.png) + +### Log in via U2F device + +1. Click **Login via U2F Device** +1. A light will start blinking on your device. Activate it by pressing its button. + +You will see a message indicating that your device responded to the authentication request. +Click on **Authenticate via U2F Device** to complete the process. + +![Two-Factor Authentication on sign in via U2F device](img/2fa_u2f_authenticate.png) + +## Disabling 2FA + +1. Log in to your GitLab account. +1. Go to your **Profile Settings**. +1. Go to **Account**. +1. Click **Disable**, under **Two-Factor Authentication**. + +This will clear all your two-factor authentication registrations, including mobile +applications and U2F devices. + +## Personal access tokens + +When 2FA is enabled, you can no longer use your normal account password to +authenticate with Git over HTTPS on the command line, you must use a personal +access token instead. + +1. Log in to your GitLab account. +1. Go to your **Profile Settings**. +1. Go to **Access Tokens**. +1. Choose a name and expiry date for the token. +1. Click on **Create Personal Access Token**. +1. Save the personal access token somewhere safe. + +When using Git over HTTPS on the command line, enter the personal access token +into the password field. + +## Recovery options + +To disable two-factor authentication on your account (for example, if you +have lost your code generation device) you can: +* [Use a saved recovery code](#use-a-saved-recovery-code) +* [Generate new recovery codes using SSH](#generate-new-recovery-codes-using-SSH) +* [Ask a GitLab administrator to disable two-factor authentication on your account](#ask-a-gitlab-administrator-to-disable-two-factor-authentication-on-your-account) + +### Use a saved recovery code + +Enabling two-factor authentication for your account generated several recovery +codes. If you saved these codes, you can use one of them to sign in. + +To use a recovery code, enter your username/email and password on the GitLab +sign-in page. When prompted for a two-factor code, enter the recovery code. + +> **Note:** Once you use a recovery code, you cannot re-use it. You can still + use the other recovery codes you saved. + +### Generate new recovery codes using SSH + +Users often forget to save their recovery codes when enabling two-factor +authentication. If an SSH key is added to your GitLab account, you can generate +a new set of recovery codes with SSH. + +1. Run `ssh git@gitlab.example.com 2fa_recovery_codes`. +2. You are prompted to confirm that you want to generate new codes. Continuing this process invalidates previously saved codes. + ``` + bash + $ ssh git@gitlab.example.com 2fa_recovery_codes + Are you sure you want to generate new two-factor recovery codes? + Any existing recovery codes you saved will be invalidated. (yes/no) + + yes + + Your two-factor authentication recovery codes are: + + 119135e5a3ebce8e + 11f6v2a498810dcd + 3924c7ab2089c902 + e79a3398bfe4f224 + 34bd7b74adbc8861 + f061691d5107df1a + 169bf32a18e63e7f + b510e7422e81c947 + 20dbed24c5e74663 + df9d3b9403b9c9f0 + + During sign in, use one of the codes above when prompted for your + two-factor code. Then, visit your Profile Settings and add a new device + so you do not lose access to your account again. + ``` +3. Go to the GitLab sign-in page and enter your username/email and password. When prompted for a two-factor code, enter one of the recovery codes obtained +from the command-line output. + +> **Note:** After signing in, visit your **Profile Settings -> Account** immediately to set up two-factor authentication with a new + device. + +### Ask a GitLab administrator to disable two-factor authentication on your account + +If you cannot use a saved recovery code or generate new recovery codes, ask a +GitLab global administrator to disable two-factor authentication for your +account. This will temporarily leave your account in a less secure state. +Sign in and re-enable two-factor authentication as soon as possible. + +## Note to GitLab administrators + +- You need to take special care to that 2FA keeps working after +[restoring a GitLab backup](../raketasks/backup_restore.md). + +- To ensure 2FA authorizes correctly with TOTP server, you may want to ensure +your GitLab server's time is synchronized via a service like NTP. Otherwise, +you may have cases where authorization always fails because of time differences. + +[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en +[FreeOTP]: https://fedorahosted.org/freeotp/ +[YubiKey]: https://www.yubico.com/products/yubikey-hardware/ diff --git a/doc/user/project/issues/due_dates.md b/doc/user/project/issues/due_dates.md new file mode 100644 index 00000000000..b516d47ffa3 --- /dev/null +++ b/doc/user/project/issues/due_dates.md @@ -0,0 +1,37 @@ +# Due dates + +> [Introduced][ce-3614] in GitLab 8.7. + +Due dates can be used in issues to keep track of deadlines and make sure +features are shipped on time. Due dates require at least [Reporter permissions][permissions] +to be able to edit them. On the contrary, they can be seen by everybody. + +## Setting a due date + +When creating or editing an issue, you can see the due date field from where +a calendar will appear to help you choose the date you want. To remove it, +select the date text and delete it. + +![Create a due date](img/due_dates_create.png) + +A quicker way to set a due date is via the issue sidebar. Simply expand the +sidebar and select **Edit** to pick a due date or remove the existing one. +Changes are saved immediately. + +![Edit a due date via the sidebar](img/due_dates_edit_sidebar.png) + +## Making use of due dates + +Issues that have a due date can be distinctively seen in the issues index page +with a calendar icon next to them. Issues where the date is past due will have +the icon and the date colored red. You can sort issues by those that are +_Due soon_ or _Due later_ from the dropdown menu in the right. + +![Issues with due dates in the issues index page](img/due_dates_issues_index_page.png) + +Due dates also appear in your [todos list](../../../workflow/todos.md). + +![Issues with due dates in the todos](img/due_dates_todos.png) + +[ce-3614]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3614 +[permissions]: ../../permissions.md#project diff --git a/doc/user/project/issues/img/confidential_issues_create.png b/doc/user/project/issues/img/confidential_issues_create.png Binary files differindex 4e6be3cc431..d259255599d 100644 --- a/doc/user/project/issues/img/confidential_issues_create.png +++ b/doc/user/project/issues/img/confidential_issues_create.png diff --git a/doc/user/project/issues/img/confidential_issues_search_guest.png b/doc/user/project/issues/img/confidential_issues_search_guest.png Binary files differindex 1af3d04d507..b85de90b4d5 100644 --- a/doc/user/project/issues/img/confidential_issues_search_guest.png +++ b/doc/user/project/issues/img/confidential_issues_search_guest.png diff --git a/doc/user/project/issues/img/confidential_issues_search_master.png b/doc/user/project/issues/img/confidential_issues_search_master.png Binary files differindex b2edeb6094a..bf2b9428875 100644 --- a/doc/user/project/issues/img/confidential_issues_search_master.png +++ b/doc/user/project/issues/img/confidential_issues_search_master.png diff --git a/doc/user/project/issues/img/due_dates_create.png b/doc/user/project/issues/img/due_dates_create.png Binary files differnew file mode 100644 index 00000000000..d2fe1172bab --- /dev/null +++ b/doc/user/project/issues/img/due_dates_create.png diff --git a/doc/user/project/issues/img/due_dates_edit_sidebar.png b/doc/user/project/issues/img/due_dates_edit_sidebar.png Binary files differnew file mode 100644 index 00000000000..6b37150e7db --- /dev/null +++ b/doc/user/project/issues/img/due_dates_edit_sidebar.png diff --git a/doc/user/project/issues/img/due_dates_issues_index_page.png b/doc/user/project/issues/img/due_dates_issues_index_page.png Binary files differnew file mode 100644 index 00000000000..defcd5eca39 --- /dev/null +++ b/doc/user/project/issues/img/due_dates_issues_index_page.png diff --git a/doc/user/project/issues/img/due_dates_todos.png b/doc/user/project/issues/img/due_dates_todos.png Binary files differnew file mode 100644 index 00000000000..92c9fd4021b --- /dev/null +++ b/doc/user/project/issues/img/due_dates_todos.png diff --git a/doc/user/project/merge_requests/img/merge_conflict_editor.png b/doc/user/project/merge_requests/img/merge_conflict_editor.png Binary files differnew file mode 100644 index 00000000000..6660920c191 --- /dev/null +++ b/doc/user/project/merge_requests/img/merge_conflict_editor.png diff --git a/doc/user/project/merge_requests/resolve_conflicts.md b/doc/user/project/merge_requests/resolve_conflicts.md index 4d7225bd820..68c49054e47 100644 --- a/doc/user/project/merge_requests/resolve_conflicts.md +++ b/doc/user/project/merge_requests/resolve_conflicts.md @@ -21,6 +21,18 @@ request into the source branch, resolving the conflicts using the options chosen. If the source branch is `feature` and the target branch is `master`, this is similar to performing `git checkout feature; git merge master` locally. +## Merge conflict editor + +> Introduced in GitLab 8.13. + +The merge conflict resolution editor allows for more complex merge conflicts, +which require the user to manually modify a file in order to resolve a conflict, +to be solved right form the GitLab interface. Use the **Edit inline** button +to open the editor. Once you're sure about your changes, hit the +**Commit conflict resolution** button. + +![Merge conflict editor](img/merge_conflict_editor.png) + ## Conflicts available for resolution GitLab allows resolving conflicts in a file where all of the below are true: diff --git a/doc/workflow/README.md b/doc/workflow/README.md index a56ae75fe69..0b6f00c6aa4 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -10,6 +10,7 @@ - Issues - The GitLab Issue Tracker is an advanced and complete tool for tracking the evolution of a new idea or the process of solving a problem. - [Confidential issues](../user/project/issues/confidential_issues.md) + - [Due date for issues](../user/project/issues/due_dates.md) - [Issue Board](../user/project/issue_board.md) - [Keyboard shortcuts](shortcuts.md) - [File finder](file_finder.md) diff --git a/features/steps/project/merge_requests/revert.rb b/features/steps/project/merge_requests/revert.rb index 3cc4fe9dafb..31f95b524b3 100644 --- a/features/steps/project/merge_requests/revert.rb +++ b/features/steps/project/merge_requests/revert.rb @@ -30,14 +30,13 @@ class Spinach::Features::RevertMergeRequests < Spinach::FeatureSteps end step 'I am signed in as a developer of the project' do + @user = create(:user) { |u| @project.add_developer(u) } login_as(@user) end step 'There is an open Merge Request' do - @user = create(:user) - @project = create(:project, :public, :repository) - @project_member = create(:project_member, :developer, user: @user, project: @project) - @merge_request = create(:merge_request, :with_diffs, :simple, source_project: @project) + @merge_request = create(:merge_request, :with_diffs, :simple) + @project = @merge_request.source_project end step 'I should see a revert error' do diff --git a/features/support/env.rb b/features/support/env.rb index 8dbe3624410..f394c30d52f 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -4,7 +4,6 @@ SimpleCovEnv.start! ENV['RAILS_ENV'] = 'test' require './config/environment' require 'rspec/expectations' -require 'sidekiq/testing/inline' require_relative 'capybara' require_relative 'db_cleaner' @@ -15,7 +14,7 @@ if ENV['CI'] Knapsack::Adapters::SpinachAdapter.bind end -%w(select2_helper test_env repo_helpers wait_for_ajax).each do |f| +%w(select2_helper test_env repo_helpers wait_for_ajax sidekiq).each do |f| require Rails.root.join('spec', 'support', f) end diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 0950c3d2e88..be659fa4a6a 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -129,12 +129,7 @@ module API end end - # Delete all merged branches - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # DELETE /projects/:id/repository/branches/delete_merged + desc 'Delete all merged branches' delete ":id/repository/merged_branches" do DeleteMergedBranchesService.new(user_project, current_user).async_execute diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index 6d04f68c8f9..a3d495a5da0 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -153,7 +153,7 @@ module Banzai title = object_link_title(object) klass = reference_class(object_sym) - data = data_attributes_for(link_content || match, project, object) + data = data_attributes_for(link_content || match, project, object, link: !!link_content) if matches.names.include?("url") && matches[:url] url = matches[:url] @@ -172,9 +172,10 @@ module Banzai end end - def data_attributes_for(text, project, object) + def data_attributes_for(text, project, object, link: false) data_attribute( original: text, + link: link, project: project.id, object_sym => object.id ) diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb index 4d1bc687696..fd6b9704132 100644 --- a/lib/banzai/filter/issue_reference_filter.rb +++ b/lib/banzai/filter/issue_reference_filter.rb @@ -62,7 +62,7 @@ module Banzai end end - def data_attributes_for(text, project, object) + def data_attributes_for(text, project, object, link: false) if object.is_a?(ExternalIssue) data_attribute( project: project.id, diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb index ab7af1cad21..6640168bfa2 100644 --- a/lib/banzai/filter/reference_filter.rb +++ b/lib/banzai/filter/reference_filter.rb @@ -53,6 +53,10 @@ module Banzai context[:project] end + def skip_project_check? + context[:skip_project_check] + end + def reference_class(type) "gfm gfm-#{type} has-tooltip" end diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb index 026b81ac175..a447e2b8bff 100644 --- a/lib/banzai/filter/syntax_highlight_filter.rb +++ b/lib/banzai/filter/syntax_highlight_filter.rb @@ -20,17 +20,19 @@ module Banzai code = node.text css_classes = "code highlight" lexer = lexer_for(language) + lang = lexer.tag begin code = format(lex(lexer, code)) - css_classes << " js-syntax-highlight #{lexer.tag}" + css_classes << " js-syntax-highlight #{lang}" rescue + lang = nil # Gracefully handle syntax highlighter bugs/errors to ensure # users can still access an issue/comment/etc. end - highlighted = %(<pre class="#{css_classes}" v-pre="true"><code>#{code}</code></pre>) + highlighted = %(<pre class="#{css_classes}" lang="#{lang}" v-pre="true"><code>#{code}</code></pre>) # Extracted to a method to measure it replace_parent_pre_element(node, highlighted) diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb index f842b1fb779..1aa9355b256 100644 --- a/lib/banzai/filter/user_reference_filter.rb +++ b/lib/banzai/filter/user_reference_filter.rb @@ -24,7 +24,7 @@ module Banzai end def call - return doc if project.nil? + return doc if project.nil? && !skip_project_check? ref_pattern = User.reference_pattern ref_pattern_start = /\A#{ref_pattern}\z/ @@ -58,7 +58,7 @@ module Banzai # have `gfm` and `gfm-project_member` class names attached for styling. def user_link_filter(text, link_content: nil) self.class.references_in(text) do |match, username| - if username == 'all' + if username == 'all' && !skip_project_check? link_to_all(link_content: link_content) elsif namespace = namespaces[username] link_to_namespace(namespace, link_content: link_content) || match diff --git a/lib/banzai/filter/video_link_filter.rb b/lib/banzai/filter/video_link_filter.rb index ac7bbcb0d10..b64a1287d4d 100644 --- a/lib/banzai/filter/video_link_filter.rb +++ b/lib/banzai/filter/video_link_filter.rb @@ -35,7 +35,8 @@ module Banzai src: element['src'], width: '400', controls: true, - 'data-setup' => '{}') + 'data-setup' => '{}', + 'data-title' => element['title'] || element['alt']) link = doc.document.create_element( 'a', diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb index 5a1f873496c..ac95a79009b 100644 --- a/lib/banzai/pipeline/gfm_pipeline.rb +++ b/lib/banzai/pipeline/gfm_pipeline.rb @@ -1,6 +1,12 @@ module Banzai module Pipeline class GfmPipeline < BasePipeline + # These filters convert GitLab Flavored Markdown (GFM) to HTML. + # The handlers defined in app/assets/javascripts/copy_as_gfm.js.es6 + # consequently convert that same HTML to GFM to be copied to the clipboard. + # Every filter that generates HTML from GFM should have a handler in + # app/assets/javascripts/copy_as_gfm.js.es6, in reverse order. + # The GFM-to-HTML-to-GFM cycle is tested in spec/features/copy_as_gfm_spec.rb. def self.filters @filters ||= FilterArray[ Filter::SyntaxHighlightFilter, diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb index f31fb6c3f71..74663556cbb 100644 --- a/lib/banzai/renderer.rb +++ b/lib/banzai/renderer.rb @@ -52,9 +52,9 @@ module Banzai end # Same as +render_field+, but without consulting or updating the cache field - def cacheless_render_field(object, field) + def cacheless_render_field(object, field, options = {}) text = object.__send__(field) - context = object.banzai_render_context(field) + context = object.banzai_render_context(field).merge(options) cacheless_render(text, context) end diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index c4bdef781f7..8b939663ffd 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -18,24 +18,31 @@ module Ci if current_runner.is_runner_queue_value_latest?(params[:last_update]) header 'X-GitLab-Last-Update', params[:last_update] + Gitlab::Metrics.add_event(:build_not_found_cached) return build_not_found! end new_update = current_runner.ensure_runner_queue_value - build = Ci::RegisterBuildService.new.execute(current_runner) + result = Ci::RegisterBuildService.new(current_runner).execute - if build - Gitlab::Metrics.add_event(:build_found, - project: build.project.path_with_namespace) + if result.valid? + if result.build + Gitlab::Metrics.add_event(:build_found, + project: result.build.project.path_with_namespace) - present build, with: Entities::BuildDetails - else - Gitlab::Metrics.add_event(:build_not_found) + present result.build, with: Entities::BuildDetails + else + Gitlab::Metrics.add_event(:build_not_found) - header 'X-GitLab-Last-Update', new_update + header 'X-GitLab-Last-Update', new_update - build_not_found! + build_not_found! + end + else + # We received build that is invalid due to concurrency conflict + Gitlab::Metrics.add_event(:build_invalid) + conflict! end end diff --git a/lib/gitlab/ci/trace_reader.rb b/lib/gitlab/ci/trace_reader.rb index 37e51536e8f..1d7ddeb3e0f 100644 --- a/lib/gitlab/ci/trace_reader.rb +++ b/lib/gitlab/ci/trace_reader.rb @@ -42,6 +42,7 @@ module Gitlab end chunks.join.lines.last(max_lines).join + .force_encoding(Encoding.default_external) end end end diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 2ff27e46d64..4ebd48a3fc7 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -9,7 +9,9 @@ module Gitlab end def ensure_application_settings! - if connect_to_db? + return fake_application_settings unless connect_to_db? + + unless ENV['IN_MEMORY_APPLICATION_SETTINGS'] == 'true' begin settings = ::ApplicationSetting.current # In case Redis isn't running or the Redis UNIX socket file is not available @@ -20,43 +22,23 @@ module Gitlab settings ||= ::ApplicationSetting.create_from_defaults unless ActiveRecord::Migrator.needs_migration? end - settings || fake_application_settings + settings || in_memory_application_settings end def sidekiq_throttling_enabled? current_application_settings.sidekiq_throttling_enabled? end + def in_memory_application_settings + @in_memory_application_settings ||= ::ApplicationSetting.new(::ApplicationSetting::DEFAULTS) + # In case migrations the application_settings table is not created yet, + # we fallback to a simple OpenStruct + rescue ActiveRecord::StatementInvalid, ActiveRecord::UnknownAttributeError + fake_application_settings + end + def fake_application_settings - OpenStruct.new( - default_projects_limit: Settings.gitlab['default_projects_limit'], - default_branch_protection: Settings.gitlab['default_branch_protection'], - signup_enabled: Settings.gitlab['signup_enabled'], - signin_enabled: Settings.gitlab['signin_enabled'], - gravatar_enabled: Settings.gravatar['enabled'], - koding_enabled: false, - plantuml_enabled: false, - sign_in_text: nil, - after_sign_up_text: nil, - help_page_text: nil, - shared_runners_text: nil, - restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], - max_attachment_size: Settings.gitlab['max_attachment_size'], - session_expire_delay: Settings.gitlab['session_expire_delay'], - default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], - default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], - domain_whitelist: Settings.gitlab['domain_whitelist'], - import_sources: %w[gitea github bitbucket gitlab google_code fogbugz git gitlab_project], - shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], - max_artifacts_size: Settings.artifacts['max_size'], - require_two_factor_authentication: false, - two_factor_grace_period: 48, - akismet_enabled: false, - repository_checks_enabled: true, - container_registry_token_expire_delay: 5, - user_default_external: false, - sidekiq_throttling_enabled: false, - ) + OpenStruct.new(::ApplicationSetting::DEFAULTS) end private diff --git a/lib/gitlab/github_import/project_creator.rb b/lib/gitlab/github_import/project_creator.rb index 3f635be22ba..a55adc9b1c8 100644 --- a/lib/gitlab/github_import/project_creator.rb +++ b/lib/gitlab/github_import/project_creator.rb @@ -1,6 +1,8 @@ module Gitlab module GithubImport class ProjectCreator + include Gitlab::CurrentSettings + attr_reader :repo, :name, :namespace, :current_user, :session_data, :type def initialize(repo, name, namespace, current_user, session_data, type: 'github') @@ -34,7 +36,7 @@ module Gitlab end def visibility_level - repo.private ? Gitlab::VisibilityLevel::PRIVATE : ApplicationSetting.current.default_project_visibility + repo.private ? Gitlab::VisibilityLevel::PRIVATE : current_application_settings.default_project_visibility end # diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb index 45958710c13..52276cbcd9a 100644 --- a/lib/gitlab/import_sources.rb +++ b/lib/gitlab/import_sources.rb @@ -5,8 +5,6 @@ # module Gitlab module ImportSources - extend CurrentSettings - ImportSource = Struct.new(:name, :title, :importer) ImportTable = [ diff --git a/lib/gitlab/job_waiter.rb b/lib/gitlab/job_waiter.rb new file mode 100644 index 00000000000..8db91d25a4b --- /dev/null +++ b/lib/gitlab/job_waiter.rb @@ -0,0 +1,27 @@ +module Gitlab + # JobWaiter can be used to wait for a number of Sidekiq jobs to complete. + class JobWaiter + # The sleep interval between checking keys, in seconds. + INTERVAL = 0.1 + + # jobs - The job IDs to wait for. + def initialize(jobs) + @jobs = jobs + end + + # Waits for all the jobs to be completed. + # + # timeout - The maximum amount of seconds to block the caller for. This + # ensures we don't indefinitely block a caller in case a job takes + # long to process, or is never processed. + def wait(timeout = 60) + start = Time.current + + while (Time.current - start) <= timeout + break if SidekiqStatus.all_completed?(@jobs) + + sleep(INTERVAL) # to not overload Redis too much. + end + end + end +end diff --git a/lib/gitlab/sidekiq_status.rb b/lib/gitlab/sidekiq_status.rb new file mode 100644 index 00000000000..aadc401ff8d --- /dev/null +++ b/lib/gitlab/sidekiq_status.rb @@ -0,0 +1,66 @@ +module Gitlab + # The SidekiqStatus module and its child classes can be used for checking if a + # Sidekiq job has been processed or not. + # + # To check if a job has been completed, simply pass the job ID to the + # `completed?` method: + # + # job_id = SomeWorker.perform_async(...) + # + # if Gitlab::SidekiqStatus.completed?(job_id) + # ... + # end + # + # For each job ID registered a separate key is stored in Redis, making lookups + # much faster than using Sidekiq's built-in job finding/status API. These keys + # expire after a certain period of time to prevent storing too many keys in + # Redis. + module SidekiqStatus + STATUS_KEY = 'gitlab-sidekiq-status:%s'.freeze + + # The default time (in seconds) after which a status key is expired + # automatically. The default of 30 minutes should be more than sufficient + # for most jobs. + DEFAULT_EXPIRATION = 30.minutes.to_i + + # Starts tracking of the given job. + # + # jid - The Sidekiq job ID + # expire - The expiration time of the Redis key. + def self.set(jid, expire = DEFAULT_EXPIRATION) + Sidekiq.redis do |redis| + redis.set(key_for(jid), 1, ex: expire) + end + end + + # Stops the tracking of the given job. + # + # jid - The Sidekiq job ID to remove. + def self.unset(jid) + Sidekiq.redis do |redis| + redis.del(key_for(jid)) + end + end + + # Returns true if all the given job have been completed. + # + # jids - The Sidekiq job IDs to check. + # + # Returns true or false. + def self.all_completed?(jids) + keys = jids.map { |jid| key_for(jid) } + + responses = Sidekiq.redis do |redis| + redis.pipelined do + keys.each { |key| redis.exists(key) } + end + end + + responses.all? { |value| !value } + end + + def self.key_for(jid) + STATUS_KEY % jid + end + end +end diff --git a/lib/gitlab/sidekiq_status/client_middleware.rb b/lib/gitlab/sidekiq_status/client_middleware.rb new file mode 100644 index 00000000000..779a9998b22 --- /dev/null +++ b/lib/gitlab/sidekiq_status/client_middleware.rb @@ -0,0 +1,10 @@ +module Gitlab + module SidekiqStatus + class ClientMiddleware + def call(_, job, _, _) + SidekiqStatus.set(job['jid']) + yield + end + end + end +end diff --git a/lib/gitlab/sidekiq_status/server_middleware.rb b/lib/gitlab/sidekiq_status/server_middleware.rb new file mode 100644 index 00000000000..31dfa46ff9d --- /dev/null +++ b/lib/gitlab/sidekiq_status/server_middleware.rb @@ -0,0 +1,13 @@ +module Gitlab + module SidekiqStatus + class ServerMiddleware + def call(worker, job, queue) + ret = yield + + SidekiqStatus.unset(job['jid']) + + ret + end + end + end +end diff --git a/lib/gitlab/view/presenter/base.rb b/lib/gitlab/view/presenter/base.rb index 83c8ba5c1cf..dbfe0941e4d 100644 --- a/lib/gitlab/view/presenter/base.rb +++ b/lib/gitlab/view/presenter/base.rb @@ -1,6 +1,8 @@ module Gitlab module View module Presenter + CannotOverrideMethodError = Class.new(StandardError) + module Base extend ActiveSupport::Concern diff --git a/lib/gitlab/view/presenter/delegated.rb b/lib/gitlab/view/presenter/delegated.rb index f4d330c590e..387ff0f5d43 100644 --- a/lib/gitlab/view/presenter/delegated.rb +++ b/lib/gitlab/view/presenter/delegated.rb @@ -8,6 +8,10 @@ module Gitlab @subject = subject attributes.each do |key, value| + if subject.respond_to?(key) + raise CannotOverrideMethodError.new("#{subject} already respond to #{key}!") + end + define_singleton_method(key) { value } end diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb index 9462f3368e6..c7953af29dd 100644 --- a/lib/gitlab/visibility_level.rb +++ b/lib/gitlab/visibility_level.rb @@ -11,6 +11,7 @@ module Gitlab included do scope :public_only, -> { where(visibility_level: PUBLIC) } scope :public_and_internal_only, -> { where(visibility_level: [PUBLIC, INTERNAL] ) } + scope :non_public_only, -> { where.not(visibility_level: PUBLIC) } scope :public_to_user, -> (user) { user && !user.external ? public_and_internal_only : public_only } end diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake index dffea8ed155..f7c831892ee 100644 --- a/lib/tasks/gitlab/info.rake +++ b/lib/tasks/gitlab/info.rake @@ -11,8 +11,10 @@ namespace :gitlab do gem_version = run_command(%W(gem --version)) # check Bundler version bunder_version = run_and_match(%W(bundle --version), /[\d\.]+/).try(:to_s) - # check Bundler version + # check Rake version rake_version = run_and_match(%W(rake --version), /[\d\.]+/).try(:to_s) + # check redis version + redis_version = run_and_match(%W(redis-cli --version), /redis-cli (\d+\.\d+\.\d+)/).to_a puts "" puts "System information".color(:yellow) @@ -24,6 +26,7 @@ namespace :gitlab do puts "Gem Version:\t#{gem_version || "unknown".color(:red)}" puts "Bundler Version:#{bunder_version || "unknown".color(:red)}" puts "Rake Version:\t#{rake_version || "unknown".color(:red)}" + puts "Redis Version:\t#{redis_version[1] || "unknown".color(:red)}" puts "Sidekiq Version:#{Sidekiq::VERSION}" diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb index 602de72d23f..84db26a958a 100644 --- a/spec/controllers/admin/groups_controller_spec.rb +++ b/spec/controllers/admin/groups_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Admin::GroupsController do let(:group) { create(:group) } - let(:project) { create(:project, namespace: group) } + let(:project) { create(:empty_project, namespace: group) } let(:admin) { create(:admin) } before do diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb index 8eaacef2024..2c35d394b74 100644 --- a/spec/controllers/admin/projects_controller_spec.rb +++ b/spec/controllers/admin/projects_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Admin::ProjectsController do - let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } + let!(:project) { create(:empty_project, :public) } before do sign_in(create(:admin)) diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index ea2fd90a9b0..7d2f6dd9d0a 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe AutocompleteController do - let!(:project) { create(:project) } + let!(:project) { create(:empty_project) } let!(:user) { create(:user) } context 'GET users' do diff --git a/spec/controllers/blob_controller_spec.rb b/spec/controllers/blob_controller_spec.rb index 465013231f9..2fcb4a6a528 100644 --- a/spec/controllers/blob_controller_spec.rb +++ b/spec/controllers/blob_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::BlobController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } before do diff --git a/spec/controllers/ci/projects_controller_spec.rb b/spec/controllers/ci/projects_controller_spec.rb index 5022a3e2c80..86f01f437a2 100644 --- a/spec/controllers/ci/projects_controller_spec.rb +++ b/spec/controllers/ci/projects_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Ci::ProjectsController do let(:visibility) { :public } - let!(:project) { create(:project, visibility, ci_id: 1) } + let!(:project) { create(:empty_project, visibility, ci_id: 1) } let(:ci_id) { project.ci_id } describe '#index' do diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb index 19fbc2f7748..79ef3a1adad 100644 --- a/spec/controllers/dashboard/todos_controller_spec.rb +++ b/spec/controllers/dashboard/todos_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Dashboard::TodosController do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:todo_service) { TodoService.new } describe 'GET #index' do diff --git a/spec/controllers/explore/projects_controller_spec.rb b/spec/controllers/explore/projects_controller_spec.rb new file mode 100644 index 00000000000..6128091f543 --- /dev/null +++ b/spec/controllers/explore/projects_controller_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe Explore::ProjectsController do + let(:user) { create(:user) } + let(:visibility) { :public } + + describe 'GET #trending' do + let!(:project_1) { create(:project, visibility, ci_id: 1) } + let!(:project_2) { create(:project, visibility, ci_id: 2) } + + let!(:trending_project_1) { create(:trending_project, project: project_1) } + let!(:trending_project_2) { create(:trending_project, project: project_2) } + + before do + sign_in(user) + end + + context 'sorting by update date' do + it 'sorts by last updated' do + get :trending, sort: 'updated_desc' + expect(assigns(:projects)).to eq [project_2, project_1] + end + + it 'sorts by oldest updated' do + get :trending, sort: 'updated_asc' + expect(assigns(:projects)).to eq [project_1, project_2] + end + end + end +end diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index 8c52f615b8b..6e4b5f78e33 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Groups::MilestonesController do let(:group) { create(:group) } - let(:project) { create(:project, group: group) } + let(:project) { create(:empty_project, group: group) } let(:project2) { create(:empty_project, group: group) } let(:user) { create(:user) } let(:title) { '肯定不是中文的问题' } diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 98dfb3e5216..cad82a34fb0 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' describe GroupsController do let(:user) { create(:user) } let(:group) { create(:group) } - let(:project) { create(:project, namespace: group) } + let(:project) { create(:empty_project, namespace: group) } let!(:group_member) { create(:group_member, group: group, user: user) } describe 'GET #index' do diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb index 56ecf2bb644..cfe18dd4b6c 100644 --- a/spec/controllers/health_check_controller_spec.rb +++ b/spec/controllers/health_check_controller_spec.rb @@ -1,10 +1,16 @@ require 'spec_helper' describe HealthCheckController do + include StubENV + let(:token) { current_application_settings.health_check_access_token } let(:json_response) { JSON.parse(response.body) } let(:xml_response) { Hash.from_xml(response.body)['hash'] } + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') + end + describe 'GET #index' do context 'when services are up but NO access token' do it 'returns a not found page' do diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb index ce7c0b334ee..fa4cc0ebbe0 100644 --- a/spec/controllers/import/bitbucket_controller_spec.rb +++ b/spec/controllers/import/bitbucket_controller_spec.rb @@ -52,7 +52,7 @@ describe Import::BitbucketController do end it "assigns variables" do - @project = create(:project, import_type: 'bitbucket', creator_id: user.id) + @project = create(:empty_project, import_type: 'bitbucket', creator_id: user.id) allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo]) get :status @@ -63,7 +63,7 @@ describe Import::BitbucketController do end it "does not show already added project" do - @project = create(:project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim') + @project = create(:empty_project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim') allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo]) get :status diff --git a/spec/controllers/import/fogbugz_controller_spec.rb b/spec/controllers/import/fogbugz_controller_spec.rb index 5f0f6dea821..fffbc805335 100644 --- a/spec/controllers/import/fogbugz_controller_spec.rb +++ b/spec/controllers/import/fogbugz_controller_spec.rb @@ -16,7 +16,7 @@ describe Import::FogbugzController do end it 'assigns variables' do - @project = create(:project, import_type: 'fogbugz', creator_id: user.id) + @project = create(:empty_project, import_type: 'fogbugz', creator_id: user.id) stub_client(repos: [@repo]) get :status @@ -26,7 +26,7 @@ describe Import::FogbugzController do end it 'does not show already added project' do - @project = create(:project, import_type: 'fogbugz', creator_id: user.id, import_source: 'vim') + @project = create(:empty_project, import_type: 'fogbugz', creator_id: user.id, import_source: 'vim') stub_client(repos: [@repo]) get :status diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb index 6f75ebb16c8..3f73ea000ae 100644 --- a/spec/controllers/import/gitlab_controller_spec.rb +++ b/spec/controllers/import/gitlab_controller_spec.rb @@ -36,7 +36,7 @@ describe Import::GitlabController do end it "assigns variables" do - @project = create(:project, import_type: 'gitlab', creator_id: user.id) + @project = create(:empty_project, import_type: 'gitlab', creator_id: user.id) stub_client(projects: [@repo]) get :status @@ -46,7 +46,7 @@ describe Import::GitlabController do end it "does not show already added project" do - @project = create(:project, import_type: 'gitlab', creator_id: user.id, import_source: 'asd/vim') + @project = create(:empty_project, import_type: 'gitlab', creator_id: user.id, import_source: 'asd/vim') stub_client(projects: [@repo]) get :status diff --git a/spec/controllers/import/google_code_controller_spec.rb b/spec/controllers/import/google_code_controller_spec.rb index 4241db6e771..c96fb90f70e 100644 --- a/spec/controllers/import/google_code_controller_spec.rb +++ b/spec/controllers/import/google_code_controller_spec.rb @@ -27,7 +27,7 @@ describe Import::GoogleCodeController do end it "assigns variables" do - @project = create(:project, import_type: 'google_code', creator_id: user.id) + @project = create(:empty_project, import_type: 'google_code', creator_id: user.id) stub_client(repos: [@repo], incompatible_repos: []) get :status @@ -38,7 +38,7 @@ describe Import::GoogleCodeController do end it "does not show already added project" do - @project = create(:project, import_type: 'google_code', creator_id: user.id, import_source: 'vim') + @project = create(:empty_project, import_type: 'google_code', creator_id: user.id, import_source: 'vim') stub_client(repos: [@repo], incompatible_repos: []) get :status diff --git a/spec/controllers/notification_settings_controller_spec.rb b/spec/controllers/notification_settings_controller_spec.rb index 79b819a1377..9e3a31e1a6b 100644 --- a/spec/controllers/notification_settings_controller_spec.rb +++ b/spec/controllers/notification_settings_controller_spec.rb @@ -93,7 +93,7 @@ describe NotificationSettingsController do end context 'not authorized' do - let(:private_project) { create(:project, :private) } + let(:private_project) { create(:empty_project, :private) } before { sign_in(user) } it 'returns 404' do diff --git a/spec/controllers/projects/avatars_controller_spec.rb b/spec/controllers/projects/avatars_controller_spec.rb index f5ea097af8b..8b71d6518bb 100644 --- a/spec/controllers/projects/avatars_controller_spec.rb +++ b/spec/controllers/projects/avatars_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::AvatarsController do - let(:project) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } + let(:project) { create(:empty_project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/blame_controller_spec.rb b/spec/controllers/projects/blame_controller_spec.rb index 4402ca43c65..addc5e7ec33 100644 --- a/spec/controllers/projects/blame_controller_spec.rb +++ b/spec/controllers/projects/blame_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::BlameController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index f35c5d992d9..b36d0e69330 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' describe Projects::BlobController do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index b88586b8678..9de03876755 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::BranchesController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:developer) { create(:user) } diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index 0fa06a38d2a..a95cfc5c6be 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::CommitController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:commit) { project.commit("master") } let(:pipeline) { create(:ci_pipeline, project: project, commit: commit) } diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb index 1ac7e03a2db..54b8d1108a5 100644 --- a/spec/controllers/projects/commits_controller_spec.rb +++ b/spec/controllers/projects/commits_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::CommitsController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index b03c4b52de6..e811c76fb31 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::CompareController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:ref_from) { "improve%2Fawesome" } let(:ref_to) { "feature" } diff --git a/spec/controllers/projects/cycle_analytics_controller_spec.rb b/spec/controllers/projects/cycle_analytics_controller_spec.rb index a971adf0539..6a6d71a16ee 100644 --- a/spec/controllers/projects/cycle_analytics_controller_spec.rb +++ b/spec/controllers/projects/cycle_analytics_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::CycleAnalyticsController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/discussions_controller_spec.rb b/spec/controllers/projects/discussions_controller_spec.rb index ff617fea847..79ab364a6f3 100644 --- a/spec/controllers/projects/discussions_controller_spec.rb +++ b/spec/controllers/projects/discussions_controller_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' describe Projects::DiscussionsController do - let(:user) { create(:user) } - let(:project) { create(:project) } - let(:merge_request) { create(:merge_request, source_project: project) } + let(:user) { create(:user) } + let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.source_project } let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) } let(:discussion) { note.discussion } diff --git a/spec/controllers/projects/find_file_controller_spec.rb b/spec/controllers/projects/find_file_controller_spec.rb index 038dfeb8466..a4884256c92 100644 --- a/spec/controllers/projects/find_file_controller_spec.rb +++ b/spec/controllers/projects/find_file_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::FindFileController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb index 028ea067a97..a867668d97b 100644 --- a/spec/controllers/projects/forks_controller_spec.rb +++ b/spec/controllers/projects/forks_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::ForksController do let(:user) { create(:user) } - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:forked_project) { Projects::ForkService.new(project, user).execute } let(:group) { create(:group, owner: forked_project.creator) } diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb index 74e6603b0cb..bbe8e4bf6b2 100644 --- a/spec/controllers/projects/graphs_controller_spec.rb +++ b/spec/controllers/projects/graphs_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::GraphsController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb index 17dc101b7ee..a976a9c27ab 100644 --- a/spec/controllers/projects/group_links_controller_spec.rb +++ b/spec/controllers/projects/group_links_controller_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Projects::GroupLinksController do let(:group) { create(:group, :private) } let(:group2) { create(:group, :private) } - let(:project) { create(:project, :private, group: group2) } + let(:project) { create(:empty_project, :private, group: group2) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index b5987a83df0..5f27f336f72 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -98,7 +98,7 @@ describe Projects::IssuesController do end it 'fills in an issue for a merge request' do - project_with_repository = create(:project) + project_with_repository = create(:project, :repository) project_with_repository.team << [user, :developer] mr = create(:merge_request_with_diff_notes, source_project: project_with_repository) @@ -124,7 +124,7 @@ describe Projects::IssuesController do describe 'PUT #update' do context 'when moving issue to another private project' do - let(:another_project) { create(:project, :private) } + let(:another_project) { create(:empty_project, :private) } before do sign_in(user) @@ -466,7 +466,7 @@ describe Projects::IssuesController do context "when the user is owner" do let(:owner) { create(:user) } let(:namespace) { create(:namespace, owner: owner) } - let(:project) { create(:project, namespace: namespace) } + let(:project) { create(:empty_project, namespace: namespace) } before { sign_in(owner) } diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb index 6d30d085056..14207bf6b7a 100644 --- a/spec/controllers/projects/milestones_controller_spec.rb +++ b/spec/controllers/projects/milestones_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::MilestonesController do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:user) { create(:user) } let(:milestone) { create(:milestone, project: project) } let(:issue) { create(:issue, project: project, milestone: milestone) } diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb index 9f6d4ec6537..dc597202050 100644 --- a/spec/controllers/projects/notes_controller_spec.rb +++ b/spec/controllers/projects/notes_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::NotesController do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:issue) { create(:issue, project: project) } let(:note) { create(:note, noteable: issue, project: project) } @@ -16,6 +16,7 @@ describe Projects::NotesController do describe 'POST create' do let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.source_project } let(:request_params) do { note: { note: 'some note', noteable_id: merge_request.id, noteable_type: 'MergeRequest' }, @@ -88,6 +89,7 @@ describe Projects::NotesController do end describe "resolving and unresolving" do + let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, source_project: project) } let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) } diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 442f81187dc..416eaa0037e 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -143,7 +143,7 @@ describe Projects::ProjectMembersController do end context 'and is an owner' do - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { create(:empty_project, namespace: user.namespace) } before { project.team << [user, :master] } @@ -234,7 +234,7 @@ describe Projects::ProjectMembersController do end describe 'POST apply_import' do - let(:another_project) { create(:project, :private) } + let(:another_project) { create(:empty_project, :private) } let(:member) { create(:user) } before do diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb index 04bd9a01f7b..b23d6e257ba 100644 --- a/spec/controllers/projects/raw_controller_spec.rb +++ b/spec/controllers/projects/raw_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::RawController do - let(:public_project) { create(:project, :public) } + let(:public_project) { create(:project, :public, :repository) } describe "#show" do context 'regular filename' do diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb index abd45a74e2d..d8fb4667c67 100644 --- a/spec/controllers/projects/refs_controller_spec.rb +++ b/spec/controllers/projects/refs_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::RefsController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb index 9fd5c3b85f6..69fcc26c77e 100644 --- a/spec/controllers/projects/releases_controller_spec.rb +++ b/spec/controllers/projects/releases_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::ReleasesController do - let!(:project) { create(:project) } + let!(:project) { create(:project, :repository) } let!(:user) { create(:user) } let!(:release) { create(:release, project: project) } let!(:tag) { release.tag } diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb index 38e02a46626..04e88879fb8 100644 --- a/spec/controllers/projects/repositories_controller_spec.rb +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe Projects::RepositoriesController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } describe "GET archive" do context 'as a guest' do diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index a6e708c01e4..16365642a34 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::ServicesController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:service) { create(:service, project: project) } diff --git a/spec/controllers/projects/settings/integrations_controller_spec.rb b/spec/controllers/projects/settings/integrations_controller_spec.rb index e0f9a5b24a6..65f7bb34f4a 100644 --- a/spec/controllers/projects/settings/integrations_controller_spec.rb +++ b/spec/controllers/projects/settings/integrations_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::Settings::IntegrationsController do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb index 5e661c2c41d..c36a5fdd66c 100644 --- a/spec/controllers/projects/tags_controller_spec.rb +++ b/spec/controllers/projects/tags_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::TagsController do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let!(:release) { create(:release, project: project) } let!(:invalid_release) { create(:release, project: project, tag: 'does-not-exist') } diff --git a/spec/controllers/projects/templates_controller_spec.rb b/spec/controllers/projects/templates_controller_spec.rb index 19a152bcb05..99d0bcfa8d1 100644 --- a/spec/controllers/projects/templates_controller_spec.rb +++ b/spec/controllers/projects/templates_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::TemplatesController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:user2) { create(:user) } let(:file_path_1) { '.gitlab/issue_templates/bug.md' } diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb index 1cc050247c6..b81645a3d2d 100644 --- a/spec/controllers/projects/tree_controller_spec.rb +++ b/spec/controllers/projects/tree_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::TreeController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb index 71d0e4be834..f1c8891e87a 100644 --- a/spec/controllers/projects/uploads_controller_spec.rb +++ b/spec/controllers/projects/uploads_controller_spec.rb @@ -1,7 +1,7 @@ require('spec_helper') describe Projects::UploadsController do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:user) { create(:user) } let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') } let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index d0a63aa9403..9323f723bdb 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -1,11 +1,11 @@ require('spec_helper') describe ProjectsController do - let(:project) { create(:project) } - let(:public_project) { create(:project, :public) } - let(:user) { create(:user) } - let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') } - let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } + let(:project) { create(:empty_project) } + let(:public_project) { create(:empty_project, :public) } + let(:user) { create(:user) } + let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') } + let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } describe 'GET index' do context 'as a user' do @@ -32,7 +32,7 @@ describe ProjectsController do before { sign_in(user) } context "user does not have access to project" do - let(:private_project) { create(:project, :private) } + let(:private_project) { create(:empty_project, :private) } it "does not initialize notification setting" do get :show, namespace_id: private_project.namespace.path, id: private_project.path @@ -146,6 +146,8 @@ describe ProjectsController do end context "rendering default project view" do + let(:public_project) { create(:project, :public, :repository) } + render_views it "renders the activity view" do @@ -190,25 +192,11 @@ describe ProjectsController do expect(assigns(:project)).to eq(public_project) expect(response).to redirect_to("/#{public_project.path_with_namespace}") end - - # MySQL queries are case insensitive by default, so this spec would fail. - if Gitlab::Database.postgresql? - context "when there is also a match with the same casing" do - let!(:other_project) { create(:project, :public, namespace: public_project.namespace, path: public_project.path.upcase) } - - it "loads the exactly matched project" do - get :show, namespace_id: public_project.namespace.path, id: public_project.path.upcase - - expect(assigns(:project)).to eq(other_project) - expect(response).to have_http_status(200) - end - end - end end end context "when the url contains .atom" do - let(:public_project_with_dot_atom) { build(:project, :public, name: 'my.atom', path: 'my.atom') } + let(:public_project_with_dot_atom) { build(:empty_project, :public, name: 'my.atom', path: 'my.atom') } it 'expects an error creating the project' do expect(public_project_with_dot_atom).not_to be_valid @@ -217,7 +205,7 @@ describe ProjectsController do context 'when the project is pending deletions' do it 'renders a 404 error' do - project = create(:project, pending_delete: true) + project = create(:empty_project, pending_delete: true) sign_in(user) get :show, namespace_id: project.namespace.path, id: project.path @@ -233,6 +221,7 @@ describe ProjectsController do let(:admin) { create(:admin) } it "sets the repository to the right path after a rename" do + project = create(:project, :repository) new_path = 'renamed_path' project_params = { path: new_path } controller.instance_variable_set(:@project, project) @@ -384,6 +373,8 @@ describe ProjectsController do end describe "GET refs" do + let(:public_project) { create(:project, :public) } + it "gets a list of branches and tags" do get :refs, namespace_id: public_project.namespace.path, id: public_project.path diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index 69124ab06bf..570d9fa43f8 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -41,7 +41,7 @@ describe UploadsController do end context "when viewing a project avatar" do - let!(:project) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } + let!(:project) { create(:empty_project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } context "when the project is public" do before do diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 19a8b1fe524..bbe9aaf737f 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -73,7 +73,7 @@ describe UsersController do end context 'forked project' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:forked_project) { Projects::ForkService.new(project, user).execute } before do @@ -91,7 +91,7 @@ describe UsersController do end describe 'GET #calendar_activities' do - let!(:project) { create(:project) } + let!(:project) { create(:empty_project) } let!(:user) { create(:user) } before do diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb index ed4acca23f1..c3b4aff55ba 100644 --- a/spec/factories/ci/runners.rb +++ b/spec/factories/ci/runners.rb @@ -16,6 +16,10 @@ FactoryGirl.define do is_shared true end + trait :specific do + is_shared false + end + trait :inactive do active false end diff --git a/spec/factories/deploy_keys_projects.rb b/spec/factories/deploy_keys_projects.rb index 27cece487bd..75f8982ecd9 100644 --- a/spec/factories/deploy_keys_projects.rb +++ b/spec/factories/deploy_keys_projects.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :deploy_keys_project do deploy_key - project + project factory: :empty_project end end diff --git a/spec/factories/events.rb b/spec/factories/events.rb index 8820d527c61..bfe41f71b57 100644 --- a/spec/factories/events.rb +++ b/spec/factories/events.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :event do - project + project factory: :empty_project author factory: :user factory :closed_issue_event do diff --git a/spec/factories/file_uploader.rb b/spec/factories/file_uploader.rb index 1b36e21f2b0..bc74aeecc3b 100644 --- a/spec/factories/file_uploader.rb +++ b/spec/factories/file_uploader.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :file_uploader do - project + project factory: :empty_project secret nil transient do diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb index ece6beb9fa9..86f51ffca99 100644 --- a/spec/factories/groups.rb +++ b/spec/factories/groups.rb @@ -1,8 +1,9 @@ FactoryGirl.define do - factory :group do + factory :group, class: Group, parent: :namespace do sequence(:name) { |n| "group#{n}" } path { name.downcase.gsub(/\s/, '_') } type 'Group' + owner nil trait :public do visibility_level Gitlab::VisibilityLevel::PUBLIC diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb index 2b4670be468..7e09f1ba8ea 100644 --- a/spec/factories/issues.rb +++ b/spec/factories/issues.rb @@ -6,7 +6,7 @@ FactoryGirl.define do factory :issue do title author - project + project factory: :empty_project trait :confidential do confidential true diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb index 3e8822faf97..5ba8443c62c 100644 --- a/spec/factories/labels.rb +++ b/spec/factories/labels.rb @@ -2,7 +2,7 @@ FactoryGirl.define do factory :label, class: ProjectLabel do sequence(:title) { |n| "label#{n}" } color "#990000" - project + project factory: :empty_project transient do priority nil diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index 37eb49c94df..22f84150bb3 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -2,7 +2,7 @@ FactoryGirl.define do factory :merge_request do title author - source_project factory: :project + association :source_project, :repository, factory: :project target_project { source_project } # $ git log --pretty=oneline feature..master diff --git a/spec/factories/milestones.rb b/spec/factories/milestones.rb index 84da71ed6dc..841ab3c73b8 100644 --- a/spec/factories/milestones.rb +++ b/spec/factories/milestones.rb @@ -1,7 +1,7 @@ FactoryGirl.define do factory :milestone do title - project + project factory: :empty_project trait :active do state "active" diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index a10ba629760..a21da7074f9 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -4,7 +4,7 @@ include ActionDispatch::TestProcess FactoryGirl.define do factory :note do - project + project factory: :empty_project note "Note" author on_issue @@ -13,12 +13,19 @@ FactoryGirl.define do factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note] factory :note_on_merge_request, traits: [:on_merge_request] factory :note_on_project_snippet, traits: [:on_project_snippet] + factory :note_on_personal_snippet, traits: [:on_personal_snippet] factory :system_note, traits: [:system] - factory :legacy_diff_note_on_commit, traits: [:on_commit, :legacy_diff_note], class: LegacyDiffNote - factory :legacy_diff_note_on_merge_request, traits: [:on_merge_request, :legacy_diff_note], class: LegacyDiffNote + factory :legacy_diff_note_on_commit, traits: [:on_commit, :legacy_diff_note], class: LegacyDiffNote do + association :project, :repository + end + + factory :legacy_diff_note_on_merge_request, traits: [:on_merge_request, :legacy_diff_note], class: LegacyDiffNote do + association :project, :repository + end factory :diff_note_on_merge_request, traits: [:on_merge_request], class: DiffNote do + association :project, :repository position do Gitlab::Diff::Position.new( old_path: "files/ruby/popen.rb", @@ -36,6 +43,7 @@ FactoryGirl.define do end factory :diff_note_on_commit, traits: [:on_commit], class: DiffNote do + association :project, :repository position do Gitlab::Diff::Position.new( old_path: "files/ruby/popen.rb", @@ -48,6 +56,7 @@ FactoryGirl.define do end trait :on_commit do + association :project, :repository noteable nil noteable_type 'Commit' noteable_id nil @@ -70,6 +79,11 @@ FactoryGirl.define do noteable { create(:project_snippet, project: project) } end + trait :on_personal_snippet do + noteable { create(:personal_snippet) } + project nil + end + trait :system do system true end diff --git a/spec/factories/project_group_links.rb b/spec/factories/project_group_links.rb index e73cc05f9d7..50341d943f5 100644 --- a/spec/factories/project_group_links.rb +++ b/spec/factories/project_group_links.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :project_group_link do - project + project factory: :empty_project group end end diff --git a/spec/factories/project_members.rb b/spec/factories/project_members.rb index c21927640d1..d62799a5a47 100644 --- a/spec/factories/project_members.rb +++ b/spec/factories/project_members.rb @@ -1,7 +1,7 @@ FactoryGirl.define do factory :project_member do user - project + project factory: :empty_project master trait(:guest) { access_level ProjectMember::GUEST } diff --git a/spec/factories/project_snippets.rb b/spec/factories/project_snippets.rb index d681a2c8483..e0fe1b36fd3 100644 --- a/spec/factories/project_snippets.rb +++ b/spec/factories/project_snippets.rb @@ -1,5 +1,5 @@ FactoryGirl.define do factory :project_snippet, parent: :snippet, class: :ProjectSnippet do - project + project factory: :empty_project end end diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb index 74497dc82c0..6a6d6fa171f 100644 --- a/spec/factories/releases.rb +++ b/spec/factories/releases.rb @@ -2,6 +2,6 @@ FactoryGirl.define do factory :release do tag "v1.1.0" description "Awesome release" - project + project factory: :empty_project end end diff --git a/spec/factories/sent_notifications.rb b/spec/factories/sent_notifications.rb index 78eb929c6e7..6287c40afe9 100644 --- a/spec/factories/sent_notifications.rb +++ b/spec/factories/sent_notifications.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :sent_notification do - project + project factory: :empty_project recipient factory: :user noteable factory: :issue reply_key "0123456789abcdef" * 2 diff --git a/spec/factories/services.rb b/spec/factories/services.rb index 9de78d68280..a14a46c803e 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -1,5 +1,5 @@ FactoryGirl.define do factory :service do - project + project factory: :empty_project end end diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb index 85a8c263643..91d6f39a5bf 100644 --- a/spec/factories/todos.rb +++ b/spec/factories/todos.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :todo do - project + project factory: :empty_project author user target factory: :issue diff --git a/spec/factories/trending_project.rb b/spec/factories/trending_project.rb new file mode 100644 index 00000000000..246176611dc --- /dev/null +++ b/spec/factories/trending_project.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + # TrendingProject + factory :trending_project, class: 'TrendingProject' do + project + end +end diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb index 66044b44495..e8e080ce3e2 100644 --- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb +++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb @@ -1,10 +1,13 @@ require 'rails_helper' feature 'Admin disables Git access protocol', feature: true do + include StubENV + let(:project) { create(:empty_project, :empty_repo) } let(:admin) { create(:admin) } background do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') login_as(admin) end diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb index dec2dedf2b5..f7e49a56deb 100644 --- a/spec/features/admin/admin_health_check_spec.rb +++ b/spec/features/admin/admin_health_check_spec.rb @@ -1,9 +1,11 @@ require 'spec_helper' feature "Admin Health Check", feature: true do + include StubENV include WaitForAjax before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') login_as :admin end @@ -12,11 +14,12 @@ feature "Admin Health Check", feature: true do visit admin_health_check_path end - it { page.has_text? 'Health Check' } - it { page.has_text? 'Health information can be retrieved' } - it 'has a health check access token' do + page.has_text? 'Health Check' + page.has_text? 'Health information can be retrieved' + token = current_application_settings.health_check_access_token + expect(page).to have_content("Access token is #{token}") expect(page).to have_selector('#health-check-token', text: token) end diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index d92c66b689d..f05fbe3d062 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -1,7 +1,10 @@ require 'spec_helper' describe "Admin Runners" do + include StubENV + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') login_as :admin end diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 47fa2f14307..de42ab81fac 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -1,7 +1,10 @@ require 'spec_helper' feature 'Admin updates settings', feature: true do - before(:each) do + include StubENV + + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') login_as :admin visit admin_application_settings_path end diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb index 661fb761809..855247de2ea 100644 --- a/spec/features/admin/admin_uses_repository_checks_spec.rb +++ b/spec/features/admin/admin_uses_repository_checks_spec.rb @@ -1,7 +1,12 @@ require 'rails_helper' feature 'Admin uses repository checks', feature: true do - before { login_as :admin } + include StubENV + + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') + login_as :admin + end scenario 'to trigger a single check' do project = create(:empty_project) @@ -29,7 +34,7 @@ feature 'Admin uses repository checks', feature: true do scenario 'to clear all repository checks', js: true do visit admin_application_settings_path - + expect(RepositoryCheck::ClearWorker).to receive(:perform_async) click_link 'Clear all repository checks' diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb new file mode 100644 index 00000000000..f3a5b565122 --- /dev/null +++ b/spec/features/copy_as_gfm_spec.rb @@ -0,0 +1,432 @@ +require 'spec_helper' + +describe 'Copy as GFM', feature: true, js: true do + include GitlabMarkdownHelper + include ActionView::Helpers::JavaScriptHelper + + before do + @feat = MarkdownFeature.new + + # `markdown` helper expects a `@project` variable + @project = @feat.project + + visit namespace_project_issue_path(@project.namespace, @project, @feat.issue) + end + + # The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert GitLab Flavored Markdown (GFM) to HTML. + # The handlers defined in app/assets/javascripts/copy_as_gfm.js.es6 consequently convert that same HTML to GFM. + # To make sure these filters and handlers are properly aligned, this spec tests the GFM-to-HTML-to-GFM cycle + # by verifying (`html_to_gfm(gfm_to_html(gfm)) == gfm`) for a number of examples of GFM for every filter, using the `verify` helper. + + # These are all in a single `it` for performance reasons. + it 'works', :aggregate_failures do + verify( + 'nesting', + + '> 1. [x] **[$`2 + 2`$ {-=-}{+=+} 2^2 ~~:thumbsup:~~](http://google.com)**' + ) + + verify( + 'a real world example from the gitlab-ce README', + + <<-GFM.strip_heredoc + # GitLab + + [![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) + [![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) + [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) + [![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) + + ## Canonical source + + The canonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/). + + ## Open source software to collaborate on code + + To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/). + + + - Manage Git repositories with fine grained access controls that keep your code secure + + - Perform code reviews and enhance collaboration with merge requests + + - Complete continuous integration (CI) and CD pipelines to builds, test, and deploy your applications + + - Each project can also have an issue tracker, issue board, and a wiki + + - Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises + + - Completely free and open source (MIT Expat license) + GFM + ) + + verify( + 'InlineDiffFilter', + + '{-Deleted text-}', + '{+Added text+}' + ) + + verify( + 'TaskListFilter', + + '- [ ] Unchecked task', + '- [x] Checked task', + '1. [ ] Unchecked numbered task', + '1. [x] Checked numbered task' + ) + + verify( + 'ReferenceFilter', + + # issue reference + @feat.issue.to_reference, + # full issue reference + @feat.issue.to_reference(full: true), + # issue URL + namespace_project_issue_url(@project.namespace, @project, @feat.issue), + # issue URL with note anchor + namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123'), + # issue link + "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue)})", + # issue link with note anchor + "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123')})", + ) + + verify( + 'AutolinkFilter', + + 'https://example.com' + ) + + verify( + 'TableOfContentsFilter', + + '[[_TOC_]]' + ) + + verify( + 'EmojiFilter', + + ':thumbsup:' + ) + + verify( + 'ImageLinkFilter', + + '![Image](https://example.com/image.png)' + ) + + verify( + 'VideoLinkFilter', + + '![Video](https://example.com/video.mp4)' + ) + + verify( + 'MathFilter: math as converted from GFM to HTML', + + '$`c = \pm\sqrt{a^2 + b^2}`$', + + # math block + <<-GFM.strip_heredoc + ```math + c = \pm\sqrt{a^2 + b^2} + ``` + GFM + ) + + aggregate_failures('MathFilter: math as transformed from HTML to KaTeX') do + gfm = '$`c = \pm\sqrt{a^2 + b^2}`$' + + html = <<-HTML.strip_heredoc + <span class="katex"> + <span class="katex-mathml"> + <math> + <semantics> + <mrow> + <mi>c</mi> + <mo>=</mo> + <mo>±</mo> + <msqrt> + <mrow> + <msup> + <mi>a</mi> + <mn>2</mn> + </msup> + <mo>+</mo> + <msup> + <mi>b</mi> + <mn>2</mn> + </msup> + </mrow> + </msqrt> + </mrow> + <annotation encoding="application/x-tex">c = \\pm\\sqrt{a^2 + b^2}</annotation> + </semantics> + </math> + </span> + <span class="katex-html" aria-hidden="true"> + <span class="strut" style="height: 0.913389em;"></span> + <span class="strut bottom" style="height: 1.04em; vertical-align: -0.126611em;"></span> + <span class="base textstyle uncramped"> + <span class="mord mathit">c</span> + <span class="mrel">=</span> + <span class="mord">±</span> + <span class="sqrt mord"><span class="sqrt-sign" style="top: -0.073389em;"> + <span class="style-wrap reset-textstyle textstyle uncramped">√</span> + </span> + <span class="vlist"> + <span class="" style="top: 0em;"> + <span class="fontsize-ensurer reset-size5 size5"> + <span class="" style="font-size: 1em;"></span> + </span> + <span class="mord textstyle cramped"> + <span class="mord"> + <span class="mord mathit">a</span> + <span class="msupsub"> + <span class="vlist"> + <span class="" style="top: -0.289em; margin-right: 0.05em;"> + <span class="fontsize-ensurer reset-size5 size5"> + <span class="" style="font-size: 0em;"></span> + </span> + <span class="reset-textstyle scriptstyle cramped"> + <span class="mord mathrm">2</span> + </span> + </span> + <span class="baseline-fix"> + <span class="fontsize-ensurer reset-size5 size5"> + <span class="" style="font-size: 0em;"></span> + </span> + </span> + </span> + </span> + </span> + <span class="mbin">+</span> + <span class="mord"> + <span class="mord mathit">b</span> + <span class="msupsub"> + <span class="vlist"> + <span class="" style="top: -0.289em; margin-right: 0.05em;"> + <span class="fontsize-ensurer reset-size5 size5"> + <span class="" style="font-size: 0em;"></span> + </span> + <span class="reset-textstyle scriptstyle cramped"> + <span class="mord mathrm">2</span> + </span> + </span> + <span class="baseline-fix"> + <span class="fontsize-ensurer reset-size5 size5"> + <span class="" style="font-size: 0em;"></span> + </span> + </span> + </span> + </span> + </span> + </span> + </span> + <span class="" style="top: -0.833389em;"> + <span class="fontsize-ensurer reset-size5 size5"> + <span class="" style="font-size: 1em;"></span> + </span> + <span class="reset-textstyle textstyle uncramped sqrt-line"></span> + </span> + <span class="baseline-fix"> + <span class="fontsize-ensurer reset-size5 size5"> + <span class="" style="font-size: 1em;"></span> + </span> + </span> + </span> + </span> + </span> + </span> + </span> + HTML + + output_gfm = html_to_gfm(html) + expect(output_gfm.strip).to eq(gfm.strip) + end + + verify( + 'SanitizationFilter', + + <<-GFM.strip_heredoc + <sub>sub</sub> + + <dl> + <dt>dt</dt> + <dd>dd</dd> + </dl> + + <kbd>kbd</kbd> + + <q>q</q> + + <samp>samp</samp> + + <var>var</var> + + <ruby>ruby</ruby> + + <rt>rt</rt> + + <rp>rp</rp> + + <abbr>abbr</abbr> + GFM + ) + + verify( + 'SanitizationFilter', + + <<-GFM.strip_heredoc, + ``` + Plain text + ``` + GFM + + <<-GFM.strip_heredoc, + ```ruby + def foo + bar + end + ``` + GFM + + <<-GFM.strip_heredoc + Foo + + This is an example of GFM + + ```js + Code goes here + ``` + GFM + ) + + verify( + 'MarkdownFilter', + + "Line with two spaces at the end \nto insert a linebreak", + + '`code`', + '`` code with ` ticks ``', + + '> Quote', + + # multiline quote + <<-GFM.strip_heredoc, + > Multiline + > Quote + > + > With multiple paragraphs + GFM + + '![Image](https://example.com/image.png)', + + '# Heading with no anchor link', + + '[Link](https://example.com)', + + '- List item', + + # multiline list item + <<-GFM.strip_heredoc, + - Multiline + List item + GFM + + # nested lists + <<-GFM.strip_heredoc, + - Nested + + + - Lists + GFM + + # list with blockquote + <<-GFM.strip_heredoc, + - List + + > Blockquote + GFM + + '1. Numbered list item', + + # multiline numbered list item + <<-GFM.strip_heredoc, + 1. Multiline + Numbered list item + GFM + + # nested numbered list + <<-GFM.strip_heredoc, + 1. Nested + + + 1. Numbered lists + GFM + + '# Heading', + '## Heading', + '### Heading', + '#### Heading', + '##### Heading', + '###### Heading', + + '**Bold**', + + '_Italics_', + + '~~Strikethrough~~', + + '2^2', + + '-----', + + # table + <<-GFM.strip_heredoc, + | Centered | Right | Left | + |:--------:|------:|------| + | Foo | Bar | **Baz** | + | Foo | Bar | **Baz** | + GFM + + # table with empty heading + <<-GFM.strip_heredoc, + | | x | y | + |---|---|---| + | a | 1 | 0 | + | b | 0 | 1 | + GFM + ) + end + + alias_method :gfm_to_html, :markdown + + def html_to_gfm(html) + js = <<-JS.strip_heredoc + (function(html) { + var node = document.createElement('div'); + node.innerHTML = html; + return window.gl.CopyAsGFM.nodeToGFM(node); + })("#{escape_javascript(html)}") + JS + page.evaluate_script(js) + end + + def verify(label, *gfms) + aggregate_failures(label) do + gfms.each do |gfm| + html = gfm_to_html(gfm) + output_gfm = html_to_gfm(html) + expect(output_gfm.strip).to eq(gfm.strip) + end + end + end + + # Fake a `current_user` helper + def current_user + @feat.user + end +end diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb index 8a155c3bfc5..93763f092fb 100644 --- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb @@ -43,14 +43,6 @@ describe 'Dropdown assignee', js: true, feature: true do expect(page).to have_css(js_dropdown_assignee, visible: true) end - it 'shows assigned to me link' do - filtered_search.set('assignee:') - - page.within js_dropdown_assignee do - expect(page).to have_content('Assigned to me') - end - end - it 'closes when the search bar is unfocused' do find('body').click() @@ -129,14 +121,6 @@ describe 'Dropdown assignee', js: true, feature: true do filtered_search.set('assignee:') end - it 'filters by current user' do - page.within js_dropdown_assignee do - click_button 'Assigned to me' - end - - expect(filtered_search.value).to eq("assignee:#{user.to_reference} ") - end - it 'fills in the assignee username when the assignee has not been filtered' do click_assignee(user_jacob.name) @@ -185,4 +169,22 @@ describe 'Dropdown assignee', js: true, feature: true do expect(page).to have_css(js_dropdown_assignee, visible: true) end end + + describe 'caching requests' do + it 'caches requests after the first load' do + filtered_search.set('assignee') + send_keys_to_filtered_search(':') + initial_size = dropdown_assignee_size + + expect(initial_size).to be > 0 + + new_user = create(:user) + project.team << [new_user, :master] + find('.filtered-search-input-container .clear-search').click + filtered_search.set('assignee') + send_keys_to_filtered_search(':') + + expect(dropdown_assignee_size).to eq(initial_size) + end + end end diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb index a5d5d9d4c5e..59e302f0e2d 100644 --- a/spec/features/issues/filtered_search/dropdown_author_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb @@ -157,4 +157,22 @@ describe 'Dropdown author', js: true, feature: true do expect(page).to have_css(js_dropdown_author, visible: true) end end + + describe 'caching requests' do + it 'caches requests after the first load' do + filtered_search.set('author') + send_keys_to_filtered_search(':') + initial_size = dropdown_author_size + + expect(initial_size).to be > 0 + + new_user = create(:user) + project.team << [new_user, :master] + find('.filtered-search-input-container .clear-search').click + filtered_search.set('author') + send_keys_to_filtered_search(':') + + expect(dropdown_author_size).to eq(initial_size) + end + end end diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb index bea00160f96..5079eb8dd00 100644 --- a/spec/features/issues/filtered_search/dropdown_label_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb @@ -40,6 +40,16 @@ describe 'Dropdown label', js: true, feature: true do visit namespace_project_issues_path(project.namespace, project) end + describe 'keyboard navigation' do + it 'selects label' do + send_keys_to_filtered_search('label:') + + filtered_search.native.send_keys(:down, :down, :enter) + + expect(filtered_search.value).to eq("label:~#{special_label.name} ") + end + end + describe 'behavior' do it 'opens when the search bar has label:' do filtered_search.set('label:') @@ -239,4 +249,21 @@ describe 'Dropdown label', js: true, feature: true do expect(page).to have_css(js_dropdown_label, visible: true) end end + + describe 'caching requests' do + it 'caches requests after the first load' do + filtered_search.set('label') + send_keys_to_filtered_search(':') + initial_size = dropdown_label_size + + expect(initial_size).to be > 0 + + create(:label, project: project) + find('.filtered-search-input-container .clear-search').click + filtered_search.set('label') + send_keys_to_filtered_search(':') + + expect(dropdown_label_size).to eq(initial_size) + end + end end diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb index 134e58ad586..0ce16715b86 100644 --- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb @@ -219,4 +219,21 @@ describe 'Dropdown milestone', js: true, feature: true do expect(page).to have_css(js_dropdown_milestone, visible: true) end end + + describe 'caching requests' do + it 'caches requests after the first load' do + filtered_search.set('milestone') + send_keys_to_filtered_search(':') + initial_size = dropdown_milestone_size + + expect(initial_size).to be > 0 + + create(:milestone, project: project) + find('.filtered-search-input-container .clear-search').click + filtered_search.set('milestone') + send_keys_to_filtered_search(':') + + expect(dropdown_milestone_size).to eq(initial_size) + end + end end diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb index 56b1d354eb0..90eb60eb337 100644 --- a/spec/features/issues/filtered_search/search_bar_spec.rb +++ b/spec/features/issues/filtered_search/search_bar_spec.rb @@ -20,6 +20,22 @@ describe 'Search bar', js: true, feature: true do left_style.to_s.gsub('left: ', '').to_f end + describe 'keyboard navigation' do + it 'makes item active' do + filtered_search.native.send_keys(:down) + + page.within '#js-dropdown-hint' do + expect(page).to have_selector('.dropdown-active') + end + end + + it 'selects item' do + filtered_search.native.send_keys(:down, :down, :enter) + + expect(filtered_search.value).to eq('author:') + end + end + describe 'clear search button' do it 'clears text' do search_text = 'search_text' diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb index 8771cc8e157..741ca95f1ca 100644 --- a/spec/features/issues/form_spec.rb +++ b/spec/features/issues/form_spec.rb @@ -68,6 +68,22 @@ describe 'New/edit issue', feature: true, js: true do end end end + + it 'correctly updates the dropdown toggle when removing a label' do + click_button 'Labels' + + page.within '.dropdown-menu-labels' do + click_link label.title + end + + expect(find('.js-label-select')).to have_content(label.title) + + page.within '.dropdown-menu-labels' do + click_link label.title + end + + expect(find('.js-label-select')).to have_content('Labels') + end end context 'edit issue' do diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb new file mode 100644 index 00000000000..b6728960fb8 --- /dev/null +++ b/spec/features/projects/merge_request_button_spec.rb @@ -0,0 +1,108 @@ +require 'spec_helper' + +feature 'Merge Request button', feature: true do + shared_examples 'Merge Request button only shown when allowed' do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:forked_project) { create(:project, :public, forked_from_project: project) } + + context 'not logged in' do + it 'does not show Create Merge Request button' do + visit url + + within("#content-body") do + expect(page).not_to have_link(label) + end + end + end + + context 'logged in as developer' do + before do + login_as(user) + project.team << [user, :developer] + end + + it 'shows Create Merge Request button' do + href = new_namespace_project_merge_request_path(project.namespace, + project, + merge_request: { source_branch: 'feature', + target_branch: 'master' }) + + visit url + + within("#content-body") do + expect(page).to have_link(label, href: href) + end + end + + context 'merge requests are disabled' do + before do + project.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED) + end + + it 'does not show Create Merge Request button' do + visit url + + within("#content-body") do + expect(page).not_to have_link(label) + end + end + end + end + + context 'logged in as non-member' do + before do + login_as(user) + end + + it 'does not show Create Merge Request button' do + visit url + + within("#content-body") do + expect(page).not_to have_link(label) + end + end + + context 'on own fork of project' do + let(:user) { forked_project.owner } + + it 'shows Create Merge Request button' do + href = new_namespace_project_merge_request_path(forked_project.namespace, + forked_project, + merge_request: { source_branch: 'feature', + target_branch: 'master' }) + + visit fork_url + + within("#content-body") do + expect(page).to have_link(label, href: href) + end + end + end + end + end + + context 'on branches page' do + it_behaves_like 'Merge Request button only shown when allowed' do + let(:label) { 'Merge Request' } + let(:url) { namespace_project_branches_path(project.namespace, project) } + let(:fork_url) { namespace_project_branches_path(forked_project.namespace, forked_project) } + end + end + + context 'on compare page' do + it_behaves_like 'Merge Request button only shown when allowed' do + let(:label) { 'Create Merge Request' } + let(:url) { namespace_project_compare_path(project.namespace, project, from: 'master', to: 'feature') } + let(:fork_url) { namespace_project_compare_path(forked_project.namespace, forked_project, from: 'master', to: 'feature') } + end + end + + context 'on commits page' do + it_behaves_like 'Merge Request button only shown when allowed' do + let(:label) { 'Create Merge Request' } + let(:url) { namespace_project_commits_path(project.namespace, project, 'feature') } + let(:fork_url) { namespace_project_commits_path(forked_project.namespace, forked_project, 'feature') } + end + end +end diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb index abb27c90e0a..a5d14aa19f1 100644 --- a/spec/features/task_lists_spec.rb +++ b/spec/features/task_lists_spec.rb @@ -36,6 +36,19 @@ feature 'Task Lists', feature: true do MARKDOWN end + let(:nested_tasks_markdown) do + <<-EOT.strip_heredoc + - [ ] Task a + - [x] Task a.1 + - [ ] Task a.2 + - [ ] Task b + + 1. [ ] Task 1 + 1. [ ] Task 1.1 + 1. [x] Task 1.2 + EOT + end + before do Warden.test_mode! @@ -123,6 +136,35 @@ feature 'Task Lists', feature: true do expect(page).to have_content("1 of 1 task completed") end end + + describe 'nested tasks', js: true do + let(:issue) { create(:issue, description: nested_tasks_markdown, author: user, project: project) } + + before { visit_issue(project, issue) } + + it 'renders' do + expect(page).to have_selector('ul.task-list', count: 2) + expect(page).to have_selector('li.task-list-item', count: 7) + expect(page).to have_selector('ul input[checked]', count: 1) + expect(page).to have_selector('ol input[checked]', count: 1) + end + + it 'solves tasks' do + expect(page).to have_content("2 of 7 tasks completed") + + page.find('li.task-list-item', text: 'Task b').find('input').click + page.find('li.task-list-item ul li.task-list-item', text: 'Task a.2').find('input').click + page.find('li.task-list-item ol li.task-list-item', text: 'Task 1.1').find('input').click + + expect(page).to have_content("5 of 7 tasks completed") + + visit_issue(project, issue) # reload to see new system notes + + expect(page).to have_content('marked the task Task b as complete') + expect(page).to have_content('marked the task Task a.2 as complete') + expect(page).to have_content('marked the task Task 1.1 as complete') + end + end end describe 'for Notes' do @@ -236,7 +278,7 @@ feature 'Task Lists', feature: true do expect(page).to have_content("2 of 6 tasks completed") end end - + describe 'single incomplete task' do let!(:merge) { create(:merge_request, :simple, description: singleIncompleteMarkdown, author: user, source_project: project) } diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb index db60c01db0d..91f34973ba5 100644 --- a/spec/finders/branches_finder_spec.rb +++ b/spec/finders/branches_finder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe BranchesFinder do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } describe '#execute' do diff --git a/spec/finders/contributed_projects_finder_spec.rb b/spec/finders/contributed_projects_finder_spec.rb index 65d7f14c721..ad2d456529a 100644 --- a/spec/finders/contributed_projects_finder_spec.rb +++ b/spec/finders/contributed_projects_finder_spec.rb @@ -6,8 +6,8 @@ describe ContributedProjectsFinder do let(:finder) { described_class.new(source_user) } - let!(:public_project) { create(:project, :public) } - let!(:private_project) { create(:project, :private) } + let!(:public_project) { create(:empty_project, :public) } + let!(:private_project) { create(:empty_project, :private) } before do private_project.team << [source_user, Gitlab::Access::MASTER] diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb index 00eec3f3f4c..ef97b061ca7 100644 --- a/spec/finders/group_projects_finder_spec.rb +++ b/spec/finders/group_projects_finder_spec.rb @@ -6,11 +6,11 @@ describe GroupProjectsFinder do let(:finder) { described_class.new(source_user) } - let!(:public_project) { create(:project, :public, group: group, path: '1') } - let!(:private_project) { create(:project, :private, group: group, path: '2') } - let!(:shared_project_1) { create(:project, :public, path: '3') } - let!(:shared_project_2) { create(:project, :private, path: '4') } - let!(:shared_project_3) { create(:project, :internal, path: '5') } + let!(:public_project) { create(:empty_project, :public, group: group, path: '1') } + let!(:private_project) { create(:empty_project, :private, group: group, path: '2') } + let!(:shared_project_1) { create(:empty_project, :public, path: '3') } + let!(:shared_project_2) { create(:empty_project, :private, path: '4') } + let!(:shared_project_3) { create(:empty_project, :internal, path: '5') } before do shared_project_1.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group) diff --git a/spec/finders/joined_groups_finder_spec.rb b/spec/finders/joined_groups_finder_spec.rb index 29a47e005a6..4c389746252 100644 --- a/spec/finders/joined_groups_finder_spec.rb +++ b/spec/finders/joined_groups_finder_spec.rb @@ -42,7 +42,7 @@ describe JoinedGroupsFinder do context 'if profile visitor is in one of the private group projects' do before do - project = create(:project, :private, group: private_group, name: 'B', path: 'B') + project = create(:empty_project, :private, group: private_group, name: 'B', path: 'B') project.add_user(profile_visitor, Gitlab::Access::DEVELOPER) end diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index e4ba1d2f1c2..3dcd7781e5b 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -4,9 +4,9 @@ describe MergeRequestsFinder do let(:user) { create :user } let(:user2) { create :user } - let(:project1) { create(:project) } - let(:project2) { create(:project, forked_from_project: project1) } - let(:project3) { create(:project, :archived, forked_from_project: project1) } + let(:project1) { create(:empty_project) } + let(:project2) { create(:empty_project, forked_from_project: project1) } + let(:project3) { create(:empty_project, :archived, forked_from_project: project1) } let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) } let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1, state: 'closed') } diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb index 4d21254323c..bac653ea451 100644 --- a/spec/finders/notes_finder_spec.rb +++ b/spec/finders/notes_finder_spec.rb @@ -28,7 +28,7 @@ describe NotesFinder do end it "excludes notes on commits the author can't download" do - project = create(:project, :private) + project = create(:project, :private, :repository) note = create(:note_on_commit, project: project) params = { target_type: 'commit', target_id: note.noteable.id } @@ -76,7 +76,7 @@ describe NotesFinder do end context 'for target' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:note1) { create :note_on_commit, project: project } let(:note2) { create :note_on_commit, project: project } let(:commit) { note1.noteable } diff --git a/spec/finders/personal_projects_finder_spec.rb b/spec/finders/personal_projects_finder_spec.rb index a4681fe59d8..e0e17af681a 100644 --- a/spec/finders/personal_projects_finder_spec.rb +++ b/spec/finders/personal_projects_finder_spec.rb @@ -4,14 +4,14 @@ describe PersonalProjectsFinder do let(:source_user) { create(:user) } let(:current_user) { create(:user) } let(:finder) { described_class.new(source_user) } - let!(:public_project) { create(:project, :public, namespace: source_user.namespace) } + let!(:public_project) { create(:empty_project, :public, namespace: source_user.namespace) } let!(:private_project) do - create(:project, :private, namespace: source_user.namespace, path: 'mepmep') + create(:empty_project, :private, namespace: source_user.namespace, path: 'mepmep') end let!(:internal_project) do - create(:project, :internal, namespace: source_user.namespace, path: 'C') + create(:empty_project, :internal, namespace: source_user.namespace, path: 'C') end before do diff --git a/spec/finders/pipelines_finder_spec.rb b/spec/finders/pipelines_finder_spec.rb index b0811d134fa..fdc8215aa47 100644 --- a/spec/finders/pipelines_finder_spec.rb +++ b/spec/finders/pipelines_finder_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe PipelinesFinder do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let!(:tag_pipeline) { create(:ci_pipeline, project: project, ref: 'v1.0.0') } let!(:branch_pipeline) { create(:ci_pipeline, project: project) } diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb index 13bda5f7c5a..e44e7434c80 100644 --- a/spec/finders/projects_finder_spec.rb +++ b/spec/finders/projects_finder_spec.rb @@ -6,19 +6,19 @@ describe ProjectsFinder do let(:group) { create(:group, :public) } let!(:private_project) do - create(:project, :private, name: 'A', path: 'A') + create(:empty_project, :private, name: 'A', path: 'A') end let!(:internal_project) do - create(:project, :internal, group: group, name: 'B', path: 'B') + create(:empty_project, :internal, group: group, name: 'B', path: 'B') end let!(:public_project) do - create(:project, :public, group: group, name: 'C', path: 'C') + create(:empty_project, :public, group: group, name: 'C', path: 'C') end let!(:shared_project) do - create(:project, :private, name: 'D', path: 'D') + create(:empty_project, :private, name: 'D', path: 'D') end let(:finder) { described_class.new } diff --git a/spec/finders/tags_finder_spec.rb b/spec/finders/tags_finder_spec.rb index 98b42e264dc..460e278e2d3 100644 --- a/spec/finders/tags_finder_spec.rb +++ b/spec/finders/tags_finder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe TagsFinder do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } describe '#execute' do diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 92053e5a7c6..8b201f348f1 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -55,7 +55,7 @@ describe ApplicationHelper do describe 'project_icon' do it 'returns an url for the avatar' do - project = create(:project, avatar: File.open(uploaded_image_temp_path)) + project = create(:empty_project, avatar: File.open(uploaded_image_temp_path)) avatar_url = "http://#{Gitlab.config.gitlab.host}/uploads/project/avatar/#{project.id}/banana_sample.gif" expect(helper.project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s). @@ -63,7 +63,7 @@ describe ApplicationHelper do end it 'gives uploaded icon when present' do - project = create(:project) + project = create(:empty_project) allow_any_instance_of(Project).to receive(:avatar_in_git).and_return(true) diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index a43a7238c70..fa516f9903e 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -70,7 +70,7 @@ describe BlobHelper do describe "#edit_blob_link" do let(:namespace) { create(:namespace, name: 'gitlab' )} - let(:project) { create(:project, namespace: namespace) } + let(:project) { create(:project, :repository, namespace: namespace) } before do allow(self).to receive(:current_user).and_return(double) diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index 810311e2b1a..b8ec3521edb 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe GitlabMarkdownHelper do include ApplicationHelper - let!(:project) { create(:project) } + let!(:project) { create(:project, :repository) } let(:user) { create(:user, username: 'gfm') } let(:commit) { project.commit } @@ -55,18 +55,18 @@ describe GitlabMarkdownHelper do end describe '#link_to_gfm' do - let(:commit_path) { namespace_project_commit_path(project.namespace, project, commit) } - let(:issues) { create_list(:issue, 2, project: project) } + let(:link) { '/commits/0a1b2c3d' } + let(:issues) { create_list(:issue, 2, project: project) } it 'handles references nested in links with all the text' do - actual = helper.link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", commit_path) + actual = helper.link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", link) doc = Nokogiri::HTML.parse(actual) # Make sure we didn't create invalid markup expect(doc.errors).to be_empty # Leading commit link - expect(doc.css('a')[0].attr('href')).to eq commit_path + expect(doc.css('a')[0].attr('href')).to eq link expect(doc.css('a')[0].text).to eq 'This should finally fix ' # First issue link @@ -75,7 +75,7 @@ describe GitlabMarkdownHelper do expect(doc.css('a')[1].text).to eq issues[0].to_reference # Internal commit link - expect(doc.css('a')[2].attr('href')).to eq commit_path + expect(doc.css('a')[2].attr('href')).to eq link expect(doc.css('a')[2].text).to eq ' and ' # Second issue link @@ -84,12 +84,12 @@ describe GitlabMarkdownHelper do expect(doc.css('a')[3].text).to eq issues[1].to_reference # Trailing commit link - expect(doc.css('a')[4].attr('href')).to eq commit_path + expect(doc.css('a')[4].attr('href')).to eq link expect(doc.css('a')[4].text).to eq ' for real' end it 'forwards HTML options' do - actual = helper.link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo') + actual = helper.link_to_gfm("Fixed in #{commit.id}", link, class: 'foo') doc = Nokogiri::HTML.parse(actual) expect(doc.css('a')).to satisfy do |v| @@ -100,7 +100,7 @@ describe GitlabMarkdownHelper do it "escapes HTML passed in as the body" do actual = "This is a <h1>test</h1> - see #{issues[0].to_reference}" - expect(helper.link_to_gfm(actual, commit_path)). + expect(helper.link_to_gfm(actual, link)). to match('<h1>test</h1>') end diff --git a/spec/helpers/graph_helper_spec.rb b/spec/helpers/graph_helper_spec.rb index 51c49f0e587..400635abdde 100644 --- a/spec/helpers/graph_helper_spec.rb +++ b/spec/helpers/graph_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe GraphHelper do describe '#get_refs' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:commit) { project.commit("master") } let(:graph) { Network::Graph.new(project, 'master', commit, '') } diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 9c7e0ee2fe0..13fb9c1f1a7 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe IssuesHelper do - let(:project) { create :project } + let(:project) { create(:empty_project) } let(:issue) { create :issue, project: project } let(:ext_project) { create :redmine_project } diff --git a/spec/helpers/members_helper_spec.rb b/spec/helpers/members_helper_spec.rb index 33934cdf8b1..2b455571d52 100644 --- a/spec/helpers/members_helper_spec.rb +++ b/spec/helpers/members_helper_spec.rb @@ -46,7 +46,7 @@ describe MembersHelper do end describe '#leave_confirmation_message' do - let(:project) { build_stubbed(:project) } + let(:project) { build_stubbed(:empty_project) } let(:group) { build_stubbed(:group) } let(:user) { build_stubbed(:user) } diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb index 1f221487393..550b4a66a6a 100644 --- a/spec/helpers/merge_requests_helper_spec.rb +++ b/spec/helpers/merge_requests_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe MergeRequestsHelper do describe 'ci_build_details_path' do - let(:project) { create :project } + let(:project) { create(:empty_project) } let(:merge_request) { MergeRequest.new } let(:ci_service) { CiService.new } let(:last_commit) { Ci::Pipeline.new({}) } @@ -30,7 +30,7 @@ describe MergeRequestsHelper do it { is_expected.to eq('#1, #2, and #3') } context 'for JIRA issues' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:issues) do [ ExternalIssue.new('JIRA-123', project), @@ -52,8 +52,8 @@ describe MergeRequestsHelper do end describe 'within different projects' do - let(:project) { create(:project) } - let(:fork_project) { create(:project, forked_from_project: project) } + let(:project) { create(:empty_project) } + let(:fork_project) { create(:empty_project, forked_from_project: project) } let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: project) } subject { format_mr_branch_names(merge_request) } let(:source_title) { "#{fork_project.path_with_namespace}:#{merge_request.source_branch}" } @@ -64,8 +64,8 @@ describe MergeRequestsHelper do end describe 'mr_widget_refresh_url' do + let(:project) { create(:empty_project) } let(:merge_request) { create(:merge_request, source_project: project) } - let(:project) { create(:project) } it 'returns correct url for MR' do expected_url = "#{project.path_with_namespace}/merge_requests/#{merge_request.iid}/merge_widget_refresh" diff --git a/spec/helpers/milestones_helper_spec.rb b/spec/helpers/milestones_helper_spec.rb index ea744dbb629..14a95479339 100644 --- a/spec/helpers/milestones_helper_spec.rb +++ b/spec/helpers/milestones_helper_spec.rb @@ -21,24 +21,22 @@ describe MilestonesHelper do end describe '#milestone_counts' do - let(:project) { FactoryGirl.create(:project) } + let(:project) { create(:empty_project) } let(:counts) { helper.milestone_counts(project.milestones) } context 'when there are milestones' do - let!(:milestone_1) { FactoryGirl.create(:active_milestone, project: project) } - let!(:milestone_2) { FactoryGirl.create(:active_milestone, project: project) } - let!(:milestone_3) { FactoryGirl.create(:closed_milestone, project: project) } - it 'returns the correct counts' do + create_list(:active_milestone, 2, project: project) + create(:closed_milestone, project: project) + expect(counts).to eq(opened: 2, closed: 1, all: 3) end end context 'when there are only milestones of one type' do - let!(:milestone_1) { FactoryGirl.create(:active_milestone, project: project) } - let!(:milestone_2) { FactoryGirl.create(:active_milestone, project: project) } - it 'returns the correct counts' do + create_list(:active_milestone, 2, project: project) + expect(counts).to eq(opened: 2, closed: 0, all: 2) end end diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb index 77841e85223..1f02e06e312 100644 --- a/spec/helpers/preferences_helper_spec.rb +++ b/spec/helpers/preferences_helper_spec.rb @@ -110,7 +110,7 @@ describe PreferencesHelper do end context 'when repository is not empty' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } it 'returns readme if user has repository access' do allow(helper).to receive(:can?).with(nil, :download_code, project).and_return(true) diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 8113742923b..8d1570aa6f3 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -10,7 +10,7 @@ describe ProjectsHelper do end describe "can_change_visibility_level?" do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:project_member, :reporter, user: create(:user), project: project).user } let(:fork_project) { Projects::ForkService.new(project, user).execute } @@ -97,7 +97,7 @@ describe ProjectsHelper do end describe '#license_short_name' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'when project.repository has a license_key' do it 'returns the nickname of the license if present' do diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index 4b2ca3514f8..e51720f10ed 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -42,7 +42,7 @@ describe SearchHelper do end it "includes the user's projects" do - project = create(:project, namespace: create(:namespace, owner: user)) + project = create(:empty_project, namespace: create(:namespace, owner: user)) expect(search_autocomplete_opts(project.name).size).to eq(1) end @@ -52,7 +52,9 @@ describe SearchHelper do end context "with a current project" do - before { @project = create(:project) } + before do + @project = create(:project, :repository) + end it "includes project-specific sections" do expect(search_autocomplete_opts("Files").size).to eq(1) diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb index 37ac6a2699d..4da1569e59f 100644 --- a/spec/helpers/submodule_helper_spec.rb +++ b/spec/helpers/submodule_helper_spec.rb @@ -116,7 +116,7 @@ describe SubmoduleHelper do context 'submodules with relative links' do let(:group) { create(:group, name: "Master Project", path: "master-project") } - let(:project) { create(:project, group: group) } + let(:project) { create(:empty_project, group: group) } let(:commit_id) { sample_commit[:id] } before do @@ -145,7 +145,7 @@ describe SubmoduleHelper do context 'personal project' do let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { create(:empty_project, namespace: user.namespace) } it 'one level down with personal project' do result = relative_self_links('../test.git', commit_id) diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb index 8d6537ba4b5..9523d0f4aa6 100644 --- a/spec/helpers/tree_helper_spec.rb +++ b/spec/helpers/tree_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe TreeHelper do describe 'flatten_tree' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do @repository = project.repository diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb index db3ad1b99e9..8942b00b128 100644 --- a/spec/helpers/visibility_level_helper_spec.rb +++ b/spec/helpers/visibility_level_helper_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe VisibilityLevelHelper do - let(:project) { build(:project) } + let(:project) { build(:empty_project) } let(:group) { build(:group) } let(:personal_snippet) { build(:personal_snippet) } let(:project_snippet) { build(:project_snippet) } @@ -60,8 +60,8 @@ describe VisibilityLevelHelper do describe "skip_level?" do describe "forks" do - let(:project) { create(:project, :internal) } - let(:fork_project) { create(:project, forked_from_project: project) } + let(:project) { create(:empty_project, :internal) } + let(:fork_project) { create(:empty_project, forked_from_project: project) } it "skips levels" do expect(skip_level?(fork_project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy @@ -71,7 +71,7 @@ describe VisibilityLevelHelper do end describe "non-forked project" do - let(:project) { create(:project, :internal) } + let(:project) { create(:empty_project, :internal) } it "skips levels" do expect(skip_level?(project, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey diff --git a/spec/initializers/metrics_spec.rb b/spec/initializers/metrics_spec.rb new file mode 100644 index 00000000000..bb595162370 --- /dev/null +++ b/spec/initializers/metrics_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' +require_relative '../../config/initializers/metrics' + +describe 'instrument_classes', lib: true do + let(:config) { double(:config) } + + before do + allow(config).to receive(:instrument_method) + allow(config).to receive(:instrument_methods) + allow(config).to receive(:instrument_instance_methods) + end + + it 'can autoload and instrument all files' do + expect { instrument_classes(config) }.not_to raise_error + end +end diff --git a/spec/javascripts/abuse_reports_spec.js.es6 b/spec/javascripts/abuse_reports_spec.js.es6 index cf19aa05031..a2d57824585 100644 --- a/spec/javascripts/abuse_reports_spec.js.es6 +++ b/spec/javascripts/abuse_reports_spec.js.es6 @@ -21,7 +21,6 @@ messages = $('.abuse-reports .message'); }); - it('should truncate long messages', () => { const $longMessage = findMessage('LONG MESSAGE'); expect($longMessage.data('original-message')).toEqual(jasmine.anything()); diff --git a/spec/javascripts/environments/environment_rollback_spec.js.es6 b/spec/javascripts/environments/environment_rollback_spec.js.es6 index 21241116e29..95796f23894 100644 --- a/spec/javascripts/environments/environment_rollback_spec.js.es6 +++ b/spec/javascripts/environments/environment_rollback_spec.js.es6 @@ -33,7 +33,6 @@ describe('Rollback Component', () => { expect(component.$el.querySelector('span').textContent).toContain('Re-deploy'); }); - it('Should render Rollback label when isLastDeployment is false', () => { const component = new window.gl.environmentsList.RollbackComponent({ el: document.querySelector('.test-dom-element'), diff --git a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6 index 6df7c0e44ef..9d9097419ea 100644 --- a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6 +++ b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6 @@ -72,6 +72,12 @@ const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`); expect(result).toEqual(tokenKeys[0]); }); + + it('should return alternative tokenKey when found by key param', () => { + const tokenKeys = gl.FilteredSearchTokenKeys.getAlternatives(); + const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`); + expect(result).toEqual(tokenKeys[0]); + }); }); describe('searchByConditionUrl', () => { diff --git a/spec/javascripts/gfm_auto_complete_spec.js.es6 b/spec/javascripts/gfm_auto_complete_spec.js.es6 index 6b48d82cb23..99cebb32a8b 100644 --- a/spec/javascripts/gfm_auto_complete_spec.js.es6 +++ b/spec/javascripts/gfm_auto_complete_spec.js.es6 @@ -62,4 +62,30 @@ describe('GfmAutoComplete', function () { }); }); }); + + describe('isLoading', function () { + it('should be true with loading data object item', function () { + expect(GfmAutoComplete.isLoading({ name: 'loading' })).toBe(true); + }); + + it('should be true with loading data array', function () { + expect(GfmAutoComplete.isLoading(['loading'])).toBe(true); + }); + + it('should be true with loading data object array', function () { + expect(GfmAutoComplete.isLoading([{ name: 'loading' }])).toBe(true); + }); + + it('should be false with actual array data', function () { + expect(GfmAutoComplete.isLoading([ + { title: 'Foo' }, + { title: 'Bar' }, + { title: 'Qux' }, + ])).toBe(false); + }); + + it('should be false with actual data item', function () { + expect(GfmAutoComplete.isLoading({ title: 'Foo' })).toBe(false); + }); + }); }); diff --git a/spec/javascripts/lib/utils/common_utils_spec.js.es6 b/spec/javascripts/lib/utils/common_utils_spec.js.es6 index 031f9ca03c9..1ce8f28e568 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js.es6 +++ b/spec/javascripts/lib/utils/common_utils_spec.js.es6 @@ -52,5 +52,22 @@ expect(value).toBe(null); }); }); + + describe('gl.utils.normalizedHeaders', () => { + it('should upperCase all the header keys to keep them consistent', () => { + const apiHeaders = { + 'X-Something-Workhorse': { workhorse: 'ok' }, + 'x-something-nginx': { nginx: 'ok' }, + }; + + const normalized = gl.utils.normalizeHeaders(apiHeaders); + + const WORKHORSE = 'X-SOMETHING-WORKHORSE'; + const NGINX = 'X-SOMETHING-NGINX'; + + expect(normalized[WORKHORSE].workhorse).toBe('ok'); + expect(normalized[NGINX].nginx).toBe('ok'); + }); + }); }); })(); diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js index 755dba19744..386fc8f514e 100644 --- a/spec/javascripts/shortcuts_issuable_spec.js +++ b/spec/javascripts/shortcuts_issuable_spec.js @@ -1,6 +1,7 @@ /* eslint-disable space-before-function-paren, no-return-assign, no-var, quotes */ /* global ShortcutsIssuable */ +/*= require copy_as_gfm */ /*= require shortcuts_issuable */ (function() { @@ -14,10 +15,12 @@ }); return describe('#replyWithSelectedText', function() { var stubSelection; - // Stub window.getSelection to return the provided String. - stubSelection = function(text) { - return window.getSelection = function() { - return text; + // Stub window.gl.utils.getSelectedFragment to return a node with the provided HTML. + stubSelection = function(html) { + window.gl.utils.getSelectedFragment = function() { + var node = document.createElement('div'); + node.innerHTML = html; + return node; }; }; beforeEach(function() { @@ -32,13 +35,13 @@ }); describe('with any selection', function() { beforeEach(function() { - return stubSelection('Selected text.'); + return stubSelection('<p>Selected text.</p>'); }); it('leaves existing input intact', function() { $(this.selector).val('This text was already here.'); expect($(this.selector).val()).toBe('This text was already here.'); this.shortcut.replyWithSelectedText(); - return expect($(this.selector).val()).toBe("This text was already here.\n> Selected text.\n\n"); + return expect($(this.selector).val()).toBe("This text was already here.\n\n> Selected text.\n\n"); }); it('triggers `input`', function() { var triggered; @@ -61,16 +64,16 @@ }); describe('with a one-line selection', function() { return it('quotes the selection', function() { - stubSelection('This text has been selected.'); + stubSelection('<p>This text has been selected.</p>'); this.shortcut.replyWithSelectedText(); return expect($(this.selector).val()).toBe("> This text has been selected.\n\n"); }); }); return describe('with a multi-line selection', function() { return it('quotes the selected lines as a group', function() { - stubSelection("Selected line one.\n\nSelected line two.\nSelected line three.\n"); + stubSelection("<p>Selected line one.</p>\n\n<p>Selected line two.</p>\n\n<p>Selected line three.</p>"); this.shortcut.replyWithSelectedText(); - return expect($(this.selector).val()).toBe("> Selected line one.\n> Selected line two.\n> Selected line three.\n\n"); + return expect($(this.selector).val()).toBe("> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n"); }); }); }); 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 9703e2315b8..deadc36524c 100644 --- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do include FilterSpecHelper - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:commit1) { project.commit("HEAD~2") } let(:commit2) { project.commit } @@ -99,7 +99,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do end context 'cross-project / cross-namespace complete reference' do - let(:project2) { create(:project, :public) } + let(:project2) { create(:project, :public, :repository) } let(:reference) { "#{project2.path_with_namespace}@#{commit1.id}...#{commit2.id}" } it 'links to a valid reference' do @@ -133,8 +133,8 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do context 'cross-project / same-namespace complete reference' do let(:namespace) { create(:namespace) } - let(:project) { create(:project, :public, namespace: namespace) } - let(:project2) { create(:project, :public, path: "same-namespace", namespace: namespace) } + let(:project) { create(:project, :public, :repository, namespace: namespace) } + let(:project2) { create(:project, :public, :repository, path: "same-namespace", namespace: namespace) } let(:reference) { "#{project2.path}@#{commit1.id}...#{commit2.id}" } it 'links to a valid reference' do @@ -168,8 +168,8 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do context 'cross-project shorthand reference' do let(:namespace) { create(:namespace) } - let(:project) { create(:project, :public, namespace: namespace) } - let(:project2) { create(:project, :public, path: "same-namespace", namespace: namespace) } + let(:project) { create(:project, :public, :repository, namespace: namespace) } + let(:project2) { create(:project, :public, :repository, path: "same-namespace", namespace: namespace) } let(:reference) { "#{project2.path}@#{commit1.id}...#{commit2.id}" } it 'links to a valid reference' do @@ -203,7 +203,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do context 'cross-project URL reference' do let(:namespace) { create(:namespace) } - let(:project2) { create(:project, :public, namespace: 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') } diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb index 2e6dcc3a434..a19aac61229 100644 --- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::Filter::CommitReferenceFilter, lib: true do include FilterSpecHelper - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:commit) { project.commit } it 'requires project context' do @@ -96,7 +96,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do context 'cross-project / cross-namespace complete reference' do let(:namespace) { create(:namespace) } - let(:project2) { create(:project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, :repository, namespace: namespace) } let(:commit) { project2.commit } let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" } @@ -122,7 +122,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do context 'cross-project / same-namespace complete reference' do let(:namespace) { create(:namespace) } let(:project) { create(:empty_project, namespace: namespace) } - let(:project2) { create(:project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, :repository, namespace: namespace) } let(:commit) { project2.commit } let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" } @@ -148,7 +148,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do context 'cross-project shorthand reference' do let(:namespace) { create(:namespace) } let(:project) { create(:empty_project, namespace: namespace) } - let(:project2) { create(:project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, :repository, namespace: namespace) } let(:commit) { project2.commit } let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" } @@ -173,7 +173,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do context 'cross-project URL reference' do let(:namespace) { create(:namespace) } - let(:project2) { create(:project, :public, namespace: 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) } diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb index fe2ce092e6b..082c0d4dd0d 100644 --- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb +++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::Filter::GollumTagsFilter, lib: true do include FilterSpecHelper - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:user) { double } let(:project_wiki) { ProjectWiki.new(project, user) } 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 275010c1a2c..3d3d36061f4 100644 --- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb @@ -188,7 +188,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do context 'cross-project URL reference' do let(:namespace) { create(:namespace, name: 'cross-reference') } - let(:project2) { create(:project, :public, namespace: namespace) } + 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' } diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index 73b5edb99b3..a317c751d32 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::Filter::MilestoneReferenceFilter, lib: true do include FilterSpecHelper - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:milestone) { create(:milestone, project: project) } let(:reference) { milestone.to_reference } diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb index df2dd173b57..1957ba739e2 100644 --- a/spec/lib/banzai/filter/relative_link_filter_spec.rb +++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb @@ -25,7 +25,7 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do %(<a href="#{path}">#{path}</a>) end - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:project_path) { project.path_with_namespace } let(:ref) { 'markdown' } let(:commit) { project.commit(ref) } diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb index d265d29ee86..69e3c52b35a 100644 --- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb +++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb @@ -6,21 +6,21 @@ describe Banzai::Filter::SyntaxHighlightFilter, lib: true do context "when no language is specified" do it "highlights as plaintext" do result = filter('<pre><code>def fun end</code></pre>') - expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" v-pre="true"><code>def fun end</code></pre>') + expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code>def fun end</code></pre>') end end context "when a valid language is specified" do it "highlights as that language" do result = filter('<pre><code class="ruby">def fun end</code></pre>') - expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby" v-pre="true"><code><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></code></pre>') + expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby" lang="ruby" v-pre="true"><code><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></code></pre>') end end context "when an invalid language is specified" do it "highlights as plaintext" do result = filter('<pre><code class="gnuplot">This is a test</code></pre>') - expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" v-pre="true"><code>This is a test</code></pre>') + expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code>This is a test</code></pre>') end end @@ -31,7 +31,7 @@ describe Banzai::Filter::SyntaxHighlightFilter, lib: true do it "highlights as plaintext" do result = filter('<pre><code class="ruby">This is a test</code></pre>') - expect(result.to_html).to eq('<pre class="code highlight" v-pre="true"><code>This is a test</code></pre>') + expect(result.to_html).to eq('<pre class="code highlight" lang="" v-pre="true"><code>This is a test</code></pre>') end end end diff --git a/spec/lib/banzai/filter/upload_link_filter_spec.rb b/spec/lib/banzai/filter/upload_link_filter_spec.rb index 8b76c1d73c9..639cac6406a 100644 --- a/spec/lib/banzai/filter/upload_link_filter_spec.rb +++ b/spec/lib/banzai/filter/upload_link_filter_spec.rb @@ -29,7 +29,7 @@ describe Banzai::Filter::UploadLinkFilter, lib: true do %(<div><a href="#{path}">#{path}</a></div>) end - let(:project) { create(:project) } + let(:project) { create(:empty_project) } shared_examples :preserve_unchanged do it 'does not modify any relative URL in anchor' do diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index 5bfeb82e738..3e1ac9fb2b2 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -152,6 +152,30 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do end end + context 'when a project is not specified' do + let(:project) { nil } + + it 'does not link a User' do + doc = reference_filter("Hey #{reference}") + + expect(doc).not_to include('a') + end + + context 'when skip_project_check set to true' do + it 'links to a User' do + doc = reference_filter("Hey #{reference}", skip_project_check: true) + + expect(doc.css('a').first.attr('href')).to eq urls.user_url(user) + end + + it 'does not link users using @all reference' do + doc = reference_filter("Hey #{User.reference_prefix}all", skip_project_check: true) + + expect(doc).not_to include('a') + end + end + end + describe '#namespaces' do it 'returns a Hash containing all Namespaces' do document = Nokogiri::HTML.fragment("<p>#{user.to_reference}</p>") diff --git a/spec/lib/banzai/filter/video_link_filter_spec.rb b/spec/lib/banzai/filter/video_link_filter_spec.rb index 6ab1be9ccb7..00494f545a3 100644 --- a/spec/lib/banzai/filter/video_link_filter_spec.rb +++ b/spec/lib/banzai/filter/video_link_filter_spec.rb @@ -13,7 +13,7 @@ describe Banzai::Filter::VideoLinkFilter, lib: true do %(<img src="#{path}" />) end - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } context 'when the element src has a video extension' do UploaderHelper::VIDEO_EXT.each do |ext| diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb index fafc2cec546..31ca9d27b0b 100644 --- a/spec/lib/banzai/reference_parser/user_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb @@ -147,7 +147,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do describe '#nodes_user_can_reference' do context 'when the link has a data-author attribute' do it 'returns the nodes when the user is a member of the project' do - other_project = create(:project) + other_project = create(:empty_project) other_project.team << [user, :developer] link['data-project'] = other_project.id.to_s @@ -164,7 +164,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do end it 'returns an empty Array when the user could not be found' do - other_project = create(:project) + other_project = create(:empty_project) link['data-project'] = other_project.id.to_s link['data-author'] = '' @@ -173,7 +173,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do end it 'returns an empty Array when the user is not a team member' do - other_project = create(:project) + other_project = create(:empty_project) link['data-project'] = other_project.id.to_s link['data-author'] = user.id.to_s diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb index 94266f6653b..a5251e9a8c2 100644 --- a/spec/lib/constraints/project_url_constrainer_spec.rb +++ b/spec/lib/constraints/project_url_constrainer_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe ProjectUrlConstrainer, lib: true do - let!(:project) { create(:project) } + let!(:project) { create(:empty_project) } let!(:namespace) { project.namespace } describe '#matches?' do diff --git a/spec/lib/event_filter_spec.rb b/spec/lib/event_filter_spec.rb index ec2f66b1136..e3066311b7d 100644 --- a/spec/lib/event_filter_spec.rb +++ b/spec/lib/event_filter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe EventFilter, lib: true do describe '#apply_filter' do let(:source_user) { create(:user) } - let!(:public_project) { create(:project, :public) } + let!(:public_project) { create(:empty_project, :public) } let!(:push_event) { create(:event, action: Event::PUSHED, project: public_project, target: public_project, author: source_user) } let!(:merged_event) { create(:event, action: Event::MERGED, project: public_project, target: public_project, author: source_user) } diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index 0e85e302f29..29c07655ae8 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -24,7 +24,7 @@ describe ExtractsPath, lib: true do let(:params) { { path: sample_commit[:line_code_path], ref: ref } } before do - @project = create(:project) + @project = create(:project, :repository) end it "log tree path has no escape sequences" do diff --git a/spec/lib/gitlab/badge/build/metadata_spec.rb b/spec/lib/gitlab/badge/build/metadata_spec.rb index d678e522721..9df96ea04eb 100644 --- a/spec/lib/gitlab/badge/build/metadata_spec.rb +++ b/spec/lib/gitlab/badge/build/metadata_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' require 'lib/gitlab/badge/shared/metadata' describe Gitlab::Badge::Build::Metadata do - let(:badge) { double(project: create(:project), ref: 'feature') } + let(:badge) { double(project: create(:empty_project), ref: 'feature') } let(:metadata) { described_class.new(badge) } it_behaves_like 'badge metadata' diff --git a/spec/lib/gitlab/badge/build/status_spec.rb b/spec/lib/gitlab/badge/build/status_spec.rb index 70f03021d36..3c5414701a7 100644 --- a/spec/lib/gitlab/badge/build/status_spec.rb +++ b/spec/lib/gitlab/badge/build/status_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Badge::Build::Status do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:sha) { project.commit.sha } let(:branch) { 'master' } let(:badge) { described_class.new(project, branch) } diff --git a/spec/lib/gitlab/badge/coverage/metadata_spec.rb b/spec/lib/gitlab/badge/coverage/metadata_spec.rb index 74eaf7eaf8b..5e93935ea37 100644 --- a/spec/lib/gitlab/badge/coverage/metadata_spec.rb +++ b/spec/lib/gitlab/badge/coverage/metadata_spec.rb @@ -3,7 +3,7 @@ require 'lib/gitlab/badge/shared/metadata' describe Gitlab::Badge::Coverage::Metadata do let(:badge) do - double(project: create(:project), ref: 'feature', job: 'test') + double(project: create(:empty_project), ref: 'feature', job: 'test') end let(:metadata) { described_class.new(badge) } diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index 72b1ba36b58..0a2fe5af2c3 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -52,7 +52,7 @@ describe Gitlab::BitbucketImport::Importer, lib: true do let(:project) do create( - :project, + :empty_project, import_source: project_identifier, import_data: ProjectImportData.new(credentials: data) ) diff --git a/spec/lib/gitlab/blame_spec.rb b/spec/lib/gitlab/blame_spec.rb index 89245761b6f..26b1baf75be 100644 --- a/spec/lib/gitlab/blame_spec.rb +++ b/spec/lib/gitlab/blame_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Blame, lib: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:path) { 'files/ruby/popen.rb' } let(:commit) { project.commit('master') } let(:blob) { project.repository.blob_at(commit.id, path) } diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb index a2d84977f58..1e81eaef18c 100644 --- a/spec/lib/gitlab/chat_commands/command_spec.rb +++ b/spec/lib/gitlab/chat_commands/command_spec.rb @@ -11,7 +11,7 @@ describe Gitlab::ChatCommands::Command, service: true do context 'when no command is available' do let(:params) { { text: 'issue show 1' } } - let(:project) { create(:project, has_external_issue_tracker: true) } + let(:project) { create(:empty_project, has_external_issue_tracker: true) } it 'displays 404 messages' do expect(subject[:response_type]).to be(:ephemeral) diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb index 98effecdbbc..cadfbadca10 100644 --- a/spec/lib/gitlab/checks/change_access_spec.rb +++ b/spec/lib/gitlab/checks/change_access_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Checks::ChangeAccess, lib: true do describe '#exec' do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user_access) { Gitlab::UserAccess.new(user, project: project) } let(:changes) do { diff --git a/spec/lib/gitlab/checks/force_push_spec.rb b/spec/lib/gitlab/checks/force_push_spec.rb index f6288011494..7a84bbebd02 100644 --- a/spec/lib/gitlab/checks/force_push_spec.rb +++ b/spec/lib/gitlab/checks/force_push_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Checks::ChangeAccess, lib: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } context "exit code checking" do it "does not raise a runtime error if the `popen` call to git returns a zero exit code" do diff --git a/spec/lib/gitlab/ci/trace_reader_spec.rb b/spec/lib/gitlab/ci/trace_reader_spec.rb index f06d78694d6..ff5551bf703 100644 --- a/spec/lib/gitlab/ci/trace_reader_spec.rb +++ b/spec/lib/gitlab/ci/trace_reader_spec.rb @@ -11,13 +11,25 @@ describe Gitlab::Ci::TraceReader do last_lines = random_lines expected = lines.last(last_lines).join + result = subject.read(last_lines: last_lines) - expect(subject.read(last_lines: last_lines)).to eq(expected) + expect(result).to eq(expected) + expect(result.encoding).to eq(Encoding.default_external) end end it 'returns everything if trying to get too many lines' do - expect(build_subject.read(last_lines: lines.size * 2)).to eq(lines.join) + result = build_subject.read(last_lines: lines.size * 2) + + expect(result).to eq(lines.join) + expect(result.encoding).to eq(Encoding.default_external) + end + + it 'returns all contents if last_lines is not specified' do + result = build_subject.read + + expect(result).to eq(lines.join) + expect(result.encoding).to eq(Encoding.default_external) end it 'raises an error if not passing an integer for last_lines' do diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index 1bbaca0739a..97af1c2523d 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -1,11 +1,11 @@ require 'spec_helper' describe Gitlab::ClosingIssueExtractor, lib: true do - let(:project) { create(:project) } - let(:project2) { create(:project) } + let(:project) { create(:empty_project) } + let(:project2) { create(:empty_project) } let(:forked_project) { Projects::ForkService.new(project, project.creator).execute } - let(:issue) { create(:issue, project: project) } - let(:issue2) { create(:issue, project: project2) } + let(:issue) { create(:issue, project: project) } + let(:issue2) { create(:issue, project: project2) } let(:reference) { issue.to_reference } let(:cross_reference) { issue2.to_reference(project) } let(:fork_cross_reference) { issue.to_reference(forked_project) } diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb index 648d342ecf8..fbf679c5215 100644 --- a/spec/lib/gitlab/conflict/file_spec.rb +++ b/spec/lib/gitlab/conflict/file_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Conflict::File, lib: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:rugged) { repository.rugged } let(:their_commit) { rugged.branches['conflict-start'].target } diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb index 004341ffd02..b01c4805a34 100644 --- a/spec/lib/gitlab/current_settings_spec.rb +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -1,36 +1,64 @@ require 'spec_helper' describe Gitlab::CurrentSettings do + include StubENV + + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') + end + describe '#current_application_settings' do - it 'attempts to use cached values first' do - allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true) - expect(ApplicationSetting).to receive(:current).and_return(::ApplicationSetting.create_from_defaults) - expect(ApplicationSetting).not_to receive(:last) + context 'with DB available' do + before do + allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true) + end - expect(current_application_settings).to be_a(ApplicationSetting) - end + it 'attempts to use cached values first' do + expect(ApplicationSetting).to receive(:current) + expect(ApplicationSetting).not_to receive(:last) + + expect(current_application_settings).to be_a(ApplicationSetting) + end - it 'does not attempt to connect to DB or Redis' do - allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(false) - expect(ApplicationSetting).not_to receive(:current) - expect(ApplicationSetting).not_to receive(:last) + it 'falls back to DB if Redis returns an empty value' do + expect(ApplicationSetting).to receive(:last).and_call_original - expect(current_application_settings).to eq fake_application_settings + expect(current_application_settings).to be_a(ApplicationSetting) + end + + it 'falls back to DB if Redis fails' do + expect(ApplicationSetting).to receive(:current).and_raise(::Redis::BaseError) + expect(ApplicationSetting).to receive(:last).and_call_original + + expect(current_application_settings).to be_a(ApplicationSetting) + end end - it 'falls back to DB if Redis returns an empty value' do - allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true) - expect(ApplicationSetting).to receive(:last).and_call_original + context 'with DB unavailable' do + before do + allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(false) + end - expect(current_application_settings).to be_a(ApplicationSetting) + it 'returns an in-memory ApplicationSetting object' do + expect(ApplicationSetting).not_to receive(:current) + expect(ApplicationSetting).not_to receive(:last) + + expect(current_application_settings).to be_a(OpenStruct) + end end - it 'falls back to DB if Redis fails' do - allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true) - expect(ApplicationSetting).to receive(:current).and_raise(::Redis::BaseError) - expect(ApplicationSetting).to receive(:last).and_call_original + context 'when ENV["IN_MEMORY_APPLICATION_SETTINGS"] is true' do + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'true') + end + + it 'returns an in-memory ApplicationSetting object' do + expect(ApplicationSetting).not_to receive(:current) + expect(ApplicationSetting).not_to receive(:last) - expect(current_application_settings).to be_a(ApplicationSetting) + expect(current_application_settings).to be_a(ApplicationSetting) + expect(current_application_settings).not_to be_persisted + end end end end diff --git a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb index fb6b6c4a8d2..3dd76ba5b8a 100644 --- a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::CycleAnalytics::StageSummary, models: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from) { 1.day.ago } let(:user) { create(:user, :admin) } subject { described_class.new(project, from: Time.now, current_user: user).data } @@ -15,7 +15,7 @@ describe Gitlab::CycleAnalytics::StageSummary, models: true do end it "doesn't find issues from other projects" do - Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project)) } + Timecop.freeze(5.days.from_now) { create(:issue, project: create(:empty_project)) } expect(subject.first[:value]).to eq(0) end @@ -30,7 +30,7 @@ describe Gitlab::CycleAnalytics::StageSummary, models: true do end it "doesn't find commits from other projects" do - Timecop.freeze(5.days.from_now) { create_commit("Test message", create(:project), user, 'master') } + Timecop.freeze(5.days.from_now) { create_commit("Test message", create(:project, :repository), user, 'master') } expect(subject.second[:value]).to eq(0) end @@ -51,7 +51,9 @@ describe Gitlab::CycleAnalytics::StageSummary, models: true do end it "doesn't find commits from other projects" do - Timecop.freeze(5.days.from_now) { create(:deployment, project: create(:project)) } + Timecop.freeze(5.days.from_now) do + create(:deployment, project: create(:project, :repository)) + end expect(subject.third[:value]).to eq(0) end diff --git a/spec/lib/gitlab/data_builder/note_spec.rb b/spec/lib/gitlab/data_builder/note_spec.rb index 9a4dec91e56..04ec34492e1 100644 --- a/spec/lib/gitlab/data_builder/note_spec.rb +++ b/spec/lib/gitlab/data_builder/note_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::DataBuilder::Note, lib: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:data) { described_class.build(note, user) } let(:fixed_time) { Time.at(1425600000) } # Avoid time precision errors diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb index a68f5943a6a..f13041e498c 100644 --- a/spec/lib/gitlab/data_builder/pipeline_spec.rb +++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::DataBuilder::Pipeline do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) do create(:ci_pipeline, diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb index a379f798a16..dbcfb9b7400 100644 --- a/spec/lib/gitlab/data_builder/push_spec.rb +++ b/spec/lib/gitlab/data_builder/push_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::DataBuilder::Push, lib: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } describe '.build_sample' do diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index 38475792d93..050689b7c9a 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Diff::File, lib: true do include RepoHelpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:commit) { project.commit(sample_commit.id) } let(:diff) { commit.raw_diffs.first } let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) } diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb index 1c2ddeed692..1e21270d928 100644 --- a/spec/lib/gitlab/diff/highlight_spec.rb +++ b/spec/lib/gitlab/diff/highlight_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Diff::Highlight, lib: true do include RepoHelpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:commit) { project.commit(sample_commit.id) } let(:diff) { commit.raw_diffs.first } let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) } diff --git a/spec/lib/gitlab/diff/line_mapper_spec.rb b/spec/lib/gitlab/diff/line_mapper_spec.rb index 4b943fa382d..2c7ecd1907e 100644 --- a/spec/lib/gitlab/diff/line_mapper_spec.rb +++ b/spec/lib/gitlab/diff/line_mapper_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Diff::LineMapper, lib: true do include RepoHelpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:commit) { project.commit(sample_commit.id) } let(:diffs) { commit.raw_diffs } diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb index af18d3c25a6..fe5fa048413 100644 --- a/spec/lib/gitlab/diff/parallel_diff_spec.rb +++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Diff::ParallelDiff, lib: true do include RepoHelpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:commit) { project.commit(sample_commit.id) } let(:diffs) { commit.raw_diffs } diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb index 6e8fff6f516..cdf0af6d7ef 100644 --- a/spec/lib/gitlab/diff/position_spec.rb +++ b/spec/lib/gitlab/diff/position_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Diff::Position, lib: true do include RepoHelpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } describe "position for an added file" do let(:commit) { project.commit("2ea1f3dec713d940208fb5ce4a38765ecb5d3f73") } diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb index c268f84c759..f5822fed37c 100644 --- a/spec/lib/gitlab/diff/position_tracer_spec.rb +++ b/spec/lib/gitlab/diff/position_tracer_spec.rb @@ -51,7 +51,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do include RepoHelpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:current_user) { project.owner } let(:repository) { project.repository } let(:file_name) { "test-file" } diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb index 08897a4c310..4a9c9a7fe34 100644 --- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb @@ -13,7 +13,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler, lib: true do let(:email_raw) { fixture_file('emails/valid_new_issue.eml') } let(:namespace) { create(:namespace, path: 'gitlabhq') } - let!(:project) { create(:project, :public, namespace: namespace) } + let!(:project) { create(:project, :public, :repository, namespace: namespace) } let!(:user) do create( :user, diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb index cebbeff50cf..17a4ef25210 100644 --- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb @@ -11,7 +11,7 @@ describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do end let(:email_raw) { fixture_file('emails/valid_reply.eml') } - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:user) { create(:user) } let(:note) { create(:diff_note_on_merge_request, project: project) } let(:noteable) { note.noteable } diff --git a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb index a444257754b..0939e6c4514 100644 --- a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb @@ -10,7 +10,7 @@ describe Gitlab::Email::Handler::UnsubscribeHandler, lib: true do end let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "#{mail_key}+unsubscribe") } - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:user) { create(:user) } let(:noteable) { create(:issue, project: project) } diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb index 5b966bddb6a..7b3291b8315 100644 --- a/spec/lib/gitlab/email/message/repository_push_spec.rb +++ b/spec/lib/gitlab/email/message/repository_push_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::Email::Message::RepositoryPush do include RepoHelpers let!(:group) { create(:group, name: 'my_group') } - let!(:project) { create(:project, name: 'my_project', namespace: group) } + let!(:project) { create(:project, :repository, name: 'my_project', namespace: group) } let!(:author) { create(:author, name: 'Author') } let(:message) do diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb index f4703dc704f..5d416c9eec3 100644 --- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Gitlab::Gfm::ReferenceRewriter do let(:text) { 'some text' } - let(:old_project) { create(:project, name: 'old') } - let(:new_project) { create(:project, name: 'new') } + let(:old_project) { create(:empty_project, name: 'old-project') } + let(:new_project) { create(:empty_project, name: 'new-project') } let(:user) { create(:user) } before { old_project.team << [user, :reporter] } diff --git a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb index 6eca33f9fee..c3016f63ebf 100644 --- a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Gitlab::Gfm::UploadsRewriter do let(:user) { create(:user) } - let(:old_project) { create(:project) } - let(:new_project) { create(:project) } + let(:old_project) { create(:empty_project) } + let(:new_project) { create(:empty_project) } let(:rewriter) { described_class.new(text, old_project, user) } context 'text contains links to uploads' do diff --git a/spec/lib/gitlab/git/hook_spec.rb b/spec/lib/gitlab/git/hook_spec.rb index d1f947b6850..3f279c21865 100644 --- a/spec/lib/gitlab/git/hook_spec.rb +++ b/spec/lib/gitlab/git/hook_spec.rb @@ -3,7 +3,7 @@ require 'fileutils' describe Gitlab::Git::Hook, lib: true do describe "#trigger" do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } def create_hook(name) diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb index 1f9c987be0b..d48629a296d 100644 --- a/spec/lib/gitlab/git/rev_list_spec.rb +++ b/spec/lib/gitlab/git/rev_list_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Git::RevList, lib: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } context "validations" do described_class::ALLOWED_VARIABLES.each do |var| diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 44b84afde47..b080be62b34 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::GitAccess, lib: true do let(:access) { Gitlab::GitAccess.new(actor, project, 'web', authentication_abilities: authentication_abilities) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:actor) { user } let(:authentication_abilities) do @@ -88,7 +88,7 @@ describe Gitlab::GitAccess, lib: true do end context 'when project is public' do - let(:public_project) { create(:project, :public) } + let(:public_project) { create(:project, :public, :repository) } let(:guest_access) { Gitlab::GitAccess.new(nil, public_project, 'web', authentication_abilities: []) } subject { guest_access.check('git-upload-pack', '_any') } @@ -124,19 +124,19 @@ describe Gitlab::GitAccess, lib: true do context 'when unauthorized' do context 'from public project' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } it { expect(subject).to be_allowed } end context 'from internal project' do - let(:project) { create(:project, :internal) } + let(:project) { create(:project, :internal, :repository) } it { expect(subject).not_to be_allowed } end context 'from private project' do - let(:project) { create(:project, :private) } + let(:project) { create(:project, :private, :repository) } it { expect(subject).not_to be_allowed } end @@ -148,7 +148,7 @@ describe Gitlab::GitAccess, lib: true do let(:authentication_abilities) { build_authentication_abilities } describe 'owner' do - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { create(:project, :repository, namespace: user.namespace) } context 'pull code' do it { expect(subject).to be_allowed } @@ -364,19 +364,19 @@ describe Gitlab::GitAccess, lib: true do context 'when unauthorized' do context 'to public project' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } it { expect(subject).not_to be_allowed } end context 'to internal project' do - let(:project) { create(:project, :internal) } + let(:project) { create(:project, :internal, :repository) } it { expect(subject).not_to be_allowed } end context 'to private project' do - let(:project) { create(:project) } + let(:project) { create(:project, :private, :repository) } it { expect(subject).not_to be_allowed } end diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index a5d172233cc..4a0cdc6887e 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::GitAccessWiki, lib: true do let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities: authentication_abilities) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:authentication_abilities) do [ diff --git a/spec/lib/gitlab/github_import/branch_formatter_spec.rb b/spec/lib/gitlab/github_import/branch_formatter_spec.rb index 462caa5b5fe..36e7d739f7e 100644 --- a/spec/lib/gitlab/github_import/branch_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/branch_formatter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GithubImport::BranchFormatter, lib: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:commit) { create(:commit, project: project) } let(:repo) { double } let(:raw) do diff --git a/spec/lib/gitlab/github_import/comment_formatter_spec.rb b/spec/lib/gitlab/github_import/comment_formatter_spec.rb index c520a9c53ad..e6e33d3686a 100644 --- a/spec/lib/gitlab/github_import/comment_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/comment_formatter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GithubImport::CommentFormatter, lib: true do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:octocat) { double(id: 123456, login: 'octocat') } let(:created_at) { DateTime.strptime('2013-04-10T20:09:31Z') } let(:updated_at) { DateTime.strptime('2014-03-03T18:58:10Z') } diff --git a/spec/lib/gitlab/github_import/issue_formatter_spec.rb b/spec/lib/gitlab/github_import/issue_formatter_spec.rb index e31ed9c1fa0..eec1fabab54 100644 --- a/spec/lib/gitlab/github_import/issue_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/issue_formatter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GithubImport::IssueFormatter, lib: true do - let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) } + let!(:project) { create(:empty_project, namespace: create(:namespace, path: 'octocat')) } let(:octocat) { double(id: 123456, login: 'octocat') } let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') } diff --git a/spec/lib/gitlab/github_import/label_formatter_spec.rb b/spec/lib/gitlab/github_import/label_formatter_spec.rb index 8098754d735..10449ef5fcb 100644 --- a/spec/lib/gitlab/github_import/label_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/label_formatter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GithubImport::LabelFormatter, lib: true do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:raw) { double(name: 'improvements', color: 'e6e6e6') } subject { described_class.new(project, raw) } diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb index 2b3256edcb2..90947ff4707 100644 --- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GithubImport::PullRequestFormatter, lib: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:source_sha) { create(:commit, project: project).id } let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id } let(:repository) { double(id: 1, fork: false) } diff --git a/spec/lib/gitlab/github_import/release_formatter_spec.rb b/spec/lib/gitlab/github_import/release_formatter_spec.rb index 793128c6ab9..13b15e669ab 100644 --- a/spec/lib/gitlab/github_import/release_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/release_formatter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GithubImport::ReleaseFormatter, lib: true do - let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) } + let!(:project) { create(:empty_project, namespace: create(:namespace, path: 'octocat')) } let(:octocat) { double(id: 123456, login: 'octocat') } let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb index 097861fd34d..ccaa88a5c79 100644 --- a/spec/lib/gitlab/google_code_import/importer_spec.rb +++ b/spec/lib/gitlab/google_code_import/importer_spec.rb @@ -10,7 +10,7 @@ describe Gitlab::GoogleCodeImport::Importer, lib: true do 'user_map' => { 'thilo...' => "@#{mapped_user.username}" } } end - let(:project) { create(:project) } + let(:project) { create(:empty_project) } subject { described_class.new(project) } diff --git a/spec/lib/gitlab/graphs/commits_spec.rb b/spec/lib/gitlab/graphs/commits_spec.rb index f5c064303ad..abb5a26060f 100644 --- a/spec/lib/gitlab/graphs/commits_spec.rb +++ b/spec/lib/gitlab/graphs/commits_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Graphs::Commits, lib: true do - let!(:project) { create(:project, :public, :empty_repo) } + let!(:project) { create(:empty_project, :public) } let!(:commit1) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: Time.now) } let!(:commit1_yesterday) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: 1.day.ago)} diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb index fc021416d92..fadfe4d378e 100644 --- a/spec/lib/gitlab/highlight_spec.rb +++ b/spec/lib/gitlab/highlight_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Highlight, lib: true do include RepoHelpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:commit) { project.commit(sample_commit.id) } diff --git a/spec/lib/gitlab/import_export/import_export_spec.rb b/spec/lib/gitlab/import_export/import_export_spec.rb index d6409a29550..53f7d244d88 100644 --- a/spec/lib/gitlab/import_export/import_export_spec.rb +++ b/spec/lib/gitlab/import_export/import_export_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::ImportExport, services: true do describe 'export filename' do - let(:project) { create(:project, :public, path: 'project-path') } + let(:project) { create(:empty_project, :public, path: 'project-path') } it 'contains the project path' do expect(described_class.export_filename(project: project)).to include(project.path) diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb index b069696b5c7..0b7984d6ca9 100644 --- a/spec/lib/gitlab/import_export/members_mapper_spec.rb +++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::ImportExport::MembersMapper, services: true do describe 'map members' do let(:user) { create(:admin, authorized_projects_populated: true) } - let(:project) { create(:project, :public, name: 'searchable_project') } + let(:project) { create(:empty_project, :public, name: 'searchable_project') } let(:user2) { create(:user, authorized_projects_populated: true) } let(:exported_user_id) { 99 } let(:exported_members) do diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index c8bba553558..d480c3821ec 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -151,6 +151,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do project = create(:project, :public, + :repository, issues: [issue], snippets: [snippet], releases: [release], diff --git a/spec/lib/gitlab/import_export/repo_bundler_spec.rb b/spec/lib/gitlab/import_export/repo_bundler_spec.rb index 135e99bc953..d39ea60ff7f 100644 --- a/spec/lib/gitlab/import_export/repo_bundler_spec.rb +++ b/spec/lib/gitlab/import_export/repo_bundler_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::ImportExport::RepoSaver, services: true do describe 'bundle a project Git repo' do let(:user) { create(:user) } - let!(:project) { create(:project, :public, name: 'searchable_project') } + let!(:project) { create(:empty_project, :public, name: 'searchable_project') } let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" } let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) } let(:bundler) { described_class.new(project: project, shared: shared) } diff --git a/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb index b628da0f3e8..47d5d2fc150 100644 --- a/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb +++ b/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::ImportExport::WikiRepoSaver, services: true do describe 'bundle a wiki Git repo' do let(:user) { create(:user) } - let!(:project) { create(:project, :public, name: 'searchable_project') } + let!(:project) { create(:empty_project, :public, name: 'searchable_project') } let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" } let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) } let(:wiki_bundler) { described_class.new(project: project, shared: shared) } diff --git a/spec/lib/gitlab/job_waiter_spec.rb b/spec/lib/gitlab/job_waiter_spec.rb new file mode 100644 index 00000000000..780f5b1f8d7 --- /dev/null +++ b/spec/lib/gitlab/job_waiter_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe Gitlab::JobWaiter do + describe '#wait' do + let(:waiter) { described_class.new(%w(a)) } + it 'returns when all jobs have been completed' do + expect(Gitlab::SidekiqStatus).to receive(:all_completed?).with(%w(a)). + and_return(true) + + expect(waiter).not_to receive(:sleep) + + waiter.wait + end + + it 'sleeps between checking the job statuses' do + expect(Gitlab::SidekiqStatus).to receive(:all_completed?). + with(%w(a)). + and_return(false, true) + + expect(waiter).to receive(:sleep).with(described_class::INTERVAL) + + waiter.wait + end + + it 'returns when timing out' do + expect(waiter).not_to receive(:sleep) + waiter.wait(0) + end + end +end diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb index d94eb52f838..92e3624a8d8 100644 --- a/spec/lib/gitlab/project_search_results_spec.rb +++ b/spec/lib/gitlab/project_search_results_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::ProjectSearchResults, lib: true do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:query) { 'hello world' } describe 'initialize with empty ref' do @@ -22,6 +22,7 @@ describe Gitlab::ProjectSearchResults, lib: true do end describe 'blob search' do + let(:project) { create(:project, :repository) } let(:results) { described_class.new(user, project, 'files').objects('blobs') } it 'finds by name' do @@ -74,6 +75,7 @@ describe Gitlab::ProjectSearchResults, lib: true do end describe 'confidential issues' do + let(:project) { create(:empty_project) } let(:query) { 'issue' } let(:author) { create(:user) } let(:assignee) { create(:user) } @@ -187,7 +189,7 @@ describe Gitlab::ProjectSearchResults, lib: true do # shared_examples 'access restricted commits' do context 'when project is internal' do - let(:project) { create(:project, :internal) } + let(:project) { create(:project, :internal, :repository) } it 'does not search if user is not authenticated' do commits = described_class.new(nil, project, search_phrase).objects('commits') @@ -204,7 +206,7 @@ describe Gitlab::ProjectSearchResults, lib: true do context 'when project is private' do let!(:creator) { create(:user, username: 'private-project-author') } - let!(:private_project) { create(:project, :private, creator: creator, namespace: creator.namespace) } + let!(:private_project) { create(:project, :private, :repository, creator: creator, namespace: creator.namespace) } let(:team_master) do user = create(:user, username: 'private-project-master') private_project.team << [user, :master] @@ -246,7 +248,7 @@ describe Gitlab::ProjectSearchResults, lib: true do describe 'commit search' do context 'by commit message' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:commit) { project.repository.commit('59e29889be61e6e0e5e223bfa9ac2721d31605b8') } let(:message) { 'Sorry, I did a mistake' } @@ -269,7 +271,7 @@ describe Gitlab::ProjectSearchResults, lib: true do end context 'by commit hash' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:commit) { project.repository.commit('0b4bc9a') } commit_hashes = { short: '0b4bc9a', full: '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index bf0ab9635fd..6b689c41ef6 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -1,9 +1,11 @@ require 'spec_helper' describe Gitlab::ReferenceExtractor, lib: true do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } - before { project.team << [project.creator, :developer] } + before do + project.team << [project.creator, :developer] + end subject { Gitlab::ReferenceExtractor.new(project, project.creator) } @@ -78,22 +80,27 @@ describe Gitlab::ReferenceExtractor, lib: true do end it 'accesses valid commits' do + project = create(:project, :repository) { |p| p.add_developer(p.creator) } commit = project.commit('master') - subject.analyze("this references commits #{commit.sha[0..6]} and 012345") - extracted = subject.commits + extractor = described_class.new(project, project.creator) + extractor.analyze("this references commits #{commit.sha[0..6]} and 012345") + extracted = extractor.commits + expect(extracted.size).to eq(1) expect(extracted[0].sha).to eq(commit.sha) expect(extracted[0].message).to eq(commit.message) end it 'accesses valid commit ranges' do + project = create(:project, :repository) { |p| p.add_developer(p.creator) } commit = project.commit('master') earlier_commit = project.commit('master~2') - subject.analyze("this references commits #{earlier_commit.sha[0..6]}...#{commit.sha[0..6]}") + extractor = described_class.new(project, project.creator) + extractor.analyze("this references commits #{earlier_commit.sha[0..6]}...#{commit.sha[0..6]}") + extracted = extractor.commit_ranges - extracted = subject.commit_ranges expect(extracted.size).to eq(1) expect(extracted.first).to be_kind_of(CommitRange) expect(extracted.first.commit_from).to eq earlier_commit @@ -102,7 +109,6 @@ describe Gitlab::ReferenceExtractor, lib: true do context 'with an external issue tracker' do let(:project) { create(:jira_project) } - subject { described_class.new(project, project.creator) } it 'returns JIRA issues for a JIRA-integrated project' do subject.analyze('JIRA-123 and FOOBAR-4567') @@ -112,7 +118,7 @@ describe Gitlab::ReferenceExtractor, lib: true do end context 'with a project with an underscore' do - let(:other_project) { create(:project, path: 'test_project') } + let(:other_project) { create(:empty_project, path: 'test_project') } let(:issue) { create(:issue, project: other_project) } before do diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb index 9614aad3e73..847fb977400 100644 --- a/spec/lib/gitlab/search_results_spec.rb +++ b/spec/lib/gitlab/search_results_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::SearchResults do let(:user) { create(:user) } - let!(:project) { create(:project, name: 'foo') } + let!(:project) { create(:empty_project, name: 'foo') } let!(:issue) { create(:issue, project: project, title: 'foo') } let!(:merge_request) do diff --git a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb new file mode 100644 index 00000000000..287bf62d9bd --- /dev/null +++ b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +describe Gitlab::SidekiqStatus::ClientMiddleware do + describe '#call' do + it 'tracks the job in Redis' do + expect(Gitlab::SidekiqStatus).to receive(:set).with('123') + + described_class.new. + call('Foo', { 'jid' => '123' }, double(:queue), double(:pool)) { nil } + end + end +end diff --git a/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb b/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb new file mode 100644 index 00000000000..80728197b8c --- /dev/null +++ b/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe Gitlab::SidekiqStatus::ServerMiddleware do + describe '#call' do + it 'stops tracking of a job upon completion' do + expect(Gitlab::SidekiqStatus).to receive(:unset).with('123') + + ret = described_class.new. + call(double(:worker), { 'jid' => '123' }, double(:queue)) { 10 } + + expect(ret).to eq(10) + end + end +end diff --git a/spec/lib/gitlab/sidekiq_status_spec.rb b/spec/lib/gitlab/sidekiq_status_spec.rb new file mode 100644 index 00000000000..0aa36a3416b --- /dev/null +++ b/spec/lib/gitlab/sidekiq_status_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +describe Gitlab::SidekiqStatus do + describe '.set', :redis do + it 'stores the job ID' do + described_class.set('123') + + key = described_class.key_for('123') + + Sidekiq.redis do |redis| + expect(redis.exists(key)).to eq(true) + expect(redis.ttl(key) > 0).to eq(true) + end + end + end + + describe '.unset', :redis do + it 'removes the job ID' do + described_class.set('123') + described_class.unset('123') + + key = described_class.key_for('123') + + Sidekiq.redis do |redis| + expect(redis.exists(key)).to eq(false) + end + end + end + + describe '.all_completed?', :redis do + it 'returns true if all jobs have been completed' do + expect(described_class.all_completed?(%w(123))).to eq(true) + end + + it 'returns false if a job has not yet been completed' do + described_class.set('123') + + expect(described_class.all_completed?(%w(123 456))).to eq(false) + end + end + + describe '.key_for' do + it 'returns the key for a job ID' do + key = described_class.key_for('123') + + expect(key).to be_an_instance_of(String) + expect(key).to include('123') + end + end +end diff --git a/spec/lib/gitlab/template/issue_template_spec.rb b/spec/lib/gitlab/template/issue_template_spec.rb index d2d334e6413..45cec65a284 100644 --- a/spec/lib/gitlab/template/issue_template_spec.rb +++ b/spec/lib/gitlab/template/issue_template_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::Template::IssueTemplate do subject { described_class } let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:file_path_1) { '.gitlab/issue_templates/bug.md' } let(:file_path_2) { '.gitlab/issue_templates/template_test.md' } let(:file_path_3) { '.gitlab/issue_templates/feature_proposal.md' } diff --git a/spec/lib/gitlab/template/merge_request_template_spec.rb b/spec/lib/gitlab/template/merge_request_template_spec.rb index ddf68c4cf78..ae51b79be22 100644 --- a/spec/lib/gitlab/template/merge_request_template_spec.rb +++ b/spec/lib/gitlab/template/merge_request_template_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::Template::MergeRequestTemplate do subject { described_class } let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:file_path_1) { '.gitlab/merge_request_templates/bug.md' } let(:file_path_2) { '.gitlab/merge_request_templates/template_test.md' } let(:file_path_3) { '.gitlab/merge_request_templates/feature_proposal.md' } diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb index a826b24419a..3fe8cf43934 100644 --- a/spec/lib/gitlab/url_builder_spec.rb +++ b/spec/lib/gitlab/url_builder_spec.rb @@ -99,7 +99,7 @@ describe Gitlab::UrlBuilder, lib: true do context 'on another object' do it 'returns a proper URL' do - project = build_stubbed(:project) + project = build_stubbed(:empty_project) expect { described_class.build(project) }. to raise_error(NotImplementedError, 'No URL builder defined for Project') diff --git a/spec/lib/gitlab/view/presenter/delegated_spec.rb b/spec/lib/gitlab/view/presenter/delegated_spec.rb index 888ab80cad5..e9d4af54389 100644 --- a/spec/lib/gitlab/view/presenter/delegated_spec.rb +++ b/spec/lib/gitlab/view/presenter/delegated_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::View::Presenter::Delegated do - let(:project) { double(:project, bar: 'baz') } + let(:project) { double(:project, user: 'John Doe') } let(:presenter_class) do Class.new(described_class) end @@ -12,10 +12,14 @@ describe Gitlab::View::Presenter::Delegated do describe '#initialize' do it 'takes arbitrary key/values and exposes them' do - presenter = presenter_class.new(project, user: 'user', foo: 'bar') + presenter = presenter_class.new(project, current_user: 'Jane Doe') - expect(presenter.user).to eq('user') - expect(presenter.foo).to eq('bar') + expect(presenter.current_user).to eq('Jane Doe') + end + + it 'raise an error if the presentee already respond to method' do + expect { presenter_class.new(project, user: 'Jane Doe') }. + to raise_error Gitlab::View::Presenter::CannotOverrideMethodError end end @@ -23,7 +27,7 @@ describe Gitlab::View::Presenter::Delegated do it 'forwards missing methods to subject' do presenter = presenter_class.new(project) - expect(presenter.bar).to eq('baz') + expect(presenter.user).to eq('John Doe') end end end diff --git a/spec/lib/gitlab/view/presenter/factory_spec.rb b/spec/lib/gitlab/view/presenter/factory_spec.rb index 55c5ecbf92f..70d2e22b48f 100644 --- a/spec/lib/gitlab/view/presenter/factory_spec.rb +++ b/spec/lib/gitlab/view/presenter/factory_spec.rb @@ -22,13 +22,6 @@ describe Gitlab::View::Presenter::Factory do end describe '#fabricate!' do - it 'exposes given params' do - presenter = described_class.new(build, user: 'user', foo: 'bar').fabricate! - - expect(presenter.user).to eq('user') - expect(presenter.foo).to eq('bar') - end - it 'detects the presenter based on the given subject' do presenter = described_class.new(build).fabricate! diff --git a/spec/lib/gitlab/view/presenter/simple_spec.rb b/spec/lib/gitlab/view/presenter/simple_spec.rb index b489bdf1981..1795ed2405b 100644 --- a/spec/lib/gitlab/view/presenter/simple_spec.rb +++ b/spec/lib/gitlab/view/presenter/simple_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::View::Presenter::Simple do - let(:project) { double(:project) } + let(:project) { double(:project, user: 'John Doe') } let(:presenter_class) do Class.new(described_class) end @@ -12,10 +12,15 @@ describe Gitlab::View::Presenter::Simple do describe '#initialize' do it 'takes arbitrary key/values and exposes them' do - presenter = presenter_class.new(project, user: 'user', foo: 'bar') + presenter = presenter_class.new(project, current_user: 'Jane Doe') - expect(presenter.user).to eq('user') - expect(presenter.foo).to eq('bar') + expect(presenter.current_user).to eq('Jane Doe') + end + + it 'override the presentee attributes' do + presenter = presenter_class.new(project, user: 'Jane Doe') + + expect(presenter.user).to eq('Jane Doe') end end @@ -23,7 +28,7 @@ describe Gitlab::View::Presenter::Simple do it 'does not forward missing methods to subject' do presenter = presenter_class.new(project) - expect { presenter.foo }.to raise_error(NoMethodError) + expect { presenter.user }.to raise_error(NoMethodError) end end end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 4b1cd466677..7dd4d76d1a3 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Workhorse, lib: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } def decode_workhorse_header(array) diff --git a/spec/lib/light_url_builder_spec.rb b/spec/lib/light_url_builder_spec.rb index a826b24419a..3fe8cf43934 100644 --- a/spec/lib/light_url_builder_spec.rb +++ b/spec/lib/light_url_builder_spec.rb @@ -99,7 +99,7 @@ describe Gitlab::UrlBuilder, lib: true do context 'on another object' do it 'returns a proper URL' do - project = build_stubbed(:project) + project = build_stubbed(:empty_project) expect { described_class.build(project) }. to raise_error(NotImplementedError, 'No URL builder defined for Project') diff --git a/spec/lib/repository_cache_spec.rb b/spec/lib/repository_cache_spec.rb index f227926f39c..5892f3481a4 100644 --- a/spec/lib/repository_cache_spec.rb +++ b/spec/lib/repository_cache_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe RepositoryCache, lib: true do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:backend) { double('backend').as_null_object } let(:cache) { RepositoryCache.new('example', project.id, backend) } diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 1bdf005c823..2f4a33a1868 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Ability, lib: true do describe '.can_edit_note?' do let(:project) { create(:empty_project) } - let!(:note) { create(:note_on_issue, project: project) } + let(:note) { create(:note_on_issue, project: project) } context 'using an anonymous user' do it 'returns false' do @@ -60,7 +60,7 @@ describe Ability, lib: true do describe '.users_that_can_read_project' do context 'using a public project' do it 'returns all the users' do - project = create(:project, :public) + project = create(:empty_project, :public) user = build(:user) expect(described_class.users_that_can_read_project([user], project)). @@ -69,7 +69,7 @@ describe Ability, lib: true do end context 'using an internal project' do - let(:project) { create(:project, :internal) } + let(:project) { create(:empty_project, :internal) } it 'returns users that are administrators' do user = build(:user, admin: true) @@ -120,7 +120,7 @@ describe Ability, lib: true do end context 'using a private project' do - let(:project) { create(:project, :private) } + let(:project) { create(:empty_project, :private) } it 'returns users that are administrators' do user = build(:user, admin: true) @@ -171,6 +171,33 @@ describe Ability, lib: true do end end + describe '.users_that_can_read_personal_snippet' do + def users_for_snippet(snippet) + described_class.users_that_can_read_personal_snippet(users, snippet) + end + + let(:users) { create_list(:user, 3) } + let(:author) { users[0] } + + it 'private snippet is readable only by its author' do + snippet = create(:personal_snippet, :private, author: author) + + expect(users_for_snippet(snippet)).to match_array([author]) + end + + it 'internal snippet is readable by all registered users' do + snippet = create(:personal_snippet, :public, author: author) + + expect(users_for_snippet(snippet)).to match_array(users) + end + + it 'public snippet is readable by all users' do + snippet = create(:personal_snippet, :public, author: author) + + expect(users_for_snippet(snippet)).to match_array(users) + end + end + describe '.issues_readable_by_user' do context 'with an admin user' do it 'returns all given issues' do @@ -220,7 +247,7 @@ describe Ability, lib: true do end describe '.project_disabled_features_rules' do - let(:project) { create(:project, wiki_access_level: ProjectFeature::DISABLED) } + let(:project) { create(:empty_project, wiki_access_level: ProjectFeature::DISABLED) } subject { described_class.allowed(project.owner, project) } diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index f031876e812..47cd5075a7d 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Ci::Build, :models do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:build) { create(:ci_build, pipeline: pipeline) } let(:test_trace) { 'This is a test' } diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 2bdd611aeed..426be74cd02 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -284,7 +284,7 @@ describe Ci::Pipeline, models: true do end describe 'merge request metrics' do - let(:project) { FactoryGirl.create :project } + let(:project) { create(:project, :repository) } let(:pipeline) { FactoryGirl.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) } let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) } @@ -339,7 +339,7 @@ describe Ci::Pipeline, models: true do end context 'with non-empty project' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) do create(:ci_pipeline, @@ -890,7 +890,7 @@ describe Ci::Pipeline, models: true do end describe "#merge_requests" do - let(:project) { FactoryGirl.create :project } + let(:project) { create(:project, :repository) } let(:pipeline) { FactoryGirl.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) } it "returns merge requests whose `diff_head_sha` matches the pipeline's SHA" do @@ -956,7 +956,7 @@ describe Ci::Pipeline, models: true do end describe 'notifications when pipeline success or failed' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) do create(:ci_pipeline, diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 2b856ca7af7..3f32248e52b 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -91,8 +91,7 @@ describe Ci::Runner, models: true do end describe '#can_pick?' do - let(:project) { create(:project) } - let(:pipeline) { create(:ci_pipeline, project: project) } + let(:pipeline) { create(:ci_pipeline) } let(:build) { create(:ci_build, pipeline: pipeline) } let(:runner) { create(:ci_runner) } @@ -321,8 +320,8 @@ describe Ci::Runner, models: true do describe '.assignable_for' do let(:runner) { create(:ci_runner) } - let(:project) { create(:project) } - let(:another_project) { create(:project) } + let(:project) { create(:empty_project) } + let(:another_project) { create(:empty_project) } before do project.runners << runner diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb index 30782ca75a0..e4bddf67096 100644 --- a/spec/models/commit_range_spec.rb +++ b/spec/models/commit_range_spec.rb @@ -7,7 +7,7 @@ describe CommitRange, models: true do it { is_expected.to include_module(Referable) } end - let!(:project) { create(:project, :public) } + let!(:project) { create(:project, :public, :repository) } let!(:commit1) { project.commit("HEAD~2") } let!(:commit2) { project.commit } diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index b2202f0fd44..32f9366a14c 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Commit, models: true do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:commit) { project.commit } describe 'modules' do @@ -34,7 +34,7 @@ describe Commit, models: true do end describe '#to_reference' do - let(:project) { create(:project, path: 'sample-project') } + let(:project) { create(:project, :repository, path: 'sample-project') } let(:commit) { project.commit } it 'returns a String reference to the object' do @@ -42,13 +42,13 @@ describe Commit, models: true do end it 'supports a cross-project reference' do - another_project = build(:project, name: 'another-project', namespace: project.namespace) + another_project = build(:project, :repository, name: 'another-project', namespace: project.namespace) expect(commit.to_reference(another_project)).to eq "sample-project@#{commit.id}" end end describe '#reference_link_text' do - let(:project) { create(:project, path: 'sample-project') } + let(:project) { create(:project, :repository, path: 'sample-project') } let(:commit) { project.commit } it 'returns a String reference to the object' do @@ -56,7 +56,7 @@ describe Commit, models: true do end it 'supports a cross-project reference' do - another_project = build(:project, name: 'another-project', namespace: project.namespace) + another_project = build(:project, :repository, name: 'another-project', namespace: project.namespace) expect(commit.reference_link_text(another_project)).to eq "sample-project@#{commit.short_id}" end end @@ -131,7 +131,7 @@ eos describe '#closes_issues' do let(:issue) { create :issue, project: project } - let(:other_project) { create :project, :public } + let(:other_project) { create(:empty_project, :public) } let(:other_issue) { create :issue, project: other_project } let(:commiter) { create :user } @@ -154,7 +154,7 @@ eos end it_behaves_like 'a mentionable' do - subject { create(:project).commit } + subject { create(:project, :repository).commit } let(:author) { create(:user, email: subject.author_email) } let(:backref_text) { "commit #{subject.id}" } diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 64ea607eb95..bf4394f7d5b 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe CommitStatus, models: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) do create(:ci_pipeline, project: project, sha: project.commit.id) diff --git a/spec/models/compare_spec.rb b/spec/models/compare_spec.rb index 49ab3c4b6e9..da003dbf794 100644 --- a/spec/models/compare_spec.rb +++ b/spec/models/compare_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Compare, models: true do include RepoHelpers - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:commit) { project.commit } let(:start_commit) { sample_image_commit } diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index d7d31892e12..545a11912e3 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -301,7 +301,7 @@ describe Issue, "Issuable" do end describe '#labels_array' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:bug) { create(:label, project: project, title: 'bug') } let(:issue) { create(:issue, project: project) } @@ -315,7 +315,7 @@ describe Issue, "Issuable" do end describe '#user_notes_count' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:issue1) { create(:issue, project: project) } let(:issue2) { create(:issue, project: project) } @@ -359,7 +359,7 @@ describe Issue, "Issuable" do end describe ".with_label" do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:bug) { create(:label, project: project, title: 'bug') } let(:feature) { create(:label, project: project, title: 'feature') } let(:enhancement) { create(:label, project: project, title: 'enhancement') } diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index 132858950d5..2092576e981 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -13,7 +13,7 @@ describe Mentionable do end describe 'references' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:mentionable) { Example.new } it 'excludes JIRA references' do @@ -30,12 +30,20 @@ describe Issue, "Mentionable" do describe '#mentioned_users' do let!(:user) { create(:user, username: 'stranger') } let!(:user2) { create(:user, username: 'john') } - let!(:issue) { create(:issue, description: "#{user.to_reference} mentioned") } + let!(:user3) { create(:user, username: 'jim') } + let(:issue) { create(:issue, description: "#{user.to_reference} mentioned") } subject { issue.mentioned_users } - it { is_expected.to include(user) } - it { is_expected.not_to include(user2) } + it { expect(subject).to contain_exactly(user) } + + context 'when a note on personal snippet' do + let!(:note) { create(:note_on_personal_snippet, note: "#{user.to_reference} mentioned #{user3.to_reference}") } + + subject { note.mentioned_users } + + it { expect(subject).to contain_exactly(user, user3) } + end end describe '#referenced_mentionables' do @@ -75,13 +83,13 @@ describe Issue, "Mentionable" do end describe '#create_cross_references!' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:author) { build(:user) } let(:commit) { project.commit } let(:commit2) { project.commit } let!(:issue) do - create(:issue, project: project, description: commit.to_reference) + create(:issue, project: project, description: "See #{commit.to_reference}") end it 'correctly removes already-mentioned Commits' do @@ -92,7 +100,7 @@ describe Issue, "Mentionable" do end describe '#create_new_cross_references!' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:author) { create(:author) } let(:issues) { create_list(:issue, 2, project: project, author: author) } @@ -138,6 +146,16 @@ describe Issue, "Mentionable" do issue.update_attributes(description: issues[1].to_reference) issue.create_new_cross_references! end + + it 'notifies new references from project snippet note' do + snippet = create(:snippet, project: project) + note = create(:note, note: issues[0].to_reference, noteable: snippet, project: project, author: author) + + expect(SystemNoteService).to receive(:cross_reference).with(issues[1], any_args) + + note.update_attributes(note: issues[1].to_reference) + note.create_new_cross_references! + end end def create_issue(description:) diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb index 0e097559b59..ad703a6c8bb 100644 --- a/spec/models/concerns/milestoneish_spec.rb +++ b/spec/models/concerns/milestoneish_spec.rb @@ -7,7 +7,7 @@ describe Milestone, 'Milestoneish' do let(:member) { create(:user) } let(:guest) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:milestone) { create(:milestone, project: project) } let!(:issue) { create(:issue, project: project, milestone: milestone) } let!(:security_issue_1) { create(:issue, :confidential, project: project, author: author, milestone: milestone) } diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb index 9041690023f..6cf5877424d 100644 --- a/spec/models/concerns/project_features_compatibility_spec.rb +++ b/spec/models/concerns/project_features_compatibility_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe ProjectFeaturesCompatibility do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:features) { %w(issues wiki builds merge_requests snippets) } # We had issues_enabled, snippets_enabled, builds_enabled, merge_requests_enabled and issues_enabled fields on projects table diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb index b556135532f..30443534cca 100644 --- a/spec/models/concerns/routable_spec.rb +++ b/spec/models/concerns/routable_spec.rb @@ -68,4 +68,14 @@ describe Group, 'Routable' do end end end + + describe '.member_descendants' do + let!(:user) { create(:user) } + let!(:nested_group) { create(:group, parent: group) } + + before { group.add_owner(user) } + subject { described_class.member_descendants(user.id) } + + it { is_expected.to eq([nested_group]) } + end end diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb index 70f985afefb..3b7cc7d9e2e 100644 --- a/spec/models/cycle_analytics/code_spec.rb +++ b/spec/models/cycle_analytics/code_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'CycleAnalytics#code', feature: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb index e4b6a8f4518..5c73edbbc53 100644 --- a/spec/models/cycle_analytics/issue_spec.rb +++ b/spec/models/cycle_analytics/issue_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'CycleAnalytics#issue', models: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb index dc5b04852d6..55483fc876a 100644 --- a/spec/models/cycle_analytics/plan_spec.rb +++ b/spec/models/cycle_analytics/plan_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'CycleAnalytics#plan', feature: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb index 5e99188f318..591bbdddf55 100644 --- a/spec/models/cycle_analytics/production_spec.rb +++ b/spec/models/cycle_analytics/production_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'CycleAnalytics#production', feature: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb index 45baa5f7006..33d2c0a7416 100644 --- a/spec/models/cycle_analytics/review_spec.rb +++ b/spec/models/cycle_analytics/review_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'CycleAnalytics#review', feature: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb index 77625aad580..00693d67475 100644 --- a/spec/models/cycle_analytics/staging_spec.rb +++ b/spec/models/cycle_analytics/staging_spec.rb @@ -3,9 +3,10 @@ require 'spec_helper' describe 'CycleAnalytics#staging', feature: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } + subject { CycleAnalytics.new(project, from: from_date) } generate_cycle_analytics_spec( diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb index 27a117d2d76..f857ea6cbec 100644 --- a/spec/models/cycle_analytics/test_spec.rb +++ b/spec/models/cycle_analytics/test_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'CycleAnalytics#test', feature: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb index 8a1e337c1a3..aacc178a19e 100644 --- a/spec/models/deploy_keys_project_spec.rb +++ b/spec/models/deploy_keys_project_spec.rb @@ -12,7 +12,7 @@ describe DeployKeysProject, models: true do end describe "Destroying" do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } subject { create(:deploy_keys_project, project: project) } let(:deploy_key) { subject.deploy_key } @@ -39,7 +39,7 @@ describe DeployKeysProject, models: true do end context "when the deploy key is used by more than one project" do - let!(:other_project) { create(:project) } + let!(:other_project) { create(:empty_project) } before do other_project.deploy_keys << deploy_key diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index ca594a320c0..fc4435a2f64 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -17,7 +17,7 @@ describe Deployment, models: true do it { is_expected.to validate_presence_of(:sha) } describe '#includes_commit?' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:environment) { create(:environment, project: project) } let(:deployment) do create(:deployment, environment: environment, sha: project.commit.id) diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index 3db5937a4f3..9ea3a4b7020 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe DiffNote, models: true do include RepoHelpers - let(:project) { create(:project) } - let(:merge_request) { create(:merge_request, source_project: project) } + let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.project } let(:commit) { project.commit(sample_commit.id) } let(:path) { "files/ruby/popen.rb" } diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 96efe1696c3..b2e06541e66 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -32,7 +32,7 @@ describe Environment, models: true do end describe '#includes_commit?' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } context 'without a last deployment' do it "returns false" do @@ -81,7 +81,7 @@ describe Environment, models: true do end describe '#first_deployment_for' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let!(:deployment) { create(:deployment, environment: environment, ref: commit.parent.id) } let!(:deployment1) { create(:deployment, environment: environment, ref: commit.id) } let(:head_commit) { project.commit } diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index f8660da031d..349474bb656 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -27,7 +27,7 @@ describe Event, models: true do end describe "Push event" do - let(:project) { create(:project, :private) } + let(:project) { create(:empty_project, :private) } let(:user) { project.owner } let(:event) { create_event(project, user) } @@ -187,7 +187,7 @@ describe Event, models: true do end context 'merge request diff note event' do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:merge_request) { create(:merge_request, source_project: project, author: author, assignee: assignee) } let(:note_on_merge_request) { create(:legacy_diff_note_on_merge_request, noteable: merge_request, project: project) } let(:target) { note_on_merge_request } @@ -202,7 +202,7 @@ describe Event, models: true do end context 'private project' do - let(:project) { create(:project, :private) } + let(:project) { create(:empty_project, :private) } it do expect(event.visible_to_user?(non_member)).to eq false diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb index 1863581f57b..454550c9710 100644 --- a/spec/models/forked_project_link_spec.rb +++ b/spec/models/forked_project_link_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe ForkedProjectLink, "add link on fork" do - let(:project_from) { create(:project) } + let(:project_from) { create(:project, :repository) } let(:namespace) { create(:namespace) } let(:user) { create(:user, namespace: namespace) } @@ -21,7 +21,7 @@ end describe '#forked?' do let(:forked_project_link) { build(:forked_project_link) } - let(:project_from) { create(:project) } + let(:project_from) { create(:project, :repository) } let(:project_to) { create(:project, forked_project_link: forked_project_link) } before :each do diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb index d87684fd49e..cacbab8bcb1 100644 --- a/spec/models/global_milestone_spec.rb +++ b/spec/models/global_milestone_spec.rb @@ -4,9 +4,9 @@ describe GlobalMilestone, models: true do let(:user) { create(:user) } let(:user2) { create(:user) } let(:group) { create(:group) } - let(:project1) { create(:project, group: group) } - let(:project2) { create(:project, path: 'gitlab-ci', group: group) } - let(:project3) { create(:project, path: 'cookbook-gitlab', group: group) } + let(:project1) { create(:empty_project, group: group) } + let(:project2) { create(:empty_project, path: 'gitlab-ci', group: group) } + let(:project3) { create(:empty_project, path: 'cookbook-gitlab', group: group) } describe '.build_collection' do let(:milestone1_due_date) { 2.weeks.from_now.to_date } diff --git a/spec/models/group_milestone_spec.rb b/spec/models/group_milestone_spec.rb index 601167c3bd3..916afb7aaf5 100644 --- a/spec/models/group_milestone_spec.rb +++ b/spec/models/group_milestone_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe GroupMilestone, models: true do let(:group) { create(:group) } - let(:project) { create(:project, group: group) } + let(:project) { create(:empty_project, group: group) } let(:project_milestone) do create(:milestone, title: "Milestone v1.2", project: project) end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 45fe927202b..9ca50555191 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -81,13 +81,19 @@ describe Group, models: true do describe 'public_only' do subject { described_class.public_only.to_a } - it{ is_expected.to eq([group]) } + it { is_expected.to eq([group]) } end describe 'public_and_internal_only' do subject { described_class.public_and_internal_only.to_a } - it{ is_expected.to match_array([group, internal_group]) } + it { is_expected.to match_array([group, internal_group]) } + end + + describe 'non_public_only' do + subject { described_class.non_public_only.to_a } + + it { is_expected.to match_array([private_group, internal_group]) } end end @@ -269,6 +275,12 @@ describe Group, models: true do it 'returns the canonical URL' do expect(group.web_url).to include("groups/#{group.name}") end + + context 'nested group' do + let(:nested_group) { create(:group, :nested) } + + it { expect(nested_group.web_url).to include("groups/#{nested_group.full_path}") } + end end describe 'nested group' do diff --git a/spec/models/guest_spec.rb b/spec/models/guest_spec.rb index d79f929f7a1..582b54c0712 100644 --- a/spec/models/guest_spec.rb +++ b/spec/models/guest_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' describe Guest, lib: true do - let(:public_project) { create(:project, :public) } - let(:private_project) { create(:project, :private) } - let(:internal_project) { create(:project, :internal) } + let(:public_project) { build_stubbed(:empty_project, :public) } + let(:private_project) { build_stubbed(:empty_project, :private) } + let(:internal_project) { build_stubbed(:empty_project, :internal) } describe '.can_pull?' do context 'when project is private' do diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index ad2b710041a..e8caad00c44 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -4,7 +4,7 @@ describe SystemHook, models: true do describe "execute" do let(:system_hook) { create(:system_hook) } let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { create(:empty_project, namespace: user.namespace) } let(:group) { create(:group) } before do diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index e52b9d75cef..9d4db1bfb52 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -25,7 +25,7 @@ describe WebHook, models: true do end describe "execute" do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:project_hook) { create(:project_hook) } before(:each) do diff --git a/spec/models/issue/metrics_spec.rb b/spec/models/issue/metrics_spec.rb index 2459a49f095..08712f2a768 100644 --- a/spec/models/issue/metrics_spec.rb +++ b/spec/models/issue/metrics_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Issue::Metrics, models: true do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } subject { create(:issue, project: project) } diff --git a/spec/models/issue_collection_spec.rb b/spec/models/issue_collection_spec.rb index d742c814680..d8aed25c041 100644 --- a/spec/models/issue_collection_spec.rb +++ b/spec/models/issue_collection_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe IssueCollection do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:issue1) { create(:issue, project: project) } let(:issue2) { create(:issue, project: project) } let(:collection) { described_class.new([issue1, issue2]) } diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index bfa36d92ac3..40c0a75c364 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -35,7 +35,7 @@ describe Issue, models: true do end it 'supports a cross-project reference' do - another_project = build(:project, name: 'another-project', namespace: project.namespace) + another_project = build(:empty_project, name: 'another-project', namespace: project.namespace) expect(issue.to_reference(another_project)).to eq "sample-project#1" end end @@ -60,9 +60,9 @@ describe Issue, models: true do end describe '#closed_by_merge_requests' do - let(:project) { create(:project) } - let(:issue) { create(:issue, project: project, state: "opened")} - let(:closed_issue) { build(:issue, project: project, state: "closed")} + let(:project) { create(:project, :repository) } + let(:issue) { create(:issue, project: project)} + let(:closed_issue) { build(:issue, :closed, project: project)} let(:mr) do opts = { @@ -104,7 +104,7 @@ describe Issue, models: true do describe '#referenced_merge_requests' do it 'returns the referenced merge requests' do - project = create(:project, :public) + project = create(:empty_project, :public) mr1 = create(:merge_request, source_project: project, @@ -137,7 +137,7 @@ describe Issue, models: true do end context 'user is reporter in project issue belongs to' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:issue) { create(:issue, project: project) } before { project.team << [user, :reporter] } @@ -151,7 +151,7 @@ describe Issue, models: true do context 'checking destination project also' do subject { issue.can_move?(user, to_project) } - let(:to_project) { create(:project) } + let(:to_project) { create(:empty_project) } context 'destination project allowed' do before { to_project.team << [user, :reporter] } @@ -217,7 +217,7 @@ describe Issue, models: true do end it_behaves_like 'an editable mentionable' do - subject { create(:issue) } + subject { create(:issue, project: create(:project, :repository)) } let(:backref_text) { "issue #{subject.to_reference}" } let(:set_mentionable_text) { ->(txt){ subject.description = txt } } @@ -246,7 +246,7 @@ describe Issue, models: true do describe '#participants' do context 'using a public project' do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:issue) { create(:issue, project: project) } let!(:note1) do @@ -268,7 +268,7 @@ describe Issue, models: true do context 'using a private project' do it 'does not include mentioned users that do not have access to the project' do - project = create(:project) + project = create(:empty_project) user = create(:user) issue = create(:issue, project: project) diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 4f7c8a36cb5..16e2144d6a1 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -481,7 +481,7 @@ describe Member, models: true do describe "destroying a record", truncate: true do it "refreshes user's authorized projects" do - project = create(:project, :private) + project = create(:empty_project, :private) user = create(:user) member = project.team << [user, :reporter] diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index 68f72f5c86e..90d14c2c0b9 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -83,8 +83,8 @@ describe ProjectMember, models: true do describe '.import_team' do before do - @project_1 = create :project - @project_2 = create :project + @project_1 = create(:empty_project) + @project_2 = create(:empty_project) @user_1 = create :user @user_2 = create :user @@ -131,8 +131,8 @@ describe ProjectMember, models: true do describe '.truncate_teams' do before do - @project_1 = create :project - @project_2 = create :project + @project_1 = create(:empty_project) + @project_2 = create(:empty_project) @user_1 = create :user @user_2 = create :user diff --git a/spec/models/merge_request/metrics_spec.rb b/spec/models/merge_request/metrics_spec.rb index 255db41cb19..9afed311e27 100644 --- a/spec/models/merge_request/metrics_spec.rb +++ b/spec/models/merge_request/metrics_spec.rb @@ -1,9 +1,7 @@ require 'spec_helper' describe MergeRequest::Metrics, models: true do - let(:project) { create(:project) } - - subject { create(:merge_request, source_project: project) } + subject { create(:merge_request) } describe "when recording the default set of metrics on merge request save" do it "records the merge time" do diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 064f29d2d66..3cee2b7714f 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -24,7 +24,7 @@ describe Milestone, models: true do it { is_expected.to have_many(:issues) } end - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:milestone) { create(:milestone, project: project) } let(:issue) { create(:issue, project: project) } let(:user) { create(:user) } @@ -44,7 +44,7 @@ describe Milestone, models: true do end it "accepts the same title in another project" do - project = build(:project) + project = build(:empty_project) new_milestone = Milestone.new(project: project, title: milestone.title) expect(new_milestone).to be_valid @@ -257,7 +257,7 @@ describe Milestone, models: true do end it 'supports a cross-project reference' do - another_project = build(:project, name: 'another-project', namespace: project.namespace) + another_project = build(:empty_project, name: 'another-project', namespace: project.namespace) expect(milestone.to_reference(another_project)).to eq "sample-project%1" end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index f8e03fa114a..4e96f19eb6f 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -5,6 +5,8 @@ describe Namespace, models: true do it { is_expected.to have_many :projects } it { is_expected.to have_many :project_statistics } + it { is_expected.to belong_to :parent } + it { is_expected.to have_many :children } it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) } @@ -105,7 +107,7 @@ describe Namespace, models: true do describe '#move_dir' do before do @namespace = create :namespace - @project = create :project, namespace: @namespace + @project = create(:empty_project, namespace: @namespace) allow(@namespace).to receive(:path_changed?).and_return(true) end @@ -137,7 +139,7 @@ describe Namespace, models: true do end describe :rm_dir do - let!(:project) { create(:project, namespace: namespace) } + let!(:project) { create(:empty_project, namespace: namespace) } let!(:path) { File.join(Gitlab.config.repositories.storages.default, namespace.path) } it "removes its dirs when deleted" do @@ -189,17 +191,31 @@ describe Namespace, models: true do it { expect(nested_group.full_name).to eq("#{group.name} / #{nested_group.name}") } end - describe '#parents' do + describe '#ancestors' do let(:group) { create(:group) } let(:nested_group) { create(:group, parent: group) } let(:deep_nested_group) { create(:group, parent: nested_group) } let(:very_deep_nested_group) { create(:group, parent: deep_nested_group) } - it 'returns the correct parents' do - expect(very_deep_nested_group.parents).to eq([group, nested_group, deep_nested_group]) - expect(deep_nested_group.parents).to eq([group, nested_group]) - expect(nested_group.parents).to eq([group]) - expect(group.parents).to eq([]) + it 'returns the correct ancestors' do + expect(very_deep_nested_group.ancestors).to eq([group, nested_group, deep_nested_group]) + expect(deep_nested_group.ancestors).to eq([group, nested_group]) + expect(nested_group.ancestors).to eq([group]) + expect(group.ancestors).to eq([]) + end + end + + describe '#descendants' do + let!(:group) { create(:group) } + let!(:nested_group) { create(:group, parent: group) } + let!(:deep_nested_group) { create(:group, parent: nested_group) } + let!(:very_deep_nested_group) { create(:group, parent: deep_nested_group) } + + it 'returns the correct descendants' do + expect(very_deep_nested_group.descendants.to_a).to eq([]) + expect(deep_nested_group.descendants.to_a).to eq([very_deep_nested_group]) + expect(nested_group.descendants.to_a).to eq([deep_nested_group, very_deep_nested_group]) + expect(group.descendants.to_a).to eq([nested_group, deep_nested_group, very_deep_nested_group]) end end end diff --git a/spec/models/network/graph_spec.rb b/spec/models/network/graph_spec.rb index b76513d2a3c..492c4e01bd8 100644 --- a/spec/models/network/graph_spec.rb +++ b/spec/models/network/graph_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Network::Graph, models: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let!(:note_on_commit) { create(:note_on_commit, project: project) } it '#initialize' do diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 310fecd8a5c..1cde9e04951 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -42,7 +42,7 @@ describe Note, models: true do context 'when noteable and note project differ' do subject do build(:note, noteable: build_stubbed(:issue), - project: build_stubbed(:project)) + project: build_stubbed(:empty_project)) end it { is_expected.to be_invalid } @@ -52,6 +52,19 @@ describe Note, models: true do subject { create(:note) } it { is_expected.to be_valid } end + + context 'when project is missing for a project related note' do + subject { build(:note, project: nil, noteable: build_stubbed(:issue)) } + it { is_expected.to be_invalid } + end + + context 'when noteable is a personal snippet' do + subject { build(:note_on_personal_snippet) } + + it 'is valid without project' do + is_expected.to be_valid + end + end end describe "Commit notes" do @@ -80,8 +93,8 @@ describe Note, models: true do describe 'authorization' do before do - @p1 = create(:project) - @p2 = create(:project) + @p1 = create(:empty_project) + @p2 = create(:empty_project) @u1 = create(:user) @u2 = create(:user) @u3 = create(:user) @@ -125,7 +138,7 @@ describe Note, models: true do it_behaves_like 'an editable mentionable' do subject { create :note, noteable: issue, project: issue.project } - let(:issue) { create :issue } + let(:issue) { create(:issue, project: create(:project, :repository)) } let(:backref_text) { issue.gfm_reference } let(:set_mentionable_text) { ->(txt) { subject.note = txt } } end @@ -139,6 +152,7 @@ describe Note, models: true do with([{ text: note1.note, context: { + skip_project_check: false, pipeline: :note, cache_key: [note1, "note"], project: note1.project, @@ -150,6 +164,7 @@ describe Note, models: true do with([{ text: note2.note, context: { + skip_project_check: false, pipeline: :note, cache_key: [note2, "note"], project: note2.project, @@ -176,10 +191,10 @@ describe Note, models: true do describe "cross_reference_not_visible_for?" do let(:private_user) { create(:user) } - let(:private_project) { create(:project, namespace: private_user.namespace).tap { |p| p.team << [private_user, :master] } } + let(:private_project) { create(:empty_project, namespace: private_user.namespace) { |p| p.team << [private_user, :master] } } let(:private_issue) { create(:issue, project: private_project) } - let(:ext_proj) { create(:project, :public) } + let(:ext_proj) { create(:empty_project, :public) } let(:ext_issue) { create(:issue, project: ext_proj) } let(:note) do @@ -222,7 +237,7 @@ describe Note, models: true do describe '#participants' do it 'includes the note author' do - project = create(:project, :public) + project = create(:empty_project, :public) issue = create(:issue, project: project) note = create(:note_on_issue, noteable: issue, project: project) @@ -306,4 +321,70 @@ describe Note, models: true do end end end + + describe '#for_personal_snippet?' do + it 'returns false for a project snippet note' do + expect(build(:note_on_project_snippet).for_personal_snippet?).to be_falsy + end + + it 'returns true for a personal snippet note' do + expect(build(:note_on_personal_snippet).for_personal_snippet?).to be_truthy + end + end + + describe '#to_ability_name' do + it 'returns snippet for a project snippet note' do + expect(build(:note_on_project_snippet).to_ability_name).to eq('snippet') + end + + it 'returns personal_snippet for a personal snippet note' do + expect(build(:note_on_personal_snippet).to_ability_name).to eq('personal_snippet') + end + + it 'returns merge_request for an MR note' do + expect(build(:note_on_merge_request).to_ability_name).to eq('merge_request') + end + + it 'returns issue for an issue note' do + expect(build(:note_on_issue).to_ability_name).to eq('issue') + end + + it 'returns issue for a commit note' do + expect(build(:note_on_commit).to_ability_name).to eq('commit') + end + end + + describe '#cache_markdown_field' do + let(:html) { '<p>some html</p>'} + + context 'note for a project snippet' do + let(:note) { build(:note_on_project_snippet) } + + before do + expect(Banzai::Renderer).to receive(:cacheless_render_field). + with(note, :note, { skip_project_check: false }).and_return(html) + + note.save + end + + it 'creates a note' do + expect(note.note_html).to eq(html) + end + end + + context 'note for a personal snippet' do + let(:note) { build(:note_on_personal_snippet) } + + before do + expect(Banzai::Renderer).to receive(:cacheless_render_field). + with(note, :note, { skip_project_check: true }).and_return(html) + + note.save + end + + it 'creates a note' do + expect(note.note_html).to eq(html) + end + end + end end diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb index a55d43ab2f9..8589f1eb712 100644 --- a/spec/models/project_feature_spec.rb +++ b/spec/models/project_feature_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe ProjectFeature do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:user) { create(:user) } describe '#feature_available?' do @@ -35,7 +35,7 @@ describe ProjectFeature do it "returns true when user is a member of project group" do group = create(:group) - project = create(:project, namespace: group) + project = create(:empty_project, namespace: group) group.add_developer(user) features.each do |feature| diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb index 47397a822c1..59a4ae1b799 100644 --- a/spec/models/project_group_link_spec.rb +++ b/spec/models/project_group_link_spec.rb @@ -17,7 +17,7 @@ describe ProjectGroupLink do describe "destroying a record", truncate: true do it "refreshes group users' authorized projects" do - project = create(:project, :private) + project = create(:empty_project, :private) group = create(:group) reporter = create(:user) group_users = group.users diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index 4d538cac007..9cdbfa44e5b 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -100,7 +100,7 @@ describe ProjectLabel, models: true do end context 'cross project reference' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'using name' do it 'returns cross reference with label name' do diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb index 8e5145e824b..48aef3a93f2 100644 --- a/spec/models/project_services/asana_service_spec.rb +++ b/spec/models/project_services/asana_service_spec.rb @@ -18,7 +18,7 @@ describe AsanaService, models: true do describe 'Execute' do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } def create_data_for_commits(*messages) { diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb index 4c5acb7990b..96f00af898e 100644 --- a/spec/models/project_services/assembla_service_spec.rb +++ b/spec/models/project_services/assembla_service_spec.rb @@ -8,7 +8,7 @@ describe AssemblaService, models: true do describe "Execute" do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do @assembla_service = AssemblaService.new diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb index a3b9d084a75..953e664fb66 100644 --- a/spec/models/project_services/campfire_service_spec.rb +++ b/spec/models/project_services/campfire_service_spec.rb @@ -22,7 +22,7 @@ describe CampfireService, models: true do describe "#execute" do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do @campfire_service = CampfireService.new diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb index 42c2ed668bc..f9307d6de7b 100644 --- a/spec/models/project_services/drone_ci_service_spec.rb +++ b/spec/models/project_services/drone_ci_service_spec.rb @@ -27,7 +27,7 @@ describe DroneCiService, models: true, caching: true do shared_context :drone_ci_service do let(:drone) { DroneCiService.new } - let(:project) { create(:project, name: 'project') } + let(:project) { create(:project, :repository, name: 'project') } let(:path) { "#{project.namespace.path}/#{project.path}" } let(:drone_url) { 'http://drone.example.com' } let(:sha) { '2ab7834c' } diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb index 342d86aeca9..bdeea1db1e3 100644 --- a/spec/models/project_services/external_wiki_service_spec.rb +++ b/spec/models/project_services/external_wiki_service_spec.rb @@ -23,7 +23,7 @@ describe ExternalWikiService, models: true do end describe 'External wiki' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'when it is active' do before do diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb index d6db02d6e76..a97e8c6e4ce 100644 --- a/spec/models/project_services/flowdock_service_spec.rb +++ b/spec/models/project_services/flowdock_service_spec.rb @@ -22,7 +22,7 @@ describe FlowdockService, models: true do describe "Execute" do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do @flowdock_service = FlowdockService.new diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb index 529044d1d8b..a13fbae03eb 100644 --- a/spec/models/project_services/gemnasium_service_spec.rb +++ b/spec/models/project_services/gemnasium_service_spec.rb @@ -24,7 +24,7 @@ describe GemnasiumService, models: true do describe "Execute" do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do @gemnasium_service = GemnasiumService.new diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb index 9b80f0e7296..dcb70ee28a8 100644 --- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb +++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb @@ -8,21 +8,21 @@ describe GitlabIssueTrackerService, models: true do describe 'Validations' do context 'when service is active' do - subject { described_class.new(project: create(:project), active: true) } + subject { described_class.new(project: create(:empty_project), active: true) } it { is_expected.to validate_presence_of(:issues_url) } it_behaves_like 'issue tracker service URL attribute', :issues_url end context 'when service is inactive' do - subject { described_class.new(project: create(:project), active: false) } + subject { described_class.new(project: create(:empty_project), active: false) } it { is_expected.not_to validate_presence_of(:issues_url) } end end describe 'project and issue urls' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'with absolute urls' do before do diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index 2da3a9cb09f..bf422ac7ce1 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -22,8 +22,8 @@ describe HipchatService, models: true do describe "Execute" do let(:hipchat) { HipchatService.new } - let(:user) { create(:user, username: 'username') } - let(:project) { create(:project, name: 'project') } + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } let(:api_url) { 'https://hipchat.example.com/v2/room/123456/notification?auth_token=verySecret' } let(:project_name) { project.name_with_namespace.gsub(/\s/, '') } let(:token) { 'verySecret' } @@ -165,7 +165,7 @@ describe HipchatService, models: true do context "Note events" do let(:user) { create(:user) } - let(:project) { create(:project, creator_id: user.id) } + let(:project) { create(:project, :repository, creator: user) } context 'when commit comment event triggered' do let(:commit_note) do diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb index f8c45b37561..b9fb6f3f6f4 100644 --- a/spec/models/project_services/irker_service_spec.rb +++ b/spec/models/project_services/irker_service_spec.rb @@ -25,7 +25,7 @@ describe IrkerService, models: true do describe 'Execute' do let(:irker) { IrkerService.new } let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:sample_data) do Gitlab::DataBuilder::Push.build_sample(project, user) end diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index 862e3a72a73..2f6b159d76e 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -71,7 +71,7 @@ describe JiraService, models: true do describe '#close_issue' do let(:custom_base_url) { 'http://custom_url' } let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:merge_request) { create(:merge_request) } before do @@ -207,12 +207,12 @@ describe JiraService, models: true do end describe "Stored password invalidation" do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context "when a password was previously set" do before do @jira_service = JiraService.create!( - project: create(:project), + project: project, properties: { url: 'http://jira.example.com/rest/api/2', username: 'mic', @@ -252,7 +252,7 @@ describe JiraService, models: true do context "when no password was previously set" do before do @jira_service = JiraService.create( - project: create(:project), + project: project, properties: { url: 'http://jira.example.com/rest/api/2', username: 'mic' @@ -281,7 +281,7 @@ describe JiraService, models: true do end describe 'description and title' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'when it is not set' do before do @@ -316,7 +316,7 @@ describe JiraService, models: true do end describe 'project and issue urls' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'when gitlab.yml was initialized' do before do diff --git a/spec/models/project_services/pipeline_email_service_spec.rb b/spec/models/project_services/pipeline_email_service_spec.rb index 7c8824485f5..03932895b0e 100644 --- a/spec/models/project_services/pipeline_email_service_spec.rb +++ b/spec/models/project_services/pipeline_email_service_spec.rb @@ -7,7 +7,7 @@ describe PipelinesEmailService do create(:ci_pipeline, project: project, sha: project.commit('master').sha) end - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:recipient) { 'test@gitlab.com' } let(:data) do diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb index 8fc92a9ab51..a7e7594a7d5 100644 --- a/spec/models/project_services/pushover_service_spec.rb +++ b/spec/models/project_services/pushover_service_spec.rb @@ -27,7 +27,7 @@ describe PushoverService, models: true do describe 'Execute' do let(:pushover) { PushoverService.new } let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:sample_data) do Gitlab::DataBuilder::Push.build_sample(project, user) end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 8048e86fc3a..646a1311462 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -832,6 +832,26 @@ describe Project, models: true do it { expect(project.builds_enabled?).to be_truthy } end + describe '.with_shared_runners' do + subject { Project.with_shared_runners } + + context 'when shared runners are enabled for project' do + let!(:project) { create(:empty_project, shared_runners_enabled: true) } + + it "returns a project" do + is_expected.to eq([project]) + end + end + + context 'when shared runners are disabled for project' do + let!(:project) { create(:empty_project, shared_runners_enabled: false) } + + it "returns an empty array" do + is_expected.to be_empty + end + end + end + describe '.cached_count', caching: true do let(:group) { create(:group, :public) } let!(:project1) { create(:empty_project, :public, group: group) } @@ -974,6 +994,28 @@ describe Project, models: true do end end + describe '#shared_runners' do + let!(:runner) { create(:ci_runner, :shared) } + + subject { project.shared_runners } + + context 'when shared runners are enabled for project' do + let!(:project) { create(:empty_project, shared_runners_enabled: true) } + + it "returns a list of shared runners" do + is_expected.to eq([runner]) + end + end + + context 'when shared runners are disabled for project' do + let!(:project) { create(:empty_project, shared_runners_enabled: false) } + + it "returns a empty list" do + is_expected.to be_empty + end + end + end + describe '#visibility_level_allowed?' do let(:project) { create(:empty_project, :internal) } diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 0475cecaa2d..942eeab251d 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -265,10 +265,10 @@ describe ProjectTeam, models: true do let(:group) { create(:group) } let(:developer) { create(:user) } let(:master) { create(:user) } - let(:personal_project) { create(:project, namespace: developer.namespace) } - let(:group_project) { create(:project, namespace: group) } - let(:members_project) { create(:project) } - let(:shared_project) { create(:project) } + let(:personal_project) { create(:empty_project, namespace: developer.namespace) } + let(:group_project) { create(:empty_project, namespace: group) } + let(:members_project) { create(:empty_project) } + let(:shared_project) { create(:empty_project) } before do group.add_master(master) @@ -330,7 +330,7 @@ describe ProjectTeam, models: true do reporter = create(:user) promoted_guest = create(:user) guest = create(:user) - project = create(:project) + project = create(:empty_project) project.add_master(master) project.add_reporter(reporter) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 99ca53938c8..829b69093c9 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -4,7 +4,7 @@ describe Repository, models: true do include RepoHelpers TestBlob = Struct.new(:name) - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:user) { create(:user) } @@ -90,6 +90,30 @@ describe Repository, models: true do it { is_expected.to eq(['v1.1.0', 'v1.0.0']) } end + + context 'annotated tag pointing to a blob' do + let(:annotated_tag_name) { 'annotated-tag' } + + subject { repository.tags_sorted_by('updated_asc').map(&:name) } + + before do + options = { message: 'test tag message\n', + tagger: { name: 'John Smith', email: 'john@gmail.com' } } + repository.rugged.tags.create(annotated_tag_name, 'a48e4fc218069f68ef2e769dd8dfea3991362175', options) + + double_first = double(committed_date: Time.now - 1.second) + double_last = double(committed_date: Time.now) + + allow(tag_a).to receive(:dereferenced_target).and_return(double_last) + allow(tag_b).to receive(:dereferenced_target).and_return(double_first) + end + + it { is_expected.to eq(['v1.1.0', 'v1.0.0', annotated_tag_name]) } + + after do + repository.rugged.tags.delete(annotated_tag_name) + end + end end end diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb index 8481a9bef16..dd2a5109abc 100644 --- a/spec/models/route_spec.rb +++ b/spec/models/route_spec.rb @@ -14,7 +14,7 @@ describe Route, models: true do it { is_expected.to validate_uniqueness_of(:path) } end - describe '#rename_children' do + describe '#rename_descendants' do let!(:nested_group) { create(:group, path: "test", parent: group) } let!(:deep_nested_group) { create(:group, path: "foo", parent: nested_group) } let!(:similar_group) { create(:group, path: 'gitlab-org') } diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 691511cd93f..0e2f07e945f 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -12,7 +12,7 @@ describe Service, models: true do end describe "Testable" do - let(:project) { create :project } + let(:project) { create(:project, :repository) } before do allow(@service).to receive(:project).and_return(project) @@ -35,7 +35,7 @@ describe Service, models: true do end describe "With commits" do - let(:project) { create :project } + let(:project) { create(:project, :repository) } before do allow(@service).to receive(:project).and_return(project) @@ -60,7 +60,7 @@ describe Service, models: true do api_key: '123456789' }) end - let(:project) { create(:project) } + let(:project) { create(:empty_project) } describe 'is prefilled for projects pushover service' do it "has all fields prefilled" do @@ -79,7 +79,7 @@ describe Service, models: true do describe "{property}_changed?" do let(:service) do BambooService.create( - project: create(:project), + project: create(:empty_project), properties: { bamboo_url: 'http://gitlab.com', username: 'mic', @@ -119,7 +119,7 @@ describe Service, models: true do describe "{property}_touched?" do let(:service) do BambooService.create( - project: create(:project), + project: create(:empty_project), properties: { bamboo_url: 'http://gitlab.com', username: 'mic', @@ -159,7 +159,7 @@ describe Service, models: true do describe "{property}_was" do let(:service) do BambooService.create( - project: create(:project), + project: create(:empty_project), properties: { bamboo_url: 'http://gitlab.com', username: 'mic', @@ -199,7 +199,7 @@ describe Service, models: true do describe 'initialize service with no properties' do let(:service) do GitlabIssueTrackerService.create( - project: create(:project), + project: create(:empty_project), title: 'random title' ) end @@ -214,7 +214,7 @@ describe Service, models: true do end describe "callbacks" do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let!(:service) do RedmineService.new( project: project, diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 7425a897769..219ab1989ea 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -42,7 +42,7 @@ describe Snippet, models: true do end it 'supports a cross-project reference' do - another_project = build(:project, name: 'another-project', namespace: project.namespace) + another_project = build(:empty_project, name: 'another-project', namespace: project.namespace) expect(snippet.to_reference(another_project)).to eq "sample-project$1" end end @@ -55,7 +55,7 @@ describe Snippet, models: true do end it 'still returns shortest reference when project arg present' do - another_project = build(:project, name: 'another-project') + another_project = build(:empty_project, name: 'another-project') expect(snippet.to_reference(another_project)).to eq "$1" end end @@ -173,7 +173,7 @@ describe Snippet, models: true do end describe '#participants' do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:snippet) { create(:snippet, content: 'foo', project: project) } let!(:note1) do diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb index 623b82c01d8..8017d1c3324 100644 --- a/spec/models/todo_spec.rb +++ b/spec/models/todo_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe Todo, models: true do - let(:project) { create(:project) } - let(:commit) { project.commit } let(:issue) { create(:issue) } describe 'relationships' do @@ -82,6 +80,9 @@ describe Todo, models: true do describe '#target' do context 'for commits' do + let(:project) { create(:project, :repository) } + let(:commit) { project.commit } + it 'returns an instance of Commit when exists' do subject.project = project subject.target_type = 'Commit' @@ -109,6 +110,9 @@ describe Todo, models: true do describe '#target_reference' do it 'returns the short commit id for commits' do + project = create(:project, :repository) + commit = project.commit + subject.project = project subject.target_type = 'Commit' subject.commit_id = commit.id diff --git a/spec/models/tree_spec.rb b/spec/models/tree_spec.rb index 0737999e125..a87983b7492 100644 --- a/spec/models/tree_spec.rb +++ b/spec/models/tree_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Tree, models: true do - let(:repository) { create(:project).repository } + let(:repository) { create(:project, :repository).repository } let(:sha) { repository.root_ref } subject { described_class.new(repository, '54fcc214') } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index ca3d4ff0aa9..6ca5ad747d1 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -797,14 +797,14 @@ describe User, models: true do describe '#avatar_type' do let(:user) { create(:user) } - it "is true if avatar is image" do + it 'is true if avatar is image' do user.update_attribute(:avatar, 'uploads/avatar.png') expect(user.avatar_type).to be_truthy end - it "is false if avatar is html page" do + it 'is false if avatar is html page' do user.update_attribute(:avatar, 'uploads/avatar.html') - expect(user.avatar_type).to eq(["only images allowed"]) + expect(user.avatar_type).to eq(['only images allowed']) end end @@ -926,8 +926,8 @@ describe User, models: true do end end - describe "#starred?" do - it "determines if user starred a project" do + describe '#starred?' do + it 'determines if user starred a project' do user = create :user project1 = create(:empty_project, :public) project2 = create(:empty_project, :public) @@ -953,8 +953,8 @@ describe User, models: true do end end - describe "#toggle_star" do - it "toggles stars" do + describe '#toggle_star' do + it 'toggles stars' do user = create :user project = create(:empty_project, :public) @@ -966,31 +966,44 @@ describe User, models: true do end end - describe "#sort" do + describe '#sort' do before do User.delete_all @user = create :user, created_at: Date.today, last_sign_in_at: Date.today, name: 'Alpha' @user1 = create :user, created_at: Date.today - 1, last_sign_in_at: Date.today - 1, name: 'Omega' + @user2 = create :user, created_at: Date.today - 2, last_sign_in_at: nil, name: 'Beta' end - it "sorts users by the recent sign-in time" do - expect(User.sort('recent_sign_in').first).to eq(@user) + context 'when sort by recent_sign_in' do + it 'sorts users by the recent sign-in time' do + expect(User.sort('recent_sign_in').first).to eq(@user) + end + + it 'pushes users who never signed in to the end' do + expect(User.sort('recent_sign_in').third).to eq(@user2) + end end - it "sorts users by the oldest sign-in time" do - expect(User.sort('oldest_sign_in').first).to eq(@user1) + context 'when sort by oldest_sign_in' do + it 'sorts users by the oldest sign-in time' do + expect(User.sort('oldest_sign_in').first).to eq(@user1) + end + + it 'pushes users who never signed in to the end' do + expect(User.sort('oldest_sign_in').third).to eq(@user2) + end end - it "sorts users in descending order by their creation time" do + it 'sorts users in descending order by their creation time' do expect(User.sort('created_desc').first).to eq(@user) end - it "sorts users in ascending order by their creation time" do - expect(User.sort('created_asc').first).to eq(@user1) + it 'sorts users in ascending order by their creation time' do + expect(User.sort('created_asc').first).to eq(@user2) end - it "sorts users by id in descending order when nil is passed" do - expect(User.sort(nil).first).to eq(@user1) + it 'sorts users by id in descending order when nil is passed' do + expect(User.sort(nil).first).to eq(@user2) end end @@ -1350,6 +1363,39 @@ describe User, models: true do end end + describe '#nested_groups' do + let!(:user) { create(:user) } + let!(:group) { create(:group) } + let!(:nested_group) { create(:group, parent: group) } + + before do + group.add_owner(user) + + # Add more data to ensure method does not include wrong groups + create(:group).add_owner(create(:user)) + end + + it { expect(user.nested_groups).to eq([nested_group]) } + end + + describe '#nested_projects' do + let!(:user) { create(:user) } + let!(:group) { create(:group) } + let!(:nested_group) { create(:group, parent: group) } + let!(:project) { create(:empty_project, namespace: group) } + let!(:nested_project) { create(:empty_project, namespace: nested_group) } + + before do + group.add_owner(user) + + # Add more data to ensure method does not include wrong projects + other_project = create(:empty_project, namespace: create(:group, :nested)) + other_project.add_developer(create(:user)) + end + + it { expect(user.nested_projects).to eq([nested_project]) } + end + describe '#refresh_authorized_projects', redis: true do let(:project1) { create(:empty_project) } let(:project2) { create(:empty_project) } diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 2878e0cb59b..5a3ffc284f2 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -6,7 +6,7 @@ describe API::Branches, api: true do let(:user) { create(:user) } let(:user2) { create(:user) } - let!(:project) { create(:project, creator_id: user.id) } + let!(:project) { create(:project, :repository, creator: user) } let!(:master) { create(:project_member, :master, user: user, project: project) } let!(:guest) { create(:project_member, :guest, user: user2, project: project) } let!(:branch_name) { 'feature' } diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index 7be7acebb19..645e36683bc 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -5,7 +5,7 @@ describe API::Builds, api: true do let(:user) { create(:user) } let(:api_user) { user } - let!(:project) { create(:project, creator_id: user.id, public_builds: false) } + let!(:project) { create(:project, :repository, creator: user, public_builds: false) } let!(:developer) { create(:project_member, :developer, user: user, project: project) } let(:reporter) { create(:project_member, :reporter, project: project) } let(:guest) { create(:project_member, :guest, project: project) } diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index c1c7c0882de..88361def3cf 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe API::CommitStatuses, api: true do include ApiHelpers - let!(:project) { create(:project) } + let!(:project) { create(:project, :repository) } let(:commit) { project.repository.commit } let(:commit_status) { create(:commit_status, pipeline: pipeline) } let(:guest) { create_user(:guest) } diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 7f8ea5251f0..af9028a8978 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -5,7 +5,7 @@ describe API::Commits, api: true do include ApiHelpers let(:user) { create(:user) } let(:user2) { create(:user) } - let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } + let!(:project) { create(:project, :repository, creator: user, namespace: user.namespace) } let!(:master) { create(:project_member, :master, user: user, project: project) } let!(:guest) { create(:project_member, :guest, user: user2, project: project) } let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') } diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 685da28c673..5e26e779366 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe API::Files, api: true do include ApiHelpers let(:user) { create(:user) } - let!(:project) { create(:project, namespace: user.namespace ) } - let(:guest) { create(:user).tap { |u| create(:project_member, :guest, user: u, project: project) } } + let!(:project) { create(:project, :repository, namespace: user.namespace ) } + let(:guest) { create(:user) { |u| project.add_guest(u) } } let(:file_path) { 'files/ruby/popen.rb' } let(:params) do { diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb index df29099bc2f..92ac4fd334d 100644 --- a/spec/requests/api/fork_spec.rb +++ b/spec/requests/api/fork_spec.rb @@ -4,7 +4,6 @@ describe API::Projects, api: true do include ApiHelpers let(:user) { create(:user) } let(:user2) { create(:user) } - let(:user3) { create(:user) } let(:admin) { create(:admin) } let(:group) { create(:group) } let(:group2) do @@ -13,17 +12,14 @@ describe API::Projects, api: true do group end - let(:project) do - create(:project, creator_id: user.id, namespace: user.namespace) - end - - let(:project_user2) do - create(:project_member, :reporter, user: user2, project: project) - end - describe 'POST /projects/fork/:id' do - before { project_user2 } - before { user3 } + let(:project) do + create(:project, :repository, creator: user, namespace: user.namespace) + end + + before do + project.add_reporter(user2) + end context 'when authenticated' do it 'forks if user has sufficient access to project' do @@ -49,7 +45,8 @@ describe API::Projects, api: true do end it 'fails on missing project access for the project to fork' do - post api("/projects/fork/#{project.id}", user3) + new_user = create(:user) + post api("/projects/fork/#{project.id}", new_user) expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index a3798c8cd6c..ffeacb15f17 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -4,7 +4,7 @@ describe API::Internal, api: true do include ApiHelpers let(:user) { create(:user) } let(:key) { create(:key, user: user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:secret_token) { Gitlab::Shell.secret_token } describe "GET /internal/check", no_db: true do @@ -337,8 +337,7 @@ describe API::Internal, api: true do context 'ssh access has been disabled' do before do - settings = ::ApplicationSetting.create_from_defaults - settings.update_attribute(:enabled_git_access_protocol, 'http') + stub_application_setting(enabled_git_access_protocol: 'http') end it 'rejects the SSH push' do @@ -360,8 +359,7 @@ describe API::Internal, api: true do context 'http access has been disabled' do before do - settings = ::ApplicationSetting.create_from_defaults - settings.update_attribute(:enabled_git_access_protocol, 'ssh') + stub_application_setting(enabled_git_access_protocol: 'ssh') end it 'rejects the HTTP push' do @@ -383,8 +381,7 @@ describe API::Internal, api: true do context 'web actions are always allowed' do it 'allows WEB push' do - settings = ::ApplicationSetting.create_from_defaults - settings.update_attribute(:enabled_git_access_protocol, 'ssh') + stub_application_setting(enabled_git_access_protocol: 'ssh') project.team << [user, :developer] push(key, project, 'web') diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 71a7994e544..21a2c583aa8 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -6,12 +6,10 @@ describe API::MergeRequests, api: true do let(:user) { create(:user) } let(:admin) { create(:user, :admin) } let(:non_member) { create(:user) } - let!(:project) { create(:project, :public, creator_id: user.id, namespace: user.namespace) } - let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) } - let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) } - let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') } - let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") } - let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") } + let!(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace) } + let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, title: "Test", created_at: base_time) } + let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, title: "Closed test", created_at: base_time + 1.second) } + let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') } let(:milestone) { create(:milestone, title: '1.0.0', project: project) } before do @@ -556,11 +554,12 @@ describe API::MergeRequests, api: true do original_count = merge_request.notes.size post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user), note: "My comment" + expect(response).to have_http_status(201) expect(json_response['note']).to eq('My comment') expect(json_response['author']['name']).to eq(user.name) expect(json_response['author']['username']).to eq(user.username) - expect(merge_request.notes.size).to eq(original_count + 1) + expect(merge_request.reload.notes.size).to eq(original_count + 1) end it "returns 400 if note is missing" do @@ -576,6 +575,9 @@ describe API::MergeRequests, api: true do end describe "GET :id/merge_requests/:merge_request_id/comments" do + let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") } + let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") } + it "returns merge_request comments ordered by created_at" do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user) expect(response).to have_http_status(200) diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index 9a01f7fa1c4..b7a0b5a9e13 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -5,7 +5,7 @@ describe API::Pipelines, api: true do let(:user) { create(:user) } let(:non_member) { create(:user) } - let(:project) { create(:project, creator_id: user.id) } + let(:project) { create(:project, :repository, creator: user) } let!(:pipeline) do create(:ci_empty_pipeline, project: project, sha: project.commit.id, diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index cc5c532de83..a1db81ce18c 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -17,6 +17,7 @@ describe API::Projects, api: true do let(:project3) do create(:project, :private, + :repository, name: 'second_project', path: 'second_project', creator_id: user.id, diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 0b19fa38c55..c61208e395c 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -8,7 +8,7 @@ describe API::Repositories, api: true do let(:user) { create(:user) } let(:guest) { create(:user).tap { |u| create(:project_member, :guest, user: u, project: project) } } - let!(:project) { create(:project, creator_id: user.id) } + let!(:project) { create(:project, :repository, creator: user) } let!(:master) { create(:project_member, :master, user: user, project: project) } describe "GET /projects/:id/repository/tree" do @@ -74,7 +74,7 @@ describe API::Repositories, api: true do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository tree' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -144,7 +144,7 @@ describe API::Repositories, api: true do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository blob' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -198,7 +198,7 @@ describe API::Repositories, api: true do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository raw blob' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -273,7 +273,7 @@ describe API::Repositories, api: true do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository archive' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -347,7 +347,7 @@ describe API::Repositories, api: true do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository compare' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -394,7 +394,7 @@ describe API::Repositories, api: true do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository contributors' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index a1c32ae65ba..898d2b27e5c 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -7,7 +7,7 @@ describe API::Tags, api: true do let(:user) { create(:user) } let(:user2) { create(:user) } - let!(:project) { create(:project, creator_id: user.id) } + let!(:project) { create(:project, :repository, creator: user) } let!(:master) { create(:project_member, :master, user: user, project: project) } let!(:guest) { create(:project_member, :guest, user: user2, project: project) } @@ -29,7 +29,7 @@ describe API::Tags, api: true do context 'when unauthenticated' do it_behaves_like 'repository tags' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -88,7 +88,7 @@ describe API::Tags, api: true do context 'when unauthenticated' do it_behaves_like 'repository tag' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb index cd01283b655..84104aa66ee 100644 --- a/spec/requests/api/triggers_spec.rb +++ b/spec/requests/api/triggers_spec.rb @@ -7,7 +7,7 @@ describe API::Triggers do let(:user2) { create(:user) } let!(:trigger_token) { 'secure_token' } let!(:trigger_token_2) { 'secure_token_2' } - let!(:project) { create(:project, creator_id: user.id) } + let!(:project) { create(:project, :repository, creator: user) } let!(:master) { create(:project_member, :master, user: user, project: project) } let!(:developer) { create(:project_member, :developer, user: user2, project: project) } let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) } diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 270c23e3f19..8dbe5f0b025 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -91,6 +91,20 @@ describe Ci::API::Builds do expect { register_builds }.to change { runner.reload.contacted_at } end + context 'when concurrently updating build' do + before do + expect_any_instance_of(Ci::Build).to receive(:run!). + and_raise(ActiveRecord::StaleObjectError.new(nil, nil)) + end + + it 'returns a conflict' do + register_builds info: { platform: :darwin } + + expect(response).to have_http_status(409) + expect(response.headers).not_to have_key('X-GitLab-Last-Update') + end + end + context 'registry credentials' do let(:registry_credentials) do { 'type' => 'registry', diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb index 2d434ab5dd8..a30be767119 100644 --- a/spec/requests/ci/api/triggers_spec.rb +++ b/spec/requests/ci/api/triggers_spec.rb @@ -5,9 +5,9 @@ describe Ci::API::Triggers do describe 'POST /projects/:project_id/refs/:ref/trigger' do let!(:trigger_token) { 'secure token' } - let!(:project) { FactoryGirl.create(:project, ci_id: 10) } - let!(:project2) { FactoryGirl.create(:empty_project, ci_id: 11) } - let!(:trigger) { FactoryGirl.create(:ci_trigger, project: project, token: trigger_token) } + let!(:project) { create(:project, :repository, ci_id: 10) } + let!(:project2) { create(:empty_project, ci_id: 11) } + let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) } let(:options) do { token: trigger_token diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 5abda28e26f..4a16824de04 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -12,7 +12,7 @@ describe 'Git HTTP requests', lib: true do describe "User with no identities" do let(:user) { create(:user) } - let(:project) { create(:project, path: 'project.git-project') } + let(:project) { create(:project, :repository, path: 'project.git-project') } context "when the project doesn't exist" do context "when no authentication is provided" do @@ -55,6 +55,28 @@ describe 'Git HTTP requests', lib: true do expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) end end + + context 'but the repo is disabled' do + let(:project) { create(:project, repository_access_level: ProjectFeature::DISABLED, wiki_access_level: ProjectFeature::ENABLED) } + let(:wiki) { ProjectWiki.new(project) } + let(:path) { "/#{wiki.repository.path_with_namespace}.git" } + + before do + project.team << [user, :developer] + end + + it 'allows clones' do + download(path, user: user.username, password: user.password) do |response| + expect(response).to have_http_status(200) + end + end + + it 'allows pushes' do + upload(path, user: user.username, password: user.password) do |response| + expect(response).to have_http_status(200) + end + end + end end context "when the project exists" do diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb index e02f0eacc93..d20866c0d44 100644 --- a/spec/requests/projects/artifacts_controller_spec.rb +++ b/spec/requests/projects/artifacts_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::ArtifactsController do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) do create(:ci_pipeline, diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb index 72978846e93..0edbffbcd3b 100644 --- a/spec/requests/projects/cycle_analytics_events_spec.rb +++ b/spec/requests/projects/cycle_analytics_events_spec.rb @@ -4,7 +4,7 @@ describe 'cycle analytics events' do include ApiHelpers let(:user) { create(:user) } - let(:project) { create(:project, public_builds: false) } + let(:project) { create(:project, :repository, public_builds: false) } let(:issue) { create(:issue, project: project, created_at: 2.days.ago) } describe 'GET /:namespace/:project/cycle_analytics/events/issues' do @@ -13,7 +13,12 @@ describe 'cycle analytics events' do allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue]) - 3.times { create_cycle } + 3.times do |count| + Timecop.freeze(Time.now + count.days) do + create_cycle + end + end + deploy_master login_as(user) diff --git a/spec/services/ci/register_build_service_spec.rb b/spec/services/ci/register_build_service_spec.rb index a3fc23ba177..d9f774a1095 100644 --- a/spec/services/ci/register_build_service_spec.rb +++ b/spec/services/ci/register_build_service_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' module Ci describe RegisterBuildService, services: true do - let!(:service) { RegisterBuildService.new } let!(:project) { FactoryGirl.create :empty_project, shared_runners_enabled: false } let!(:pipeline) { FactoryGirl.create :ci_pipeline, project: project } let!(:pending_build) { FactoryGirl.create :ci_build, pipeline: pipeline } @@ -19,29 +18,29 @@ module Ci pending_build.tag_list = ["linux"] pending_build.save specific_runner.tag_list = ["linux"] - expect(service.execute(specific_runner)).to eq(pending_build) + expect(execute(specific_runner)).to eq(pending_build) end it "does not pick build with different tag" do pending_build.tag_list = ["linux"] pending_build.save specific_runner.tag_list = ["win32"] - expect(service.execute(specific_runner)).to be_falsey + expect(execute(specific_runner)).to be_falsey end it "picks build without tag" do - expect(service.execute(specific_runner)).to eq(pending_build) + expect(execute(specific_runner)).to eq(pending_build) end it "does not pick build with tag" do pending_build.tag_list = ["linux"] pending_build.save - expect(service.execute(specific_runner)).to be_falsey + expect(execute(specific_runner)).to be_falsey end it "pick build without tag" do specific_runner.tag_list = ["win32"] - expect(service.execute(specific_runner)).to eq(pending_build) + expect(execute(specific_runner)).to eq(pending_build) end end @@ -56,13 +55,13 @@ module Ci end it 'does not pick a build' do - expect(service.execute(shared_runner)).to be_nil + expect(execute(shared_runner)).to be_nil end end context 'for specific runner' do it 'does not pick a build' do - expect(service.execute(specific_runner)).to be_nil + expect(execute(specific_runner)).to be_nil end end end @@ -86,34 +85,34 @@ module Ci it 'prefers projects without builds first' do # it gets for one build from each of the projects - expect(service.execute(shared_runner)).to eq(build1_project1) - expect(service.execute(shared_runner)).to eq(build1_project2) - expect(service.execute(shared_runner)).to eq(build1_project3) + expect(execute(shared_runner)).to eq(build1_project1) + expect(execute(shared_runner)).to eq(build1_project2) + expect(execute(shared_runner)).to eq(build1_project3) # then it gets a second build from each of the projects - expect(service.execute(shared_runner)).to eq(build2_project1) - expect(service.execute(shared_runner)).to eq(build2_project2) + expect(execute(shared_runner)).to eq(build2_project1) + expect(execute(shared_runner)).to eq(build2_project2) # in the end the third build - expect(service.execute(shared_runner)).to eq(build3_project1) + expect(execute(shared_runner)).to eq(build3_project1) end it 'equalises number of running builds' do # after finishing the first build for project 1, get a second build from the same project - expect(service.execute(shared_runner)).to eq(build1_project1) + expect(execute(shared_runner)).to eq(build1_project1) build1_project1.reload.success - expect(service.execute(shared_runner)).to eq(build2_project1) + expect(execute(shared_runner)).to eq(build2_project1) - expect(service.execute(shared_runner)).to eq(build1_project2) + expect(execute(shared_runner)).to eq(build1_project2) build1_project2.reload.success - expect(service.execute(shared_runner)).to eq(build2_project2) - expect(service.execute(shared_runner)).to eq(build1_project3) - expect(service.execute(shared_runner)).to eq(build3_project1) + expect(execute(shared_runner)).to eq(build2_project2) + expect(execute(shared_runner)).to eq(build1_project3) + expect(execute(shared_runner)).to eq(build3_project1) end end context 'shared runner' do - let(:build) { service.execute(shared_runner) } + let(:build) { execute(shared_runner) } it { expect(build).to be_kind_of(Build) } it { expect(build).to be_valid } @@ -122,7 +121,7 @@ module Ci end context 'specific runner' do - let(:build) { service.execute(specific_runner) } + let(:build) { execute(specific_runner) } it { expect(build).to be_kind_of(Build) } it { expect(build).to be_valid } @@ -137,13 +136,13 @@ module Ci end context 'shared runner' do - let(:build) { service.execute(shared_runner) } + let(:build) { execute(shared_runner) } it { expect(build).to be_nil } end context 'specific runner' do - let(:build) { service.execute(specific_runner) } + let(:build) { execute(specific_runner) } it { expect(build).to be_kind_of(Build) } it { expect(build).to be_valid } @@ -159,17 +158,21 @@ module Ci end context 'and uses shared runner' do - let(:build) { service.execute(shared_runner) } + let(:build) { execute(shared_runner) } it { expect(build).to be_nil } end context 'and uses specific runner' do - let(:build) { service.execute(specific_runner) } + let(:build) { execute(specific_runner) } it { expect(build).to be_nil } end end + + def execute(runner) + described_class.new(runner).execute.build + end end end end diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index b0cc3ce5f5a..9c92a5080c6 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -15,39 +15,45 @@ describe Notes::CreateService, services: true do context "valid params" do it 'returns a valid note' do - note = Notes::CreateService.new(project, user, opts).execute + note = described_class.new(project, user, opts).execute expect(note).to be_valid end it 'returns a persisted note' do - note = Notes::CreateService.new(project, user, opts).execute + note = described_class.new(project, user, opts).execute expect(note).to be_persisted end it 'note has valid content' do - note = Notes::CreateService.new(project, user, opts).execute + note = described_class.new(project, user, opts).execute expect(note.note).to eq(opts[:note]) end + it 'note belongs to the correct project' do + note = described_class.new(project, user, opts).execute + + expect(note.project).to eq(project) + end + it 'TodoService#new_note is called' do - note = build(:note) - allow(project).to receive_message_chain(:notes, :new).with(opts) { note } + note = build(:note, project: project) + allow(Note).to receive(:new).with(opts) { note } expect_any_instance_of(TodoService).to receive(:new_note).with(note, user) - Notes::CreateService.new(project, user, opts).execute + described_class.new(project, user, opts).execute end it 'enqueues NewNoteWorker' do - note = build(:note, id: 999) - allow(project).to receive_message_chain(:notes, :new).with(opts) { note } + note = build(:note, id: 999, project: project) + allow(Note).to receive(:new).with(opts) { note } expect(NewNoteWorker).to receive(:perform_async).with(note.id) - Notes::CreateService.new(project, user, opts).execute + described_class.new(project, user, opts).execute end end @@ -75,6 +81,27 @@ describe Notes::CreateService, services: true do end end end + + describe 'personal snippet note' do + subject { described_class.new(nil, user, params).execute } + + let(:snippet) { create(:personal_snippet) } + let(:params) do + { note: 'comment', noteable_type: 'Snippet', noteable_id: snippet.id } + end + + it 'returns a valid note' do + expect(subject).to be_valid + end + + it 'returns a persisted note' do + expect(subject).to be_persisted + end + + it 'note has valid content' do + expect(subject.note).to eq(params[:note]) + end + end end describe "award emoji" do @@ -88,7 +115,7 @@ describe Notes::CreateService, services: true do noteable_type: 'Issue', noteable_id: issue.id } - note = Notes::CreateService.new(project, user, opts).execute + note = described_class.new(project, user, opts).execute expect(note).to be_valid expect(note.name).to eq('smile') @@ -100,7 +127,7 @@ describe Notes::CreateService, services: true do noteable_type: 'Issue', noteable_id: issue.id } - note = Notes::CreateService.new(project, user, opts).execute + note = described_class.new(project, user, opts).execute expect(note).to be_valid expect(note.note).to eq(opts[:note]) @@ -115,7 +142,7 @@ describe Notes::CreateService, services: true do expect_any_instance_of(TodoService).to receive(:new_award_emoji).with(issue, user) - Notes::CreateService.new(project, user, opts).execute + described_class.new(project, user, opts).execute end end end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index f3e80ac22a0..309985a5d90 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -33,6 +33,49 @@ describe NotificationService, services: true do end end + # Next shared examples are intended to test notifications of "participants" + # + # they take the following parameters: + # * issuable + # * notification trigger + # * participant + # + shared_examples 'participating by note notification' do + it 'emails the participant' do + create(:note_on_issue, noteable: issuable, project_id: project.id, note: 'anything', author: participant) + + notification_trigger + + should_email(participant) + end + end + + shared_examples 'participating by assignee notification' do + it 'emails the participant' do + issuable.update_attribute(:assignee, participant) + + notification_trigger + + should_email(participant) + end + end + + shared_examples 'participating by author notification' do + it 'emails the participant' do + issuable.author = participant + + notification_trigger + + should_email(participant) + end + end + + shared_examples_for 'participating notifications' do + it_should_behave_like 'participating by note notification' + it_should_behave_like 'participating by author notification' + it_should_behave_like 'participating by assignee notification' + end + describe 'Keys' do describe '#new_key' do let!(:key) { create(:personal_key) } @@ -269,6 +312,55 @@ describe NotificationService, services: true do end end + context 'personal snippet note' do + let(:snippet) { create(:personal_snippet, :public, author: @u_snippet_author) } + let(:note) { create(:note_on_personal_snippet, noteable: snippet, note: '@mentioned note', author: @u_note_author) } + + before do + @u_watcher = create_global_setting_for(create(:user), :watch) + @u_participant = create_global_setting_for(create(:user), :participating) + @u_disabled = create_global_setting_for(create(:user), :disabled) + @u_mentioned = create_global_setting_for(create(:user, username: 'mentioned'), :mention) + @u_mentioned_level = create_global_setting_for(create(:user, username: 'participator'), :mention) + @u_note_author = create(:user, username: 'note_author') + @u_snippet_author = create(:user, username: 'snippet_author') + @u_not_mentioned = create_global_setting_for(create(:user, username: 'regular'), :participating) + + reset_delivered_emails! + end + + let!(:notes) do + [ + create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_watcher), + create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_participant), + create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_mentioned), + create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_disabled), + create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_note_author), + ] + end + + describe '#new_note' do + it 'notifies the participants' do + notification.new_note(note) + + # it emails participants + should_email(@u_watcher) + should_email(@u_participant) + should_email(@u_watcher) + should_email(@u_snippet_author) + + # it emails mentioned users + should_email(@u_mentioned) + + # it does not email participants with mention notification level + should_not_email(@u_mentioned_level) + + # it does not email note author + should_not_email(@u_note_author) + end + end + end + context 'commit note' do let(:project) { create(:project, :public) } let(:note) { create(:note_on_commit, project: project) } @@ -539,32 +631,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - issue.update_attribute(:assignee, @u_lazy_participant) - notification.reassigned_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: 'anything', author: @u_lazy_participant) } - - before { notification.reassigned_issue(issue, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - issue.author = @u_lazy_participant - notification.reassigned_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { issue } + let(:notification_trigger) { notification.reassigned_issue(issue, @u_disabled) } end end @@ -671,32 +741,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - issue.update_attribute(:assignee, @u_lazy_participant) - notification.close_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: 'anything', author: @u_lazy_participant) } - - before { notification.close_issue(issue, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - issue.author = @u_lazy_participant - notification.close_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { issue } + let(:notification_trigger) { notification.close_issue(issue, @u_disabled) } end end @@ -723,32 +771,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - issue.update_attribute(:assignee, @u_lazy_participant) - notification.reopen_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: 'anything', author: @u_lazy_participant) } - - before { notification.reopen_issue(issue, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - issue.author = @u_lazy_participant - notification.reopen_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { issue } + let(:notification_trigger) { notification.reopen_issue(issue, @u_disabled) } end end end @@ -809,31 +835,28 @@ describe NotificationService, services: true do end context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.new_merge_request(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } + it_should_behave_like 'participating by assignee notification' do + let(:participant) { create(:user, username: 'user-participant')} + let(:issuable) { merge_request } + let(:notification_trigger) { notification.new_merge_request(merge_request, @u_disabled) } end - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.new_merge_request(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } + it_should_behave_like 'participating by note notification' do + let(:participant) { create(:user, username: 'user-participant')} + let(:issuable) { merge_request } + let(:notification_trigger) { notification.new_merge_request(merge_request, @u_disabled) } end context 'by author' do + let(:participant) { create(:user, username: 'user-participant')} + before do - merge_request.author = @u_lazy_participant + merge_request.author = participant merge_request.save notification.new_merge_request(merge_request, @u_disabled) end - it { should_not_email(@u_lazy_participant) } + it { should_not_email(participant) } end end end @@ -868,33 +891,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.reassigned_merge_request(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.reassigned_merge_request(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.reassigned_merge_request(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { merge_request } + let(:notification_trigger) { notification.reassigned_merge_request(merge_request, @u_disabled) } end end @@ -965,33 +965,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.close_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.close_mr(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.close_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { merge_request } + let(:notification_trigger) { notification.close_mr(merge_request, @u_disabled) } end end @@ -1032,33 +1009,10 @@ describe NotificationService, services: true do should_not_email(@u_watcher) end - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.merge_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.merge_mr(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.merge_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { merge_request } + let(:notification_trigger) { notification.merge_mr(merge_request, @u_disabled) } end end @@ -1085,33 +1039,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.reopen_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.reopen_mr(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.reopen_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { merge_request } + let(:notification_trigger) { notification.reopen_mr(merge_request, @u_disabled) } end end @@ -1131,33 +1062,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.resolve_all_discussions(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.resolve_all_discussions(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.resolve_all_discussions(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { merge_request } + let(:notification_trigger) { notification.resolve_all_discussions(merge_request, @u_disabled) } end end end diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index fef211ded50..db9f1231682 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -12,6 +12,7 @@ describe SystemHooksService, services: true do it { expect(event_data(user, :create)).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username) } it { expect(event_data(user, :destroy)).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username) } it { expect(event_data(project, :create)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) } + it { expect(event_data(project, :update)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) } it { expect(event_data(project, :destroy)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) } it { expect(event_data(project_member, :create)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) } it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) } @@ -68,6 +69,7 @@ describe SystemHooksService, services: true do it { expect(event_name(project, :destroy)).to eq "project_destroy" } it { expect(event_name(project, :rename)).to eq "project_rename" } it { expect(event_name(project, :transfer)).to eq "project_transfer" } + it { expect(event_name(project, :update)).to eq "project_update" } it { expect(event_name(project_member, :create)).to eq "user_add_to_team" } it { expect(event_name(project_member, :destroy)).to eq "user_remove_from_team" } it { expect(event_name(key, :create)).to eq 'key_create' } diff --git a/spec/services/user_project_access_changed_service_spec.rb b/spec/services/user_project_access_changed_service_spec.rb new file mode 100644 index 00000000000..b4efe7de431 --- /dev/null +++ b/spec/services/user_project_access_changed_service_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +describe UserProjectAccessChangedService do + describe '#execute' do + it 'schedules the user IDs' do + expect(AuthorizedProjectsWorker).to receive(:bulk_perform_and_wait). + with([[1], [2]]) + + described_class.new([1, 2]).execute + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6ee3307512d..e160c11111b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,11 +2,11 @@ require './spec/simplecov_env' SimpleCovEnv.start! ENV["RAILS_ENV"] ||= 'test' +ENV["IN_MEMORY_APPLICATION_SETTINGS"] = 'true' require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' require 'shoulda/matchers' -require 'sidekiq/testing/inline' require 'rspec/retry' if ENV['CI'] && !ENV['NO_KNAPSACK'] diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb index 35b40d73191..10b90b40ba7 100644 --- a/spec/support/cycle_analytics_helpers/test_generation.rb +++ b/spec/support/cycle_analytics_helpers/test_generation.rb @@ -54,7 +54,7 @@ module CycleAnalyticsHelpers end context "when the data belongs to another project" do - let(:other_project) { create(:project) } + let(:other_project) { create(:project, :repository) } it "returns nil" do # Use a stub to "trick" the data/condition functions diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index f57c82809a6..87936bb4859 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -12,7 +12,7 @@ shared_context 'mentionable context' do let!(:mentioned_mr) { create(:merge_request, source_project: project) } let(:mentioned_commit) { project.commit("HEAD~1") } - let(:ext_proj) { create(:project, :public) } + let(:ext_proj) { create(:project, :public, :repository) } let(:ext_issue) { create(:issue, project: ext_proj) } let(:ext_mr) { create(:merge_request, :simple, source_project: ext_proj) } let(:ext_commit) { ext_proj.commit("HEAD~2") } diff --git a/spec/support/sidekiq.rb b/spec/support/sidekiq.rb new file mode 100644 index 00000000000..575d3451150 --- /dev/null +++ b/spec/support/sidekiq.rb @@ -0,0 +1,5 @@ +require 'sidekiq/testing/inline' + +Sidekiq::Testing.server_middleware do |chain| + chain.add Gitlab::SidekiqStatus::ServerMiddleware +end diff --git a/spec/support/slack_mattermost_notifications_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb index 74d9b8c6313..704922b6cf4 100644 --- a/spec/support/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/slack_mattermost_notifications_shared_examples.rb @@ -26,7 +26,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do describe "#execute" do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:username) { 'slack_username' } let(:channel) { 'slack_channel' } @@ -196,7 +196,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do describe "Note events" do let(:user) { create(:user) } - let(:project) { create(:project, creator_id: user.id) } + let(:project) { create(:project, :repository, creator: user) } before do allow(chat_service).to receive_messages( @@ -269,7 +269,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do describe 'Pipeline events' do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) do create(:ci_pipeline, diff --git a/spec/support/taskable_shared_examples.rb b/spec/support/taskable_shared_examples.rb index ad1c783df4d..4056ff06b84 100644 --- a/spec/support/taskable_shared_examples.rb +++ b/spec/support/taskable_shared_examples.rb @@ -33,6 +33,30 @@ shared_examples 'a Taskable' do end end + describe 'with nested tasks' do + before do + subject.description = <<-EOT.strip_heredoc + - [ ] Task a + - [x] Task a.1 + - [ ] Task a.2 + - [ ] Task b + + 1. [ ] Task 1 + 1. [ ] Task 1.1 + 1. [ ] Task 1.2 + 1. [x] Task 2 + 1. [x] Task 2.1 + EOT + end + + it 'returns the correct task status' do + expect(subject.task_status).to match('3 of') + expect(subject.task_status).to match('9 tasks completed') + expect(subject.task_status_short).to match('3/') + expect(subject.task_status_short).to match('9 tasks') + end + end + describe 'with an incomplete task' do before do subject.description = <<-EOT.strip_heredoc @@ -48,6 +72,25 @@ shared_examples 'a Taskable' do end end + describe 'with tasks that are not formatted correctly' do + before do + subject.description = <<-EOT.strip_heredoc + [ ] task 1 + [ ] task 2 + + - [ ]task 1 + -[ ] task 2 + EOT + end + + it 'returns the correct task status' do + expect(subject.task_status).to match('0 of') + expect(subject.task_status).to match('0 tasks completed') + expect(subject.task_status_short).to match('0/') + expect(subject.task_status_short).to match('0 task') + end + end + describe 'with a complete task' do before do subject.description = <<-EOT.strip_heredoc diff --git a/spec/views/ci/lints/show.html.haml_spec.rb b/spec/views/ci/lints/show.html.haml_spec.rb index 2dac5ee23c8..3390ae247ff 100644 --- a/spec/views/ci/lints/show.html.haml_spec.rb +++ b/spec/views/ci/lints/show.html.haml_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'ci/lints/show' do - include Devise::TestHelpers + include Devise::Test::ControllerHelpers describe 'XSS protection' do let(:config_processor) { Ci::GitlabCiYamlProcessor.new(YAML.dump(content)) } diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb index 745d0c745bd..44870cfcfb3 100644 --- a/spec/views/projects/builds/show.html.haml_spec.rb +++ b/spec/views/projects/builds/show.html.haml_spec.rb @@ -15,6 +15,36 @@ describe 'projects/builds/show', :view do allow(view).to receive(:can?).and_return(true) end + describe 'build information in header' do + let(:build) do + create(:ci_build, :success, environment: 'staging') + end + + before do + render + end + + it 'shows status name' do + expect(rendered).to have_css('.ci-status.ci-success', text: 'passed') + end + + it 'does not render a link to the build' do + expect(rendered).not_to have_link('passed') + end + + it 'shows build id' do + expect(rendered).to have_css('.js-build-id', text: build.id) + end + + it 'shows a link to the pipeline' do + expect(rendered).to have_link(build.pipeline.id) + end + + it 'shows a link to the commit' do + expect(rendered).to have_link(build.pipeline.short_sha) + end + end + describe 'environment info in build view' do context 'build with latest deployment' do let(:build) do diff --git a/spec/workers/authorized_projects_worker_spec.rb b/spec/workers/authorized_projects_worker_spec.rb index b6591f272f6..97c4bfcd248 100644 --- a/spec/workers/authorized_projects_worker_spec.rb +++ b/spec/workers/authorized_projects_worker_spec.rb @@ -3,6 +3,18 @@ require 'spec_helper' describe AuthorizedProjectsWorker do let(:worker) { described_class.new } + describe '.bulk_perform_and_wait' do + it 'schedules the ids and waits for the jobs to complete' do + project = create(:project) + + project.owner.project_authorizations.delete_all + + described_class.bulk_perform_and_wait([[project.owner.id]]) + + expect(project.owner.project_authorizations.count).to eq(1) + end + end + describe '#perform' do it "refreshes user's authorized projects" do user = create(:user) |