summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml8
-rw-r--r--.scss-lint.yml2
-rw-r--r--CHANGELOG.md8
-rw-r--r--app/assets/javascripts/application.js6
-rw-r--r--app/assets/javascripts/blob/template_selector.js.es67
-rw-r--r--app/assets/javascripts/extensions/element.js.es66
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js8
-rw-r--r--app/assets/javascripts/pipelines.js.es647
-rw-r--r--app/assets/javascripts/templates/issuable_template_selector.js.es612
-rw-r--r--app/assets/stylesheets/framework/animations.scss3
-rw-r--r--app/assets/stylesheets/framework/blocks.scss3
-rw-r--r--app/assets/stylesheets/framework/buttons.scss3
-rw-r--r--app/assets/stylesheets/framework/common.scss6
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss6
-rw-r--r--app/assets/stylesheets/framework/flash.scss6
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss4
-rw-r--r--app/assets/stylesheets/framework/header.scss13
-rw-r--r--app/assets/stylesheets/framework/lists.scss6
-rw-r--r--app/assets/stylesheets/framework/mobile.scss12
-rw-r--r--app/assets/stylesheets/framework/nav.scss22
-rw-r--r--app/assets/stylesheets/framework/selects.scss6
-rw-r--r--app/assets/stylesheets/framework/tables.scss3
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap.scss3
-rw-r--r--app/assets/stylesheets/framework/typography.scss28
-rw-r--r--app/assets/stylesheets/highlight/dark.scss19
-rw-r--r--app/assets/stylesheets/highlight/monokai.scss19
-rw-r--r--app/assets/stylesheets/highlight/solarized_dark.scss19
-rw-r--r--app/assets/stylesheets/highlight/solarized_light.scss19
-rw-r--r--app/assets/stylesheets/highlight/white.scss13
-rw-r--r--app/assets/stylesheets/pages/admin.scss3
-rw-r--r--app/assets/stylesheets/pages/ci_projects.scss3
-rw-r--r--app/assets/stylesheets/pages/commit.scss6
-rw-r--r--app/assets/stylesheets/pages/commits.scss6
-rw-r--r--app/assets/stylesheets/pages/confirmation.scss10
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss3
-rw-r--r--app/assets/stylesheets/pages/diff.scss9
-rw-r--r--app/assets/stylesheets/pages/editor.scss4
-rw-r--r--app/assets/stylesheets/pages/errors.scss4
-rw-r--r--app/assets/stylesheets/pages/issues.scss3
-rw-r--r--app/assets/stylesheets/pages/login.scss9
-rw-r--r--app/assets/stylesheets/pages/merge_conflicts.scss3
-rw-r--r--app/assets/stylesheets/pages/milestone.scss3
-rw-r--r--app/assets/stylesheets/pages/note_form.scss3
-rw-r--r--app/assets/stylesheets/pages/notes.scss3
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss12
-rw-r--r--app/assets/stylesheets/pages/profile.scss3
-rw-r--r--app/assets/stylesheets/pages/projects.scss12
-rw-r--r--app/assets/stylesheets/pages/search.scss6
-rw-r--r--app/assets/stylesheets/pages/tree.scss6
-rw-r--r--app/assets/stylesheets/print.scss25
-rw-r--r--app/finders/labels_finder.rb2
-rw-r--r--app/helpers/boards_helper.rb2
-rw-r--r--app/models/merge_request_diff.rb8
-rw-r--r--app/models/user.rb2
-rw-r--r--app/views/projects/merge_requests/show/_commits.html.haml2
-rw-r--r--app/workers/project_cache_worker.rb16
-rw-r--r--doc/development/README.md2
-rw-r--r--doc/development/api_styleguide.md96
-rw-r--r--doc/development/doc_styleguide.md6
-rw-r--r--doc/update/8.12-to-8.13.md2
-rw-r--r--doc/user/project/new_ci_build_permissions_model.md6
-rw-r--r--lib/api/users.rb4
-rw-r--r--lib/banzai/filter/relative_link_filter.rb4
-rw-r--r--lib/gitlab/ee_compat_check.rb261
-rw-r--r--lib/gitlab/exclusive_lease.rb9
-rw-r--r--lib/tasks/ce_to_ee_merge_check.rake4
-rw-r--r--lib/tasks/ee_compat_check.rake4
-rw-r--r--lib/tasks/gitlab/dev.rake109
-rw-r--r--spec/controllers/snippets_controller_spec.rb156
-rw-r--r--spec/features/boards/boards_spec.rb4
-rw-r--r--spec/features/projects/issuable_templates_spec.rb2
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb40
-rw-r--r--spec/lib/gitlab/exclusive_lease_spec.rb43
-rw-r--r--spec/models/merge_request_spec.rb3
-rw-r--r--spec/requests/api/users_spec.rb23
-rw-r--r--spec/services/issues/move_service_spec.rb14
-rw-r--r--spec/spec_helper.rb6
-rw-r--r--spec/views/projects/merge_requests/_commits.html.haml_spec.rb38
-rw-r--r--spec/workers/project_cache_worker_spec.rb20
79 files changed, 969 insertions, 372 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 76117a48730..474bf6765f4 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -210,7 +210,7 @@ rake brakeman: *exec
rake flay: *exec
license_finder: *exec
rake downtime_check: *exec
-rake ce_to_ee_merge_check:
+rake ee_compat_check:
<<: *exec
only:
- branches
@@ -279,16 +279,20 @@ bundler:audit:
migration paths:
stage: test
<<: *use-db
+ variables:
+ SETUP_DB: "false"
only:
- master@gitlab-org/gitlab-ce
script:
- git checkout HEAD .
- git fetch --tags
- git checkout v8.5.9
- - 'echo test: unix:/var/opt/gitlab/redis/redis.socket > config/resque.yml'
+ - cp config/resque.yml.example config/resque.yml
+ - sed -i 's/localhost/redis/g' config/resque.yml
- bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}" --retry=3
- rake db:drop db:create db:schema:load db:seed_fu
- git checkout $CI_BUILD_REF
+ - source scripts/prepare_build.sh
- rake db:migrate
coverage:
diff --git a/.scss-lint.yml b/.scss-lint.yml
index 9bdf438d995..5c8e5ac0758 100644
--- a/.scss-lint.yml
+++ b/.scss-lint.yml
@@ -172,7 +172,7 @@ linters:
# Split selectors onto separate lines after each comma, and have each
# individual selector occupy a single line.
SingleLinePerSelector:
- enabled: false
+ enabled: true
# Commas in lists should be followed by a space.
SpaceAfterComma:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f029c8a94fd..0989345d230 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix extra space on Build sidebar on Firefox !7060
- Fix HipChat notifications rendering (airatshigapov, eisnerd)
- Add hover to trash icon in notes !7008 (blackst0ne)
+ - Escape ref and path for relative links !6050 (winniehell)
- Simpler arguments passed to named_route on toggle_award_url helper method
- Fix: Backup restore doesn't clear cache
- Use MergeRequestsClosingIssues cache data on Issue#closed_by_merge_requests method
@@ -21,10 +22,13 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix reply-by-email not working due to queue name mismatch
- Fixed hidden pipeline graph on commit and MR page !6895
- Expire and build repository cache after project import
+ - Reduce overhead of LabelFinder by avoiding #presence call !7094
- Fix 404 for group pages when GitLab setup uses relative url
- - Simpler arguments passed to named_route on toggle_award_url helper method
+ - Simpler arguments passed to named_route on toggle_award_url helper method
+ - Fix unauthorized users dragging on issue boards
- Better handle when no users were selected for adding to group or project. (Linus Thiel)
- Only show register tab if signup enabled.
+ - Only schedule ProjectCacheWorker jobs when needed
## 8.13.0 (2016-10-22)
- Removes extra line for empty issue description. (!7045)
@@ -73,6 +77,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs)
- Add tag shortcut from the Commit page. !6543
- Keep refs for each deployment
+ - Close open tooltips on page navigation (Linus Thiel)
- Allow browsing branches that end with '.atom'
- Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller)
- Replace unique keyframes mixin with keyframe mixin with specific names (ClemMakesApps)
@@ -107,6 +112,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- Add visibility level to project repository
- Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison)
- Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska)
+ - Fix showing commits from source project for merge request !6658
- Fix that manual jobs would no longer block jobs in the next stage. !6604
- Add configurable email subject suffix (Fu Xu)
- Use defined colour for a language when available !6748 (nilsding)
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 17cbfd0e66f..c6c3c82e1ee 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -124,15 +124,11 @@
return str.replace(/<(?:.|\n)*?>/gm, '');
};
- window.unbindEvents = function() {
- return $(document).off('scroll');
- };
-
window.shiftWindow = function() {
return scrollBy(0, -100);
};
- document.addEventListener("page:fetch", unbindEvents);
+ document.addEventListener("page:fetch", gl.utils.cleanupBeforeFetch);
window.addEventListener("hashchange", shiftWindow);
diff --git a/app/assets/javascripts/blob/template_selector.js.es6 b/app/assets/javascripts/blob/template_selector.js.es6
index 4e309e480b0..2d5c6ade053 100644
--- a/app/assets/javascripts/blob/template_selector.js.es6
+++ b/app/assets/javascripts/blob/template_selector.js.es6
@@ -68,14 +68,10 @@
// To be implemented on the extending class
// e.g.
// Api.gitignoreText item.name, @requestFileSuccess.bind(@)
- requestFileSuccess(file, { skipFocus, append } = {}) {
+ requestFileSuccess(file, { skipFocus } = {}) {
const oldValue = this.editor.getValue();
let newValue = file.content;
- if (append && oldValue.length && oldValue !== newValue) {
- newValue = oldValue + '\n\n' + newValue;
- }
-
this.editor.setValue(newValue, 1);
if (!skipFocus) this.editor.focus();
@@ -99,4 +95,3 @@
global.TemplateSelector = TemplateSelector;
})(window.gl || ( window.gl = {}));
-
diff --git a/app/assets/javascripts/extensions/element.js.es6 b/app/assets/javascripts/extensions/element.js.es6
new file mode 100644
index 00000000000..d5d4af3573c
--- /dev/null
+++ b/app/assets/javascripts/extensions/element.js.es6
@@ -0,0 +1,6 @@
+Element.prototype.matches = Element.prototype.matches || Element.prototype.msMatches;
+
+Element.prototype.closest = function closest(selector, selectedElement = this) {
+ if (!selectedElement) return;
+ return selectedElement.matches(selector) ? selectedElement : Element.prototype.closest(selector, selectedElement.parentElement);
+};
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index b170e26eebf..698abae6228 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -43,6 +43,14 @@
parser.href = url;
return parser;
};
+
+ gl.utils.cleanupBeforeFetch = function() {
+ // Unbind scroll events
+ $(document).off('scroll');
+ // Close any open tooltips
+ $('.has-tooltip, [data-toggle="tooltip"]').tooltip('destroy');
+ };
+
return jQuery.timefor = function(time, suffix, expiredLabel) {
var suffixFromNow, timefor;
if (!time) {
diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6
index a7624de6089..0fa56df0d2a 100644
--- a/app/assets/javascripts/pipelines.js.es6
+++ b/app/assets/javascripts/pipelines.js.es6
@@ -2,36 +2,39 @@
class Pipelines {
constructor() {
- $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph);
+ this.initGraphToggle();
this.addMarginToBuildColumns();
}
- toggleGraph() {
- const $pipelineBtn = $(this).closest('.toggle-pipeline-btn');
- const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph');
- const $btnText = $(this).find('.toggle-btn-text');
- const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed');
-
- $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed');
-
+ initGraphToggle() {
+ this.pipelineGraph = document.querySelector('.pipeline-graph');
+ this.toggleButton = document.querySelector('.toggle-pipeline-btn');
+ this.toggleButtonText = this.toggleButton.querySelector('.toggle-btn-text');
+ this.toggleButton.addEventListener('click', this.toggleGraph.bind(this));
+ }
- graphCollapsed ? $btnText.text('Hide') : $btnText.text('Expand')
+ toggleGraph() {
+ const graphCollapsed = this.pipelineGraph.classList.contains('graph-collapsed');
+ this.toggleButton.classList.toggle('graph-collapsed');
+ this.pipelineGraph.classList.toggle('graph-collapsed');
+ this.toggleButtonText.textContent = graphCollapsed ? 'Hide' : 'Expand';
}
addMarginToBuildColumns() {
- const $secondChildBuildNode = $('.build:nth-child(2)');
- if ($secondChildBuildNode.length) {
- const $firstChildBuildNode = $secondChildBuildNode.prev('.build');
- const $multiBuildColumn = $secondChildBuildNode.closest('.stage-column');
- const $previousColumn = $multiBuildColumn.prev('.stage-column');
- $multiBuildColumn.addClass('left-margin');
- $firstChildBuildNode.addClass('left-connector');
- $previousColumn.each(function() {
- $this = $(this);
- if ($('.build', $this).length === 1) $this.addClass('no-margin');
- });
+ const secondChildBuildNodes = this.pipelineGraph.querySelectorAll('.build:nth-child(2)');
+ for (buildNodeIndex in secondChildBuildNodes) {
+ const buildNode = secondChildBuildNodes[buildNodeIndex];
+ const firstChildBuildNode = buildNode.previousElementSibling;
+ if (!firstChildBuildNode || !firstChildBuildNode.matches('.build')) continue;
+ const multiBuildColumn = buildNode.closest('.stage-column');
+ const previousColumn = multiBuildColumn.previousElementSibling;
+ if (!previousColumn || !previousColumn.matches('.stage-column')) continue;
+ multiBuildColumn.classList.add('left-margin');
+ firstChildBuildNode.classList.add('left-connector');
+ const columnBuilds = previousColumn.querySelectorAll('.build');
+ if (columnBuilds.length === 1) previousColumn.classList.add('no-margin');
}
- $('.pipeline-graph').removeClass('hidden');
+ this.pipelineGraph.classList.remove('hidden');
}
}
diff --git a/app/assets/javascripts/templates/issuable_template_selector.js.es6 b/app/assets/javascripts/templates/issuable_template_selector.js.es6
index bd4e3c3d00d..fa1b79c8415 100644
--- a/app/assets/javascripts/templates/issuable_template_selector.js.es6
+++ b/app/assets/javascripts/templates/issuable_template_selector.js.es6
@@ -32,24 +32,22 @@
this.currentTemplate = currentTemplate;
if (err) return; // Error handled by global AJAX error handler
this.stopLoadingSpinner();
- this.setInputValueToTemplateContent(true);
+ this.setInputValueToTemplateContent();
});
return;
}
- setInputValueToTemplateContent(append) {
+ setInputValueToTemplateContent() {
// `this.requestFileSuccess` sets the value of the description input field
- // to the content of the template selected. If `append` is true, the
- // template content will be appended to the previous value of the field,
- // separated by a blank line if the previous value is non-empty.
+ // to the content of the template selected.
if (this.titleInput.val() === '') {
// If the title has not yet been set, focus the title input and
// skip focusing the description input by setting `true` as the
// `skipFocus` option to `requestFileSuccess`.
- this.requestFileSuccess(this.currentTemplate, {skipFocus: true, append});
+ this.requestFileSuccess(this.currentTemplate, {skipFocus: true});
this.titleInput.focus();
} else {
- this.requestFileSuccess(this.currentTemplate, {skipFocus: false, append});
+ this.requestFileSuccess(this.currentTemplate, {skipFocus: false});
}
return;
}
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 98d3889cd44..f1d36efb3de 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -37,7 +37,8 @@
}
@include keyframes(pulse) {
- from, to {
+ from,
+ to {
@include webkit-prefix(transform, scale3d(1, 1, 1));
}
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index df2e2ea8d2c..7e168092522 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -128,7 +128,8 @@
position: relative;
.avatar-holder {
- .avatar, .identicon {
+ .avatar,
+ .identicon {
margin: 0 auto;
float: none;
}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index e6656c2d69a..c0e9c8bf829 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -213,7 +213,8 @@
top: 2px;
}
- svg, .fa {
+ svg,
+ .fa {
&:not(:last-child) {
margin-right: 3px;
}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 800e2dba018..ad5ac589d0f 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -143,7 +143,8 @@ li.note {
}
}
-.wiki_content code, .readme code {
+.wiki_content code,
+.readme code {
background-color: inherit;
}
@@ -350,7 +351,8 @@ table {
margin-right: 10px;
}
-.alert, .progress {
+.alert,
+.progress {
margin-bottom: $gl-padding;
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 224bc58f7a7..1de246600fd 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -275,7 +275,8 @@
a {
padding-left: 25px;
- &.is-indeterminate, &.is-active {
+ &.is-indeterminate,
+ &.is-active {
&::before {
position: absolute;
left: 5px;
@@ -373,7 +374,8 @@
}
}
-.dropdown-input-field, .default-dropdown-input {
+.dropdown-input-field,
+.default-dropdown-input {
width: 100%;
min-height: 30px;
padding: 0 7px;
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index a55dcf4a699..a9006de6d3e 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -18,7 +18,8 @@
margin: 0;
}
- .flash-notice, .flash-alert {
+ .flash-notice,
+ .flash-alert {
border-radius: $border-radius-default;
.container-fluid,
@@ -30,7 +31,8 @@
&.flash-container-page {
margin-bottom: 0;
- .flash-notice, .flash-alert {
+ .flash-notice,
+ .flash-alert {
border-radius: 0;
}
}
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index fe834f4e2f6..3f877d86a26 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -25,7 +25,9 @@
a {
color: $color-light;
- &:hover, &:focus, &:active {
+ &:hover,
+ &:focus,
+ &:active {
background: $color-dark;
}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 3a4fdd0da22..142076f65b2 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -15,7 +15,8 @@ header {
margin: 8px 0;
text-align: center;
- .tanuki-logo, img {
+ .tanuki-logo,
+ img {
height: 36px;
}
}
@@ -54,7 +55,9 @@ header {
line-height: 28px;
text-align: center;
- &:hover, &:focus, &:active {
+ &:hover,
+ &:focus,
+ &:active {
background-color: $background-color;
}
@@ -125,7 +128,8 @@ header {
left: -50%;
}
- svg, img {
+ svg,
+ img {
height: 36px;
}
@@ -222,7 +226,8 @@ header {
margin: 0;
float: none !important;
- .visible-xs, .visable-sm {
+ .visible-xs,
+ .visable-sm {
display: table-cell !important;
}
}
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 4b2627c1b87..48e34a0066e 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -76,14 +76,16 @@
/** light list with border-bottom between li **/
-ul.bordered-list, ul.unstyled-list {
+ul.bordered-list,
+ul.unstyled-list {
@include basic-list;
&.top-list {
li:first-child {
padding-top: 0;
- h4, h5 {
+ h4,
+ h5 {
margin-top: 0;
}
}
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 9fe390eb09d..c1ed43bc20f 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -79,7 +79,8 @@
padding-left: 15px !important;
}
- .nav-links, .nav-links {
+ .nav-links,
+ .nav-links {
li a {
font-size: 14px;
padding: 19px 10px;
@@ -99,18 +100,21 @@
@media (max-width: $screen-sm-max) {
.issues-filters {
- .milestone-filter, .labels-filter {
+ .milestone-filter,
+ .labels-filter {
display: none;
}
}
.page-title {
- .note-created-ago, .new-issue-link {
+ .note-created-ago,
+ .new-issue-link {
display: none;
}
}
- .issue_edited_ago, .note_edited_ago {
+ .issue_edited_ago,
+ .note_edited_ago {
display: none;
}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 899db045b74..fcaf5e18633 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -54,7 +54,9 @@
color: #959494;
border-bottom: 2px solid transparent;
- &:hover, &:active, &:focus {
+ &:hover,
+ &:active,
+ &:focus {
text-decoration: none;
outline: none;
}
@@ -211,7 +213,11 @@
padding-bottom: 0;
width: 100%;
- .btn, form, .dropdown, .dropdown-menu-toggle, .form-control {
+ .btn,
+ form,
+ .dropdown,
+ .dropdown-menu-toggle,
+ .form-control {
margin: 0 0 10px;
display: block;
width: 100%;
@@ -245,7 +251,8 @@
}
&.adjust {
- .nav-text, .nav-controls {
+ .nav-text,
+ .nav-controls {
width: auto;
}
}
@@ -309,13 +316,15 @@
padding-top: 10px;
}
- a, i {
+ a,
+ i {
color: $layout-link-gray;
}
&.active {
- a, i {
+ a,
+ i {
color: $black;
}
@@ -328,7 +337,8 @@
}
&:hover {
- a, i {
+ a,
+ i {
color: $black;
}
}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index e0708c65695..ecdf0be1a05 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -3,7 +3,8 @@
width: 100% !important;
}
-.select2-container, .select2-container.select2-drop-above {
+.select2-container,
+.select2-container.select2-drop-above {
.select2-choice {
background: #fff;
border-color: $input-border;
@@ -71,7 +72,8 @@
}
.select2-container-active {
- .select2-choice, .select2-choices {
+ .select2-choice,
+ .select2-choices {
box-shadow: none;
}
}
diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss
index b42075c98d0..9a90d3794fd 100644
--- a/app/assets/stylesheets/framework/tables.scss
+++ b/app/assets/stylesheets/framework/tables.scss
@@ -23,7 +23,8 @@ table {
}
tr {
- td, th {
+ td,
+ th {
padding: 10px $gl-padding;
line-height: 20px;
vertical-align: middle;
diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
index f4106641269..59f4594bb83 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -126,7 +126,8 @@
box-shadow: none;
.panel-body {
- form, pre {
+ form,
+ pre {
margin: 0;
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 55de9053be5..266a8024809 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -131,12 +131,14 @@
font-weight: inherit;
}
- ul, ol {
+ ul,
+ ol {
padding: 0;
margin: 3px 0 3px 28px !important;
}
- ul:dir(rtl), ol:dir(rtl) {
+ ul:dir(rtl),
+ ol:dir(rtl) {
margin: 3px 28px 3px 0 !important;
}
@@ -144,7 +146,8 @@
line-height: 1.6em;
}
- a[href*="/uploads/"], a[href*="storage.googleapis.com/google-code-attachments/"] {
+ a[href*="/uploads/"],
+ a[href*="storage.googleapis.com/google-code-attachments/"] {
&:before {
margin-right: 4px;
@@ -167,7 +170,12 @@
}
/* Link to current header. */
- h1, h2, h3, h4, h5, h6 {
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
position: relative;
a.anchor {
@@ -215,7 +223,12 @@ body {
margin: 12px 7px;
}
-h1, h2, h3, h4, h5, h6 {
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
color: $gl-title-color;
font-weight: 600;
}
@@ -273,7 +286,10 @@ a > code {
text-decoration: line-through;
}
-h1, h2, h3, h4 {
+h1,
+h2,
+h3,
+h4 {
small {
color: $gl-gray;
}
diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index a3acee299e3..d22d9b01495 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -1,20 +1,25 @@
/* https://github.com/MozMorris/tomorrow-pygments */
.code.dark {
// Line numbers
- .line-numbers, .diff-line-num {
+ .line-numbers,
+ .diff-line-num {
background-color: #1d1f21;
}
- .diff-line-num, .diff-line-num a {
+ .diff-line-num,
+ .diff-line-num a {
color: rgba(255, 255, 255, 0.3);
}
// Code itself
- pre.code, .diff-line-num {
+ pre.code,
+ .diff-line-num {
border-color: #666;
}
- &, pre.code, .line_holder .line_content {
+ &,
+ pre.code,
+ .line_holder .line_content {
background-color: #1d1f21;
color: #c5c8c6;
}
@@ -31,11 +36,13 @@
border-color: darken(#557, 15%);
}
- .diff-line-num.new, .line_content.new {
+ .diff-line-num.new,
+ .line_content.new {
@include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.2), #808080);
}
- .diff-line-num.old, .line_content.old {
+ .diff-line-num.old,
+ .line_content.old {
@include diff_background(rgba(255, 51, 51, 0.2), rgba(255, 51, 51, 0.25), #808080);
}
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index e9228c94db9..db8da8aab10 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -1,20 +1,25 @@
/* https://github.com/richleland/pygments-css/blob/master/monokai.css */
.code.monokai {
// Line numbers
- .line-numbers, .diff-line-num {
+ .line-numbers,
+ .diff-line-num {
background-color: #272822;
}
- .diff-line-num, .diff-line-num a {
+ .diff-line-num,
+ .diff-line-num a {
color: rgba(255, 255, 255, 0.3);
}
// Code itself
- pre.code, .diff-line-num {
+ pre.code,
+ .diff-line-num {
border-color: #555;
}
- &, pre.code, .line_holder .line_content {
+ &,
+ pre.code,
+ .line_holder .line_content {
background-color: #272822;
color: #f8f8f2;
}
@@ -31,11 +36,13 @@
border-color: darken(#49483e, 15%);
}
- .diff-line-num.new, .line_content.new {
+ .diff-line-num.new,
+ .line_content.new {
@include diff_background(rgba(166, 226, 46, 0.1), rgba(166, 226, 46, 0.15), #808080);
}
- .diff-line-num.old, .line_content.old {
+ .diff-line-num.old,
+ .line_content.old {
@include diff_background(rgba(254, 147, 140, 0.15), rgba(254, 147, 140, 0.2), #808080);
}
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss
index c3c7773b9e2..a87333146de 100644
--- a/app/assets/stylesheets/highlight/solarized_dark.scss
+++ b/app/assets/stylesheets/highlight/solarized_dark.scss
@@ -1,20 +1,25 @@
/* https://gist.github.com/qguv/7936275 */
.code.solarized-dark {
// Line numbers
- .line-numbers, .diff-line-num {
+ .line-numbers,
+ .diff-line-num {
background-color: #002b36;
}
- .diff-line-num, .diff-line-num a {
+ .diff-line-num,
+ .diff-line-num a {
color: rgba(255, 255, 255, 0.3);
}
// Code itself
- pre.code, .diff-line-num {
+ pre.code,
+ .diff-line-num {
border-color: #113b46;
}
- &, pre.code, .line_holder .line_content {
+ &,
+ pre.code,
+ .line_holder .line_content {
background-color: #002b36;
color: #93a1a1;
}
@@ -31,11 +36,13 @@
border-color: darken(#174652, 15%);
}
- .diff-line-num.new, .line_content.new {
+ .diff-line-num.new,
+ .line_content.new {
@include diff_background(rgba(133, 153, 0, 0.15), rgba(133, 153, 0, 0.25), #113b46);
}
- .diff-line-num.old, .line_content.old {
+ .diff-line-num.old,
+ .line_content.old {
@include diff_background(rgba(220, 50, 47, 0.3), rgba(220, 50, 47, 0.25), #113b46);
}
diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss
index 5956a28cafe..faff353ded7 100644
--- a/app/assets/stylesheets/highlight/solarized_light.scss
+++ b/app/assets/stylesheets/highlight/solarized_light.scss
@@ -7,20 +7,25 @@
.code.solarized-light {
// Line numbers
- .line-numbers, .diff-line-num {
+ .line-numbers,
+ .diff-line-num {
background-color: #fdf6e3;
}
- .diff-line-num, .diff-line-num a {
+ .diff-line-num,
+ .diff-line-num a {
color: $black-transparent;
}
// Code itself
- pre.code, .diff-line-num {
+ pre.code,
+ .diff-line-num {
border-color: #c5d0d4;
}
- &, pre.code, .line_holder .line_content {
+ &,
+ pre.code,
+ .line_holder .line_content {
background-color: #fdf6e3;
color: #586e75;
}
@@ -37,11 +42,13 @@
border-color: darken(#ddd8c5, 15%);
}
- .diff-line-num.new, .line_content.new {
+ .diff-line-num.new,
+ .line_content.new {
@include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.25), #c5d0d4);
}
- .diff-line-num.old, .line_content.old {
+ .diff-line-num.old,
+ .line_content.old {
@include diff_background(rgba(220, 50, 47, 0.2), rgba(220, 50, 47, 0.25), #c5d0d4);
}
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 6f31a5235c0..d5367d5f3f0 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -7,20 +7,25 @@
.code.white {
// Line numbers
- .line-numbers, .diff-line-num {
+ .line-numbers,
+ .diff-line-num {
background-color: $background-color;
}
- .diff-line-num, .diff-line-num a {
+ .diff-line-num,
+ .diff-line-num a {
color: $black-transparent;
}
// Code itself
- pre.code, .diff-line-num {
+ pre.code,
+ .diff-line-num {
border-color: $table-border-gray;
}
- &, pre.code, .line_holder .line_content {
+ &,
+ pre.code,
+ .line_holder .line_content {
background-color: #fff;
color: #333;
}
diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss
index 140d589024b..63396a6bb29 100644
--- a/app/assets/stylesheets/pages/admin.scss
+++ b/app/assets/stylesheets/pages/admin.scss
@@ -56,7 +56,8 @@
padding: 10px;
text-align: center;
- > div, p {
+ > div,
+ p {
display: inline;
margin: 0;
diff --git a/app/assets/stylesheets/pages/ci_projects.scss b/app/assets/stylesheets/pages/ci_projects.scss
index 67a9d7d2cf7..87c453a7a27 100644
--- a/app/assets/stylesheets/pages/ci_projects.scss
+++ b/app/assets/stylesheets/pages/ci_projects.scss
@@ -12,7 +12,8 @@
border-color: $border-color;
}
- th, td {
+ th,
+ td {
padding: 10px $gl-padding;
}
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index 264e7e01a34..8ecac08137b 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -2,14 +2,16 @@
display: block;
}
-.commit-author, .commit-committer {
+.commit-author,
+.commit-committer {
display: block;
color: #999;
font-weight: normal;
font-style: italic;
}
-.commit-author strong, .commit-committer strong {
+.commit-author strong,
+.commit-committer strong {
font-weight: bold;
font-style: normal;
}
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 2b5621e20d6..ad315cfae62 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -63,7 +63,8 @@
display: inline-block;
}
- .btn-clipboard, .btn-transparent {
+ .btn-clipboard,
+ .btn-transparent {
padding-left: 0;
padding-right: 0;
}
@@ -162,7 +163,8 @@
.branch-commit {
color: $gl-gray;
- .commit-id, .commit-row-message {
+ .commit-id,
+ .commit-row-message {
color: $gl-gray;
}
}
diff --git a/app/assets/stylesheets/pages/confirmation.scss b/app/assets/stylesheets/pages/confirmation.scss
index 292225c5261..81e5cee240d 100644
--- a/app/assets/stylesheets/pages/confirmation.scss
+++ b/app/assets/stylesheets/pages/confirmation.scss
@@ -2,7 +2,12 @@
margin-bottom: 20px;
border-bottom: 1px solid #eee;
- > h1, h2, h3, h4, h5, h6 {
+ > h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
font-weight: 400;
}
@@ -10,7 +15,8 @@
margin-bottom: 20px;
}
- ul, ol {
+ ul,
+ ol {
padding-left: 0;
}
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 2357671c2ae..0f0c0abe7ae 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -13,7 +13,8 @@
color: #5c5d5e;
}
- .issue_created_ago, .author_link {
+ .issue_created_ago,
+ .author_link {
white-space: nowrap;
}
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index f8e3ca29a2b..e0367d1d942 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -124,7 +124,8 @@
}
}
- .old_line, .new_line {
+ .old_line,
+ .new_line {
margin: 0;
padding: 0;
border: none;
@@ -281,7 +282,8 @@
position: relative;
}
- .frame.added, .frame.deleted {
+ .frame.added,
+ .frame.deleted {
position: absolute;
display: block;
top: 0;
@@ -347,7 +349,8 @@
text-align: center;
background: #eee;
- ul, li {
+ ul,
+ li {
list-style: none;
margin: 0;
padding: 0;
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index 029dabd2138..cb8cefaca97 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -91,7 +91,9 @@
}
}
- .gitignore-selector, .license-selector, .gitlab-ci-yml-selector {
+ .gitignore-selector,
+ .license-selector,
+ .gitlab-ci-yml-selector {
.dropdown {
line-height: 21px;
}
diff --git a/app/assets/stylesheets/pages/errors.scss b/app/assets/stylesheets/pages/errors.scss
index 32d2d7b1dbf..11309817d31 100644
--- a/app/assets/stylesheets/pages/errors.scss
+++ b/app/assets/stylesheets/pages/errors.scss
@@ -2,7 +2,9 @@
max-width: 400px;
margin: 0 auto;
- h1, h2, h3 {
+ h1,
+ h2,
+ h3 {
text-align: center;
}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 623da67a239..3e7fc3fa52c 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -43,7 +43,8 @@ ul.related-merge-requests > li {
}
}
-.merge-requests-title, .related-branches-title {
+.merge-requests-title,
+.related-branches-title {
font-size: 16px;
font-weight: 600;
}
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index 2be9453aaee..3d2b024fe5c 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -41,7 +41,8 @@
font-size: 13px;
}
- .login-box, .omniauth-container {
+ .login-box,
+ .omniauth-container {
box-shadow: 0 0 0 1px $border-color;
border-bottom-right-radius: 2px;
border-bottom-left-radius: 2px;
@@ -198,7 +199,8 @@
.form-control {
- &:active, &:focus {
+ &:active,
+ &:focus {
background-color: #fff;
}
}
@@ -261,7 +263,8 @@
position: relative;
}
- .footer-container, hr.footer-fixed {
+ .footer-container,
+ hr.footer-fixed {
position: absolute;
bottom: 0;
left: 0;
diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss
index 2e917361b25..032feae8854 100644
--- a/app/assets/stylesheets/pages/merge_conflicts.scss
+++ b/app/assets/stylesheets/pages/merge_conflicts.scss
@@ -101,7 +101,8 @@ $colors: (
@mixin color-scheme($color) {
- .header.line_content, .diff-line-num {
+ .header.line_content,
+ .diff-line-num {
&.origin {
background-color: map-get($colors, #{$color}_header_origin_neutral);
border-color: map-get($colors, #{$color}_header_origin_neutral);
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index dd6d1783667..13402acd8e1 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -50,7 +50,8 @@
}
}
-.issues-sortable-list, .merge_requests-sortable-list {
+.issues-sortable-list,
+.merge_requests-sortable-list {
.issuable-detail {
display: block;
margin-top: 7px;
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 25c1bbdc1c9..16ddef481bd 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -24,7 +24,8 @@
display: none;
}
-.new-note, .note-edit-form {
+.new-note,
+.note-edit-form {
.note-form-actions {
margin-top: $gl-padding;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index faa0fc82ca8..b90c91831f2 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -28,7 +28,8 @@ ul.notes {
}
}
- .note-created-ago, .note-updated-at {
+ .note-created-ago,
+ .note-updated-at {
white-space: nowrap;
}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 5b8dc7f8c40..f88175365c6 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -248,7 +248,8 @@
font-size: 14px;
}
- svg, .fa {
+ svg,
+ .fa {
margin-right: 0;
}
}
@@ -529,7 +530,8 @@
// Connect each build (except for first) with curved lines
&:not(:first-child) {
- &::after, &::before {
+ &::after,
+ &::before {
content: '';
top: -49px;
position: absolute;
@@ -555,7 +557,8 @@
// Connect second build to first build with smaller curved line
&:nth-child(2) {
- &::after, &::before {
+ &::after,
+ &::before {
height: 29px;
top: -9px;
}
@@ -570,7 +573,8 @@
.build {
// Remove right connecting horizontal line from first build in last stage
&:first-child {
- &::after, &::before {
+ &::after,
+ &::before {
border: none;
}
}
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index ed80d2beec2..3f6fdaebc1d 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -253,7 +253,8 @@
}
table.u2f-registrations {
- th:not(:last-child), td:not(:last-child) {
+ th:not(:last-child),
+ td:not(:last-child) {
border-right: solid 1px transparent;
}
} \ No newline at end of file
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index fe7cf3c87e3..f6355941837 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -6,7 +6,8 @@
}
}
-.no-ssh-key-message, .project-limit-message {
+.no-ssh-key-message,
+.project-limit-message {
background-color: #f28d35;
margin-bottom: 0;
}
@@ -385,7 +386,8 @@ a.deploy-project-label {
text-align: center;
width: 169px;
- &:hover, &.forked {
+ &:hover,
+ &.forked {
background-color: $row-hover;
border-color: $row-hover-border;
}
@@ -734,7 +736,8 @@ pre.light-well {
.table-bordered {
border-radius: 1px;
- th:not(:last-child), td:not(:last-child) {
+ th:not(:last-child),
+ td:not(:last-child) {
border-right: solid 1px transparent;
}
}
@@ -757,7 +760,8 @@ pre.light-well {
}
}
-.project-refs-form .dropdown-menu, .dropdown-menu-projects {
+.project-refs-form .dropdown-menu,
+.dropdown-menu-projects {
width: 300px;
@media (min-width: $screen-sm-min) {
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index e77f9816d8a..6d472e8293f 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -65,7 +65,8 @@
.search-input-wrap {
width: 100%;
- .search-icon, .clear-icon {
+ .search-icon,
+ .clear-icon {
position: absolute;
right: 5px;
top: 0;
@@ -185,7 +186,8 @@
padding-right: $gl-padding + 15px;
}
- .btn-search, .btn-new {
+ .btn-search,
+ .btn-new {
width: 100%;
margin-top: 5px;
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 84dcd6835d5..2b836fa1f4a 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -23,7 +23,8 @@
border-bottom: 1px solid $table-border-gray;
border-top: 1px solid $table-border-gray;
- td, th {
+ td,
+ th {
line-height: 21px;
}
@@ -74,7 +75,8 @@
max-width: 320px;
vertical-align: middle;
- i, a {
+ i,
+ a {
color: $gl-dark-link-color;
}
diff --git a/app/assets/stylesheets/print.scss b/app/assets/stylesheets/print.scss
index a30b6492572..8239b7e6879 100644
--- a/app/assets/stylesheets/print.scss
+++ b/app/assets/stylesheets/print.scss
@@ -1,7 +1,24 @@
-.wiki h1, .wiki h2, .wiki h3, .wiki h4, .wiki h5, .wiki h6 {margin-top: 17px; }
-.wiki h1 {font-size: 30px;}
-.wiki h2 {font-size: 22px;}
-.wiki h3 {font-size: 18px; font-weight: bold; }
+.wiki h1,
+.wiki h2,
+.wiki h3,
+.wiki h4,
+.wiki h5,
+.wiki h6 {
+ margin-top: 17px;
+}
+
+.wiki h1 {
+ font-size: 30px;
+}
+
+.wiki h2 {
+ font-size: 22px;
+}
+
+.wiki h3 {
+ font-size: 18px;
+ font-weight: bold;
+}
header,
nav,
diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb
index 95e62cdb02a..44484d64567 100644
--- a/app/finders/labels_finder.rb
+++ b/app/finders/labels_finder.rb
@@ -50,7 +50,7 @@ class LabelsFinder < UnionFinder
end
def projects_ids
- params[:project_ids].presence
+ params[:project_ids]
end
def title
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index b7247ffa8b2..38c586ccd31 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -5,7 +5,7 @@ module BoardsHelper
{
endpoint: namespace_project_boards_path(@project.namespace, @project),
board_id: board.id,
- disabled: !can?(current_user, :admin_list, @project),
+ disabled: "#{!can?(current_user, :admin_list, @project)}",
issue_link_base: namespace_project_issues_path(@project.namespace, @project)
}
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index b8a10b7968e..dd65a9a8b86 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -299,8 +299,10 @@ class MergeRequestDiff < ActiveRecord::Base
end
def keep_around_commits
- repository.keep_around(start_commit_sha)
- repository.keep_around(head_commit_sha)
- repository.keep_around(base_commit_sha)
+ [repository, merge_request.source_project.repository].each do |repo|
+ repo.keep_around(start_commit_sha)
+ repo.keep_around(head_commit_sha)
+ repo.keep_around(base_commit_sha)
+ end
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 521879444d4..9e76df63d31 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -309,7 +309,7 @@ class User < ActiveRecord::Base
username
end
- def to_reference(_from_project = nil)
+ def to_reference(_from_project = nil, _target_project = nil)
"#{self.class.reference_prefix}#{username}"
end
diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml
index 0b05785430b..61020516bcf 100644
--- a/app/views/projects/merge_requests/show/_commits.html.haml
+++ b/app/views/projects/merge_requests/show/_commits.html.haml
@@ -3,4 +3,4 @@
Most recent commits displayed first
%ol#commits-list.list-unstyled
- = render "projects/commits/commits", project: @merge_request.project
+ = render "projects/commits/commits", project: @merge_request.source_project
diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb
index 71b274e0c99..4dfa745fb50 100644
--- a/app/workers/project_cache_worker.rb
+++ b/app/workers/project_cache_worker.rb
@@ -9,6 +9,18 @@ class ProjectCacheWorker
LEASE_TIMEOUT = 15.minutes.to_i
+ def self.lease_for(project_id)
+ Gitlab::ExclusiveLease.
+ new("project_cache_worker:#{project_id}", timeout: LEASE_TIMEOUT)
+ end
+
+ # Overwrite Sidekiq's implementation so we only schedule when actually needed.
+ def self.perform_async(project_id)
+ # If a lease for this project is still being held there's no point in
+ # scheduling a new job.
+ super unless lease_for(project_id).exists?
+ end
+
def perform(project_id)
if try_obtain_lease_for(project_id)
Rails.logger.
@@ -37,8 +49,6 @@ class ProjectCacheWorker
end
def try_obtain_lease_for(project_id)
- Gitlab::ExclusiveLease.
- new("project_cache_worker:#{project_id}", timeout: LEASE_TIMEOUT).
- try_obtain
+ self.class.lease_for(project_id).try_obtain
end
end
diff --git a/doc/development/README.md b/doc/development/README.md
index fb6a8a5b095..14d6f08e43a 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -8,6 +8,8 @@
## Styleguides
+- [API styleguide](api_styleguide.md) Use this styleguide if you are
+ contributing to the API.
- [Documentation styleguide](doc_styleguide.md) Use this styleguide if you are
contributing to documentation.
- [SQL Migration Style Guide](migration_style_guide.md) for creating safe SQL migrations
diff --git a/doc/development/api_styleguide.md b/doc/development/api_styleguide.md
new file mode 100644
index 00000000000..ce444ebdde4
--- /dev/null
+++ b/doc/development/api_styleguide.md
@@ -0,0 +1,96 @@
+# API styleguide
+
+This styleguide recommends best practices for API development.
+
+## Instance variables
+
+Please do not use instance variables, there is no need for them (we don't need
+to access them as we do in Rails views), local variables are fine.
+
+## Entities
+
+Always use an [Entity] to present the endpoint's payload.
+
+## Methods and parameters description
+
+Every method must be described using the [Grape DSL](https://github.com/ruby-grape/grape#describing-methods)
+(see https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/api/environments.rb
+for a good example):
+
+- `desc` for the method summary. You should pass it a block for additional
+ details such as:
+ - The GitLab version when the endpoint was added
+ - If the endpoint is deprecated, and if so, when will it be removed
+
+- `params` for the method params. This acts as description,
+ [validation, and coercion of the parameters]
+
+A good example is as follows:
+
+```ruby
+desc 'Get all broadcast messages' do
+ detail 'This feature was introduced in GitLab 8.12.'
+ success Entities::BroadcastMessage
+end
+params do
+ optional :page, type: Integer, desc: 'Current page number'
+ optional :per_page, type: Integer, desc: 'Number of messages per page'
+end
+get do
+ messages = BroadcastMessage.all
+
+ present paginate(messages), with: Entities::BroadcastMessage
+end
+```
+
+## Declared params
+
+> Grape allows you to access only the parameters that have been declared by your
+`params` block. It filters out the params that have been passed, but are not
+allowed.
+
+– https://github.com/ruby-grape/grape#declared
+
+### Exclude params from parent namespaces!
+
+> By default `declared(params) `includes parameters that were defined in all
+parent namespaces.
+
+– https://github.com/ruby-grape/grape#include-parent-namespaces
+
+In most cases you will want to exclude params from the parent namespaces:
+
+```ruby
+declared(params, include_parent_namespaces: false)
+```
+
+### When to use `declared(params)`?
+
+You should always use `declared(params)` when you pass the params hash as
+arguments to a method call.
+
+For instance:
+
+```ruby
+# bad
+User.create(params) # imagine the user submitted `admin=1`... :)
+
+# good
+User.create(declared(params, include_parent_namespaces: false).to_h)
+```
+
+>**Note:**
+`declared(params)` return a `Hashie::Mash` object, on which you will have to
+call `.to_h`.
+
+But we can use `params[key]` directly when we access single elements.
+
+For instance:
+
+```ruby
+# good
+Model.create(foo: params[:foo])
+```
+
+[Entity]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/api/entities.rb
+[validation, and coercion of the parameters]: https://github.com/ruby-grape/grape#parameter-validation-and-coercion
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index f07d2c9af2d..4cc581dd991 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -342,12 +342,6 @@ You can use the following fake tokens as examples.
Here is a list of must-have items. Use them in the exact order that appears
on this document. Further explanation is given below.
-- Every method must be described using [Grape's DSL](https://github.com/ruby-grape/grape/tree/v0.13.0#describing-methods)
- (see https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/api/environments.rb
- for a good example):
- - `desc` for the method summary (you can pass it a block for additional details)
- - `params` for the method params (this acts as description **and** validation
- of the params)
- Every method must have the REST API request. For example:
```
diff --git a/doc/update/8.12-to-8.13.md b/doc/update/8.12-to-8.13.md
index 8940d14559b..c0084d9d59c 100644
--- a/doc/update/8.12-to-8.13.md
+++ b/doc/update/8.12-to-8.13.md
@@ -72,7 +72,7 @@ sudo -u git -H git checkout 8-13-stable-ee
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch --all --tags
-sudo -u git -H git checkout v3.6.3
+sudo -u git -H git checkout v3.6.6
```
### 6. Update gitlab-workhorse
diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md
index 8827b501901..60b7bec2ba7 100644
--- a/doc/user/project/new_ci_build_permissions_model.md
+++ b/doc/user/project/new_ci_build_permissions_model.md
@@ -254,6 +254,12 @@ test:
This will make GitLab CI initialize (fetch) and update (checkout) all your
submodules recursively.
+If Git does not use the newly added relative URLs but still uses your old URLs,
+you might need to add `git submodule sync --recursive` to your `.gitlab-ci.yml`,
+prior to running `git submodule update --init --recursive`. This transfers the
+changes from your `.gitmodules` file into the `.git` folder, which is kept by
+runners between runs.
+
In case your environment or your Docker image doesn't have Git installed,
you have to either ask your Administrator or install the missing dependency
yourself:
diff --git a/lib/api/users.rb b/lib/api/users.rb
index e868f628404..c28e07a76b7 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -333,11 +333,11 @@ module API
user = User.find_by(id: declared(params).id)
not_found!('User') unless user
- events = user.recent_events.
+ events = user.events.
merge(ProjectsFinder.new.execute(current_user)).
references(:project).
with_associations.
- page(params[:page])
+ recent
present paginate(events), with: Entities::Event
end
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 4fa8d05481f..f09d78be0ce 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -52,8 +52,8 @@ module Banzai
relative_url_root,
context[:project].path_with_namespace,
uri_type(file_path),
- ref,
- file_path
+ Addressable::URI.escape(ref),
+ Addressable::URI.escape(file_path)
].compact.join('/').squeeze('/').chomp('/')
uri
diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb
new file mode 100644
index 00000000000..b1a6d5fe0f6
--- /dev/null
+++ b/lib/gitlab/ee_compat_check.rb
@@ -0,0 +1,261 @@
+# rubocop: disable Rails/Output
+module Gitlab
+ # Checks if a set of migrations requires downtime or not.
+ class EeCompatCheck
+ EE_REPO = 'https://gitlab.com/gitlab-org/gitlab-ee.git'.freeze
+
+ attr_reader :ce_branch, :check_dir, :ce_repo
+
+ def initialize(branch:, check_dir:, ce_repo: nil)
+ @ce_branch = branch
+ @check_dir = check_dir
+ @ce_repo = ce_repo || 'https://gitlab.com/gitlab-org/gitlab-ce.git'
+ end
+
+ def check
+ ensure_ee_repo
+ delete_patches
+
+ generate_patch(ce_branch, ce_patch_full_path)
+
+ Dir.chdir(check_dir) do
+ step("In the #{check_dir} directory")
+
+ step("Pulling latest master", %w[git pull --ff-only origin master])
+
+ status = catch(:halt_check) do
+ ce_branch_compat_check!
+
+ delete_ee_branch_locally
+
+ ee_branch_presence_check!
+
+ ee_branch_compat_check!
+ end
+
+ delete_ee_branch_locally
+ delete_patches
+
+ if status.nil?
+ true
+ else
+ false
+ end
+ end
+ end
+
+ private
+
+ def ensure_ee_repo
+ if Dir.exist?(check_dir)
+ step("#{check_dir} already exists")
+ else
+ cmd = %W[git clone --branch master --single-branch --depth 1 #{EE_REPO} #{check_dir}]
+ step("Cloning #{EE_REPO} into #{check_dir}", cmd)
+ end
+ end
+
+ def ce_branch_compat_check!
+ cmd = %W[git apply --check #{ce_patch_full_path}]
+ status = step("Checking if #{ce_patch_name} applies cleanly to EE/master", cmd)
+
+ if status.zero?
+ puts ce_applies_cleanly_msg(ce_branch)
+ throw(:halt_check)
+ end
+ end
+
+ def ee_branch_presence_check!
+ status = step("Fetching origin/#{ee_branch}", %W[git fetch origin #{ee_branch}])
+
+ unless status.zero?
+ puts
+ puts ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg
+
+ throw(:halt_check, :ko)
+ end
+ end
+
+ def ee_branch_compat_check!
+ step("Checking out origin/#{ee_branch}", %W[git checkout -b #{ee_branch} FETCH_HEAD])
+
+ generate_patch(ee_branch, ee_patch_full_path)
+ cmd = %W[git apply --check #{ee_patch_full_path}]
+ status = step("Checking if #{ee_patch_name} applies cleanly to EE/master", cmd)
+
+ unless status.zero?
+ puts
+ puts ee_branch_doesnt_apply_cleanly_msg
+
+ throw(:halt_check, :ko)
+ end
+
+ puts
+ puts ee_applies_cleanly_msg
+ end
+
+ def generate_patch(branch, filepath)
+ FileUtils.rm(filepath, force: true)
+
+ depth = 0
+ loop do
+ depth += 10
+ step("Fetching origin/master", %W[git fetch origin master --depth=#{depth}])
+ status = step("Finding merge base with master", %W[git merge-base FETCH_HEAD #{branch}])
+
+ break if status.zero? || depth > 500
+ end
+
+ raise "#{branch} is too far behind master, please rebase it!" if depth > 500
+
+ step("Generating the patch against master")
+ output, status = Gitlab::Popen.popen(%w[git format-patch FETCH_HEAD --stdout])
+ throw(:halt_check, :ko) unless status.zero?
+
+ File.write(filepath, output)
+ throw(:halt_check, :ko) unless File.exist?(filepath)
+ end
+
+ def delete_ee_branch_locally
+ command(%w[git checkout master])
+ step("Deleting the local #{ee_branch} branch", %W[git branch -D #{ee_branch}])
+ end
+
+ def delete_patches
+ step("Deleting #{ce_patch_full_path}")
+ FileUtils.rm(ce_patch_full_path, force: true)
+
+ step("Deleting #{ee_patch_full_path}")
+ FileUtils.rm(ee_patch_full_path, force: true)
+ end
+
+ def ce_patch_name
+ @ce_patch_name ||= "#{ce_branch}.patch"
+ end
+
+ def ce_patch_full_path
+ @ce_patch_full_path ||= File.expand_path(ce_patch_name, check_dir)
+ end
+
+ def ee_branch
+ @ee_branch ||= "#{ce_branch}-ee"
+ end
+
+ def ee_patch_name
+ @ee_patch_name ||= "#{ee_branch}.patch"
+ end
+
+ def ee_patch_full_path
+ @ee_patch_full_path ||= File.expand_path(ee_patch_name, check_dir)
+ end
+
+ def step(desc, cmd = nil)
+ puts "\n=> #{desc}\n"
+
+ if cmd
+ puts "\n$ #{cmd.join(' ')}"
+ command(cmd)
+ end
+ end
+
+ def command(cmd)
+ output, status = Gitlab::Popen.popen(cmd)
+ puts output
+
+ status
+ end
+
+ def ce_applies_cleanly_msg(ce_branch)
+ <<-MSG.strip_heredoc
+ =================================================================
+ 🎉 Congratulations!! 🎉
+
+ The #{ce_branch} branch applies cleanly to EE/master!
+
+ Much ❤️!!
+ =================================================================\n
+ MSG
+ end
+
+ def ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg
+ <<-MSG.strip_heredoc
+ =================================================================
+ 💥 Oh no! 💥
+
+ The #{ce_branch} branch does not apply cleanly to the current
+ EE/master, and no #{ee_branch} branch was found in the EE repository.
+
+ Please create a #{ee_branch} branch that includes changes from
+ #{ce_branch} but also specific changes than can be applied cleanly
+ to EE/master.
+
+ There are different ways to create such branch:
+
+ 1. Create a new branch based on the CE branch and rebase it on top of EE/master
+
+ # In the EE repo
+ $ git fetch #{ce_repo} #{ce_branch}
+ $ git checkout -b #{ee_branch} FETCH_HEAD
+
+ # You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE" commit
+ # before rebasing to limit the conflicts-resolving steps during the rebase
+ $ git fetch origin
+ $ git rebase origin/master
+
+ At this point you will likely have conflicts.
+ Solve them, and continue/finish the rebase.
+
+ You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE".
+
+ 2. Create a new branch from master and cherry-pick your CE commits
+
+ # In the EE repo
+ $ git fetch origin
+ $ git checkout -b #{ee_branch} FETCH_HEAD
+ $ git fetch #{ce_repo} #{ce_branch}
+ $ git cherry-pick SHA # Repeat for all the commits you want to pick
+
+ You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE" commit.
+
+ Don't forget to push your branch to #{EE_REPO}:
+
+ # In the EE repo
+ $ git push origin #{ee_branch}
+
+ You can then retry this failed build, and hopefully it should pass.
+
+ Stay 💪 !
+ =================================================================\n
+ MSG
+ end
+
+ def ee_branch_doesnt_apply_cleanly_msg
+ <<-MSG.strip_heredoc
+ =================================================================
+ 💥 Oh no! 💥
+
+ The #{ce_branch} does not apply cleanly to the current
+ EE/master, and even though a #{ee_branch} branch exists in the EE
+ repository, it does not apply cleanly either to EE/master!
+
+ Please update the #{ee_branch}, push it again to #{EE_REPO}, and
+ retry this build.
+
+ Stay 💪 !
+ =================================================================\n
+ MSG
+ end
+
+ def ee_applies_cleanly_msg
+ <<-MSG.strip_heredoc
+ =================================================================
+ 🎉 Congratulations!! 🎉
+
+ The #{ee_branch} branch applies cleanly to EE/master!
+
+ Much ❤️!!
+ =================================================================\n
+ MSG
+ end
+ end
+end
diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb
index ffe49364379..7e8f35e9298 100644
--- a/lib/gitlab/exclusive_lease.rb
+++ b/lib/gitlab/exclusive_lease.rb
@@ -27,7 +27,7 @@ module Gitlab
# on begin/ensure blocks to cancel a lease, because the 'ensure' does
# not always run. Think of 'kill -9' from the Unicorn master for
# instance.
- #
+ #
# If you find that leases are getting in your way, ask yourself: would
# it be enough to lower the lease timeout? Another thing that might be
# appropriate is to only use a lease for bulk/automated operations, and
@@ -48,6 +48,13 @@ module Gitlab
end
end
+ # Returns true if the key for this lease is set.
+ def exists?
+ Gitlab::Redis.with do |redis|
+ redis.exists(redis_key)
+ end
+ end
+
# No #cancel method. See comments above!
private
diff --git a/lib/tasks/ce_to_ee_merge_check.rake b/lib/tasks/ce_to_ee_merge_check.rake
deleted file mode 100644
index 424e7883060..00000000000
--- a/lib/tasks/ce_to_ee_merge_check.rake
+++ /dev/null
@@ -1,4 +0,0 @@
-desc 'Checks if the branch would apply cleanly to EE'
-task ce_to_ee_merge_check: :environment do
- Rake::Task['gitlab:dev:ce_to_ee_merge_check'].invoke
-end
diff --git a/lib/tasks/ee_compat_check.rake b/lib/tasks/ee_compat_check.rake
new file mode 100644
index 00000000000..f494fa5c5c2
--- /dev/null
+++ b/lib/tasks/ee_compat_check.rake
@@ -0,0 +1,4 @@
+desc 'Checks if the branch would apply cleanly to EE'
+task ee_compat_check: :environment do
+ Rake::Task['gitlab:dev:ee_compat_check'].invoke
+end
diff --git a/lib/tasks/gitlab/dev.rake b/lib/tasks/gitlab/dev.rake
index 47bdb2d32d2..5ee99dfc810 100644
--- a/lib/tasks/gitlab/dev.rake
+++ b/lib/tasks/gitlab/dev.rake
@@ -1,106 +1,21 @@
namespace :gitlab do
namespace :dev do
desc 'Checks if the branch would apply cleanly to EE'
- task ce_to_ee_merge_check: :environment do
+ task ee_compat_check: :environment do
return if defined?(Gitlab::License)
return unless ENV['CI']
- ce_repo = ENV['CI_BUILD_REPO']
- ce_branch = ENV['CI_BUILD_REF_NAME']
-
- ee_repo = 'https://gitlab.com/gitlab-org/gitlab-ee.git'
- ee_branch = "#{ce_branch}-ee"
- ee_dir = 'gitlab-ee-merge-check'
-
- puts "\n=> Cloning #{ee_repo} into #{ee_dir}\n"
- `git clone #{ee_repo} #{ee_dir} --depth 1`
- Dir.chdir(ee_dir) do
- puts "\n => Fetching #{ce_repo}/#{ce_branch}\n"
- `git fetch #{ce_repo} #{ce_branch} --depth 1`
-
- # Try to merge the current tested branch to EE/master...
- puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n"
- `git merge FETCH_HEAD`
-
- exit 0 if $?.success?
-
- # Check if the <branch>-ee branch exists...
- puts "\n => Check if #{ee_repo}/#{ee_branch} exists\n"
- `git rev-parse --verify #{ee_branch}`
-
- # The <branch>-ee doesn't exist
- unless $?.success?
- puts
- puts <<-MSG.strip_heredoc
- =================================================================
- The #{ce_branch} branch cannot be merged without conflicts to the
- current EE/master, and no #{ee_branch} branch was detected in
- the EE repository.
-
- Please create a #{ee_branch} branch that includes changes from
- #{ce_branch} but also specific changes than can be applied cleanly
- to EE/master.
-
- You can create this branch as follows:
-
- 1. In the EE repo:
- $ git fetch origin
- $ git fetch #{ce_repo} #{ce_branch}
- $ git checkout -b #{ee_branch} FETCH_HEAD
- $ git rebase origin/master
- 2. At this point you will likely have conflicts, solve them, and
- continue/finish the rebase. Note: You can squash the CE commits
- before rebasing.
- 3. You can squash all the original #{ce_branch} commits into a
- single "Port of #{ce_branch} to EE".
- 4. Push your branch to #{ee_repo}:
- $ git push origin #{ee_branch}
- =================================================================\n
- MSG
-
- exit 1
- end
-
- # Try to merge the <branch>-ee branch to EE/master...
- puts "\n => Merging #{ee_repo}/#{ee_branch} into #{ee_repo}/master\n"
- `git merge #{ee_branch} master`
-
- # The <branch>-ee cannot be merged cleanly to EE/master...
- unless $?.success?
- puts
- puts <<-MSG.strip_heredoc
- =================================================================
- The #{ce_branch} branch cannot be merged without conflicts to
- EE/master, and even though the #{ee_branch} branch exists in the EE
- repository, it cannot be merged without conflicts to EE/master.
-
- Please update the #{ee_branch}, push it again to #{ee_repo}, and
- retry this job.
- =================================================================\n
- MSG
-
- exit 2
- end
-
- puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n"
- `git merge FETCH_HEAD`
- exit 0 if $?.success?
-
- # The <branch>-ee can be merged cleanly to EE/master, but <branch> still
- # cannot be merged cleanly to EE/master...
- puts
- puts <<-MSG.strip_heredoc
- =================================================================
- The #{ce_branch} branch cannot be merged without conflicts to EE, and
- even though the #{ee_branch} branch exists in the EE repository and
- applies cleanly to EE/master, it doesn't prevent conflicts when
- merging #{ce_branch} into EE.
-
- We may be in a complex situation here.
- =================================================================\n
- MSG
-
- exit 3
+ success =
+ Gitlab::EeCompatCheck.new(
+ branch: ENV['CI_BUILD_REF_NAME'],
+ check_dir: File.expand_path('ee-compat-check', __dir__),
+ ce_repo: ENV['CI_BUILD_REPO']
+ ).check
+
+ if success
+ exit 0
+ else
+ exit 1
end
end
end
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 41d263a46a4..2d762fdaa04 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -116,116 +116,126 @@ describe SnippetsController do
end
end
- describe 'GET #raw' do
- let(:user) { create(:user) }
+ %w(raw download).each do |action|
+ describe "GET #{action}" do
+ context 'when the personal snippet is private' do
+ let(:personal_snippet) { create(:personal_snippet, :private, author: user) }
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
- context 'when the personal snippet is private' do
- let(:personal_snippet) { create(:personal_snippet, :private, author: user) }
+ context 'when signed in user is not the author' do
+ let(:other_author) { create(:author) }
+ let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) }
- context 'when signed in' do
- before do
- sign_in(user)
- end
+ it 'responds with status 404' do
+ get action, id: other_personal_snippet.to_param
- context 'when signed in user is not the author' do
- let(:other_author) { create(:author) }
- let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) }
+ expect(response).to have_http_status(404)
+ end
+ end
- it 'responds with status 404' do
- get :raw, id: other_personal_snippet.to_param
+ context 'when signed in user is the author' do
+ before { get action, id: personal_snippet.to_param }
- expect(response).to have_http_status(404)
- end
- end
+ it 'responds with status 200' do
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response).to have_http_status(200)
+ end
- context 'when signed in user is the author' do
- it 'renders the raw snippet' do
- get :raw, id: personal_snippet.to_param
+ it 'has expected headers' do
+ expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
- expect(assigns(:snippet)).to eq(personal_snippet)
- expect(response).to have_http_status(200)
+ if action == :download
+ expect(response.header['Content-Disposition']).to match(/attachment/)
+ elsif action == :raw
+ expect(response.header['Content-Disposition']).to match(/inline/)
+ end
+ end
end
end
- end
- context 'when not signed in' do
- it 'redirects to the sign in page' do
- get :raw, id: personal_snippet.to_param
+ context 'when not signed in' do
+ it 'redirects to the sign in page' do
+ get action, id: personal_snippet.to_param
- expect(response).to redirect_to(new_user_session_path)
+ expect(response).to redirect_to(new_user_session_path)
+ end
end
end
- end
- context 'when the personal snippet is internal' do
- let(:personal_snippet) { create(:personal_snippet, :internal, author: user) }
+ context 'when the personal snippet is internal' do
+ let(:personal_snippet) { create(:personal_snippet, :internal, author: user) }
- context 'when signed in' do
- before do
- sign_in(user)
- end
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
- it 'renders the raw snippet' do
- get :raw, id: personal_snippet.to_param
+ it 'responds with status 200' do
+ get action, id: personal_snippet.to_param
- expect(assigns(:snippet)).to eq(personal_snippet)
- expect(response).to have_http_status(200)
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response).to have_http_status(200)
+ end
end
- end
- context 'when not signed in' do
- it 'redirects to the sign in page' do
- get :raw, id: personal_snippet.to_param
+ context 'when not signed in' do
+ it 'redirects to the sign in page' do
+ get action, id: personal_snippet.to_param
- expect(response).to redirect_to(new_user_session_path)
+ expect(response).to redirect_to(new_user_session_path)
+ end
end
end
- end
- context 'when the personal snippet is public' do
- let(:personal_snippet) { create(:personal_snippet, :public, author: user) }
+ context 'when the personal snippet is public' do
+ let(:personal_snippet) { create(:personal_snippet, :public, author: user) }
- context 'when signed in' do
- before do
- sign_in(user)
- end
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
- it 'renders the raw snippet' do
- get :raw, id: personal_snippet.to_param
+ it 'responds with status 200' do
+ get action, id: personal_snippet.to_param
- expect(assigns(:snippet)).to eq(personal_snippet)
- expect(response).to have_http_status(200)
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response).to have_http_status(200)
+ end
end
- end
- context 'when not signed in' do
- it 'renders the raw snippet' do
- get :raw, id: personal_snippet.to_param
+ context 'when not signed in' do
+ it 'responds with status 200' do
+ get action, id: personal_snippet.to_param
- expect(assigns(:snippet)).to eq(personal_snippet)
- expect(response).to have_http_status(200)
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response).to have_http_status(200)
+ end
end
end
- end
- context 'when the personal snippet does not exist' do
- context 'when signed in' do
- before do
- sign_in(user)
- end
+ context 'when the personal snippet does not exist' do
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
- it 'responds with status 404' do
- get :raw, id: 'doesntexist'
+ it 'responds with status 404' do
+ get action, id: 'doesntexist'
- expect(response).to have_http_status(404)
+ expect(response).to have_http_status(404)
+ end
end
- end
- context 'when not signed in' do
- it 'responds with status 404' do
- get :raw, id: 'doesntexist'
+ context 'when not signed in' do
+ it 'responds with status 404' do
+ get action, id: 'doesntexist'
- expect(response).to have_http_status(404)
+ expect(response).to have_http_status(404)
+ end
end
end
end
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 0fb1608a0a3..c533ce1d87f 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -624,6 +624,10 @@ describe 'Issue Boards', feature: true, js: true do
it 'does not show create new list' do
expect(page).not_to have_selector('.js-new-board-list')
end
+
+ it 'does not allow dragging' do
+ expect(page).not_to have_selector('.user-can-drag')
+ end
end
context 'as guest user' do
diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb
index d886909ce85..2f377312ea5 100644
--- a/spec/features/projects/issuable_templates_spec.rb
+++ b/spec/features/projects/issuable_templates_spec.rb
@@ -77,7 +77,7 @@ feature 'issuable templates', feature: true, js: true do
scenario 'user selects "bug" template' do
select_template 'bug'
wait_for_ajax
- preview_template("#{prior_description}\n\n#{template_content}")
+ preview_template("#{template_content}")
save_changes
end
end
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index 6b58f3e43ee..2bfa51deb20 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -50,14 +50,6 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
end
end
- shared_examples :relative_to_requested do
- it 'rebuilds URL relative to the requested path' do
- doc = filter(link('users.md'))
- expect(doc.at_css('a')['href']).
- to eq "/#{project_path}/blob/#{ref}/doc/api/users.md"
- end
- end
-
context 'with a project_wiki' do
let(:project_wiki) { double('ProjectWiki') }
include_examples :preserve_unchanged
@@ -188,12 +180,38 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
context 'when requested path is a file in the repo' do
let(:requested_path) { 'doc/api/README.md' }
- include_examples :relative_to_requested
+ it 'rebuilds URL relative to the containing directory' do
+ doc = filter(link('users.md'))
+ expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/doc/api/users.md"
+ end
end
context 'when requested path is a directory in the repo' do
- let(:requested_path) { 'doc/api' }
- include_examples :relative_to_requested
+ let(:requested_path) { 'doc/api/' }
+ it 'rebuilds URL relative to the directory' do
+ doc = filter(link('users.md'))
+ expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/doc/api/users.md"
+ end
+ end
+
+ context 'when ref name contains percent sign' do
+ let(:ref) { '100%branch' }
+ let(:commit) { project.commit('1b12f15a11fc6e62177bef08f47bc7b5ce50b141') }
+ let(:requested_path) { 'foo/bar/' }
+ it 'correctly escapes the ref' do
+ doc = filter(link('.gitkeep'))
+ expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/foo/bar/.gitkeep"
+ end
+ end
+
+ context 'when requested path is a directory with space in the repo' do
+ let(:ref) { 'master' }
+ let(:commit) { project.commit('38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e') }
+ let(:requested_path) { 'with space/' }
+ it 'does not escape the space twice' do
+ doc = filter(link('README.md'))
+ expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/with%20space/README.md"
+ end
end
end
diff --git a/spec/lib/gitlab/exclusive_lease_spec.rb b/spec/lib/gitlab/exclusive_lease_spec.rb
index fbdb7ea34ac..6b3bd08b978 100644
--- a/spec/lib/gitlab/exclusive_lease_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_spec.rb
@@ -1,21 +1,36 @@
require 'spec_helper'
-describe Gitlab::ExclusiveLease do
- it 'cannot obtain twice before the lease has expired' do
- lease = Gitlab::ExclusiveLease.new(unique_key, timeout: 3600)
- expect(lease.try_obtain).to eq(true)
- expect(lease.try_obtain).to eq(false)
- end
+describe Gitlab::ExclusiveLease, type: :redis do
+ let(:unique_key) { SecureRandom.hex(10) }
+
+ describe '#try_obtain' do
+ it 'cannot obtain twice before the lease has expired' do
+ lease = Gitlab::ExclusiveLease.new(unique_key, timeout: 3600)
+ expect(lease.try_obtain).to eq(true)
+ expect(lease.try_obtain).to eq(false)
+ end
- it 'can obtain after the lease has expired' do
- timeout = 1
- lease = Gitlab::ExclusiveLease.new(unique_key, timeout: timeout)
- lease.try_obtain # start the lease
- sleep(2 * timeout) # lease should have expired now
- expect(lease.try_obtain).to eq(true)
+ it 'can obtain after the lease has expired' do
+ timeout = 1
+ lease = Gitlab::ExclusiveLease.new(unique_key, timeout: timeout)
+ lease.try_obtain # start the lease
+ sleep(2 * timeout) # lease should have expired now
+ expect(lease.try_obtain).to eq(true)
+ end
end
- def unique_key
- SecureRandom.hex(10)
+ describe '#exists?' do
+ it 'returns true for an existing lease' do
+ lease = Gitlab::ExclusiveLease.new(unique_key, timeout: 3600)
+ lease.try_obtain
+
+ expect(lease.exists?).to eq(true)
+ end
+
+ it 'returns false for a lease that does not exist' do
+ lease = Gitlab::ExclusiveLease.new(unique_key, timeout: 3600)
+
+ expect(lease.exists?).to eq(false)
+ end
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 3155eff9ee1..1067ff7bb4d 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1286,7 +1286,8 @@ describe MergeRequest, models: true do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) }
- let(:merge_request) do
+
+ let!(:merge_request) do
create(:closed_merge_request,
source_project: fork_project,
target_project: project)
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index d48752473f3..ae8639d78d5 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -958,6 +958,29 @@ describe API::API, api: true do
expect(joined_event['author']['name']).to eq(user.name)
end
end
+
+ context 'when there are multiple events from different projects' do
+ let(:second_note) { create(:note_on_issue, project: create(:empty_project)) }
+ let(:third_note) { create(:note_on_issue, project: project) }
+
+ before do
+ second_note.project.add_user(user, :developer)
+
+ [second_note, third_note].each do |note|
+ EventCreateService.new.leave_note(note, user)
+ end
+ end
+
+ it 'returns events in the correct order (from newest to oldest)' do
+ get api("/users/#{user.id}/events", user)
+
+ comment_events = json_response.select { |e| e['action_name'] == 'commented on' }
+
+ expect(comment_events[0]['target_id']).to eq(third_note.id)
+ expect(comment_events[1]['target_id']).to eq(second_note.id)
+ expect(comment_events[2]['target_id']).to eq(note.id)
+ end
+ end
end
it 'returns a 404 error if not found' do
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index 302eef8bf7e..f0ded06b785 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -208,10 +208,10 @@ describe Issues::MoveService, services: true do
end
end
- describe 'rewritting references' do
+ describe 'rewriting references' do
include_context 'issue move executed'
- context 'issue reference' do
+ context 'issue references' do
let(:another_issue) { create(:issue, project: old_project) }
let(:description) { "Some description #{another_issue.to_reference}" }
@@ -220,6 +220,16 @@ describe Issues::MoveService, services: true do
.to eq "Some description #{old_project.to_reference}#{another_issue.to_reference}"
end
end
+
+ context "user references" do
+ let(:another_issue) { create(:issue, project: old_project) }
+ let(:description) { "Some description #{user.to_reference}" }
+
+ it "doesn't throw any errors for issues containing user references" do
+ expect(new_issue.description)
+ .to eq "Some description #{user.to_reference}"
+ end
+ end
end
context 'moving to same project' do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index b19f5824236..06d52f0f735 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -50,6 +50,12 @@ RSpec.configure do |config|
example.run
Rails.cache = caching_store
end
+
+ config.around(:each, :redis) do |example|
+ Gitlab::Redis.with(&:flushall)
+ example.run
+ Gitlab::Redis.with(&:flushall)
+ end
end
FactoryGirl::SyntaxRunner.class_eval do
diff --git a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
new file mode 100644
index 00000000000..6f70b3daf8e
--- /dev/null
+++ b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe 'projects/merge_requests/show/_commits.html.haml' do
+ include Devise::Test::ControllerHelpers
+
+ let(:user) { create(:user) }
+ let(:target_project) { create(:project) }
+
+ let(:source_project) do
+ create(:project, forked_from_project: target_project)
+ end
+
+ let(:merge_request) do
+ create(:merge_request, :simple,
+ source_project: source_project,
+ target_project: target_project,
+ author: user)
+ end
+
+ before do
+ controller.prepend_view_path('app/views/projects')
+
+ assign(:merge_request, merge_request)
+ assign(:commits, merge_request.commits)
+ end
+
+ it 'shows commits from source project' do
+ render
+
+ commit = source_project.commit(merge_request.source_branch)
+ href = namespace_project_commit_path(
+ source_project.namespace,
+ source_project,
+ commit)
+
+ expect(rendered).to have_link(Commit.truncate_sha(commit.sha), href: href)
+ end
+end
diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb
index f5b60b90d11..bfa8c0ff2c6 100644
--- a/spec/workers/project_cache_worker_spec.rb
+++ b/spec/workers/project_cache_worker_spec.rb
@@ -5,6 +5,26 @@ describe ProjectCacheWorker do
subject { described_class.new }
+ describe '.perform_async' do
+ it 'schedules the job when no lease exists' do
+ allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:exists?).
+ and_return(false)
+
+ expect_any_instance_of(described_class).to receive(:perform)
+
+ described_class.perform_async(project.id)
+ end
+
+ it 'does not schedule the job when a lease exists' do
+ allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:exists?).
+ and_return(true)
+
+ expect_any_instance_of(described_class).not_to receive(:perform)
+
+ described_class.perform_async(project.id)
+ end
+ end
+
describe '#perform' do
context 'when an exclusive lease can be obtained' do
before do