Commit 6fae9319 authored by Steve Halasz's avatar Steve Halasz

Merge branch 'master' into 18256-secret-token-docs

parents 7612f1c4 8765d2f9
...@@ -252,6 +252,9 @@ coverage: ...@@ -252,6 +252,9 @@ coverage:
notify:slack: notify:slack:
stage: post-test stage: post-test
variables:
USE_DB: "false"
USE_BUNDLE_INSTALL: "false"
script: script:
- ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>" - ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>"
when: on_failure when: on_failure
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.11.0 (unreleased) v 8.11.0 (unreleased)
- Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres)
- Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres)
- Fix the title of the toggle dropdown button. !5515 (herminiotorres) - Fix the title of the toggle dropdown button. !5515 (herminiotorres)
- Improve diff performance by eliminating redundant checks for text blobs - Improve diff performance by eliminating redundant checks for text blobs
- Convert switch icon into icon font (ClemMakesApps) - Convert switch icon into icon font (ClemMakesApps)
- Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell) - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell)
- Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell) - Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell)
- Ignore URLs starting with // in Markdown links !5677 (winniehell)
- Fix CI status icon link underline (ClemMakesApps) - Fix CI status icon link underline (ClemMakesApps)
- The Repository class is now instrumented - The Repository class is now instrumented
- Cache the commit author in RequestStore to avoid extra lookups in PostReceive - Cache the commit author in RequestStore to avoid extra lookups in PostReceive
...@@ -15,14 +18,19 @@ v 8.11.0 (unreleased) ...@@ -15,14 +18,19 @@ v 8.11.0 (unreleased)
- Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable - Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable
- Optimize maximum user access level lookup in loading of notes - Optimize maximum user access level lookup in loading of notes
- Add "No one can push" as an option for protected branches. !5081 - Add "No one can push" as an option for protected branches. !5081
- Improve performance of AutolinkFilter#text_parse by using XPath
- Environments have an url to link to - Environments have an url to link to
- Update `timeago` plugin to use multiple string/locale settings
- Remove unused images (ClemMakesApps)
- Limit git rev-list output count to one in forced push check - Limit git rev-list output count to one in forced push check
- Clean up unused routes (Josef Strzibny) - Clean up unused routes (Josef Strzibny)
- Add green outline to New Branch button. !5447 (winniehell) - Add green outline to New Branch button. !5447 (winniehell)
- Improve performance of syntax highlighting Markdown code blocks
- Update to gitlab_git 10.4.1 and take advantage of preserved Ref objects - Update to gitlab_git 10.4.1 and take advantage of preserved Ref objects
- Remove delay when hitting "Reply..." button on page with a lot of discussions - Remove delay when hitting "Reply..." button on page with a lot of discussions
- Retrieve rendered HTML from cache in one request - Retrieve rendered HTML from cache in one request
- Fix renaming repository when name contains invalid chararacters under project settings - Fix renaming repository when name contains invalid chararacters under project settings
- Fix devise deprecation warnings.
- Optimize checking if a user has read access to a list of issues !5370 - Optimize checking if a user has read access to a list of issues !5370
- Nokogiri's various parsing methods are now instrumented - Nokogiri's various parsing methods are now instrumented
- Add simple identifier to public SSH keys (muteor) - Add simple identifier to public SSH keys (muteor)
...@@ -31,14 +39,17 @@ v 8.11.0 (unreleased) ...@@ -31,14 +39,17 @@ v 8.11.0 (unreleased)
- Include old revision in merge request update hooks (Ben Boeckel) - Include old revision in merge request update hooks (Ben Boeckel)
- Add build event color in HipChat messages (David Eisner) - Add build event color in HipChat messages (David Eisner)
- Make fork counter always clickable. !5463 (winniehell) - Make fork counter always clickable. !5463 (winniehell)
- Gitlab::Highlight is now instrumented
- All created issues, API or WebUI, can be submitted to Akismet for spam check !5333 - All created issues, API or WebUI, can be submitted to Akismet for spam check !5333
- The overhead of instrumented method calls has been reduced - The overhead of instrumented method calls has been reduced
- Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le) - Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le)
- Load project invited groups and members eagerly in `ProjectTeam#fetch_members` - Load project invited groups and members eagerly in `ProjectTeam#fetch_members`
- Bump gitlab_git to speedup DiffCollection iterations - Bump gitlab_git to speedup DiffCollection iterations
- Rewrite description of a blocked user in admin settings. (Elias Werberich)
- Make branches sortable without push permission !5462 (winniehell) - Make branches sortable without push permission !5462 (winniehell)
- Check for Ci::Build artifacts at database level on pipeline partial - Check for Ci::Build artifacts at database level on pipeline partial
- Convert image diff background image to CSS (ClemMakesApps) - Convert image diff background image to CSS (ClemMakesApps)
- Remove unnecessary index_projects_on_builds_enabled index from the projects table
- Make "New issue" button in Issue page less obtrusive !5457 (winniehell) - Make "New issue" button in Issue page less obtrusive !5457 (winniehell)
- Gitlab::Metrics.current_transaction needs to be public for RailsQueueDuration - Gitlab::Metrics.current_transaction needs to be public for RailsQueueDuration
- Fix search for notes which belongs to deleted objects - Fix search for notes which belongs to deleted objects
...@@ -54,12 +65,23 @@ v 8.11.0 (unreleased) ...@@ -54,12 +65,23 @@ v 8.11.0 (unreleased)
- Make error pages responsive (Takuya Noguchi) - Make error pages responsive (Takuya Noguchi)
- Fix skip_repo parameter being ignored when destroying a namespace - Fix skip_repo parameter being ignored when destroying a namespace
- Change requests_profiles resource constraint to catch virtually any file - Change requests_profiles resource constraint to catch virtually any file
- Bump gitlab_git to lazy load compare commits
- Reduce number of queries made for merge_requests/:id/diffs - Reduce number of queries made for merge_requests/:id/diffs
- Sensible state specific default sort order for issues and merge requests !5453 (tomb0y) - Sensible state specific default sort order for issues and merge requests !5453 (tomb0y)
- Fix RequestProfiler::Middleware error when code is reloaded in development - Fix RequestProfiler::Middleware error when code is reloaded in development
- Catch what warden might throw when profiling requests to re-throw it - Catch what warden might throw when profiling requests to re-throw it
- Add description to new_issue email and new_merge_request_email in text/plain content type. !5663 (dixpac)
- Speed up and reduce memory usage of Commit#repo_changes, Repository#expire_avatar_cache and IrkerWorker - Speed up and reduce memory usage of Commit#repo_changes, Repository#expire_avatar_cache and IrkerWorker
- Document that webhook secret token is sent in X-Gitlab-Token HTTP header - Document that webhook secret token is sent in X-Gitlab-Token HTTP header
- Add unfold links for Side-by-Side view. !5415 (Tim Masliuchenko)
v 8.10.5 (unreleased)
v 8.10.4
- Don't close referenced upstream issues from a forked project.
- Fixes issue with dropdowns `enter` key not working correctly. !5544
- Fix Import/Export project import not working in HA mode. !5618
- Fix Import/Export error checking versions. !5638
v 8.10.3 v 8.10.3
- Fix Import/Export issue importing milestones and labels not associated properly. !5426 - Fix Import/Export issue importing milestones and labels not associated properly. !5426
......
...@@ -462,7 +462,8 @@ merge request: ...@@ -462,7 +462,8 @@ merge request:
- string literal quoting style **Option A**: single quoted by default - string literal quoting style **Option A**: single quoted by default
1. [Rails](https://github.com/bbatsov/rails-style-guide) 1. [Rails](https://github.com/bbatsov/rails-style-guide)
1. [Testing](doc/development/testing.md) 1. [Testing](doc/development/testing.md)
1. [CoffeeScript](https://github.com/thoughtbot/guides/tree/master/style/coffeescript) 1. [JavaScript (ES6)](https://github.com/airbnb/javascript)
1. [JavaScript (ES5)](https://github.com/airbnb/javascript/tree/master/es5)
1. [SCSS styleguide][scss-styleguide] 1. [SCSS styleguide][scss-styleguide]
1. [Shell commands](doc/development/shell_commands.md) created by GitLab 1. [Shell commands](doc/development/shell_commands.md) created by GitLab
contributors to enhance security contributors to enhance security
......
...@@ -53,7 +53,7 @@ gem 'browser', '~> 2.2' ...@@ -53,7 +53,7 @@ gem 'browser', '~> 2.2'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem 'gitlab_git', '~> 10.4.3' gem 'gitlab_git', '~> 10.4.5'
# LDAP Auth # LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes # GitLab fork with several improvements to original library. For full list of changes
...@@ -326,7 +326,7 @@ group :production do ...@@ -326,7 +326,7 @@ group :production do
gem 'gitlab_meta', '7.0' gem 'gitlab_meta', '7.0'
end end
gem 'newrelic_rpm', '~> 3.14' gem 'newrelic_rpm', '~> 3.16'
gem 'octokit', '~> 4.3.0' gem 'octokit', '~> 4.3.0'
......
...@@ -278,7 +278,7 @@ GEM ...@@ -278,7 +278,7 @@ GEM
diff-lcs (~> 1.1) diff-lcs (~> 1.1)
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab_git (10.4.3) gitlab_git (10.4.5)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
...@@ -404,7 +404,7 @@ GEM ...@@ -404,7 +404,7 @@ GEM
nested_form (0.3.2) nested_form (0.3.2)
net-ldap (0.12.1) net-ldap (0.12.1)
net-ssh (3.0.1) net-ssh (3.0.1)
newrelic_rpm (3.14.1.311) newrelic_rpm (3.16.0.318)
nokogiri (1.6.8) nokogiri (1.6.8)
mini_portile2 (~> 2.1.0) mini_portile2 (~> 2.1.0)
pkg-config (~> 1.1.7) pkg-config (~> 1.1.7)
...@@ -870,7 +870,7 @@ DEPENDENCIES ...@@ -870,7 +870,7 @@ DEPENDENCIES
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
github-markup (~> 1.4) github-markup (~> 1.4)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab_git (~> 10.4.3) gitlab_git (~> 10.4.5)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.2) gollum-lib (~> 4.2)
...@@ -902,7 +902,7 @@ DEPENDENCIES ...@@ -902,7 +902,7 @@ DEPENDENCIES
mysql2 (~> 0.3.16) mysql2 (~> 0.3.16)
nested_form (~> 0.3.2) nested_form (~> 0.3.2)
net-ssh (~> 3.0.1) net-ssh (~> 3.0.1)
newrelic_rpm (~> 3.14) newrelic_rpm (~> 3.16)
nokogiri (~> 1.6.7, >= 1.6.7.2) nokogiri (~> 1.6.7, >= 1.6.7.2)
oauth2 (~> 1.2.0) oauth2 (~> 1.2.0)
octokit (~> 4.3.0) octokit (~> 4.3.0)
......
...@@ -287,7 +287,7 @@ ...@@ -287,7 +287,7 @@
$('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded').removeClass('page-sidebar-pinned'); $('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded').removeClass('page-sidebar-pinned');
$('.navbar-fixed-top').removeClass('header-pinned-nav'); $('.navbar-fixed-top').removeClass('header-pinned-nav');
} }
return $document.off('click', '.js-nav-pin').on('click', '.js-nav-pin', function(e) { $document.off('click', '.js-nav-pin').on('click', '.js-nav-pin', function(e) {
var $page, $pinBtn, $tooltip, $topNav, doPinNav, tooltipText; var $page, $pinBtn, $tooltip, $topNav, doPinNav, tooltipText;
e.preventDefault(); e.preventDefault();
$pinBtn = $(e.currentTarget); $pinBtn = $(e.currentTarget);
...@@ -315,6 +315,8 @@ ...@@ -315,6 +315,8 @@
$tooltip.find('.tooltip-inner').text(tooltipText); $tooltip.find('.tooltip-inner').text(tooltipText);
return $pinBtn.attr('title', tooltipText).tooltip('fixTitle'); return $pinBtn.attr('title', tooltipText).tooltip('fixTitle');
}); });
});
// Custom time ago
gl.utils.shortTimeAgo($('.js-short-timeago'));
});
}).call(this); }).call(this);
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
$(document).off('click', '.js-unfold'); $(document).off('click', '.js-unfold');
$(document).on('click', '.js-unfold', (function(_this) { $(document).on('click', '.js-unfold', (function(_this) {
return function(event) { return function(event) {
var line_number, link, offset, old_line, params, prev_new_line, prev_old_line, ref, ref1, since, target, to, unfold, unfoldBottom; var line_number, link, file, offset, old_line, params, prev_new_line, prev_old_line, ref, ref1, since, target, to, unfold, unfoldBottom;
target = $(event.target); target = $(event.target);
unfoldBottom = target.hasClass('js-unfold-bottom'); unfoldBottom = target.hasClass('js-unfold-bottom');
unfold = true; unfold = true;
...@@ -31,14 +31,16 @@ ...@@ -31,14 +31,16 @@
unfold = false; unfold = false;
} }
} }
link = target.parents('.diff-file').attr('data-blob-diff-path'); file = target.parents('.diff-file');
link = file.data('blob-diff-path');
params = { params = {
since: since, since: since,
to: to, to: to,
bottom: unfoldBottom, bottom: unfoldBottom,
offset: offset, offset: offset,
unfold: unfold, unfold: unfold,
indent: 1 indent: 1,
view: file.data('view')
}; };
return $.get(link, params, function(response) { return $.get(link, params, function(response) {
return target.parent().replaceWith(response); return target.parent().replaceWith(response);
...@@ -48,26 +50,13 @@ ...@@ -48,26 +50,13 @@
} }
Diff.prototype.lineNumbers = function(line) { Diff.prototype.lineNumbers = function(line) {
var i, l, len, line_number, line_numbers, lines, results;
if (!line.children().length) { if (!line.children().length) {
return [0, 0]; return [0, 0];
} }
lines = line.children().slice(0, 2);
line_numbers = (function() { return line.find('.diff-line-num').map(function() {
var i, len, results; return parseInt($(this).data('linenumber'));
results = []; });
for (i = 0, len = lines.length; i < len; i++) {
l = lines[i];
results.push($(l).attr('data-linenumber'));
}
return results;
})();
results = [];
for (i = 0, len = line_numbers.length; i < len; i++) {
line_number = line_numbers[i];
results.push(parseInt(line_number));
}
return results;
}; };
return Diff; return Diff;
......
...@@ -28,38 +28,43 @@ ...@@ -28,38 +28,43 @@
}; };
})(this)); })(this));
timeout = ""; timeout = "";
this.input.on("keyup", (function(_this) { this.input
return function(e) { .on('keydown', function (e) {
var keyCode = e.which;
if (keyCode === 13) {
e.preventDefault()
}
})
.on('keyup', function(e) {
var keyCode; var keyCode;
keyCode = e.which; keyCode = e.which;
if (ARROW_KEY_CODES.indexOf(keyCode) >= 0) { if (ARROW_KEY_CODES.indexOf(keyCode) >= 0) {
return; return;
} }
if (_this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) { if (this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) {
$inputContainer.addClass(HAS_VALUE_CLASS); $inputContainer.addClass(HAS_VALUE_CLASS);
} else if (_this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) { } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) {
$inputContainer.removeClass(HAS_VALUE_CLASS); $inputContainer.removeClass(HAS_VALUE_CLASS);
} }
if (keyCode === 13) { if (keyCode === 13) {
return false; return false;
} }
if (_this.options.remote) { if (this.options.remote) {
clearTimeout(timeout); clearTimeout(timeout);
return timeout = setTimeout(function() { return timeout = setTimeout(function() {
var blur_field; var blurField = this.shouldBlur(keyCode);
blur_field = _this.shouldBlur(keyCode); if (blurField && this.filterInputBlur) {
if (blur_field && _this.filterInputBlur) { this.input.blur();
_this.input.blur();
} }
return _this.options.query(_this.input.val(), function(data) { return this.options.query(this.input.val(), function(data) {
return _this.options.callback(data); return this.options.callback(data);
}); }.bind(this));
}, 250); }.bind(this), 250);
} else { } else {
return _this.filter(_this.input.val()); return this.filter(this.input.val());
} }
}; }.bind(this));
})(this));
} }
GitLabDropdownFilter.prototype.shouldBlur = function(keyCode) { GitLabDropdownFilter.prototype.shouldBlur = function(keyCode) {
...@@ -382,6 +387,7 @@ ...@@ -382,6 +387,7 @@
GitLabDropdown.prototype.opened = function() { GitLabDropdown.prototype.opened = function() {
var contentHtml; var contentHtml;
currentIndex = -1;
this.addArrowKeyEvent(); this.addArrowKeyEvent();
if (this.options.setIndeterminateIds) { if (this.options.setIndeterminateIds) {
this.options.setIndeterminateIds.call(this); this.options.setIndeterminateIds.call(this);
...@@ -619,7 +625,7 @@ ...@@ -619,7 +625,7 @@
var $input, ARROW_KEY_CODES, selector; var $input, ARROW_KEY_CODES, selector;
ARROW_KEY_CODES = [38, 40]; ARROW_KEY_CODES = [38, 40];
$input = this.dropdown.find(".dropdown-input-field"); $input = this.dropdown.find(".dropdown-input-field");
selector = '.dropdown-content li:not(.divider,.dropdown-header,.separator)'; selector = '.dropdown-content li:not(.divider,.dropdown-header,.separator):visible';
if (this.dropdown.find(".dropdown-toggle-page").length) { if (this.dropdown.find(".dropdown-toggle-page").length) {
selector = ".dropdown-page-one " + selector; selector = ".dropdown-page-one " + selector;
} }
...@@ -647,7 +653,7 @@ ...@@ -647,7 +653,7 @@
return false; return false;
} }
if (currentKeyCode === 13 && currentIndex !== -1) { if (currentKeyCode === 13 && currentIndex !== -1) {
return _this.selectRowAtIndex(e, currentIndex); return _this.selectRowAtIndex(e, $('.is-focused', _this.dropdown).closest('li').index() - 1);
} }
}; };
})(this)); })(this));
......
...@@ -8,13 +8,16 @@ ...@@ -8,13 +8,16 @@
base.utils = {}; base.utils = {};
} }
w.gl.utils.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; w.gl.utils.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
w.gl.utils.formatDate = function(datetime) { w.gl.utils.formatDate = function(datetime) {
return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z'); return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
}; };
w.gl.utils.getDayName = function(date) { w.gl.utils.getDayName = function(date) {
return this.days[date.getDay()]; return this.days[date.getDay()];
}; };
return w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago) {
w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago) {
if (setTimeago == null) { if (setTimeago == null) {
setTimeago = true; setTimeago = true;
} }
...@@ -31,6 +34,39 @@ ...@@ -31,6 +34,39 @@
}); });
} }
}; };
w.gl.utils.shortTimeAgo = function($el) {
var shortLocale, tmpLocale;
shortLocale = {
prefixAgo: null,
prefixFromNow: null,
suffixAgo: 'ago',
suffixFromNow: 'from now',
seconds: '1 min',
minute: '1 min',
minutes: '%d mins',
hour: '1 hr',
hours: '%d hrs',
day: '1 day',
days: '%d days',
month: '1 month',
months: '%d months',
year: '1 year',
years: '%d years',
wordSeparator: ' ',
numbers: []
};
tmpLocale = $.timeago.settings.strings;
$el.each(function(el) {
var $el1;
$el1 = $(this);
return $el1.attr('title', gl.utils.formatDate($el.attr('datetime')));
});
$.timeago.settings.strings = shortLocale;
$el.timeago();
$.timeago.settings.strings = tmpLocale;
};
})(window); })(window);
}).call(this); }).call(this);
...@@ -89,8 +89,14 @@ ...@@ -89,8 +89,14 @@
toggleLabel: function(obj, $el) { toggleLabel: function(obj, $el) {
return $el.text().trim(); return $el.text().trim();
}, },
clicked: function(e) { clicked: function(selected, $el, e) {
return $dropdown.closest('form').submit(); e.preventDefault()
if ($('input[name="ref"]').length) {
var $form = $dropdown.closest('form'),
action = $form.attr('action'),
divider = action.indexOf('?') < 0 ? '?' : '&';
Turbolinks.visit(action + '' + divider + '' + $form.serialize());
}
} }
}); });
}); });
......
...@@ -114,6 +114,12 @@ ul.content-list { ...@@ -114,6 +114,12 @@ ul.content-list {
font-size: $list-font-size; font-size: $list-font-size;
color: $list-text-color; color: $list-text-color;
&.no-description {
.title {
line-height: $list-text-height;
}
}
.title { .title {
font-weight: 600; font-weight: 600;
} }
...@@ -134,12 +140,11 @@ ul.content-list { ...@@ -134,12 +140,11 @@ ul.content-list {
} }
.controls { .controls {
padding-top: 1px;
float: right; float: right;
> .control-text { > .control-text {
margin-right: $gl-padding-top; margin-right: $gl-padding-top;
line-height: 40px; line-height: $list-text-height;
&:last-child { &:last-child {
margin-right: 0; margin-right: 0;
...@@ -150,7 +155,7 @@ ul.content-list { ...@@ -150,7 +155,7 @@ ul.content-list {
> .btn-group { > .btn-group {
margin-right: $gl-padding-top; margin-right: $gl-padding-top;
display: inline-block; display: inline-block;
margin-top: 4px; margin-top: 3px;
margin-bottom: 4px; margin-bottom: 4px;
&:last-child { &:last-child {
......
...@@ -43,6 +43,7 @@ $gl-header-color: $gl-title-color; ...@@ -43,6 +43,7 @@ $gl-header-color: $gl-title-color;
$list-font-size: $gl-font-size; $list-font-size: $gl-font-size;
$list-title-color: $gl-title-color; $list-title-color: $gl-title-color;
$list-text-color: $gl-text-color; $list-text-color: $gl-text-color;
$list-text-height: 42px;
/* /*
* Markdown * Markdown
......
...@@ -23,15 +23,9 @@ ...@@ -23,15 +23,9 @@
} }
.group-row { .group-row {
&.no-description {
.group-name {
line-height: 44px;
}
}
.stats { .stats {
float: right; float: right;
line-height: 44px; line-height: $list-text-height;
color: $gl-gray; color: $gl-gray;
span { span {
......
...@@ -512,18 +512,12 @@ pre.light-well { ...@@ -512,18 +512,12 @@ pre.light-well {
.project-row { .project-row {
border-color: $table-border-color; border-color: $table-border-color;
&.no-description {
.project {
line-height: 40px;
}
}
.project-full-name { .project-full-name {
@include str-truncated; @include str-truncated;
} }
.controls { .controls {
line-height: 40px; line-height: $list-text-height;
a:hover { a:hover {
text-decoration: none; text-decoration: none;
......
...@@ -12,13 +12,14 @@ class Import::GitlabProjectsController < Import::BaseController ...@@ -12,13 +12,14 @@ class Import::GitlabProjectsController < Import::BaseController
return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." }) return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." })
end end
imported_file = project_params[:file].path + "-import" import_upload_path = Gitlab::ImportExport.import_upload_path(filename: project_params[:file].original_filename)
FileUtils.copy_entry(project_params[:file].path, imported_file) FileUtils.mkdir_p(File.dirname(import_upload_path))
FileUtils.copy_entry(project_params[:file].path, import_upload_path)
@project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id], @project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id],
current_user, current_user,
File.expand_path(imported_file), import_upload_path,
project_params[:path]).execute project_params[:path]).execute
if @project.saved? if @project.saved?
......
...@@ -76,6 +76,8 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -76,6 +76,8 @@ class Projects::BlobController < Projects::ApplicationController
end end
def diff def diff
apply_diff_view_cookie!
@form = UnfoldForm.new(params) @form = UnfoldForm.new(params)
@lines = Gitlab::Highlight.highlight_lines(repository, @ref, @path) @lines = Gitlab::Highlight.highlight_lines(repository, @ref, @path)
@lines = @lines[@form.since - 1..@form.to - 1] @lines = @lines[@form.since - 1..@form.to - 1]
......
...@@ -101,7 +101,7 @@ class SessionsController < Devise::SessionsController ...@@ -101,7 +101,7 @@ class SessionsController < Devise::SessionsController
# Prevent alert from popping up on the first page shown after authentication. # Prevent alert from popping up on the first page shown after authentication.
flash[:alert] = nil flash[:alert] = nil
redirect_to user_omniauth_authorize_path(provider.to_sym) redirect_to omniauth_authorize_path(:user, provider)
end end
def valid_otp_attempt?(user) def valid_otp_attempt?(user)
......
...@@ -163,9 +163,13 @@ module ApplicationHelper ...@@ -163,9 +163,13 @@ module ApplicationHelper
# `html_class` argument is provided. # `html_class` argument is provided.
# #
# Returns an HTML-safe String # Returns an HTML-safe String
def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago', skip_js: false) def time_ago_with_tooltip(time, placement: 'top', html_class: '', skip_js: false, short_format: false)
css_classes = short_format ? 'js-short-timeago' : 'js-timeago'
css_classes << " #{html_class}" unless html_class.blank?
css_classes << ' js-timeago-pending' unless skip_js
element = content_tag :time, time.to_s, element = content_tag :time, time.to_s,
class: "#{html_class} js-timeago #{"js-timeago-pending" unless skip_js}", class: css_classes,
datetime: time.to_time.getutc.iso8601, datetime: time.to_time.getutc.iso8601,
title: time.to_time.in_time_zone.to_s(:medium), title: time.to_time.in_time_zone.to_s(:medium),
data: { toggle: 'tooltip', placement: placement, container: 'body' } data: { toggle: 'tooltip', placement: placement, container: 'body' }
......
...@@ -13,12 +13,11 @@ module DiffHelper ...@@ -13,12 +13,11 @@ module DiffHelper
end end
def diff_view def diff_view
diff_views = %w(inline parallel) @diff_view ||= begin
diff_views = %w(inline parallel)
if diff_views.include?(cookies[:diff_view]) diff_view = cookies[:diff_view]
cookies[:diff_view] diff_view = diff_views.first unless diff_views.include?(diff_view)
else diff_view.to_sym
diff_views.first
end end
end end
...@@ -33,12 +32,23 @@ module DiffHelper ...@@ -33,12 +32,23 @@ module DiffHelper
options options
end end
def unfold_bottom_class(bottom) def diff_match_line(old_pos, new_pos, text: '', view: :inline, bottom: false)
bottom ? 'js-unfold js-unfold-bottom' : '' content = content_tag :td, text, class: "line_content match #{view == :inline ? '' : view}"
end cls = ['diff-line-num', 'unfold', 'js-unfold']
cls << 'js-unfold-bottom' if bottom
html = ''
if old_pos
html << content_tag(:td, '...', class: cls + ['old_line'], data: { linenumber: old_pos })
html << content unless view == :inline
end
if new_pos
html << content_tag(:td, '...', class: cls + ['new_line'], data: { linenumber: new_pos })
html << content
end
def unfold_class(unfold) html.html_safe
unfold ? 'unfold js-unfold' : ''
end end
def diff_line_content(line, line_type = nil) def diff_line_content(line, line_type = nil)
...@@ -67,11 +77,11 @@ module DiffHelper ...@@ -67,11 +77,11 @@ module DiffHelper
end end
def inline_diff_btn def inline_diff_btn
diff_btn('Inline', 'inline', diff_view == 'inline') diff_btn('Inline', 'inline', diff_view == :inline)
end end
def parallel_diff_btn def parallel_diff_btn
diff_btn('Side-by-side', 'parallel', diff_view == 'parallel') diff_btn('Side-by-side', 'parallel', diff_view == :parallel)
end end
def submodule_link(blob, ref, repository = @repository) def submodule_link(blob, ref, repository = @repository)
...@@ -103,7 +113,8 @@ module DiffHelper ...@@ -103,7 +113,8 @@ module DiffHelper
commit = commit_for_diff(diff_file) commit = commit_for_diff(diff_file)
{ {
blob_diff_path: namespace_project_blob_diff_path(project.namespace, project, blob_diff_path: namespace_project_blob_diff_path(project.namespace, project,
tree_join(commit.id, diff_file.file_path)) tree_join(commit.id, diff_file.file_path)),
view: diff_view
} }
end end
......
...@@ -6,6 +6,10 @@ class Ability ...@@ -6,6 +6,10 @@ class Ability
return [] unless user.is_a?(User) return [] unless user.is_a?(User)
return [] if user.blocked? return [] if user.blocked?
abilities_by_subject_class(user: user, subject: subject)
end
def abilities_by_subject_class(user:, subject:)
case subject case subject
when CommitStatus then commit_status_abilities(user, subject) when CommitStatus then commit_status_abilities(user, subject)
when Project then project_abilities(user, subject) when Project then project_abilities(user, subject)
......
...@@ -21,19 +21,19 @@ class ProjectMember < Member ...@@ -21,19 +21,19 @@ class ProjectMember < Member
# or symbol like :master representing role # or symbol like :master representing role
# #
# Ex. # Ex.
# add_users_into_projects( # add_users_to_projects(
# project_ids, # project_ids,
# user_ids, # user_ids,
# ProjectMember::MASTER # ProjectMember::MASTER
# ) # )
# #
# add_users_into_projects( # add_users_to_projects(
# project_ids, # project_ids,
# user_ids, # user_ids,
# :master # :master
# ) # )
# #
def add_users_into_projects(project_ids, user_ids, access, current_user = nil) def add_users_to_projects(project_ids, user_ids, access, current_user = nil)
access_level = if roles_hash.has_key?(access) access_level = if roles_hash.has_key?(access)
roles_hash[access] roles_hash[access]
elsif roles_hash.values.include?(access.to_i) elsif roles_hash.values.include?(access.to_i)
......
...@@ -378,11 +378,6 @@ class Project < ActiveRecord::Base ...@@ -378,11 +378,6 @@ class Project < ActiveRecord::Base
joins(join_body).reorder('join_note_counts.amount DESC') joins(join_body).reorder('join_note_counts.amount DESC')
end end
# Deletes gitlab project export files older than 24 hours
def remove_gitlab_exports!
Gitlab::Popen.popen(%W(find #{Gitlab::ImportExport.storage_path} -not -path #{Gitlab::ImportExport.storage_path} -mmin +1440 -delete))
end
end end
def repository_storage_path def repository_storage_path
......
...@@ -34,7 +34,7 @@ class ProjectTeam ...@@ -34,7 +34,7 @@ class ProjectTeam
end end
def add_users(users, access, current_user = nil) def add_users(users, access, current_user = nil)
ProjectMember.add_users_into_projects( ProjectMember.add_users_to_projects(
[project.id], [project.id],
users, users,
access, access,
......
class ImportExportCleanUpService
LAST_MODIFIED_TIME_IN_MINUTES = 1440
attr_reader :mmin, :path
def initialize(mmin = LAST_MODIFIED_TIME_IN_MINUTES)
@mmin = mmin
@path = Gitlab::ImportExport.storage_path
end
def execute
Gitlab::Metrics.measure(:import_export_clean_up) do
return unless File.directory?(path)
clean_up_export_files
end
end
private
def clean_up_export_files
Gitlab::Popen.popen(%W(find #{path} -not -path #{path} -mmin +#{mmin} -delete))
end
end
class RepositoryArchiveCleanUpService class RepositoryArchiveCleanUpService
LAST_MODIFIED_TIME_IN_MINUTES = 120 LAST_MODIFIED_TIME_IN_MINUTES = 120
attr_reader :mmin, :path
def initialize(mmin = LAST_MODIFIED_TIME_IN_MINUTES) def initialize(mmin = LAST_MODIFIED_TIME_IN_MINUTES)
@mmin = mmin @mmin = mmin
@path = Gitlab.config.gitlab.repository_downloads_path @path = Gitlab.config.gitlab.repository_downloads_path
...@@ -17,8 +19,6 @@ class RepositoryArchiveCleanUpService ...@@ -17,8 +19,6 @@ class RepositoryArchiveCleanUpService
private private
attr_reader :mmin, :path
def clean_up_old_archives def clean_up_old_archives
run(%W(find #{path} -not -path #{path} -type f \( -name \*.tar -o -name \*.bz2 -o -name \*.tar.gz -o -name \*.zip \) -maxdepth 2 -mmin +#{mmin} -delete)) run(%W(find #{path} -not -path #{path} -type f \( -name \*.tar -o -name \*.bz2 -o -name \*.tar.gz -o -name \*.zip \) -maxdepth 2 -mmin +#{mmin} -delete))
end end
......
...@@ -228,6 +228,9 @@ ...@@ -228,6 +228,9 @@
= f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'control-label col-sm-2' = f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= f.number_field :max_artifacts_size, class: 'form-control' = f.number_field :max_artifacts_size, class: 'form-control'
.help-block
Set the maximum file size each build's artifacts can have
= link_to "(?)", help_page_path("user/admin_area/settings/continuous_integration", anchor: "maximum-artifacts-size")
- if Gitlab.config.registry.enabled - if Gitlab.config.registry.enabled
%fieldset %fieldset
...@@ -385,4 +388,4 @@ ...@@ -385,4 +388,4 @@
.form-actions .form-actions
= f.submit 'Save', class: 'btn btn-save' = f.submit 'Save', class: 'btn btn-save'
\ No newline at end of file
...@@ -140,12 +140,10 @@ ...@@ -140,12 +140,10 @@
.panel-heading .panel-heading
This user is blocked This user is blocked
.panel-body .panel-body
%p Blocking user has the following effects: %p A blocked user cannot:
%ul %ul
%li User will not be able to login %li Log in
%li User will not be able to access git repositories %li Access Git repositories
%li Personal projects will be left
%li Owned groups will be left
%br %br
= link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' } = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' }
- else - else
......
= form_tag(user_omniauth_authorize_path("crowd"), id: 'new_crowd_user' ) do = form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user' ) do
= text_field_tag :username, nil, {class: "form-control top", placeholder: "Username", autofocus: "autofocus"} = text_field_tag :username, nil, {class: "form-control top", placeholder: "Username", autofocus: "autofocus"}
= password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
- if devise_mapping.rememberable? - if devise_mapping.rememberable?
......
...@@ -5,4 +5,4 @@ ...@@ -5,4 +5,4 @@
- providers.each do |provider| - providers.each do |provider|
%span.light %span.light
- has_icon = provider_has_icon?(provider) - has_icon = provider_has_icon?(provider)
= link_to provider_image_tag(provider), user_omniauth_authorize_path(provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn'), "data-no-turbolink" => "true" = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn'), "data-no-turbolink" => "true"
...@@ -3,3 +3,5 @@ New Issue was created. ...@@ -3,3 +3,5 @@ New Issue was created.
Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %> Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %>
Author: <%= @issue.author_name %> Author: <%= @issue.author_name %>
Assignee: <%= @issue.assignee_name %> Assignee: <%= @issue.assignee_name %>
<%= @issue.description %>
...@@ -6,3 +6,5 @@ New Merge Request <%= @merge_request.to_reference %> ...@@ -6,3 +6,5 @@ New Merge Request <%= @merge_request.to_reference %>
Author: <%= @merge_request.author_name %> Author: <%= @merge_request.author_name %>
Assignee: <%= @merge_request.assignee_name %> Assignee: <%= @merge_request.assignee_name %>
<%= @merge_request.description %>
...@@ -70,7 +70,7 @@ ...@@ -70,7 +70,7 @@
= link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'provider-btn' do = link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'provider-btn' do
Disconnect Disconnect
- else - else
= link_to user_omniauth_authorize_path(provider), method: :post, class: 'provider-btn not-active', "data-no-turbolink" => "true" do = link_to omniauth_authorize_path(:user, provider), method: :post, class: 'provider-btn not-active', "data-no-turbolink" => "true" do
Connect Connect
%hr %hr
- if current_user.can_change_username? - if current_user.can_change_username?
......
- if @lines.present? - if @lines.present?
- line_class = diff_view == :inline ? '' : diff_view
- if @form.unfold? && @form.since != 1 && !@form.bottom? - if @form.unfold? && @form.since != 1 && !@form.bottom?
%tr.line_holder %tr.line_holder{ class: line_class }
= render "projects/diffs/match_line", { line: @match_line, = diff_match_line @form.since, @form.since, text: @match_line, view: diff_view
line_old: @form.since, line_new: @form.since, bottom: false, new_file: false }
- @lines.each_with_index do |line, index| - @lines.each_with_index do |line, index|
- line_new = index + @form.since - line_new = index + @form.since
- line_old = line_new - @form.offset - line_old = line_new - @form.offset
%tr.line_holder{ id: line_old } - line_content = capture do
%td.old_line.diff-line-num{ data: { linenumber: line_old } } %td.line_content.noteable_line{ class: line_class }==#{' ' * @form.indent}#{line}
= link_to raw(line_old), "##{line_old}" %tr.line_holder{ id: line_old, class: line_class }
%td.new_line.diff-line-num{ data: { linenumber: line_old } } - case diff_view
= link_to raw(line_new) , "##{line_old}" - when :inline
%td.line_content.noteable_line==#{' ' * @form.indent}#{line} %td.old_line.diff-line-num{ data: { linenumber: line_old } }
%a{href: "##{line_old}", data: { linenumber: line_old }}
%td.new_line.diff-line-num{ data: { linenumber: line_new } }
%a{href: "##{line_new}", data: { linenumber: line_new }}
= line_content
- when :parallel
%td.old_line.diff-line-num{data: { linenumber: line_old }}
= link_to raw(line_old), "##{line_old}"
= line_content
%td.new_line.diff-line-num{data: { linenumber: line_new }}
= link_to raw(line_new), "##{line_new}"
= line_content
- if @form.unfold? && @form.bottom? && @form.to < @blob.loc - if @form.unfold? && @form.bottom? && @form.to < @blob.loc
%tr.line_holder{ id: @form.to } %tr.line_holder{ id: @form.to, class: line_class }
= render "projects/diffs/match_line", { line: @match_line, = diff_match_line @form.to, @form.to, text: @match_line, view: diff_view, bottom: true
line_old: @form.to, line_new: @form.to, bottom: true, new_file: false }
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
- if pipeline.finished_at - if pipeline.finished_at
%p.finished-at %p.finished-at
= icon("calendar") = icon("calendar")
#{time_ago_with_tooltip(pipeline.finished_at)} #{time_ago_with_tooltip(pipeline.finished_at, short_format: true, skip_js: true)}
%td.pipeline-actions %td.pipeline-actions
.controls.hidden-xs.pull-right .controls.hidden-xs.pull-right
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
.nothing-here-block.diff-collapsed{data: { diff_for_path: url } } .nothing-here-block.diff-collapsed{data: { diff_for_path: url } }
This diff is collapsed. Click to expand it. This diff is collapsed. Click to expand it.
- elsif diff_file.diff_lines.length > 0 - elsif diff_file.diff_lines.length > 0
- if diff_view == 'parallel' - if diff_view == :parallel
= render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob
- else - else
= render "projects/diffs/text_file", diff_file: diff_file = render "projects/diffs/text_file", diff_file: diff_file
......
- show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true) - show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true)
- diff_files = diffs.diff_files - diff_files = diffs.diff_files
- if diff_view == 'parallel' - if diff_view == :parallel
- fluid_layout true - fluid_layout true
.content-block.oneline-block.files-changed .content-block.oneline-block.files-changed
...@@ -29,5 +29,5 @@ ...@@ -29,5 +29,5 @@
- next unless blob - next unless blob
- blob.load_all_data!(diffs.project.repository) unless blob.only_display_raw? - blob.load_all_data!(diffs.project.repository) unless blob.only_display_raw?
= render 'projects/diffs/file', i: index, project: diffs.project, = render 'projects/diffs/file', index: index, project: diffs.project,
diff_file: diff_file, diff_commit: diff_commit, blob: blob diff_file: diff_file, diff_commit: diff_commit, blob: blob
.diff-file.file-holder{id: "diff-#{i}", data: diff_file_html_data(project, diff_file)} .diff-file.file-holder{id: "diff-#{index}", data: diff_file_html_data(project, diff_file)}
.file-title{id: "file-path-#{hexdigest(diff_file.file_path)}"} .file-title{id: "file-path-#{hexdigest(diff_file.file_path)}"}
= render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_commit, project: project, url: "#diff-#{i}" = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_commit, project: project, url: "#diff-#{index}"
- unless diff_file.submodule? - unless diff_file.submodule?
.file-actions.hidden-xs .file-actions.hidden-xs
......
...@@ -4,8 +4,7 @@ ...@@ -4,8 +4,7 @@
%tr.line_holder{ plain ? { class: type} : { class: type, id: line_code } } %tr.line_holder{ plain ? { class: type} : { class: type, id: line_code } }
- case type - case type
- when 'match' - when 'match'
= render "projects/diffs/match_line", { line: line.text, = diff_match_line line.old_pos, line.new_pos, text: line.text
line_old: line.old_pos, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file }
- when 'nonewline' - when 'nonewline'
%td.old_line.diff-line-num %td.old_line.diff-line-num
%td.new_line.diff-line-num %td.new_line.diff-line-num
......
%td.old_line.diff-line-num{data: {linenumber: line_old},
class: [unfold_bottom_class(bottom), unfold_class(!new_file)]}
\...
%td.new_line.diff-line-num{data: {linenumber: line_new},
class: [unfold_bottom_class(bottom), unfold_class(!new_file)]}
\...
%td.line_content.match= line
/ Side-by-side diff view / Side-by-side diff view
%div.text-file.diff-wrap-lines.code.file-content.js-syntax-highlight{ data: diff_view_data } %div.text-file.diff-wrap-lines.code.file-content.js-syntax-highlight{ data: diff_view_data }
%table %table
- last_line = 0
- diff_file.parallel_diff_lines.each do |line| - diff_file.parallel_diff_lines.each do |line|
- left = line[:left] - left = line[:left]
- right = line[:right] - right = line[:right]
- last_line = right.new_pos if right
%tr.line_holder.parallel %tr.line_holder.parallel
- if left - if left
- if left.meta? - if left.meta?
%td.old_line.diff-line-num.empty-cell = diff_match_line left.old_pos, nil, text: left.text, view: :parallel
%td.line_content.parallel.match= left.text
- else - else
- left_line_code = diff_file.line_code(left) - left_line_code = diff_file.line_code(left)
- left_position = diff_file.position(left) - left_position = diff_file.position(left)
...@@ -21,8 +22,7 @@ ...@@ -21,8 +22,7 @@
- if right - if right
- if right.meta? - if right.meta?
%td.old_line.diff-line-num.empty-cell = diff_match_line nil, right.new_pos, text: left.text, view: :parallel
%td.line_content.parallel.match= left.text
- else - else
- right_line_code = diff_file.line_code(right) - right_line_code = diff_file.line_code(right)
- right_position = diff_file.position(right) - right_position = diff_file.position(right)
...@@ -37,3 +37,5 @@ ...@@ -37,3 +37,5 @@
- discussion_left, discussion_right = parallel_diff_discussions(left, right, diff_file) - discussion_left, discussion_right = parallel_diff_discussions(left, right, diff_file)
- if discussion_left || discussion_right - if discussion_left || discussion_right
= render "discussions/parallel_diff_discussion", discussion_left: discussion_left, discussion_right: discussion_right = render "discussions/parallel_diff_discussion", discussion_left: discussion_left, discussion_right: discussion_right
- if !diff_file.new_file && last_line > 0
= diff_match_line last_line, last_line, bottom: true, view: :parallel
...@@ -15,6 +15,5 @@ ...@@ -15,6 +15,5 @@
- if discussion - if discussion
= render "discussions/diff_discussion", discussion: discussion = render "discussions/diff_discussion", discussion: discussion
- if last_line > 0 - if !diff_file.new_file && last_line > 0
= render "projects/diffs/match_line", { line: "", = diff_match_line last_line, last_line, bottom: true
line_old: last_line, line_new: last_line, bottom: true, new_file: diff_file.new_file }
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- page_description @merge_request.description - page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes - page_card_attributes @merge_request.card_attributes
- if diff_view == 'parallel' - if diff_view == :parallel
- fluid_layout true - fluid_layout true
.merge-request{'data-url' => merge_request_path(@merge_request)} .merge-request{'data-url' => merge_request_path(@merge_request)}
......
class GitlabRemoveProjectExportWorker class ImportExportProjectCleanupWorker
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options queue: :default sidekiq_options queue: :default
def perform def perform
Project.remove_gitlab_exports! ImportExportCleanUpService.new.execute
end end
end end
...@@ -287,9 +287,9 @@ Settings.cron_jobs['admin_email_worker']['job_class'] = 'AdminEmailWorker' ...@@ -287,9 +287,9 @@ Settings.cron_jobs['admin_email_worker']['job_class'] = 'AdminEmailWorker'
Settings.cron_jobs['repository_archive_cache_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['repository_archive_cache_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['repository_archive_cache_worker']['cron'] ||= '0 * * * *' Settings.cron_jobs['repository_archive_cache_worker']['cron'] ||= '0 * * * *'
Settings.cron_jobs['repository_archive_cache_worker']['job_class'] = 'RepositoryArchiveCacheWorker' Settings.cron_jobs['repository_archive_cache_worker']['job_class'] = 'RepositoryArchiveCacheWorker'
Settings.cron_jobs['gitlab_remove_project_export_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['import_export_project_cleanup_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['gitlab_remove_project_export_worker']['cron'] ||= '0 * * * *' Settings.cron_jobs['import_export_project_cleanup_worker']['cron'] ||= '0 * * * *'
Settings.cron_jobs['gitlab_remove_project_export_worker']['job_class'] = 'GitlabRemoveProjectExportWorker' Settings.cron_jobs['import_export_project_cleanup_worker']['job_class'] = 'ImportExportProjectCleanupWorker'
Settings.cron_jobs['requests_profiles_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['requests_profiles_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['requests_profiles_worker']['cron'] ||= '0 0 * * *' Settings.cron_jobs['requests_profiles_worker']['cron'] ||= '0 0 * * *'
Settings.cron_jobs['requests_profiles_worker']['job_class'] = 'RequestsProfilesWorker' Settings.cron_jobs['requests_profiles_worker']['job_class'] = 'RequestsProfilesWorker'
......
...@@ -145,6 +145,9 @@ if Gitlab::Metrics.enabled? ...@@ -145,6 +145,9 @@ if Gitlab::Metrics.enabled?
config.instrument_methods(Rinku) config.instrument_methods(Rinku)
config.instrument_instance_methods(Repository) config.instrument_instance_methods(Repository)
config.instrument_methods(Gitlab::Highlight)
config.instrument_instance_methods(Gitlab::Highlight)
end end
GC::Profiler.enable GC::Profiler.enable
......
class Gitlab::Seeder::Builds class Gitlab::Seeder::Builds
STAGES = %w[build notify_build test notify_test deploy notify_deploy] STAGES = %w[build notify_build test notify_test deploy notify_deploy]
def initialize(project) def initialize(project)
@project = project @project = project
end end
...@@ -8,26 +8,26 @@ class Gitlab::Seeder::Builds ...@@ -8,26 +8,26 @@ class Gitlab::Seeder::Builds
def seed! def seed!
pipelines.each do |pipeline| pipelines.each do |pipeline|
begin begin
build_create!(pipeline, name: 'build:linux', stage: 'build') build_create!(pipeline, name: 'build:linux', stage: 'build', status_event: :success)
build_create!(pipeline, name: 'build:osx', stage: 'build') build_create!(pipeline, name: 'build:osx', stage: 'build', status_event: :success)
build_create!(pipeline, name: 'slack post build', stage: 'notify_build') build_create!(pipeline, name: 'slack post build', stage: 'notify_build', status_event: :success)
build_create!(pipeline, name: 'rspec:linux', stage: 'test') build_create!(pipeline, name: 'rspec:linux', stage: 'test', status_event: :success)
build_create!(pipeline, name: 'rspec:windows', stage: 'test') build_create!(pipeline, name: 'rspec:windows', stage: 'test', status_event: :success)
build_create!(pipeline, name: 'rspec:windows', stage: 'test') build_create!(pipeline, name: 'rspec:windows', stage: 'test', status_event: :success)
build_create!(pipeline, name: 'rspec:osx', stage: 'test') build_create!(pipeline, name: 'rspec:osx', stage: 'test', status_event: :success)
build_create!(pipeline, name: 'spinach:linux', stage: 'test') build_create!(pipeline, name: 'spinach:linux', stage: 'test', status: :pending)
build_create!(pipeline, name: 'spinach:osx', stage: 'test') build_create!(pipeline, name: 'spinach:osx', stage: 'test', status_event: :cancel)
build_create!(pipeline, name: 'cucumber:linux', stage: 'test') build_create!(pipeline, name: 'cucumber:linux', stage: 'test', status_event: :run)
build_create!(pipeline, name: 'cucumber:osx', stage: 'test') build_create!(pipeline, name: 'cucumber:osx', stage: 'test', status_event: :drop)
build_create!(pipeline, name: 'slack post test', stage: 'notify_test') build_create!(pipeline, name: 'slack post test', stage: 'notify_test', status_event: :success)
build_create!(pipeline, name: 'staging', stage: 'deploy', environment: 'staging') build_create!(pipeline, name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success)
build_create!(pipeline, name: 'production', stage: 'deploy', environment: 'production', when: 'manual') build_create!(pipeline, name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :success)
commit_status_create!(pipeline, name: 'jenkins') commit_status_create!(pipeline, name: 'jenkins', status: :success)
print '.' print '.'
rescue ActiveRecord::RecordInvalid rescue ActiveRecord::RecordInvalid
...@@ -48,7 +48,7 @@ class Gitlab::Seeder::Builds ...@@ -48,7 +48,7 @@ class Gitlab::Seeder::Builds
def build_create!(pipeline, opts = {}) def build_create!(pipeline, opts = {})
attributes = build_attributes_for(pipeline, opts) attributes = build_attributes_for(pipeline, opts)
build = Ci::Build.new(attributes) build = Ci::Build.create!(attributes)
if opts[:name].start_with?('build') if opts[:name].start_with?('build')
artifacts_cache_file(artifacts_archive_path) do |file| artifacts_cache_file(artifacts_archive_path) do |file|
...@@ -60,23 +60,20 @@ class Gitlab::Seeder::Builds ...@@ -60,23 +60,20 @@ class Gitlab::Seeder::Builds
end end
end end
build.save!
build.update(status: build_status)
if %w(running success failed).include?(build.status) if %w(running success failed).include?(build.status)
# We need to set build trace after saving a build (id required) # We need to set build trace after saving a build (id required)
build.trace = FFaker::Lorem.paragraphs(6).join("\n\n") build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
end end
end end
def commit_status_create!(pipeline, opts = {}) def commit_status_create!(pipeline, opts = {})
attributes = commit_status_attributes_for(pipeline, opts) attributes = commit_status_attributes_for(pipeline, opts)
GenericCommitStatus.create(attributes) GenericCommitStatus.create!(attributes)
end end
def commit_status_attributes_for(pipeline, opts) def commit_status_attributes_for(pipeline, opts)
{ name: 'test build', stage: 'test', stage_idx: stage_index(opts[:stage]), { name: 'test build', stage: 'test', stage_idx: stage_index(opts[:stage]),
ref: 'master', user: build_user, project: @project, pipeline: pipeline, ref: 'master', tag: false, user: build_user, project: @project, pipeline: pipeline,
created_at: Time.now, updated_at: Time.now created_at: Time.now, updated_at: Time.now
}.merge(opts) }.merge(opts)
end end
......
...@@ -14,7 +14,7 @@ class MoveFromDevelopersCanMergeToProtectedBranchesMergeAccess < ActiveRecord::M ...@@ -14,7 +14,7 @@ class MoveFromDevelopersCanMergeToProtectedBranchesMergeAccess < ActiveRecord::M
def up def up
execute <<-HEREDOC execute <<-HEREDOC
INSERT into protected_branch_merge_access_levels (protected_branch_id, access_level, created_at, updated_at) INSERT into protected_branch_merge_access_levels (protected_branch_id, access_level, created_at, updated_at)
SELECT id, (CASE WHEN developers_can_merge THEN 1 ELSE 0 END), now(), now() SELECT id, (CASE WHEN developers_can_merge THEN 30 ELSE 40 END), now(), now()
FROM protected_branches FROM protected_branches
HEREDOC HEREDOC
end end
...@@ -23,7 +23,7 @@ class MoveFromDevelopersCanMergeToProtectedBranchesMergeAccess < ActiveRecord::M ...@@ -23,7 +23,7 @@ class MoveFromDevelopersCanMergeToProtectedBranchesMergeAccess < ActiveRecord::M
execute <<-HEREDOC execute <<-HEREDOC
UPDATE protected_branches SET developers_can_merge = TRUE UPDATE protected_branches SET developers_can_merge = TRUE
WHERE id IN (SELECT protected_branch_id FROM protected_branch_merge_access_levels WHERE id IN (SELECT protected_branch_id FROM protected_branch_merge_access_levels
WHERE access_level = 1); WHERE access_level = 30);
HEREDOC HEREDOC
end end
end end
...@@ -14,7 +14,7 @@ class MoveFromDevelopersCanPushToProtectedBranchesPushAccess < ActiveRecord::Mig ...@@ -14,7 +14,7 @@ class MoveFromDevelopersCanPushToProtectedBranchesPushAccess < ActiveRecord::Mig
def up def up
execute <<-HEREDOC execute <<-HEREDOC
INSERT into protected_branch_push_access_levels (protected_branch_id, access_level, created_at, updated_at) INSERT into protected_branch_push_access_levels (protected_branch_id, access_level, created_at, updated_at)
SELECT id, (CASE WHEN developers_can_push THEN 1 ELSE 0 END), now(), now() SELECT id, (CASE WHEN developers_can_push THEN 30 ELSE 40 END), now(), now()
FROM protected_branches FROM protected_branches
HEREDOC HEREDOC
end end
...@@ -23,7 +23,7 @@ class MoveFromDevelopersCanPushToProtectedBranchesPushAccess < ActiveRecord::Mig ...@@ -23,7 +23,7 @@ class MoveFromDevelopersCanPushToProtectedBranchesPushAccess < ActiveRecord::Mig
execute <<-HEREDOC execute <<-HEREDOC
UPDATE protected_branches SET developers_can_push = TRUE UPDATE protected_branches SET developers_can_push = TRUE
WHERE id IN (SELECT protected_branch_id FROM protected_branch_push_access_levels WHERE id IN (SELECT protected_branch_id FROM protected_branch_push_access_levels
WHERE access_level = 1); WHERE access_level = 30);
HEREDOC HEREDOC
end end
end end
...@@ -14,6 +14,6 @@ class RemoveDevelopersCanPushFromProtectedBranches < ActiveRecord::Migration ...@@ -14,6 +14,6 @@ class RemoveDevelopersCanPushFromProtectedBranches < ActiveRecord::Migration
end end
def down def down
add_column_with_default(:protected_branches, :developers_can_push, :boolean, default: false, null: false) add_column_with_default(:protected_branches, :developers_can_push, :boolean, default: false, allow_null: false)
end end
end end
...@@ -14,6 +14,6 @@ class RemoveDevelopersCanMergeFromProtectedBranches < ActiveRecord::Migration ...@@ -14,6 +14,6 @@ class RemoveDevelopersCanMergeFromProtectedBranches < ActiveRecord::Migration
end end
def down def down
add_column_with_default(:protected_branches, :developers_can_merge, :boolean, default: false, null: false) add_column_with_default(:protected_branches, :developers_can_merge, :boolean, default: false, allow_null: false)
end end
end end
class RemoveBuildsEnableIndexOnProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
remove_index :projects, column: :builds_enabled if index_exists?(:projects, :builds_enabled)
end
end
# rubocop:disable all
# 20141121133009_add_timestamps_to_members.rb was meant to ensure that all
# rows in the members table had created_at and updated_at set, following an
# error in a previous migration. This failed to set all rows in at least one
# case: https://gitlab.com/gitlab-org/gitlab-ce/issues/20568
#
# Why this happened is lost in the mists of time, so repeat the SQL query
# without speculation, just in case more than one person was affected.
class AddTimestampsToMembersAgain < ActiveRecord::Migration
DOWNTIME = false
def up
execute "UPDATE members SET created_at = NOW() WHERE created_at IS NULL"
execute "UPDATE members SET updated_at = NOW() WHERE updated_at IS NULL"
end
def down
# no change
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160726093600) do ActiveRecord::Schema.define(version: 20160804150737) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -852,7 +852,6 @@ ActiveRecord::Schema.define(version: 20160726093600) do ...@@ -852,7 +852,6 @@ ActiveRecord::Schema.define(version: 20160726093600) do
end end
add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree
add_index "projects", ["builds_enabled"], name: "index_projects_on_builds_enabled", using: :btree
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree
add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree
......
...@@ -54,7 +54,5 @@ ...@@ -54,7 +54,5 @@
## Contributor documentation ## Contributor documentation
- [Documentation styleguide](development/doc_styleguide.md) Use this styleguide if you are - [Development](development/README.md) All styleguides and explanations how to contribute.
contributing to documentation.
- [Development](development/README.md) Explains the architecture and the guidelines for shell commands.
- [Legal](legal/README.md) Contributor license agreements. - [Legal](legal/README.md) Contributor license agreements.
# Build artifacts administration
>**Notes:**
>- Introduced in GitLab 8.2 and GitLab Runner 0.7.0.
>- Starting from GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format
changed to `ZIP`.
>- This is the administration documentation. For the user guide see
[user/project/builds/artifacts.md](../user/project/builds/artifacts.md).
Artifacts is a list of files and directories which are attached to a build
after it completes successfully. This feature is enabled by default in all
GitLab installations. Keep reading if you want to know how to disable it.
## Disabling build artifacts
To disable artifacts site-wide, follow the steps below.
---
**In Omnibus installations:**
1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
```ruby
gitlab_rails['artifacts_enabled'] = false
```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
---
**In installations from source:**
1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
```yaml
artifacts:
enabled: false
```
1. Save the file and [restart GitLab][] for the changes to take effect.
## Storing build artifacts
After a successful build, GitLab Runner uploads an archive containing the build
artifacts to GitLab.
To change the location where the artifacts are stored, follow the steps below.
---
**In Omnibus installations:**
_The artifacts are stored by default in
`/var/opt/gitlab/gitlab-rails/shared/artifacts`._
1. To change the storage path for example to `/mnt/storage/artifacts`, edit
`/etc/gitlab/gitlab.rb` and add the following line:
```ruby
gitlab_rails['artifacts_path'] = "/mnt/storage/artifacts"
```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
---
**In installations from source:**
_The artifacts are stored by default in
`/home/git/gitlab/shared/artifacts`._
1. To change the storage path for example to `/mnt/storage/artifacts`, edit
`/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
```yaml
artifacts:
enabled: true
path: /mnt/storage/artifacts
```
1. Save the file and [restart GitLab][] for the changes to take effect.
## Set the maximum file size of the artifacts
Provided the artifacts are enabled, you can change the maximum file size of the
artifacts through the [Admin area settings](../user/admin_area/settings/continuous_integration#maximum-artifacts-size).
[reconfigure gitlab]: restart_gitlab.md "How to restart GitLab"
[restart gitlab]: restart_gitlab.md "How to restart GitLab"
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
- [Use variables in your `.gitlab-ci.yml`](variables/README.md) - [Use variables in your `.gitlab-ci.yml`](variables/README.md)
- [Use SSH keys in your build environment](ssh_keys/README.md) - [Use SSH keys in your build environment](ssh_keys/README.md)
- [Trigger builds through the API](triggers/README.md) - [Trigger builds through the API](triggers/README.md)
- [Build artifacts](build_artifacts/README.md) - [Build artifacts](../user/project/builds/artifacts.md)
- [User permissions](../user/permissions.md#gitlab-ci) - [User permissions](../user/permissions.md#gitlab-ci)
- [API](../api/ci/README.md) - [API](../api/ci/README.md)
- [CI services (linked docker containers)](services/README.md) - [CI services (linked docker containers)](services/README.md)
# Introduction to build artifacts This document was moved to:
Artifacts is a list of files and directories which are attached to a build - [user/project/builds/artifacts.md](../../user/project/builds/artifacts.md) - user guide
after it completes successfully. This feature is enabled by default in all GitLab installations. - [administration/build_artifacts.md](../../administration/build_artifacts.md) - administrator guide
_If you are searching for ways to use artifacts, jump to
[Defining artifacts in `.gitlab-ci.yml`](#defining-artifacts-in-gitlab-ciyml)._
Since GitLab 8.2 and [GitLab Runner] 0.7.0, build artifacts that are created by
GitLab Runner are uploaded to GitLab and are downloadable as a single archive
(`tar.gz`) using the GitLab UI.
Starting from GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format
changed to `ZIP`, and it is now possible to browse its contents, with the added
ability of downloading the files separately.
**Note:**
The artifacts browser will be available only for new artifacts that are sent
to GitLab using GitLab Runner version 1.0 and up. It will not be possible to
browse old artifacts already uploaded to GitLab.
## Disabling build artifacts
To disable artifacts site-wide, follow the steps below.
---
**In Omnibus installations:**
1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
```ruby
gitlab_rails['artifacts_enabled'] = false
```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
---
**In installations from source:**
1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
```yaml
artifacts:
enabled: false
```
1. Save the file and [restart GitLab][] for the changes to take effect.
## Defining artifacts in `.gitlab-ci.yml`
A simple example of using the artifacts definition in `.gitlab-ci.yml` would be
the following:
```yaml
pdf:
script: xelatex mycv.tex
artifacts:
paths:
- mycv.pdf
```
A job named `pdf` calls the `xelatex` command in order to build a pdf file from
the latex source file `mycv.tex`. We then define the `artifacts` paths which in
turn are defined with the `paths` keyword. All paths to files and directories
are relative to the repository that was cloned during the build.
For more examples on artifacts, follow the
[separate artifacts yaml documentation](../yaml/README.md#artifacts).
## Storing build artifacts
After a successful build, GitLab Runner uploads an archive containing the build
artifacts to GitLab.
To change the location where the artifacts are stored, follow the steps below.
---
**In Omnibus installations:**
_The artifacts are stored by default in
`/var/opt/gitlab/gitlab-rails/shared/artifacts`._
1. To change the storage path for example to `/mnt/storage/artifacts`, edit
`/etc/gitlab/gitlab.rb` and add the following line:
```ruby
gitlab_rails['artifacts_path'] = "/mnt/storage/artifacts"
```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
---
**In installations from source:**
_The artifacts are stored by default in
`/home/git/gitlab/shared/artifacts`._
1. To change the storage path for example to `/mnt/storage/artifacts`, edit
`/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
```yaml
artifacts:
enabled: true
path: /mnt/storage/artifacts
```
1. Save the file and [restart GitLab][] for the changes to take effect.
## Browsing build artifacts
When GitLab receives an artifacts archive, an archive metadata file is also
generated. This metadata file describes all the entries that are located in the
artifacts archive itself. The metadata file is in a binary format, with
additional GZIP compression.
GitLab does not extract the artifacts archive in order to save space, memory
and disk I/O. It instead inspects the metadata file which contains all the
relevant information. This is especially important when there is a lot of
artifacts, or an archive is a very large file.
---
After a successful build, if you visit the build's specific page, you can see
that there are two buttons.
One is for downloading the artifacts archive and the other for browsing its
contents.
![Build artifacts browser button](img/build_artifacts_browser_button.png)
---
The archive browser shows the name and the actual file size of each file in the
archive. If your artifacts contained directories, then you are also able to
browse inside them.
Below you can see an image of three different file formats, as well as two
directories.
![Build artifacts browser](img/build_artifacts_browser.png)
---
## Downloading build artifacts
If you need to download the whole archive, there are buttons in various places
inside GitLab that make that possible.
1. While on the builds page, you can see the download icon for each build's
artifacts archive in the right corner
1. While inside a specific build, you are presented with a download button
along with the one that browses the archive
1. And finally, when browsing an archive you can see the download button at
the top right corner
---
Note that GitLab does not extract the entire artifacts archive to send just a
single file to the user.
When clicking on a specific file, [GitLab Workhorse] extracts it from the
archive and the download begins.
This implementation saves space, memory and disk I/O.
[gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner "GitLab Runner repository"
[reconfigure gitlab]: ../../administration/restart_gitlab.md "How to restart GitLab documentation"
[restart gitlab]: ../../administration/restart_gitlab.md "How to restart GitLab documentation"
[gitlab workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse "GitLab Workhorse repository"
# Development # Development
## Outside of docs
- [CONTRIBUTING.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) main contributing guide
- [PROCESS.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/PROCESS.md) contributing process
- [GitLab Development Kit (GDK)](https://gitlab.com/gitlab-org/gitlab-development-kit) to install a development version
## Styleguides
- [Documentation styleguide](development/doc_styleguide.md) Use this styleguide if you are
contributing to documentation.
- [Migration Style Guide](migration_style_guide.md) for creating safe migrations
- [Testing standards and style guidelines](testing.md)
- [UI guide](ui_guide.md) for building GitLab with existing css styles and elements
- [SQL guidelines](sql.md) for SQL guidelines
## Process
- [Code review guidelines](code_review.md) for reviewing code and having code reviewed.
## Backend howtos
- [Architecture](architecture.md) of GitLab - [Architecture](architecture.md) of GitLab
- [CI setup](ci_setup.md) for testing GitLab - [CI setup](ci_setup.md) for testing GitLab
- [Code review guidelines](code_review.md) for reviewing code and having code
reviewed.
- [Gotchas](gotchas.md) to avoid - [Gotchas](gotchas.md) to avoid
- [How to dump production data to staging](db_dump.md) - [How to dump production data to staging](db_dump.md)
- [Instrumentation](instrumentation.md) - [Instrumentation](instrumentation.md)
- [Licensing](licensing.md) for ensuring license compliance
- [Migration Style Guide](migration_style_guide.md) for creating safe migrations
- [Performance guidelines](performance.md) - [Performance guidelines](performance.md)
- [Rake tasks](rake_tasks.md) for development - [Rake tasks](rake_tasks.md) for development
- [Shell commands](shell_commands.md) in the GitLab codebase - [Shell commands](shell_commands.md) in the GitLab codebase
- [Sidekiq debugging](sidekiq_debugging.md) - [Sidekiq debugging](sidekiq_debugging.md)
- [SQL guidelines](sql.md) for SQL guidelines - [What requires downtime?](what_requires_downtime.md)
- [Testing standards and style guidelines](testing.md)
- [UI guide](ui_guide.md) for building GitLab with existing css styles and elements ## Compliance
- [Licensing](licensing.md) for ensuring license compliance
...@@ -41,10 +41,10 @@ Rubocop](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/.rubocop.yml#L9 ...@@ -41,10 +41,10 @@ Rubocop](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/.rubocop.yml#L9
[Exception]: http://stackoverflow.com/q/10048173/223897 [Exception]: http://stackoverflow.com/q/10048173/223897
## Don't use inline CoffeeScript in views ## Don't use inline CoffeeScript/JavaScript in views
Using the inline `:coffee` or `:coffeescript` Haml filters comes with a Using the inline `:coffee` or `:coffeescript` Haml filters comes with a
performance overhead. performance overhead. Using inline JavaScript is not a good way to structure your code and should be avoided.
_**Note:** We've [removed these two filters](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/initializers/hamlit.rb) _**Note:** We've [removed these two filters](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/initializers/hamlit.rb)
in an initializer._ in an initializer._
...@@ -52,6 +52,7 @@ in an initializer._ ...@@ -52,6 +52,7 @@ in an initializer._
### Further reading ### Further reading
- Pull Request: [Replace CoffeeScript block into JavaScript in Views](https://git.io/vztMu) - Pull Request: [Replace CoffeeScript block into JavaScript in Views](https://git.io/vztMu)
- Stack Overflow: [Why you should not write inline JavaScript](http://programmers.stackexchange.com/questions/86589/why-should-i-avoid-inline-scripting)
- Stack Overflow: [Performance implications of using :coffescript filter inside HAML templates?](http://stackoverflow.com/a/17571242/223897) - Stack Overflow: [Performance implications of using :coffescript filter inside HAML templates?](http://stackoverflow.com/a/17571242/223897)
## ID-based CSS selectors need to be a bit more specific ## ID-based CSS selectors need to be a bit more specific
......
# What requires downtime?
When working with a database certain operations can be performed without taking
GitLab offline, others do require a downtime period. This guide describes
various operations and their impact.
## Adding Columns
On PostgreSQL you can safely add a new column to an existing table as long as it
does **not** have a default value. For example, this query would not require
downtime:
```sql
ALTER TABLE projects ADD COLUMN random_value int;
```
Add a column _with_ a default however does require downtime. For example,
consider this query:
```sql
ALTER TABLE projects ADD COLUMN random_value int DEFAULT 42;
```
This requires updating every single row in the `projects` table so that
`random_value` is set to `42` by default. This requires updating all rows and
indexes in a table. This in turn acquires enough locks on the table for it to
effectively block any other queries.
As of MySQL 5.6 adding a column to a table is still quite an expensive
operation, even when using `ALGORITHM=INPLACE` and `LOCK=NONE`. This means
downtime _may_ be required when modifying large tables as otherwise the
operation could potentially take hours to complete.
## Dropping Columns
On PostgreSQL you can safely remove an existing column without the need for
downtime. When you drop a column in PostgreSQL it's not immediately removed,
instead it is simply disabled. The data is removed on the next vacuum run.
On MySQL this operation requires downtime.
While database wise dropping a column may be fine on PostgreSQL this operation
still requires downtime because the application code may still be using the
column that was removed. For example, consider the following migration:
```ruby
class MyMigration < ActiveRecord::Migration
def change
remove_column :projects, :dummy
end
end
```
Now imagine that the GitLab instance is running and actively uses the `dummy`
column. If we were to run the migration this would result in the GitLab instance
producing errors whenever it tries to use the `dummy` column.
As a result of the above downtime _is_ required when removing a column, even
when using PostgreSQL.
## Changing Column Constraints
Generally changing column constraints requires checking all rows in the table to
see if they meet the new constraint, unless a constraint is _removed_. For
example, changing a column that previously allowed NULL values to not allow NULL
values requires the database to verify all existing rows.
The specific behaviour varies a bit between databases but in general the safest
approach is to assume changing constraints requires downtime.
## Changing Column Types
This operation requires downtime.
## Adding Indexes
Adding indexes is an expensive process that blocks INSERT and UPDATE queries for
the duration. When using PostgreSQL one can work arounds this by using the
`CONCURRENTLY` option:
```sql
CREATE INDEX CONCURRENTLY index_name ON projects (column_name);
```
Migrations can take advantage of this by using the method
`add_concurrent_index`. For example:
```ruby
class MyMigration < ActiveRecord::Migration
def change
add_concurrent_index :projects, :column_name
end
end
```
When running this on PostgreSQL the `CONCURRENTLY` option mentioned above is
used. On MySQL this method produces a regular `CREATE INDEX` query.
MySQL doesn't really have a workaround for this. Supposedly it _can_ create
indexes without the need for downtime but only for variable width columns. The
details on this are a bit sketchy. Since it's better to be safe than sorry one
should assume that adding indexes requires downtime on MySQL.
## Dropping Indexes
Dropping an index does not require downtime on both PostgreSQL and MySQL.
## Adding Tables
This operation is safe as there's no code using the table just yet.
## Dropping Tables
This operation requires downtime as application code may still be using the
table.
## Adding Foreign Keys
Adding foreign keys acquires an exclusive lock on both the source and target
tables in PostgreSQL. This requires downtime as otherwise the entire application
grinds to a halt for the duration of the operation.
On MySQL this operation also requires downtime _unless_ foreign key checks are
disabled. Because this means checks aren't enforced this is not ideal, as such
one should assume MySQL also requires downtime.
## Removing Foreign Keys
This operation should not require downtime on both PostgreSQL and MySQL.
## Updating Data
Updating data should generally be safe. The exception to this is data that's
being migrated from one version to another while the application still produces
data in the old version.
For example, imagine the application writes the string `'dog'` to a column but
it really is meant to write `'cat'` instead. One might think that the following
migration is all that is needed to solve this problem:
```ruby
class MyMigration < ActiveRecord::Migration
def up
execute("UPDATE some_table SET column = 'cat' WHERE column = 'dog';")
end
end
```
Unfortunately this is not enough. Because the application is still running and
using the old value this may result in the table still containing rows where
`column` is set to `dog`, even after the migration finished.
In these cases downtime _is_ required, even for rarely updated tables.
# Continuous integration Admin settings
## Maximum artifacts size
The maximum size of the [build artifacts][art-yml] can be set in the Admin area
of your GitLab instance. The value is in MB and the default is 100MB. Note that
this setting is set for each build.
1. Go to **Admin area > Settings** (`/admin/application_settings`).
![Admin area settings button](img/admin_area_settings_button.png)
1. Change the value of the maximum artifacts size (in MB):
![Admin area maximum artifacts size](img/admin_area_maximum_artifacts_size.png)
1. Hit **Save** for the changes to take effect.
[art-yml]: ../../../administration/build_artifacts.md
# Introduction to build artifacts
>**Notes:**
>- Since GitLab 8.2 and GitLab Runner 0.7.0, build artifacts that are created by
GitLab Runner are uploaded to GitLab and are downloadable as a single archive
(`tar.gz`) using the GitLab UI.
>- Starting from GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format
changed to `ZIP`, and it is now possible to browse its contents, with the added
ability of downloading the files separately.
>- The artifacts browser will be available only for new artifacts that are sent
to GitLab using GitLab Runner version 1.0 and up. It will not be possible to
browse old artifacts already uploaded to GitLab.
>- This is the user documentation. For the administration guide see
[administration/build_artifacts.md](../../../administration/build_artifacts.md).
Artifacts is a list of files and directories which are attached to a build
after it completes successfully. This feature is enabled by default in all GitLab installations.
## Defining artifacts in `.gitlab-ci.yml`
A simple example of using the artifacts definition in `.gitlab-ci.yml` would be
the following:
```yaml
pdf:
script: xelatex mycv.tex
artifacts:
paths:
- mycv.pdf
```
A job named `pdf` calls the `xelatex` command in order to build a pdf file from
the latex source file `mycv.tex`. We then define the `artifacts` paths which in
turn are defined with the `paths` keyword. All paths to files and directories
are relative to the repository that was cloned during the build.
For more examples on artifacts, follow the artifacts reference in
[`.gitlab-ci.yml` documentation](../../../ci/yaml/README.md#artifacts).
## Browsing build artifacts
When GitLab receives an artifacts archive, an archive metadata file is also
generated. This metadata file describes all the entries that are located in the
artifacts archive itself. The metadata file is in a binary format, with
additional GZIP compression.
GitLab does not extract the artifacts archive in order to save space, memory
and disk I/O. It instead inspects the metadata file which contains all the
relevant information. This is especially important when there is a lot of
artifacts, or an archive is a very large file.
---
After a build finishes, if you visit the build's specific page, you can see
that there are two buttons. One is for downloading the artifacts archive and
the other for browsing its contents.
![Build artifacts browser button](img/build_artifacts_browser_button.png)
---
The archive browser shows the name and the actual file size of each file in the
archive. If your artifacts contained directories, then you are also able to
browse inside them.
Below you can see how browsing looks like. In this case we have browsed inside
the archive and at this point there is one directory and one HTML file.
![Build artifacts browser](img/build_artifacts_browser.png)
---
## Downloading build artifacts
>**Note:**
GitLab does not extract the entire artifacts archive to send just a single file
to the user. When clicking on a specific file, [GitLab Workhorse] extracts it
from the archive and the download begins. This implementation saves space,
memory and disk I/O.
If you need to download the whole archive, there are buttons in various places
inside GitLab that make that possible.
1. While on the pipelines page, you can see the download icon for each build's
artifacts archive in the right corner:
![Build artifacts in Pipelines page](img/build_artifacts_pipelines_page.png)
1. While on the builds page, you can see the download icon for each build's
artifacts archive in the right corner:
![Build artifacts in Builds page](img/build_artifacts_builds_page.png)
1. While inside a specific build, you are presented with a download button
along with the one that browses the archive:
![Build artifacts browser button](img/build_artifacts_browser_button.png)
1. And finally, when browsing an archive you can see the download button at
the top right corner:
![Build artifacts browser](img/build_artifacts_browser.png)
[gitlab workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse "GitLab Workhorse repository"
...@@ -236,6 +236,15 @@ Feature: Project Merge Requests ...@@ -236,6 +236,15 @@ Feature: Project Merge Requests
And I unfold diff And I unfold diff
Then I should see additional file lines Then I should see additional file lines
@javascript
Scenario: I unfold diff in Side-by-Side view
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05"
And I click on the Changes tab
And I click Side-by-side Diff tab
And I unfold diff
Then I should see additional file lines
@javascript @javascript
Scenario: I show comments on a merge request side-by-side diff with comments in multiple files Scenario: I show comments on a merge request side-by-side diff with comments in multiple files
Given project "Shop" have "Bug NS-05" open merge request with diffs inside Given project "Shop" have "Bug NS-05" open merge request with diffs inside
......
...@@ -477,6 +477,9 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -477,6 +477,9 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I click Side-by-side Diff tab' do step 'I click Side-by-side Diff tab' do
find('a', text: 'Side-by-side').trigger('click') find('a', text: 'Side-by-side').trigger('click')
# Waits for load
expect(page).to have_css('.parallel')
end end
step 'I should see comments on the side-by-side diff page' do step 'I should see comments on the side-by-side diff page' do
......
...@@ -31,6 +31,14 @@ module Banzai ...@@ -31,6 +31,14 @@ module Banzai
# Text matching LINK_PATTERN inside these elements will not be linked # Text matching LINK_PATTERN inside these elements will not be linked
IGNORE_PARENTS = %w(a code kbd pre script style).to_set IGNORE_PARENTS = %w(a code kbd pre script style).to_set
# The XPath query to use for finding text nodes to parse.
TEXT_QUERY = %Q(descendant-or-self::text()[
not(#{IGNORE_PARENTS.map { |p| "ancestor::#{p}" }.join(' or ')})
and contains(., '://')
and not(starts-with(., 'http'))
and not(starts-with(., 'ftp'))
])
def call def call
return doc if context[:autolink] == false return doc if context[:autolink] == false
...@@ -66,16 +74,11 @@ module Banzai ...@@ -66,16 +74,11 @@ module Banzai
# Autolinks any text matching LINK_PATTERN that Rinku didn't already # Autolinks any text matching LINK_PATTERN that Rinku didn't already
# replace # replace
def text_parse def text_parse
search_text_nodes(doc).each do |node| doc.xpath(TEXT_QUERY).each do |node|
content = node.to_html content = node.to_html
next if has_ancestor?(node, IGNORE_PARENTS)
next unless content.match(LINK_PATTERN) next unless content.match(LINK_PATTERN)
# If Rinku didn't link this, there's probably a good reason, so we'll
# skip it too
next if content.start_with?(*%w(http https ftp))
html = autolink_filter(content) html = autolink_filter(content)
next if html == content next if html == content
......
...@@ -35,6 +35,7 @@ module Banzai ...@@ -35,6 +35,7 @@ module Banzai
def process_link_attr(html_attr) def process_link_attr(html_attr)
return if html_attr.blank? return if html_attr.blank?
return if html_attr.value.start_with?('//')
uri = URI(html_attr.value) uri = URI(html_attr.value)
if uri.relative? && uri.path.present? if uri.relative? && uri.path.present?
...@@ -92,7 +93,7 @@ module Banzai ...@@ -92,7 +93,7 @@ module Banzai
parts = request_path.split('/') parts = request_path.split('/')
parts.pop if uri_type(request_path) != :tree parts.pop if uri_type(request_path) != :tree
path.sub!(%r{^\./}, '') path.sub!(%r{\A\./}, '')
while path.start_with?('../') while path.start_with?('../')
parts.pop parts.pop
......
...@@ -17,15 +17,12 @@ module Banzai ...@@ -17,15 +17,12 @@ module Banzai
def highlight_node(node) def highlight_node(node)
language = node.attr('class') language = node.attr('class')
code = node.text code = node.text
css_classes = "code highlight" css_classes = "code highlight"
lexer = lexer_for(language)
lexer = Rouge::Lexer.find_fancy(language) || Rouge::Lexers::PlainText
formatter = Rouge::Formatters::HTML.new
begin begin
code = formatter.format(lexer.lex(code)) code = format(lex(lexer, code))
css_classes << " js-syntax-highlight #{lexer.tag}" css_classes << " js-syntax-highlight #{lexer.tag}"
rescue rescue
...@@ -41,14 +38,27 @@ module Banzai ...@@ -41,14 +38,27 @@ module Banzai
private private
# Separate method so it can be instrumented.
def lex(lexer, code)
lexer.lex(code)
end
def format(tokens)
rouge_formatter.format(tokens)
end
def lexer_for(language)
(Rouge::Lexer.find(language) || Rouge::Lexers::PlainText).new
end
def replace_parent_pre_element(node, highlighted) def replace_parent_pre_element(node, highlighted)
# Replace the parent `pre` element with the entire highlighted block # Replace the parent `pre` element with the entire highlighted block
node.parent.replace(highlighted) node.parent.replace(highlighted)
end end
# Override Rouge::Plugins::Redcarpet#rouge_formatter # Override Rouge::Plugins::Redcarpet#rouge_formatter
def rouge_formatter(lexer) def rouge_formatter(lexer = nil)
Rouge::Formatters::HTML.new @rouge_formatter ||= Rouge::Formatters::HTML.new
end end
end end
end end
......
...@@ -22,7 +22,9 @@ module Gitlab ...@@ -22,7 +22,9 @@ module Gitlab
@extractor.analyze(closing_statements.join(" ")) @extractor.analyze(closing_statements.join(" "))
@extractor.issues @extractor.issues.reject do |issue|
@extractor.project.forked_from?(issue.project) # Don't extract issues on original project
end
end end
end end
end end
...@@ -13,6 +13,10 @@ module Gitlab ...@@ -13,6 +13,10 @@ module Gitlab
File.join(Settings.shared['path'], 'tmp/project_exports') File.join(Settings.shared['path'], 'tmp/project_exports')
end end
def import_upload_path(filename:)
File.join(storage_path, 'uploads', filename)
end
def project_filename def project_filename
"project.json" "project.json"
end end
......
...@@ -25,7 +25,7 @@ module Gitlab ...@@ -25,7 +25,7 @@ module Gitlab
def verify_version!(version) def verify_version!(version)
if Gem::Version.new(version) > Gem::Version.new(Gitlab::ImportExport.version) if Gem::Version.new(version) > Gem::Version.new(Gitlab::ImportExport.version)
raise Gitlab::ImportExport::Error("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}") raise Gitlab::ImportExport::Error.new("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}")
else else
true true
end end
......
...@@ -4,13 +4,13 @@ namespace :gitlab do ...@@ -4,13 +4,13 @@ namespace :gitlab do
task all_users_to_all_projects: :environment do |t, args| task all_users_to_all_projects: :environment do |t, args|
user_ids = User.where(admin: false).pluck(:id) user_ids = User.where(admin: false).pluck(:id)
admin_ids = User.where(admin: true).pluck(:id) admin_ids = User.where(admin: true).pluck(:id)
projects_ids = Project.pluck(:id) project_ids = Project.pluck(:id)
puts "Importing #{user_ids.size} users into #{projects_ids.size} projects" puts "Importing #{user_ids.size} users into #{project_ids.size} projects"
ProjectMember.add_users_into_projects(projects_ids, user_ids, ProjectMember::DEVELOPER) ProjectMember.add_users_to_projects(project_ids, user_ids, ProjectMember::DEVELOPER)
puts "Importing #{admin_ids.size} admins into #{projects_ids.size} projects" puts "Importing #{admin_ids.size} admins into #{project_ids.size} projects"
ProjectMember.add_users_into_projects(projects_ids, admin_ids, ProjectMember::MASTER) ProjectMember.add_users_to_projects(project_ids, admin_ids, ProjectMember::MASTER)
end end
desc "GitLab | Add a specific user to all projects (as a developer)" desc "GitLab | Add a specific user to all projects (as a developer)"
...@@ -18,7 +18,7 @@ namespace :gitlab do ...@@ -18,7 +18,7 @@ namespace :gitlab do
user = User.find_by(email: args.email) user = User.find_by(email: args.email)
project_ids = Project.pluck(:id) project_ids = Project.pluck(:id)
puts "Importing #{user.email} users into #{project_ids.size} projects" puts "Importing #{user.email} users into #{project_ids.size} projects"
ProjectMember.add_users_into_projects(project_ids, Array.wrap(user.id), ProjectMember::DEVELOPER) ProjectMember.add_users_to_projects(project_ids, Array.wrap(user.id), ProjectMember::DEVELOPER)
end end
desc "GitLab | Add all users to all groups (admin users are added as owners)" desc "GitLab | Add all users to all groups (admin users are added as owners)"
......
...@@ -26,10 +26,10 @@ namespace :gitlab do ...@@ -26,10 +26,10 @@ namespace :gitlab do
namespace_path = ENV['NAMESPACE'] namespace_path = ENV['NAMESPACE']
projects = find_projects(namespace_path) projects = find_projects(namespace_path)
projects_ids = projects.pluck(:id) project_ids = projects.pluck(:id)
puts "Removing webhooks with the url '#{web_hook_url}' ... " puts "Removing webhooks with the url '#{web_hook_url}' ... "
count = WebHook.where(url: web_hook_url, project_id: projects_ids, type: 'ProjectHook').delete_all count = WebHook.where(url: web_hook_url, project_id: project_ids, type: 'ProjectHook').delete_all
puts "#{count} webhooks were removed." puts "#{count} webhooks were removed."
end end
......
...@@ -128,7 +128,7 @@ feature 'Login', feature: true do ...@@ -128,7 +128,7 @@ feature 'Login', feature: true do
end end
allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: saml_config) allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: saml_config)
allow(Gitlab.config.omniauth).to receive_messages(messages) allow(Gitlab.config.omniauth).to receive_messages(messages)
allow_any_instance_of(Object).to receive(:user_omniauth_authorize_path).with('saml').and_return('/users/auth/saml') expect_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml')
end end
it 'should show 2FA prompt after OAuth login' do it 'should show 2FA prompt after OAuth login' do
......
require 'rails_helper'
feature 'Ref switcher', feature: true, js: true do
include WaitForAjax
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
before do
project.team << [user, :master]
login_as(user)
visit namespace_project_tree_path(project.namespace, project, 'master')
end
it 'allow user to change ref by enter key' do
click_button 'master'
wait_for_ajax
page.within '.project-refs-form' do
input = find('input[type="search"]')
input.set 'expand'
input.native.send_keys :down
input.native.send_keys :down
input.native.send_keys :enter
expect(page).to have_content 'expand-collapse-files'
end
end
end
...@@ -218,12 +218,12 @@ describe ApplicationHelper do ...@@ -218,12 +218,12 @@ describe ApplicationHelper do
end end
it 'includes a default js-timeago class' do it 'includes a default js-timeago class' do
expect(element.attr('class')).to eq 'time_ago js-timeago js-timeago-pending' expect(element.attr('class')).to eq 'js-timeago js-timeago-pending'
end end
it 'accepts a custom html_class' do it 'accepts a custom html_class' do
expect(element(html_class: 'custom_class').attr('class')). expect(element(html_class: 'custom_class').attr('class')).
to eq 'custom_class js-timeago js-timeago-pending' to eq 'js-timeago custom_class js-timeago-pending'
end end
it 'accepts a custom tooltip placement' do it 'accepts a custom tooltip placement' do
...@@ -244,6 +244,19 @@ describe ApplicationHelper do ...@@ -244,6 +244,19 @@ describe ApplicationHelper do
it 'converts to Time' do it 'converts to Time' do
expect { helper.time_ago_with_tooltip(Date.today) }.not_to raise_error expect { helper.time_ago_with_tooltip(Date.today) }.not_to raise_error
end end
it 'add class for the short format and includes inline script' do
timeago_element = element(short_format: 'short')
expect(timeago_element.attr('class')).to eq 'js-short-timeago js-timeago-pending'
script_element = timeago_element.next_element
expect(script_element.name).to eq 'script'
end
it 'add class for the short format and does not include inline script' do
timeago_element = element(short_format: 'short', skip_js: true)
expect(timeago_element.attr('class')).to eq 'js-short-timeago'
expect(timeago_element.next_element).to eq nil
end
end end
describe 'render_markup' do describe 'render_markup' do
......
...@@ -15,22 +15,22 @@ describe DiffHelper do ...@@ -15,22 +15,22 @@ describe DiffHelper do
it 'returns a valid value when cookie is set' do it 'returns a valid value when cookie is set' do
helper.request.cookies[:diff_view] = 'parallel' helper.request.cookies[:diff_view] = 'parallel'
expect(helper.diff_view).to eq 'parallel' expect(helper.diff_view).to eq :parallel
end end
it 'returns a default value when cookie is invalid' do it 'returns a default value when cookie is invalid' do
helper.request.cookies[:diff_view] = 'invalid' helper.request.cookies[:diff_view] = 'invalid'
expect(helper.diff_view).to eq 'inline' expect(helper.diff_view).to eq :inline
end end
it 'returns a default value when cookie is nil' do it 'returns a default value when cookie is nil' do
expect(helper.request.cookies).to be_empty expect(helper.request.cookies).to be_empty
expect(helper.diff_view).to eq 'inline' expect(helper.diff_view).to eq :inline
end end
end end
describe 'diff_options' do describe 'diff_options' do
it 'should return no collapse false' do it 'should return no collapse false' do
expect(diff_options).to include(no_collapse: false) expect(diff_options).to include(no_collapse: false)
...@@ -59,26 +59,6 @@ describe DiffHelper do ...@@ -59,26 +59,6 @@ describe DiffHelper do
end end
end end
describe 'unfold_bottom_class' do
it 'should return empty string when bottom line shouldnt be unfolded' do
expect(unfold_bottom_class(false)).to eq('')
end
it 'should return js class when bottom lines should be unfolded' do
expect(unfold_bottom_class(true)).to include('js-unfold-bottom')
end
end
describe 'unfold_class' do
it 'returns empty on false' do
expect(unfold_class(false)).to eq('')
end
it 'returns a class on true' do
expect(unfold_class(true)).to eq('unfold js-unfold')
end
end
describe '#diff_line_content' do describe '#diff_line_content' do
it 'should return non breaking space when line is empty' do it 'should return non breaking space when line is empty' do
expect(diff_line_content(nil)).to eq(' &nbsp;') expect(diff_line_content(nil)).to eq(' &nbsp;')
...@@ -105,4 +85,56 @@ describe DiffHelper do ...@@ -105,4 +85,56 @@ describe DiffHelper do
expect(marked_new_line).to be_html_safe expect(marked_new_line).to be_html_safe
end end
end end
describe "#diff_match_line" do
let(:old_pos) { 40 }
let(:new_pos) { 50 }
let(:text) { 'some_text' }
it "should generate foldable top match line for inline view with empty text by default" do
output = diff_match_line old_pos, new_pos
expect(output).to be_html_safe
expect(output).to have_css "td:nth-child(1):not(.js-unfold-bottom).diff-line-num.unfold.js-unfold.old_line[data-linenumber='#{old_pos}']", text: '...'
expect(output).to have_css "td:nth-child(2):not(.js-unfold-bottom).diff-line-num.unfold.js-unfold.new_line[data-linenumber='#{new_pos}']", text: '...'
expect(output).to have_css 'td:nth-child(3):not(.parallel).line_content.match', text: ''
end
it "should allow to define text and bottom option" do
output = diff_match_line old_pos, new_pos, text: text, bottom: true
expect(output).to be_html_safe
expect(output).to have_css "td:nth-child(1).diff-line-num.unfold.js-unfold.js-unfold-bottom.old_line[data-linenumber='#{old_pos}']", text: '...'
expect(output).to have_css "td:nth-child(2).diff-line-num.unfold.js-unfold.js-unfold-bottom.new_line[data-linenumber='#{new_pos}']", text: '...'
expect(output).to have_css 'td:nth-child(3):not(.parallel).line_content.match', text: text
end
it "should generate match line for parallel view" do
output = diff_match_line old_pos, new_pos, text: text, view: :parallel
expect(output).to be_html_safe
expect(output).to have_css "td:nth-child(1):not(.js-unfold-bottom).diff-line-num.unfold.js-unfold.old_line[data-linenumber='#{old_pos}']", text: '...'
expect(output).to have_css 'td:nth-child(2).line_content.match.parallel', text: text
expect(output).to have_css "td:nth-child(3):not(.js-unfold-bottom).diff-line-num.unfold.js-unfold.new_line[data-linenumber='#{new_pos}']", text: '...'
expect(output).to have_css 'td:nth-child(4).line_content.match.parallel', text: text
end
it "should allow to generate only left match line for parallel view" do
output = diff_match_line old_pos, nil, text: text, view: :parallel
expect(output).to be_html_safe
expect(output).to have_css "td:nth-child(1):not(.js-unfold-bottom).diff-line-num.unfold.js-unfold.old_line[data-linenumber='#{old_pos}']", text: '...'
expect(output).to have_css 'td:nth-child(2).line_content.match.parallel', text: text
expect(output).not_to have_css 'td:nth-child(3)'
end
it "should allow to generate only right match line for parallel view" do
output = diff_match_line nil, new_pos, text: text, view: :parallel
expect(output).to be_html_safe
expect(output).to have_css "td:nth-child(1):not(.js-unfold-bottom).diff-line-num.unfold.js-unfold.new_line[data-linenumber='#{new_pos}']", text: '...'
expect(output).to have_css 'td:nth-child(2).line_content.match.parallel', text: text
expect(output).not_to have_css 'td:nth-child(3)'
end
end
end end
...@@ -84,6 +84,11 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do ...@@ -84,6 +84,11 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
to eq "/#{project_path}/blob/#{ref}/doc/api/README.md" to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
end end
it 'ignores absolute URLs with two leading slashes' do
doc = filter(link('//doc/api/README.md'))
expect(doc.at_css('a')['href']).to eq '//doc/api/README.md'
end
it 'rebuilds relative URL for a file in the repo' do it 'rebuilds relative URL for a file in the repo' do
doc = filter(link('doc/api/README.md')) doc = filter(link('doc/api/README.md'))
expect(doc.at_css('a')['href']). expect(doc.at_css('a')['href']).
......
...@@ -3,10 +3,12 @@ require 'spec_helper' ...@@ -3,10 +3,12 @@ require 'spec_helper'
describe Gitlab::ClosingIssueExtractor, lib: true do describe Gitlab::ClosingIssueExtractor, lib: true do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:project2) { create(:project) } let(:project2) { create(:project) }
let(:forked_project) { Projects::ForkService.new(project, project.creator).execute }
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
let(:issue2) { create(:issue, project: project2) } let(:issue2) { create(:issue, project: project2) }
let(:reference) { issue.to_reference } let(:reference) { issue.to_reference }
let(:cross_reference) { issue2.to_reference(project) } let(:cross_reference) { issue2.to_reference(project) }
let(:fork_cross_reference) { issue.to_reference(forked_project) }
subject { described_class.new(project, project.creator) } subject { described_class.new(project, project.creator) }
...@@ -278,6 +280,15 @@ describe Gitlab::ClosingIssueExtractor, lib: true do ...@@ -278,6 +280,15 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
end end
end end
context "with a cross-project fork reference" do
subject { described_class.new(forked_project, forked_project.creator) }
it do
message = "Closes #{fork_cross_reference}"
expect(subject.closed_by_message(message)).to be_empty
end
end
context "with an invalid URL" do context "with an invalid URL" do
it do it do
message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)}" message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)}"
......
require 'spec_helper'
describe Gitlab::ImportExport::VersionChecker, services: true do
describe 'bundle a project Git repo' do
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') }
let(:version) { Gitlab::ImportExport.version }
before do
allow(File).to receive(:open).and_return(version)
end
it 'returns true if Import/Export have the same version' do
expect(described_class.check!(shared: shared)).to be true
end
context 'newer version' do
let(:version) { '900.0'}
it 'returns false if export version is newer' do
expect(described_class.check!(shared: shared)).to be false
end
it 'shows the correct error message' do
described_class.check!(shared: shared)
expect(shared.errors.first).to eq("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}")
end
end
end
end
...@@ -101,7 +101,7 @@ describe ProjectMember, models: true do ...@@ -101,7 +101,7 @@ describe ProjectMember, models: true do
end end
end end
describe '.add_users_into_projects' do describe '.add_users_to_projects' do
before do before do
@project_1 = create :project @project_1 = create :project
@project_2 = create :project @project_2 = create :project
...@@ -109,7 +109,7 @@ describe ProjectMember, models: true do ...@@ -109,7 +109,7 @@ describe ProjectMember, models: true do
@user_1 = create :user @user_1 = create :user
@user_2 = create :user @user_2 = create :user
ProjectMember.add_users_into_projects( ProjectMember.add_users_to_projects(
[@project_1.id, @project_2.id], [@project_1.id, @project_2.id],
[@user_1.id, @user_2.id], [@user_1.id, @user_2.id],
ProjectMember::MASTER ProjectMember::MASTER
......
require 'spec_helper'
describe ImportExportCleanUpService, services: true do
describe '#execute' do
let(:service) { described_class.new }
let(:tmp_import_export_folder) { 'tmp/project_exports' }
context 'when the import/export directory does not exist' do
it 'does not remove any archives' do
path = '/invalid/path/'
stub_repository_downloads_path(path)
expect(File).to receive(:directory?).with(path + tmp_import_export_folder).and_return(false).at_least(:once)
expect(service).not_to receive(:clean_up_export_files)
service.execute
end
end
context 'when the import/export directory exists' do
it 'removes old files' do
in_directory_with_files(mtime: 2.days.ago) do |dir, files|
service.execute
files.each { |file| expect(File.exist?(file)).to eq false }
expect(File.directory?(dir)).to eq false
end
end
it 'does not remove new files' do
in_directory_with_files(mtime: 2.hours.ago) do |dir, files|
service.execute
files.each { |file| expect(File.exist?(file)).to eq true }
expect(File.directory?(dir)).to eq true
end
end
end
def in_directory_with_files(mtime:)
Dir.mktmpdir do |tmpdir|
stub_repository_downloads_path(tmpdir)
dir = File.join(tmpdir, tmp_import_export_folder, 'subfolder')
FileUtils.mkdir_p(dir)
files = FileUtils.touch(file_list(dir) + [dir], mtime: mtime.to_time)
yield(dir, files)
end
end
def stub_repository_downloads_path(path)
new_shared_settings = Settings.shared.merge('path' => path)
allow(Settings).to receive(:shared).and_return(new_shared_settings)
end
def file_list(dir)
Array.new(5) do |num|
File.join(dir, "random-#{num}.tar.gz")
end
end
end
end
...@@ -31,7 +31,7 @@ describe 'devise/shared/_signin_box' do ...@@ -31,7 +31,7 @@ describe 'devise/shared/_signin_box' do
def enable_crowd def enable_crowd
allow(view).to receive(:form_based_providers).and_return([:crowd]) allow(view).to receive(:form_based_providers).and_return([:crowd])
allow(view).to receive(:crowd_enabled?).and_return(true) allow(view).to receive(:crowd_enabled?).and_return(true)
allow(view).to receive(:user_omniauth_authorize_path).with('crowd'). allow(view).to receive(:omniauth_authorize_path).with(:user, :crowd).
and_return('/crowd') and_return('/crowd')
end end
end end
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