Commit 3cc76aa8 authored by Nick Thomas's avatar Nick Thomas

Merge remote-tracking branch 'ce/master' into ce-to-ee

parents ae674f38 88d38963
......@@ -427,6 +427,7 @@ entry.
- Speed up group milestone index by passing group_id to IssuesFinder. !8363
- Ensure issuable state changes only fire webhooks once.
<<<<<<< HEAD
## 8.14.7 (2017-01-21)
- Ensure export files are removed after a namespace is deleted.
......@@ -435,6 +436,8 @@ entry.
- Prevent users from deleting system deploy keys via the project deploy key API.
- Upgrade omniauth gem to 1.3.2.
=======
>>>>>>> ce/master
## 8.14.6 (2017-01-10)
- Update the gitlab-markup gem to the version 1.5.1. !8509
......
......@@ -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].
......@@ -450,8 +451,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
......
/* 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();
})();
......@@ -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);
......@@ -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;
})();
......
......@@ -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;
......
......@@ -160,6 +160,62 @@
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
......
......@@ -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);
......
......@@ -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() {
......
......@@ -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 {
......
......@@ -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 {
......
......@@ -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 {
......
......@@ -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 {
......
......@@ -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;
}
......
......@@ -203,6 +203,10 @@
position: relative;
margin-right: 6px;
.tooltip {
white-space: nowrap;
}
.tooltip-inner {
padding: 3px 4px;
}
......
......@@ -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);
}
......
......@@ -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
......
......@@ -86,7 +86,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
......@@ -105,7 +105,12 @@ class Projects::GitHttpController < Projects::GitHttpClientController
access_check.allowed?
end
<<<<<<< HEAD
def log_user_activity
Users::ActivityService.new(user, 'pull').execute
=======
def access_klass
@access_klass ||= wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
>>>>>>> ce/master
end
end
......@@ -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)
......
......@@ -16,7 +16,10 @@ class ProjectGroupLink < ActiveRecord::Base
validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true
validate :different_group
<<<<<<< HEAD
before_destroy :delete_branch_protection
=======
>>>>>>> ce/master
after_commit :refresh_group_members_authorized_projects
def self.access_options
......
module ApplicationSettings
class UpdateService < ApplicationSettings::BaseService
def execute
<<<<<<< HEAD
# Repository size limit comes as MB from the view
limit = @params.delete(:repository_size_limit)
@application_setting.repository_size_limit = (limit.to_i.megabytes if limit.present?)
=======
>>>>>>> ce/master
@application_setting.update(@params)
end
end
......
......@@ -3,10 +3,18 @@ module Ci
# proper pending build to runner on runner API request
class RegisterBuildService
include Gitlab::CurrentSettings
<<<<<<< HEAD
prepend EE::Ci::RegisterBuildService
attr_reader :runner
=======
attr_reader :runner
Result = Struct.new(:build, :valid?)
>>>>>>> ce/master
def initialize(runner)
@runner = runner
end
......@@ -30,10 +38,10 @@ module Ci
build.run!
end
build
Result.new(build, true)
rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError
nil
Result.new(build, false)
end
private
......
......@@ -22,9 +22,12 @@ module Projects
return @project
end
<<<<<<< HEAD
# Repository size limit comes as MB from the view
set_repository_size_limit_as_bytes
=======
>>>>>>> ce/master
set_project_name_from_path
# get namespace id
......@@ -145,11 +148,14 @@ module Projects
end
end
<<<<<<< HEAD
def set_repository_size_limit_as_bytes
limit = params.delete(:repository_size_limit)
@project.repository_size_limit = (limit.to_i.megabytes if limit.present?)
end
=======
>>>>>>> ce/master
def set_project_name_from_path
# Set project name from path
if @project.name.present? && @project.path.present?
......
......@@ -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
......
......@@ -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
......
......@@ -18,12 +18,15 @@
- if @project.protected_branch? branch.name
%span.label.label-success
protected
<<<<<<< HEAD
- if @project.mirror_ever_updated_successfully? && @repository.diverged_from_upstream?(branch.name)
%span.label.label-danger.has-tooltip{ data: { html: "true", title: branch_diverged_tooltip_message } }
= icon('exclamation-triangle')
diverged from upstream
=======
>>>>>>> ce/master
.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
......
.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
......
......@@ -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
......
......@@ -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
......
......@@ -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 &#124;
= lock_file_link(html_options: {class: 'pull-right prepend-left-10 path-lock'})
......
---
title: Prevent removal of input fields if it is the parent dropdown element
merge_request: 8397
author:
---
title: Don’t count tasks that are not defined as list items correctly
merge_request: 8526
author:
---
title: Color + and - signs in diffs to increase code legibility
merge_request:
author:
---
title: Fix autocomplete initial undefined state
merge_request:
author:
---
title: Fix commit title bar and repository view copy clipboard button order on last commit in repository view
merge_request:
author:
---
title: Fix mini-pipeline stage tooltip text wrapping
merge_request:
author:
---
title: Prevent copying of line numbers in parallel diff view
merge_request: 8706
author:
---
title: Updated builds info link on the project settings page
merge_request:
author: Ryan Harris
---
title: Copying a rendered issue/comment will paste into GFM textareas as actual GFM
merge_request:
author:
---
title: Display project ID in project settings
merge_request: 8572
author: winniehell
---
title: Fix access to the wiki code via HTTP when repository feature disabled
merge_request: 8758
author:
---
title: Fixed label dropdown toggle text not correctly updating
merge_request:
author:
---
title: Display fullscreen button on small screens
merge_request: 5302
author: winniehell
......@@ -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
......
......@@ -14,7 +14,11 @@ if ENV['CI']
Knapsack::Adapters::SpinachAdapter.bind
end
<<<<<<< HEAD
%w(select2_helper test_env repo_helpers license wait_for_ajax sidekiq).each do |f|
=======
%w(select2_helper test_env repo_helpers wait_for_ajax sidekiq).each do |f|
>>>>>>> ce/master
require Rails.root.join('spec', 'support', f)
end
......
......@@ -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
)
......
......@@ -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,
......
......@@ -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)
......
......@@ -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',
......
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,
......
......@@ -18,24 +18,35 @@ 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
<<<<<<< HEAD
build = Ci::RegisterBuildService.new(current_runner).execute
=======
result = Ci::RegisterBuildService.new(current_runner).execute
>>>>>>> ce/master
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
......
......@@ -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
......
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))
......
require 'spec_helper'
describe AutocompleteController do
let!(:project) { create(:project) }
let!(:project) { create(:empty_project) }
let!(:user) { create(:user) }
context 'GET users' do
......
require 'spec_helper'
describe Projects::BlobController do
let(:project) { create(:project) }
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
before do
......
......@@ -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
......
......@@ -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
......
......@@ -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) { '肯定不是中文的问题' }
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
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
......
require 'spec_helper'
describe Projects::BlameController do
let(:project) { create(:project) }
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
before do
......
require 'rails_helper'
describe Projects::BlobController do
let(:project) { create(:project, :public) }
let(:project) { create(:project, :public, :repository) }
let(:user) { create(:user) }
before do
......
require 'spec_helper'
describe Projects::BranchesController do
let(:project) { create(:project) }
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:developer) { create(:user) }
......
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) }
......
require 'spec_helper'
describe Projects::CommitsController do
let(:project) { create(:project) }
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
before do
......
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" }
......
require 'spec_helper'
describe Projects::CycleAnalyticsController do
let(:project) { create(:project) }
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
before do
......
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 }
......
require 'spec_helper'
describe Projects::FindFileController do
let(:project) { create(:project) }
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
before do
......
......@@ -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) }
......
require 'spec_helper'
describe Projects::GraphsController do
let(:project) { create(:project) }
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
before do
......
......@@ -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
......
......@@ -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) }
......
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) }
......
......@@ -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) }
......
......@@ -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
......
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
......
require 'spec_helper'
describe Projects::RefsController do
let(:project) { create(:project) }
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
before do
......
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 }
......
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
......
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) }
......
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
......
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') }
......
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' }
......
require 'spec_helper'
describe Projects::TreeController do
let(:project) { create(:project) }
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
before do
......
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') }
......
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
......@@ -211,25 +213,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
......@@ -238,7 +226,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
......@@ -254,6 +242,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)
......@@ -405,6 +394,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
......
......@@ -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
......
......@@ -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
......
FactoryGirl.define do
factory :deploy_keys_project do
deploy_key
project
project factory: :empty_project
end
end
FactoryGirl.define do
factory :event do
project
project factory: :empty_project
author factory: :user
factory :closed_issue_event do
......
FactoryGirl.define do
factory :file_uploader do
project
project factory: :empty_project
secret nil
transient do
......
......@@ -6,7 +6,7 @@ FactoryGirl.define do
factory :issue do
title
author
project
project factory: :empty_project
trait :confidential do
confidential true
......
......@@ -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
......
......@@ -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
......
FactoryGirl.define do
factory :milestone do
title
project
project factory: :empty_project
trait :active do
state "active"
......
......@@ -4,7 +4,7 @@ include ActionDispatch::TestProcess
FactoryGirl.define do
factory :note do
project
project factory: :empty_project
note "Note"
author
on_issue
......@@ -16,10 +16,16 @@ FactoryGirl.define do
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",
......@@ -37,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",
......@@ -49,6 +56,7 @@ FactoryGirl.define do
end
trait :on_commit do
association :project, :repository
noteable nil
noteable_type 'Commit'
noteable_id nil
......
FactoryGirl.define do
factory :project_group_link do
project
project factory: :empty_project
group
end
end
FactoryGirl.define do
factory :project_member do
user
project
project factory: :empty_project
master
trait(:guest) { access_level ProjectMember::GUEST }
......
FactoryGirl.define do
factory :project_snippet, parent: :snippet, class: :ProjectSnippet do
project
project factory: :empty_project
end
end
......@@ -2,6 +2,6 @@ FactoryGirl.define do
factory :release do
tag "v1.1.0"
description "Awesome release"
project
project factory: :empty_project
end
end
FactoryGirl.define do
factory :sent_notification do
project
project factory: :empty_project
recipient factory: :user
noteable factory: :issue
reply_key "0123456789abcdef" * 2
......
FactoryGirl.define do
factory :service do
project
project factory: :empty_project
end
end
FactoryGirl.define do
factory :todo do
project
project factory: :empty_project
author
user
target factory: :issue
......
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
......@@ -80,6 +80,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
......
......@@ -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
......
......@@ -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]
......
......@@ -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)
......
......@@ -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
......
......@@ -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') }
......
......@@ -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 }
......
......@@ -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
......
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) }
......
......@@ -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 }
......
......@@ -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
......
......@@ -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)
......
......@@ -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)
......
......@@ -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('&lt;h1&gt;test&lt;/h1&gt;')
end
......
......@@ -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, '') }
......
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 }
......
......@@ -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) }
......
......@@ -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"
......
......@@ -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
......
......@@ -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)
......
......@@ -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 }
......@@ -106,7 +106,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
......
......@@ -75,7 +75,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
......@@ -85,7 +85,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)
......
......@@ -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)
......
......@@ -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
......
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
......
......@@ -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);
});
});
});
/* 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");
});
});
});
......
......@@ -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
......@@ -190,7 +190,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')
......@@ -207,7 +207,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]
......@@ -249,7 +249,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' }
......@@ -272,7 +272,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' }
......
......@@ -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 } }
......
......@@ -138,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
......
......@@ -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',
......
......@@ -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
......
......@@ -171,7 +171,11 @@ module Ci
end
def execute(runner)
<<<<<<< HEAD
described_class.new(runner).execute
=======
described_class.new(runner).execute.build
>>>>>>> ce/master
end
end
end
......
......@@ -72,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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment