Commit 2836b473 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce

parents 71dec8b1 b767d868
...@@ -8,6 +8,7 @@ v 8.11.0 (unreleased) ...@@ -8,6 +8,7 @@ v 8.11.0 (unreleased)
- 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
...@@ -19,6 +20,7 @@ v 8.11.0 (unreleased) ...@@ -19,6 +20,7 @@ v 8.11.0 (unreleased)
- 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 - 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) - 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)
...@@ -38,6 +40,7 @@ v 8.11.0 (unreleased) ...@@ -38,6 +40,7 @@ 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)
- Document that webhook secret token is sent in X-Gitlab-Token HTTP header !5664 (lycoperdon)
- Gitlab::Highlight is now instrumented - 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
...@@ -64,11 +67,14 @@ v 8.11.0 (unreleased) ...@@ -64,11 +67,14 @@ 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
- Add unfold links for Side-by-Side view. !5415 (Tim Masliuchenko)
v 8.10.5 (unreleased) v 8.10.5 (unreleased)
...@@ -165,6 +171,9 @@ v 8.10.0 ...@@ -165,6 +171,9 @@ v 8.10.0
- Fix check for New Branch button on Issue page. !4630 (winniehell) - Fix check for New Branch button on Issue page. !4630 (winniehell)
- Fix GFM autocomplete not working on wiki pages - Fix GFM autocomplete not working on wiki pages
- Fixed enter key not triggering click on first row when searching in a dropdown - Fixed enter key not triggering click on first row when searching in a dropdown
- Updated dropdowns in issuable form to use new GitLab dropdown style
- Make images fit to the size of the viewport !4810
- Fix check for New Branch button on Issue page !4630 (winniehell)
- Fix MR-auto-close text added to description. !4836 - Fix MR-auto-close text added to description. !4836
- Support U2F devices in Firefox. !5177 - Support U2F devices in Firefox. !5177
- Fix issue, preventing users w/o push access to sort tags. !5105 (redetection) - Fix issue, preventing users w/o push access to sort tags. !5105 (redetection)
......
...@@ -336,6 +336,10 @@ request is as follows: ...@@ -336,6 +336,10 @@ request is as follows:
1. If your code creates new files on disk please read the 1. If your code creates new files on disk please read the
[shared files guidelines](doc/development/shared_files.md). [shared files guidelines](doc/development/shared_files.md).
1. When writing commit messages please follow [these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) [guidelines](http://chris.beams.io/posts/git-commit/). 1. When writing commit messages please follow [these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) [guidelines](http://chris.beams.io/posts/git-commit/).
1. If your merge request adds one or more migrations, make sure to execute all
migrations on a fresh database before the MR is reviewed. If the review leads
to large changes in the MR, do this again once the review is complete.
1. For more complex migrations, write tests.
The **official merge window** is in the beginning of the month from the 1st to The **official merge window** is in the beginning of the month from the 1st to
the 7th day of the month. This is the best time to submit an MR and get the 7th day of the month. This is the best time to submit an MR and get
......
...@@ -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;
......
...@@ -173,8 +173,8 @@ ...@@ -173,8 +173,8 @@
new Search(); new Search();
break; break;
case 'projects:protected_branches:index': case 'projects:protected_branches:index':
new ProtectedBranchesAccessSelect($(".new_protected_branch"), false, true); new gl.ProtectedBranchCreate();
new ProtectedBranchesAccessSelect($(".protected-branches-list"), true, false); new gl.ProtectedBranchEditList();
break; break;
} }
switch (path.first()) { switch (path.first()) {
......
...@@ -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);
...@@ -601,7 +607,7 @@ ...@@ -601,7 +607,7 @@
return this.dropdown.before($input); return this.dropdown.before($input);
}; };
GitLabDropdown.prototype.selectRowAtIndex = function(e, index) { GitLabDropdown.prototype.selectRowAtIndex = function(index) {
var $el, selector; var $el, selector;
selector = ".dropdown-content li:not(.divider,.dropdown-header,.separator):eq(" + index + ") a"; selector = ".dropdown-content li:not(.divider,.dropdown-header,.separator):eq(" + index + ") a";
if (this.dropdown.find(".dropdown-toggle-page").length) { if (this.dropdown.find(".dropdown-toggle-page").length) {
...@@ -609,8 +615,6 @@ ...@@ -609,8 +615,6 @@
} }
$el = $(selector, this.dropdown); $el = $(selector, this.dropdown);
if ($el.length) { if ($el.length) {
e.preventDefault();
e.stopImmediatePropagation();
return $el.first().trigger('click'); return $el.first().trigger('click');
} }
}; };
...@@ -619,7 +623,7 @@ ...@@ -619,7 +623,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 +651,7 @@ ...@@ -647,7 +651,7 @@
return false; return false;
} }
if (currentKeyCode === 13 && currentIndex !== -1) { if (currentKeyCode === 13 && currentIndex !== -1) {
return _this.selectRowAtIndex(e, currentIndex); return _this.selectRowAtIndex($('.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());
}
} }
}); });
}); });
......
(global => {
global.gl = global.gl || {};
gl.ProtectedBranchAccessDropdown = class {
constructor(options) {
const { $dropdown, data, onSelect } = options;
$dropdown.glDropdown({
data: data,
selectable: true,
inputId: $dropdown.data('input-id'),
fieldName: $dropdown.data('field-name'),
toggleLabel(item) {
return item.text;
},
clicked(item, $el, e) {
e.preventDefault();
onSelect();
}
});
}
}
})(window);
(global => {
global.gl = global.gl || {};
gl.ProtectedBranchCreate = class {
constructor() {
this.$wrap = this.$form = $('#new_protected_branch');
this.buildDropdowns();
}
buildDropdowns() {
const $allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
const $allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
// Cache callback
this.onSelectCallback = this.onSelect.bind(this);
// Allowed to Merge dropdown
new gl.ProtectedBranchAccessDropdown({
$dropdown: $allowedToMergeDropdown,
data: gon.merge_access_levels,
onSelect: this.onSelectCallback
});
// Allowed to Push dropdown
new gl.ProtectedBranchAccessDropdown({
$dropdown: $allowedToPushDropdown,
data: gon.push_access_levels,
onSelect: this.onSelectCallback
});
// Select default
$allowedToPushDropdown.data('glDropdown').selectRowAtIndex(0);
$allowedToMergeDropdown.data('glDropdown').selectRowAtIndex(0);
// Protected branch dropdown
new ProtectedBranchDropdown({
$dropdown: this.$wrap.find('.js-protected-branch-select'),
onSelect: this.onSelectCallback
});
}
// This will run after clicked callback
onSelect() {
// Enable submit button
const $branchInput = this.$wrap.find('input[name="protected_branch[name]"]');
const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_level_attributes][access_level]"]');
const $allowedToPushInput = this.$wrap.find('input[name="protected_branch[push_access_level_attributes][access_level]"]');
if ($branchInput.val() && $allowedToMergeInput.val() && $allowedToPushInput.val()){
this.$form.find('input[type="submit"]').removeAttr('disabled');
}
}
}
})(window);
class ProtectedBranchDropdown {
constructor(options) {
this.onSelect = options.onSelect;
this.$dropdown = options.$dropdown;
this.$dropdownContainer = this.$dropdown.parent();
this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer');
this.$protectedBranch = this.$dropdownContainer.find('.create-new-protected-branch');
this.buildDropdown();
this.bindEvents();
// Hide footer
this.$dropdownFooter.addClass('hidden');
}
buildDropdown() {
this.$dropdown.glDropdown({
data: this.getProtectedBranches.bind(this),
filterable: true,
remote: false,
search: {
fields: ['title']
},
selectable: true,
toggleLabel(selected) {
return (selected && 'id' in selected) ? selected.title : 'Protected Branch';
},
fieldName: 'protected_branch[name]',
text(protectedBranch) {
return _.escape(protectedBranch.title);
},
id(protectedBranch) {
return _.escape(protectedBranch.id);
},
onFilter: this.toggleCreateNewButton.bind(this),
clicked: (item, $el, e) => {
e.preventDefault();
this.onSelect();
}
});
}
bindEvents() {
this.$protectedBranch.on('click', this.onClickCreateWildcard.bind(this));
}
onClickCreateWildcard() {
this.$dropdown.data('glDropdown').remote.execute();
this.$dropdown.data('glDropdown').selectRowAtIndex(0);
}
getProtectedBranches(term, callback) {
if (this.selectedBranch) {
callback(gon.open_branches.concat(this.selectedBranch));
} else {
callback(gon.open_branches);
}
}
toggleCreateNewButton(branchName) {
this.selectedBranch = {
title: branchName,
id: branchName,
text: branchName
};
if (branchName) {
this.$dropdownContainer
.find('.create-new-protected-branch code')
.text(branchName);
}
this.$dropdownFooter.toggleClass('hidden', !branchName);
}
}
(global => {
global.gl = global.gl || {};
gl.ProtectedBranchEdit = class {
constructor(options) {
this.$wrap = options.$wrap;
this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
this.$allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
this.buildDropdowns();
}
buildDropdowns() {
// Allowed to merge dropdown
new gl.ProtectedBranchAccessDropdown({
$dropdown: this.$allowedToMergeDropdown,
data: gon.merge_access_levels,
onSelect: this.onSelect.bind(this)
});
// Allowed to push dropdown
new gl.ProtectedBranchAccessDropdown({
$dropdown: this.$allowedToPushDropdown,
data: gon.push_access_levels,
onSelect: this.onSelect.bind(this)
});
}
onSelect() {
const $allowedToMergeInput = this.$wrap.find(`input[name="${this.$allowedToMergeDropdown.data('fieldName')}"]`);
const $allowedToPushInput = this.$wrap.find(`input[name="${this.$allowedToPushDropdown.data('fieldName')}"]`);
$.ajax({
type: 'POST',
url: this.$wrap.data('url'),
dataType: 'json',
data: {
_method: 'PATCH',
id: this.$wrap.data('banchId'),
protected_branch: {
merge_access_level_attributes: {
access_level: $allowedToMergeInput.val()
},
push_access_level_attributes: {
access_level: $allowedToPushInput.val()
}
}
},
success: () => {
this.$wrap.effect('highlight');
},
error() {
$.scrollTo(0);
new Flash('Failed to update branch!');
}
});
}
}
})(window);
(global => {
global.gl = global.gl || {};
gl.ProtectedBranchEditList = class {
constructor() {
this.$wrap = $('.protected-branches-list');
// Build edit forms
this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => {
new gl.ProtectedBranchEdit({
$wrap: $(el)
});
});
}
}
})(window);
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
this.ProtectedBranchSelect = (function() {
function ProtectedBranchSelect(currentProject) {
this.toggleCreateNewButton = bind(this.toggleCreateNewButton, this);
this.getProtectedBranches = bind(this.getProtectedBranches, this);
$('.dropdown-footer').hide();
this.dropdown = $('.js-protected-branch-select').glDropdown({
data: this.getProtectedBranches,
filterable: true,
remote: false,
search: {
fields: ['title']
},
selectable: true,
toggleLabel: function(selected) {
if (selected && 'id' in selected) {
return selected.title;
} else {
return 'Protected Branch';
}
},
fieldName: 'protected_branch[name]',
text: function(protected_branch) {
return _.escape(protected_branch.title);
},
id: function(protected_branch) {
return _.escape(protected_branch.id);
},
onFilter: this.toggleCreateNewButton,
clicked: function() {
return $('.protect-branch-btn').attr('disabled', false);
}
});
$('.create-new-protected-branch').on('click', (function(_this) {
return function(event) {
_this.dropdown.data('glDropdown').remote.execute();
return _this.dropdown.data('glDropdown').selectRowAtIndex(event, 0);
};
})(this));
}
ProtectedBranchSelect.prototype.getProtectedBranches = function(term, callback) {
if (this.selectedBranch) {
return callback(gon.open_branches.concat(this.selectedBranch));
} else {
return callback(gon.open_branches);
}
};
ProtectedBranchSelect.prototype.toggleCreateNewButton = function(branchName) {
this.selectedBranch = {
title: branchName,
id: branchName,
text: branchName
};
if (branchName === '') {
$('.protected-branch-select-footer-list').addClass('hidden');
return $('.dropdown-footer').hide();
} else {
$('.create-new-protected-branch').text("Create Protected Branch: " + branchName);
$('.protected-branch-select-footer-list').removeClass('hidden');
return $('.dropdown-footer').show();
}
};
return ProtectedBranchSelect;
})();
}).call(this);
class ProtectedBranchesAccessSelect {
constructor(container, saveOnSelect, selectDefault) {
this.container = container;
this.saveOnSelect = saveOnSelect;
this.container.find(".allowed-to-merge").each((i, element) => {
var fieldName = $(element).data('field-name');
var dropdown = $(element).glDropdown({
data: gon.merge_access_levels,
selectable: true,
fieldName: fieldName,
clicked: _.chain(this.onSelect).partial(element).bind(this).value()
});
if (selectDefault) {
dropdown.data('glDropdown').selectRowAtIndex(document.createEvent("Event"), 0);
}
});
this.container.find(".allowed-to-push").each((i, element) => {
var fieldName = $(element).data('field-name');
var dropdown = $(element).glDropdown({
data: gon.push_access_levels,
selectable: true,
fieldName: fieldName,
clicked: _.chain(this.onSelect).partial(element).bind(this).value()
});
if (selectDefault) {
dropdown.data('glDropdown').selectRowAtIndex(document.createEvent("Event"), 0);
}
});
}
onSelect(dropdown, selected, element, e) {
$(dropdown).find('.dropdown-toggle-text').text(selected.text);
if (this.saveOnSelect) {
return $.ajax({
type: "POST",
url: $(dropdown).data('url'),
dataType: "json",
data: {
_method: 'PATCH',
id: $(dropdown).data('id'),
protected_branch: {
["" + ($(dropdown).data('type')) + "_attributes"]: {
"access_level": selected.id
}
}
},
success: function() {
var row;
row = $(e.target);
return row.closest('tr').effect('highlight');
},
error: function() {
return new Flash("Failed to update branch!", "alert");
}
});
}
}
}
...@@ -13,14 +13,15 @@ ...@@ -13,14 +13,15 @@
} }
$('.js-user-search').each((function(_this) { $('.js-user-search').each((function(_this) {
return function(i, dropdown) { return function(i, dropdown) {
var options = {};
var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser; var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser;
$dropdown = $(dropdown); $dropdown = $(dropdown);
_this.projectId = $dropdown.data('project-id'); options.projectId = $dropdown.data('project-id');
_this.showCurrentUser = $dropdown.data('current-user'); options.showCurrentUser = $dropdown.data('current-user');
showNullUser = $dropdown.data('null-user'); showNullUser = $dropdown.data('null-user');
showAnyUser = $dropdown.data('any-user'); showAnyUser = $dropdown.data('any-user');
firstUser = $dropdown.data('first-user'); firstUser = $dropdown.data('first-user');
_this.authorId = $dropdown.data('author-id'); options.authorId = $dropdown.data('author-id');
selectedId = $dropdown.data('selected'); selectedId = $dropdown.data('selected');
defaultLabel = $dropdown.data('default-label'); defaultLabel = $dropdown.data('default-label');
issueURL = $dropdown.data('issueUpdate'); issueURL = $dropdown.data('issueUpdate');
...@@ -75,7 +76,7 @@ ...@@ -75,7 +76,7 @@
data: function(term, callback) { data: function(term, callback) {
var isAuthorFilter; var isAuthorFilter;
isAuthorFilter = $('.js-author-search'); isAuthorFilter = $('.js-author-search');
return _this.users(term, function(users) { return _this.users(term, options, function(users) {
var anyUser, index, j, len, name, obj, showDivider; var anyUser, index, j, len, name, obj, showDivider;
if (term.length === 0) { if (term.length === 0) {
showDivider = 0; showDivider = 0;
...@@ -185,11 +186,14 @@ ...@@ -185,11 +186,14 @@
$('.ajax-users-select').each((function(_this) { $('.ajax-users-select').each((function(_this) {
return function(i, select) { return function(i, select) {
var firstUser, showAnyUser, showEmailUser, showNullUser; var firstUser, showAnyUser, showEmailUser, showNullUser;
_this.projectId = $(select).data('project-id'); var options = {};
_this.groupId = $(select).data('group-id'); options.skipLdap = $(select).hasClass('skip_ldap');
_this.showCurrentUser = $(select).data('current-user'); options.projectId = $(select).data('project-id');
_this.authorId = $(select).data('author-id'); options.groupId = $(select).data('group-id');
_this.skipUsers = $(select).data('skip-users'); options.showCurrentUser = $(select).data('current-user');
options.pushCodeToProtectedBranches = $(select).data('push-code-to-protected-branches');
options.authorId = $(select).data('author-id');
options.skipUsers = $(select).data('skip-users');
showNullUser = $(select).data('null-user'); showNullUser = $(select).data('null-user');
showAnyUser = $(select).data('any-user'); showAnyUser = $(select).data('any-user');
showEmailUser = $(select).data('email-user'); showEmailUser = $(select).data('email-user');
...@@ -199,7 +203,7 @@ ...@@ -199,7 +203,7 @@
multiple: $(select).hasClass('multiselect'), multiple: $(select).hasClass('multiselect'),
minimumInputLength: 0, minimumInputLength: 0,
query: function(query) { query: function(query) {
return _this.users(query.term, function(users) { return _this.users(query.term, options, function(users) {
var anyUser, data, emailUser, index, j, len, name, nullUser, obj, ref; var anyUser, data, emailUser, index, j, len, name, nullUser, obj, ref;
data = { data = {
results: users results: users
...@@ -309,7 +313,7 @@ ...@@ -309,7 +313,7 @@
}); });
}; };
UsersSelect.prototype.users = function(query, callback) { UsersSelect.prototype.users = function(query, options, callback) {
var url; var url;
url = this.buildUrl(this.usersPath); url = this.buildUrl(this.usersPath);
return $.ajax({ return $.ajax({
...@@ -318,11 +322,13 @@ ...@@ -318,11 +322,13 @@
search: query, search: query,
per_page: 20, per_page: 20,
active: true, active: true,
project_id: this.projectId, project_id: options.projectId || null,
group_id: this.groupId, group_id: options.groupId || null,
current_user: this.showCurrentUser, skip_ldap: options.skipLdap || null,
author_id: this.authorId, current_user: options.showCurrentUser || null,
skip_users: this.skipUsers push_code_to_protected_branches: options.pushCodeToProtectedBranches || null,
author_id: options.authorId || null,
skip_users: options.skipUsers || null
}, },
dataType: "json" dataType: "json"
}).done(function(users) { }).done(function(users) {
......
...@@ -72,6 +72,14 @@ ...@@ -72,6 +72,14 @@
&.large { &.large {
width: 200px; width: 200px;
} }
&.wide {
width: 100%;
+ .dropdown-select {
width: 100%;
}
}
} }
.dropdown-menu, .dropdown-menu,
......
...@@ -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 {
......
...@@ -23,4 +23,9 @@ ...@@ -23,4 +23,9 @@
margin-top: $gl-padding; margin-top: $gl-padding;
} }
} }
.panel-title {
font-size: inherit;
line-height: inherit;
}
} }
...@@ -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;
...@@ -662,13 +656,9 @@ pre.light-well { ...@@ -662,13 +656,9 @@ pre.light-well {
} }
.new_protected_branch { .new_protected_branch {
.dropdown {
display: inline;
margin-left: 15px;
}
label { label {
min-width: 120px; margin-top: 6px;
font-weight: normal;
} }
} }
...@@ -684,6 +674,21 @@ pre.light-well { ...@@ -684,6 +674,21 @@ pre.light-well {
font-weight: 600; font-weight: 600;
} }
} }
.settings-message {
margin: 0;
border-radius: 0 0 1px 1px;
padding: 20px 0;
border: none;
}
.table-bordered {
border-radius: 1px;
th:not(:last-child), td:not(:last-child) {
border-right: solid 1px transparent;
}
}
} }
.custom-notifications-form { .custom-notifications-form {
......
...@@ -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]
......
...@@ -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)
......
...@@ -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
...@@ -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 %>
- 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
......
...@@ -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)}
......
%h5.prepend-top-0 .panel.panel-default.protected-branches-list
Already Protected (#{@protected_branches.size}) - if @protected_branches.empty?
- if @protected_branches.empty? .panel-heading
%p.settings-message.text-center %h3.panel-title
No branches are protected, protect a branch with the form above. Protected branch (#{@protected_branches.size})
- else %p.settings-message.text-center
- can_admin_project = can?(current_user, :admin_project, @project) There are currently no protected branches, protect a branch with the form above.
- else
- can_admin_project = can?(current_user, :admin_project, @project)
%table.table.protected-branches-list %table.table.table-bordered
%colgroup %colgroup
%col{ width: "20%" } %col{ width: "25%" }
%col{ width: "30%" } %col{ width: "30%" }
%col{ width: "25%" } %col{ width: "25%" }
%col{ width: "25%" } %col{ width: "20%" }
%thead %thead
%tr %tr
%th Branch %th Protected branch (#{@protected_branches.size})
%th Last commit %th Last commit
%th Allowed to merge %th Allowed to merge
%th Allowed to push %th Allowed to push
- if can_admin_project - if can_admin_project
%th %th
%tbody %tbody
= render partial: @protected_branches, locals: { can_admin_project: can_admin_project } = render partial: @protected_branches, locals: { can_admin_project: can_admin_project }
= paginate @protected_branches, theme: 'gitlab' = paginate @protected_branches, theme: 'gitlab'
= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f|
.panel.panel-default
.panel-heading
%h3.panel-title
Protect a branch
.panel-body
.form-horizontal
.form-group
= f.label :name, class: 'col-md-2 text-right' do
Branch:
.col-md-10
= render partial: "dropdown", locals: { f: f }
.help-block
= link_to 'Wildcards', help_page_path('user/project/protected_branches', anchor: 'wildcard-protected-branches')
such as
%code *-stable
or
%code production/*
are supported
.form-group
%label.col-md-2.text-right{ for: 'merge_access_level_attributes' }
Allowed to merge:
.col-md-10
= dropdown_tag('Select',
options: { toggle_class: 'js-allowed-to-merge wide',
data: { field_name: 'protected_branch[merge_access_level_attributes][access_level]', input_id: 'merge_access_level_attributes' }})
.form-group
%label.col-md-2.text-right{ for: 'push_access_level_attributes' }
Allowed to push:
.col-md-10
= dropdown_tag('Select',
options: { toggle_class: 'js-allowed-to-push wide',
data: { field_name: 'protected_branch[push_access_level_attributes][access_level]', input_id: 'push_access_level_attributes' }})
.panel-footer
= f.submit 'Protect', class: 'btn-create btn', disabled: true
= f.hidden_field(:name) = f.hidden_field(:name)
= dropdown_tag("Protected Branch", = dropdown_tag('Select branch or create wildcard',
options: { title: "Pick protected branch", toggle_class: 'js-protected-branch-select js-filter-submit', options: { toggle_class: 'js-protected-branch-select js-filter-submit wide',
filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search protected branches", filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search protected branches",
footer_content: true, footer_content: true,
data: { show_no: true, show_any: true, show_upcoming: true, data: { show_no: true, show_any: true, show_upcoming: true,
selected: params[:protected_branch_name], selected: params[:protected_branch_name],
project_id: @project.try(:id) } }) do project_id: @project.try(:id) } }) do
%ul.dropdown-footer-list.hidden.protected-branch-select-footer-list %ul.dropdown-footer-list
%li %li
= link_to '#', title: "New Protected Branch", class: "create-new-protected-branch" do = link_to '#', title: "New Protected Branch", class: "create-new-protected-branch" do
Create new Create wildcard
%code
:javascript
new ProtectedBranchSelect();
- url = namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) %tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch), branch_id: protected_branch.id } }
%tr
%td %td
= protected_branch.name = protected_branch.name
- if @project.root_ref?(protected_branch.name) - if @project.root_ref?(protected_branch.name)
...@@ -16,14 +15,14 @@ ...@@ -16,14 +15,14 @@
(branch was removed from repository) (branch was removed from repository)
%td %td
= hidden_field_tag "allowed_to_merge_#{protected_branch.id}", protected_branch.merge_access_level.access_level = hidden_field_tag "allowed_to_merge_#{protected_branch.id}", protected_branch.merge_access_level.access_level
= dropdown_tag(protected_branch.merge_access_level.humanize, = dropdown_tag( (protected_branch.merge_access_level.humanize || 'Select') ,
options: { title: "Allowed to merge", toggle_class: 'allowed-to-merge', dropdown_class: 'dropdown-menu-selectable merge', options: { toggle_class: 'js-allowed-to-merge', dropdown_class: 'dropdown-menu-selectable js-allowed-to-merge-container',
data: { field_name: "allowed_to_merge_#{protected_branch.id}", url: url, id: protected_branch.id, type: "merge_access_level" }}) data: { field_name: "allowed_to_merge_#{protected_branch.id}" }})
%td %td
= hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_level.access_level = hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_level.access_level
= dropdown_tag(protected_branch.push_access_level.humanize, = dropdown_tag( (protected_branch.push_access_level.humanize || 'Select') ,
options: { title: "Allowed to push", toggle_class: 'allowed-to-push', dropdown_class: 'dropdown-menu-selectable push', options: { toggle_class: 'js-allowed-to-push', dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container',
data: { field_name: "allowed_to_push_#{protected_branch.id}", url: url, id: protected_branch.id, type: "push_access_level" }}) data: { field_name: "allowed_to_push_#{protected_branch.id}" }})
- if can_admin_project - if can_admin_project
%td %td
= link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm pull-right" = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning'
...@@ -14,41 +14,7 @@ ...@@ -14,41 +14,7 @@
%li prevent <strong>anyone</strong> from deleting the branch %li prevent <strong>anyone</strong> from deleting the branch
%p.append-bottom-0 Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches"), class: "underlined-link"} and #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}. %p.append-bottom-0 Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches"), class: "underlined-link"} and #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}.
.col-lg-9 .col-lg-9
%h5.prepend-top-0
Protect a branch
- if can? current_user, :admin_project, @project - if can? current_user, :admin_project, @project
= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f| = render 'create_protected_branch'
= form_errors(@protected_branch)
.form-group
= f.label :name, "Branch", class: "label-light"
= render partial: "dropdown", locals: { f: f }
%p.help-block
= link_to "Wildcards", help_page_path('user/project/protected_branches', anchor: "wildcard-protected-branches")
such as
%code *-stable
or
%code production/*
are supported.
.form-group
= hidden_field_tag 'protected_branch[merge_access_level_attributes][access_level]'
= label_tag "Allowed to merge: ", nil, class: "label-light append-bottom-0"
= dropdown_tag("<Make a selection>",
options: { title: "Allowed to merge", toggle_class: 'allowed-to-merge',
dropdown_class: 'dropdown-menu-selectable',
data: { field_name: "protected_branch[merge_access_level_attributes][access_level]" }})
.form-group
= hidden_field_tag 'protected_branch[push_access_level_attributes][access_level]'
= label_tag "Allowed to push: ", nil, class: "label-light append-bottom-0"
= dropdown_tag("<Make a selection>",
options: { title: "Allowed to push", toggle_class: 'allowed-to-push',
dropdown_class: 'dropdown-menu-selectable',
data: { field_name: "protected_branch[push_access_level_attributes][access_level]" }})
= f.submit "Protect", class: "btn-create btn protect-branch-btn", disabled: true
%hr
= render "branches_list" = render "branches_list"
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
= f.label :token, "Secret Token", class: 'label-light' = f.label :token, "Secret Token", class: 'label-light'
= f.text_field :token, class: "form-control", placeholder: '' = f.text_field :token, class: "form-control", placeholder: ''
%p.help-block %p.help-block
Use this token to validate received payloads Use this token to validate received payloads. It will be sent with the request in the X-Gitlab-Token HTTP header.
.form-group .form-group
= f.label :url, "Trigger", class: 'label-light' = f.label :url, "Trigger", class: 'label-light'
%ul.list-unstyled %ul.list-unstyled
......
...@@ -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
# 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: 20160802010328) 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"
......
# 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"
# GitLab Container Registry Administration # GitLab Container Registry Administration
> **Note:** > [Introduced][ce-4040] in GitLab 8.8.
This feature was [introduced][ce-4040] in GitLab 8.8.
With the Docker Container Registry integrated into GitLab, every project can With the Docker Container Registry integrated into GitLab, every project can
have its own space to store its Docker images. have its own space to store its Docker images.
......
...@@ -44,8 +44,7 @@ as appropriate. ...@@ -44,8 +44,7 @@ as appropriate.
## Custom error messages ## Custom error messages
>**Note:** > [Introduced][5073] in GitLab 8.10.
This feature was [introduced][5073] in GitLab 8.10.
If the commit is declined or an error occurs during the Git hook check, If the commit is declined or an error occurs during the Git hook check,
the STDERR or STDOUT message of the hook will be present in GitLab's UI. the STDERR or STDOUT message of the hook will be present in GitLab's UI.
......
# Housekeeping # Housekeeping
_**Note:** This feature was [introduced][ce-2371] in GitLab 8.4_ > [Introduced][ce-2371] in GitLab 8.4.
--- ---
......
# Project import/export # Project import/export
>**Note:** >**Note:**
- This feature was [introduced][ce-3050] in GitLab 8.9 >
- Importing will not be possible if the import instance version is lower > - [Introduced][ce-3050] in GitLab 8.9.
than that of the exporter. > - Importing will not be possible if the import instance version is lower
- For existing installations, the project import option has to be enabled in > than that of the exporter.
application settings (`/admin/application_settings`) under 'Import sources'. > - For existing installations, the project import option has to be enabled in
- The exports are stored in a temporary [shared directory][tmp] and are deleted > application settings (`/admin/application_settings`) under 'Import sources'.
every 24 hours by a specific worker. > - The exports are stored in a temporary [shared directory][tmp] and are deleted
> every 24 hours by a specific worker.
The GitLab Import/Export version can be checked by using: The GitLab Import/Export version can be checked by using:
......
# Repository checks # Repository checks
>**Note:** > [Introduced][ce-3232] in GitLab 8.7. It is OFF by default because it still
This feature was [introduced][ce-3232] in GitLab 8.7. It is OFF by causes too many false alarms.
default because it still causes too many false alarms.
Git has a built-in mechanism, [git fsck][git-fsck], to verify the Git has a built-in mechanism, [git fsck][git-fsck], to verify the
integrity of all data committed to a repository. GitLab administrators integrity of all data committed to a repository. GitLab administrators
......
...@@ -81,7 +81,7 @@ Read more about [GitLab as an OAuth2 client](oauth2.md). ...@@ -81,7 +81,7 @@ Read more about [GitLab as an OAuth2 client](oauth2.md).
### Personal Access Tokens ### Personal Access Tokens
> **Note:** This feature was [introduced][ce-3749] in GitLab 8.8 > [Introduced][ce-3749] in GitLab 8.8.
You can create as many personal access tokens as you like from your GitLab You can create as many personal access tokens as you like from your GitLab
profile (`/profile/personal_access_tokens`); perhaps one for each application profile (`/profile/personal_access_tokens`); perhaps one for each application
......
# Award Emoji # Award Emoji
>**Note:** This feature was introduced in GitLab 8.9 > [Introduced][ce-4575] in GitLab 8.9.
An awarded emoji tells a thousand words, and can be awarded on issues, merge An awarded emoji tells a thousand words, and can be awarded on issues, merge
requests and notes/comments. Issues, merge requests and notes are further called requests and notes/comments. Issues, merge requests and notes are further called
...@@ -365,3 +365,5 @@ Example Response: ...@@ -365,3 +365,5 @@ Example Response:
"awardable_type": "Note" "awardable_type": "Note"
} }
``` ```
[ce-4575]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4575
...@@ -12,6 +12,10 @@ Allows you to receive information about file in repository like name, size, cont ...@@ -12,6 +12,10 @@ Allows you to receive information about file in repository like name, size, cont
GET /projects/:id/repository/files GET /projects/:id/repository/files
``` ```
```bash
curl -X GET -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/models/key.rb&ref=master'
```
Example response: Example response:
```json ```json
...@@ -39,6 +43,10 @@ Parameters: ...@@ -39,6 +43,10 @@ Parameters:
POST /projects/:id/repository/files POST /projects/:id/repository/files
``` ```
```bash
curl -X POST -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&content=some%20content&commit_message=create%20a%20new%20file'
```
Example response: Example response:
```json ```json
...@@ -62,6 +70,10 @@ Parameters: ...@@ -62,6 +70,10 @@ Parameters:
PUT /projects/:id/repository/files PUT /projects/:id/repository/files
``` ```
```bash
curl -X PUT -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&content=some%20other%20content&commit_message=update%20file'
```
Example response: Example response:
```json ```json
...@@ -94,6 +106,10 @@ Currently gitlab-shell has a boolean return code, preventing GitLab from specify ...@@ -94,6 +106,10 @@ Currently gitlab-shell has a boolean return code, preventing GitLab from specify
DELETE /projects/:id/repository/files DELETE /projects/:id/repository/files
``` ```
```bash
curl -X PUT -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&commit_message=delete%20file'
```
Example response: Example response:
```json ```json
......
# Todos # Todos
**Note:** This feature was [introduced][ce-3188] in GitLab 8.10 > [Introduced][ce-3188] in GitLab 8.10.
## Get a list of todos ## Get a list of todos
......
...@@ -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"
# Triggering Builds through the API # Triggering Builds through the API
_**Note:** This feature was [introduced][ci-229] in GitLab CE 7.14_ > [Introduced][ci-229] in GitLab CE 7.14.
Triggers can be used to force a rebuild of a specific branch, tag or commit, Triggers can be used to force a rebuild of a specific branch, tag or commit,
with an API call. with an API call.
......
# GitLab Container Registry # GitLab Container Registry
> **Note:** > [Introduced][ce-4040] in GitLab 8.8. Docker Registry manifest
This feature was [introduced][ce-4040] in GitLab 8.8. Docker Registry manifest `v1` support was added in GitLab 8.9 to support Docker versions earlier than 1.10.
v1 support was added in GitLab 8.9 to support Docker versions earlier than 1.10.
> **Note:** > **Note:**
This document is about the user guide. To learn how to enable GitLab Container This document is about the user guide. To learn how to enable GitLab Container
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
- [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)
- [What requires downtime?](what_requires_downtime.md)
## Compliance ## Compliance
......
...@@ -3,12 +3,64 @@ ...@@ -3,12 +3,64 @@
This styleguide recommends best practices to improve documentation and to keep This styleguide recommends best practices to improve documentation and to keep
it organized and easy to find. it organized and easy to find.
## Naming ## Location and naming of documents
- When creating a new document and it has more than one word in its name, >**Note:**
make sure to use underscores instead of spaces or dashes (`-`). For example, These guidelines derive from the discussion taken place in issue [#3349](ce-3349).
a proper naming would be `import_projects_from_github.md`. The same rule
applies to images. The documentation hierarchy can be vastly improved by providing a better layout
and organization of directories.
Having a structured document layout, we will be able to have meaningful URLs
like `docs.gitlab.com/user/project/merge_requests.html`. With this pattern,
you can immediately tell that you are navigating a user related documentation
and is about the project and its merge requests.
The table below shows what kind of documentation goes where.
| Directory | What belongs here |
| --------- | -------------- |
| `doc/user/` | User related documentation. Anything that can be done within the GitLab UI goes here including `/admin`. |
| `doc/administration/` | Documentation that requires the user to have access to the server where GitLab is installed. The admin settings that can be accessed via GitLab's interface go under `doc/user/admin_area/`. |
| `doc/api/` | API related documentation. |
| `doc/development/` | Documentation related to the development of GitLab. Any styleguides should go here. |
| `doc/legal/` | Legal documents about contributing to GitLab. |
| `doc/install/`| Probably the most visited directory, since `installation.md` is there. Ideally this should go under `doc/administration/`, but it's best to leave it as-is in order to avoid confusion (still debated though). |
| `doc/update/` | Same with `doc/install/`. Should be under `administration/`, but this is a well known location, better leave as-is, at least for now. |
---
**General rules:**
1. The correct naming and location of a new document, is a combination
of the relative URL of the document in question and the GitLab Map design
that is used for UX purposes ([source][graffle], [image][gitlab-map]).
1. When creating a new document and it has more than one word in its name,
make sure to use underscores instead of spaces or dashes (`-`). For example,
a proper naming would be `import_projects_from_github.md`. The same rule
applies to images.
1. There are four main directories, `user`, `administration`, `api` and `development`.
1. The `doc/user/` directory has five main subdirectories: `project/`, `group/`,
`profile/`, `dashboard/` and `admin_area/`.
1. `doc/user/project/` should contain all project related documentation.
1. `doc/user/group/` should contain all group related documentation.
1. `doc/user/profile/` should contain all profile related documentation.
Every page you would navigate under `/profile` should have its own document,
i.e. `account.md`, `applications.md`, `emails.md`, etc.
1. `doc/user/dashboard/` should contain all dashboard related documentation.
1. `doc/user/admin_area/` should contain all admin related documentation
describing what can be achieved by accessing GitLab's admin interface
(_not to be confused with `doc/administration` where server access is
required_).
1. Every category under `/admin/application_settings` should have its
own document located at `doc/user/admin_area/settings/`. For example,
the **Visibility and Access Controls** category should have a document
located at `doc/user/admin_area/settings/visibility_and_access_controls.md`.
---
If you are unsure where a document should live, you can ping `@axil` in your
merge request.
## Text ## Text
...@@ -103,15 +155,15 @@ Inside the document: ...@@ -103,15 +155,15 @@ Inside the document:
- Every piece of documentation that comes with a new feature should declare the - Every piece of documentation that comes with a new feature should declare the
GitLab version that feature got introduced. Right below the heading add a GitLab version that feature got introduced. Right below the heading add a
note: `>**Note:** This feature was introduced in GitLab 8.3` note: `> Introduced in GitLab 8.3.`.
- If possible every feature should have a link to the MR that introduced it. - If possible every feature should have a link to the MR that introduced it.
The above note would be then transformed to: The above note would be then transformed to:
`>**Note:** This feature was [introduced][ce-1242] in GitLab 8.3`, where `> [Introduced][ce-1242] in GitLab 8.3.`, where
the [link identifier](#links) is named after the repository (CE) and the MR the [link identifier](#links) is named after the repository (CE) and the MR
number number.
- If the feature is only in GitLab EE, don't forget to mention it, like: - If the feature is only in GitLab EE, don't forget to mention it, like:
`>**Note:** This feature was introduced in GitLab EE 8.3`. Otherwise, leave `> Introduced in GitLab EE 8.3.`. Otherwise, leave
this mention out this mention out.
## References ## References
...@@ -372,3 +424,6 @@ curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -d "domain_whitelist[]=*.ex ...@@ -372,3 +424,6 @@ curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -d "domain_whitelist[]=*.ex
[single spaces]: http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html [single spaces]: http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html
[gfm]: http://docs.gitlab.com/ce/markdown/markdown.html#newlines "GitLab flavored markdown documentation" [gfm]: http://docs.gitlab.com/ce/markdown/markdown.html#newlines "GitLab flavored markdown documentation"
[doc-restart]: ../administration/restart_gitlab.md "GitLab restart documentation" [doc-restart]: ../administration/restart_gitlab.md "GitLab restart documentation"
[ce-3349]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3349 "Documentation restructure"
[graffle]: https://gitlab.com/gitlab-org/gitlab-design/blob/d8d39f4a87b90fb9ae89ca12dc565347b4900d5e/production/resources/gitlab-map.graffle
[gitlab-map]: https://gitlab.com/gitlab-org/gitlab-design/raw/master/production/resources/gitlab-map.png
...@@ -14,11 +14,33 @@ Note: `db:setup` calls `db:seed` but this does nothing. ...@@ -14,11 +14,33 @@ Note: `db:setup` calls `db:seed` but this does nothing.
## Run tests ## Run tests
This runs all test suites present in GitLab. In order to run the test you can use the following commands:
- `rake spinach` to run the spinach suite
- `rake spec` to run the rspec suite
- `rake teaspoon` to run the teaspoon test suite
- `rake gitlab:test` to run all the tests
``` Note: Both `rake spinach` and `rake spec` takes significant time to pass.
bundle exec rake test Instead of running full test suite locally you can save a lot of time by running
``` a single test or directory related to your changes. After you submit merge request
CI will run full test suite for you. Green CI status in the merge request means
full test suite is passed.
Note: You can't run `rspec .` since this will try to run all the `_spec.rb`
files it can find, also the ones in `/tmp`
To run a single test file you can use:
- `bundle exec rspec spec/controllers/commit_controller_spec.rb` for a rspec test
- `bundle exec spinach features/project/issues/milestones.feature` for a spinach test
To run several tests inside one directory:
- `bundle exec rspec spec/requests/api/` for the rspec tests if you want to test API only
- `bundle exec spinach features/profile/` for the spinach tests if you want to test only profile pages
If you want to use [Spring](https://github.com/rails/spring) set
`ENABLE_SPRING=1` in your environment.
## Generate searchable docs for source code ## Generate searchable docs for source code
......
# 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.
# Health Check # Health Check
>**Note:** This feature was [introduced][ce-3888] in GitLab 8.8. > [Introduced][ce-3888] in GitLab 8.8.
GitLab provides a health check endpoint for uptime monitoring on the `health_check` web GitLab provides a health check endpoint for uptime monitoring on the `health_check` web
endpoint. The health check reports on the overall system status based on the status of endpoint. The health check reports on the overall system status based on the status of
......
# 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"
...@@ -46,10 +46,11 @@ When you are ready press the **Create label** button to create the new label. ...@@ -46,10 +46,11 @@ When you are ready press the **Create label** button to create the new label.
## Prioritize labels ## Prioritize labels
>**Notes:** >**Notes:**
- This feature was introduced in GitLab 8.9. >
- Priority sorting is based on the highest priority label only. This might > - Introduced in GitLab 8.9.
change in the future, follow the discussion in > - Priority sorting is based on the highest priority label only. This might
https://gitlab.com/gitlab-org/gitlab-ce/issues/18554. > change in the future, follow the discussion in
> https://gitlab.com/gitlab-org/gitlab-ce/issues/18554.
Prioritized labels are like any other label, but sorted by priority. This allows Prioritized labels are like any other label, but sorted by priority. This allows
you to sort issues and merge requests by priority. you to sort issues and merge requests by priority.
...@@ -87,8 +88,7 @@ important. ...@@ -87,8 +88,7 @@ important.
## Create a new label right from the issue tracker ## Create a new label right from the issue tracker
>**Note:** > Introduced in GitLab 8.6.
This feature was introduced in GitLab 8.6.
There are times when you are already in the issue tracker searching for a There are times when you are already in the issue tracker searching for a
label, only to realize it doesn't exist. Instead of going to the **Labels** label, only to realize it doesn't exist. Instead of going to the **Labels**
......
...@@ -47,8 +47,7 @@ creation. ...@@ -47,8 +47,7 @@ creation.
## Wildcard protected branches ## Wildcard protected branches
>**Note:** > [Introduced][ce-4665] in GitLab 8.10.
This feature was [introduced][ce-4665] in GitLab 8.10.
You can specify a wildcard protected branch, which will protect all branches You can specify a wildcard protected branch, which will protect all branches
matching the wildcard. For example: matching the wildcard. For example:
......
# Project import/export # Project import/export
>**Notes:** >**Notes:**
- This feature was [introduced][ce-3050] in GitLab 8.9 >
- Importing will not be possible if the import instance version is lower > - [Introduced][ce-3050] in GitLab 8.9.
than that of the exporter. > - Importing will not be possible if the import instance version is lower
- For existing installations, the project import option has to be enabled in > than that of the exporter.
application settings (`/admin/application_settings`) under 'Import sources'. > - For existing installations, the project import option has to be enabled in
Ask your administrator if you don't see the **GitLab export** button when > application settings (`/admin/application_settings`) under 'Import sources'.
creating a new project. > Ask your administrator if you don't see the **GitLab export** button when
- You can find some useful raketasks if you are an administrator in the > creating a new project.
[import_export](../../../administration/raketasks/project_import_export.md) > - You can find some useful raketasks if you are an administrator in the
raketask. > [import_export](../../../administration/raketasks/project_import_export.md)
- The exports are stored in a temporary [shared directory][tmp] and are deleted > raketask.
every 24 hours by a specific worker. > - The exports are stored in a temporary [shared directory][tmp] and are deleted
> every 24 hours by a specific worker.
Existing projects running on any GitLab instance or GitLab.com can be exported Existing projects running on any GitLab instance or GitLab.com can be exported
with all their related data and be moved into a new GitLab instance. with all their related data and be moved into a new GitLab instance.
......
...@@ -26,6 +26,10 @@ GitLab webhooks keep in mind the following things: ...@@ -26,6 +26,10 @@ GitLab webhooks keep in mind the following things:
you are writing a low-level hook this is important to remember. you are writing a low-level hook this is important to remember.
- GitLab ignores the HTTP status code returned by your endpoint. - GitLab ignores the HTTP status code returned by your endpoint.
## Secret Token
If you specify a secret token, it will be sent with the hook request in the `X-Gitlab-Token` HTTP header. Your webhook endpoint can check that to verify that the request is legitimate.
## SSL Verification ## SSL Verification
By default, the SSL certificate of the webhook endpoint is verified based on By default, the SSL certificate of the webhook endpoint is verified based on
......
# Award emoji # Award emoji
>**Note:** >**Note:**
This feature was [introduced][1825] in GitLab 8.2. [Introduced][1825] in GitLab 8.2.
When you're collaborating online, you get fewer opportunities for high-fives When you're collaborating online, you get fewer opportunities for high-fives
and thumbs-ups. Emoji can be awarded to issues and merge requests, making and thumbs-ups. Emoji can be awarded to issues and merge requests, making
...@@ -16,7 +16,7 @@ award emoji. ...@@ -16,7 +16,7 @@ award emoji.
## Sort issues and merge requests on vote count ## Sort issues and merge requests on vote count
>**Note:** >**Note:**
This feature was [introduced][2871] in GitLab 8.5. [Introduced][2871] in GitLab 8.5.
You can quickly sort issues and merge requests by the number of votes they You can quickly sort issues and merge requests by the number of votes they
have received. The sort options can be found in the dropdown menu as "Most have received. The sort options can be found in the dropdown menu as "Most
...@@ -45,7 +45,7 @@ downvotes. ...@@ -45,7 +45,7 @@ downvotes.
## Award emoji for comments ## Award emoji for comments
>**Note:** >**Note:**
This feature was [introduced][4291] in GitLab 8.9. [Introduced][4291] in GitLab 8.9.
Award emoji can also be applied to individual comments when you want to Award emoji can also be applied to individual comments when you want to
celebrate an accomplishment or agree with an opinion. celebrate an accomplishment or agree with an opinion.
......
# Cherry-pick changes # Cherry-pick changes
>**Note:** > [Introduced][ce-3514] in GitLab 8.7.
This feature was [introduced][ce-3514] in GitLab 8.7.
--- ---
......
# File finder # File finder
_**Note:** This feature was [introduced][gh-9889] in GitLab 8.4._ > [Introduced][gh-9889] in GitLab 8.4.
--- ---
......
# Reverting changes # Reverting changes
_**Note:** This feature was [introduced][ce-1990] in GitLab 8.5._ > [Introduced][ce-1990] in GitLab 8.5.
--- ---
......
# GitLab Todos # GitLab Todos
>**Note:** This feature was [introduced][ce-2817] in GitLab 8.5. > [Introduced][ce-2817] in GitLab 8.5.
When you log into GitLab, you normally want to see where you should spend your When you log into GitLab, you normally want to see where you should spend your
time and take some action, or what you need to keep an eye on. All without the time and take some action, or what you need to keep an eye on. All without the
......
...@@ -70,8 +70,7 @@ There are multiple ways to create a branch from GitLab's web interface. ...@@ -70,8 +70,7 @@ There are multiple ways to create a branch from GitLab's web interface.
### Create a new branch from an issue ### Create a new branch from an issue
>**Note:** > [Introduced][ce-2808] in GitLab 8.6.
This feature was [introduced][ce-2808] in GitLab 8.6.
In case your development workflow dictates to have an issue for every merge In case your development workflow dictates to have an issue for every merge
request, you can quickly create a branch right on the issue page which will be request, you can quickly create a branch right on the issue page which will be
......
...@@ -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
......
...@@ -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
......
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
...@@ -11,7 +11,7 @@ feature 'Projected Branches', feature: true, js: true do ...@@ -11,7 +11,7 @@ feature 'Projected Branches', feature: true, js: true do
def set_protected_branch_name(branch_name) def set_protected_branch_name(branch_name)
find(".js-protected-branch-select").click find(".js-protected-branch-select").click
find(".dropdown-input-field").set(branch_name) find(".dropdown-input-field").set(branch_name)
click_on "Create Protected Branch: #{branch_name}" click_on("Create wildcard #{branch_name}")
end end
describe "explicit protected branches" do describe "explicit protected branches" do
...@@ -90,7 +90,7 @@ feature 'Projected Branches', feature: true, js: true do ...@@ -90,7 +90,7 @@ feature 'Projected Branches', feature: true, js: true do
visit namespace_project_protected_branches_path(project.namespace, project) visit namespace_project_protected_branches_path(project.namespace, project)
set_protected_branch_name('master') set_protected_branch_name('master')
within('.new_protected_branch') do within('.new_protected_branch') do
find(".allowed-to-push").click find(".js-allowed-to-push").click
within(".dropdown.open .dropdown-menu") { click_on access_type_name } within(".dropdown.open .dropdown-menu") { click_on access_type_name }
end end
click_on "Protect" click_on "Protect"
...@@ -107,8 +107,8 @@ feature 'Projected Branches', feature: true, js: true do ...@@ -107,8 +107,8 @@ feature 'Projected Branches', feature: true, js: true do
expect(ProtectedBranch.count).to eq(1) expect(ProtectedBranch.count).to eq(1)
within(".protected-branches-list") do within(".protected-branches-list") do
find(".allowed-to-push").click find(".js-allowed-to-push").click
within('.dropdown-menu.push') { click_on access_type_name } within('.js-allowed-to-push-container') { click_on access_type_name }
end end
wait_for_ajax wait_for_ajax
...@@ -121,7 +121,7 @@ feature 'Projected Branches', feature: true, js: true do ...@@ -121,7 +121,7 @@ feature 'Projected Branches', feature: true, js: true do
visit namespace_project_protected_branches_path(project.namespace, project) visit namespace_project_protected_branches_path(project.namespace, project)
set_protected_branch_name('master') set_protected_branch_name('master')
within('.new_protected_branch') do within('.new_protected_branch') do
find(".allowed-to-merge").click find(".js-allowed-to-merge").click
within(".dropdown.open .dropdown-menu") { click_on access_type_name } within(".dropdown.open .dropdown-menu") { click_on access_type_name }
end end
click_on "Protect" click_on "Protect"
...@@ -138,8 +138,8 @@ feature 'Projected Branches', feature: true, js: true do ...@@ -138,8 +138,8 @@ feature 'Projected Branches', feature: true, js: true do
expect(ProtectedBranch.count).to eq(1) expect(ProtectedBranch.count).to eq(1)
within(".protected-branches-list") do within(".protected-branches-list") do
find(".allowed-to-merge").click find(".js-allowed-to-merge").click
within('.dropdown-menu.merge') { click_on access_type_name } within('.js-allowed-to-merge-container') { click_on access_type_name }
end end
wait_for_ajax wait_for_ajax
......
...@@ -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
#= require lib/utils/datetime_utility
describe 'Date time utils', ->
describe 'get day name', ->
it 'should return Sunday', ->
day = gl.utils.getDayName(new Date('07/17/2016'))
expect(day).toBe('Sunday')
it 'should return Monday', ->
day = gl.utils.getDayName(new Date('07/18/2016'))
expect(day).toBe('Monday')
it 'should return Tuesday', ->
day = gl.utils.getDayName(new Date('07/19/2016'))
expect(day).toBe('Tuesday')
it 'should return Wednesday', ->
day = gl.utils.getDayName(new Date('07/20/2016'))
expect(day).toBe('Wednesday')
it 'should return Thursday', ->
day = gl.utils.getDayName(new Date('07/21/2016'))
expect(day).toBe('Thursday')
it 'should return Friday', ->
day = gl.utils.getDayName(new Date('07/22/2016'))
expect(day).toBe('Friday')
it 'should return Saturday', ->
day = gl.utils.getDayName(new Date('07/23/2016'))
expect(day).toBe('Saturday')
...@@ -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']).
......
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