Merge branch 'master' into 19703-direct-link-pipelines
* master: (175 commits) Fix typo Always use `fixture_file_upload` helper to upload files in tests. Add CHANGELOG Fix extra spacing in all rgba methods in status file Improve spacing and fixes manual status color Add `ci-manual` status CSS with darkest gray color Move admin application spinach test to rspec Move admin deploy keys spinach test to rspec Fix CI/CD statuses actions' CSS on pipeline graphs Fix rubocop failures Store mattermost_url in settings Improve Mattermost Session specs Ensure the session is destroyed Improve session tests Setup mattermost session Fix link from doc/development/performance.md to 'Performance Monitoring' Fix query in Projects::ProjectMembersController to fetch members Improve test for sort dropdown on members page Fix sort dropdown alignment Undo changes on members search button stylesheet ...
This commit is contained in:
commit
54ab4adc33
|
@ -15,6 +15,7 @@ variables:
|
|||
USE_BUNDLE_INSTALL: "true"
|
||||
GIT_DEPTH: "20"
|
||||
PHANTOMJS_VERSION: "2.1.1"
|
||||
GET_SOURCES_ATTEMPTS: "3"
|
||||
|
||||
before_script:
|
||||
- source ./scripts/prepare_build.sh
|
||||
|
|
|
@ -292,7 +292,8 @@ Style/MultilineMethodDefinitionBraceLayout:
|
|||
|
||||
# Checks indentation of binary operations that span more than one line.
|
||||
Style/MultilineOperationIndentation:
|
||||
Enabled: false
|
||||
Enabled: true
|
||||
EnforcedStyle: indented
|
||||
|
||||
# Avoid multi-line `? :` (the ternary operator), use if/unless instead.
|
||||
Style/MultilineTernaryOperator:
|
||||
|
|
1
Gemfile
1
Gemfile
|
@ -22,7 +22,6 @@ gem 'doorkeeper', '~> 4.2.0'
|
|||
gem 'omniauth', '~> 1.3.1'
|
||||
gem 'omniauth-auth0', '~> 1.4.1'
|
||||
gem 'omniauth-azure-oauth2', '~> 0.0.6'
|
||||
gem 'omniauth-bitbucket', '~> 0.0.2'
|
||||
gem 'omniauth-cas3', '~> 1.1.2'
|
||||
gem 'omniauth-facebook', '~> 4.0.0'
|
||||
gem 'omniauth-github', '~> 1.1.1'
|
||||
|
|
|
@ -432,10 +432,6 @@ GEM
|
|||
jwt (~> 1.0)
|
||||
omniauth (~> 1.0)
|
||||
omniauth-oauth2 (~> 1.1)
|
||||
omniauth-bitbucket (0.0.2)
|
||||
multi_json (~> 1.7)
|
||||
omniauth (~> 1.1)
|
||||
omniauth-oauth (~> 1.0)
|
||||
omniauth-cas3 (1.1.3)
|
||||
addressable (~> 2.3)
|
||||
nokogiri (~> 1.6.6)
|
||||
|
@ -902,7 +898,6 @@ DEPENDENCIES
|
|||
omniauth (~> 1.3.1)
|
||||
omniauth-auth0 (~> 1.4.1)
|
||||
omniauth-azure-oauth2 (~> 0.0.6)
|
||||
omniauth-bitbucket (~> 0.0.2)
|
||||
omniauth-cas3 (~> 1.1.2)
|
||||
omniauth-facebook (~> 4.0.0)
|
||||
omniauth-github (~> 1.1.1)
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
licensePath: "/api/:version/templates/licenses/:key",
|
||||
gitignorePath: "/api/:version/templates/gitignores/:key",
|
||||
gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key",
|
||||
dockerfilePath: "/api/:version/dockerfiles/:key",
|
||||
issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key",
|
||||
group: function(group_id, callback) {
|
||||
var url = Api.buildUrl(Api.groupPath)
|
||||
|
@ -120,6 +121,10 @@
|
|||
return callback(file);
|
||||
});
|
||||
},
|
||||
dockerfileYml: function(key, callback) {
|
||||
var url = Api.buildUrl(Api.dockerfilePath).replace(':key', key);
|
||||
$.get(url, callback);
|
||||
},
|
||||
issueTemplate: function(namespacePath, projectPath, key, type, callback) {
|
||||
var url = Api.buildUrl(Api.issuableTemplatePath)
|
||||
.replace(':key', key)
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/* global Api */
|
||||
/*= require blob/template_selector */
|
||||
|
||||
(() => {
|
||||
const global = window.gl || (window.gl = {});
|
||||
|
||||
class BlobDockerfileSelector extends gl.TemplateSelector {
|
||||
requestFile(query) {
|
||||
return Api.dockerfileYml(query.name, this.requestFileSuccess.bind(this));
|
||||
}
|
||||
|
||||
requestFileSuccess(file) {
|
||||
return super.requestFileSuccess(file);
|
||||
}
|
||||
}
|
||||
|
||||
global.BlobDockerfileSelector = BlobDockerfileSelector;
|
||||
})();
|
|
@ -0,0 +1,27 @@
|
|||
(() => {
|
||||
const global = window.gl || (window.gl = {});
|
||||
|
||||
class BlobDockerfileSelectors {
|
||||
constructor({ editor, $dropdowns } = {}) {
|
||||
this.editor = editor;
|
||||
this.$dropdowns = $dropdowns || $('.js-dockerfile-selector');
|
||||
this.initSelectors();
|
||||
}
|
||||
|
||||
initSelectors() {
|
||||
const editor = this.editor;
|
||||
this.$dropdowns.each((i, dropdown) => {
|
||||
const $dropdown = $(dropdown);
|
||||
return new gl.BlobDockerfileSelector({
|
||||
editor,
|
||||
pattern: /(Dockerfile)/,
|
||||
data: $dropdown.data('data'),
|
||||
wrapper: $dropdown.closest('.js-dockerfile-selector-wrap'),
|
||||
dropdown: $dropdown,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
global.BlobDockerfileSelectors = BlobDockerfileSelectors;
|
||||
})();
|
|
@ -36,6 +36,9 @@
|
|||
new gl.BlobCiYamlSelectors({
|
||||
editor: this.editor
|
||||
});
|
||||
new gl.BlobDockerfileSelectors({
|
||||
editor: this.editor
|
||||
});
|
||||
}
|
||||
|
||||
EditBlob.prototype.initModePanesAndLinks = function() {
|
||||
|
|
|
@ -449,7 +449,7 @@
|
|||
</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<td class="environments-build-cell">
|
||||
<a v-if="shouldRenderBuildName"
|
||||
class="build-link"
|
||||
:href="model.last_deployment.deployable.build_path">
|
||||
|
|
|
@ -2,19 +2,28 @@
|
|||
|
||||
// Creates the variables for setting up GFM auto-completion
|
||||
(function() {
|
||||
if (window.GitLab == null) {
|
||||
window.GitLab = {};
|
||||
if (window.gl == null) {
|
||||
window.gl = {};
|
||||
}
|
||||
|
||||
function sanitize(str) {
|
||||
return str.replace(/<(?:.|\n)*?>/gm, '');
|
||||
}
|
||||
|
||||
window.GitLab.GfmAutoComplete = {
|
||||
dataLoading: false,
|
||||
dataLoaded: false,
|
||||
window.gl.GfmAutoComplete = {
|
||||
dataSources: {},
|
||||
defaultLoadingData: ['loading'],
|
||||
cachedData: {},
|
||||
dataSource: '',
|
||||
isLoadingData: {},
|
||||
atTypeMap: {
|
||||
':': 'emojis',
|
||||
'@': 'members',
|
||||
'#': 'issues',
|
||||
'!': 'mergeRequests',
|
||||
'~': 'labels',
|
||||
'%': 'milestones',
|
||||
'/': 'commands'
|
||||
},
|
||||
// Emoji
|
||||
Emoji: {
|
||||
template: '<li>${name} <img alt="${name}" height="20" src="${path}" width="20" /></li>'
|
||||
|
@ -35,33 +44,31 @@
|
|||
template: '<li>${title}</li>'
|
||||
},
|
||||
Loading: {
|
||||
template: '<li><i class="fa fa-refresh fa-spin"></i> Loading...</li>'
|
||||
template: '<li style="pointer-events: none;"><i class="fa fa-refresh fa-spin"></i> Loading...</li>'
|
||||
},
|
||||
DefaultOptions: {
|
||||
sorter: function(query, items, searchKey) {
|
||||
// Highlight first item only if at least one char was typed
|
||||
this.setting.highlightFirst = this.setting.alwaysHighlightFirst || query.length > 0;
|
||||
if ((items[0].name != null) && items[0].name === 'loading') {
|
||||
if (gl.GfmAutoComplete.isLoading(items)) {
|
||||
return items;
|
||||
}
|
||||
return $.fn.atwho["default"].callbacks.sorter(query, items, searchKey);
|
||||
},
|
||||
filter: function(query, data, searchKey) {
|
||||
if (data[0] === 'loading') {
|
||||
if (gl.GfmAutoComplete.isLoading(data)) {
|
||||
gl.GfmAutoComplete.togglePreventSelection.call(this, true);
|
||||
gl.GfmAutoComplete.fetchData(this.$inputor, this.at);
|
||||
return data;
|
||||
} else {
|
||||
gl.GfmAutoComplete.togglePreventSelection.call(this, false);
|
||||
return $.fn.atwho["default"].callbacks.filter(query, data, searchKey);
|
||||
}
|
||||
return $.fn.atwho["default"].callbacks.filter(query, data, searchKey);
|
||||
},
|
||||
beforeInsert: function(value) {
|
||||
if (value && !this.setting.skipSpecialCharacterTest) {
|
||||
var withoutAt = value.substring(1);
|
||||
if (withoutAt && /[^\w\d]/.test(withoutAt)) value = value.charAt() + '"' + withoutAt + '"';
|
||||
}
|
||||
if (!window.GitLab.GfmAutoComplete.dataLoaded) {
|
||||
return this.at;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
matcher: function (flag, subtext) {
|
||||
// The below is taken from At.js source
|
||||
|
@ -86,69 +93,46 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
setup: _.debounce(function(input) {
|
||||
setup: function(input) {
|
||||
// Add GFM auto-completion to all input fields, that accept GFM input.
|
||||
this.input = input || $('.js-gfm-input');
|
||||
// destroy previous instances
|
||||
this.destroyAtWho();
|
||||
// set up instances
|
||||
this.setupAtWho();
|
||||
|
||||
if (this.dataSource && !this.dataLoading && !this.cachedData) {
|
||||
this.dataLoading = true;
|
||||
return this.fetchData(this.dataSource)
|
||||
.done((data) => {
|
||||
this.dataLoading = false;
|
||||
this.loadData(data);
|
||||
});
|
||||
};
|
||||
|
||||
if (this.cachedData != null) {
|
||||
return this.loadData(this.cachedData);
|
||||
}
|
||||
}, 1000),
|
||||
setupAtWho: function() {
|
||||
this.setupLifecycle();
|
||||
},
|
||||
setupLifecycle() {
|
||||
this.input.each((i, input) => {
|
||||
const $input = $(input);
|
||||
$input.off('focus.setupAtWho').on('focus.setupAtWho', this.setupAtWho.bind(this, $input));
|
||||
});
|
||||
},
|
||||
setupAtWho: function($input) {
|
||||
// Emoji
|
||||
this.input.atwho({
|
||||
$input.atwho({
|
||||
at: ':',
|
||||
displayTpl: (function(_this) {
|
||||
return function(value) {
|
||||
if (value.path != null) {
|
||||
return _this.Emoji.template;
|
||||
} else {
|
||||
return _this.Loading.template;
|
||||
}
|
||||
};
|
||||
})(this),
|
||||
displayTpl: function(value) {
|
||||
return value.path != null ? this.Emoji.template : this.Loading.template;
|
||||
}.bind(this),
|
||||
insertTpl: ':${name}:',
|
||||
data: ['loading'],
|
||||
startWithSpace: false,
|
||||
skipSpecialCharacterTest: true,
|
||||
data: this.defaultLoadingData,
|
||||
callbacks: {
|
||||
sorter: this.DefaultOptions.sorter,
|
||||
filter: this.DefaultOptions.filter,
|
||||
beforeInsert: this.DefaultOptions.beforeInsert,
|
||||
matcher: this.DefaultOptions.matcher
|
||||
filter: this.DefaultOptions.filter
|
||||
}
|
||||
});
|
||||
// Team Members
|
||||
this.input.atwho({
|
||||
$input.atwho({
|
||||
at: '@',
|
||||
displayTpl: (function(_this) {
|
||||
return function(value) {
|
||||
if (value.username != null) {
|
||||
return _this.Members.template;
|
||||
} else {
|
||||
return _this.Loading.template;
|
||||
}
|
||||
};
|
||||
})(this),
|
||||
displayTpl: function(value) {
|
||||
return value.username != null ? this.Members.template : this.Loading.template;
|
||||
}.bind(this),
|
||||
insertTpl: '${atwho-at}${username}',
|
||||
searchKey: 'search',
|
||||
data: ['loading'],
|
||||
startWithSpace: false,
|
||||
alwaysHighlightFirst: true,
|
||||
skipSpecialCharacterTest: true,
|
||||
data: this.defaultLoadingData,
|
||||
callbacks: {
|
||||
sorter: this.DefaultOptions.sorter,
|
||||
filter: this.DefaultOptions.filter,
|
||||
|
@ -179,20 +163,14 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
this.input.atwho({
|
||||
$input.atwho({
|
||||
at: '#',
|
||||
alias: 'issues',
|
||||
searchKey: 'search',
|
||||
displayTpl: (function(_this) {
|
||||
return function(value) {
|
||||
if (value.title != null) {
|
||||
return _this.Issues.template;
|
||||
} else {
|
||||
return _this.Loading.template;
|
||||
}
|
||||
};
|
||||
})(this),
|
||||
data: ['loading'],
|
||||
displayTpl: function(value) {
|
||||
return value.title != null ? this.Issues.template : this.Loading.template;
|
||||
}.bind(this),
|
||||
data: this.defaultLoadingData,
|
||||
insertTpl: '${atwho-at}${id}',
|
||||
startWithSpace: false,
|
||||
callbacks: {
|
||||
|
@ -214,26 +192,21 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
this.input.atwho({
|
||||
$input.atwho({
|
||||
at: '%',
|
||||
alias: 'milestones',
|
||||
searchKey: 'search',
|
||||
displayTpl: (function(_this) {
|
||||
return function(value) {
|
||||
if (value.title != null) {
|
||||
return _this.Milestones.template;
|
||||
} else {
|
||||
return _this.Loading.template;
|
||||
}
|
||||
};
|
||||
})(this),
|
||||
insertTpl: '${atwho-at}${title}',
|
||||
data: ['loading'],
|
||||
displayTpl: function(value) {
|
||||
return value.title != null ? this.Milestones.template : this.Loading.template;
|
||||
}.bind(this),
|
||||
startWithSpace: false,
|
||||
data: this.defaultLoadingData,
|
||||
callbacks: {
|
||||
matcher: this.DefaultOptions.matcher,
|
||||
sorter: this.DefaultOptions.sorter,
|
||||
beforeInsert: this.DefaultOptions.beforeInsert,
|
||||
filter: this.DefaultOptions.filter,
|
||||
beforeSave: function(milestones) {
|
||||
return $.map(milestones, function(m) {
|
||||
if (m.title == null) {
|
||||
|
@ -248,21 +221,15 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
this.input.atwho({
|
||||
$input.atwho({
|
||||
at: '!',
|
||||
alias: 'mergerequests',
|
||||
searchKey: 'search',
|
||||
displayTpl: (function(_this) {
|
||||
return function(value) {
|
||||
if (value.title != null) {
|
||||
return _this.Issues.template;
|
||||
} else {
|
||||
return _this.Loading.template;
|
||||
}
|
||||
};
|
||||
})(this),
|
||||
data: ['loading'],
|
||||
startWithSpace: false,
|
||||
displayTpl: function(value) {
|
||||
return value.title != null ? this.Issues.template : this.Loading.template;
|
||||
}.bind(this),
|
||||
data: this.defaultLoadingData,
|
||||
insertTpl: '${atwho-at}${id}',
|
||||
callbacks: {
|
||||
sorter: this.DefaultOptions.sorter,
|
||||
|
@ -283,18 +250,31 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
this.input.atwho({
|
||||
$input.atwho({
|
||||
at: '~',
|
||||
alias: 'labels',
|
||||
searchKey: 'search',
|
||||
displayTpl: this.Labels.template,
|
||||
data: this.defaultLoadingData,
|
||||
displayTpl: function(value) {
|
||||
return this.isLoading(value) ? this.Loading.template : this.Labels.template;
|
||||
}.bind(this),
|
||||
insertTpl: '${atwho-at}${title}',
|
||||
startWithSpace: false,
|
||||
callbacks: {
|
||||
matcher: this.DefaultOptions.matcher,
|
||||
sorter: this.DefaultOptions.sorter,
|
||||
beforeInsert: this.DefaultOptions.beforeInsert,
|
||||
filter: this.DefaultOptions.filter,
|
||||
beforeSave: function(merges) {
|
||||
if (gl.GfmAutoComplete.isLoading(merges)) return merges;
|
||||
var sanitizeLabelTitle;
|
||||
sanitizeLabelTitle = function(title) {
|
||||
if (/[\w\?&]+\s+[\w\?&]+/g.test(title)) {
|
||||
return "\"" + (sanitize(title)) + "\"";
|
||||
} else {
|
||||
return sanitize(title);
|
||||
}
|
||||
};
|
||||
return $.map(merges, function(m) {
|
||||
return {
|
||||
title: sanitize(m.title),
|
||||
|
@ -306,12 +286,14 @@
|
|||
}
|
||||
});
|
||||
// We don't instantiate the slash commands autocomplete for note and issue/MR edit forms
|
||||
this.input.filter('[data-supports-slash-commands="true"]').atwho({
|
||||
$input.filter('[data-supports-slash-commands="true"]').atwho({
|
||||
at: '/',
|
||||
alias: 'commands',
|
||||
searchKey: 'search',
|
||||
skipSpecialCharacterTest: true,
|
||||
data: this.defaultLoadingData,
|
||||
displayTpl: function(value) {
|
||||
if (this.isLoading(value)) return this.Loading.template;
|
||||
var tpl = '<li>/${name}';
|
||||
if (value.aliases.length > 0) {
|
||||
tpl += ' <small>(or /<%- aliases.join(", /") %>)</small>';
|
||||
|
@ -324,7 +306,7 @@
|
|||
}
|
||||
tpl += '</li>';
|
||||
return _.template(tpl)(value);
|
||||
},
|
||||
}.bind(this),
|
||||
insertTpl: function(value) {
|
||||
var tpl = "/${name} ";
|
||||
var reference_prefix = null;
|
||||
|
@ -342,6 +324,7 @@
|
|||
filter: this.DefaultOptions.filter,
|
||||
beforeInsert: this.DefaultOptions.beforeInsert,
|
||||
beforeSave: function(commands) {
|
||||
if (gl.GfmAutoComplete.isLoading(commands)) return commands;
|
||||
return $.map(commands, function(c) {
|
||||
var search = c.name;
|
||||
if (c.aliases.length > 0) {
|
||||
|
@ -369,32 +352,40 @@
|
|||
});
|
||||
return;
|
||||
},
|
||||
destroyAtWho: function() {
|
||||
return this.input.atwho('destroy');
|
||||
fetchData: function($input, at) {
|
||||
if (this.isLoadingData[at]) return;
|
||||
this.isLoadingData[at] = true;
|
||||
if (this.cachedData[at]) {
|
||||
this.loadData($input, at, this.cachedData[at]);
|
||||
} else {
|
||||
$.getJSON(this.dataSources[this.atTypeMap[at]], (data) => {
|
||||
this.loadData($input, at, data);
|
||||
}).fail(() => { this.isLoadingData[at] = false; });
|
||||
}
|
||||
},
|
||||
fetchData: function(dataSource) {
|
||||
return $.getJSON(dataSource);
|
||||
},
|
||||
loadData: function(data) {
|
||||
this.cachedData = data;
|
||||
this.dataLoaded = true;
|
||||
// load members
|
||||
this.input.atwho('load', '@', data.members);
|
||||
// load issues
|
||||
this.input.atwho('load', 'issues', data.issues);
|
||||
// load milestones
|
||||
this.input.atwho('load', 'milestones', data.milestones);
|
||||
// load merge requests
|
||||
this.input.atwho('load', 'mergerequests', data.mergerequests);
|
||||
// load emojis
|
||||
this.input.atwho('load', ':', data.emojis);
|
||||
// load labels
|
||||
this.input.atwho('load', '~', data.labels);
|
||||
// load commands
|
||||
this.input.atwho('load', '/', data.commands);
|
||||
loadData: function($input, at, data) {
|
||||
this.isLoadingData[at] = false;
|
||||
this.cachedData[at] = data;
|
||||
$input.atwho('load', at, data);
|
||||
// This trigger at.js again
|
||||
// otherwise we would be stuck with loading until the user types
|
||||
return $(':focus').trigger('keyup');
|
||||
return $input.trigger('keyup');
|
||||
},
|
||||
isLoading(data) {
|
||||
if (!data) return false;
|
||||
if (Array.isArray(data)) data = data[0];
|
||||
return data === this.defaultLoadingData[0] || data.name === this.defaultLoadingData[0];
|
||||
},
|
||||
togglePreventSelection(isPrevented = !!this.setting.tabSelectsMatch) {
|
||||
this.setting.tabSelectsMatch = !isPrevented;
|
||||
this.setting.spaceSelectsMatch = !isPrevented;
|
||||
const eventListenerAction = `${isPrevented ? 'add' : 'remove'}EventListener`;
|
||||
this.$inputor[0][eventListenerAction]('keydown', gl.GfmAutoComplete.preventSpaceTabEnter);
|
||||
},
|
||||
preventSpaceTabEnter(e) {
|
||||
const key = e.which || e.keyCode;
|
||||
const preventables = [9, 13, 32];
|
||||
if (preventables.indexOf(key) > -1) e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
this.form.addClass('gfm-form');
|
||||
// remove notify commit author checkbox for non-commit notes
|
||||
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
|
||||
GitLab.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
|
||||
gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
|
||||
new DropzoneInput(this.form);
|
||||
autosize(this.textarea);
|
||||
// form and textarea event listeners
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
this.renderWipExplanation = bind(this.renderWipExplanation, this);
|
||||
this.resetAutosave = bind(this.resetAutosave, this);
|
||||
this.handleSubmit = bind(this.handleSubmit, this);
|
||||
GitLab.GfmAutoComplete.setup();
|
||||
gl.GfmAutoComplete.setup();
|
||||
new UsersSelect();
|
||||
new ZenMode();
|
||||
this.titleField = this.form.find("input[name*='[title]']");
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, padded-blocks, max-len */
|
||||
/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, padded-blocks, max-len, prefer-arrow-callback */
|
||||
/* global MergeRequestTabs */
|
||||
|
||||
/*= require jquery.waitforimages */
|
||||
|
@ -27,6 +27,7 @@
|
|||
// Prevent duplicate event bindings
|
||||
this.disableTaskList();
|
||||
this.initMRBtnListeners();
|
||||
this.initCommitMessageListeners();
|
||||
if ($("a.btn-close").length) {
|
||||
this.initTaskList();
|
||||
}
|
||||
|
@ -108,6 +109,26 @@
|
|||
// note so that we can re-use its form here
|
||||
};
|
||||
|
||||
MergeRequest.prototype.initCommitMessageListeners = function() {
|
||||
var textarea = $('textarea.js-commit-message');
|
||||
|
||||
$('a.js-with-description-link').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
textarea.val(textarea.data('messageWithDescription'));
|
||||
$('p.js-with-description-hint').hide();
|
||||
$('p.js-without-description-hint').show();
|
||||
});
|
||||
|
||||
$('a.js-without-description-link').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
textarea.val(textarea.data('messageWithoutDescription'));
|
||||
$('p.js-with-description-hint').show();
|
||||
$('p.js-without-description-hint').hide();
|
||||
});
|
||||
};
|
||||
|
||||
return MergeRequest;
|
||||
|
||||
})();
|
||||
|
|
|
@ -49,3 +49,11 @@
|
|||
fill: $gray-darkest;
|
||||
}
|
||||
}
|
||||
|
||||
.ci-status-icon-manual {
|
||||
color: $gl-text-color;
|
||||
|
||||
svg {
|
||||
fill: $gl-text-color;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,41 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
.alert-wrapper {
|
||||
margin-bottom: $gl-padding;
|
||||
|
||||
.alert {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Stripe the background colors so that adjacent alert-warnings are distinct from one another */
|
||||
.alert-warning {
|
||||
transition: background-color 0.15s, border-color 0.15s;
|
||||
background-color: lighten($gl-warning, 4%);
|
||||
border-color: lighten($gl-warning, 4%);
|
||||
}
|
||||
|
||||
.alert-warning + .alert-warning {
|
||||
background-color: $gl-warning;
|
||||
border-color: $gl-warning;
|
||||
}
|
||||
|
||||
.alert-warning + .alert-warning + .alert-warning {
|
||||
background-color: darken($gl-warning, 4%);
|
||||
border-color: darken($gl-warning, 4%);
|
||||
}
|
||||
|
||||
.alert-warning + .alert-warning + .alert-warning + .alert-warning {
|
||||
background-color: darken($gl-warning, 8%);
|
||||
border-color: darken($gl-warning, 8%);
|
||||
}
|
||||
|
||||
.alert-warning:only-of-type {
|
||||
background-color: $gl-warning;
|
||||
border-color: $gl-warning;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* The following prevents side effects related to iOS Safari's implementation of -webkit-overflow-scrolling: touch,
|
||||
which is applied to the body by jquery.nicescroling plugin to force hardware acceleration for momentum scrolling. Side
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
@import "bootstrap/alerts";
|
||||
// @import "bootstrap/progress-bars";
|
||||
@import "bootstrap/list-group";
|
||||
// @import "bootstrap/wells";
|
||||
@import "bootstrap/wells";
|
||||
@import "bootstrap/close";
|
||||
@import "bootstrap/panels";
|
||||
|
||||
|
|
|
@ -438,7 +438,7 @@ $jq-ui-default-color: #777;
|
|||
$label-gray-bg: #f8fafc;
|
||||
$label-inverse-bg: #333;
|
||||
$label-remove-border: rgba(0, 0, 0, .1);
|
||||
$label-border-radius: 14px;
|
||||
$label-border-radius: 100px;
|
||||
|
||||
/*
|
||||
* Lint
|
||||
|
@ -474,7 +474,6 @@ $project-option-descr-color: #54565b;
|
|||
$project-breadcrumb-color: #999;
|
||||
$project-private-forks-notice-odd: #2aa056;
|
||||
$project-network-controls-color: #888;
|
||||
$project-limit-message-bg: #f28d35;
|
||||
|
||||
/*
|
||||
* Runners
|
||||
|
|
|
@ -75,7 +75,8 @@
|
|||
.soft-wrap-toggle,
|
||||
.license-selector,
|
||||
.gitignore-selector,
|
||||
.gitlab-ci-yml-selector {
|
||||
.gitlab-ci-yml-selector,
|
||||
.dockerfile-selector {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
font-family: $regular_font;
|
||||
|
@ -105,7 +106,8 @@
|
|||
|
||||
.gitignore-selector,
|
||||
.license-selector,
|
||||
.gitlab-ci-yml-selector {
|
||||
.gitlab-ci-yml-selector,
|
||||
.dockerfile-selector {
|
||||
.dropdown {
|
||||
line-height: 21px;
|
||||
}
|
||||
|
|
|
@ -30,19 +30,25 @@
|
|||
display: table-cell;
|
||||
}
|
||||
|
||||
.environments-name,
|
||||
.environments-commit,
|
||||
.environments-actions {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.environments-deploy,
|
||||
.environments-build,
|
||||
.environments-date {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
.environments-name {
|
||||
width: 30%;
|
||||
.environments-deploy,
|
||||
.environments-build {
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
.environment-name,
|
||||
.environments-build-cell,
|
||||
.deployment-column {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.deployment-column {
|
||||
|
|
|
@ -98,7 +98,7 @@
|
|||
}
|
||||
|
||||
.label {
|
||||
padding: 9px;
|
||||
padding: 8px 9px 9px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
@ -201,6 +201,8 @@
|
|||
.label-remove {
|
||||
border-left: 1px solid $label-remove-border;
|
||||
z-index: 3;
|
||||
border-radius: $label-border-radius;
|
||||
padding: 6px 10px 6px 9px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
|
|
|
@ -78,6 +78,21 @@
|
|||
float: right;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
width: 100%;
|
||||
margin-top: 5px;
|
||||
|
||||
.dropdown-menu-toggle {
|
||||
vertical-align: middle;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
margin-top: 0;
|
||||
width: 155px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding-right: 35px;
|
||||
|
@ -85,12 +100,22 @@
|
|||
@media (min-width: $screen-sm-min) {
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
&.input-short {
|
||||
@media (min-width: $screen-md-min) {
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
@media (min-width: $screen-lg-min) {
|
||||
width: 210px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.member-search-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
right: 4px;
|
||||
top: 0;
|
||||
height: 35px;
|
||||
padding-left: 10px;
|
||||
|
@ -99,4 +124,8 @@
|
|||
background: transparent;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
right: 160px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -262,3 +262,13 @@ table.u2f-registrations {
|
|||
border-right: solid 1px transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.oauth-application-show {
|
||||
.scope-name {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.scopes-list {
|
||||
padding-left: 18px;
|
||||
}
|
||||
}
|
|
@ -6,12 +6,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.no-ssh-key-message,
|
||||
.project-limit-message {
|
||||
background-color: $project-limit-message-bg;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.new_project,
|
||||
.edit-project {
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
.container-fluid {
|
||||
.ci-status {
|
||||
display: inline-block;
|
||||
padding: 2px 7px;
|
||||
margin-right: 10px;
|
||||
border: 1px solid $gray-darker;
|
||||
|
@ -15,8 +16,7 @@
|
|||
height: 13px;
|
||||
width: 13px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
margin-right: 3px;
|
||||
top: 2px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
|||
border-color: $gl-danger;
|
||||
|
||||
&:not(span):hover {
|
||||
background-color: rgba( $gl-danger, .07);
|
||||
background-color: rgba($gl-danger, .07);
|
||||
}
|
||||
|
||||
svg {
|
||||
|
@ -39,7 +39,7 @@
|
|||
border-color: $gl-success;
|
||||
|
||||
&:not(span):hover {
|
||||
background-color: rgba( $gl-success, .07);
|
||||
background-color: rgba($gl-success, .07);
|
||||
}
|
||||
|
||||
svg {
|
||||
|
@ -52,7 +52,7 @@
|
|||
border-color: $gl-info;
|
||||
|
||||
&:not(span):hover {
|
||||
background-color: rgba( $gl-info, .07);
|
||||
background-color: rgba($gl-info, .07);
|
||||
}
|
||||
|
||||
svg {
|
||||
|
@ -66,7 +66,7 @@
|
|||
border-color: $gl-gray;
|
||||
|
||||
&:not(span):hover {
|
||||
background-color: rgba( $gl-gray, .07);
|
||||
background-color: rgba($gl-gray, .07);
|
||||
}
|
||||
|
||||
svg {
|
||||
|
@ -79,7 +79,7 @@
|
|||
border-color: $gl-warning;
|
||||
|
||||
&:not(span):hover {
|
||||
background-color: rgba( $gl-warning, .07);
|
||||
background-color: rgba($gl-warning, .07);
|
||||
}
|
||||
|
||||
svg {
|
||||
|
@ -92,7 +92,7 @@
|
|||
border-color: $blue-normal;
|
||||
|
||||
&:not(span):hover {
|
||||
background-color: rgba( $blue-normal, .07);
|
||||
background-color: rgba($blue-normal, .07);
|
||||
}
|
||||
|
||||
svg {
|
||||
|
@ -106,13 +106,26 @@
|
|||
border-color: $gl-gray-light;
|
||||
|
||||
&:not(span):hover {
|
||||
background-color: rgba( $gl-gray-light, .07);
|
||||
background-color: rgba($gl-gray-light, .07);
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: $gl-gray-light;
|
||||
}
|
||||
}
|
||||
|
||||
&.ci-manual {
|
||||
color: $gl-text-color;
|
||||
border-color: $gl-text-color;
|
||||
|
||||
&:not(span):hover {
|
||||
background-color: rgba($gl-text-color, .07);
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: $gl-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
class Admin::ApplicationsController < Admin::ApplicationController
|
||||
include OauthApplications
|
||||
|
||||
before_action :set_application, only: [:show, :edit, :update, :destroy]
|
||||
before_action :load_scopes, only: [:new, :edit]
|
||||
|
||||
def index
|
||||
@applications = Doorkeeper::Application.where("owner_id IS NULL")
|
||||
|
@ -47,6 +50,6 @@ class Admin::ApplicationsController < Admin::ApplicationController
|
|||
|
||||
# Only allow a trusted parameter "white list" through.
|
||||
def application_params
|
||||
params[:doorkeeper_application].permit(:name, :redirect_uri)
|
||||
params[:doorkeeper_application].permit(:name, :redirect_uri, :scopes)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -262,7 +262,7 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
def bitbucket_import_configured?
|
||||
Gitlab::OAuth::Provider.enabled?(:bitbucket) && Gitlab::BitbucketImport.public_key.present?
|
||||
Gitlab::OAuth::Provider.enabled?(:bitbucket)
|
||||
end
|
||||
|
||||
def google_code_import_enabled?
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
module OauthApplications
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :prepare_scopes, only: [:create, :update]
|
||||
end
|
||||
|
||||
def prepare_scopes
|
||||
scopes = params.fetch(:doorkeeper_application, {}).fetch(:scopes, nil)
|
||||
|
||||
if scopes
|
||||
params[:doorkeeper_application][:scopes] = scopes.join(' ')
|
||||
end
|
||||
end
|
||||
|
||||
def load_scopes
|
||||
@scopes = Doorkeeper.configuration.scopes
|
||||
end
|
||||
end
|
|
@ -1,20 +1,20 @@
|
|||
class Groups::GroupMembersController < Groups::ApplicationController
|
||||
include MembershipActions
|
||||
include SortingHelper
|
||||
|
||||
# Authorize
|
||||
before_action :authorize_admin_group_member!, except: [:index, :leave, :request_access]
|
||||
|
||||
def index
|
||||
@sort = params[:sort].presence || sort_value_name
|
||||
@project = @group.projects.find(params[:project_id]) if params[:project_id]
|
||||
|
||||
@members = @group.group_members
|
||||
@members = @members.non_invite unless can?(current_user, :admin_group, @group)
|
||||
@members = @members.search(params[:search]) if params[:search].present?
|
||||
@members = @members.sort(@sort)
|
||||
@members = @members.page(params[:page]).per(50)
|
||||
|
||||
if params[:search].present?
|
||||
users = @group.users.search(params[:search]).to_a
|
||||
@members = @members.where(user_id: users)
|
||||
end
|
||||
|
||||
@members = @members.order('access_level DESC').page(params[:page]).per(50)
|
||||
@requesters = AccessRequestsFinder.new(@group).execute(current_user)
|
||||
|
||||
@group_member = @group.group_members.new
|
||||
|
|
|
@ -2,50 +2,57 @@ class Import::BitbucketController < Import::BaseController
|
|||
before_action :verify_bitbucket_import_enabled
|
||||
before_action :bitbucket_auth, except: :callback
|
||||
|
||||
rescue_from OAuth::Error, with: :bitbucket_unauthorized
|
||||
rescue_from Gitlab::BitbucketImport::Client::Unauthorized, with: :bitbucket_unauthorized
|
||||
rescue_from OAuth2::Error, with: :bitbucket_unauthorized
|
||||
rescue_from Bitbucket::Error::Unauthorized, with: :bitbucket_unauthorized
|
||||
|
||||
def callback
|
||||
request_token = session.delete(:oauth_request_token)
|
||||
raise "Session expired!" if request_token.nil?
|
||||
response = client.auth_code.get_token(params[:code], redirect_uri: callback_import_bitbucket_url)
|
||||
|
||||
request_token.symbolize_keys!
|
||||
|
||||
access_token = client.get_token(request_token, params[:oauth_verifier], callback_import_bitbucket_url)
|
||||
|
||||
session[:bitbucket_access_token] = access_token.token
|
||||
session[:bitbucket_access_token_secret] = access_token.secret
|
||||
session[:bitbucket_token] = response.token
|
||||
session[:bitbucket_expires_at] = response.expires_at
|
||||
session[:bitbucket_expires_in] = response.expires_in
|
||||
session[:bitbucket_refresh_token] = response.refresh_token
|
||||
|
||||
redirect_to status_import_bitbucket_url
|
||||
end
|
||||
|
||||
def status
|
||||
@repos = client.projects
|
||||
@incompatible_repos = client.incompatible_projects
|
||||
bitbucket_client = Bitbucket::Client.new(credentials)
|
||||
repos = bitbucket_client.repos
|
||||
|
||||
@already_added_projects = current_user.created_projects.where(import_type: "bitbucket")
|
||||
@repos, @incompatible_repos = repos.partition { |repo| repo.valid? }
|
||||
|
||||
@already_added_projects = current_user.created_projects.where(import_type: 'bitbucket')
|
||||
already_added_projects_names = @already_added_projects.pluck(:import_source)
|
||||
|
||||
@repos.to_a.reject!{ |repo| already_added_projects_names.include? "#{repo["owner"]}/#{repo["slug"]}" }
|
||||
@repos.to_a.reject! { |repo| already_added_projects_names.include?(repo.full_name) }
|
||||
end
|
||||
|
||||
def jobs
|
||||
jobs = current_user.created_projects.where(import_type: "bitbucket").to_json(only: [:id, :import_status])
|
||||
render json: jobs
|
||||
render json: current_user.created_projects
|
||||
.where(import_type: 'bitbucket')
|
||||
.to_json(only: [:id, :import_status])
|
||||
end
|
||||
|
||||
def create
|
||||
bitbucket_client = Bitbucket::Client.new(credentials)
|
||||
|
||||
@repo_id = params[:repo_id].to_s
|
||||
repo = client.project(@repo_id.gsub('___', '/'))
|
||||
@project_name = repo['slug']
|
||||
@target_namespace = find_or_create_namespace(repo['owner'], client.user['user']['username'])
|
||||
name = @repo_id.gsub('___', '/')
|
||||
repo = bitbucket_client.repo(name)
|
||||
@project_name = params[:new_name].presence || repo.name
|
||||
|
||||
unless Gitlab::BitbucketImport::KeyAdder.new(repo, current_user, access_params).execute
|
||||
render 'deploy_key' and return
|
||||
end
|
||||
repo_owner = repo.owner
|
||||
repo_owner = current_user.username if repo_owner == bitbucket_client.user.username
|
||||
@target_namespace = params[:new_namespace].presence || repo_owner
|
||||
|
||||
if current_user.can?(:create_projects, @target_namespace)
|
||||
@project = Gitlab::BitbucketImport::ProjectCreator.new(repo, @target_namespace, current_user, access_params).execute
|
||||
namespace = find_or_create_namespace(@target_namespace, current_user)
|
||||
|
||||
if current_user.can?(:create_projects, namespace)
|
||||
# The token in a session can be expired, we need to get most recent one because
|
||||
# Bitbucket::Connection class refreshes it.
|
||||
session[:bitbucket_token] = bitbucket_client.connection.token
|
||||
@project = Gitlab::BitbucketImport::ProjectCreator.new(repo, @project_name, namespace, current_user, credentials).execute
|
||||
else
|
||||
render 'unauthorized'
|
||||
end
|
||||
|
@ -54,8 +61,15 @@ class Import::BitbucketController < Import::BaseController
|
|||
private
|
||||
|
||||
def client
|
||||
@client ||= Gitlab::BitbucketImport::Client.new(session[:bitbucket_access_token],
|
||||
session[:bitbucket_access_token_secret])
|
||||
@client ||= OAuth2::Client.new(provider.app_id, provider.app_secret, options)
|
||||
end
|
||||
|
||||
def provider
|
||||
Gitlab::OAuth::Provider.config_for('bitbucket')
|
||||
end
|
||||
|
||||
def options
|
||||
OmniAuth::Strategies::Bitbucket.default_options[:client_options].deep_symbolize_keys
|
||||
end
|
||||
|
||||
def verify_bitbucket_import_enabled
|
||||
|
@ -63,26 +77,23 @@ class Import::BitbucketController < Import::BaseController
|
|||
end
|
||||
|
||||
def bitbucket_auth
|
||||
if session[:bitbucket_access_token].blank?
|
||||
go_to_bitbucket_for_permissions
|
||||
end
|
||||
go_to_bitbucket_for_permissions if session[:bitbucket_token].blank?
|
||||
end
|
||||
|
||||
def go_to_bitbucket_for_permissions
|
||||
request_token = client.request_token(callback_import_bitbucket_url)
|
||||
session[:oauth_request_token] = request_token
|
||||
|
||||
redirect_to client.authorize_url(request_token, callback_import_bitbucket_url)
|
||||
redirect_to client.auth_code.authorize_url(redirect_uri: callback_import_bitbucket_url)
|
||||
end
|
||||
|
||||
def bitbucket_unauthorized
|
||||
go_to_bitbucket_for_permissions
|
||||
end
|
||||
|
||||
def access_params
|
||||
def credentials
|
||||
{
|
||||
bitbucket_access_token: session[:bitbucket_access_token],
|
||||
bitbucket_access_token_secret: session[:bitbucket_access_token_secret]
|
||||
token: session[:bitbucket_token],
|
||||
expires_at: session[:bitbucket_expires_at],
|
||||
expires_in: session[:bitbucket_expires_in],
|
||||
refresh_token: session[:bitbucket_refresh_token]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,7 +26,7 @@ class JwtController < ApplicationController
|
|||
@authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip)
|
||||
|
||||
render_unauthorized unless @authentication_result.success? &&
|
||||
(@authentication_result.actor.nil? || @authentication_result.actor.is_a?(User))
|
||||
(@authentication_result.actor.nil? || @authentication_result.actor.is_a?(User))
|
||||
end
|
||||
rescue Gitlab::Auth::MissingPersonalTokenError
|
||||
render_missing_personal_token
|
||||
|
|
|
@ -2,10 +2,12 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
|
|||
include Gitlab::CurrentSettings
|
||||
include Gitlab::GonHelper
|
||||
include PageLayoutHelper
|
||||
include OauthApplications
|
||||
|
||||
before_action :verify_user_oauth_applications_enabled
|
||||
before_action :authenticate_user!
|
||||
before_action :add_gon_variables
|
||||
before_action :load_scopes, only: [:index, :create, :edit]
|
||||
|
||||
layout 'profile'
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
|
||||
before_action :load_personal_access_tokens, only: :index
|
||||
|
||||
def index
|
||||
@personal_access_token = current_user.personal_access_tokens.build
|
||||
set_index_vars
|
||||
end
|
||||
|
||||
def create
|
||||
|
@ -12,7 +10,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
|
|||
flash[:personal_access_token] = @personal_access_token.token
|
||||
redirect_to profile_personal_access_tokens_path, notice: "Your new personal access token has been created."
|
||||
else
|
||||
load_personal_access_tokens
|
||||
set_index_vars
|
||||
render :index
|
||||
end
|
||||
end
|
||||
|
@ -32,10 +30,12 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
|
|||
private
|
||||
|
||||
def personal_access_token_params
|
||||
params.require(:personal_access_token).permit(:name, :expires_at)
|
||||
params.require(:personal_access_token).permit(:name, :expires_at, scopes: [])
|
||||
end
|
||||
|
||||
def load_personal_access_tokens
|
||||
def set_index_vars
|
||||
@personal_access_token ||= current_user.personal_access_tokens.build
|
||||
@scopes = Gitlab::Auth::SCOPES
|
||||
@active_personal_access_tokens = current_user.personal_access_tokens.active.order(:expires_at)
|
||||
@inactive_personal_access_tokens = current_user.personal_access_tokens.inactive
|
||||
end
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
class Projects::AutocompleteSourcesController < Projects::ApplicationController
|
||||
before_action :load_autocomplete_service, except: [:emojis, :members]
|
||||
|
||||
def emojis
|
||||
render json: Gitlab::AwardEmoji.urls
|
||||
end
|
||||
|
||||
def members
|
||||
render json: ::Projects::ParticipantsService.new(@project, current_user).execute(noteable)
|
||||
end
|
||||
|
||||
def issues
|
||||
render json: @autocomplete_service.issues
|
||||
end
|
||||
|
||||
def merge_requests
|
||||
render json: @autocomplete_service.merge_requests
|
||||
end
|
||||
|
||||
def labels
|
||||
render json: @autocomplete_service.labels
|
||||
end
|
||||
|
||||
def milestones
|
||||
render json: @autocomplete_service.milestones
|
||||
end
|
||||
|
||||
def commands
|
||||
render json: @autocomplete_service.commands(noteable, params[:type])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_autocomplete_service
|
||||
@autocomplete_service = ::Projects::AutocompleteService.new(@project, current_user)
|
||||
end
|
||||
|
||||
def noteable
|
||||
case params[:type]
|
||||
when 'Issue'
|
||||
IssuesFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:type_id])
|
||||
when 'MergeRequest'
|
||||
MergeRequestsFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:type_id])
|
||||
when 'Commit'
|
||||
@project.commit(params[:type_id])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,10 +1,12 @@
|
|||
class Projects::ProjectMembersController < Projects::ApplicationController
|
||||
include MembershipActions
|
||||
include SortingHelper
|
||||
|
||||
# Authorize
|
||||
before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access]
|
||||
|
||||
def index
|
||||
@sort = params[:sort].presence || sort_value_name
|
||||
@group_links = @project.project_group_links
|
||||
|
||||
@project_members = @project.project_members
|
||||
|
@ -35,12 +37,13 @@ class Projects::ProjectMembersController < Projects::ApplicationController
|
|||
@group_links = @project.project_group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
|
||||
end
|
||||
|
||||
wheres = ["id IN (#{@project_members.select(:id).to_sql})"]
|
||||
wheres << "id IN (#{group_members.select(:id).to_sql})" if group_members
|
||||
wheres = ["members.id IN (#{@project_members.select(:id).to_sql})"]
|
||||
wheres << "members.id IN (#{group_members.select(:id).to_sql})" if group_members
|
||||
|
||||
@project_members = Member.
|
||||
where(wheres.join(' OR ')).
|
||||
order(access_level: :desc).page(params[:page])
|
||||
sort(@sort).
|
||||
page(params[:page])
|
||||
|
||||
@requesters = AccessRequestsFinder.new(@project).execute(current_user)
|
||||
|
||||
|
|
|
@ -127,39 +127,6 @@ class ProjectsController < Projects::ApplicationController
|
|||
redirect_to edit_project_path(@project), alert: ex.message
|
||||
end
|
||||
|
||||
def autocomplete_sources
|
||||
noteable =
|
||||
case params[:type]
|
||||
when 'Issue'
|
||||
IssuesFinder.new(current_user, project_id: @project.id).
|
||||
execute.find_by(iid: params[:type_id])
|
||||
when 'MergeRequest'
|
||||
MergeRequestsFinder.new(current_user, project_id: @project.id).
|
||||
execute.find_by(iid: params[:type_id])
|
||||
when 'Commit'
|
||||
@project.commit(params[:type_id])
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
autocomplete = ::Projects::AutocompleteService.new(@project, current_user)
|
||||
participants = ::Projects::ParticipantsService.new(@project, current_user).execute(noteable)
|
||||
|
||||
@suggestions = {
|
||||
emojis: Gitlab::AwardEmoji.urls,
|
||||
issues: autocomplete.issues,
|
||||
milestones: autocomplete.milestones,
|
||||
mergerequests: autocomplete.merge_requests,
|
||||
labels: autocomplete.labels,
|
||||
members: participants,
|
||||
commands: autocomplete.commands(noteable, params[:type])
|
||||
}
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: @suggestions }
|
||||
end
|
||||
end
|
||||
|
||||
def new_issue_address
|
||||
return render_404 unless Gitlab::IncomingEmail.supports_issue_creation?
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@ class SessionsController < Devise::SessionsController
|
|||
|
||||
def valid_otp_attempt?(user)
|
||||
user.validate_and_consume_otp!(user_params[:otp_attempt]) ||
|
||||
user.invalidate_otp_backup_code!(user_params[:otp_attempt])
|
||||
user.invalidate_otp_backup_code!(user_params[:otp_attempt])
|
||||
end
|
||||
|
||||
def log_audit_event(user, options = {})
|
||||
|
|
|
@ -191,6 +191,10 @@ module BlobHelper
|
|||
@gitlab_ci_ymls ||= Gitlab::Template::GitlabCiYmlTemplate.dropdown_names
|
||||
end
|
||||
|
||||
def dockerfile_names
|
||||
@dockerfile_names ||= Gitlab::Template::DockerfileTemplate.dropdown_names
|
||||
end
|
||||
|
||||
def blob_editor_paths
|
||||
{
|
||||
'relative-url-root' => Rails.application.config.relative_url_root,
|
||||
|
|
|
@ -7,12 +7,12 @@ module FormHelper
|
|||
|
||||
content_tag(:div, class: 'alert alert-danger', id: 'error_explanation') do
|
||||
content_tag(:h4, headline) <<
|
||||
content_tag(:ul) do
|
||||
model.errors.full_messages.
|
||||
map { |msg| content_tag(:li, msg) }.
|
||||
join.
|
||||
html_safe
|
||||
end
|
||||
content_tag(:ul) do
|
||||
model.errors.full_messages.
|
||||
map { |msg| content_tag(:li, msg) }.
|
||||
join.
|
||||
html_safe
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -36,4 +36,12 @@ module MembersHelper
|
|||
"Are you sure you want to leave the " \
|
||||
"\"#{member_source.human_name}\" #{member_source.class.to_s.humanize(capitalize: false)}?"
|
||||
end
|
||||
|
||||
def filter_group_project_member_path(options = {})
|
||||
options = params.slice(:search, :sort).merge(options)
|
||||
|
||||
path = request.path
|
||||
path << "?#{options.to_param}"
|
||||
path
|
||||
end
|
||||
end
|
||||
|
|
|
@ -59,6 +59,10 @@ module MergeRequestsHelper
|
|||
@mr_closes_issues ||= @merge_request.closes_issues
|
||||
end
|
||||
|
||||
def mr_issues_mentioned_but_not_closing
|
||||
@mr_issues_mentioned_but_not_closing ||= @merge_request.issues_mentioned_but_not_closing
|
||||
end
|
||||
|
||||
def mr_change_branches_path(merge_request)
|
||||
new_namespace_project_merge_request_path(
|
||||
@project.namespace, @project,
|
||||
|
|
|
@ -7,12 +7,12 @@ module NavHelper
|
|||
|
||||
def page_gutter_class
|
||||
if current_path?('merge_requests#show') ||
|
||||
current_path?('merge_requests#diffs') ||
|
||||
current_path?('merge_requests#commits') ||
|
||||
current_path?('merge_requests#builds') ||
|
||||
current_path?('merge_requests#conflicts') ||
|
||||
current_path?('merge_requests#pipelines') ||
|
||||
current_path?('issues#show')
|
||||
current_path?('merge_requests#diffs') ||
|
||||
current_path?('merge_requests#commits') ||
|
||||
current_path?('merge_requests#builds') ||
|
||||
current_path?('merge_requests#conflicts') ||
|
||||
current_path?('merge_requests#pipelines') ||
|
||||
current_path?('issues#show')
|
||||
if cookies[:collapsed_gutter] == 'true'
|
||||
"page-gutter right-sidebar-collapsed"
|
||||
else
|
||||
|
@ -21,9 +21,9 @@ module NavHelper
|
|||
elsif current_path?('builds#show')
|
||||
"page-gutter build-sidebar right-sidebar-expanded"
|
||||
elsif current_path?('wikis#show') ||
|
||||
current_path?('wikis#edit') ||
|
||||
current_path?('wikis#history') ||
|
||||
current_path?('wikis#git_access')
|
||||
current_path?('wikis#edit') ||
|
||||
current_path?('wikis#history') ||
|
||||
current_path?('wikis#git_access')
|
||||
"page-gutter wiki-sidebar right-sidebar-expanded"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -25,7 +25,7 @@ module SortingHelper
|
|||
sort_value_recently_updated => sort_title_recently_updated,
|
||||
sort_value_oldest_updated => sort_title_oldest_updated,
|
||||
sort_value_recently_created => sort_title_recently_created,
|
||||
sort_value_oldest_created => sort_title_oldest_created,
|
||||
sort_value_oldest_created => sort_title_oldest_created
|
||||
}
|
||||
|
||||
if current_controller?('admin/projects')
|
||||
|
@ -35,6 +35,19 @@ module SortingHelper
|
|||
options
|
||||
end
|
||||
|
||||
def member_sort_options_hash
|
||||
{
|
||||
sort_value_access_level_asc => sort_title_access_level_asc,
|
||||
sort_value_access_level_desc => sort_title_access_level_desc,
|
||||
sort_value_last_joined => sort_title_last_joined,
|
||||
sort_value_oldest_joined => sort_title_oldest_joined,
|
||||
sort_value_name => sort_title_name_asc,
|
||||
sort_value_name_desc => sort_title_name_desc,
|
||||
sort_value_recently_signin => sort_title_recently_signin,
|
||||
sort_value_oldest_signin => sort_title_oldest_signin
|
||||
}
|
||||
end
|
||||
|
||||
def sort_title_priority
|
||||
'Priority'
|
||||
end
|
||||
|
@ -95,6 +108,50 @@ module SortingHelper
|
|||
'Most popular'
|
||||
end
|
||||
|
||||
def sort_title_last_joined
|
||||
'Last joined'
|
||||
end
|
||||
|
||||
def sort_title_oldest_joined
|
||||
'Oldest joined'
|
||||
end
|
||||
|
||||
def sort_title_access_level_asc
|
||||
'Access level, ascending'
|
||||
end
|
||||
|
||||
def sort_title_access_level_desc
|
||||
'Access level, descending'
|
||||
end
|
||||
|
||||
def sort_title_name_asc
|
||||
'Name, ascending'
|
||||
end
|
||||
|
||||
def sort_title_name_desc
|
||||
'Name, descending'
|
||||
end
|
||||
|
||||
def sort_value_last_joined
|
||||
'last_joined'
|
||||
end
|
||||
|
||||
def sort_value_oldest_joined
|
||||
'oldest_joined'
|
||||
end
|
||||
|
||||
def sort_value_access_level_asc
|
||||
'access_level_asc'
|
||||
end
|
||||
|
||||
def sort_value_access_level_desc
|
||||
'access_level_desc'
|
||||
end
|
||||
|
||||
def sort_value_name_desc
|
||||
'name_desc'
|
||||
end
|
||||
|
||||
def sort_value_priority
|
||||
'priority'
|
||||
end
|
||||
|
|
|
@ -106,9 +106,9 @@ module TabHelper
|
|||
|
||||
def branches_tab_class
|
||||
if current_controller?(:protected_branches) ||
|
||||
current_controller?(:branches) ||
|
||||
current_page?(namespace_project_repository_path(@project.namespace,
|
||||
@project))
|
||||
current_controller?(:branches) ||
|
||||
current_page?(namespace_project_repository_path(@project.namespace,
|
||||
@project))
|
||||
'active'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -155,7 +155,7 @@ module Ci
|
|||
end
|
||||
|
||||
def has_environment?
|
||||
self.environment.present?
|
||||
environment.present?
|
||||
end
|
||||
|
||||
def starts_environment?
|
||||
|
@ -221,6 +221,7 @@ module Ci
|
|||
variables += pipeline.predefined_variables
|
||||
variables += runner.predefined_variables if runner
|
||||
variables += project.container_registry_variables
|
||||
variables += project.deployment_variables if has_environment?
|
||||
variables += yaml_variables
|
||||
variables += user_variables
|
||||
variables += project.secret_variables
|
||||
|
|
|
@ -57,6 +57,11 @@ class Member < ActiveRecord::Base
|
|||
scope :owners, -> { active.where(access_level: OWNER) }
|
||||
scope :owners_and_masters, -> { active.where(access_level: [OWNER, MASTER]) }
|
||||
|
||||
scope :order_name_asc, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.name', 'ASC')) }
|
||||
scope :order_name_desc, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.name', 'DESC')) }
|
||||
scope :order_recent_sign_in, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.last_sign_in_at', 'DESC')) }
|
||||
scope :order_oldest_sign_in, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.last_sign_in_at', 'ASC')) }
|
||||
|
||||
before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? }
|
||||
|
||||
after_create :send_invite, if: :invite?, unless: :importing?
|
||||
|
@ -72,6 +77,34 @@ class Member < ActiveRecord::Base
|
|||
default_value_for :notification_level, NotificationSetting.levels[:global]
|
||||
|
||||
class << self
|
||||
def search(query)
|
||||
joins(:user).merge(User.search(query))
|
||||
end
|
||||
|
||||
def sort(method)
|
||||
case method.to_s
|
||||
when 'access_level_asc' then reorder(access_level: :asc)
|
||||
when 'access_level_desc' then reorder(access_level: :desc)
|
||||
when 'recent_sign_in' then order_recent_sign_in
|
||||
when 'oldest_sign_in' then order_oldest_sign_in
|
||||
when 'last_joined' then order_created_desc
|
||||
when 'oldest_joined' then order_created_asc
|
||||
else
|
||||
order_by(method)
|
||||
end
|
||||
end
|
||||
|
||||
def left_join_users
|
||||
users = User.arel_table
|
||||
members = Member.arel_table
|
||||
|
||||
member_users = members.join(users, Arel::Nodes::OuterJoin).
|
||||
on(members[:user_id].eq(users[:id])).
|
||||
join_sources
|
||||
|
||||
joins(member_users)
|
||||
end
|
||||
|
||||
def access_for_user_ids(user_ids)
|
||||
where(user_id: user_ids).has_access.pluck(:user_id, :access_level).to_h
|
||||
end
|
||||
|
@ -89,8 +122,8 @@ class Member < ActiveRecord::Base
|
|||
member =
|
||||
if user.is_a?(User)
|
||||
source.members.find_by(user_id: user.id) ||
|
||||
source.requesters.find_by(user_id: user.id) ||
|
||||
source.members.build(user_id: user.id)
|
||||
source.requesters.find_by(user_id: user.id) ||
|
||||
source.members.build(user_id: user.id)
|
||||
else
|
||||
source.members.build(invite_email: user)
|
||||
end
|
||||
|
|
|
@ -568,6 +568,19 @@ class MergeRequest < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def issues_mentioned_but_not_closing(current_user = self.author)
|
||||
return [] unless target_branch == project.default_branch
|
||||
|
||||
ext = Gitlab::ReferenceExtractor.new(project, current_user)
|
||||
ext.analyze(description)
|
||||
|
||||
issues = ext.issues
|
||||
closing_issues = Gitlab::ClosingIssueExtractor.new(project, current_user).
|
||||
closed_by_message(description)
|
||||
|
||||
issues - closing_issues
|
||||
end
|
||||
|
||||
def target_project_path
|
||||
if target_project
|
||||
target_project.path_with_namespace
|
||||
|
@ -612,13 +625,24 @@ class MergeRequest < ActiveRecord::Base
|
|||
self.target_project.repository.branch_names.include?(self.target_branch)
|
||||
end
|
||||
|
||||
def merge_commit_message
|
||||
message = "Merge branch '#{source_branch}' into '#{target_branch}'\n\n"
|
||||
message << "#{title}\n\n"
|
||||
message << "#{description}\n\n" if description.present?
|
||||
def merge_commit_message(include_description: false)
|
||||
closes_issues_references = closes_issues.map do |issue|
|
||||
issue.to_reference(target_project)
|
||||
end
|
||||
|
||||
message = [
|
||||
"Merge branch '#{source_branch}' into '#{target_branch}'",
|
||||
title
|
||||
]
|
||||
|
||||
if !include_description && closes_issues_references.present?
|
||||
message << "Closes #{closes_issues_references.to_sentence}"
|
||||
end
|
||||
|
||||
message << "#{description}" if include_description && description.present?
|
||||
message << "See merge request #{to_reference}"
|
||||
|
||||
message
|
||||
message.join("\n\n")
|
||||
end
|
||||
|
||||
def reset_merge_when_build_succeeds
|
||||
|
|
|
@ -161,8 +161,8 @@ module Network
|
|||
def is_overlap?(range, overlap_space)
|
||||
range.each do |i|
|
||||
if i != range.first &&
|
||||
i != range.last &&
|
||||
@commits[i].spaces.include?(overlap_space)
|
||||
i != range.last &&
|
||||
@commits[i].spaces.include?(overlap_space)
|
||||
|
||||
return true
|
||||
end
|
||||
|
|
|
@ -2,6 +2,8 @@ class PersonalAccessToken < ActiveRecord::Base
|
|||
include TokenAuthenticatable
|
||||
add_authentication_token_field :token
|
||||
|
||||
serialize :scopes, Array
|
||||
|
||||
belongs_to :user
|
||||
|
||||
scope :active, -> { where(revoked: false).where("expires_at >= NOW() OR expires_at IS NULL") }
|
||||
|
|
|
@ -1230,6 +1230,12 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def deployment_variables
|
||||
return [] unless deployment_service
|
||||
|
||||
deployment_service.predefined_variables
|
||||
end
|
||||
|
||||
def append_or_update_attribute(name, value)
|
||||
old_values = public_send(name.to_s)
|
||||
|
||||
|
|
|
@ -8,4 +8,8 @@ class DeploymentService < Service
|
|||
def supported_events
|
||||
[]
|
||||
end
|
||||
|
||||
def predefined_variables
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -85,8 +85,8 @@ class IssueTrackerService < Service
|
|||
|
||||
def enabled_in_gitlab_config
|
||||
Gitlab.config.issues_tracker &&
|
||||
Gitlab.config.issues_tracker.values.any? &&
|
||||
issues_tracker
|
||||
Gitlab.config.issues_tracker.values.any? &&
|
||||
issues_tracker
|
||||
end
|
||||
|
||||
def issues_tracker
|
||||
|
|
|
@ -83,6 +83,16 @@ class KubernetesService < DeploymentService
|
|||
{ success: false, result: err }
|
||||
end
|
||||
|
||||
def predefined_variables
|
||||
variables = [
|
||||
{ key: 'KUBE_URL', value: api_url, public: true },
|
||||
{ key: 'KUBE_TOKEN', value: token, public: false },
|
||||
{ key: 'KUBE_NAMESPACE', value: namespace, public: true }
|
||||
]
|
||||
variables << { key: 'KUBE_CA_PEM', value: ca_pem, public: true } if ca_pem.present?
|
||||
variables
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_kubeclient(api_path = '/api', api_version = 'v1')
|
||||
|
|
|
@ -178,6 +178,8 @@ class User < ActiveRecord::Base
|
|||
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
|
||||
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members WHERE user_id IS NOT NULL AND requested_at IS NULL)') }
|
||||
scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) }
|
||||
scope :order_recent_sign_in, -> { reorder(last_sign_in_at: :desc) }
|
||||
scope :order_oldest_sign_in, -> { reorder(last_sign_in_at: :asc) }
|
||||
|
||||
def self.with_two_factor
|
||||
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id").
|
||||
|
@ -205,8 +207,8 @@ class User < ActiveRecord::Base
|
|||
|
||||
def sort(method)
|
||||
case method.to_s
|
||||
when 'recent_sign_in' then reorder(last_sign_in_at: :desc)
|
||||
when 'oldest_sign_in' then reorder(last_sign_in_at: :asc)
|
||||
when 'recent_sign_in' then order_recent_sign_in
|
||||
when 'oldest_sign_in' then order_oldest_sign_in
|
||||
else
|
||||
order_by(method)
|
||||
end
|
||||
|
@ -390,7 +392,7 @@ class User < ActiveRecord::Base
|
|||
def namespace_uniq
|
||||
# Return early if username already failed the first uniqueness validation
|
||||
return if errors.key?(:username) &&
|
||||
errors[:username].include?('has already been taken')
|
||||
errors[:username].include?('has already been taken')
|
||||
|
||||
existing_namespace = Namespace.by_path(username)
|
||||
if existing_namespace && existing_namespace != namespace
|
||||
|
|
|
@ -12,7 +12,7 @@ class NotePolicy < BasePolicy
|
|||
end
|
||||
|
||||
if @subject.for_merge_request? &&
|
||||
@subject.noteable.author == @user
|
||||
@subject.noteable.author == @user
|
||||
can! :resolve_note
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@ class ProjectPolicy < BasePolicy
|
|||
team_access!(user)
|
||||
|
||||
owner = project.owner == user ||
|
||||
(project.group && project.group.has_owner?(user))
|
||||
(project.group && project.group.has_owner?(user))
|
||||
|
||||
owner_access! if user.admin? || owner
|
||||
team_member_owner_access! if owner
|
||||
|
@ -13,7 +13,7 @@ class ProjectPolicy < BasePolicy
|
|||
public_access!
|
||||
|
||||
if project.request_access_enabled &&
|
||||
!(owner || user.admin? || project.team.member?(user) || project_group_member?(user))
|
||||
!(owner || user.admin? || project.team.member?(user) || project_group_member?(user))
|
||||
can! :request_access
|
||||
end
|
||||
end
|
||||
|
@ -244,10 +244,10 @@ class ProjectPolicy < BasePolicy
|
|||
|
||||
def project_group_member?(user)
|
||||
project.group &&
|
||||
(
|
||||
project.group.members.exists?(user_id: user.id) ||
|
||||
project.group.requesters.exists?(user_id: user.id)
|
||||
)
|
||||
(
|
||||
project.group.members.exists?(user_id: user.id) ||
|
||||
project.group.requesters.exists?(user_id: user.id)
|
||||
)
|
||||
end
|
||||
|
||||
def named_abilities(name)
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
AccessTokenValidationService = Struct.new(:token) do
|
||||
# Results:
|
||||
VALID = :valid
|
||||
EXPIRED = :expired
|
||||
REVOKED = :revoked
|
||||
INSUFFICIENT_SCOPE = :insufficient_scope
|
||||
|
||||
def validate(scopes: [])
|
||||
if token.expired?
|
||||
return EXPIRED
|
||||
|
||||
elsif token.revoked?
|
||||
return REVOKED
|
||||
|
||||
elsif !self.include_any_scope?(scopes)
|
||||
return INSUFFICIENT_SCOPE
|
||||
|
||||
else
|
||||
return VALID
|
||||
end
|
||||
end
|
||||
|
||||
# True if the token's scope contains any of the passed scopes.
|
||||
def include_any_scope?(scopes)
|
||||
if scopes.blank?
|
||||
true
|
||||
else
|
||||
# Check whether the token is allowed access to any of the required scopes.
|
||||
Set.new(scopes).intersection(Set.new(token.scopes)).present?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,7 +5,7 @@ module Groups
|
|||
new_visibility = params[:visibility_level]
|
||||
if new_visibility && new_visibility.to_i != group.visibility_level
|
||||
unless can?(current_user, :change_visibility_level, group) &&
|
||||
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
|
||||
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
|
||||
|
||||
deny_visibility_level(group, new_visibility)
|
||||
return group
|
||||
|
|
|
@ -10,7 +10,7 @@ module Issues
|
|||
end
|
||||
|
||||
if issue.previous_changes.include?('title') ||
|
||||
issue.previous_changes.include?('description')
|
||||
issue.previous_changes.include?('description')
|
||||
todo_service.update_issue(issue, current_user)
|
||||
end
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ module MergeRequests
|
|||
end
|
||||
|
||||
if merge_request.source_project == merge_request.target_project &&
|
||||
merge_request.target_branch == merge_request.source_branch
|
||||
merge_request.target_branch == merge_request.source_branch
|
||||
|
||||
messages << 'You must select different branches'
|
||||
end
|
||||
|
|
|
@ -25,7 +25,7 @@ module MergeRequests
|
|||
end
|
||||
|
||||
if merge_request.previous_changes.include?('title') ||
|
||||
merge_request.previous_changes.include?('description')
|
||||
merge_request.previous_changes.include?('description')
|
||||
todo_service.update_merge_request(merge_request, current_user)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
module Oauth2::AccessTokenValidationService
|
||||
# Results:
|
||||
VALID = :valid
|
||||
EXPIRED = :expired
|
||||
REVOKED = :revoked
|
||||
INSUFFICIENT_SCOPE = :insufficient_scope
|
||||
|
||||
class << self
|
||||
def validate(token, scopes: [])
|
||||
if token.expired?
|
||||
return EXPIRED
|
||||
|
||||
elsif token.revoked?
|
||||
return REVOKED
|
||||
|
||||
elsif !self.sufficient_scope?(token, scopes)
|
||||
return INSUFFICIENT_SCOPE
|
||||
|
||||
else
|
||||
return VALID
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# True if the token's scope is a superset of required scopes,
|
||||
# or the required scopes is empty.
|
||||
def sufficient_scope?(token, scopes)
|
||||
if scopes.blank?
|
||||
# if no any scopes required, the scopes of token is sufficient.
|
||||
return true
|
||||
else
|
||||
# If there are scopes required, then check whether
|
||||
# the set of authorized scopes is a superset of the set of required scopes
|
||||
required_scopes = Set.new(scopes)
|
||||
authorized_scopes = Set.new(token.scopes)
|
||||
|
||||
return authorized_scopes >= required_scopes
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,7 +6,7 @@ module Projects
|
|||
|
||||
if new_visibility && new_visibility.to_i != project.visibility_level
|
||||
unless can?(current_user, :change_visibility_level, project) &&
|
||||
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
|
||||
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
|
||||
|
||||
deny_visibility_level(project, new_visibility)
|
||||
return project
|
||||
|
|
|
@ -18,6 +18,12 @@
|
|||
Use
|
||||
%code= Doorkeeper.configuration.native_redirect_uri
|
||||
for local tests
|
||||
|
||||
.form-group
|
||||
= f.label :scopes, class: 'col-sm-2 control-label'
|
||||
.col-sm-10
|
||||
= render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes
|
||||
|
||||
.form-actions
|
||||
= f.submit 'Submit', class: "btn btn-save wide"
|
||||
= link_to "Cancel", admin_applications_path, class: "btn btn-default"
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
%h3.page-title
|
||||
Application: #{@application.name}
|
||||
|
||||
|
||||
.table-holder
|
||||
.table-holder.oauth-application-show
|
||||
%table.table
|
||||
%tr
|
||||
%td
|
||||
|
@ -23,6 +22,9 @@
|
|||
- @application.redirect_uri.split.each do |uri|
|
||||
%div
|
||||
%span.monospace= uri
|
||||
|
||||
= render "shared/tokens/scopes_list", token: @application
|
||||
|
||||
.form-actions
|
||||
= link_to 'Edit', edit_admin_application_path(@application), class: 'btn btn-primary wide pull-left'
|
||||
= render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10'
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
- status = local_assigns.fetch(:status)
|
||||
- css_classes = "ci-status ci-#{status.group}"
|
||||
|
||||
- if status.has_details?
|
||||
= link_to status.details_path, class: "ci-status ci-#{status}" do
|
||||
= link_to status.details_path, class: css_classes do
|
||||
= custom_icon(status.icon)
|
||||
= status.text
|
||||
- else
|
||||
%span{ class: "ci-status ci-#{status}" }
|
||||
%span{ class: css_classes }
|
||||
= custom_icon(status.icon)
|
||||
= status.text
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
- subject = local_assigns.fetch(:subject)
|
||||
- status = subject.detailed_status(current_user)
|
||||
- klass = "ci-status-icon ci-status-icon-#{status}"
|
||||
- klass = "ci-status-icon ci-status-icon-#{status.group}"
|
||||
|
||||
- if status.has_details?
|
||||
= link_to status.details_path, data: { toggle: 'tooltip', title: "#{subject.name} - #{status.label}" } do
|
||||
|
|
|
@ -17,5 +17,9 @@
|
|||
%code= Doorkeeper.configuration.native_redirect_uri
|
||||
for local tests
|
||||
|
||||
.form-group
|
||||
= f.label :scopes, class: 'label-light'
|
||||
= render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes
|
||||
|
||||
.prepend-top-default
|
||||
= f.submit 'Save application', class: "btn btn-create"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
%h3.page-title
|
||||
Application: #{@application.name}
|
||||
|
||||
.table-holder
|
||||
.table-holder.oauth-application-show
|
||||
%table.table
|
||||
%tr
|
||||
%td
|
||||
|
@ -22,6 +22,9 @@
|
|||
- @application.redirect_uri.split.each do |uri|
|
||||
%div
|
||||
%span.monospace= uri
|
||||
|
||||
= render "shared/tokens/scopes_list", token: @application
|
||||
|
||||
.form-actions
|
||||
= link_to 'Edit', edit_oauth_application_path(@application), class: 'btn btn-primary wide pull-left'
|
||||
= render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10'
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
|
||||
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
|
||||
= icon("search")
|
||||
= render 'shared/members/sort_dropdown'
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
Users with access to
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
- page_title "Bitbucket import"
|
||||
- header_title "Projects", root_path
|
||||
- page_title 'Bitbucket import'
|
||||
- header_title 'Projects', root_path
|
||||
|
||||
%h3.page-title
|
||||
%i.fa.fa-bitbucket
|
||||
Import projects from Bitbucket
|
||||
|
@ -10,13 +11,13 @@
|
|||
%hr
|
||||
%p
|
||||
- if @incompatible_repos.any?
|
||||
= button_tag class: "btn btn-import btn-success js-import-all" do
|
||||
= button_tag class: 'btn btn-import btn-success js-import-all' do
|
||||
Import all compatible projects
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
= icon('spinner spin', class: 'loading-icon')
|
||||
- else
|
||||
= button_tag class: "btn btn-success js-import-all" do
|
||||
= button_tag class: 'btn btn-import btn-success js-import-all' do
|
||||
Import all projects
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
= icon('spinner spin', class: 'loading-icon')
|
||||
|
||||
.table-responsive
|
||||
%table.table.import-jobs
|
||||
|
@ -32,7 +33,7 @@
|
|||
- @already_added_projects.each do |project|
|
||||
%tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
|
||||
%td
|
||||
= link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank"
|
||||
= link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: '_blank'
|
||||
%td
|
||||
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
%td.job-status
|
||||
|
@ -47,31 +48,41 @@
|
|||
= project.human_import_status_name
|
||||
|
||||
- @repos.each do |repo|
|
||||
%tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"}
|
||||
%tr{id: "repo_#{repo.owner}___#{repo.slug}"}
|
||||
%td
|
||||
= link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank"
|
||||
= link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: "_blank"
|
||||
%td.import-target
|
||||
= import_project_target(repo['owner'], repo['slug'])
|
||||
%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, path: repo.owner) } : {}
|
||||
= 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
|
||||
= button_tag class: "btn btn-import js-add-to-import" do
|
||||
= button_tag class: 'btn btn-import js-add-to-import' do
|
||||
Import
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
= icon('spinner spin', class: 'loading-icon')
|
||||
- @incompatible_repos.each do |repo|
|
||||
%tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"}
|
||||
%tr{id: "repo_#{repo.owner}___#{repo.slug}"}
|
||||
%td
|
||||
= link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank"
|
||||
= link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank'
|
||||
%td.import-target
|
||||
%td.import-actions-job-status
|
||||
= label_tag "Incompatible Project", nil, class: "label label-danger"
|
||||
= label_tag 'Incompatible Project', nil, class: 'label label-danger'
|
||||
|
||||
- if @incompatible_repos.any?
|
||||
%p
|
||||
One or more of your Bitbucket projects cannot be imported into GitLab
|
||||
directly because they use Subversion or Mercurial for version control,
|
||||
rather than Git. Please convert
|
||||
= link_to "them to Git,", "https://www.atlassian.com/git/tutorials/migrating-overview"
|
||||
= link_to 'them to Git,', 'https://www.atlassian.com/git/tutorials/migrating-overview'
|
||||
and go through the
|
||||
= link_to "import flow", status_import_bitbucket_path, "data-no-turbolink" => "true"
|
||||
= link_to 'import flow', status_import_bitbucket_path, 'data-no-turbolink' => 'true'
|
||||
again.
|
||||
|
||||
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_path}", import_path: "#{import_bitbucket_path}" } }
|
||||
|
|
|
@ -3,6 +3,14 @@
|
|||
|
||||
- if project
|
||||
:javascript
|
||||
GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: noteable_type, type_id: params[:id])}"
|
||||
GitLab.GfmAutoComplete.cachedData = undefined;
|
||||
GitLab.GfmAutoComplete.setup();
|
||||
gl.GfmAutoComplete.dataSources = {
|
||||
emojis: "#{emojis_namespace_project_autocomplete_sources_path(project.namespace, project)}",
|
||||
members: "#{members_namespace_project_autocomplete_sources_path(project.namespace, project, type: noteable_type, type_id: params[:id])}",
|
||||
issues: "#{issues_namespace_project_autocomplete_sources_path(project.namespace, project)}",
|
||||
mergeRequests: "#{merge_requests_namespace_project_autocomplete_sources_path(project.namespace, project)}",
|
||||
labels: "#{labels_namespace_project_autocomplete_sources_path(project.namespace, project)}",
|
||||
milestones: "#{milestones_namespace_project_autocomplete_sources_path(project.namespace, project)}",
|
||||
commands: "#{commands_namespace_project_autocomplete_sources_path(project.namespace, project, type: noteable_type, type_id: params[:id])}"
|
||||
};
|
||||
|
||||
gl.GfmAutoComplete.setup();
|
||||
|
|
|
@ -22,9 +22,10 @@
|
|||
= render "layouts/nav/#{nav}"
|
||||
.content-wrapper{ class: "#{layout_nav_class}" }
|
||||
= yield :sub_nav
|
||||
= render "layouts/broadcast"
|
||||
= render "layouts/flash"
|
||||
= yield :flash_message
|
||||
.alert-wrapper
|
||||
= render "layouts/broadcast"
|
||||
= render "layouts/flash"
|
||||
= yield :flash_message
|
||||
%div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
|
||||
.content{ id: "content-body" }
|
||||
= yield
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
- personal_access_token = local_assigns.fetch(:personal_access_token)
|
||||
- scopes = local_assigns.fetch(:scopes)
|
||||
|
||||
= form_for [:profile, personal_access_token], method: :post, html: { class: 'js-requires-input' } do |f|
|
||||
|
||||
= form_errors(personal_access_token)
|
||||
|
||||
.form-group
|
||||
= f.label :name, class: 'label-light'
|
||||
= f.text_field :name, class: "form-control", required: true
|
||||
|
||||
.form-group
|
||||
= f.label :expires_at, class: 'label-light'
|
||||
= f.text_field :expires_at, class: "datepicker form-control"
|
||||
|
||||
.form-group
|
||||
= f.label :scopes, class: 'label-light'
|
||||
= render 'shared/tokens/scopes_form', prefix: 'personal_access_token', token: personal_access_token, scopes: scopes
|
||||
|
||||
.prepend-top-default
|
||||
= f.submit 'Create Personal Access Token', class: "btn btn-create"
|
|
@ -28,21 +28,8 @@
|
|||
Add a Personal Access Token
|
||||
%p.profile-settings-content
|
||||
Pick a name for the application, and we'll give you a unique token.
|
||||
= form_for [:profile, @personal_access_token],
|
||||
method: :post, html: { class: 'js-requires-input' } do |f|
|
||||
|
||||
= form_errors(@personal_access_token)
|
||||
|
||||
.form-group
|
||||
= f.label :name, class: 'label-light'
|
||||
= f.text_field :name, class: "form-control", required: true
|
||||
|
||||
.form-group
|
||||
= f.label :expires_at, class: 'label-light'
|
||||
= f.text_field :expires_at, class: "datepicker form-control", required: false
|
||||
|
||||
.prepend-top-default
|
||||
= f.submit 'Create Personal Access Token', class: "btn btn-create"
|
||||
= render "form", personal_access_token: @personal_access_token, scopes: @scopes
|
||||
|
||||
%hr
|
||||
|
||||
|
@ -56,6 +43,7 @@
|
|||
%th Name
|
||||
%th Created
|
||||
%th Expires
|
||||
%th Scopes
|
||||
%th
|
||||
%tbody
|
||||
- @active_personal_access_tokens.each do |token|
|
||||
|
@ -67,6 +55,7 @@
|
|||
= token.expires_at.to_date.to_s(:medium)
|
||||
- else
|
||||
%span.personal-access-tokens-never-expires-label Never
|
||||
%td= token.scopes.present? ? token.scopes.join(", ") : "<no scopes selected>"
|
||||
%td= link_to "Revoke", revoke_profile_personal_access_token_path(token), method: :put, class: "btn btn-danger pull-right", data: { confirm: "Are you sure you want to revoke this token? This action cannot be undone." }
|
||||
|
||||
- else
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
= dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'btn js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
|
||||
.gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.hidden
|
||||
= dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'btn js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
|
||||
.dockerfile-selector.js-dockerfile-selector-wrap.hidden
|
||||
= dropdown_tag("Choose a Dockerfile template", options: { toggle_class: 'btn js-dockerfile-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: dockerfile_names } } )
|
||||
= button_tag class: 'soft-wrap-toggle btn', type: 'button' do
|
||||
%span.no-wrap
|
||||
= custom_icon('icon_no_wrap')
|
||||
|
|
|
@ -30,11 +30,18 @@
|
|||
- elsif @merge_request.can_be_merged? || resolved_conflicts
|
||||
= render 'projects/merge_requests/widget/open/accept'
|
||||
|
||||
- if mr_closes_issues.present?
|
||||
- if mr_closes_issues.present? || mr_issues_mentioned_but_not_closing.present?
|
||||
.mr-widget-footer
|
||||
%span
|
||||
%i.fa.fa-check
|
||||
Accepting this merge request will close #{"issue".pluralize(mr_closes_issues.size)}
|
||||
= succeed '.' do
|
||||
!= markdown issues_sentence(mr_closes_issues), pipeline: :gfm, author: @merge_request.author
|
||||
= mr_assign_issues_link
|
||||
= icon('check')
|
||||
- if mr_closes_issues.present?
|
||||
Accepting this merge request will close #{"issue".pluralize(mr_closes_issues.size)}
|
||||
= succeed '.' do
|
||||
!= markdown issues_sentence(mr_closes_issues), pipeline: :gfm, author: @merge_request.author
|
||||
= mr_assign_issues_link
|
||||
- if mr_issues_mentioned_but_not_closing.present?
|
||||
#{"Issue".pluralize(mr_issues_mentioned_but_not_closing.size)}
|
||||
!= markdown issues_sentence(mr_issues_mentioned_but_not_closing), pipeline: :gfm, author: @merge_request.author
|
||||
#{mr_issues_mentioned_but_not_closing.size > 1 ? 'are' : 'is'} mentioned but will not closed.
|
||||
|
||||
|
||||
|
|
|
@ -41,6 +41,8 @@
|
|||
Modify commit message
|
||||
.js-toggle-content.hide.prepend-top-default
|
||||
= render 'shared/commit_message_container', params: params,
|
||||
message_with_description: @merge_request.merge_commit_message(include_description: true),
|
||||
message_without_description: @merge_request.merge_commit_message,
|
||||
text: @merge_request.merge_commit_message,
|
||||
rows: 14, hint: true
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
|
||||
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
|
||||
= icon("search")
|
||||
= render 'shared/members/sort_dropdown'
|
||||
- if @group_links.any?
|
||||
= render 'groups', group_links: @group_links
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
- pretty_path_with_namespace = "#{@project ? @project.namespace.name : 'namespace'} / #{@project ? @project.name : 'name'}"
|
||||
- run_actions_text = "Perform common operations on this project: #{pretty_path_with_namespace}"
|
||||
- run_actions_text = "Perform common operations on this project: #{@project.name_with_namespace}"
|
||||
|
||||
.well
|
||||
This service allows GitLab users to perform common operations on this
|
||||
|
@ -27,7 +26,7 @@
|
|||
.form-group
|
||||
= label_tag :display_name, 'Display name', class: 'col-sm-2 col-xs-12 control-label'
|
||||
.col-sm-10.col-xs-12.input-group
|
||||
= text_field_tag :display_name, "GitLab / #{pretty_path_with_namespace}", class: 'form-control input-sm', readonly: 'readonly'
|
||||
= text_field_tag :display_name, "GitLab / #{@project.name_with_namespace}", class: 'form-control input-sm', readonly: 'readonly'
|
||||
.input-group-btn
|
||||
= clipboard_button(clipboard_target: '#display_name')
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
.form-group.commit_message-group
|
||||
- nonce = SecureRandom.hex
|
||||
- descriptions = local_assigns.slice(:message_with_description, :message_without_description)
|
||||
= label_tag "commit_message-#{nonce}", class: 'control-label' do
|
||||
Commit message
|
||||
.col-sm-10
|
||||
|
@ -8,9 +9,17 @@
|
|||
= text_area_tag 'commit_message',
|
||||
(params[:commit_message] || local_assigns[:text] || local_assigns[:placeholder]),
|
||||
class: 'form-control js-commit-message', placeholder: local_assigns[:placeholder],
|
||||
data: descriptions,
|
||||
required: true, rows: (local_assigns[:rows] || 3),
|
||||
id: "commit_message-#{nonce}"
|
||||
- if local_assigns[:hint]
|
||||
%p.hint
|
||||
Try to keep the first line under 52 characters
|
||||
and the others under 72.
|
||||
- if descriptions.present?
|
||||
%p.hint.js-with-description-hint
|
||||
= link_to "#", class: "js-with-description-link" do
|
||||
Include description in commit message
|
||||
%p.hint.js-without-description-hint.hide
|
||||
= link_to "#", class: "js-without-description-link" do
|
||||
Don't include description in commit message
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
.dropdown.inline.member-sort-dropdown
|
||||
= dropdown_toggle(member_sort_options_hash[@sort], { toggle: 'dropdown' })
|
||||
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
|
||||
%li.dropdown-header
|
||||
Sort by
|
||||
- member_sort_options_hash.each do |value, title|
|
||||
%li
|
||||
= link_to filter_group_project_member_path(sort: value), class: ("is-active" if @sort == value) do
|
||||
= title
|
|
@ -0,0 +1,9 @@
|
|||
- scopes = local_assigns.fetch(:scopes)
|
||||
- prefix = local_assigns.fetch(:prefix)
|
||||
- token = local_assigns.fetch(:token)
|
||||
|
||||
- scopes.each do |scope|
|
||||
%fieldset
|
||||
= check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}"
|
||||
= label_tag "#{prefix}_scopes_#{scope}", scope
|
||||
%span= "(#{t(scope, scope: [:doorkeeper, :scopes])})"
|
|
@ -0,0 +1,13 @@
|
|||
- token = local_assigns.fetch(:token)
|
||||
|
||||
- return unless token.scopes.present?
|
||||
|
||||
%tr
|
||||
%td
|
||||
Scopes
|
||||
%td
|
||||
%ul.scopes-list.append-bottom-0
|
||||
- token.scopes.each do |scope|
|
||||
%li
|
||||
%span.scope-name= scope
|
||||
= "(#{t(scope, scope: [:doorkeeper, :scopes])})"
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Made comment autocomplete more performant and removed some loading bugs
|
||||
merge_request: 6856
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Add scopes for personal access tokens and OAuth tokens
|
||||
merge_request: 5951
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Add sorting functionality for group/project members
|
||||
merge_request: 7032
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Prevent enviroment table to overflow when name has underscores
|
||||
merge_request: 8142
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Accept environment variables from the `pre-receive` script
|
||||
merge_request: 7967
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: fix colors and margins for adjacent alert banners
|
||||
merge_request: 8151
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Refactor Bitbucket importer to use BitBucket API Version 2
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Add support for Dockerfile templates
|
||||
merge_request: 7247
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Pass variables from deployment project services to CI runner
|
||||
merge_request: 8107
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Additional rounded label fixes
|
||||
merge_request:
|
||||
author:
|
|
@ -153,6 +153,12 @@ production: &base
|
|||
# The location where LFS objects are stored (default: shared/lfs-objects).
|
||||
# storage_path: shared/lfs-objects
|
||||
|
||||
## Mattermost
|
||||
## For enabling Add to Mattermost button
|
||||
mattermost:
|
||||
enabled: false
|
||||
host: 'https://mattermost.example.com'
|
||||
|
||||
## Gravatar
|
||||
## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
|
||||
gravatar:
|
||||
|
|
|
@ -261,6 +261,13 @@ Settings['lfs'] ||= Settingslogic.new({})
|
|||
Settings.lfs['enabled'] = true if Settings.lfs['enabled'].nil?
|
||||
Settings.lfs['storage_path'] = File.expand_path(Settings.lfs['storage_path'] || File.join(Settings.shared['path'], "lfs-objects"), Rails.root)
|
||||
|
||||
#
|
||||
# Mattermost
|
||||
#
|
||||
Settings['mattermost'] ||= Settingslogic.new({})
|
||||
Settings.mattermost['enabled'] = false if Settings.mattermost['enabled'].nil?
|
||||
Settings.mattermost['host'] = nil unless Settings.mattermost.enabled
|
||||
|
||||
#
|
||||
# Gravatar
|
||||
#
|
||||
|
|
|
@ -52,8 +52,8 @@ Doorkeeper.configure do
|
|||
# Define access token scopes for your provider
|
||||
# For more information go to
|
||||
# https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes
|
||||
default_scopes :api
|
||||
# optional_scopes :write, :update
|
||||
default_scopes(*Gitlab::Auth::DEFAULT_SCOPES)
|
||||
optional_scopes(*Gitlab::Auth::OPTIONAL_SCOPES)
|
||||
|
||||
# Change the way client credentials are retrieved from the request object.
|
||||
# By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
|
||||
|
|
|
@ -26,3 +26,9 @@ if Gitlab.config.omniauth.enabled
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
autoload :Bitbucket, Rails.root.join('lib', 'omniauth', 'strategies', 'bitbucket')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
path = File.expand_path("~/.ssh/bitbucket_rsa.pub")
|
||||
Gitlab::BitbucketImport.public_key = File.read(path) if File.exist?(path)
|
|
@ -34,7 +34,7 @@ Sidekiq.configure_server do |config|
|
|||
# Database pool should be at least `sidekiq_concurrency` + 2
|
||||
# For more info, see: https://github.com/mperham/sidekiq/blob/master/4.0-Upgrade.md
|
||||
config = ActiveRecord::Base.configurations[Rails.env] ||
|
||||
Rails.application.config.database_configuration[Rails.env]
|
||||
Rails.application.config.database_configuration[Rails.env]
|
||||
config['pool'] = Sidekiq.options[:concurrency] + 2
|
||||
ActiveRecord::Base.establish_connection(config)
|
||||
Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}")
|
||||
|
|
|
@ -59,6 +59,7 @@ en:
|
|||
unknown: "The access token is invalid"
|
||||
scopes:
|
||||
api: Access your API
|
||||
read_user: Read user information
|
||||
|
||||
flash:
|
||||
applications:
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue