Commit 7a67ed66 authored by Rubén Dávila Santos's avatar Rubén Dávila Santos

Merge branch '8-12-stable-ee' into 'master'

8-12-stable-ee to EE



See merge request !747
parents 63260200 ed6fc16b
...@@ -5,7 +5,9 @@ v 8.12.0 (unreleased) ...@@ -5,7 +5,9 @@ v 8.12.0 (unreleased)
- Only check :can_resolve permission if the note is resolvable - Only check :can_resolve permission if the note is resolvable
- Bump fog-aws to v0.11.0 to support ap-south-1 region - Bump fog-aws to v0.11.0 to support ap-south-1 region
- Add ability to fork to a specific namespace using API. (ritave) - Add ability to fork to a specific namespace using API. (ritave)
- Allow to set request_access_enabled for groups and projects
- Cleanup misalignments in Issue list view !6206 - Cleanup misalignments in Issue list view !6206
- Only create a protected branch upon a push to a new branch if a rule for that branch doesn't exist
- Prune events older than 12 months. (ritave) - Prune events older than 12 months. (ritave)
- Prevent secrets to be pushed to the repository - Prevent secrets to be pushed to the repository
- Prepend blank line to `Closes` message on merge request linked to issue (lukehowell) - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell)
...@@ -13,19 +15,28 @@ v 8.12.0 (unreleased) ...@@ -13,19 +15,28 @@ v 8.12.0 (unreleased)
- Filter tags by name !6121 - Filter tags by name !6121
- Update gitlab shell secret file also when it is empty. !3774 (glensc) - Update gitlab shell secret file also when it is empty. !3774 (glensc)
- Give project selection dropdowns responsive width, make non-wrapping. - Give project selection dropdowns responsive width, make non-wrapping.
- Fix note form hint showing slash commands supported for commits.
- Make push events have equal vertical spacing. - Make push events have equal vertical spacing.
- API: Ensure invitees are not returned in Members API.
- Add two-factor recovery endpoint to internal API !5510 - Add two-factor recovery endpoint to internal API !5510
- Pass the "Remember me" value to the U2F authentication form - Pass the "Remember me" value to the U2F authentication form
- Display stages in valid order in stages dropdown on build page
- Only update projects.last_activity_at once per hour when creating a new event
- Remove vendor prefixes for linear-gradient CSS (ClemMakesApps) - Remove vendor prefixes for linear-gradient CSS (ClemMakesApps)
- Move pushes_since_gc from the database to Redis - Move pushes_since_gc from the database to Redis
- Add font color contrast to external label in admin area (ClemMakesApps) - Add font color contrast to external label in admin area (ClemMakesApps)
- Change logo animation to CSS (ClemMakesApps) - Change logo animation to CSS (ClemMakesApps)
- Instructions for enabling Git packfile bitmaps !6104 - Instructions for enabling Git packfile bitmaps !6104
- Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint
- Fix long comments in diffs messing with table width
- Fix pagination on user snippets page - Fix pagination on user snippets page
- Run CI builds with the permissions of users !5735
- Fix sorting of issues in API - Fix sorting of issues in API
- Fix download artifacts button links !6407
- Sort project variables by key. !6275 (Diego Souza) - Sort project variables by key. !6275 (Diego Souza)
- Ensure specs on sorting of issues in API are deterministic on MySQL - Ensure specs on sorting of issues in API are deterministic on MySQL
- Added ability to use predefined CI variables for environment name
- Added ability to specify URL in environment configuration in gitlab-ci.yml
- Escape search term before passing it to Regexp.new !6241 (winniehell) - Escape search term before passing it to Regexp.new !6241 (winniehell)
- Fix pinned sidebar behavior in smaller viewports !6169 - Fix pinned sidebar behavior in smaller viewports !6169
- Fix file permissions change when updating a file on the Gitlab UI !5979 - Fix file permissions change when updating a file on the Gitlab UI !5979
...@@ -46,11 +57,13 @@ v 8.12.0 (unreleased) ...@@ -46,11 +57,13 @@ v 8.12.0 (unreleased)
- Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps) - Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps)
- Fix bug stopping issue description being scrollable after selecting issue template - Fix bug stopping issue description being scrollable after selecting issue template
- Remove suggested colors hover underline (ClemMakesApps) - Remove suggested colors hover underline (ClemMakesApps)
- Fix jump to discussion button being displayed on commit notes
- Shorten task status phrase (ClemMakesApps) - Shorten task status phrase (ClemMakesApps)
- Fix project visibility level fields on settings - Fix project visibility level fields on settings
- Add hover color to emoji icon (ClemMakesApps) - Add hover color to emoji icon (ClemMakesApps)
- Increase ci_builds artifacts_size column to 8-byte integer to allow larger files - Increase ci_builds artifacts_size column to 8-byte integer to allow larger files
- Add textarea autoresize after comment (ClemMakesApps) - Add textarea autoresize after comment (ClemMakesApps)
- Do not write SSH public key 'comments' to authorized_keys !6381
- Refresh todos count cache when an Issue/MR is deleted - Refresh todos count cache when an Issue/MR is deleted
- Fix branches page dropdown sort alignment (ClemMakesApps) - Fix branches page dropdown sort alignment (ClemMakesApps)
- Hides merge request button on branches page is user doesn't have permissions - Hides merge request button on branches page is user doesn't have permissions
...@@ -60,6 +73,7 @@ v 8.12.0 (unreleased) ...@@ -60,6 +73,7 @@ v 8.12.0 (unreleased)
- Test migration paths from 8.5 until current release !4874 - Test migration paths from 8.5 until current release !4874
- Replace animateEmoji timeout with eventListener (ClemMakesApps) - Replace animateEmoji timeout with eventListener (ClemMakesApps)
- Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention)
- Require confirmation when not logged in for unsubscribe links !6223 (Maximiliano Perez Coto)
- Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Add `wiki_page_events` to project hook APIs (Ben Boeckel)
- Remove Gitorious import - Remove Gitorious import
- Fix inconsistent background color for filter input field (ClemMakesApps) - Fix inconsistent background color for filter input field (ClemMakesApps)
...@@ -87,6 +101,7 @@ v 8.12.0 (unreleased) ...@@ -87,6 +101,7 @@ v 8.12.0 (unreleased)
- Fix missing flash messages on service edit page (airatshigapov) - Fix missing flash messages on service edit page (airatshigapov)
- Added project-specific enable/disable setting for LFS !5997 - Added project-specific enable/disable setting for LFS !5997
- Added group-specific enable/disable setting for LFS !6164 - Added group-specific enable/disable setting for LFS !6164
- Add optional 'author' param when making commits. !5822 (dandunckelman)
- Don't expose a user's token in the `/api/v3/user` API (!6047) - Don't expose a user's token in the `/api/v3/user` API (!6047)
- Remove redundant js-timeago-pending from user activity log (ClemMakesApps) - Remove redundant js-timeago-pending from user activity log (ClemMakesApps)
- Ability to manage project issues, snippets, wiki, merge requests and builds access level - Ability to manage project issues, snippets, wiki, merge requests and builds access level
...@@ -104,6 +119,7 @@ v 8.12.0 (unreleased) ...@@ -104,6 +119,7 @@ v 8.12.0 (unreleased)
- Remove green outline from `New branch unavailable` button on issue page !5858 (winniehell) - Remove green outline from `New branch unavailable` button on issue page !5858 (winniehell)
- Fix repo title alignment (ClemMakesApps) - Fix repo title alignment (ClemMakesApps)
- Change update interval of contacted_at - Change update interval of contacted_at
- Add LFS support to SSH !6043
- Fix branch title trailing space on hover (ClemMakesApps) - Fix branch title trailing space on hover (ClemMakesApps)
- Don't include 'Created By' tag line when importing from GitHub if there is a linked GitLab account (EspadaV8) - Don't include 'Created By' tag line when importing from GitHub if there is a linked GitLab account (EspadaV8)
- Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison)
...@@ -138,17 +154,23 @@ v 8.12.0 (unreleased) ...@@ -138,17 +154,23 @@ v 8.12.0 (unreleased)
- Refactor the triggers page and documentation !6217 - Refactor the triggers page and documentation !6217
- Show values of CI trigger variables only when clicked (Katarzyna Kobierska Ula Budziszewska) - Show values of CI trigger variables only when clicked (Katarzyna Kobierska Ula Budziszewska)
- Use default clone protocol on "check out, review, and merge locally" help page URL - Use default clone protocol on "check out, review, and merge locally" help page URL
- Let the user choose a namespace and name on GitHub imports
- API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska) - API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska)
- Allow bulk update merge requests from merge requests index page - Allow bulk update merge requests from merge requests index page
- Ensure validation messages are shown within the milestone form
- Add notification_settings API calls !5632 (mahcsig) - Add notification_settings API calls !5632 (mahcsig)
- Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska) - Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska)
- Fix URLs with anchors in wiki !6300 (houqp) - Fix URLs with anchors in wiki !6300 (houqp)
- Use a ConnectionPool for Rails.cache on Sidekiq servers
- Deleting source project with existing fork link will close all related merge requests !6177 (Katarzyna Kobierska Ula Budziszeska) - Deleting source project with existing fork link will close all related merge requests !6177 (Katarzyna Kobierska Ula Budziszeska)
- Return 204 instead of 404 for /ci/api/v1/builds/register.json if no builds are scheduled for a runner !6225 - Return 204 instead of 404 for /ci/api/v1/builds/register.json if no builds are scheduled for a runner !6225
- Fix Gitlab::Popen.popen thread-safety issue - Fix Gitlab::Popen.popen thread-safety issue
- Add specs to removing project (Katarzyna Kobierska Ula Budziszewska) - Add specs to removing project (Katarzyna Kobierska Ula Budziszewska)
- Clean environment variables when running git hooks - Clean environment variables when running git hooks
- Add UX improvements for merge request version diffs
- Fix Import/Export issues importing protected branches and some specific models
- Fix non-master branch readme display in tree view - Fix non-master branch readme display in tree view
- Add UX improvements for merge request version diffs
v 8.11.6 v 8.11.6
- Fix unnecessary horizontal scroll area in pipeline visualizations. !6005 - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005
...@@ -220,6 +242,7 @@ v 8.11.0 ...@@ -220,6 +242,7 @@ v 8.11.0
- Add Koding (online IDE) integration - Add Koding (online IDE) integration
- Ability to specify branches for Pivotal Tracker integration (Egor Lynko) - Ability to specify branches for Pivotal Tracker integration (Egor Lynko)
- Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres) - Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres)
- Add delimiter to project stars and forks count (ClemMakesApps)
- Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres) - Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres)
- Fix adding line comments on the initial commit to a repo !5900 - Fix adding line comments on the initial commit to a repo !5900
- Fix the title of the toggle dropdown button. !5515 (herminiotorres) - Fix the title of the toggle dropdown button. !5515 (herminiotorres)
...@@ -273,6 +296,7 @@ v 8.11.0 ...@@ -273,6 +296,7 @@ v 8.11.0
- Enforce 2FA restrictions on API authentication endpoints !5820 - Enforce 2FA restrictions on API authentication endpoints !5820
- Limit git rev-list output count to one in forced push check - Limit git rev-list output count to one in forced push check
- Show deployment status on merge requests with external URLs - Show deployment status on merge requests with external URLs
- Fix branch title trailing space on hover (ClemMakesApps)
- Clean up unused routes (Josef Strzibny) - Clean up unused routes (Josef Strzibny)
- Fix issue on empty project to allow developers to only push to protected branches if given permission - Fix issue on empty project to allow developers to only push to protected branches if given permission
- API: Add enpoints for pipelines - API: Add enpoints for pipelines
......
8.12.0-ee-pre 8.12.0-rc5-ee
...@@ -17,9 +17,6 @@ ...@@ -17,9 +17,6 @@
.replace(':id', group_id); .replace(':id', group_id);
return $.ajax({ return $.ajax({
url: url, url: url,
data: {
private_token: gon.api_token
},
dataType: "json" dataType: "json"
}).done(function(group) { }).done(function(group) {
return callback(group); return callback(group);
...@@ -32,7 +29,6 @@ ...@@ -32,7 +29,6 @@
return $.ajax({ return $.ajax({
url: url, url: url,
data: { data: {
private_token: gon.api_token,
search: query, search: query,
per_page: 20 per_page: 20
}, },
...@@ -47,7 +43,6 @@ ...@@ -47,7 +43,6 @@
return $.ajax({ return $.ajax({
url: url, url: url,
data: { data: {
private_token: gon.api_token,
search: query, search: query,
per_page: 20 per_page: 20
}, },
...@@ -62,7 +57,6 @@ ...@@ -62,7 +57,6 @@
return $.ajax({ return $.ajax({
url: url, url: url,
data: { data: {
private_token: gon.api_token,
search: query, search: query,
order_by: order, order_by: order,
per_page: 20 per_page: 20
...@@ -75,7 +69,6 @@ ...@@ -75,7 +69,6 @@
newLabel: function(project_id, data, callback) { newLabel: function(project_id, data, callback) {
var url = Api.buildUrl(Api.labelsPath) var url = Api.buildUrl(Api.labelsPath)
.replace(':id', project_id); .replace(':id', project_id);
data.private_token = gon.api_token;
return $.ajax({ return $.ajax({
url: url, url: url,
type: "POST", type: "POST",
...@@ -94,7 +87,6 @@ ...@@ -94,7 +87,6 @@
return $.ajax({ return $.ajax({
url: url, url: url,
data: { data: {
private_token: gon.api_token,
search: query, search: query,
per_page: 20 per_page: 20
}, },
......
...@@ -27,10 +27,11 @@ ...@@ -27,10 +27,11 @@
$(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar); $(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
$(window).off('resize.build').on('resize.build', this.hideSidebar); $(window).off('resize.build').on('resize.build', this.hideSidebar);
$(document).off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown); $(document).off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown);
$('#js-build-scroll > a').off('click').on('click', this.stepTrace);
this.updateArtifactRemoveDate(); this.updateArtifactRemoveDate();
if ($('#build-trace').length) { if ($('#build-trace').length) {
this.getInitialBuildTrace(); this.getInitialBuildTrace();
this.initScrollButtonAffix(); this.initScrollButtons();
} }
if (this.build_status === "running" || this.build_status === "pending") { if (this.build_status === "running" || this.build_status === "pending") {
$('#autoscroll-button').on('click', function() { $('#autoscroll-button').on('click', function() {
...@@ -106,7 +107,7 @@ ...@@ -106,7 +107,7 @@
} }
}; };
Build.prototype.initScrollButtonAffix = function() { Build.prototype.initScrollButtons = function() {
var $body, $buildScroll, $buildTrace; var $body, $buildScroll, $buildTrace;
$buildScroll = $('#js-build-scroll'); $buildScroll = $('#js-build-scroll');
$body = $('body'); $body = $('body');
...@@ -165,6 +166,14 @@ ...@@ -165,6 +166,14 @@
this.populateJobs(stage); this.populateJobs(stage);
}; };
Build.prototype.stepTrace = function(e) {
e.preventDefault();
$currentTarget = $(e.currentTarget);
$.scrollTo($currentTarget.attr('href'), {
offset: -($('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight())
});
};
return Build; return Build;
})(); })();
......
...@@ -607,26 +607,29 @@ ...@@ -607,26 +607,29 @@
selectedObject = this.renderedData[selectedIndex]; selectedObject = this.renderedData[selectedIndex];
} }
} }
field = [];
value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id; value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id;
if (isInput) { if (isInput) {
field = $(this.el); field = $(this.el);
} else { } else if(value) {
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + escape(value) + "']"); field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']");
} }
if (el.hasClass(ACTIVE_CLASS)) { if (el.hasClass(ACTIVE_CLASS)) {
el.removeClass(ACTIVE_CLASS); el.removeClass(ACTIVE_CLASS);
if (isInput) { if (field && field.length) {
field.val(''); if (isInput) {
} else { field.val('');
field.remove(); } else {
field.remove();
}
} }
} else if (el.hasClass(INDETERMINATE_CLASS)) { } else if (el.hasClass(INDETERMINATE_CLASS)) {
el.addClass(ACTIVE_CLASS); el.addClass(ACTIVE_CLASS);
el.removeClass(INDETERMINATE_CLASS); el.removeClass(INDETERMINATE_CLASS);
if (value == null) { if (field && field.length && value == null) {
field.remove(); field.remove();
} }
if (!field.length && fieldName) { if ((!field || !field.length) && fieldName) {
this.addInput(fieldName, value, selectedObject); this.addInput(fieldName, value, selectedObject);
} }
} else { } else {
...@@ -636,15 +639,15 @@ ...@@ -636,15 +639,15 @@
this.dropdown.parent().find("input[name='" + fieldName + "']").remove(); this.dropdown.parent().find("input[name='" + fieldName + "']").remove();
} }
} }
if (value == null) { if (field && field.length && value == null) {
field.remove(); field.remove();
} }
// Toggle active class for the tick mark // Toggle active class for the tick mark
el.addClass(ACTIVE_CLASS); el.addClass(ACTIVE_CLASS);
if (value != null) { if (value != null) {
if (!field.length && fieldName) { if ((!field || !field.length) && fieldName) {
this.addInput(fieldName, value, selectedObject); this.addInput(fieldName, value, selectedObject);
} else { } else if (field && field.length) {
field.val(value).trigger('change'); field.val(value).trigger('change');
} }
} }
......
...@@ -10,24 +10,24 @@ ...@@ -10,24 +10,24 @@
ImporterStatus.prototype.initStatusPage = function() { ImporterStatus.prototype.initStatusPage = function() {
$('.js-add-to-import').off('click').on('click', (function(_this) { $('.js-add-to-import').off('click').on('click', (function(_this) {
return function(e) { return function(e) {
var $btn, $namespace_input, $target_field, $tr, id, target_namespace; var $btn, $namespace_input, $target_field, $tr, id, target_namespace, newName;
$btn = $(e.currentTarget); $btn = $(e.currentTarget);
$tr = $btn.closest('tr'); $tr = $btn.closest('tr');
$target_field = $tr.find('.import-target'); $target_field = $tr.find('.import-target');
$namespace_input = $target_field.find('input'); $namespace_input = $target_field.find('.js-select-namespace option:selected');
id = $tr.attr('id').replace('repo_', ''); id = $tr.attr('id').replace('repo_', '');
target_namespace = null; target_namespace = null;
newName = null;
if ($namespace_input.length > 0) { if ($namespace_input.length > 0) {
target_namespace = $namespace_input.prop('value'); target_namespace = $namespace_input[0].innerHTML;
$target_field.empty().append(target_namespace + "/" + ($target_field.data('project_name'))); newName = $target_field.find('#path').prop('value');
$target_field.empty().append(target_namespace + "/" + newName);
} }
$btn.disable().addClass('is-loading'); $btn.disable().addClass('is-loading');
return $.post(_this.import_url, { return $.post(_this.import_url, {
repo_id: id, repo_id: id,
target_namespace: target_namespace target_namespace: target_namespace,
new_name: newName
}, { }, {
dataType: 'script' dataType: 'script'
}); });
......
...@@ -16,11 +16,11 @@ ...@@ -16,11 +16,11 @@
}, },
initSearch: function() { initSearch: function() {
this.timer = null; this.timer = null;
return $('#issue_search').off('keyup').on('keyup', function() { return $('#issuable_search').off('keyup').on('keyup', function() {
clearTimeout(this.timer); clearTimeout(this.timer);
return this.timer = setTimeout(function() { return this.timer = setTimeout(function() {
var $form, $input, $search; var $form, $input, $search;
$search = $('#issue_search'); $search = $('#issuable_search');
$form = $('.js-filter-form'); $form = $('.js-filter-form');
$input = $("input[name='" + ($search.attr('name')) + "']", $form); $input = $("input[name='" + ($search.attr('name')) + "']", $form);
if ($input.length === 0) { if ($input.length === 0) {
......
...@@ -166,7 +166,7 @@ ...@@ -166,7 +166,7 @@
instance.addInput(this.fieldName, label.id); instance.addInput(this.fieldName, label.id);
} }
} }
if ($form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + escape(this.id(label)) + "']").length) { if (this.id(label) && $form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + this.id(label).toString().replace(/'/g, '\\\'') + "']").length) {
selectedClass.push('is-active'); selectedClass.push('is-active');
} }
if ($dropdown.hasClass('js-multiselect') && removesAll) { if ($dropdown.hasClass('js-multiselect') && removesAll) {
......
...@@ -53,11 +53,6 @@ ...@@ -53,11 +53,6 @@
this.$protectedBranch.on('click', this.onClickCreateWildcard.bind(this)); this.$protectedBranch.on('click', this.onClickCreateWildcard.bind(this));
} }
onClickCreateWildcard() {
this.$dropdown.data('glDropdown').remote.execute();
this.$dropdown.data('glDropdown').selectRowAtIndex(0);
}
getProtectedBranches(term, callback) { getProtectedBranches(term, callback) {
if (this.selectedBranch) { if (this.selectedBranch) {
callback(gon.open_branches.concat(this.selectedBranch)); callback(gon.open_branches.concat(this.selectedBranch));
......
...@@ -68,6 +68,11 @@ ...@@ -68,6 +68,11 @@
border-collapse: separate; border-collapse: separate;
margin: 0; margin: 0;
padding: 0; padding: 0;
table-layout: fixed;
.diff-line-num {
width: 50px;
}
.line_holder td { .line_holder td {
line-height: $code_line_height; line-height: $code_line_height;
...@@ -98,10 +103,6 @@ ...@@ -98,10 +103,6 @@
} }
tr.line_holder.parallel { tr.line_holder.parallel {
.old_line, .new_line {
min-width: 50px;
}
td.line_content.parallel { td.line_content.parallel {
width: 46%; width: 46%;
} }
......
...@@ -373,11 +373,40 @@ ...@@ -373,11 +373,40 @@
.mr-version-controls { .mr-version-controls {
background: $background-color; background: $background-color;
padding: $gl-btn-padding; border-bottom: 1px solid $border-color;
color: $gl-placeholder-color; color: $gl-text-color;
.mr-version-menus-container {
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
padding: 16px;
}
.comments-disabled-notif {
padding: 10px 16px;
.btn {
margin-left: 5px;
}
}
.mr-version-dropdown,
.mr-version-compare-dropdown {
margin: 0 7px;
}
.comments-disabled-notif {
border-top: 1px solid $border-color;
}
.dropdown-title {
color: $gl-text-color;
}
a.btn-link { .fa-info-circle {
color: $gl-dark-link-color; color: $orange-normal;
padding-right: 5px;
} }
} }
......
...@@ -799,3 +799,13 @@ a.allowed-to-merge, a.allowed-to-push { ...@@ -799,3 +799,13 @@ a.allowed-to-merge, a.allowed-to-push {
} }
} }
} }
.project-path {
.form-control {
min-width: 100px;
}
.select2-choice {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
\ No newline at end of file
...@@ -13,10 +13,18 @@ module IssuableCollections ...@@ -13,10 +13,18 @@ module IssuableCollections
issues_finder.execute issues_finder.execute
end end
def all_issues_collection
IssuesFinder.new(current_user, filter_params_all).execute
end
def merge_requests_collection def merge_requests_collection
merge_requests_finder.execute merge_requests_finder.execute
end end
def all_merge_requests_collection
MergeRequestsFinder.new(current_user, filter_params_all).execute
end
def issues_finder def issues_finder
@issues_finder ||= issuable_finder_for(IssuesFinder) @issues_finder ||= issuable_finder_for(IssuesFinder)
end end
...@@ -54,6 +62,10 @@ module IssuableCollections ...@@ -54,6 +62,10 @@ module IssuableCollections
@filter_params @filter_params
end end
def filter_params_all
@filter_params_all ||= filter_params.merge(state: 'all', sort: nil)
end
def set_default_scope def set_default_scope
params[:scope] = 'all' if params[:scope].blank? params[:scope] = 'all' if params[:scope].blank?
end end
......
...@@ -10,6 +10,8 @@ module IssuesAction ...@@ -10,6 +10,8 @@ module IssuesAction
.preload(:author, :project) .preload(:author, :project)
.page(params[:page]) .page(params[:page])
@all_issues = all_issues_collection.non_archived
respond_to do |format| respond_to do |format|
format.html format.html
format.atom { render layout: false } format.atom { render layout: false }
......
...@@ -9,5 +9,7 @@ module MergeRequestsAction ...@@ -9,5 +9,7 @@ module MergeRequestsAction
.non_archived .non_archived
.preload(:author, :target_project) .preload(:author, :target_project)
.page(params[:page]) .page(params[:page])
@all_merge_requests = all_merge_requests_collection.non_archived
end end
end end
...@@ -40,11 +40,12 @@ class Import::GithubController < Import::BaseController ...@@ -40,11 +40,12 @@ class Import::GithubController < Import::BaseController
def create def create
@repo_id = params[:repo_id].to_i @repo_id = params[:repo_id].to_i
repo = client.repo(@repo_id) repo = client.repo(@repo_id)
@project_name = repo.name @project_name = params[:new_name].presence || repo.name
@target_namespace = find_or_create_namespace(repo.owner.login, client.user.login) namespace_path = params[:target_namespace].presence || current_user.namespace_path
@target_namespace = find_or_create_namespace(namespace_path, current_user.namespace_path)
if current_user.can?(:create_projects, @target_namespace) if current_user.can?(:create_projects, @target_namespace)
@project = Gitlab::GithubImport::ProjectCreator.new(repo, @target_namespace, current_user, access_params).execute @project = Gitlab::GithubImport::ProjectCreator.new(repo, @project_name, @target_namespace, current_user, access_params).execute
else else
render 'unauthorized' render 'unauthorized'
end end
......
...@@ -11,7 +11,10 @@ class JwtController < ApplicationController ...@@ -11,7 +11,10 @@ class JwtController < ApplicationController
service = SERVICES[params[:service]] service = SERVICES[params[:service]]
return head :not_found unless service return head :not_found unless service
result = service.new(@project, @user, auth_params).execute @authentication_result ||= Gitlab::Auth::Result.new
result = service.new(@authentication_result.project, @authentication_result.actor, auth_params).
execute(authentication_abilities: @authentication_result.authentication_abilities)
render json: result, status: result[:http_status] render json: result, status: result[:http_status]
end end
...@@ -20,30 +23,23 @@ class JwtController < ApplicationController ...@@ -20,30 +23,23 @@ class JwtController < ApplicationController
def authenticate_project_or_user def authenticate_project_or_user
authenticate_with_http_basic do |login, password| authenticate_with_http_basic do |login, password|
# if it's possible we first try to authenticate project with login and password @authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip)
@project = authenticate_project(login, password)
return if @project
@user = authenticate_user(login, password)
return if @user
render_403 render_403 unless @authentication_result.success? &&
(@authentication_result.actor.nil? || @authentication_result.actor.is_a?(User))
end end
rescue Gitlab::Auth::MissingPersonalTokenError
render_missing_personal_token
end end
def auth_params def render_missing_personal_token
params.permit(:service, :scope, :account, :client_id) render plain: "HTTP Basic: Access denied\n" \
"You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \
"You can generate one at #{profile_personal_access_tokens_url}",
status: 401
end end
def authenticate_project(login, password) def auth_params
if login == 'gitlab-ci-token' params.permit(:service, :scope, :account, :client_id)
Project.with_builds_enabled.find_by(runners_token: password)
end
end
def authenticate_user(login, password)
user = Gitlab::Auth.find_with_user_password(login, password)
Gitlab::Auth.rate_limit!(request.ip, success: user.present?, login: login)
user
end end
end end
...@@ -35,7 +35,11 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -35,7 +35,11 @@ class Projects::BuildsController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html format.html
format.json do format.json do
render json: @build.to_json(methods: :trace_html) render json: {
id: @build.id,
status: @build.status,
trace_html: @build.trace_html
}
end end
end end
end end
......
...@@ -4,7 +4,11 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -4,7 +4,11 @@ class Projects::GitHttpClientController < Projects::ApplicationController
include ActionController::HttpAuthentication::Basic include ActionController::HttpAuthentication::Basic
include KerberosSpnegoHelper include KerberosSpnegoHelper
attr_reader :user attr_reader :authentication_result
delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true
alias_method :user, :actor
# Git clients will not know what authenticity token to send along # Git clients will not know what authenticity token to send along
skip_before_action :verify_authenticity_token skip_before_action :verify_authenticity_token
...@@ -15,32 +19,25 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -15,32 +19,25 @@ class Projects::GitHttpClientController < Projects::ApplicationController
private private
def authenticate_user def authenticate_user
@authentication_result = Gitlab::Auth::Result.new
if project && project.public? && download_request? if project && project.public? && download_request?
return # Allow access return # Allow access
end end
if allow_basic_auth? && basic_auth_provided? if allow_basic_auth? && basic_auth_provided?
login, password = user_name_and_password(request) login, password = user_name_and_password(request)
auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
if auth_result.type == :ci && download_request?
@ci = true
elsif auth_result.type == :oauth && !download_request?
# Not allowed
elsif auth_result.type == :missing_personal_token
render_missing_personal_token
return # Render above denied access, nothing left to do
else
@user = auth_result.user
end
if ci? || user if handle_basic_authentication(login, password)
return # Allow access return # Allow access
end end
elsif allow_kerberos_spnego_auth? && spnego_provided? elsif allow_kerberos_spnego_auth? && spnego_provided?
@user = find_kerberos_user user = find_kerberos_user
if user if user
@authentication_result = Gitlab::Auth::Result.new(
user, nil, :kerberos, Gitlab::Auth.full_authentication_abilities)
send_final_spnego_response send_final_spnego_response
return # Allow access return # Allow access
end end
...@@ -48,6 +45,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -48,6 +45,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController
send_challenges send_challenges
render plain: "HTTP Basic: Access denied\n", status: 401 render plain: "HTTP Basic: Access denied\n", status: 401
rescue Gitlab::Auth::MissingPersonalTokenError
render_missing_personal_token
end end
def basic_auth_provided? def basic_auth_provided?
...@@ -114,8 +113,41 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -114,8 +113,41 @@ class Projects::GitHttpClientController < Projects::ApplicationController
render plain: 'Not Found', status: :not_found render plain: 'Not Found', status: :not_found
end end
def handle_basic_authentication(login, password)
@authentication_result = Gitlab::Auth.find_for_git_client(
login, password, project: project, ip: request.ip)
return false unless @authentication_result.success?
if download_request?
authentication_has_download_access?
else
authentication_has_upload_access?
end
end
def ci? def ci?
@ci.present? authentication_result.ci?(project)
end
def lfs_deploy_token?
authentication_result.lfs_deploy_token?(project)
end
def authentication_has_download_access?
has_authentication_ability?(:download_code) || has_authentication_ability?(:build_download_code)
end
def authentication_has_upload_access?
has_authentication_ability?(:push_code)
end
def has_authentication_ability?(capability)
(authentication_abilities || []).include?(capability)
end
def authentication_project
authentication_result.project
end end
def verify_workhorse_api! def verify_workhorse_api!
......
...@@ -86,7 +86,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController ...@@ -86,7 +86,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
end end
def access def access
@access ||= Gitlab::GitAccess.new(user, project, 'http') @access ||= Gitlab::GitAccess.new(user, project, 'http', authentication_abilities: authentication_abilities)
end end
def access_check def access_check
......
...@@ -23,20 +23,13 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -23,20 +23,13 @@ class Projects::IssuesController < Projects::ApplicationController
respond_to :html respond_to :html
def index def index
terms = params['issue_search']
@issues = issues_collection @issues = issues_collection
if terms.present?
if terms =~ /\A#(\d+)\z/
@issues = @issues.where(iid: $1)
else
@issues = @issues.full_search(terms)
end
end
@issues = @issues.page(params[:page]) @issues = @issues.page(params[:page])
@labels = @project.labels.where(title: params[:label_name]) @labels = @project.labels.where(title: params[:label_name])
@all_issues = all_issues_collection
respond_to do |format| respond_to do |format|
format.html format.html
format.atom { render layout: false } format.atom { render layout: false }
......
...@@ -32,22 +32,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -32,22 +32,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :resolve_conflicts] before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :resolve_conflicts]
def index def index
terms = params['issue_search']
@merge_requests = merge_requests_collection @merge_requests = merge_requests_collection
if terms.present?
if terms =~ /\A[#!](\d+)\z/
@merge_requests = @merge_requests.where(iid: $1)
else
@merge_requests = @merge_requests.full_search(terms)
end
end
@merge_requests = @merge_requests.page(params[:page]) @merge_requests = @merge_requests.page(params[:page])
@merge_requests = @merge_requests.preload(:target_project) @merge_requests = @merge_requests.preload(:target_project)
@labels = @project.labels.where(title: params[:label_name]) @labels = @project.labels.where(title: params[:label_name])
@all_merge_requests = all_merge_requests_collection
respond_to do |format| respond_to do |format|
format.html format.html
format.json do format.json do
...@@ -468,6 +460,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -468,6 +460,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def validates_merge_request def validates_merge_request
# If source project was removed and merge request for some reason
# wasn't close (Ex. mr from fork to origin)
return invalid_mr if !@merge_request.source_project && @merge_request.open?
# Show git not found page # Show git not found page
# if there is no saved commits between source & target branch # if there is no saved commits between source & target branch
if @merge_request.commits.blank? if @merge_request.commits.blank?
......
...@@ -3,12 +3,19 @@ class SentNotificationsController < ApplicationController ...@@ -3,12 +3,19 @@ class SentNotificationsController < ApplicationController
def unsubscribe def unsubscribe
@sent_notification = SentNotification.for(params[:id]) @sent_notification = SentNotification.for(params[:id])
return render_404 unless @sent_notification && @sent_notification.unsubscribable? return render_404 unless @sent_notification && @sent_notification.unsubscribable?
return unsubscribe_and_redirect if current_user || params[:force]
end
private
def unsubscribe_and_redirect
noteable = @sent_notification.noteable noteable = @sent_notification.noteable
noteable.unsubscribe(@sent_notification.recipient) noteable.unsubscribe(@sent_notification.recipient)
flash[:notice] = "You have been unsubscribed from this thread." flash[:notice] = "You have been unsubscribed from this thread."
if current_user if current_user
case noteable case noteable
when Issue when Issue
......
...@@ -217,7 +217,14 @@ class IssuableFinder ...@@ -217,7 +217,14 @@ class IssuableFinder
end end
def by_search(items) def by_search(items)
items = items.search(search) if search if search
items =
if search =~ iid_pattern
items.where(iid: $~[:iid])
else
items.full_search(search)
end
end
items items
end end
......
...@@ -25,4 +25,8 @@ class IssuesFinder < IssuableFinder ...@@ -25,4 +25,8 @@ class IssuesFinder < IssuableFinder
def init_collection def init_collection
Issue.visible_to_user(current_user) Issue.visible_to_user(current_user)
end end
def iid_pattern
@iid_pattern ||= %r{\A#{Regexp.escape(Issue.reference_prefix)}(?<iid>\d+)\z}
end
end end
...@@ -19,4 +19,14 @@ class MergeRequestsFinder < IssuableFinder ...@@ -19,4 +19,14 @@ class MergeRequestsFinder < IssuableFinder
def klass def klass
MergeRequest MergeRequest
end end
private
def iid_pattern
@iid_pattern ||= %r{\A[
#{Regexp.escape(MergeRequest.reference_prefix)}
#{Regexp.escape(Issue.reference_prefix)}
](?<iid>\d+)\z
}x
end
end end
...@@ -252,7 +252,7 @@ module ApplicationHelper ...@@ -252,7 +252,7 @@ module ApplicationHelper
milestone_title: params[:milestone_title], milestone_title: params[:milestone_title],
assignee_id: params[:assignee_id], assignee_id: params[:assignee_id],
author_id: params[:author_id], author_id: params[:author_id],
issue_search: params[:issue_search], search: params[:search],
label_name: params[:label_name] label_name: params[:label_name]
} }
...@@ -283,23 +283,14 @@ module ApplicationHelper ...@@ -283,23 +283,14 @@ module ApplicationHelper
end end
end end
def state_filters_text_for(entity, project) def state_filters_text_for(state, records)
titles = { titles = {
opened: "Open" opened: "Open"
} }
entity_title = titles[entity] || entity.to_s.humanize state_title = titles[state] || state.to_s.humanize
count = records.public_send(state).size
count = html = content_tag :span, state_title
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.visible_to_user(current_user).send(entity).count
elsif current_controller?(:merge_requests)
project.merge_requests.send(entity).count
end
html = content_tag :span, entity_title
if count.present? if count.present?
html += " " html += " "
......
...@@ -25,13 +25,21 @@ module LfsHelper ...@@ -25,13 +25,21 @@ module LfsHelper
def lfs_download_access? def lfs_download_access?
return false unless project.lfs_enabled? return false unless project.lfs_enabled?
project.public? || ci? || (user && user.can?(:download_code, project)) project.public? || ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code?
end
def user_can_download_code?
has_authentication_ability?(:download_code) && can?(user, :download_code, project)
end
def build_can_download_code?
has_authentication_ability?(:build_download_code) && can?(user, :build_download_code, project)
end end
def lfs_upload_access? def lfs_upload_access?
return false unless project.lfs_enabled? return false unless project.lfs_enabled?
user && user.can?(:push_code, project) has_authentication_ability?(:push_code) && can?(user, :push_code, project)
end end
def render_lfs_forbidden def render_lfs_forbidden
......
module NamespacesHelper module NamespacesHelper
def namespaces_options(selected = :current_user, display_path: false) def namespaces_options(selected = :current_user, display_path: false, extra_group: nil)
groups = current_user.owned_groups + current_user.masters_groups groups = current_user.owned_groups + current_user.masters_groups
groups << extra_group if extra_group && !Group.exists?(name: extra_group.name)
users = [current_user.namespace] users = [current_user.namespace]
data_attr_group = { 'data-options-parent' => 'groups' } data_attr_group = { 'data-options-parent' => 'groups' }
......
...@@ -10,6 +10,10 @@ module NotesHelper ...@@ -10,6 +10,10 @@ module NotesHelper
Ability.can_edit_note?(current_user, note) Ability.can_edit_note?(current_user, note)
end end
def note_supports_slash_commands?(note)
Notes::SlashCommandsService.supported?(note, current_user)
end
def noteable_json(noteable) def noteable_json(noteable)
{ {
id: noteable.id, id: noteable.id,
......
...@@ -109,6 +109,12 @@ class Notify < BaseMailer ...@@ -109,6 +109,12 @@ class Notify < BaseMailer
headers["X-GitLab-#{model.class.name}-ID"] = model.id headers["X-GitLab-#{model.class.name}-ID"] = model.id
headers['X-GitLab-Reply-Key'] = reply_key headers['X-GitLab-Reply-Key'] = reply_key
if !@labels_url && @sent_notification && @sent_notification.unsubscribable?
headers['List-Unsubscribe'] = unsubscribe_sent_notification_url(@sent_notification, force: true)
@sent_notification_url = unsubscribe_sent_notification_url(@sent_notification)
end
if Gitlab::IncomingEmail.enabled? if Gitlab::IncomingEmail.enabled?
address = Mail::Address.new(Gitlab::IncomingEmail.reply_address(reply_key)) address = Mail::Address.new(Gitlab::IncomingEmail.reply_address(reply_key))
address.display_name = @project.name_with_namespace address.display_name = @project.name_with_namespace
......
module Ci module Ci
class Build < CommitStatus class Build < CommitStatus
include TokenAuthenticatable
belongs_to :runner, class_name: 'Ci::Runner' belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest' belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
belongs_to :erased_by, class_name: 'User' belongs_to :erased_by, class_name: 'User'
...@@ -23,7 +25,10 @@ module Ci ...@@ -23,7 +25,10 @@ module Ci
acts_as_taggable acts_as_taggable
add_authentication_token_field :token
before_save :update_artifacts_size, if: :artifacts_file_changed? before_save :update_artifacts_size, if: :artifacts_file_changed?
before_save :ensure_token
before_destroy { project } before_destroy { project }
after_create :execute_hooks after_create :execute_hooks
...@@ -38,6 +43,7 @@ module Ci ...@@ -38,6 +43,7 @@ module Ci
new_build.status = 'pending' new_build.status = 'pending'
new_build.runner_id = nil new_build.runner_id = nil
new_build.trigger_request_id = nil new_build.trigger_request_id = nil
new_build.token = nil
new_build.save new_build.save
end end
...@@ -79,11 +85,14 @@ module Ci ...@@ -79,11 +85,14 @@ module Ci
after_transition any => [:success] do |build| after_transition any => [:success] do |build|
if build.environment.present? if build.environment.present?
service = CreateDeploymentService.new(build.project, build.user, service = CreateDeploymentService.new(
environment: build.environment, build.project, build.user,
sha: build.sha, environment: build.environment,
ref: build.ref, sha: build.sha,
tag: build.tag) ref: build.ref,
tag: build.tag,
options: build.options[:environment],
variables: build.variables)
service.execute(build) service.execute(build)
end end
end end
...@@ -173,7 +182,7 @@ module Ci ...@@ -173,7 +182,7 @@ module Ci
end end
def repo_url def repo_url
auth = "gitlab-ci-token:#{token}@" auth = "gitlab-ci-token:#{ensure_token!}@"
project.http_url_to_repo.sub(/^https?:\/\//) do |prefix| project.http_url_to_repo.sub(/^https?:\/\//) do |prefix|
prefix + auth prefix + auth
end end
...@@ -235,12 +244,7 @@ module Ci ...@@ -235,12 +244,7 @@ module Ci
end end
def trace def trace
trace = raw_trace hide_secrets(raw_trace)
if project && trace.present? && project.runners_token.present?
trace.gsub(project.runners_token, 'xxxxxx')
else
trace
end
end end
def trace_length def trace_length
...@@ -253,6 +257,7 @@ module Ci ...@@ -253,6 +257,7 @@ module Ci
def trace=(trace) def trace=(trace)
recreate_trace_dir recreate_trace_dir
trace = hide_secrets(trace)
File.write(path_to_trace, trace) File.write(path_to_trace, trace)
end end
...@@ -266,6 +271,8 @@ module Ci ...@@ -266,6 +271,8 @@ module Ci
def append_trace(trace_part, offset) def append_trace(trace_part, offset)
recreate_trace_dir recreate_trace_dir
trace_part = hide_secrets(trace_part)
File.truncate(path_to_trace, offset) if File.exist?(path_to_trace) File.truncate(path_to_trace, offset) if File.exist?(path_to_trace)
File.open(path_to_trace, 'ab') do |f| File.open(path_to_trace, 'ab') do |f|
f.write(trace_part) f.write(trace_part)
...@@ -341,12 +348,8 @@ module Ci ...@@ -341,12 +348,8 @@ module Ci
) )
end end
def token
project.runners_token
end
def valid_token?(token) def valid_token?(token)
project.valid_runners_token?(token) self.token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
end end
def has_tags? def has_tags?
...@@ -489,5 +492,11 @@ module Ci ...@@ -489,5 +492,11 @@ module Ci
pipeline.config_processor.build_attributes(name) pipeline.config_processor.build_attributes(name)
end end
def hide_secrets(trace)
trace = Ci::MaskSecret.mask(trace, project.runners_token) if project
trace = Ci::MaskSecret.mask(trace, token)
trace
end
end end
end end
...@@ -2,6 +2,7 @@ module Ci ...@@ -2,6 +2,7 @@ module Ci
class Pipeline < ActiveRecord::Base class Pipeline < ActiveRecord::Base
extend Ci::Model extend Ci::Model
include HasStatus include HasStatus
include Importable
self.table_name = 'ci_commits' self.table_name = 'ci_commits'
...@@ -12,12 +13,12 @@ module Ci ...@@ -12,12 +13,12 @@ module Ci
has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id
validates_presence_of :sha validates_presence_of :sha, unless: :importing?
validates_presence_of :ref validates_presence_of :ref, unless: :importing?
validates_presence_of :status validates_presence_of :status, unless: :importing?
validate :valid_commit_sha validate :valid_commit_sha, unless: :importing?
after_save :keep_around_commits after_save :keep_around_commits, unless: :importing?
delegate :stages, to: :statuses delegate :stages, to: :statuses
...@@ -241,13 +242,16 @@ module Ci ...@@ -241,13 +242,16 @@ module Ci
end end
def build_updated def build_updated
case latest_builds_status with_lock do
when 'pending' then enqueue reload
when 'running' then run case latest_builds_status
when 'success' then succeed when 'pending' then enqueue
when 'failed' then drop when 'running' then run
when 'canceled' then cancel when 'success' then succeed
when 'skipped' then skip when 'failed' then drop
when 'canceled' then cancel
when 'skipped' then skip
end
end end
end end
......
...@@ -69,15 +69,15 @@ class CommitStatus < ActiveRecord::Base ...@@ -69,15 +69,15 @@ class CommitStatus < ActiveRecord::Base
commit_status.update_attributes finished_at: Time.now commit_status.update_attributes finished_at: Time.now
end end
after_transition do |commit_status, transition|
commit_status.pipeline.try(:build_updated) unless transition.loopback?
end
after_transition any => [:success, :failed, :canceled] do |commit_status| after_transition any => [:success, :failed, :canceled] do |commit_status|
commit_status.pipeline.try(:process!) commit_status.pipeline.try(:process!)
true true
end end
after_transition do |commit_status, transition|
commit_status.pipeline.try(:build_updated) unless transition.loopback?
end
after_transition [:created, :pending, :running] => :success do |commit_status| after_transition [:created, :pending, :running] => :success do |commit_status|
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status)
end end
......
...@@ -4,6 +4,7 @@ class Environment < ActiveRecord::Base ...@@ -4,6 +4,7 @@ class Environment < ActiveRecord::Base
has_many :deployments has_many :deployments
before_validation :nullify_external_url before_validation :nullify_external_url
before_save :set_environment_type
validates :name, validates :name,
presence: true, presence: true,
...@@ -26,6 +27,17 @@ class Environment < ActiveRecord::Base ...@@ -26,6 +27,17 @@ class Environment < ActiveRecord::Base
self.external_url = nil if self.external_url.blank? self.external_url = nil if self.external_url.blank?
end end
def set_environment_type
names = name.split('/')
self.environment_type =
if names.many?
names.first
else
nil
end
end
def includes_commit?(commit) def includes_commit?(commit)
return false unless last_deployment return false unless last_deployment
......
...@@ -13,6 +13,8 @@ class Event < ActiveRecord::Base ...@@ -13,6 +13,8 @@ class Event < ActiveRecord::Base
LEFT = 9 # User left project LEFT = 9 # User left project
DESTROYED = 10 DESTROYED = 10
RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour
delegate :name, :email, to: :author, prefix: true, allow_nil: true delegate :name, :email, to: :author, prefix: true, allow_nil: true
delegate :title, to: :issue, prefix: true, allow_nil: true delegate :title, to: :issue, prefix: true, allow_nil: true
delegate :title, to: :merge_request, prefix: true, allow_nil: true delegate :title, to: :merge_request, prefix: true, allow_nil: true
...@@ -329,8 +331,27 @@ class Event < ActiveRecord::Base ...@@ -329,8 +331,27 @@ class Event < ActiveRecord::Base
end end
def reset_project_activity def reset_project_activity
if project && Gitlab::ExclusiveLease.new("project:update_last_activity_at:#{project.id}", timeout: 60).try_obtain return unless project
project.update_column(:last_activity_at, self.created_at)
end # Don't even bother obtaining a lock if the last update happened less than
# 60 minutes ago.
return if recent_update?
return unless try_obtain_lease
project.update_column(:last_activity_at, created_at)
end
private
def recent_update?
project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago
end
def try_obtain_lease
Gitlab::ExclusiveLease.
new("project:update_last_activity_at:#{project.id}",
timeout: RESET_PROJECT_ACTIVITY_INTERVAL.to_i).
try_obtain
end end
end end
...@@ -1267,12 +1267,6 @@ class Project < ActiveRecord::Base ...@@ -1267,12 +1267,6 @@ class Project < ActiveRecord::Base
self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token) self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
end end
# TODO (ayufan): For now we use runners_token (backward compatibility)
# In 8.4 every build will have its own individual token valid for time of build
def valid_build_token?(token)
self.builds_enabled? && self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
end
def build_coverage_enabled? def build_coverage_enabled?
build_coverage_regex.present? build_coverage_regex.present?
end end
......
...@@ -840,62 +840,59 @@ class Repository ...@@ -840,62 +840,59 @@ class Repository
@root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref } @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
end end
def commit_dir(user, path, message, branch) def commit_dir(user, path, message, branch, author_email: nil, author_name: nil)
update_branch_with_hooks(user, branch) do |ref| update_branch_with_hooks(user, branch) do |ref|
committer = user_to_committer(user) options = {
options = {} commit: {
options[:committer] = committer branch: ref,
options[:author] = committer message: message,
update_ref: false
options[:commit] = { }
message: message,
branch: ref,
update_ref: false,
} }
options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
raw_repository.mkdir(path, options) raw_repository.mkdir(path, options)
end end
end end
def commit_file(user, path, content, message, branch, update) def commit_file(user, path, content, message, branch, update, author_email: nil, author_name: nil)
update_branch_with_hooks(user, branch) do |ref| update_branch_with_hooks(user, branch) do |ref|
committer = user_to_committer(user) options = {
options = {} commit: {
options[:committer] = committer branch: ref,
options[:author] = committer message: message,
options[:commit] = { update_ref: false
message: message, },
branch: ref, file: {
update_ref: false, content: content,
path: path,
update: update
}
} }
options[:file] = { options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
content: content,
path: path,
update: update
}
Gitlab::Git::Blob.commit(raw_repository, options) Gitlab::Git::Blob.commit(raw_repository, options)
end end
end end
def update_file(user, path, content, branch:, previous_path:, message:) def update_file(user, path, content, branch:, previous_path:, message:, author_email: nil, author_name: nil)
update_branch_with_hooks(user, branch) do |ref| update_branch_with_hooks(user, branch) do |ref|
committer = user_to_committer(user) options = {
options = {} commit: {
options[:committer] = committer branch: ref,
options[:author] = committer message: message,
options[:commit] = { update_ref: false
message: message, },
branch: ref, file: {
update_ref: false content: content,
path: path,
update: true
}
} }
options[:file] = { options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
content: content,
path: path,
update: true
}
if previous_path && previous_path != path if previous_path && previous_path != path
options[:file][:previous_path] = previous_path options[:file][:previous_path] = previous_path
...@@ -906,34 +903,39 @@ class Repository ...@@ -906,34 +903,39 @@ class Repository
end end
end end
def remove_file(user, path, message, branch) def remove_file(user, path, message, branch, author_email: nil, author_name: nil)
update_branch_with_hooks(user, branch) do |ref| update_branch_with_hooks(user, branch) do |ref|
committer = user_to_committer(user) options = {
options = {} commit: {
options[:committer] = committer branch: ref,
options[:author] = committer message: message,
options[:commit] = { update_ref: false
message: message, },
branch: ref, file: {
update_ref: false, path: path
}
} }
options[:file] = { options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
path: path
}
Gitlab::Git::Blob.remove(raw_repository, options) Gitlab::Git::Blob.remove(raw_repository, options)
end end
end end
def user_to_committer(user) def get_committer_and_author(user, email: nil, name: nil)
committer = user_to_committer(user)
author = name && email ? Gitlab::Git::committer_hash(email: email, name: name) : committer
{ {
email: user.email, author: author,
name: user.name, committer: committer
time: Time.now
} }
end end
def user_to_committer(user)
Gitlab::Git::committer_hash(email: user.email, name: user.name)
end
def can_be_merged?(source_sha, target_branch) def can_be_merged?(source_sha, target_branch)
our_commit = rugged.branches[target_branch].target our_commit = rugged.branches[target_branch].target
their_commit = rugged.lookup(source_sha) their_commit = rugged.lookup(source_sha)
......
...@@ -67,6 +67,12 @@ class ProjectPolicy < BasePolicy ...@@ -67,6 +67,12 @@ class ProjectPolicy < BasePolicy
can! :read_deployment can! :read_deployment
end end
# Permissions given when an user is team member of a project
def team_member_reporter_access!
can! :build_download_code
can! :build_read_container_image
end
def developer_access! def developer_access!
can! :admin_merge_request can! :admin_merge_request
can! :update_merge_request can! :update_merge_request
...@@ -118,6 +124,8 @@ class ProjectPolicy < BasePolicy ...@@ -118,6 +124,8 @@ class ProjectPolicy < BasePolicy
can! :read_commit_status can! :read_commit_status
can! :read_pipeline can! :read_pipeline
can! :read_container_image can! :read_container_image
can! :build_download_code
can! :build_read_container_image
end end
def owner_access! def owner_access!
...@@ -142,10 +150,11 @@ class ProjectPolicy < BasePolicy ...@@ -142,10 +150,11 @@ class ProjectPolicy < BasePolicy
def team_access!(user) def team_access!(user)
access = project.team.max_member_access(user.id) access = project.team.max_member_access(user.id)
guest_access! if access >= Gitlab::Access::GUEST guest_access! if access >= Gitlab::Access::GUEST
reporter_access! if access >= Gitlab::Access::REPORTER reporter_access! if access >= Gitlab::Access::REPORTER
developer_access! if access >= Gitlab::Access::DEVELOPER team_member_reporter_access! if access >= Gitlab::Access::REPORTER
master_access! if access >= Gitlab::Access::MASTER developer_access! if access >= Gitlab::Access::DEVELOPER
master_access! if access >= Gitlab::Access::MASTER
end end
def archived_access! def archived_access!
......
...@@ -4,7 +4,9 @@ module Auth ...@@ -4,7 +4,9 @@ module Auth
AUDIENCE = 'container_registry' AUDIENCE = 'container_registry'
def execute def execute(authentication_abilities:)
@authentication_abilities = authentication_abilities || []
return error('not found', 404) unless registry.enabled return error('not found', 404) unless registry.enabled
unless current_user || project unless current_user || project
...@@ -74,9 +76,9 @@ module Auth ...@@ -74,9 +76,9 @@ module Auth
case requested_action case requested_action
when 'pull' when 'pull'
requested_project == project || can?(current_user, :read_container_image, requested_project) requested_project.public? || build_can_pull?(requested_project) || user_can_pull?(requested_project)
when 'push' when 'push'
requested_project == project || can?(current_user, :create_container_image, requested_project) build_can_push?(requested_project) || user_can_push?(requested_project)
else else
false false
end end
...@@ -85,5 +87,29 @@ module Auth ...@@ -85,5 +87,29 @@ module Auth
def registry def registry
Gitlab.config.registry Gitlab.config.registry
end end
def build_can_pull?(requested_project)
# Build can:
# 1. pull from its own project (for ex. a build)
# 2. read images from dependent projects if creator of build is a team member
@authentication_abilities.include?(:build_read_container_image) &&
(requested_project == project || can?(current_user, :build_read_container_image, requested_project))
end
def user_can_pull?(requested_project)
@authentication_abilities.include?(:read_container_image) &&
can?(current_user, :read_container_image, requested_project)
end
def build_can_push?(requested_project)
# Build can push only to the project from which it originates
@authentication_abilities.include?(:build_create_container_image) &&
requested_project == project
end
def user_can_push?(requested_project)
@authentication_abilities.include?(:create_container_image) &&
can?(current_user, :create_container_image, requested_project)
end
end end
end end
...@@ -2,9 +2,7 @@ require_relative 'base_service' ...@@ -2,9 +2,7 @@ require_relative 'base_service'
class CreateDeploymentService < BaseService class CreateDeploymentService < BaseService
def execute(deployable = nil) def execute(deployable = nil)
environment = project.environments.find_or_create_by( environment = find_or_create_environment
name: params[:environment]
)
project.deployments.create( project.deployments.create(
environment: environment, environment: environment,
...@@ -15,4 +13,38 @@ class CreateDeploymentService < BaseService ...@@ -15,4 +13,38 @@ class CreateDeploymentService < BaseService
deployable: deployable deployable: deployable
) )
end end
private
def find_or_create_environment
project.environments.find_or_create_by(name: expanded_name) do |environment|
environment.external_url = expanded_url
end
end
def expanded_name
ExpandVariables.expand(name, variables)
end
def expanded_url
return unless url
@expanded_url ||= ExpandVariables.expand(url, variables)
end
def name
params[:environment]
end
def url
options[:url]
end
def options
params[:options] || {}
end
def variables
params[:variables] || []
end
end end
...@@ -16,6 +16,8 @@ module Files ...@@ -16,6 +16,8 @@ module Files
params[:file_content] params[:file_content]
end end
@last_commit_sha = params[:last_commit_sha] @last_commit_sha = params[:last_commit_sha]
@author_email = params[:author_email]
@author_name = params[:author_name]
# Validate parameters # Validate parameters
validate validate
......
...@@ -3,7 +3,7 @@ require_relative "base_service" ...@@ -3,7 +3,7 @@ require_relative "base_service"
module Files module Files
class CreateDirService < Files::BaseService class CreateDirService < Files::BaseService
def commit def commit
repository.commit_dir(current_user, @file_path, @commit_message, @target_branch) repository.commit_dir(current_user, @file_path, @commit_message, @target_branch, author_email: @author_email, author_name: @author_name)
end end
def validate def validate
......
...@@ -3,7 +3,7 @@ require_relative "base_service" ...@@ -3,7 +3,7 @@ require_relative "base_service"
module Files module Files
class CreateService < Files::BaseService class CreateService < Files::BaseService
def commit def commit
repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch, false) repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch, false, author_email: @author_email, author_name: @author_name)
end end
def validate def validate
......
...@@ -3,7 +3,7 @@ require_relative "base_service" ...@@ -3,7 +3,7 @@ require_relative "base_service"
module Files module Files
class DeleteService < Files::BaseService class DeleteService < Files::BaseService
def commit def commit
repository.remove_file(current_user, @file_path, @commit_message, @target_branch) repository.remove_file(current_user, @file_path, @commit_message, @target_branch, author_email: @author_email, author_name: @author_name)
end end
end end
end end
...@@ -8,7 +8,9 @@ module Files ...@@ -8,7 +8,9 @@ module Files
repository.update_file(current_user, @file_path, @file_content, repository.update_file(current_user, @file_path, @file_content,
branch: @target_branch, branch: @target_branch,
previous_path: @previous_path, previous_path: @previous_path,
message: @commit_message) message: @commit_message,
author_email: @author_email,
author_name: @author_name)
end end
private private
......
...@@ -92,7 +92,7 @@ class GitPushService < BaseService ...@@ -92,7 +92,7 @@ class GitPushService < BaseService
project.change_head(branch_name) project.change_head(branch_name)
# Set protection on the default branch if configured # Set protection on the default branch if configured
if current_application_settings.default_branch_protection != PROTECTION_NONE if current_application_settings.default_branch_protection != PROTECTION_NONE && !@project.protected_branch?(@project.default_branch)
params = { params = {
name: @project.default_branch, name: @project.default_branch,
......
...@@ -3,7 +3,7 @@ module Milestones ...@@ -3,7 +3,7 @@ module Milestones
def execute def execute
milestone = project.milestones.new(params) milestone = project.milestones.new(params)
if milestone.save! if milestone.save
event_service.open_milestone(milestone, current_user) event_service.open_milestone(milestone, current_user)
end end
......
...@@ -5,9 +5,18 @@ module Notes ...@@ -5,9 +5,18 @@ module Notes
'MergeRequest' => MergeRequests::UpdateService 'MergeRequest' => MergeRequests::UpdateService
} }
def supported?(note) def self.noteable_update_service(note)
UPDATE_SERVICES[note.noteable_type]
end
def self.supported?(note, current_user)
noteable_update_service(note) && noteable_update_service(note) &&
can?(current_user, :"update_#{note.noteable_type.underscore}", note.noteable) current_user &&
current_user.can?(:"update_#{note.noteable_type.underscore}", note.noteable)
end
def supported?(note)
self.class.supported?(note, current_user)
end end
def extract_commands(note) def extract_commands(note)
...@@ -21,13 +30,7 @@ module Notes ...@@ -21,13 +30,7 @@ module Notes
return if command_params.empty? return if command_params.empty?
return unless supported?(note) return unless supported?(note)
noteable_update_service(note).new(project, current_user, command_params).execute(note.noteable) self.class.noteable_update_service(note).new(project, current_user, command_params).execute(note.noteable)
end
private
def noteable_update_service(note)
UPDATE_SERVICES[note.noteable_type]
end end
end end
end end
- diff_notes_disabled = (@merge_request_diff.latest? && !!@start_sha) if @merge_request_diff
- discussion = local_assigns.fetch(:discussion, nil) - discussion = local_assigns.fetch(:discussion, nil)
- if current_user - if current_user
%jump-to-discussion{ "inline-template" => true, ":discussion-id" => "'#{discussion.try(:id)}'" } %jump-to-discussion{ "inline-template" => true, ":discussion-id" => "'#{discussion.try(:id)}'" }
...@@ -5,5 +6,6 @@ ...@@ -5,5 +6,6 @@
%button.btn.btn-default.discussion-next-btn.has-tooltip{ "@click" => "jumpToNextUnresolvedDiscussion", %button.btn.btn-default.discussion-next-btn.has-tooltip{ "@click" => "jumpToNextUnresolvedDiscussion",
title: "Jump to next unresolved discussion", title: "Jump to next unresolved discussion",
"aria-label" => "Jump to next unresolved discussion", "aria-label" => "Jump to next unresolved discussion",
data: { container: "body" } } data: { container: "body" },
disabled: diff_notes_disabled }
= custom_icon("next_discussion") = custom_icon("next_discussion")
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
.btn-group{ role: "group" } .btn-group{ role: "group" }
= link_to_reply_discussion(discussion, line_type) = link_to_reply_discussion(discussion, line_type)
= render "discussions/resolve_all", discussion: discussion = render "discussions/resolve_all", discussion: discussion
= render "discussions/jump_to_next", discussion: discussion - if discussion.for_merge_request?
= render "discussions/jump_to_next", discussion: discussion
- else - else
= link_to_reply_discussion(discussion) = link_to_reply_discussion(discussion)
...@@ -45,7 +45,17 @@ ...@@ -45,7 +45,17 @@
%td %td
= github_project_link(repo.full_name) = github_project_link(repo.full_name)
%td.import-target %td.import-target
= import_project_target(repo.owner.login, repo.name) %fieldset.row
.input-group
.project-path.input-group-btn
- if current_user.can_select_namespace?
- selected = params[:namespace_id] || :current_user
- opts = current_user.can_create_group? ? { extra_group: Group.new(name: repo.owner.login, path: repo.owner.login) } : {}
= select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'select2 js-select-namespace', tabindex: 1 }
- else
= text_field_tag :path, current_user.namespace_path, class: "input-large form-control", tabindex: 1, disabled: true
%span.input-group-addon /
= text_field_tag :path, repo.name, class: "input-mini form-control", tabindex: 2, autofocus: true, required: true
%td.import-actions.job-status %td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do = button_tag class: "btn btn-import js-add-to-import" do
Import Import
......
...@@ -25,8 +25,8 @@ ...@@ -25,8 +25,8 @@
- if @labels_url - if @labels_url
adjust your #{link_to 'label subscriptions', @labels_url}. adjust your #{link_to 'label subscriptions', @labels_url}.
- else - else
- if @sent_notification && @sent_notification.unsubscribable? - if @sent_notification_url
= link_to "unsubscribe", unsubscribe_sent_notification_url(@sent_notification) = link_to "unsubscribe", @sent_notification_url
from this thread or from this thread or
adjust your notification settings. adjust your notification settings.
......
...@@ -112,14 +112,14 @@ ...@@ -112,14 +112,14 @@
%span.label.label-primary %span.label.label-primary
= tag = tag
- if builds.size > 1 - if @build.pipeline.stages.many?
.dropdown.build-dropdown .dropdown.build-dropdown
.title Stage .title Stage
%button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'}
%span.stage-selection More %span.stage-selection More
= icon('caret-down') = icon('caret-down')
%ul.dropdown-menu %ul.dropdown-menu
- builds.map(&:stage).uniq.each do |stage| - @build.pipeline.stages.each do |stage|
%li %li
%a.stage-item= stage %a.stage-item= stage
......
...@@ -37,6 +37,6 @@ ...@@ -37,6 +37,6 @@
%li.dropdown-header Previous Artifacts %li.dropdown-header Previous Artifacts
- artifacts.each do |job| - artifacts.each do |job|
%li %li
= link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{ref}/download", job: job.name), rel: 'nofollow' do
%i.fa.fa-download %i.fa.fa-download
%span Download '#{job.name}' %span Download '#{job.name}'
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
- unless diff_file.submodule? - unless diff_file.submodule?
.file-actions.hidden-xs .file-actions.hidden-xs
- if blob_text_viewable?(blob) - if blob_text_viewable?(blob)
= link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file" do = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this files", disabled: @diff_notes_disabled do
= icon('comment') = icon('comment')
\ \
......
- if @merge_request_diffs.size > 1 - if @merge_request_diffs.size > 1
.mr-version-controls .mr-version-controls
Changes between %div.mr-version-menus-container.content-block
%span.dropdown.inline.mr-version-dropdown Changes between
%a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} } %span.dropdown.inline.mr-version-dropdown
%strong %a.dropdown-toggle.btn.btn-default{ data: {toggle: :dropdown} }
- if @merge_request_diff.latest? %span
latest version - if @merge_request_diff.latest?
- else latest version
version #{version_index(@merge_request_diff)}
%span.caret
%ul.dropdown-menu.dropdown-menu-selectable
- @merge_request_diffs.each do |merge_request_diff|
%li
= link_to merge_request_version_path(@project, @merge_request, merge_request_diff), class: ('is-active' if merge_request_diff == @merge_request_diff) do
%strong
- if merge_request_diff.latest?
latest version
- else
version #{version_index(merge_request_diff)}
.monospace #{short_sha(merge_request_diff.head_commit_sha)}
%small
#{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)},
= time_ago_with_tooltip(merge_request_diff.created_at)
- if @merge_request_diff.base_commit_sha
and
%span.dropdown.inline.mr-version-compare-dropdown
%a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} }
%strong
- if @start_sha
version #{version_index(@start_version)}
- else - else
#{@merge_request.target_branch} version #{version_index(@merge_request_diff)}
%span.caret %span.caret
%ul.dropdown-menu.dropdown-menu-selectable %ul.dropdown-menu.dropdown-menu-selectable
- @comparable_diffs.each do |merge_request_diff| .dropdown-title
%span Version:
%button.dropdown-title-button.dropdown-menu-close
%i.fa.fa-times.dropdown-menu-close-icon
- @merge_request_diffs.each do |merge_request_diff|
%li %li
= link_to merge_request_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff == @start_version) do = link_to merge_request_version_path(@project, @merge_request, merge_request_diff), class: ('is-active' if merge_request_diff == @merge_request_diff) do
%strong %strong
- if merge_request_diff.latest? - if merge_request_diff.latest?
latest version latest version
...@@ -44,17 +25,46 @@ ...@@ -44,17 +25,46 @@
version #{version_index(merge_request_diff)} version #{version_index(merge_request_diff)}
.monospace #{short_sha(merge_request_diff.head_commit_sha)} .monospace #{short_sha(merge_request_diff.head_commit_sha)}
%small %small
#{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)},
= time_ago_with_tooltip(merge_request_diff.created_at) = time_ago_with_tooltip(merge_request_diff.created_at)
%li
= link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do - if @merge_request_diff.base_commit_sha
%strong and
#{@merge_request.target_branch} (base) %span.dropdown.inline.mr-version-compare-dropdown
.monospace #{short_sha(@merge_request_diff.base_commit_sha)} %a.btn.btn-default.dropdown-toggle{ data: {toggle: :dropdown} }
%span
- if @start_sha
version #{version_index(@start_version)}
- else
#{@merge_request.target_branch}
%span.caret
%ul.dropdown-menu.dropdown-menu-selectable
.dropdown-title
%span Compared with:
%button.dropdown-title-button.dropdown-menu-close
%i.fa.fa-times.dropdown-menu-close-icon
- @comparable_diffs.each do |merge_request_diff|
%li
= link_to merge_request_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff == @start_version) do
%strong
- if merge_request_diff.latest?
latest version
- else
version #{version_index(merge_request_diff)}
.monospace #{short_sha(merge_request_diff.head_commit_sha)}
%small
= time_ago_with_tooltip(merge_request_diff.created_at)
%li
= link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do
%strong
#{@merge_request.target_branch} (base)
.monospace #{short_sha(@merge_request_diff.base_commit_sha)}
- unless @merge_request_diff.latest? && !@start_sha - unless @merge_request_diff.latest? && !@start_sha
.prepend-top-10 .comments-disabled-notif.content-block
= icon('info-circle') = icon('info-circle')
- if @start_sha - if @start_sha
Comments are disabled because you're comparing two versions of this merge request. Comments are disabled because you're comparing two versions of this merge request.
- else - else
Comments are disabled because you're viewing an old version of this merge request. Comments are disabled because you're viewing an old version of this merge request.
= link_to 'Show latest version', merge_request_version_path(@project, @merge_request, @merge_request_diff), class: 'btn btn-sm'
- supports_slash_commands = note_supports_slash_commands?(@note)
= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form", "data-noteable-iid" => @note.noteable.try(:iid), }, authenticity_token: true do |f| = form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form", "data-noteable-iid" => @note.noteable.try(:iid), }, authenticity_token: true do |f|
= hidden_field_tag :view, diff_view = hidden_field_tag :view, diff_view
= hidden_field_tag :line_type = hidden_field_tag :line_type
...@@ -14,8 +16,8 @@ ...@@ -14,8 +16,8 @@
attr: :note, attr: :note,
classes: 'note-textarea js-note-text', classes: 'note-textarea js-note-text',
placeholder: "Write a comment or drag your files here...", placeholder: "Write a comment or drag your files here...",
supports_slash_commands: true supports_slash_commands: supports_slash_commands
= render 'projects/notes/hints', supports_slash_commands: true = render 'projects/notes/hints', supports_slash_commands: supports_slash_commands
.error-alert .error-alert
.note-form-actions.clearfix .note-form-actions.clearfix
......
- noteable = @sent_notification.noteable
- noteable_type = @sent_notification.noteable_type.humanize(capitalize: false)
- noteable_text = %(#{noteable.title} (#{noteable.to_reference}))
- page_title "Unsubscribe", noteable_text, @sent_notification.noteable_type.humanize.pluralize, @sent_notification.project.name_with_namespace
%h3.page-title
Unsubscribe from #{noteable_type} #{noteable_text}
%p
= succeed '?' do
Are you sure you want to unsubscribe from #{noteable_type}
= link_to noteable_text, url_for([@sent_notification.project.namespace.becomes(Namespace), @sent_notification.project, noteable])
%p
= link_to 'Unsubscribe', unsubscribe_sent_notification_path(@sent_notification, force: true),
class: 'btn btn-primary append-right-10'
= link_to 'Cancel', new_user_session_path, class: 'btn append-right-10'
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
.issues-filters .issues-filters
.issues-details-filters.row-content-block.second-block .issues-details-filters.row-content-block.second-block
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :issue_search]), method: :get, class: 'filter-form js-filter-form' do = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do
- if params[:issue_search].present? - if params[:search].present?
= hidden_field_tag :issue_search, params[:issue_search] = hidden_field_tag :search, params[:search]
- if @bulk_edit - if @bulk_edit
.check-all-holder .check-all-holder
= check_box_tag "check_all_issues", nil, false, = check_box_tag "check_all_issues", nil, false,
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
= weight = weight
.filter-item.inline.reset-filters .filter-item.inline.reset-filters
%a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :issue_search])} Reset filters %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters
.pull-right .pull-right
- if boards_page - if boards_page
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
- if params[:label_name].present? - if params[:label_name].present?
- if params[:label_name].respond_to?('any?') - if params[:label_name].respond_to?('any?')
- params[:label_name].each do |label| - params[:label_name].each do |label|
= hidden_field_tag "label_name[]", u(label), id: nil = hidden_field_tag "label_name[]", label, id: nil
.dropdown .dropdown
%button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data}
%span.dropdown-toggle-text %span.dropdown-toggle-text
......
%ul.nav-links.issues-state-filters %ul.nav-links.issues-state-filters
- if defined?(type) && type == :merge_requests - if defined?(type) && type == :merge_requests
- page_context_word = 'merge requests' - page_context_word = 'merge requests'
- records = @all_merge_requests
- else - else
- page_context_word = 'issues' - page_context_word = 'issues'
- records = @all_issues
%li{class: ("active" if params[:state] == 'opened')} %li{class: ("active" if params[:state] == 'opened')}
= link_to page_filter_path(state: 'opened', label: true), title: "Filter by #{page_context_word} that are currently opened." do = link_to page_filter_path(state: 'opened', label: true), title: "Filter by #{page_context_word} that are currently opened." do
#{state_filters_text_for(:opened, @project)} #{state_filters_text_for(:opened, records)}
- if defined?(type) && type == :merge_requests - if defined?(type) && type == :merge_requests
%li{class: ("active" if params[:state] == 'merged')} %li{class: ("active" if params[:state] == 'merged')}
= link_to page_filter_path(state: 'merged', label: true), title: 'Filter by merge requests that are currently merged.' do = link_to page_filter_path(state: 'merged', label: true), title: 'Filter by merge requests that are currently merged.' do
#{state_filters_text_for(:merged, @project)} #{state_filters_text_for(:merged, records)}
%li{class: ("active" if params[:state] == 'closed')} %li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed', label: true), title: 'Filter by merge requests that are currently closed and unmerged.' do = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by merge requests that are currently closed and unmerged.' do
#{state_filters_text_for(:closed, @project)} #{state_filters_text_for(:closed, records)}
- else - else
%li{class: ("active" if params[:state] == 'closed')} %li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed', label: true), title: 'Filter by issues that are currently closed.' do = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by issues that are currently closed.' do
#{state_filters_text_for(:closed, @project)} #{state_filters_text_for(:closed, records)}
%li{class: ("active" if params[:state] == 'all')} %li{class: ("active" if params[:state] == 'all')}
= link_to page_filter_path(state: 'all', label: true), title: "Show all #{page_context_word}." do = link_to page_filter_path(state: 'all', label: true), title: "Show all #{page_context_word}." do
#{state_filters_text_for(:all, @project)} #{state_filters_text_for(:all, records)}
= form_tag(path, method: :get, id: "issue_search_form", class: 'issue-search-form') do = form_tag(path, method: :get, id: "issuable_search_form", class: 'issuable-search-form') do
= search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by name ...', class: 'form-control issue_search search-text-input input-short', spellcheck: false } = search_field_tag :search, params[:search], { id: 'issuable_search', placeholder: 'Filter by name ...', class: 'form-control issuable_search search-text-input input-short', spellcheck: false }
...@@ -119,6 +119,10 @@ module Gitlab ...@@ -119,6 +119,10 @@ module Gitlab
redis_config_hash = Gitlab::Redis.params redis_config_hash = Gitlab::Redis.params
redis_config_hash[:namespace] = Gitlab::Redis::CACHE_NAMESPACE redis_config_hash[:namespace] = Gitlab::Redis::CACHE_NAMESPACE
redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever
if Sidekiq.server? # threaded context
redis_config_hash[:pool_size] = Sidekiq.options[:concurrency] + 5
redis_config_hash[:pool_timeout] = 1
end
config.cache_store = :redis_store, redis_config_hash config.cache_store = :redis_store, redis_config_hash
config.active_record.raise_in_transactional_callbacks = true config.active_record.raise_in_transactional_callbacks = true
......
class AddTokenToBuild < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
add_column :ci_builds, :token, :string
end
end
class AddIndexForBuildToken < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
disable_ddl_transaction!
def change
add_concurrent_index :ci_builds, :token, unique: true
end
end
class AddEnvironmentTypeToEnvironments < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :environments, :environment_type, :string
end
end
...@@ -207,6 +207,7 @@ ActiveRecord::Schema.define(version: 20160915201649) do ...@@ -207,6 +207,7 @@ ActiveRecord::Schema.define(version: 20160915201649) do
t.string "when" t.string "when"
t.text "yaml_variables" t.text "yaml_variables"
t.datetime "queued_at" t.datetime "queued_at"
t.string "token"
end end
add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
...@@ -218,6 +219,7 @@ ActiveRecord::Schema.define(version: 20160915201649) do ...@@ -218,6 +219,7 @@ ActiveRecord::Schema.define(version: 20160915201649) do
add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree
add_index "ci_builds", ["token"], name: "index_ci_builds_on_token", unique: true, using: :btree
create_table "ci_commits", force: :cascade do |t| create_table "ci_commits", force: :cascade do |t|
t.integer "project_id" t.integer "project_id"
...@@ -416,10 +418,11 @@ ActiveRecord::Schema.define(version: 20160915201649) do ...@@ -416,10 +418,11 @@ ActiveRecord::Schema.define(version: 20160915201649) do
create_table "environments", force: :cascade do |t| create_table "environments", force: :cascade do |t|
t.integer "project_id" t.integer "project_id"
t.string "name", null: false t.string "name", null: false
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.string "external_url" t.string "external_url"
t.string "environment_type"
end end
add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", using: :btree add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", using: :btree
......
...@@ -27,8 +27,8 @@ following locations: ...@@ -27,8 +27,8 @@ following locations:
- [Milestones](milestones.md) - [Milestones](milestones.md)
- [Namespaces](namespaces.md) - [Namespaces](namespaces.md)
- [Notes](notes.md) (comments) - [Notes](notes.md) (comments)
- [Open source license templates](licenses.md)
- [Notification settings](notification_settings.md) - [Notification settings](notification_settings.md)
- [Open source license templates](licenses.md)
- [Pipelines](pipelines.md) - [Pipelines](pipelines.md)
- [Projects](projects.md) including setting Webhooks - [Projects](projects.md) including setting Webhooks
- [Project Access Requests](access_requests.md) - [Project Access Requests](access_requests.md)
...@@ -56,11 +56,12 @@ The following documentation is for the [internal CI API](ci/README.md): ...@@ -56,11 +56,12 @@ The following documentation is for the [internal CI API](ci/README.md):
## Authentication ## Authentication
All API requests require authentication via a token. There are three types of tokens All API requests require authentication via a session cookie or token. There are
available: private tokens, OAuth 2 tokens, and personal access tokens. three types of tokens available: private tokens, OAuth 2 tokens, and personal
access tokens.
If a token is invalid or omitted, an error message will be returned with If authentication information is invalid or omitted, an error message will be
status code `401`: returned with status code `401`:
```json ```json
{ {
...@@ -99,6 +100,13 @@ that needs access to the GitLab API. ...@@ -99,6 +100,13 @@ that needs access to the GitLab API.
Once you have your token, pass it to the API using either the `private_token` Once you have your token, pass it to the API using either the `private_token`
parameter or the `PRIVATE-TOKEN` header. parameter or the `PRIVATE-TOKEN` header.
### Session cookie
When signing in to GitLab as an ordinary user, a `_gitlab_session` cookie is
set. The API will use this cookie for authentication if it is present, but using
the API to generate a new session cookie is currently not supported.
## Basic Usage ## Basic Usage
API requests should be prefixed with `api` and the API version. The API version API requests should be prefixed with `api` and the API version. The API version
......
...@@ -83,7 +83,8 @@ Parameters: ...@@ -83,7 +83,8 @@ Parameters:
"forks_count": 0, "forks_count": 0,
"open_issues_count": 3, "open_issues_count": 3,
"public_builds": true, "public_builds": true,
"shared_with_groups": [] "shared_with_groups": [],
"request_access_enabled": false
} }
] ]
``` ```
...@@ -117,6 +118,7 @@ Example response: ...@@ -117,6 +118,7 @@ Example response:
"visibility_level": 20, "visibility_level": 20,
"avatar_url": null, "avatar_url": null,
"web_url": "https://gitlab.example.com/groups/twitter", "web_url": "https://gitlab.example.com/groups/twitter",
"request_access_enabled": false,
"projects": [ "projects": [
{ {
"id": 7, "id": 7,
...@@ -162,7 +164,8 @@ Example response: ...@@ -162,7 +164,8 @@ Example response:
"forks_count": 0, "forks_count": 0,
"open_issues_count": 3, "open_issues_count": 3,
"public_builds": true, "public_builds": true,
"shared_with_groups": [] "shared_with_groups": [],
"request_access_enabled": false
}, },
{ {
"id": 6, "id": 6,
...@@ -208,7 +211,8 @@ Example response: ...@@ -208,7 +211,8 @@ Example response:
"forks_count": 0, "forks_count": 0,
"open_issues_count": 8, "open_issues_count": 8,
"public_builds": true, "public_builds": true,
"shared_with_groups": [] "shared_with_groups": [],
"request_access_enabled": false
} }
], ],
"shared_projects": [ "shared_projects": [
...@@ -306,6 +310,7 @@ Parameters: ...@@ -306,6 +310,7 @@ Parameters:
- `share_with_group_lock` (optional, boolean) - Prevent sharing a project with another group within this group - `share_with_group_lock` (optional, boolean) - Prevent sharing a project with another group within this group
- `visibility_level` (optional) - The group's visibility. 0 for private, 10 for internal, 20 for public. - `visibility_level` (optional) - The group's visibility. 0 for private, 10 for internal, 20 for public.
- `lfs_enabled` (optional) - Enable/disable Large File Storage (LFS) for the projects in this group - `lfs_enabled` (optional) - Enable/disable Large File Storage (LFS) for the projects in this group
- `request_access_enabled` (optional) - Allow users to request member access.
## Transfer project to group ## Transfer project to group
...@@ -336,6 +341,7 @@ PUT /groups/:id ...@@ -336,6 +341,7 @@ PUT /groups/:id
| `description` | string | no | The description of the group | | `description` | string | no | The description of the group |
| `visibility_level` | integer | no | The visibility level of the group. 0 for private, 10 for internal, 20 for public. | | `visibility_level` | integer | no | The visibility level of the group. 0 for private, 10 for internal, 20 for public. |
| `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group | | `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group |
| `request_access_enabled` | boolean | no | Allow users to request member access. |
```bash ```bash
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/groups/5?name=Experimental" curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/groups/5?name=Experimental"
...@@ -353,6 +359,7 @@ Example response: ...@@ -353,6 +359,7 @@ Example response:
"visibility_level": 10, "visibility_level": 10,
"avatar_url": null, "avatar_url": null,
"web_url": "http://gitlab.example.com/groups/h5bp", "web_url": "http://gitlab.example.com/groups/h5bp",
"request_access_enabled": false,
"projects": [ "projects": [
{ {
"id": 9, "id": 9,
...@@ -397,7 +404,8 @@ Example response: ...@@ -397,7 +404,8 @@ Example response:
"forks_count": 0, "forks_count": 0,
"open_issues_count": 3, "open_issues_count": 3,
"public_builds": true, "public_builds": true,
"shared_with_groups": [] "shared_with_groups": [],
"request_access_enabled": false
} }
] ]
} }
......
...@@ -85,7 +85,8 @@ Parameters: ...@@ -85,7 +85,8 @@ Parameters:
"runners_token": "b8547b1dc37721d05889db52fa2f02", "runners_token": "b8547b1dc37721d05889db52fa2f02",
"public_builds": true, "public_builds": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false "only_allow_merge_if_build_succeeds": false,
"request_access_enabled": false
}, },
{ {
"id": 6, "id": 6,
...@@ -146,7 +147,8 @@ Parameters: ...@@ -146,7 +147,8 @@ Parameters:
"runners_token": "b8547b1dc37721d05889db52fa2f02", "runners_token": "b8547b1dc37721d05889db52fa2f02",
"public_builds": true, "public_builds": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false "only_allow_merge_if_build_succeeds": false,
"request_access_enabled": false
} }
] ]
``` ```
...@@ -283,7 +285,8 @@ Parameters: ...@@ -283,7 +285,8 @@ Parameters:
"group_access_level": 10 "group_access_level": 10
} }
], ],
"repository_storage": "default" "repository_storage": "default",
"request_access_enabled": false,
"only_allow_merge_if_build_succeeds": false "only_allow_merge_if_build_succeeds": false
} }
``` ```
...@@ -455,6 +458,7 @@ Parameters: ...@@ -455,6 +458,7 @@ Parameters:
- `repository_storage` (optional, available only for admins) - `repository_storage` (optional, available only for admins)
- `only_allow_merge_if_build_succeeds` (optional) - `only_allow_merge_if_build_succeeds` (optional)
- `lfs_enabled` (optional) - `lfs_enabled` (optional)
- `request_access_enabled` (optional) - Allow users to request member access.
### Create project for user ### Create project for user
...@@ -483,6 +487,7 @@ Parameters: ...@@ -483,6 +487,7 @@ Parameters:
- `repository_storage` (optional, available only for admins) - `repository_storage` (optional, available only for admins)
- `only_allow_merge_if_build_succeeds` (optional) - `only_allow_merge_if_build_succeeds` (optional)
- `lfs_enabled` (optional) - `lfs_enabled` (optional)
- `request_access_enabled` (optional) - Allow users to request member access.
### Edit project ### Edit project
...@@ -512,6 +517,7 @@ Parameters: ...@@ -512,6 +517,7 @@ Parameters:
- `repository_storage` (optional, available only for admins) - `repository_storage` (optional, available only for admins)
- `only_allow_merge_if_build_succeeds` (optional) - `only_allow_merge_if_build_succeeds` (optional)
- `lfs_enabled` (optional) - `lfs_enabled` (optional)
- `request_access_enabled` (optional) - Allow users to request member access.
On success, method returns 200 with the updated project. If parameters are On success, method returns 200 with the updated project. If parameters are
invalid, 400 is returned. invalid, 400 is returned.
...@@ -592,7 +598,8 @@ Example response: ...@@ -592,7 +598,8 @@ Example response:
"star_count": 1, "star_count": 1,
"public_builds": true, "public_builds": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false "only_allow_merge_if_build_succeeds": false,
"request_access_enabled": false
} }
``` ```
...@@ -659,7 +666,8 @@ Example response: ...@@ -659,7 +666,8 @@ Example response:
"star_count": 0, "star_count": 0,
"public_builds": true, "public_builds": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false "only_allow_merge_if_build_succeeds": false,
"request_access_enabled": false
} }
``` ```
...@@ -746,7 +754,8 @@ Example response: ...@@ -746,7 +754,8 @@ Example response:
"runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b",
"public_builds": true, "public_builds": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false "only_allow_merge_if_build_succeeds": false,
"request_access_enabled": false
} }
``` ```
...@@ -833,7 +842,8 @@ Example response: ...@@ -833,7 +842,8 @@ Example response:
"runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b",
"public_builds": true, "public_builds": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false "only_allow_merge_if_build_succeeds": false,
"request_access_enabled": false
} }
``` ```
......
...@@ -44,7 +44,7 @@ POST /projects/:id/repository/files ...@@ -44,7 +44,7 @@ POST /projects/:id/repository/files
``` ```
```bash ```bash
curl --request POST --header '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' curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20content&commit_message=create%20a%20new%20file'
``` ```
Example response: Example response:
...@@ -61,6 +61,8 @@ Parameters: ...@@ -61,6 +61,8 @@ Parameters:
- `file_path` (required) - Full path to new file. Ex. lib/class.rb - `file_path` (required) - Full path to new file. Ex. lib/class.rb
- `branch_name` (required) - The name of branch - `branch_name` (required) - The name of branch
- `encoding` (optional) - 'text' or 'base64'. Text is default. - `encoding` (optional) - 'text' or 'base64'. Text is default.
- `author_email` (optional) - Specify the commit author's email address
- `author_name` (optional) - Specify the commit author's name
- `content` (required) - File content - `content` (required) - File content
- `commit_message` (required) - Commit message - `commit_message` (required) - Commit message
...@@ -71,7 +73,7 @@ PUT /projects/:id/repository/files ...@@ -71,7 +73,7 @@ PUT /projects/:id/repository/files
``` ```
```bash ```bash
curl --request PUT --header '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' curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20other%20content&commit_message=update%20file'
``` ```
Example response: Example response:
...@@ -88,6 +90,8 @@ Parameters: ...@@ -88,6 +90,8 @@ Parameters:
- `file_path` (required) - Full path to file. Ex. lib/class.rb - `file_path` (required) - Full path to file. Ex. lib/class.rb
- `branch_name` (required) - The name of branch - `branch_name` (required) - The name of branch
- `encoding` (optional) - 'text' or 'base64'. Text is default. - `encoding` (optional) - 'text' or 'base64'. Text is default.
- `author_email` (optional) - Specify the commit author's email address
- `author_name` (optional) - Specify the commit author's name
- `content` (required) - New file content - `content` (required) - New file content
- `commit_message` (required) - Commit message - `commit_message` (required) - Commit message
...@@ -107,7 +111,7 @@ DELETE /projects/:id/repository/files ...@@ -107,7 +111,7 @@ DELETE /projects/:id/repository/files
``` ```
```bash ```bash
curl --request PUT --header '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' curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file'
``` ```
Example response: Example response:
...@@ -123,4 +127,6 @@ Parameters: ...@@ -123,4 +127,6 @@ Parameters:
- `file_path` (required) - Full path to file. Ex. lib/class.rb - `file_path` (required) - Full path to file. Ex. lib/class.rb
- `branch_name` (required) - The name of branch - `branch_name` (required) - The name of branch
- `author_email` (optional) - Specify the commit author's email address
- `author_name` (optional) - Specify the commit author's name
- `commit_message` (required) - Commit message - `commit_message` (required) - Commit message
...@@ -90,8 +90,7 @@ builds, including deploy builds. This can be an array or a multi-line string. ...@@ -90,8 +90,7 @@ builds, including deploy builds. This can be an array or a multi-line string.
### after_script ### after_script
>**Note:** > Introduced in GitLab 8.7 and requires Gitlab Runner v1.2
Introduced in GitLab 8.7 and requires Gitlab Runner v1.2
`after_script` is used to define the command that will be run after for all `after_script` is used to define the command that will be run after for all
builds. This has to be an array or a multi-line string. builds. This has to be an array or a multi-line string.
...@@ -135,8 +134,7 @@ Alias for [stages](#stages). ...@@ -135,8 +134,7 @@ Alias for [stages](#stages).
### variables ### variables
>**Note:** > Introduced in GitLab Runner v0.5.0.
Introduced in GitLab Runner v0.5.0.
GitLab CI allows you to add variables to `.gitlab-ci.yml` that are set in the GitLab CI allows you to add variables to `.gitlab-ci.yml` that are set in the
build environment. The variables are stored in the Git repository and are meant build environment. The variables are stored in the Git repository and are meant
...@@ -158,8 +156,7 @@ Variables can be also defined on [job level](#job-variables). ...@@ -158,8 +156,7 @@ Variables can be also defined on [job level](#job-variables).
### cache ### cache
>**Note:** > Introduced in GitLab Runner v0.7.0.
Introduced in GitLab Runner v0.7.0.
`cache` is used to specify a list of files and directories which should be `cache` is used to specify a list of files and directories which should be
cached between builds. cached between builds.
...@@ -220,8 +217,7 @@ will be always present. For implementation details, please check GitLab Runner. ...@@ -220,8 +217,7 @@ will be always present. For implementation details, please check GitLab Runner.
#### cache:key #### cache:key
>**Note:** > Introduced in GitLab Runner v1.0.0.
Introduced in GitLab Runner v1.0.0.
The `key` directive allows you to define the affinity of caching The `key` directive allows you to define the affinity of caching
between jobs, allowing to have a single cache for all jobs, between jobs, allowing to have a single cache for all jobs,
...@@ -531,8 +527,7 @@ The above script will: ...@@ -531,8 +527,7 @@ The above script will:
#### Manual actions #### Manual actions
>**Note:** > Introduced in GitLab 8.10.
Introduced in GitLab 8.10.
Manual actions are a special type of job that are not executed automatically; Manual actions are a special type of job that are not executed automatically;
they need to be explicitly started by a user. Manual actions can be started they need to be explicitly started by a user. Manual actions can be started
...@@ -543,17 +538,16 @@ An example usage of manual actions is deployment to production. ...@@ -543,17 +538,16 @@ An example usage of manual actions is deployment to production.
### environment ### environment
>**Note:** > Introduced in GitLab 8.9.
Introduced in GitLab 8.9.
`environment` is used to define that a job deploys to a specific environment. `environment` is used to define that a job deploys to a specific [environment].
This allows easy tracking of all deployments to your environments straight from This allows easy tracking of all deployments to your environments straight from
GitLab. GitLab.
If `environment` is specified and no environment under that name exists, a new If `environment` is specified and no environment under that name exists, a new
one will be created automatically. one will be created automatically.
The `environment` name must contain only letters, digits, '-' and '_'. Common The `environment` name must contain only letters, digits, '-', '_', '/', '$', '{', '}' and spaces. Common
names are `qa`, `staging`, and `production`, but you can use whatever name works names are `qa`, `staging`, and `production`, but you can use whatever name works
with your workflow. with your workflow.
...@@ -571,6 +565,35 @@ deploy to production: ...@@ -571,6 +565,35 @@ deploy to production:
The `deploy to production` job will be marked as doing deployment to The `deploy to production` job will be marked as doing deployment to
`production` environment. `production` environment.
#### dynamic environments
> [Introduced][ce-6323] in GitLab 8.12 and GitLab Runner 1.6.
`environment` can also represent a configuration hash with `name` and `url`.
These parameters can use any of the defined CI [variables](#variables)
(including predefined, secure variables and `.gitlab-ci.yml` variables).
The common use case is to create dynamic environments for branches and use them
as review apps.
---
**Example configurations**
```
deploy as review app:
stage: deploy
script: ...
environment:
name: review-apps/$CI_BUILD_REF_NAME
url: https://$CI_BUILD_REF_NAME.review.example.com/
```
The `deploy as review app` job will be marked as deployment to dynamically
create the `review-apps/branch-name` environment.
This environment should be accessible under `https://branch-name.review.example.com/`.
### artifacts ### artifacts
>**Notes:** >**Notes:**
...@@ -638,8 +661,7 @@ be available for download in the GitLab UI. ...@@ -638,8 +661,7 @@ be available for download in the GitLab UI.
#### artifacts:name #### artifacts:name
>**Note:** > Introduced in GitLab 8.6 and GitLab Runner v1.1.0.
Introduced in GitLab 8.6 and GitLab Runner v1.1.0.
The `name` directive allows you to define the name of the created artifacts The `name` directive allows you to define the name of the created artifacts
archive. That way, you can have a unique name for every archive which could be archive. That way, you can have a unique name for every archive which could be
...@@ -702,8 +724,7 @@ job: ...@@ -702,8 +724,7 @@ job:
#### artifacts:when #### artifacts:when
>**Note:** > Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
`artifacts:when` is used to upload artifacts on build failure or despite the `artifacts:when` is used to upload artifacts on build failure or despite the
failure. failure.
...@@ -728,8 +749,7 @@ job: ...@@ -728,8 +749,7 @@ job:
#### artifacts:expire_in #### artifacts:expire_in
>**Note:** > Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
`artifacts:expire_in` is used to delete uploaded artifacts after the specified `artifacts:expire_in` is used to delete uploaded artifacts after the specified
time. By default, artifacts are stored on GitLab forever. `expire_in` allows you time. By default, artifacts are stored on GitLab forever. `expire_in` allows you
...@@ -764,8 +784,7 @@ job: ...@@ -764,8 +784,7 @@ job:
### dependencies ### dependencies
>**Note:** > Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
This feature should be used in conjunction with [`artifacts`](#artifacts) and This feature should be used in conjunction with [`artifacts`](#artifacts) and
allows you to define the artifacts to pass between different builds. allows you to define the artifacts to pass between different builds.
...@@ -839,9 +858,8 @@ job: ...@@ -839,9 +858,8 @@ job:
## Git Strategy ## Git Strategy
>**Note:** > Introduced in GitLab 8.9 as an experimental feature. May change in future
Introduced in GitLab 8.9 as an experimental feature. May change in future releases or be removed completely.
releases or be removed completely.
You can set the `GIT_STRATEGY` used for getting recent application code. `clone` You can set the `GIT_STRATEGY` used for getting recent application code. `clone`
is slower, but makes sure you have a clean directory before every build. `fetch` is slower, but makes sure you have a clean directory before every build. `fetch`
...@@ -863,8 +881,7 @@ variables: ...@@ -863,8 +881,7 @@ variables:
## Shallow cloning ## Shallow cloning
>**Note:** > Introduced in GitLab 8.9 as an experimental feature. May change in future
Introduced in GitLab 8.9 as an experimental feature. May change in future
releases or be removed completely. releases or be removed completely.
You can specify the depth of fetching and cloning using `GIT_DEPTH`. This allows You can specify the depth of fetching and cloning using `GIT_DEPTH`. This allows
...@@ -894,8 +911,7 @@ variables: ...@@ -894,8 +911,7 @@ variables:
## Hidden keys ## Hidden keys
>**Note:** > Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
Keys that start with a dot (`.`) will be not processed by GitLab CI. You can Keys that start with a dot (`.`) will be not processed by GitLab CI. You can
use this feature to ignore jobs, or use the use this feature to ignore jobs, or use the
...@@ -923,8 +939,7 @@ Read more about the various [YAML features](https://learnxinyminutes.com/docs/ya ...@@ -923,8 +939,7 @@ Read more about the various [YAML features](https://learnxinyminutes.com/docs/ya
### Anchors ### Anchors
>**Note:** > Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
YAML also has a handy feature called 'anchors', which let you easily duplicate YAML also has a handy feature called 'anchors', which let you easily duplicate
content across your document. Anchors can be used to duplicate/inherit content across your document. Anchors can be used to duplicate/inherit
...@@ -1067,3 +1082,5 @@ Visit the [examples README][examples] to see a list of examples using GitLab ...@@ -1067,3 +1082,5 @@ Visit the [examples README][examples] to see a list of examples using GitLab
CI with various languages. CI with various languages.
[examples]: ../examples/README.md [examples]: ../examples/README.md
[ce-6323]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6323
[environment]: ../environments.md
...@@ -403,7 +403,7 @@ If you are not using Linux you may have to run `gmake` instead of ...@@ -403,7 +403,7 @@ If you are not using Linux you may have to run `gmake` instead of
cd /home/git cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse cd gitlab-workhorse
sudo -u git -H git checkout v0.8.1 sudo -u git -H git checkout v0.8.2
sudo -u git -H make sudo -u git -H make
### Initialize Database and Activate Advanced Features ### Initialize Database and Activate Advanced Features
......
...@@ -82,7 +82,7 @@ GitLab 8.1. ...@@ -82,7 +82,7 @@ GitLab 8.1.
```bash ```bash
cd /home/git/gitlab-workhorse cd /home/git/gitlab-workhorse
sudo -u git -H git fetch --all sudo -u git -H git fetch --all
sudo -u git -H git checkout v0.8.1 sudo -u git -H git checkout v0.8.2
sudo -u git -H make sudo -u git -H make
``` ```
......
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
>**Notes:** >**Notes:**
> >
> - [Introduced][ce-3050] in GitLab 8.9. > - [Introduced][ce-3050] in GitLab 8.9.
> - Importing will not be possible if the import instance version is lower > - Importing will not be possible if the import instance version differs from
> than that of the exporter. > that of the exporter.
> - For existing installations, the project import option has to be enabled in > - For existing installations, the project import option has to be enabled in
> application settings (`/admin/application_settings`) under 'Import sources'. > application settings (`/admin/application_settings`) under 'Import sources'.
> You will have to be an administrator to enable and use the import functionality. > You will have to be an administrator to enable and use the import functionality.
...@@ -17,6 +17,20 @@ ...@@ -17,6 +17,20 @@
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.
## Version history
| GitLab version | Import/Export version |
| -------- | -------- |
| 8.12.0 to current | 0.1.4 |
| 8.10.3 | 0.1.3 |
| 8.10.0 | 0.1.2 |
| 8.9.5 | 0.1.1 |
| 8.9.0 | 0.1.0 |
> The table reflects what GitLab version we updated the Import/Export version at.
> For instance, 8.10.3 and 8.11 will have the same Import/Export version (0.1.3)
> and the exports between them will be compatible.
## Exported contents ## Exported contents
The following items will be exported: The following items will be exported:
......
...@@ -106,6 +106,11 @@ If you want, you can import all your GitHub projects in one go by hitting ...@@ -106,6 +106,11 @@ If you want, you can import all your GitHub projects in one go by hitting
![GitHub importer page](img/import_projects_from_github_importer.png) ![GitHub importer page](img/import_projects_from_github_importer.png)
---
You can also choose a different name for the project and a different namespace,
if you have the privileges to do so.
[gh-import]: ../../integration/github.md "GitHub integration" [gh-import]: ../../integration/github.md "GitHub integration"
[new-project]: ../../gitlab-basics/create-project.md "How to create a new project in GitLab" [new-project]: ../../gitlab-basics/create-project.md "How to create a new project in GitLab"
[gh-integration]: #authorize-access-to-your-repositories-using-the-github-integration [gh-integration]: #authorize-access-to-your-repositories-using-the-github-integration
......
...@@ -45,5 +45,5 @@ In `config/gitlab.yml`: ...@@ -45,5 +45,5 @@ In `config/gitlab.yml`:
* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets) * Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets)
is not supported is not supported
* Currently, removing LFS objects from GitLab Git LFS storage is not supported * Currently, removing LFS objects from GitLab Git LFS storage is not supported
* LFS authentications via SSH is not supported for the time being * LFS authentications via SSH was added with GitLab 8.12
* Only compatible with the GitLFS client versions 1.1.0 or 1.0.2. * Only compatible with the GitLFS client versions 1.1.0 and up, or 1.0.2.
...@@ -35,6 +35,10 @@ Documentation for GitLab instance administrators is under [LFS administration do ...@@ -35,6 +35,10 @@ Documentation for GitLab instance administrators is under [LFS administration do
credentials store is recommended credentials store is recommended
* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have * Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have
to add the URL to Git config manually (see #troubleshooting) to add the URL to Git config manually (see #troubleshooting)
>**Note**: With 8.12 GitLab added LFS support to SSH. The Git LFS communication
still goes over HTTP, but now the SSH client passes the correct credentials
to the Git LFS client, so no action is required by the user.
## Using Git LFS ## Using Git LFS
...@@ -132,6 +136,10 @@ git config --add lfs.url "http://gitlab.example.com/group/project.git/info/lfs" ...@@ -132,6 +136,10 @@ git config --add lfs.url "http://gitlab.example.com/group/project.git/info/lfs"
### Credentials are always required when pushing an object ### Credentials are always required when pushing an object
>**Note**: With 8.12 GitLab added LFS support to SSH. The Git LFS communication
still goes over HTTP, but now the SSH client passes the correct credentials
to the Git LFS client, so no action is required by the user.
Given that Git LFS uses HTTP Basic Authentication to authenticate the user pushing Given that Git LFS uses HTTP Basic Authentication to authenticate the user pushing
the LFS object on every push for every object, user HTTPS credentials are required. the LFS object on every push for every object, user HTTPS credentials are required.
......
...@@ -356,6 +356,6 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps ...@@ -356,6 +356,6 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end end
def filter_issue(text) def filter_issue(text)
fill_in 'issue_search', with: text fill_in 'issuable_search', with: text
end end
end end
...@@ -513,7 +513,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -513,7 +513,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end end
step 'I fill in merge request search with "Fe"' do step 'I fill in merge request search with "Fe"' do
fill_in 'issue_search', with: "Fe" fill_in 'issuable_search', with: "Fe"
end end
step 'I click the "Target branch" dropdown' do step 'I click the "Target branch" dropdown' do
......
...@@ -20,7 +20,7 @@ module API ...@@ -20,7 +20,7 @@ module API
access_requesters = paginate(source.requesters.includes(:user)) access_requesters = paginate(source.requesters.includes(:user))
present access_requesters.map(&:user), with: Entities::AccessRequester, access_requesters: access_requesters present access_requesters.map(&:user), with: Entities::AccessRequester, source: source
end end
# Request access to the group/project # Request access to the group/project
......
...@@ -33,46 +33,29 @@ module API ...@@ -33,46 +33,29 @@ module API
# #
# If the token is revoked, then it raises RevokedError. # If the token is revoked, then it raises RevokedError.
# #
# If the token is not found (nil), then it raises TokenNotFoundError. # If the token is not found (nil), then it returns nil
# #
# Arguments: # Arguments:
# #
# scopes: (optional) scopes required for this guard. # scopes: (optional) scopes required for this guard.
# Defaults to empty array. # Defaults to empty array.
# #
def doorkeeper_guard!(scopes: [])
if (access_token = find_access_token).nil?
raise TokenNotFoundError
else
case validate_access_token(access_token, scopes)
when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
raise InsufficientScopeError.new(scopes)
when Oauth2::AccessTokenValidationService::EXPIRED
raise ExpiredError
when Oauth2::AccessTokenValidationService::REVOKED
raise RevokedError
when Oauth2::AccessTokenValidationService::VALID
@current_user = User.find(access_token.resource_owner_id)
end
end
end
def doorkeeper_guard(scopes: []) def doorkeeper_guard(scopes: [])
if access_token = find_access_token access_token = find_access_token
case validate_access_token(access_token, scopes) return nil unless access_token
when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
raise InsufficientScopeError.new(scopes) case validate_access_token(access_token, scopes)
when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
raise InsufficientScopeError.new(scopes)
when Oauth2::AccessTokenValidationService::EXPIRED when Oauth2::AccessTokenValidationService::EXPIRED
raise ExpiredError raise ExpiredError
when Oauth2::AccessTokenValidationService::REVOKED when Oauth2::AccessTokenValidationService::REVOKED
raise RevokedError raise RevokedError
when Oauth2::AccessTokenValidationService::VALID when Oauth2::AccessTokenValidationService::VALID
@current_user = User.find(access_token.resource_owner_id) @current_user = User.find(access_token.resource_owner_id)
end
end end
end end
...@@ -96,19 +79,6 @@ module API ...@@ -96,19 +79,6 @@ module API
end end
module ClassMethods module ClassMethods
# Installs the doorkeeper guard on the whole Grape API endpoint.
#
# Arguments:
#
# scopes: (optional) scopes required for this guard.
# Defaults to empty array.
#
def guard_all!(scopes: [])
before do
guard! scopes: scopes
end
end
private private
def install_error_responders(base) def install_error_responders(base)
......
...@@ -106,22 +106,23 @@ module API ...@@ -106,22 +106,23 @@ module API
end end
expose :repository_storage, if: lambda { |_project, options| options[:user].try(:admin?) } expose :repository_storage, if: lambda { |_project, options| options[:user].try(:admin?) }
expose :only_allow_merge_if_build_succeeds expose :only_allow_merge_if_build_succeeds
expose :request_access_enabled
end end
class Member < UserBasic class Member < UserBasic
expose :access_level do |user, options| expose :access_level do |user, options|
member = options[:member] || options[:members].find { |m| m.user_id == user.id } member = options[:member] || options[:source].members.find_by(user_id: user.id)
member.access_level member.access_level
end end
expose :expires_at do |user, options| expose :expires_at do |user, options|
member = options[:member] || options[:members].find { |m| m.user_id == user.id } member = options[:member] || options[:source].members.find_by(user_id: user.id)
member.expires_at member.expires_at
end end
end end
class AccessRequester < UserBasic class AccessRequester < UserBasic
expose :requested_at do |user, options| expose :requested_at do |user, options|
access_requester = options[:access_requester] || options[:access_requesters].find { |m| m.user_id == user.id } access_requester = options[:access_requester] || options[:source].requesters.find_by(user_id: user.id)
access_requester.requested_at access_requester.requested_at
end end
end end
...@@ -141,6 +142,7 @@ module API ...@@ -141,6 +142,7 @@ module API
expose :lfs_enabled?, as: :lfs_enabled expose :lfs_enabled?, as: :lfs_enabled
expose :avatar_url expose :avatar_url
expose :web_url expose :web_url
expose :request_access_enabled
end end
class GroupDetail < Group class GroupDetail < Group
......
...@@ -11,14 +11,16 @@ module API ...@@ -11,14 +11,16 @@ module API
target_branch: attrs[:branch_name], target_branch: attrs[:branch_name],
commit_message: attrs[:commit_message], commit_message: attrs[:commit_message],
file_content: attrs[:content], file_content: attrs[:content],
file_content_encoding: attrs[:encoding] file_content_encoding: attrs[:encoding],
author_email: attrs[:author_email],
author_name: attrs[:author_name]
} }
end end
def commit_response(attrs) def commit_response(attrs)
{ {
file_path: attrs[:file_path], file_path: attrs[:file_path],
branch_name: attrs[:branch_name], branch_name: attrs[:branch_name]
} }
end end
end end
...@@ -96,7 +98,7 @@ module API ...@@ -96,7 +98,7 @@ module API
authorize! :push_code, user_project authorize! :push_code, user_project
required_attributes! [:file_path, :branch_name, :content, :commit_message] required_attributes! [:file_path, :branch_name, :content, :commit_message]
attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding] attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding, :author_email, :author_name]
result = ::Files::CreateService.new(user_project, current_user, commit_params(attrs)).execute result = ::Files::CreateService.new(user_project, current_user, commit_params(attrs)).execute
if result[:status] == :success if result[:status] == :success
...@@ -122,7 +124,7 @@ module API ...@@ -122,7 +124,7 @@ module API
authorize! :push_code, user_project authorize! :push_code, user_project
required_attributes! [:file_path, :branch_name, :content, :commit_message] required_attributes! [:file_path, :branch_name, :content, :commit_message]
attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding] attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding, :author_email, :author_name]
result = ::Files::UpdateService.new(user_project, current_user, commit_params(attrs)).execute result = ::Files::UpdateService.new(user_project, current_user, commit_params(attrs)).execute
if result[:status] == :success if result[:status] == :success
...@@ -149,7 +151,7 @@ module API ...@@ -149,7 +151,7 @@ module API
authorize! :push_code, user_project authorize! :push_code, user_project
required_attributes! [:file_path, :branch_name, :commit_message] required_attributes! [:file_path, :branch_name, :commit_message]
attrs = attributes_for_keys [:file_path, :branch_name, :commit_message] attrs = attributes_for_keys [:file_path, :branch_name, :commit_message, :author_email, :author_name]
result = ::Files::DeleteService.new(user_project, current_user, commit_params(attrs)).execute result = ::Files::DeleteService.new(user_project, current_user, commit_params(attrs)).execute
if result[:status] == :success if result[:status] == :success
......
...@@ -30,13 +30,14 @@ module API ...@@ -30,13 +30,14 @@ module API
# membership_lock (optional, boolean) - Prevent adding new members to project membership within this group # membership_lock (optional, boolean) - Prevent adding new members to project membership within this group
# share_with_group_lock (optional, boolean) - Prevent sharing a project with another group within this group # share_with_group_lock (optional, boolean) - Prevent sharing a project with another group within this group
# lfs_enabled (optional) - Enable/disable LFS for the projects in this group # lfs_enabled (optional) - Enable/disable LFS for the projects in this group
# request_access_enabled (optional) - Allow users to request member access
# Example Request: # Example Request:
# POST /groups # POST /groups
post do post do
authorize! :create_group authorize! :create_group
required_attributes! [:name, :path] required_attributes! [:name, :path]
attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :membership_lock, :share_with_group_lock, :lfs_enabled] attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :membership_lock, :share_with_group_lock, :lfs_enabled, :request_access_enabled]
@group = Group.new(attrs) @group = Group.new(attrs)
if @group.save if @group.save
...@@ -64,15 +65,17 @@ module API ...@@ -64,15 +65,17 @@ module API
# path (required) - The path of the group # path (required) - The path of the group
# description (optional) - The details of the group # description (optional) - The details of the group
# visibility_level (optional) - The visibility level of the group # visibility_level (optional) - The visibility level of the group
# lfs_enabled (optional) - Enable/disable LFS for the projects in this group
# membership_lock (optional, boolean) - Prevent adding new members to project membership within this group # membership_lock (optional, boolean) - Prevent adding new members to project membership within this group
# share_with_group_lock (optional, boolean) - Prevent sharing a project with another group within this group # share_with_group_lock (optional, boolean) - Prevent sharing a project with another group within this group
# request_access_enabled (optional) - Allow users to request member access
# Example Request: # Example Request:
# PUT /groups/:id # PUT /groups/:id
put ':id' do put ':id' do
group = find_group(params[:id]) group = find_group(params[:id])
authorize! :admin_group, group authorize! :admin_group, group
attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :membership_lock, :share_with_group_lock, :lfs_enabled] attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :membership_lock, :share_with_group_lock, :lfs_enabled, :request_access_enabled]
if ::Groups::UpdateService.new(group, current_user, attrs).execute if ::Groups::UpdateService.new(group, current_user, attrs).execute
present group, with: Entities::GroupDetail present group, with: Entities::GroupDetail
......
...@@ -12,13 +12,30 @@ module API ...@@ -12,13 +12,30 @@ module API
nil nil
end end
def private_token
params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]
end
def warden
env['warden']
end
# Check the Rails session for valid authentication details
def find_user_from_warden
warden ? warden.authenticate : nil
end
def find_user_by_private_token def find_user_by_private_token
token_string = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s token = private_token
User.find_by_authentication_token(token_string) || User.find_by_personal_access_token(token_string) return nil unless token.present?
User.find_by_authentication_token(token) || User.find_by_personal_access_token(token)
end end
def current_user def current_user
@current_user ||= (find_user_by_private_token || doorkeeper_guard) @current_user ||= find_user_by_private_token
@current_user ||= doorkeeper_guard
@current_user ||= find_user_from_warden
unless @current_user && Gitlab::UserAccess.new(@current_user).allowed? unless @current_user && Gitlab::UserAccess.new(@current_user).allowed?
return nil return nil
......
...@@ -35,6 +35,14 @@ module API ...@@ -35,6 +35,14 @@ module API
Project.find_with_namespace(project_path) Project.find_with_namespace(project_path)
end end
end end
def ssh_authentication_abilities
[
:read_project,
:download_code,
:push_code
]
end
end end
post "/allowed" do post "/allowed" do
...@@ -51,9 +59,9 @@ module API ...@@ -51,9 +59,9 @@ module API
access = access =
if wiki? if wiki?
Gitlab::GitAccessWiki.new(actor, project, protocol) Gitlab::GitAccessWiki.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities)
else else
Gitlab::GitAccess.new(actor, project, protocol) Gitlab::GitAccess.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities)
end end
access_status = access.check(params[:action], params[:changes]) access_status = access.check(params[:action], params[:changes])
...@@ -74,6 +82,19 @@ module API ...@@ -74,6 +82,19 @@ module API
response response
end end
post "/lfs_authenticate" do
status 200
key = Key.find(params[:key_id])
token_handler = Gitlab::LfsToken.new(key)
{
username: token_handler.actor_name,
lfs_token: token_handler.generate,
repository_http_path: project.http_url_to_repo
}
end
get "/merge_request_urls" do get "/merge_request_urls" do
::MergeRequests::GetUrlsService.new(project).execute(params[:changes]) ::MergeRequests::GetUrlsService.new(project).execute(params[:changes])
end end
......
...@@ -18,11 +18,11 @@ module API ...@@ -18,11 +18,11 @@ module API
get ":id/members" do get ":id/members" do
source = find_source(source_type, params[:id]) source = find_source(source_type, params[:id])
members = source.members.includes(:user) users = source.users
members = members.joins(:user).merge(User.search(params[:query])) if params[:query] users = users.merge(User.search(params[:query])) if params[:query]
members = paginate(members) users = paginate(users)
present members.map(&:user), with: Entities::Member, members: members present users, with: Entities::Member, source: source
end end
# Get a group/project member # Get a group/project member
......
...@@ -91,8 +91,8 @@ module API ...@@ -91,8 +91,8 @@ module API
# Create new project # Create new project
# #
# Parameters: # Parameters:
# name (required) - name for new project # name (required) - name for new project
# description (optional) - short project description # description (optional) - short project description
# issues_enabled (optional) # issues_enabled (optional)
# merge_requests_enabled (optional) # merge_requests_enabled (optional)
# builds_enabled (optional) # builds_enabled (optional)
...@@ -100,35 +100,37 @@ module API ...@@ -100,35 +100,37 @@ module API
# snippets_enabled (optional) # snippets_enabled (optional)
# container_registry_enabled (optional) # container_registry_enabled (optional)
# shared_runners_enabled (optional) # shared_runners_enabled (optional)
# namespace_id (optional) - defaults to user namespace # namespace_id (optional) - defaults to user namespace
# public (optional) - if true same as setting visibility_level = 20 # public (optional) - if true same as setting visibility_level = 20
# visibility_level (optional) - 0 by default # visibility_level (optional) - 0 by default
# import_url (optional) # import_url (optional)
# public_builds (optional) # public_builds (optional)
# repository_storage (optional) # repository_storage (optional)
# lfs_enabled (optional) # lfs_enabled (optional)
# request_access_enabled (optional) - Allow users to request member access
# Example Request # Example Request
# POST /projects # POST /projects
post do post do
required_attributes! [:name] required_attributes! [:name]
attrs = attributes_for_keys [:name, attrs = attributes_for_keys [:builds_enabled,
:path, :container_registry_enabled,
:description, :description,
:import_url,
:issues_enabled, :issues_enabled,
:lfs_enabled,
:merge_requests_enabled, :merge_requests_enabled,
:builds_enabled, :name,
:wiki_enabled,
:snippets_enabled,
:container_registry_enabled,
:shared_runners_enabled,
:namespace_id, :namespace_id,
:only_allow_merge_if_build_succeeds,
:path,
:public, :public,
:visibility_level,
:import_url,
:public_builds, :public_builds,
:repository_storage, :repository_storage,
:only_allow_merge_if_build_succeeds, :request_access_enabled,
:lfs_enabled] :shared_runners_enabled,
:snippets_enabled,
:visibility_level,
:wiki_enabled]
attrs = map_public_to_visibility_level(attrs) attrs = map_public_to_visibility_level(attrs)
@project = ::Projects::CreateService.new(current_user, attrs).execute @project = ::Projects::CreateService.new(current_user, attrs).execute
if @project.saved? if @project.saved?
...@@ -145,10 +147,10 @@ module API ...@@ -145,10 +147,10 @@ module API
# Create new project for a specified user. Only available to admin users. # Create new project for a specified user. Only available to admin users.
# #
# Parameters: # Parameters:
# user_id (required) - The ID of a user # user_id (required) - The ID of a user
# name (required) - name for new project # name (required) - name for new project
# description (optional) - short project description # description (optional) - short project description
# default_branch (optional) - 'master' by default # default_branch (optional) - 'master' by default
# issues_enabled (optional) # issues_enabled (optional)
# merge_requests_enabled (optional) # merge_requests_enabled (optional)
# builds_enabled (optional) # builds_enabled (optional)
...@@ -156,33 +158,35 @@ module API ...@@ -156,33 +158,35 @@ module API
# snippets_enabled (optional) # snippets_enabled (optional)
# container_registry_enabled (optional) # container_registry_enabled (optional)
# shared_runners_enabled (optional) # shared_runners_enabled (optional)
# public (optional) - if true same as setting visibility_level = 20 # public (optional) - if true same as setting visibility_level = 20
# visibility_level (optional) # visibility_level (optional)
# import_url (optional) # import_url (optional)
# public_builds (optional) # public_builds (optional)
# repository_storage (optional) # repository_storage (optional)
# lfs_enabled (optional) # lfs_enabled (optional)
# request_access_enabled (optional) - Allow users to request member access
# Example Request # Example Request
# POST /projects/user/:user_id # POST /projects/user/:user_id
post "user/:user_id" do post "user/:user_id" do
authenticated_as_admin! authenticated_as_admin!
user = User.find(params[:user_id]) user = User.find(params[:user_id])
attrs = attributes_for_keys [:name, attrs = attributes_for_keys [:builds_enabled,
:description,
:default_branch, :default_branch,
:description,
:import_url,
:issues_enabled, :issues_enabled,
:lfs_enabled,
:merge_requests_enabled, :merge_requests_enabled,
:builds_enabled, :name,
:wiki_enabled, :only_allow_merge_if_build_succeeds,
:snippets_enabled,
:shared_runners_enabled,
:public, :public,
:visibility_level,
:import_url,
:public_builds, :public_builds,
:repository_storage, :repository_storage,
:only_allow_merge_if_build_succeeds, :request_access_enabled,
:lfs_enabled] :shared_runners_enabled,
:snippets_enabled,
:visibility_level,
:wiki_enabled]
attrs = map_public_to_visibility_level(attrs) attrs = map_public_to_visibility_level(attrs)
@project = ::Projects::CreateService.new(user, attrs).execute @project = ::Projects::CreateService.new(user, attrs).execute
if @project.saved? if @project.saved?
...@@ -247,23 +251,24 @@ module API ...@@ -247,23 +251,24 @@ module API
# Example Request # Example Request
# PUT /projects/:id # PUT /projects/:id
put ':id' do put ':id' do
attrs = attributes_for_keys [:name, attrs = attributes_for_keys [:builds_enabled,
:path, :container_registry_enabled,
:description,
:default_branch, :default_branch,
:description,
:issues_enabled, :issues_enabled,
:lfs_enabled,
:merge_requests_enabled, :merge_requests_enabled,
:builds_enabled, :name,
:wiki_enabled, :only_allow_merge_if_build_succeeds,
:snippets_enabled, :path,
:container_registry_enabled,
:shared_runners_enabled,
:public, :public,
:visibility_level,
:public_builds, :public_builds,
:repository_storage, :repository_storage,
:only_allow_merge_if_build_succeeds, :request_access_enabled,
:lfs_enabled] :shared_runners_enabled,
:snippets_enabled,
:visibility_level,
:wiki_enabled]
attrs = map_public_to_visibility_level(attrs) attrs = map_public_to_visibility_level(attrs)
authorize_admin_project authorize_admin_project
authorize! :rename_project, user_project if attrs[:name].present? authorize! :rename_project, user_project if attrs[:name].present?
......
...@@ -15,6 +15,15 @@ module Ci ...@@ -15,6 +15,15 @@ module Ci
expose :filename, :size expose :filename, :size
end end
class BuildOptions < Grape::Entity
expose :image
expose :services
expose :artifacts
expose :cache
expose :dependencies
expose :after_script
end
class Build < Grape::Entity class Build < Grape::Entity
expose :id, :ref, :tag, :sha, :status expose :id, :ref, :tag, :sha, :status
expose :name, :token, :stage expose :name, :token, :stage
......
...@@ -14,12 +14,20 @@ module Ci ...@@ -14,12 +14,20 @@ module Ci
end end
def authenticate_build_token!(build) def authenticate_build_token!(build)
token = (params[BUILD_TOKEN_PARAM] || env[BUILD_TOKEN_HEADER]).to_s forbidden! unless build_token_valid?(build)
forbidden! unless token && build.valid_token?(token)
end end
def runner_registration_token_valid? def runner_registration_token_valid?
params[:token] == current_application_settings.runners_registration_token ActiveSupport::SecurityUtils.variable_size_secure_compare(
params[:token],
current_application_settings.runners_registration_token)
end
def build_token_valid?(build)
token = (params[BUILD_TOKEN_PARAM] || env[BUILD_TOKEN_HEADER]).to_s
# We require to also check `runners_token` to maintain compatibility with old version of runners
token && (build.valid_token?(token) || build.project.valid_runners_token?(token))
end end
def update_runner_last_contact(save: true) def update_runner_last_contact(save: true)
......
...@@ -60,7 +60,7 @@ module Ci ...@@ -60,7 +60,7 @@ module Ci
name: job[:name].to_s, name: job[:name].to_s,
allow_failure: job[:allow_failure] || false, allow_failure: job[:allow_failure] || false,
when: job[:when] || 'on_success', when: job[:when] || 'on_success',
environment: job[:environment], environment: job[:environment_name],
yaml_variables: yaml_variables(name), yaml_variables: yaml_variables(name),
options: { options: {
image: job[:image], image: job[:image],
...@@ -69,6 +69,7 @@ module Ci ...@@ -69,6 +69,7 @@ module Ci
cache: job[:cache], cache: job[:cache],
dependencies: job[:dependencies], dependencies: job[:dependencies],
after_script: job[:after_script], after_script: job[:after_script],
environment: job[:environment],
}.compact }.compact
} }
end end
......
module Ci::MaskSecret
class << self
def mask(value, token)
return value unless value.present? && token.present?
value.gsub(token, 'x' * token.length)
end
end
end
module ExpandVariables
class << self
def expand(value, variables)
# Convert hash array to variables
if variables.is_a?(Array)
variables = variables.reduce({}) do |hash, variable|
hash[variable[:key]] = variable[:value]
hash
end
end
value.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) do
variables[$1 || $2]
end
end
end
end
module Gitlab module Gitlab
module Auth module Auth
Result = Struct.new(:user, :type) class MissingPersonalTokenError < StandardError; end
class << self class << self
def find_for_git_client(login, password, project:, ip:) def find_for_git_client(login, password, project:, ip:)
raise "Must provide an IP for rate limiting" if ip.nil? raise "Must provide an IP for rate limiting" if ip.nil?
result = Result.new result =
service_request_check(login, password, project) ||
build_access_token_check(login, password) ||
user_with_password_for_git(login, password) ||
oauth_access_token_check(login, password) ||
lfs_token_check(login, password) ||
personal_access_token_check(login, password) ||
Gitlab::Auth::Result.new
if valid_ci_request?(login, password, project) rate_limit!(ip, success: result.success?, login: login)
result.type = :ci
else
result = populate_result(login, password)
end
success = result.user.present? || [:ci, :missing_personal_token].include?(result.type)
rate_limit!(ip, success: success, login: login)
result result
end end
...@@ -62,44 +63,31 @@ module Gitlab ...@@ -62,44 +63,31 @@ module Gitlab
private private
def valid_ci_request?(login, password, project) def service_request_check(login, password, project)
matched_login = /(?<service>^[a-zA-Z]*-ci)-token$/.match(login) matched_login = /(?<service>^[a-zA-Z]*-ci)-token$/.match(login)
return false unless project && matched_login.present? return unless project && matched_login.present?
underscored_service = matched_login['service'].underscore underscored_service = matched_login['service'].underscore
if underscored_service == 'gitlab_ci' if Service.available_services_names.include?(underscored_service)
project && project.valid_build_token?(password)
elsif Service.available_services_names.include?(underscored_service)
# We treat underscored_service as a trusted input because it is included # We treat underscored_service as a trusted input because it is included
# in the Service.available_services_names whitelist. # in the Service.available_services_names whitelist.
service = project.public_send("#{underscored_service}_service") service = project.public_send("#{underscored_service}_service")
service && service.activated? && service.valid_token?(password) if service && service.activated? && service.valid_token?(password)
end Gitlab::Auth::Result.new(nil, project, :ci, build_authentication_abilities)
end
def populate_result(login, password)
result =
user_with_password_for_git(login, password) ||
oauth_access_token_check(login, password) ||
personal_access_token_check(login, password)
if result
result.type = nil unless result.user
if result.user && result.user.two_factor_enabled? && result.type == :gitlab_or_ldap
result.type = :missing_personal_token
end end
end end
result || Result.new
end end
def user_with_password_for_git(login, password) def user_with_password_for_git(login, password)
user = find_with_user_password(login, password) user = find_with_user_password(login, password)
Result.new(user, :gitlab_or_ldap) if user return unless user
raise Gitlab::Auth::MissingPersonalTokenError if user.two_factor_enabled?
Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)
end end
def oauth_access_token_check(login, password) def oauth_access_token_check(login, password)
...@@ -107,7 +95,7 @@ module Gitlab ...@@ -107,7 +95,7 @@ module Gitlab
token = Doorkeeper::AccessToken.by_token(password) token = Doorkeeper::AccessToken.by_token(password)
if token && token.accessible? if token && token.accessible?
user = User.find_by(id: token.resource_owner_id) user = User.find_by(id: token.resource_owner_id)
Result.new(user, :oauth) Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities)
end end
end end
end end
...@@ -116,9 +104,76 @@ module Gitlab ...@@ -116,9 +104,76 @@ module Gitlab
if login && password if login && password
user = User.find_by_personal_access_token(password) user = User.find_by_personal_access_token(password)
validation = User.by_login(login) validation = User.by_login(login)
Result.new(user, :personal_token) if user == validation Gitlab::Auth::Result.new(user, nil, :personal_token, full_authentication_abilities) if user.present? && user == validation
end
end
def lfs_token_check(login, password)
deploy_key_matches = login.match(/\Alfs\+deploy-key-(\d+)\z/)
actor =
if deploy_key_matches
DeployKey.find(deploy_key_matches[1])
else
User.by_login(login)
end
return unless actor
token_handler = Gitlab::LfsToken.new(actor)
authentication_abilities =
if token_handler.user?
full_authentication_abilities
else
read_authentication_abilities
end
Result.new(actor, nil, token_handler.type, authentication_abilities) if Devise.secure_compare(token_handler.value, password)
end
def build_access_token_check(login, password)
return unless login == 'gitlab-ci-token'
return unless password
build = ::Ci::Build.running.find_by_token(password)
return unless build
return unless build.project.builds_enabled?
if build.user
# If user is assigned to build, use restricted credentials of user
Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities)
else
# Otherwise use generic CI credentials (backward compatibility)
Gitlab::Auth::Result.new(nil, build.project, :ci, build_authentication_abilities)
end end
end end
public
def build_authentication_abilities
[
:read_project,
:build_download_code,
:build_read_container_image,
:build_create_container_image
]
end
def read_authentication_abilities
[
:read_project,
:download_code,
:read_container_image
]
end
def full_authentication_abilities
read_authentication_abilities + [
:push_code,
:create_container_image
]
end
end end
end end
end end
module Gitlab
module Auth
Result = Struct.new(:actor, :project, :type, :authentication_abilities) do
def ci?(for_project)
type == :ci &&
project &&
project == for_project
end
def lfs_deploy_token?(for_project)
type == :lfs_deploy_token &&
actor &&
actor.projects.include?(for_project)
end
def success?
actor.present? || type == :ci
end
end
end
end
...@@ -6,7 +6,12 @@ module Gitlab ...@@ -6,7 +6,12 @@ module Gitlab
KeyAdder = Struct.new(:io) do KeyAdder = Struct.new(:io) do
def add_key(id, key) def add_key(id, key)
key.gsub!(/[[:space:]]+/, ' ').strip! key = Gitlab::Shell.strip_key(key)
# Newline and tab are part of the 'protocol' used to transmit id+key to the other end
if key.include?("\t") || key.include?("\n")
raise Error.new("Invalid key: #{key.inspect}")
end
io.puts("#{id}\t#{key}") io.puts("#{id}\t#{key}")
end end
end end
...@@ -16,6 +21,10 @@ module Gitlab ...@@ -16,6 +21,10 @@ module Gitlab
@version_required ||= File.read(Rails.root. @version_required ||= File.read(Rails.root.
join('GITLAB_SHELL_VERSION')).strip join('GITLAB_SHELL_VERSION')).strip
end end
def strip_key(key)
key.split(/ /)[0, 2].join(' ')
end
end end
# Init new repository # Init new repository
...@@ -168,7 +177,7 @@ module Gitlab ...@@ -168,7 +177,7 @@ module Gitlab
# #
def add_key(key_id, key_content) def add_key(key_id, key_content)
Gitlab::Utils.system_silent([gitlab_shell_keys_path, Gitlab::Utils.system_silent([gitlab_shell_keys_path,
'add-key', key_id, key_content]) 'add-key', key_id, self.class.strip_key(key_content)])
end end
# Batch-add keys to authorized_keys # Batch-add keys to authorized_keys
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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