Merge branch 'master' into 4273-slash-commands
# Conflicts: # app/services/issues/create_service.rb
This commit is contained in:
commit
e07c27fee4
238 changed files with 4067 additions and 893 deletions
|
@ -15,6 +15,7 @@ variables:
|
||||||
USE_DB: "true"
|
USE_DB: "true"
|
||||||
USE_BUNDLE_INSTALL: "true"
|
USE_BUNDLE_INSTALL: "true"
|
||||||
GIT_DEPTH: "20"
|
GIT_DEPTH: "20"
|
||||||
|
PHANTOMJS_VERSION: "2.1.1"
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- source ./scripts/prepare_build.sh
|
- source ./scripts/prepare_build.sh
|
||||||
|
|
16
CHANGELOG
16
CHANGELOG
|
@ -1,7 +1,9 @@
|
||||||
Please view this file on the master branch, on stable branches it's out of date.
|
Please view this file on the master branch, on stable branches it's out of date.
|
||||||
|
|
||||||
v 8.11.0 (unreleased)
|
v 8.11.0 (unreleased)
|
||||||
|
- Add test coverage report badge. !5708
|
||||||
- Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar)
|
- Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar)
|
||||||
|
- 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)
|
||||||
- Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres)
|
- Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres)
|
||||||
- Fix the title of the toggle dropdown button. !5515 (herminiotorres)
|
- Fix the title of the toggle dropdown button. !5515 (herminiotorres)
|
||||||
|
@ -32,12 +34,15 @@ v 8.11.0 (unreleased)
|
||||||
- Add "No one can push" as an option for protected branches. !5081
|
- Add "No one can push" as an option for protected branches. !5081
|
||||||
- Improve performance of AutolinkFilter#text_parse by using XPath
|
- Improve performance of AutolinkFilter#text_parse by using XPath
|
||||||
- Add experimental Redis Sentinel support !1877
|
- Add experimental Redis Sentinel support !1877
|
||||||
|
- Rendering of SVGs as blobs is now limited to SVGs with a size smaller or equal to 2MB
|
||||||
- Fix branches page dropdown sort initial state (ClemMakesApps)
|
- Fix branches page dropdown sort initial state (ClemMakesApps)
|
||||||
- Environments have an url to link to
|
- Environments have an url to link to
|
||||||
- Various redundant database indexes have been removed
|
- Various redundant database indexes have been removed
|
||||||
- Update `timeago` plugin to use multiple string/locale settings
|
- Update `timeago` plugin to use multiple string/locale settings
|
||||||
- Remove unused images (ClemMakesApps)
|
- Remove unused images (ClemMakesApps)
|
||||||
|
- Get issue and merge request description templates from repositories
|
||||||
- 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
|
||||||
- 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
|
||||||
- Add green outline to New Branch button. !5447 (winniehell)
|
- Add green outline to New Branch button. !5447 (winniehell)
|
||||||
|
@ -55,6 +60,7 @@ v 8.11.0 (unreleased)
|
||||||
- Store all DB secrets in secrets.yml, under descriptive names !5274
|
- Store all DB secrets in secrets.yml, under descriptive names !5274
|
||||||
- Support slash commands in issue and merge request descriptions as well as comments. !5021
|
- Support slash commands in issue and merge request descriptions as well as comments. !5021
|
||||||
- Nokogiri's various parsing methods are now instrumented
|
- Nokogiri's various parsing methods are now instrumented
|
||||||
|
- Add archived badge to project list !5798
|
||||||
- Add simple identifier to public SSH keys (muteor)
|
- Add simple identifier to public SSH keys (muteor)
|
||||||
- Admin page now references docs instead of a specific file !5600 (AnAverageHuman)
|
- Admin page now references docs instead of a specific file !5600 (AnAverageHuman)
|
||||||
- Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363
|
- Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363
|
||||||
|
@ -69,6 +75,7 @@ v 8.11.0 (unreleased)
|
||||||
- The overhead of instrumented method calls has been reduced
|
- The overhead of instrumented method calls has been reduced
|
||||||
- Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le)
|
- Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le)
|
||||||
- Load project invited groups and members eagerly in `ProjectTeam#fetch_members`
|
- Load project invited groups and members eagerly in `ProjectTeam#fetch_members`
|
||||||
|
- Add pipeline events hook
|
||||||
- Bump gitlab_git to speedup DiffCollection iterations
|
- Bump gitlab_git to speedup DiffCollection iterations
|
||||||
- Rewrite description of a blocked user in admin settings. (Elias Werberich)
|
- Rewrite description of a blocked user in admin settings. (Elias Werberich)
|
||||||
- Make branches sortable without push permission !5462 (winniehell)
|
- Make branches sortable without push permission !5462 (winniehell)
|
||||||
|
@ -78,6 +85,7 @@ v 8.11.0 (unreleased)
|
||||||
- Make "New issue" button in Issue page less obtrusive !5457 (winniehell)
|
- Make "New issue" button in Issue page less obtrusive !5457 (winniehell)
|
||||||
- Gitlab::Metrics.current_transaction needs to be public for RailsQueueDuration
|
- Gitlab::Metrics.current_transaction needs to be public for RailsQueueDuration
|
||||||
- Fix search for notes which belongs to deleted objects
|
- Fix search for notes which belongs to deleted objects
|
||||||
|
- Allow Akismet to be trained by submitting issues as spam or ham !5538
|
||||||
- Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska)
|
- Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska)
|
||||||
- Allow branch names ending with .json for graph and network page !5579 (winniehell)
|
- Allow branch names ending with .json for graph and network page !5579 (winniehell)
|
||||||
- Add the `sprockets-es6` gem
|
- Add the `sprockets-es6` gem
|
||||||
|
@ -108,6 +116,12 @@ v 8.11.0 (unreleased)
|
||||||
- Sort folders with submodules in Files view !5521
|
- Sort folders with submodules in Files view !5521
|
||||||
- Each `File::exists?` replaced to `File::exist?` because of deprecate since ruby version 2.2.0
|
- Each `File::exists?` replaced to `File::exist?` because of deprecate since ruby version 2.2.0
|
||||||
- Add auto-completition in pipeline (Katarzyna Kobierska Ula Budziszewska)
|
- Add auto-completition in pipeline (Katarzyna Kobierska Ula Budziszewska)
|
||||||
|
- Fix a memory leak caused by Banzai::Filter::SanitizationFilter
|
||||||
|
- Speed up todos queries by limiting the projects set we join with
|
||||||
|
- Ensure file editing in UI does not overwrite commited changes without warning user
|
||||||
|
|
||||||
|
v 8.10.6 (unreleased)
|
||||||
|
- Fix import/export configuration missing some included attributes
|
||||||
|
|
||||||
v 8.10.5
|
v 8.10.5
|
||||||
- Add a data migration to fix some missing timestamps in the members table. !5670
|
- Add a data migration to fix some missing timestamps in the members table. !5670
|
||||||
|
@ -272,9 +286,11 @@ v 8.10.0
|
||||||
- Fix new snippet style bug (elliotec)
|
- Fix new snippet style bug (elliotec)
|
||||||
- Instrument Rinku usage
|
- Instrument Rinku usage
|
||||||
- Be explicit to define merge request discussion variables
|
- Be explicit to define merge request discussion variables
|
||||||
|
- Use cache for todos counter calling TodoService
|
||||||
- Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab
|
- Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab
|
||||||
- RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info.
|
- RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info.
|
||||||
- Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w)
|
- Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w)
|
||||||
|
- Made project list visibility icon fixed width
|
||||||
- Set import_url validation to be more strict
|
- Set import_url validation to be more strict
|
||||||
- Memoize MR merged/closed events retrieval
|
- Memoize MR merged/closed events retrieval
|
||||||
- Don't render discussion notes when requesting diff tab through AJAX
|
- Don't render discussion notes when requesting diff tab through AJAX
|
||||||
|
|
|
@ -339,7 +339,7 @@ GEM
|
||||||
httparty (0.13.7)
|
httparty (0.13.7)
|
||||||
json (~> 1.8)
|
json (~> 1.8)
|
||||||
multi_xml (>= 0.5.2)
|
multi_xml (>= 0.5.2)
|
||||||
httpclient (2.7.0.1)
|
httpclient (2.8.2)
|
||||||
i18n (0.7.0)
|
i18n (0.7.0)
|
||||||
ice_nine (0.11.1)
|
ice_nine (0.11.1)
|
||||||
influxdb (0.2.3)
|
influxdb (0.2.3)
|
||||||
|
|
|
@ -9,10 +9,11 @@
|
||||||
licensePath: "/api/:version/licenses/:key",
|
licensePath: "/api/:version/licenses/:key",
|
||||||
gitignorePath: "/api/:version/gitignores/:key",
|
gitignorePath: "/api/:version/gitignores/:key",
|
||||||
gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key",
|
gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key",
|
||||||
|
issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key",
|
||||||
|
|
||||||
group: function(group_id, callback) {
|
group: function(group_id, callback) {
|
||||||
var url;
|
var url = Api.buildUrl(Api.groupPath)
|
||||||
url = Api.buildUrl(Api.groupPath);
|
.replace(':id', group_id);
|
||||||
url = url.replace(':id', group_id);
|
|
||||||
return $.ajax({
|
return $.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
data: {
|
data: {
|
||||||
|
@ -24,8 +25,7 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
groups: function(query, skip_ldap, callback) {
|
groups: function(query, skip_ldap, callback) {
|
||||||
var url;
|
var url = Api.buildUrl(Api.groupsPath);
|
||||||
url = Api.buildUrl(Api.groupsPath);
|
|
||||||
return $.ajax({
|
return $.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
data: {
|
data: {
|
||||||
|
@ -39,8 +39,7 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
namespaces: function(query, callback) {
|
namespaces: function(query, callback) {
|
||||||
var url;
|
var url = Api.buildUrl(Api.namespacesPath);
|
||||||
url = Api.buildUrl(Api.namespacesPath);
|
|
||||||
return $.ajax({
|
return $.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
data: {
|
data: {
|
||||||
|
@ -54,8 +53,7 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
projects: function(query, order, callback) {
|
projects: function(query, order, callback) {
|
||||||
var url;
|
var url = Api.buildUrl(Api.projectsPath);
|
||||||
url = Api.buildUrl(Api.projectsPath);
|
|
||||||
return $.ajax({
|
return $.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
data: {
|
data: {
|
||||||
|
@ -70,9 +68,8 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
newLabel: function(project_id, data, callback) {
|
newLabel: function(project_id, data, callback) {
|
||||||
var url;
|
var url = Api.buildUrl(Api.labelsPath)
|
||||||
url = Api.buildUrl(Api.labelsPath);
|
.replace(':id', project_id);
|
||||||
url = url.replace(':id', project_id);
|
|
||||||
data.private_token = gon.api_token;
|
data.private_token = gon.api_token;
|
||||||
return $.ajax({
|
return $.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
|
@ -86,9 +83,8 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
groupProjects: function(group_id, query, callback) {
|
groupProjects: function(group_id, query, callback) {
|
||||||
var url;
|
var url = Api.buildUrl(Api.groupProjectsPath)
|
||||||
url = Api.buildUrl(Api.groupProjectsPath);
|
.replace(':id', group_id);
|
||||||
url = url.replace(':id', group_id);
|
|
||||||
return $.ajax({
|
return $.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
data: {
|
data: {
|
||||||
|
@ -102,8 +98,8 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
licenseText: function(key, data, callback) {
|
licenseText: function(key, data, callback) {
|
||||||
var url;
|
var url = Api.buildUrl(Api.licensePath)
|
||||||
url = Api.buildUrl(Api.licensePath).replace(':key', key);
|
.replace(':key', key);
|
||||||
return $.ajax({
|
return $.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
data: data
|
data: data
|
||||||
|
@ -112,19 +108,32 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
gitignoreText: function(key, callback) {
|
gitignoreText: function(key, callback) {
|
||||||
var url;
|
var url = Api.buildUrl(Api.gitignorePath)
|
||||||
url = Api.buildUrl(Api.gitignorePath).replace(':key', key);
|
.replace(':key', key);
|
||||||
return $.get(url, function(gitignore) {
|
return $.get(url, function(gitignore) {
|
||||||
return callback(gitignore);
|
return callback(gitignore);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
gitlabCiYml: function(key, callback) {
|
gitlabCiYml: function(key, callback) {
|
||||||
var url;
|
var url = Api.buildUrl(Api.gitlabCiYmlPath)
|
||||||
url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key);
|
.replace(':key', key);
|
||||||
return $.get(url, function(file) {
|
return $.get(url, function(file) {
|
||||||
return callback(file);
|
return callback(file);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
issueTemplate: function(namespacePath, projectPath, key, type, callback) {
|
||||||
|
var url = Api.buildUrl(Api.issuableTemplatePath)
|
||||||
|
.replace(':key', key)
|
||||||
|
.replace(':type', type)
|
||||||
|
.replace(':project_path', projectPath)
|
||||||
|
.replace(':namespace_path', namespacePath);
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
dataType: 'json'
|
||||||
|
}).done(function(file) {
|
||||||
|
callback(null, file);
|
||||||
|
}).error(callback);
|
||||||
|
},
|
||||||
buildUrl: function(url) {
|
buildUrl: function(url) {
|
||||||
if (gon.relative_url_root != null) {
|
if (gon.relative_url_root != null) {
|
||||||
url = gon.relative_url_root + url;
|
url = gon.relative_url_root + url;
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
/*= require date.format */
|
/*= require date.format */
|
||||||
/*= require_directory ./behaviors */
|
/*= require_directory ./behaviors */
|
||||||
/*= require_directory ./blob */
|
/*= require_directory ./blob */
|
||||||
|
/*= require_directory ./templates */
|
||||||
/*= require_directory ./commit */
|
/*= require_directory ./commit */
|
||||||
/*= require_directory ./extensions */
|
/*= require_directory ./extensions */
|
||||||
/*= require_directory ./lib/utils */
|
/*= require_directory ./lib/utils */
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
}
|
}
|
||||||
this.onClick = bind(this.onClick, this);
|
this.onClick = bind(this.onClick, this);
|
||||||
this.dropdown = opts.dropdown, this.data = opts.data, this.pattern = opts.pattern, this.wrapper = opts.wrapper, this.editor = opts.editor, this.fileEndpoint = opts.fileEndpoint, this.$input = (ref = opts.$input) != null ? ref : $('#file_name');
|
this.dropdown = opts.dropdown, this.data = opts.data, this.pattern = opts.pattern, this.wrapper = opts.wrapper, this.editor = opts.editor, this.fileEndpoint = opts.fileEndpoint, this.$input = (ref = opts.$input) != null ? ref : $('#file_name');
|
||||||
|
this.dropdownIcon = $('.fa-chevron-down', this.dropdown);
|
||||||
this.buildDropdown();
|
this.buildDropdown();
|
||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
this.onFilenameUpdate();
|
this.onFilenameUpdate();
|
||||||
|
@ -60,11 +61,26 @@
|
||||||
return this.requestFile(item);
|
return this.requestFile(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
TemplateSelector.prototype.requestFile = function(item) {};
|
TemplateSelector.prototype.requestFile = function(item) {
|
||||||
|
// This `requestFile` method is an abstract method that should
|
||||||
|
// be added by all subclasses.
|
||||||
|
};
|
||||||
|
|
||||||
TemplateSelector.prototype.requestFileSuccess = function(file) {
|
TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus) {
|
||||||
this.editor.setValue(file.content, 1);
|
this.editor.setValue(file.content, 1);
|
||||||
return this.editor.focus();
|
if (!skipFocus) this.editor.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
TemplateSelector.prototype.startLoadingSpinner = function() {
|
||||||
|
this.dropdownIcon
|
||||||
|
.addClass('fa-spinner fa-spin')
|
||||||
|
.removeClass('fa-chevron-down');
|
||||||
|
};
|
||||||
|
|
||||||
|
TemplateSelector.prototype.stopLoadingSpinner = function() {
|
||||||
|
this.dropdownIcon
|
||||||
|
.addClass('fa-chevron-down')
|
||||||
|
.removeClass('fa-spinner fa-spin');
|
||||||
};
|
};
|
||||||
|
|
||||||
return TemplateSelector;
|
return TemplateSelector;
|
||||||
|
|
|
@ -55,6 +55,7 @@
|
||||||
shortcut_handler = new ShortcutsNavigation();
|
shortcut_handler = new ShortcutsNavigation();
|
||||||
new GLForm($('.issue-form'));
|
new GLForm($('.issue-form'));
|
||||||
new IssuableForm($('.issue-form'));
|
new IssuableForm($('.issue-form'));
|
||||||
|
new IssuableTemplateSelectors();
|
||||||
break;
|
break;
|
||||||
case 'projects:merge_requests:new':
|
case 'projects:merge_requests:new':
|
||||||
case 'projects:merge_requests:edit':
|
case 'projects:merge_requests:edit':
|
||||||
|
@ -62,6 +63,7 @@
|
||||||
shortcut_handler = new ShortcutsNavigation();
|
shortcut_handler = new ShortcutsNavigation();
|
||||||
new GLForm($('.merge-request-form'));
|
new GLForm($('.merge-request-form'));
|
||||||
new IssuableForm($('.merge-request-form'));
|
new IssuableForm($('.merge-request-form'));
|
||||||
|
new IssuableTemplateSelectors();
|
||||||
break;
|
break;
|
||||||
case 'projects:tags:new':
|
case 'projects:tags:new':
|
||||||
new ZenMode();
|
new ZenMode();
|
||||||
|
|
|
@ -5,13 +5,10 @@
|
||||||
|
|
||||||
this.Issuable = {
|
this.Issuable = {
|
||||||
init: function() {
|
init: function() {
|
||||||
if (!issuable_created) {
|
Issuable.initTemplates();
|
||||||
issuable_created = true;
|
Issuable.initSearch();
|
||||||
Issuable.initTemplates();
|
Issuable.initChecks();
|
||||||
Issuable.initSearch();
|
return Issuable.initLabelFilterRemove();
|
||||||
Issuable.initChecks();
|
|
||||||
return Issuable.initLabelFilterRemove();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
initTemplates: function() {
|
initTemplates: function() {
|
||||||
return Issuable.labelRow = _.template('<% _.each(labels, function(label){ %> <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> <%- label.title %> </a> <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>"> <i class="fa fa-times"></i> </button> </span> <% }); %>');
|
return Issuable.labelRow = _.template('<% _.each(labels, function(label){ %> <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> <%- label.title %> </a> <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>"> <i class="fa fa-times"></i> </button> </span> <% }); %>');
|
||||||
|
|
|
@ -44,8 +44,8 @@
|
||||||
|
|
||||||
// Enable submit button
|
// Enable submit button
|
||||||
const $branchInput = this.$wrap.find('input[name="protected_branch[name]"]');
|
const $branchInput = this.$wrap.find('input[name="protected_branch[name]"]');
|
||||||
const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_level_attributes][access_level]"]');
|
const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]');
|
||||||
const $allowedToPushInput = this.$wrap.find('input[name="protected_branch[push_access_level_attributes][access_level]"]');
|
const $allowedToPushInput = this.$wrap.find('input[name="protected_branch[push_access_levels_attributes][0][access_level]"]');
|
||||||
|
|
||||||
if ($branchInput.val() && $allowedToMergeInput.val() && $allowedToPushInput.val()){
|
if ($branchInput.val() && $allowedToMergeInput.val() && $allowedToPushInput.val()){
|
||||||
this.$form.find('input[type="submit"]').removeAttr('disabled');
|
this.$form.find('input[type="submit"]').removeAttr('disabled');
|
||||||
|
|
|
@ -39,12 +39,14 @@
|
||||||
_method: 'PATCH',
|
_method: 'PATCH',
|
||||||
id: this.$wrap.data('banchId'),
|
id: this.$wrap.data('banchId'),
|
||||||
protected_branch: {
|
protected_branch: {
|
||||||
merge_access_level_attributes: {
|
merge_access_levels_attributes: [{
|
||||||
|
id: this.$allowedToMergeDropdown.data('access-level-id'),
|
||||||
access_level: $allowedToMergeInput.val()
|
access_level: $allowedToMergeInput.val()
|
||||||
},
|
}],
|
||||||
push_access_level_attributes: {
|
push_access_levels_attributes: [{
|
||||||
|
id: this.$allowedToPushDropdown.data('access-level-id'),
|
||||||
access_level: $allowedToPushInput.val()
|
access_level: $allowedToPushInput.val()
|
||||||
}
|
}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
success: () => {
|
success: () => {
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*= require ../blob/template_selector */
|
||||||
|
|
||||||
|
((global) => {
|
||||||
|
class IssuableTemplateSelector extends TemplateSelector {
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
this.projectPath = this.dropdown.data('project-path');
|
||||||
|
this.namespacePath = this.dropdown.data('namespace-path');
|
||||||
|
this.issuableType = this.wrapper.data('issuable-type');
|
||||||
|
this.titleInput = $(`#${this.issuableType}_title`);
|
||||||
|
|
||||||
|
let initialQuery = {
|
||||||
|
name: this.dropdown.data('selected')
|
||||||
|
};
|
||||||
|
|
||||||
|
if (initialQuery.name) this.requestFile(initialQuery);
|
||||||
|
|
||||||
|
$('.reset-template', this.dropdown.parent()).on('click', () => {
|
||||||
|
if (this.currentTemplate) this.setInputValueToTemplateContent();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
requestFile(query) {
|
||||||
|
this.startLoadingSpinner();
|
||||||
|
Api.issueTemplate(this.namespacePath, this.projectPath, query.name, this.issuableType, (err, currentTemplate) => {
|
||||||
|
this.currentTemplate = currentTemplate;
|
||||||
|
if (err) return; // Error handled by global AJAX error handler
|
||||||
|
this.stopLoadingSpinner();
|
||||||
|
this.setInputValueToTemplateContent();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setInputValueToTemplateContent() {
|
||||||
|
// `this.requestFileSuccess` sets the value of the description input field
|
||||||
|
// to the content of the template selected.
|
||||||
|
if (this.titleInput.val() === '') {
|
||||||
|
// If the title has not yet been set, focus the title input and
|
||||||
|
// skip focusing the description input by setting `true` as the 2nd
|
||||||
|
// argument to `requestFileSuccess`.
|
||||||
|
this.requestFileSuccess(this.currentTemplate, true);
|
||||||
|
this.titleInput.focus();
|
||||||
|
} else {
|
||||||
|
this.requestFileSuccess(this.currentTemplate);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global.IssuableTemplateSelector = IssuableTemplateSelector;
|
||||||
|
})(window);
|
|
@ -0,0 +1,29 @@
|
||||||
|
((global) => {
|
||||||
|
class IssuableTemplateSelectors {
|
||||||
|
constructor(opts = {}) {
|
||||||
|
this.$dropdowns = opts.$dropdowns || $('.js-issuable-selector');
|
||||||
|
this.editor = opts.editor || this.initEditor();
|
||||||
|
|
||||||
|
this.$dropdowns.each((i, dropdown) => {
|
||||||
|
let $dropdown = $(dropdown);
|
||||||
|
new IssuableTemplateSelector({
|
||||||
|
pattern: /(\.md)/,
|
||||||
|
data: $dropdown.data('data'),
|
||||||
|
wrapper: $dropdown.closest('.js-issuable-selector-wrap'),
|
||||||
|
dropdown: $dropdown,
|
||||||
|
editor: this.editor
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initEditor() {
|
||||||
|
let editor = $('.markdown-area');
|
||||||
|
// Proxy ace-editor's .setValue to jQuery's .val
|
||||||
|
editor.setValue = editor.val;
|
||||||
|
editor.getValue = editor.val;
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global.IssuableTemplateSelectors = IssuableTemplateSelectors;
|
||||||
|
})(window);
|
|
@ -164,6 +164,10 @@
|
||||||
@include btn-outline($white-light, $orange-normal, $orange-normal, $orange-light, $white-light, $orange-light);
|
@include btn-outline($white-light, $orange-normal, $orange-normal, $orange-light, $white-light, $orange-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.btn-spam {
|
||||||
|
@include btn-outline($white-light, $red-normal, $red-normal, $red-light, $white-light, $red-light);
|
||||||
|
}
|
||||||
|
|
||||||
&.btn-danger,
|
&.btn-danger,
|
||||||
&.btn-remove,
|
&.btn-remove,
|
||||||
&.btn-red {
|
&.btn-red {
|
||||||
|
|
|
@ -56,9 +56,13 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
right: 6px;
|
right: 6px;
|
||||||
margin-top: -4px;
|
margin-top: -6px;
|
||||||
color: $dropdown-toggle-icon-color;
|
color: $dropdown-toggle-icon-color;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
|
&.fa-spinner {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: -8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover, {
|
&:hover, {
|
||||||
|
@ -406,6 +410,7 @@
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
cursor: pointer;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,11 @@
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
padding: 10px;
|
padding: 10px 0;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
font-family: $monospace_font;
|
font-family: $monospace_font;
|
||||||
font-size: $code_font_size !important;
|
font-size: $code_font_size;
|
||||||
line-height: $code_line_height !important;
|
line-height: $code_line_height !important;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
@ -20,13 +20,20 @@
|
||||||
border-left: 1px solid;
|
border-left: 1px solid;
|
||||||
|
|
||||||
code {
|
code {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 100%;
|
||||||
font-family: $monospace_font;
|
font-family: $monospace_font;
|
||||||
white-space: pre;
|
white-space: normal;
|
||||||
word-wrap: normal;
|
word-wrap: normal;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
.line {
|
.line {
|
||||||
display: inline-block;
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 19px;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
white-space: pre;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -395,3 +395,12 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.js-issuable-selector-wrap {
|
||||||
|
.js-issuable-selector {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
@media (max-width: $screen-sm-max) {
|
||||||
|
margin-bottom: $gl-padding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -69,6 +69,10 @@
|
||||||
|
|
||||||
&.ci-success {
|
&.ci-success {
|
||||||
color: $gl-success;
|
color: $gl-success;
|
||||||
|
|
||||||
|
a.environment {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.ci-success_with_warnings {
|
&.ci-success_with_warnings {
|
||||||
|
@ -126,7 +130,6 @@
|
||||||
&.has-conflicts .fa-exclamation-triangle {
|
&.has-conflicts .fa-exclamation-triangle {
|
||||||
color: $gl-warning;
|
color: $gl-warning;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p:last-child {
|
p:last-child {
|
||||||
|
|
|
@ -14,4 +14,14 @@ class Admin::SpamLogsController < Admin::ApplicationController
|
||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mark_as_ham
|
||||||
|
spam_log = SpamLog.find(params[:id])
|
||||||
|
|
||||||
|
if HamService.new(spam_log).mark_as_ham!
|
||||||
|
redirect_to admin_spam_logs_path, notice: 'Spam log successfully submitted as ham.'
|
||||||
|
else
|
||||||
|
redirect_to admin_spam_logs_path, alert: 'Error with Akismet. Please check the logs for more info.'
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
class AutocompleteController < ApplicationController
|
class AutocompleteController < ApplicationController
|
||||||
skip_before_action :authenticate_user!, only: [:users]
|
skip_before_action :authenticate_user!, only: [:users]
|
||||||
|
before_action :load_project, only: [:users]
|
||||||
before_action :find_users, only: [:users]
|
before_action :find_users, only: [:users]
|
||||||
|
|
||||||
def users
|
def users
|
||||||
|
@ -55,11 +56,8 @@ class AutocompleteController < ApplicationController
|
||||||
|
|
||||||
def find_users
|
def find_users
|
||||||
@users =
|
@users =
|
||||||
if params[:project_id].present?
|
if @project
|
||||||
project = Project.find(params[:project_id])
|
@project.team.users
|
||||||
return render_404 unless can?(current_user, :read_project, project)
|
|
||||||
|
|
||||||
project.team.users
|
|
||||||
elsif params[:group_id].present?
|
elsif params[:group_id].present?
|
||||||
group = Group.find(params[:group_id])
|
group = Group.find(params[:group_id])
|
||||||
return render_404 unless can?(current_user, :read_group, group)
|
return render_404 unless can?(current_user, :read_group, group)
|
||||||
|
@ -71,4 +69,14 @@ class AutocompleteController < ApplicationController
|
||||||
User.none
|
User.none
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def load_project
|
||||||
|
@project ||= begin
|
||||||
|
if params[:project_id].present?
|
||||||
|
project = Project.find(params[:project_id])
|
||||||
|
return render_404 unless can?(current_user, :read_project, project)
|
||||||
|
project
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,11 +7,16 @@ module ServiceParams
|
||||||
:build_key, :server, :teamcity_url, :drone_url, :build_type,
|
:build_key, :server, :teamcity_url, :drone_url, :build_type,
|
||||||
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
|
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
|
||||||
:colorize_messages, :channels,
|
:colorize_messages, :channels,
|
||||||
:push_events, :issues_events, :merge_requests_events, :tag_push_events,
|
# We're using `issues_events` and `merge_requests_events`
|
||||||
:note_events, :build_events, :wiki_page_events,
|
# in the view so we still need to explicitly state them
|
||||||
:notify_only_broken_builds, :add_pusher,
|
# here. `Service#event_names` would only give
|
||||||
:send_from_committer_email, :disable_diffs, :external_wiki_url,
|
# `issue_events` and `merge_request_events` (singular!)
|
||||||
:notify, :color,
|
# See app/helpers/services_helper.rb for how we
|
||||||
|
# make those event names plural as special case.
|
||||||
|
:issues_events, :merge_requests_events,
|
||||||
|
:notify_only_broken_builds, :notify_only_broken_pipelines,
|
||||||
|
:add_pusher, :send_from_committer_email, :disable_diffs,
|
||||||
|
:external_wiki_url, :notify, :color,
|
||||||
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification,
|
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification,
|
||||||
:jira_issue_transition_id]
|
:jira_issue_transition_id]
|
||||||
|
|
||||||
|
@ -19,9 +24,7 @@ module ServiceParams
|
||||||
FILTER_BLANK_PARAMS = [:password]
|
FILTER_BLANK_PARAMS = [:password]
|
||||||
|
|
||||||
def service_params
|
def service_params
|
||||||
dynamic_params = []
|
dynamic_params = @service.event_channel_names + @service.event_names
|
||||||
dynamic_params.concat(@service.event_channel_names)
|
|
||||||
|
|
||||||
service_params = params.permit(:id, service: ALLOWED_PARAMS + dynamic_params)
|
service_params = params.permit(:id, service: ALLOWED_PARAMS + dynamic_params)
|
||||||
|
|
||||||
if service_params[:service].is_a?(Hash)
|
if service_params[:service].is_a?(Hash)
|
||||||
|
|
25
app/controllers/concerns/spammable_actions.rb
Normal file
25
app/controllers/concerns/spammable_actions.rb
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
module SpammableActions
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
before_action :authorize_submit_spammable!, only: :mark_as_spam
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_as_spam
|
||||||
|
if SpamService.new(spammable).mark_as_spam!
|
||||||
|
redirect_to spammable, notice: "#{spammable.class.to_s} was submitted to Akismet successfully."
|
||||||
|
else
|
||||||
|
redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def spammable
|
||||||
|
raise NotImplementedError, "#{self.class} does not implement #{__method__}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize_submit_spammable!
|
||||||
|
access_denied! unless current_user.admin?
|
||||||
|
end
|
||||||
|
end
|
|
@ -37,8 +37,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController
|
||||||
|
|
||||||
def todos_counts
|
def todos_counts
|
||||||
{
|
{
|
||||||
count: TodosFinder.new(current_user, state: :pending).execute.count,
|
count: current_user.todos_pending_count,
|
||||||
done_count: TodosFinder.new(current_user, state: :done).execute.count
|
done_count: current_user.todos_done_count
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
class Import::GitlabProjectsController < Import::BaseController
|
class Import::GitlabProjectsController < Import::BaseController
|
||||||
before_action :verify_gitlab_project_import_enabled
|
before_action :verify_gitlab_project_import_enabled
|
||||||
|
before_action :authenticate_admin!
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@namespace_id = project_params[:namespace_id]
|
@namespace_id = project_params[:namespace_id]
|
||||||
|
@ -47,4 +48,8 @@ class Import::GitlabProjectsController < Import::BaseController
|
||||||
:path, :namespace_id, :file
|
:path, :namespace_id, :file
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def authenticate_admin!
|
||||||
|
render_404 unless current_user.is_admin?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,11 +4,24 @@ class Projects::BadgesController < Projects::ApplicationController
|
||||||
before_action :no_cache_headers, except: [:index]
|
before_action :no_cache_headers, except: [:index]
|
||||||
|
|
||||||
def build
|
def build
|
||||||
badge = Gitlab::Badge::Build.new(project, params[:ref])
|
build_status = Gitlab::Badge::Build::Status
|
||||||
|
.new(project, params[:ref])
|
||||||
|
|
||||||
|
render_badge build_status
|
||||||
|
end
|
||||||
|
|
||||||
|
def coverage
|
||||||
|
coverage_report = Gitlab::Badge::Coverage::Report
|
||||||
|
.new(project, params[:ref], params[:job])
|
||||||
|
|
||||||
|
render_badge coverage_report
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def render_badge(badge)
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { render_404 }
|
format.html { render_404 }
|
||||||
|
|
||||||
format.svg do
|
format.svg do
|
||||||
render 'badge', locals: { badge: badge.template }
|
render 'badge', locals: { badge: badge.template }
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,6 +17,7 @@ class Projects::BlobController < Projects::ApplicationController
|
||||||
before_action :require_branch_head, only: [:edit, :update]
|
before_action :require_branch_head, only: [:edit, :update]
|
||||||
before_action :editor_variables, except: [:show, :preview, :diff]
|
before_action :editor_variables, except: [:show, :preview, :diff]
|
||||||
before_action :validate_diff_params, only: :diff
|
before_action :validate_diff_params, only: :diff
|
||||||
|
before_action :set_last_commit_sha, only: [:edit, :update]
|
||||||
|
|
||||||
def new
|
def new
|
||||||
commit unless @repository.empty?
|
commit unless @repository.empty?
|
||||||
|
@ -33,7 +34,6 @@ class Projects::BlobController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
@last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
|
|
||||||
blob.load_all_data!(@repository)
|
blob.load_all_data!(@repository)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -55,6 +55,10 @@ class Projects::BlobController < Projects::ApplicationController
|
||||||
create_commit(Files::UpdateService, success_path: after_edit_path,
|
create_commit(Files::UpdateService, success_path: after_edit_path,
|
||||||
failure_view: :edit,
|
failure_view: :edit,
|
||||||
failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
|
failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
|
||||||
|
|
||||||
|
rescue Files::UpdateService::FileChangedError
|
||||||
|
@conflict = true
|
||||||
|
render :edit
|
||||||
end
|
end
|
||||||
|
|
||||||
def preview
|
def preview
|
||||||
|
@ -152,7 +156,8 @@ class Projects::BlobController < Projects::ApplicationController
|
||||||
file_path: @file_path,
|
file_path: @file_path,
|
||||||
commit_message: params[:commit_message],
|
commit_message: params[:commit_message],
|
||||||
file_content: params[:content],
|
file_content: params[:content],
|
||||||
file_content_encoding: params[:encoding]
|
file_content_encoding: params[:encoding],
|
||||||
|
last_commit_sha: params[:last_commit_sha]
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -161,4 +166,9 @@ class Projects::BlobController < Projects::ApplicationController
|
||||||
render nothing: true
|
render nothing: true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_last_commit_sha
|
||||||
|
@last_commit_sha = Gitlab::Git::Commit.
|
||||||
|
last_for_path(@repository, @ref, @path).sha
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -56,6 +56,7 @@ class Projects::HooksController < Projects::ApplicationController
|
||||||
def hook_params
|
def hook_params
|
||||||
params.require(:hook).permit(
|
params.require(:hook).permit(
|
||||||
:build_events,
|
:build_events,
|
||||||
|
:pipeline_events,
|
||||||
:enable_ssl_verification,
|
:enable_ssl_verification,
|
||||||
:issues_events,
|
:issues_events,
|
||||||
:merge_requests_events,
|
:merge_requests_events,
|
||||||
|
|
|
@ -4,6 +4,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||||
include IssuableActions
|
include IssuableActions
|
||||||
include ToggleAwardEmoji
|
include ToggleAwardEmoji
|
||||||
include IssuableCollections
|
include IssuableCollections
|
||||||
|
include SpammableActions
|
||||||
|
|
||||||
before_action :redirect_to_external_issue_tracker, only: [:index, :new]
|
before_action :redirect_to_external_issue_tracker, only: [:index, :new]
|
||||||
before_action :module_enabled
|
before_action :module_enabled
|
||||||
|
@ -181,6 +182,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||||
alias_method :subscribable_resource, :issue
|
alias_method :subscribable_resource, :issue
|
||||||
alias_method :issuable, :issue
|
alias_method :issuable, :issue
|
||||||
alias_method :awardable, :issue
|
alias_method :awardable, :issue
|
||||||
|
alias_method :spammable, :issue
|
||||||
|
|
||||||
def authorize_read_issue!
|
def authorize_read_issue!
|
||||||
return render_404 unless can?(current_user, :read_issue, @issue)
|
return render_404 unless can?(current_user, :read_issue, @issue)
|
||||||
|
|
|
@ -3,7 +3,13 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@ref = params[:ref] || @project.default_branch || 'master'
|
@ref = params[:ref] || @project.default_branch || 'master'
|
||||||
@build_badge = Gitlab::Badge::Build.new(@project, @ref).metadata
|
|
||||||
|
@badges = [Gitlab::Badge::Build::Status,
|
||||||
|
Gitlab::Badge::Coverage::Report]
|
||||||
|
|
||||||
|
@badges.map! do |badge|
|
||||||
|
badge.new(@project, @ref).metadata
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
|
|
@ -9,16 +9,16 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@protected_branch = @project.protected_branches.new
|
@protected_branch = @project.protected_branches.new
|
||||||
load_protected_branches_gon_variables
|
load_gon_index
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@protected_branch = ProtectedBranches::CreateService.new(@project, current_user, protected_branch_params).execute
|
@protected_branch = ::ProtectedBranches::CreateService.new(@project, current_user, protected_branch_params).execute
|
||||||
if @protected_branch.persisted?
|
if @protected_branch.persisted?
|
||||||
redirect_to namespace_project_protected_branches_path(@project.namespace, @project)
|
redirect_to namespace_project_protected_branches_path(@project.namespace, @project)
|
||||||
else
|
else
|
||||||
load_protected_branches
|
load_protected_branches
|
||||||
load_protected_branches_gon_variables
|
load_gon_index
|
||||||
render :index
|
render :index
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -28,7 +28,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@protected_branch = ProtectedBranches::UpdateService.new(@project, current_user, protected_branch_params).execute(@protected_branch)
|
@protected_branch = ::ProtectedBranches::UpdateService.new(@project, current_user, protected_branch_params).execute(@protected_branch)
|
||||||
|
|
||||||
if @protected_branch.valid?
|
if @protected_branch.valid?
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
@ -58,17 +58,23 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
|
||||||
|
|
||||||
def protected_branch_params
|
def protected_branch_params
|
||||||
params.require(:protected_branch).permit(:name,
|
params.require(:protected_branch).permit(:name,
|
||||||
merge_access_level_attributes: [:access_level],
|
merge_access_levels_attributes: [:access_level, :id],
|
||||||
push_access_level_attributes: [:access_level])
|
push_access_levels_attributes: [:access_level, :id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_protected_branches
|
def load_protected_branches
|
||||||
@protected_branches = @project.protected_branches.order(:name).page(params[:page])
|
@protected_branches = @project.protected_branches.order(:name).page(params[:page])
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_protected_branches_gon_variables
|
def access_levels_options
|
||||||
gon.push({ open_branches: @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } },
|
{
|
||||||
push_access_levels: ProtectedBranch::PushAccessLevel.human_access_levels.map { |id, text| { id: id, text: text } },
|
push_access_levels: ProtectedBranch::PushAccessLevel.human_access_levels.map { |id, text| { id: id, text: text, before_divider: true } },
|
||||||
merge_access_levels: ProtectedBranch::MergeAccessLevel.human_access_levels.map { |id, text| { id: id, text: text } } })
|
merge_access_levels: ProtectedBranch::MergeAccessLevel.human_access_levels.map { |id, text| { id: id, text: text, before_divider: true } }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_gon_index
|
||||||
|
params = { open_branches: @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } } }
|
||||||
|
gon.push(params.merge(access_levels_options))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
19
app/controllers/projects/templates_controller.rb
Normal file
19
app/controllers/projects/templates_controller.rb
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
class Projects::TemplatesController < Projects::ApplicationController
|
||||||
|
before_action :authenticate_user!, :get_template_class
|
||||||
|
|
||||||
|
def show
|
||||||
|
template = @template_type.find(params[:key], project)
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.json { render json: template.to_json }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def get_template_class
|
||||||
|
template_types = { issue: Gitlab::Template::IssueTemplate, merge_request: Gitlab::Template::MergeRequestTemplate }.with_indifferent_access
|
||||||
|
@template_type = template_types[params[:template_type]]
|
||||||
|
render json: [], status: 404 unless @template_type
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,7 @@
|
||||||
class ProjectsFinder < UnionFinder
|
class ProjectsFinder < UnionFinder
|
||||||
def execute(current_user = nil, options = {})
|
def execute(current_user = nil, project_ids_relation = nil)
|
||||||
segments = all_projects(current_user)
|
segments = all_projects(current_user)
|
||||||
|
segments.map! { |s| s.where(id: project_ids_relation) } if project_ids_relation
|
||||||
|
|
||||||
find_union(segments, Project)
|
find_union(segments, Project)
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,9 +27,11 @@ class TodosFinder
|
||||||
items = by_action_id(items)
|
items = by_action_id(items)
|
||||||
items = by_action(items)
|
items = by_action(items)
|
||||||
items = by_author(items)
|
items = by_author(items)
|
||||||
items = by_project(items)
|
|
||||||
items = by_state(items)
|
items = by_state(items)
|
||||||
items = by_type(items)
|
items = by_type(items)
|
||||||
|
# Filtering by project HAS TO be the last because we use
|
||||||
|
# the project IDs yielded by the todos query thus far
|
||||||
|
items = by_project(items)
|
||||||
|
|
||||||
items.reorder(id: :desc)
|
items.reorder(id: :desc)
|
||||||
end
|
end
|
||||||
|
@ -91,14 +93,9 @@ class TodosFinder
|
||||||
@project
|
@project
|
||||||
end
|
end
|
||||||
|
|
||||||
def projects
|
def projects(items)
|
||||||
return @projects if defined?(@projects)
|
item_project_ids = items.reorder(nil).select(:project_id)
|
||||||
|
ProjectsFinder.new.execute(current_user, item_project_ids)
|
||||||
if project?
|
|
||||||
@projects = project
|
|
||||||
else
|
|
||||||
@projects = ProjectsFinder.new.execute(current_user)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def type?
|
def type?
|
||||||
|
@ -136,8 +133,9 @@ class TodosFinder
|
||||||
def by_project(items)
|
def by_project(items)
|
||||||
if project?
|
if project?
|
||||||
items = items.where(project: project)
|
items = items.where(project: project)
|
||||||
elsif projects
|
else
|
||||||
items = items.merge(projects).joins(:project)
|
item_projects = projects(items)
|
||||||
|
items = items.merge(item_projects).joins(:project)
|
||||||
end
|
end
|
||||||
|
|
||||||
items
|
items
|
||||||
|
|
|
@ -182,17 +182,42 @@ module BlobHelper
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def selected_template(issuable)
|
||||||
|
templates = issuable_templates(issuable)
|
||||||
|
params[:issuable_template] if templates.include?(params[:issuable_template])
|
||||||
|
end
|
||||||
|
|
||||||
|
def can_add_template?(issuable)
|
||||||
|
names = issuable_templates(issuable)
|
||||||
|
names.empty? && can?(current_user, :push_code, @project) && !@project.private?
|
||||||
|
end
|
||||||
|
|
||||||
|
def merge_request_template_names
|
||||||
|
@merge_request_templates ||= Gitlab::Template::MergeRequestTemplate.dropdown_names(ref_project)
|
||||||
|
end
|
||||||
|
|
||||||
|
def issue_template_names
|
||||||
|
@issue_templates ||= Gitlab::Template::IssueTemplate.dropdown_names(ref_project)
|
||||||
|
end
|
||||||
|
|
||||||
|
def issuable_templates(issuable)
|
||||||
|
@issuable_templates ||=
|
||||||
|
if issuable.is_a?(Issue)
|
||||||
|
issue_template_names
|
||||||
|
elsif issuable.is_a?(MergeRequest)
|
||||||
|
merge_request_template_names
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def ref_project
|
||||||
|
@ref_project ||= @target_project || @project
|
||||||
|
end
|
||||||
|
|
||||||
def gitignore_names
|
def gitignore_names
|
||||||
@gitignore_names ||=
|
@gitignore_names ||= Gitlab::Template::GitignoreTemplate.dropdown_names
|
||||||
Gitlab::Template::Gitignore.categories.keys.map do |k|
|
|
||||||
[k, Gitlab::Template::Gitignore.by_category(k).map { |t| { name: t.name } }]
|
|
||||||
end.to_h
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def gitlab_ci_ymls
|
def gitlab_ci_ymls
|
||||||
@gitlab_ci_ymls ||=
|
@gitlab_ci_ymls ||= Gitlab::Template::GitlabCiYmlTemplate.dropdown_names
|
||||||
Gitlab::Template::GitlabCiYml.categories.keys.map do |k|
|
|
||||||
[k, Gitlab::Template::GitlabCiYml.by_category(k).map { |t| { name: t.name } }]
|
|
||||||
end.to_h
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,13 +20,19 @@ module SortingHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def projects_sort_options_hash
|
def projects_sort_options_hash
|
||||||
{
|
options = {
|
||||||
sort_value_name => sort_title_name,
|
sort_value_name => sort_title_name,
|
||||||
sort_value_recently_updated => sort_title_recently_updated,
|
sort_value_recently_updated => sort_title_recently_updated,
|
||||||
sort_value_oldest_updated => sort_title_oldest_updated,
|
sort_value_oldest_updated => sort_title_oldest_updated,
|
||||||
sort_value_recently_created => sort_title_recently_created,
|
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')
|
||||||
|
options.merge!(sort_value_largest_repo => sort_title_largest_repo)
|
||||||
|
end
|
||||||
|
|
||||||
|
options
|
||||||
end
|
end
|
||||||
|
|
||||||
def sort_title_priority
|
def sort_title_priority
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
module TodosHelper
|
module TodosHelper
|
||||||
def todos_pending_count
|
def todos_pending_count
|
||||||
@todos_pending_count ||= TodosFinder.new(current_user, state: :pending).execute.count
|
@todos_pending_count ||= current_user.todos_pending_count
|
||||||
end
|
end
|
||||||
|
|
||||||
def todos_done_count
|
def todos_done_count
|
||||||
@todos_done_count ||= TodosFinder.new(current_user, state: :done).execute.count
|
@todos_done_count ||= current_user.todos_done_count
|
||||||
end
|
end
|
||||||
|
|
||||||
def todo_action_name(todo)
|
def todo_action_name(todo)
|
||||||
|
|
|
@ -3,6 +3,9 @@ class Blob < SimpleDelegator
|
||||||
CACHE_TIME = 60 # Cache raw blobs referred to by a (mutable) ref for 1 minute
|
CACHE_TIME = 60 # Cache raw blobs referred to by a (mutable) ref for 1 minute
|
||||||
CACHE_TIME_IMMUTABLE = 3600 # Cache blobs referred to by an immutable reference for 1 hour
|
CACHE_TIME_IMMUTABLE = 3600 # Cache blobs referred to by an immutable reference for 1 hour
|
||||||
|
|
||||||
|
# The maximum size of an SVG that can be displayed.
|
||||||
|
MAXIMUM_SVG_SIZE = 2.megabytes
|
||||||
|
|
||||||
# Wrap a Gitlab::Git::Blob object, or return nil when given nil
|
# Wrap a Gitlab::Git::Blob object, or return nil when given nil
|
||||||
#
|
#
|
||||||
# This method prevents the decorated object from evaluating to "truthy" when
|
# This method prevents the decorated object from evaluating to "truthy" when
|
||||||
|
@ -31,6 +34,10 @@ class Blob < SimpleDelegator
|
||||||
text? && language && language.name == 'SVG'
|
text? && language && language.name == 'SVG'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def size_within_svg_limits?
|
||||||
|
size <= MAXIMUM_SVG_SIZE
|
||||||
|
end
|
||||||
|
|
||||||
def video?
|
def video?
|
||||||
UploaderHelper::VIDEO_EXT.include?(extname.downcase.delete('.'))
|
UploaderHelper::VIDEO_EXT.include?(extname.downcase.delete('.'))
|
||||||
end
|
end
|
||||||
|
|
|
@ -344,7 +344,7 @@ module Ci
|
||||||
|
|
||||||
def execute_hooks
|
def execute_hooks
|
||||||
return unless project
|
return unless project
|
||||||
build_data = Gitlab::BuildDataBuilder.build(self)
|
build_data = Gitlab::DataBuilder::Build.build(self)
|
||||||
project.execute_hooks(build_data.dup, :build_hooks)
|
project.execute_hooks(build_data.dup, :build_hooks)
|
||||||
project.execute_services(build_data.dup, :build_hooks)
|
project.execute_services(build_data.dup, :build_hooks)
|
||||||
project.running_or_pending_build_count(force: true)
|
project.running_or_pending_build_count(force: true)
|
||||||
|
|
|
@ -19,6 +19,8 @@ module Ci
|
||||||
|
|
||||||
after_save :keep_around_commits
|
after_save :keep_around_commits
|
||||||
|
|
||||||
|
delegate :stages, to: :statuses
|
||||||
|
|
||||||
state_machine :status, initial: :created do
|
state_machine :status, initial: :created do
|
||||||
event :enqueue do
|
event :enqueue do
|
||||||
transition created: :pending
|
transition created: :pending
|
||||||
|
@ -56,6 +58,10 @@ module Ci
|
||||||
before_transition do |pipeline|
|
before_transition do |pipeline|
|
||||||
pipeline.update_duration
|
pipeline.update_duration
|
||||||
end
|
end
|
||||||
|
|
||||||
|
after_transition do |pipeline, transition|
|
||||||
|
pipeline.execute_hooks unless transition.loopback?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# ref can't be HEAD or SHA, can only be branch/tag name
|
# ref can't be HEAD or SHA, can only be branch/tag name
|
||||||
|
@ -243,8 +249,18 @@ module Ci
|
||||||
self.duration = statuses.latest.duration
|
self.duration = statuses.latest.duration
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def execute_hooks
|
||||||
|
data = pipeline_data
|
||||||
|
project.execute_hooks(data, :pipeline_hooks)
|
||||||
|
project.execute_services(data, :pipeline_hooks)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def pipeline_data
|
||||||
|
Gitlab::DataBuilder::Pipeline.build(self)
|
||||||
|
end
|
||||||
|
|
||||||
def latest_builds_status
|
def latest_builds_status
|
||||||
return 'failed' unless yaml_errors.blank?
|
return 'failed' unless yaml_errors.blank?
|
||||||
|
|
||||||
|
|
7
app/models/concerns/protected_branch_access.rb
Normal file
7
app/models/concerns/protected_branch_access.rb
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
module ProtectedBranchAccess
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
def humanize
|
||||||
|
self.class.human_access_levels[self.access_level]
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,9 +1,32 @@
|
||||||
module Spammable
|
module Spammable
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def attr_spammable(attr, options = {})
|
||||||
|
spammable_attrs << [attr.to_s, options]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
included do
|
included do
|
||||||
|
has_one :user_agent_detail, as: :subject, dependent: :destroy
|
||||||
|
|
||||||
attr_accessor :spam
|
attr_accessor :spam
|
||||||
|
|
||||||
after_validation :check_for_spam, on: :create
|
after_validation :check_for_spam, on: :create
|
||||||
|
|
||||||
|
cattr_accessor :spammable_attrs, instance_accessor: false do
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
delegate :ip_address, :user_agent, to: :user_agent_detail, allow_nil: true
|
||||||
|
end
|
||||||
|
|
||||||
|
def submittable_as_spam?
|
||||||
|
if user_agent_detail
|
||||||
|
user_agent_detail.submittable?
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def spam?
|
def spam?
|
||||||
|
@ -13,4 +36,33 @@ module Spammable
|
||||||
def check_for_spam
|
def check_for_spam
|
||||||
self.errors.add(:base, "Your #{self.class.name.underscore} has been recognized as spam and has been discarded.") if spam?
|
self.errors.add(:base, "Your #{self.class.name.underscore} has been recognized as spam and has been discarded.") if spam?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def spam_title
|
||||||
|
attr = self.class.spammable_attrs.find do |_, options|
|
||||||
|
options.fetch(:spam_title, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
public_send(attr.first) if attr && respond_to?(attr.first.to_sym)
|
||||||
|
end
|
||||||
|
|
||||||
|
def spam_description
|
||||||
|
attr = self.class.spammable_attrs.find do |_, options|
|
||||||
|
options.fetch(:spam_description, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
public_send(attr.first) if attr && respond_to?(attr.first.to_sym)
|
||||||
|
end
|
||||||
|
|
||||||
|
def spammable_text
|
||||||
|
result = self.class.spammable_attrs.map do |attr|
|
||||||
|
public_send(attr.first)
|
||||||
|
end
|
||||||
|
|
||||||
|
result.reject(&:blank?).join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Override in Spammable if further checks are necessary
|
||||||
|
def check_for_spam?
|
||||||
|
true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -36,4 +36,10 @@ class Deployment < ActiveRecord::Base
|
||||||
def manual_actions
|
def manual_actions
|
||||||
deployable.try(:other_actions)
|
deployable.try(:other_actions)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def includes_commit?(commit)
|
||||||
|
return false unless commit
|
||||||
|
|
||||||
|
project.repository.is_ancestor?(commit.id, sha)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,4 +25,10 @@ class Environment < ActiveRecord::Base
|
||||||
def nullify_external_url
|
def nullify_external_url
|
||||||
self.external_url = nil if self.external_url.blank?
|
self.external_url = nil if self.external_url.blank?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def includes_commit?(commit)
|
||||||
|
return false unless last_deployment
|
||||||
|
|
||||||
|
last_deployment.includes_commit?(commit)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,5 +5,6 @@ class ProjectHook < WebHook
|
||||||
scope :note_hooks, -> { where(note_events: true) }
|
scope :note_hooks, -> { where(note_events: true) }
|
||||||
scope :merge_request_hooks, -> { where(merge_requests_events: true) }
|
scope :merge_request_hooks, -> { where(merge_requests_events: true) }
|
||||||
scope :build_hooks, -> { where(build_events: true) }
|
scope :build_hooks, -> { where(build_events: true) }
|
||||||
|
scope :pipeline_hooks, -> { where(pipeline_events: true) }
|
||||||
scope :wiki_page_hooks, -> { where(wiki_page_events: true) }
|
scope :wiki_page_hooks, -> { where(wiki_page_events: true) }
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,6 +8,7 @@ class WebHook < ActiveRecord::Base
|
||||||
default_value_for :merge_requests_events, false
|
default_value_for :merge_requests_events, false
|
||||||
default_value_for :tag_push_events, false
|
default_value_for :tag_push_events, false
|
||||||
default_value_for :build_events, false
|
default_value_for :build_events, false
|
||||||
|
default_value_for :pipeline_events, false
|
||||||
default_value_for :enable_ssl_verification, true
|
default_value_for :enable_ssl_verification, true
|
||||||
|
|
||||||
scope :push_hooks, -> { where(push_events: true) }
|
scope :push_hooks, -> { where(push_events: true) }
|
||||||
|
|
|
@ -36,6 +36,9 @@ class Issue < ActiveRecord::Base
|
||||||
scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
|
scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
|
||||||
scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
|
scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
|
||||||
|
|
||||||
|
attr_spammable :title, spam_title: true
|
||||||
|
attr_spammable :description, spam_description: true
|
||||||
|
|
||||||
state_machine :state, initial: :opened do
|
state_machine :state, initial: :opened do
|
||||||
event :close do
|
event :close do
|
||||||
transition [:reopened, :opened] => :closed
|
transition [:reopened, :opened] => :closed
|
||||||
|
@ -262,4 +265,9 @@ class Issue < ActiveRecord::Base
|
||||||
def overdue?
|
def overdue?
|
||||||
due_date.try(:past?) || false
|
due_date.try(:past?) || false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Only issues on public projects should be checked for spam
|
||||||
|
def check_for_spam?
|
||||||
|
project.public?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -591,6 +591,14 @@ class MergeRequest < ActiveRecord::Base
|
||||||
!pipeline || pipeline.success?
|
!pipeline || pipeline.success?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def environments
|
||||||
|
return unless diff_head_commit
|
||||||
|
|
||||||
|
target_project.environments.select do |environment|
|
||||||
|
environment.includes_commit?(diff_head_commit)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def state_human_name
|
def state_human_name
|
||||||
if merged?
|
if merged?
|
||||||
"Merged"
|
"Merged"
|
||||||
|
|
|
@ -51,8 +51,7 @@ class BuildsEmailService < Service
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_data(project = nil, user = nil)
|
def test_data(project = nil, user = nil)
|
||||||
build = project.builds.last
|
Gitlab::DataBuilder::Build.build(project.builds.last)
|
||||||
Gitlab::BuildDataBuilder.build(build)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def fields
|
def fields
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
class PivotaltrackerService < Service
|
class PivotaltrackerService < Service
|
||||||
include HTTParty
|
include HTTParty
|
||||||
|
|
||||||
prop_accessor :token
|
API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits'
|
||||||
|
|
||||||
|
prop_accessor :token, :restrict_to_branch
|
||||||
validates :token, presence: true, if: :activated?
|
validates :token, presence: true, if: :activated?
|
||||||
|
|
||||||
def title
|
def title
|
||||||
|
@ -18,7 +20,17 @@ class PivotaltrackerService < Service
|
||||||
|
|
||||||
def fields
|
def fields
|
||||||
[
|
[
|
||||||
{ type: 'text', name: 'token', placeholder: '' }
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'token',
|
||||||
|
placeholder: 'Pivotal Tracker API token.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'restrict_to_branch',
|
||||||
|
placeholder: 'Comma-separated list of branches which will be ' \
|
||||||
|
'automatically inspected. Leave blank to include all branches.'
|
||||||
|
}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -28,8 +40,8 @@ class PivotaltrackerService < Service
|
||||||
|
|
||||||
def execute(data)
|
def execute(data)
|
||||||
return unless supported_events.include?(data[:object_kind])
|
return unless supported_events.include?(data[:object_kind])
|
||||||
|
return unless allowed_branch?(data[:ref])
|
||||||
|
|
||||||
url = 'https://www.pivotaltracker.com/services/v5/source_commits'
|
|
||||||
data[:commits].each do |commit|
|
data[:commits].each do |commit|
|
||||||
message = {
|
message = {
|
||||||
'source_commit' => {
|
'source_commit' => {
|
||||||
|
@ -40,7 +52,7 @@ class PivotaltrackerService < Service
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PivotaltrackerService.post(
|
PivotaltrackerService.post(
|
||||||
url,
|
API_ENDPOINT,
|
||||||
body: message.to_json,
|
body: message.to_json,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
|
@ -49,4 +61,15 @@ class PivotaltrackerService < Service
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def allowed_branch?(ref)
|
||||||
|
return true unless ref.present? && restrict_to_branch.present?
|
||||||
|
|
||||||
|
branch = Gitlab::Git.ref_name(ref)
|
||||||
|
allowed_branches = restrict_to_branch.split(',').map(&:strip)
|
||||||
|
|
||||||
|
branch.present? && allowed_branches.include?(branch)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -56,6 +56,10 @@ class ProjectWiki
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def repository_exists?
|
||||||
|
!!repository.exists?
|
||||||
|
end
|
||||||
|
|
||||||
def empty?
|
def empty?
|
||||||
pages.empty?
|
pages.empty?
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,11 +5,14 @@ class ProtectedBranch < ActiveRecord::Base
|
||||||
validates :name, presence: true
|
validates :name, presence: true
|
||||||
validates :project, presence: true
|
validates :project, presence: true
|
||||||
|
|
||||||
has_one :merge_access_level, dependent: :destroy
|
has_many :merge_access_levels, dependent: :destroy
|
||||||
has_one :push_access_level, dependent: :destroy
|
has_many :push_access_levels, dependent: :destroy
|
||||||
|
|
||||||
accepts_nested_attributes_for :push_access_level
|
validates_length_of :merge_access_levels, is: 1, message: "are restricted to a single instance per protected branch."
|
||||||
accepts_nested_attributes_for :merge_access_level
|
validates_length_of :push_access_levels, is: 1, message: "are restricted to a single instance per protected branch."
|
||||||
|
|
||||||
|
accepts_nested_attributes_for :push_access_levels
|
||||||
|
accepts_nested_attributes_for :merge_access_levels
|
||||||
|
|
||||||
def commit
|
def commit
|
||||||
project.commit(self.name)
|
project.commit(self.name)
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base
|
class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base
|
||||||
|
include ProtectedBranchAccess
|
||||||
|
|
||||||
belongs_to :protected_branch
|
belongs_to :protected_branch
|
||||||
delegate :project, to: :protected_branch
|
delegate :project, to: :protected_branch
|
||||||
|
|
||||||
|
@ -17,8 +19,4 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base
|
||||||
|
|
||||||
project.team.max_member_access(user.id) >= access_level
|
project.team.max_member_access(user.id) >= access_level
|
||||||
end
|
end
|
||||||
|
|
||||||
def humanize
|
|
||||||
self.class.human_access_levels[self.access_level]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
class ProtectedBranch::PushAccessLevel < ActiveRecord::Base
|
class ProtectedBranch::PushAccessLevel < ActiveRecord::Base
|
||||||
|
include ProtectedBranchAccess
|
||||||
|
|
||||||
belongs_to :protected_branch
|
belongs_to :protected_branch
|
||||||
delegate :project, to: :protected_branch
|
delegate :project, to: :protected_branch
|
||||||
|
|
||||||
|
@ -20,8 +22,4 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base
|
||||||
|
|
||||||
project.team.max_member_access(user.id) >= access_level
|
project.team.max_member_access(user.id) >= access_level
|
||||||
end
|
end
|
||||||
|
|
||||||
def humanize
|
|
||||||
self.class.human_access_levels[self.access_level]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -36,6 +36,7 @@ class Service < ActiveRecord::Base
|
||||||
scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) }
|
scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) }
|
||||||
scope :note_hooks, -> { where(note_events: true, active: true) }
|
scope :note_hooks, -> { where(note_events: true, active: true) }
|
||||||
scope :build_hooks, -> { where(build_events: true, active: true) }
|
scope :build_hooks, -> { where(build_events: true, active: true) }
|
||||||
|
scope :pipeline_hooks, -> { where(pipeline_events: true, active: true) }
|
||||||
scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
|
scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
|
||||||
scope :external_issue_trackers, -> { issue_trackers.active.without_defaults }
|
scope :external_issue_trackers, -> { issue_trackers.active.without_defaults }
|
||||||
|
|
||||||
|
@ -79,13 +80,17 @@ class Service < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_data(project, user)
|
def test_data(project, user)
|
||||||
Gitlab::PushDataBuilder.build_sample(project, user)
|
Gitlab::DataBuilder::Push.build_sample(project, user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def event_channel_names
|
def event_channel_names
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def event_names
|
||||||
|
supported_events.map { |event| "#{event}_events" }
|
||||||
|
end
|
||||||
|
|
||||||
def event_field(event)
|
def event_field(event)
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,4 +7,8 @@ class SpamLog < ActiveRecord::Base
|
||||||
user.block
|
user.block
|
||||||
user.destroy
|
user.destroy
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def text
|
||||||
|
[title, description].join("\n")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -809,13 +809,13 @@ class User < ActiveRecord::Base
|
||||||
|
|
||||||
def todos_done_count(force: false)
|
def todos_done_count(force: false)
|
||||||
Rails.cache.fetch(['users', id, 'todos_done_count'], force: force) do
|
Rails.cache.fetch(['users', id, 'todos_done_count'], force: force) do
|
||||||
todos.done.count
|
TodosFinder.new(self, state: :done).execute.count
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def todos_pending_count(force: false)
|
def todos_pending_count(force: false)
|
||||||
Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force) do
|
Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force) do
|
||||||
todos.pending.count
|
TodosFinder.new(self, state: :pending).execute.count
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
9
app/models/user_agent_detail.rb
Normal file
9
app/models/user_agent_detail.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
class UserAgentDetail < ActiveRecord::Base
|
||||||
|
belongs_to :subject, polymorphic: true
|
||||||
|
|
||||||
|
validates :user_agent, :ip_address, :subject_id, :subject_type, presence: true
|
||||||
|
|
||||||
|
def submittable?
|
||||||
|
!submitted?
|
||||||
|
end
|
||||||
|
end
|
79
app/services/akismet_service.rb
Normal file
79
app/services/akismet_service.rb
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
class AkismetService
|
||||||
|
attr_accessor :owner, :text, :options
|
||||||
|
|
||||||
|
def initialize(owner, text, options = {})
|
||||||
|
@owner = owner
|
||||||
|
@text = text
|
||||||
|
@options = options
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_spam?
|
||||||
|
return false unless akismet_enabled?
|
||||||
|
|
||||||
|
params = {
|
||||||
|
type: 'comment',
|
||||||
|
text: text,
|
||||||
|
created_at: DateTime.now,
|
||||||
|
author: owner.name,
|
||||||
|
author_email: owner.email,
|
||||||
|
referrer: options[:referrer],
|
||||||
|
}
|
||||||
|
|
||||||
|
begin
|
||||||
|
is_spam, is_blatant = akismet_client.check(options[:ip_address], options[:user_agent], params)
|
||||||
|
is_spam || is_blatant
|
||||||
|
rescue => e
|
||||||
|
Rails.logger.error("Unable to connect to Akismet: #{e}, skipping check")
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def submit_ham
|
||||||
|
return false unless akismet_enabled?
|
||||||
|
|
||||||
|
params = {
|
||||||
|
type: 'comment',
|
||||||
|
text: text,
|
||||||
|
author: owner.name,
|
||||||
|
author_email: owner.email
|
||||||
|
}
|
||||||
|
|
||||||
|
begin
|
||||||
|
akismet_client.submit_ham(options[:ip_address], options[:user_agent], params)
|
||||||
|
true
|
||||||
|
rescue => e
|
||||||
|
Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!")
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def submit_spam
|
||||||
|
return false unless akismet_enabled?
|
||||||
|
|
||||||
|
params = {
|
||||||
|
type: 'comment',
|
||||||
|
text: text,
|
||||||
|
author: owner.name,
|
||||||
|
author_email: owner.email
|
||||||
|
}
|
||||||
|
|
||||||
|
begin
|
||||||
|
akismet_client.submit_spam(options[:ip_address], options[:user_agent], params)
|
||||||
|
true
|
||||||
|
rescue => e
|
||||||
|
Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!")
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def akismet_client
|
||||||
|
@akismet_client ||= ::Akismet::Client.new(current_application_settings.akismet_api_key,
|
||||||
|
Gitlab.config.gitlab.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
def akismet_enabled?
|
||||||
|
current_application_settings.akismet_enabled
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,13 +0,0 @@
|
||||||
class CreateSpamLogService < BaseService
|
|
||||||
def initialize(project, user, params)
|
|
||||||
super(project, user, params)
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute
|
|
||||||
spam_params = params.merge({ user_id: @current_user.id,
|
|
||||||
project_id: @project.id } )
|
|
||||||
spam_log = SpamLog.new(spam_params)
|
|
||||||
spam_log.save
|
|
||||||
spam_log
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -39,7 +39,12 @@ class DeleteBranchService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_push_data(branch)
|
def build_push_data(branch)
|
||||||
Gitlab::PushDataBuilder
|
Gitlab::DataBuilder::Push.build(
|
||||||
.build(project, current_user, branch.target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", [])
|
project,
|
||||||
|
current_user,
|
||||||
|
branch.target.sha,
|
||||||
|
Gitlab::Git::BLANK_SHA,
|
||||||
|
"#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}",
|
||||||
|
[])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,7 +33,12 @@ class DeleteTagService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_push_data(tag)
|
def build_push_data(tag)
|
||||||
Gitlab::PushDataBuilder
|
Gitlab::DataBuilder::Push.build(
|
||||||
.build(project, current_user, tag.target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", [])
|
project,
|
||||||
|
current_user,
|
||||||
|
tag.target.sha,
|
||||||
|
Gitlab::Git::BLANK_SHA,
|
||||||
|
"#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}",
|
||||||
|
[])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,6 +15,7 @@ module Files
|
||||||
else
|
else
|
||||||
params[:file_content]
|
params[:file_content]
|
||||||
end
|
end
|
||||||
|
@last_commit_sha = params[:last_commit_sha]
|
||||||
|
|
||||||
# Validate parameters
|
# Validate parameters
|
||||||
validate
|
validate
|
||||||
|
|
|
@ -2,11 +2,34 @@ require_relative "base_service"
|
||||||
|
|
||||||
module Files
|
module Files
|
||||||
class UpdateService < Files::BaseService
|
class UpdateService < Files::BaseService
|
||||||
|
class FileChangedError < StandardError; end
|
||||||
|
|
||||||
def commit
|
def commit
|
||||||
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)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def validate
|
||||||
|
super
|
||||||
|
|
||||||
|
if file_has_changed?
|
||||||
|
raise FileChangedError.new("You are attempting to update a file that has changed since you started editing it.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def file_has_changed?
|
||||||
|
return false unless @last_commit_sha && last_commit
|
||||||
|
|
||||||
|
@last_commit_sha != last_commit.sha
|
||||||
|
end
|
||||||
|
|
||||||
|
def last_commit
|
||||||
|
@last_commit ||= Gitlab::Git::Commit.
|
||||||
|
last_for_path(@source_project.repository, @source_branch, @file_path)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -91,12 +91,12 @@ class GitPushService < BaseService
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
name: @project.default_branch,
|
name: @project.default_branch,
|
||||||
push_access_level_attributes: {
|
push_access_levels_attributes: [{
|
||||||
access_level: current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
|
access_level: current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
|
||||||
},
|
}],
|
||||||
merge_access_level_attributes: {
|
merge_access_levels_attributes: [{
|
||||||
access_level: current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_MERGE ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
|
access_level: current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_MERGE ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
|
||||||
}
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
ProtectedBranches::CreateService.new(@project, current_user, params).execute
|
ProtectedBranches::CreateService.new(@project, current_user, params).execute
|
||||||
|
@ -138,13 +138,23 @@ class GitPushService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_push_data
|
def build_push_data
|
||||||
@push_data ||= Gitlab::PushDataBuilder.
|
@push_data ||= Gitlab::DataBuilder::Push.build(
|
||||||
build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], push_commits)
|
@project,
|
||||||
|
current_user,
|
||||||
|
params[:oldrev],
|
||||||
|
params[:newrev],
|
||||||
|
params[:ref],
|
||||||
|
push_commits)
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_push_data_system_hook
|
def build_push_data_system_hook
|
||||||
@push_data_system ||= Gitlab::PushDataBuilder.
|
@push_data_system ||= Gitlab::DataBuilder::Push.build(
|
||||||
build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], [])
|
@project,
|
||||||
|
current_user,
|
||||||
|
params[:oldrev],
|
||||||
|
params[:newrev],
|
||||||
|
params[:ref],
|
||||||
|
[])
|
||||||
end
|
end
|
||||||
|
|
||||||
def push_to_existing_branch?
|
def push_to_existing_branch?
|
||||||
|
|
|
@ -34,12 +34,24 @@ class GitTagPushService < BaseService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Gitlab::PushDataBuilder.
|
Gitlab::DataBuilder::Push.build(
|
||||||
build(project, current_user, params[:oldrev], params[:newrev], params[:ref], commits, message)
|
project,
|
||||||
|
current_user,
|
||||||
|
params[:oldrev],
|
||||||
|
params[:newrev],
|
||||||
|
params[:ref],
|
||||||
|
commits,
|
||||||
|
message)
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_system_push_data
|
def build_system_push_data
|
||||||
Gitlab::PushDataBuilder.
|
Gitlab::DataBuilder::Push.build(
|
||||||
build(project, current_user, params[:oldrev], params[:newrev], params[:ref], [], '')
|
project,
|
||||||
|
current_user,
|
||||||
|
params[:oldrev],
|
||||||
|
params[:newrev],
|
||||||
|
params[:ref],
|
||||||
|
[],
|
||||||
|
'')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
26
app/services/ham_service.rb
Normal file
26
app/services/ham_service.rb
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
class HamService
|
||||||
|
attr_accessor :spam_log
|
||||||
|
|
||||||
|
def initialize(spam_log)
|
||||||
|
@spam_log = spam_log
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_as_ham!
|
||||||
|
if akismet.submit_ham
|
||||||
|
spam_log.update_attribute(:submitted_as_ham, true)
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def akismet
|
||||||
|
@akismet ||= AkismetService.new(
|
||||||
|
spam_log.user,
|
||||||
|
spam_log.text,
|
||||||
|
ip_address: spam_log.source_ip,
|
||||||
|
user_agent: spam_log.user_agent
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,25 +1,30 @@
|
||||||
module Issues
|
module Issues
|
||||||
class CreateService < Issues::BaseService
|
class CreateService < Issues::BaseService
|
||||||
def execute
|
def execute
|
||||||
issue = project.issues.new
|
@request = params.delete(:request)
|
||||||
request = params.delete(:request)
|
@api = params.delete(:api)
|
||||||
api = params.delete(:api)
|
|
||||||
|
|
||||||
issue.spam = spam_check_service.execute(request, api)
|
@issue = project.issues.new
|
||||||
|
@issue.spam = spam_service.check(@api)
|
||||||
|
|
||||||
create(issue)
|
create(@issue)
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_creation(issuable)
|
def handle_creation(issuable)
|
||||||
event_service.open_issue(issuable, current_user)
|
event_service.open_issue(issuable, current_user)
|
||||||
notification_service.new_issue(issuable, current_user)
|
notification_service.new_issue(issuable, current_user)
|
||||||
todo_service.new_issue(issuable, current_user)
|
todo_service.new_issue(issuable, current_user)
|
||||||
|
user_agent_detail_service.create
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def spam_check_service
|
def spam_service
|
||||||
SpamCheckService.new(project, current_user, params)
|
SpamService.new(@issue, @request)
|
||||||
|
end
|
||||||
|
|
||||||
|
def user_agent_detail_service
|
||||||
|
UserAgentDetailService.new(@issue, @request)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,10 +30,21 @@ module MergeRequests
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_branches(changes)
|
def get_branches(changes)
|
||||||
|
return [] if project.empty_repo?
|
||||||
|
return [] unless project.merge_requests_enabled
|
||||||
|
|
||||||
changes_list = Gitlab::ChangesList.new(changes)
|
changes_list = Gitlab::ChangesList.new(changes)
|
||||||
changes_list.map do |change|
|
changes_list.map do |change|
|
||||||
next unless Gitlab::Git.branch_ref?(change[:ref])
|
next unless Gitlab::Git.branch_ref?(change[:ref])
|
||||||
Gitlab::Git.branch_name(change[:ref])
|
|
||||||
|
# Deleted branch
|
||||||
|
next if Gitlab::Git.blank_ref?(change[:newrev])
|
||||||
|
|
||||||
|
# Default branch
|
||||||
|
branch_name = Gitlab::Git.branch_name(change[:ref])
|
||||||
|
next if branch_name == project.default_branch
|
||||||
|
|
||||||
|
branch_name
|
||||||
end.compact
|
end.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ module Notes
|
||||||
end
|
end
|
||||||
|
|
||||||
def hook_data
|
def hook_data
|
||||||
Gitlab::NoteDataBuilder.build(@note, @note.author)
|
Gitlab::DataBuilder::Note.build(@note, @note.author)
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute_note_hooks
|
def execute_note_hooks
|
||||||
|
|
|
@ -5,23 +5,7 @@ module ProtectedBranches
|
||||||
def execute
|
def execute
|
||||||
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project)
|
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project)
|
||||||
|
|
||||||
protected_branch = project.protected_branches.new(params)
|
project.protected_branches.create(params)
|
||||||
|
|
||||||
ProtectedBranch.transaction do
|
|
||||||
protected_branch.save!
|
|
||||||
|
|
||||||
if protected_branch.push_access_level.blank?
|
|
||||||
protected_branch.create_push_access_level!(access_level: Gitlab::Access::MASTER)
|
|
||||||
end
|
|
||||||
|
|
||||||
if protected_branch.merge_access_level.blank?
|
|
||||||
protected_branch.create_merge_access_level!(access_level: Gitlab::Access::MASTER)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
protected_branch
|
|
||||||
rescue ActiveRecord::RecordInvalid
|
|
||||||
protected_branch
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
class SpamCheckService < BaseService
|
|
||||||
include Gitlab::AkismetHelper
|
|
||||||
|
|
||||||
attr_accessor :request, :api
|
|
||||||
|
|
||||||
def execute(request, api)
|
|
||||||
@request, @api = request, api
|
|
||||||
return false unless request || check_for_spam?(project)
|
|
||||||
return false unless is_spam?(request.env, current_user, text)
|
|
||||||
|
|
||||||
create_spam_log
|
|
||||||
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def text
|
|
||||||
[params[:title], params[:description]].reject(&:blank?).join("\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
def spam_log_attrs
|
|
||||||
{
|
|
||||||
user_id: current_user.id,
|
|
||||||
project_id: project.id,
|
|
||||||
title: params[:title],
|
|
||||||
description: params[:description],
|
|
||||||
source_ip: client_ip(request.env),
|
|
||||||
user_agent: user_agent(request.env),
|
|
||||||
noteable_type: 'Issue',
|
|
||||||
via_api: api
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_spam_log
|
|
||||||
CreateSpamLogService.new(project, current_user, spam_log_attrs).execute
|
|
||||||
end
|
|
||||||
end
|
|
78
app/services/spam_service.rb
Normal file
78
app/services/spam_service.rb
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
class SpamService
|
||||||
|
attr_accessor :spammable, :request, :options
|
||||||
|
|
||||||
|
def initialize(spammable, request = nil)
|
||||||
|
@spammable = spammable
|
||||||
|
@request = request
|
||||||
|
@options = {}
|
||||||
|
|
||||||
|
if @request
|
||||||
|
@options[:ip_address] = @request.env['action_dispatch.remote_ip'].to_s
|
||||||
|
@options[:user_agent] = @request.env['HTTP_USER_AGENT']
|
||||||
|
@options[:referrer] = @request.env['HTTP_REFERRER']
|
||||||
|
else
|
||||||
|
@options[:ip_address] = @spammable.ip_address
|
||||||
|
@options[:user_agent] = @spammable.user_agent
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check(api = false)
|
||||||
|
return false unless request && check_for_spam?
|
||||||
|
|
||||||
|
return false unless akismet.is_spam?
|
||||||
|
|
||||||
|
create_spam_log(api)
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_as_spam!
|
||||||
|
return false unless spammable.submittable_as_spam?
|
||||||
|
|
||||||
|
if akismet.submit_spam
|
||||||
|
spammable.user_agent_detail.update_attribute(:submitted, true)
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def akismet
|
||||||
|
@akismet ||= AkismetService.new(
|
||||||
|
spammable_owner,
|
||||||
|
spammable.spammable_text,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def spammable_owner
|
||||||
|
@user ||= User.find(spammable_owner_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def spammable_owner_id
|
||||||
|
@owner_id ||=
|
||||||
|
if spammable.respond_to?(:author_id)
|
||||||
|
spammable.author_id
|
||||||
|
elsif spammable.respond_to?(:creator_id)
|
||||||
|
spammable.creator_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_for_spam?
|
||||||
|
spammable.check_for_spam?
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_spam_log(api)
|
||||||
|
SpamLog.create(
|
||||||
|
{
|
||||||
|
user_id: spammable_owner_id,
|
||||||
|
title: spammable.spam_title,
|
||||||
|
description: spammable.spam_description,
|
||||||
|
source_ip: options[:ip_address],
|
||||||
|
user_agent: options[:user_agent],
|
||||||
|
noteable_type: spammable.class.to_s,
|
||||||
|
via_api: api
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,6 @@
|
||||||
class TestHookService
|
class TestHookService
|
||||||
def execute(hook, current_user)
|
def execute(hook, current_user)
|
||||||
data = Gitlab::PushDataBuilder.build_sample(hook.project, current_user)
|
data = Gitlab::DataBuilder::Push.build_sample(hook.project, current_user)
|
||||||
hook.execute(data, 'push_hooks')
|
hook.execute(data, 'push_hooks')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -144,8 +144,9 @@ class TodoService
|
||||||
def mark_todos_as_done(todos, current_user)
|
def mark_todos_as_done(todos, current_user)
|
||||||
todos = current_user.todos.where(id: todos.map(&:id)) unless todos.respond_to?(:update_all)
|
todos = current_user.todos.where(id: todos.map(&:id)) unless todos.respond_to?(:update_all)
|
||||||
|
|
||||||
todos.update_all(state: :done)
|
marked_todos = todos.update_all(state: :done)
|
||||||
current_user.update_todos_count_cache
|
current_user.update_todos_count_cache
|
||||||
|
marked_todos
|
||||||
end
|
end
|
||||||
|
|
||||||
# When user marks an issue as todo
|
# When user marks an issue as todo
|
||||||
|
|
13
app/services/user_agent_detail_service.rb
Normal file
13
app/services/user_agent_detail_service.rb
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
class UserAgentDetailService
|
||||||
|
attr_accessor :spammable, :request
|
||||||
|
|
||||||
|
def initialize(spammable, request)
|
||||||
|
@spammable, @request = spammable, request
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
return unless request
|
||||||
|
|
||||||
|
spammable.create_user_agent_detail(user_agent: request.env['HTTP_USER_AGENT'], ip_address: request.env['action_dispatch.remote_ip'].to_s)
|
||||||
|
end
|
||||||
|
end
|
|
@ -24,6 +24,11 @@
|
||||||
= link_to 'Remove user', admin_spam_log_path(spam_log, remove_user: true),
|
= link_to 'Remove user', admin_spam_log_path(spam_log, remove_user: true),
|
||||||
data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove"
|
data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove"
|
||||||
%td
|
%td
|
||||||
|
- if spam_log.submitted_as_ham?
|
||||||
|
.btn.btn-xs.disabled
|
||||||
|
Submitted as ham
|
||||||
|
- else
|
||||||
|
= link_to 'Submit as ham', mark_as_ham_admin_spam_log_path(spam_log), method: :post, class: 'btn btn-xs btn-warning'
|
||||||
- if user && !user.blocked?
|
- if user && !user.blocked?
|
||||||
= link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs"
|
= link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs"
|
||||||
- else
|
- else
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
.encoding-selector
|
.encoding-selector
|
||||||
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
|
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
|
||||||
|
|
||||||
.file-content.code
|
.file-editor.code
|
||||||
%pre.js-edit-mode-pane#editor #{params[:content] || local_assigns[:blob_data]}
|
%pre.js-edit-mode-pane#editor #{params[:content] || local_assigns[:blob_data]}
|
||||||
- if local_assigns[:path]
|
- if local_assigns[:path]
|
||||||
.js-edit-mode-pane#preview.hide
|
.js-edit-mode-pane#preview.hide
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
.file-content.image_file
|
.file-content.image_file
|
||||||
- if blob.svg?
|
- if blob.svg?
|
||||||
- # We need to scrub SVG but we cannot do so in the RawController: it would
|
- if blob.size_within_svg_limits?
|
||||||
- # be wrong/strange if RawController modified the data.
|
- # We need to scrub SVG but we cannot do so in the RawController: it would
|
||||||
- blob.load_all_data!(@repository)
|
- # be wrong/strange if RawController modified the data.
|
||||||
- blob = sanitize_svg(blob)
|
- blob.load_all_data!(@repository)
|
||||||
%img{src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"}
|
- blob = sanitize_svg(blob)
|
||||||
|
%img{src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"}
|
||||||
|
- else
|
||||||
|
.nothing-here-block
|
||||||
|
The SVG could not be displayed as it is too large, you can
|
||||||
|
#{link_to('view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank')}
|
||||||
|
instead.
|
||||||
- else
|
- else
|
||||||
%img{src: namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, blob.path))}
|
%img{src: namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, blob.path))}
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
- page_title "Edit", @blob.path, @ref
|
- page_title "Edit", @blob.path, @ref
|
||||||
|
|
||||||
|
- if @conflict
|
||||||
|
.alert.alert-danger
|
||||||
|
Someone edited the file the same time you did. Please check out
|
||||||
|
= link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@target_branch, @file_path)), target: "_blank"
|
||||||
|
and make sure your changes will not unintentionally remove theirs.
|
||||||
|
|
||||||
.file-editor
|
.file-editor
|
||||||
%ul.nav-links.no-bottom.js-edit-mode
|
%ul.nav-links.no-bottom.js-edit-mode
|
||||||
%li.active
|
%li.active
|
||||||
|
@ -13,8 +19,7 @@
|
||||||
= form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form') do
|
= form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form') do
|
||||||
= render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
|
= render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
|
||||||
= render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
|
= render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
|
||||||
|
= hidden_field_tag 'last_commit_sha', @last_commit_sha
|
||||||
= hidden_field_tag 'last_commit', @last_commit
|
|
||||||
= hidden_field_tag 'content', '', id: "file-content"
|
= hidden_field_tag 'content', '', id: "file-content"
|
||||||
= hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
|
= hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
|
||||||
= render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id)
|
= render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
.col-md-8.col-lg-7
|
.col-md-8.col-lg-7
|
||||||
%strong.light-header= hook.url
|
%strong.light-header= hook.url
|
||||||
%div
|
%div
|
||||||
- %w(push_events tag_push_events issues_events note_events merge_requests_events build_events wiki_page_events).each do |trigger|
|
- %w(push_events tag_push_events issues_events note_events merge_requests_events build_events pipeline_events wiki_page_events).each do |trigger|
|
||||||
- if hook.send(trigger)
|
- if hook.send(trigger)
|
||||||
%span.label.label-gray.deploy-project-label= trigger.titleize
|
%span.label.label-gray.deploy-project-label= trigger.titleize
|
||||||
.col-md-4.col-lg-5.text-right-lg.prepend-top-5
|
.col-md-4.col-lg-5.text-right-lg.prepend-top-5
|
||||||
|
|
|
@ -37,14 +37,19 @@
|
||||||
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
|
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
|
||||||
%li
|
%li
|
||||||
= link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
|
= link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
|
||||||
|
- if @issue.submittable_as_spam? && current_user.admin?
|
||||||
|
%li
|
||||||
|
= link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
|
||||||
|
|
||||||
- if can?(current_user, :create_issue, @project)
|
- if can?(current_user, :create_issue, @project)
|
||||||
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
|
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
|
||||||
New issue
|
New issue
|
||||||
- if can?(current_user, :update_issue, @issue)
|
- if can?(current_user, :update_issue, @issue)
|
||||||
= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
|
= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
|
||||||
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
|
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
|
||||||
= link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit' do
|
- if @issue.submittable_as_spam? && current_user.admin?
|
||||||
Edit
|
= link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam'
|
||||||
|
= link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit'
|
||||||
|
|
||||||
|
|
||||||
.issue-details.issuable-details
|
.issue-details.issuable-details
|
||||||
|
|
|
@ -42,3 +42,16 @@
|
||||||
.ci_widget.ci-error{style: "display:none"}
|
.ci_widget.ci-error{style: "display:none"}
|
||||||
= icon("times-circle")
|
= icon("times-circle")
|
||||||
Could not connect to the CI server. Please check your settings and try again.
|
Could not connect to the CI server. Please check your settings and try again.
|
||||||
|
|
||||||
|
- @merge_request.environments.each do |environment|
|
||||||
|
.mr-widget-heading
|
||||||
|
.ci_widget.ci-success
|
||||||
|
= ci_icon_for_status("success")
|
||||||
|
%span.hidden-sm
|
||||||
|
Deployed to
|
||||||
|
= succeed '.' do
|
||||||
|
= link_to environment.name, namespace_project_environment_path(@project.namespace, @project, environment), class: 'environment'
|
||||||
|
- external_url = environment.external_url
|
||||||
|
- if external_url
|
||||||
|
= link_to external_url, target: '_blank' do
|
||||||
|
= icon('external-link', text: "View on #{external_url.gsub(/\A.*?:\/\//, '')}", right: true)
|
||||||
|
|
|
@ -77,7 +77,7 @@
|
||||||
= link_to "#", class: 'btn js-toggle-button import_git' do
|
= link_to "#", class: 'btn js-toggle-button import_git' do
|
||||||
= icon('git', text: 'Repo by URL')
|
= icon('git', text: 'Repo by URL')
|
||||||
%div{ class: 'import_gitlab_project' }
|
%div{ class: 'import_gitlab_project' }
|
||||||
- if gitlab_project_import_enabled?
|
- if gitlab_project_import_enabled? && current_user.is_admin?
|
||||||
= link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
|
= link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
|
||||||
= icon('gitlab', text: 'GitLab export')
|
= icon('gitlab', text: 'GitLab export')
|
||||||
|
|
||||||
|
|
27
app/views/projects/pipelines_settings/_badge.html.haml
Normal file
27
app/views/projects/pipelines_settings/_badge.html.haml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
.row{ class: badge.title.gsub(' ', '-') }
|
||||||
|
.col-lg-3.profile-settings-sidebar
|
||||||
|
%h4.prepend-top-0
|
||||||
|
= badge.title.capitalize
|
||||||
|
.col-lg-9
|
||||||
|
.prepend-top-10
|
||||||
|
.panel.panel-default
|
||||||
|
.panel-heading
|
||||||
|
%b
|
||||||
|
= badge.title.capitalize
|
||||||
|
·
|
||||||
|
= badge.to_html
|
||||||
|
.pull-right
|
||||||
|
= render 'shared/ref_switcher', destination: 'badges', align_right: true
|
||||||
|
.panel-body
|
||||||
|
.row
|
||||||
|
.col-md-2.text-center
|
||||||
|
Markdown
|
||||||
|
.col-md-10.code.js-syntax-highlight
|
||||||
|
= highlight('.md', badge.to_markdown)
|
||||||
|
.row
|
||||||
|
%hr
|
||||||
|
.row
|
||||||
|
.col-md-2.text-center
|
||||||
|
HTML
|
||||||
|
.col-md-10.code.js-syntax-highlight
|
||||||
|
= highlight('.html', badge.to_html)
|
|
@ -77,27 +77,4 @@
|
||||||
%hr
|
%hr
|
||||||
|
|
||||||
.row.prepend-top-default
|
.row.prepend-top-default
|
||||||
.col-lg-3.profile-settings-sidebar
|
= render partial: 'badge', collection: @badges
|
||||||
%h4.prepend-top-0
|
|
||||||
Builds Badge
|
|
||||||
.col-lg-9
|
|
||||||
.prepend-top-10
|
|
||||||
.panel.panel-default
|
|
||||||
.panel-heading
|
|
||||||
%b Builds badge ·
|
|
||||||
= @build_badge.to_html
|
|
||||||
.pull-right
|
|
||||||
= render 'shared/ref_switcher', destination: 'badges', align_right: true
|
|
||||||
.panel-body
|
|
||||||
.row
|
|
||||||
.col-md-2.text-center
|
|
||||||
Markdown
|
|
||||||
.col-md-10.code.js-syntax-highlight
|
|
||||||
= highlight('.md', @build_badge.to_markdown)
|
|
||||||
.row
|
|
||||||
%hr
|
|
||||||
.row
|
|
||||||
.col-md-2.text-center
|
|
||||||
HTML
|
|
||||||
.col-md-10.code.js-syntax-highlight
|
|
||||||
= highlight('.html', @build_badge.to_html)
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
Protect a branch
|
Protect a branch
|
||||||
.panel-body
|
.panel-body
|
||||||
.form-horizontal
|
.form-horizontal
|
||||||
|
= form_errors(@protected_branch)
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :name, class: 'col-md-2 text-right' do
|
= f.label :name, class: 'col-md-2 text-right' do
|
||||||
Branch:
|
Branch:
|
||||||
|
@ -18,19 +19,19 @@
|
||||||
%code production/*
|
%code production/*
|
||||||
are supported
|
are supported
|
||||||
.form-group
|
.form-group
|
||||||
%label.col-md-2.text-right{ for: 'merge_access_level_attributes' }
|
%label.col-md-2.text-right{ for: 'merge_access_levels_attributes' }
|
||||||
Allowed to merge:
|
Allowed to merge:
|
||||||
.col-md-10
|
.col-md-10
|
||||||
= dropdown_tag('Select',
|
= dropdown_tag('Select',
|
||||||
options: { toggle_class: 'js-allowed-to-merge wide',
|
options: { toggle_class: 'js-allowed-to-merge wide',
|
||||||
data: { field_name: 'protected_branch[merge_access_level_attributes][access_level]', input_id: 'merge_access_level_attributes' }})
|
data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }})
|
||||||
.form-group
|
.form-group
|
||||||
%label.col-md-2.text-right{ for: 'push_access_level_attributes' }
|
%label.col-md-2.text-right{ for: 'push_access_levels_attributes' }
|
||||||
Allowed to push:
|
Allowed to push:
|
||||||
.col-md-10
|
.col-md-10
|
||||||
= dropdown_tag('Select',
|
= dropdown_tag('Select',
|
||||||
options: { toggle_class: 'js-allowed-to-push wide',
|
options: { toggle_class: 'js-allowed-to-push wide',
|
||||||
data: { field_name: 'protected_branch[push_access_level_attributes][access_level]', input_id: 'push_access_level_attributes' }})
|
data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }})
|
||||||
|
|
||||||
.panel-footer
|
.panel-footer
|
||||||
= f.submit 'Protect', class: 'btn-create btn', disabled: true
|
= f.submit 'Protect', class: 'btn-create btn', disabled: true
|
||||||
|
|
|
@ -13,16 +13,9 @@
|
||||||
= time_ago_with_tooltip(commit.committed_date)
|
= time_ago_with_tooltip(commit.committed_date)
|
||||||
- else
|
- else
|
||||||
(branch was removed from repository)
|
(branch was removed from repository)
|
||||||
%td
|
|
||||||
= hidden_field_tag "allowed_to_merge_#{protected_branch.id}", protected_branch.merge_access_level.access_level
|
= render partial: 'update_protected_branch', locals: { protected_branch: protected_branch }
|
||||||
= dropdown_tag( (protected_branch.merge_access_level.humanize || 'Select') ,
|
|
||||||
options: { toggle_class: 'js-allowed-to-merge', dropdown_class: 'dropdown-menu-selectable js-allowed-to-merge-container',
|
|
||||||
data: { field_name: "allowed_to_merge_#{protected_branch.id}" }})
|
|
||||||
%td
|
|
||||||
= hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_level.access_level
|
|
||||||
= dropdown_tag( (protected_branch.push_access_level.humanize || 'Select') ,
|
|
||||||
options: { toggle_class: 'js-allowed-to-push', dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container',
|
|
||||||
data: { field_name: "allowed_to_push_#{protected_branch.id}" }})
|
|
||||||
- if can_admin_project
|
- if can_admin_project
|
||||||
%td
|
%td
|
||||||
= link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning'
|
= link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning'
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
%td
|
||||||
|
= hidden_field_tag "allowed_to_merge_#{protected_branch.id}", protected_branch.merge_access_levels.first.access_level
|
||||||
|
= dropdown_tag( (protected_branch.merge_access_levels.first.humanize || 'Select') ,
|
||||||
|
options: { toggle_class: 'js-allowed-to-merge', dropdown_class: 'dropdown-menu-selectable js-allowed-to-merge-container',
|
||||||
|
data: { field_name: "allowed_to_merge_#{protected_branch.id}", access_level_id: protected_branch.merge_access_levels.first.id }})
|
||||||
|
%td
|
||||||
|
= hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_levels.first.access_level
|
||||||
|
= dropdown_tag( (protected_branch.push_access_levels.first.humanize || 'Select') ,
|
||||||
|
options: { toggle_class: 'js-allowed-to-push', dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container',
|
||||||
|
data: { field_name: "allowed_to_push_#{protected_branch.id}", access_level_id: protected_branch.push_access_levels.first.id }})
|
|
@ -45,7 +45,7 @@
|
||||||
.filter-item.inline
|
.filter-item.inline
|
||||||
= dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update', filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true } })
|
= dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update', filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true } })
|
||||||
.filter-item.inline.labels-filter
|
.filter-item.inline.labels-filter
|
||||||
= render "shared/issuable/label_dropdown", classes: ['js-filter-bulk-update', 'js-multiselect'], show_create: false, show_footer: false, extra_options: false, filter_submit: false, show_footer: false, data_options: { persist_when_hide: "true", field_name: "update[label_ids][]", show_no: false, show_any: false, use_id: true }
|
= render "shared/issuable/label_dropdown", classes: ['js-filter-bulk-update', 'js-multiselect'], show_create: false, show_footer: false, extra_options: false, filter_submit: false, data_options: { persist_when_hide: "true", field_name: "update[label_ids][]", show_no: false, show_any: false, use_id: true }
|
||||||
.filter-item.inline
|
.filter-item.inline
|
||||||
= dropdown_tag("Subscription", options: { toggle_class: "js-subscription-event", title: "Change subscription", dropdown_class: "dropdown-menu-selectable", data: { field_name: "update[subscription_event]" } } ) do
|
= dropdown_tag("Subscription", options: { toggle_class: "js-subscription-event", title: "Change subscription", dropdown_class: "dropdown-menu-selectable", data: { field_name: "update[subscription_event]" } } ) do
|
||||||
%ul
|
%ul
|
||||||
|
|
|
@ -2,7 +2,22 @@
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :title, class: 'control-label'
|
= f.label :title, class: 'control-label'
|
||||||
.col-sm-10
|
|
||||||
|
- issuable_template_names = issuable_templates(issuable)
|
||||||
|
|
||||||
|
- if issuable_template_names.any?
|
||||||
|
.col-sm-3.col-lg-2
|
||||||
|
.js-issuable-selector-wrap{ data: { issuable_type: issuable.class.to_s.underscore.downcase } }
|
||||||
|
- title = selected_template(issuable) || "Choose a template"
|
||||||
|
|
||||||
|
= dropdown_tag(title, options: { toggle_class: 'js-issuable-selector',
|
||||||
|
title: title, filter: true, placeholder: 'Filter', footer_content: true,
|
||||||
|
data: { data: issuable_template_names, field_name: 'issuable_template', selected: selected_template(issuable), project_path: @project.path, namespace_path: @project.namespace.path } } ) do
|
||||||
|
%ul.dropdown-footer-list
|
||||||
|
%li
|
||||||
|
%a.reset-template
|
||||||
|
Reset template
|
||||||
|
%div{ class: issuable_template_names.any? ? 'col-sm-7 col-lg-8' : 'col-sm-10' }
|
||||||
= f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off',
|
= f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off',
|
||||||
class: 'form-control pad', required: true
|
class: 'form-control pad', required: true
|
||||||
|
|
||||||
|
@ -23,6 +38,13 @@
|
||||||
to prevent a
|
to prevent a
|
||||||
%strong Work In Progress
|
%strong Work In Progress
|
||||||
merge request from being merged before it's ready.
|
merge request from being merged before it's ready.
|
||||||
|
|
||||||
|
- if can_add_template?(issuable)
|
||||||
|
%p.help-block
|
||||||
|
Add
|
||||||
|
= link_to "issuable templates", help_page_path('workflow/description_templates')
|
||||||
|
to help your contributors communicate effectively!
|
||||||
|
|
||||||
.form-group.detail-page-description
|
.form-group.detail-page-description
|
||||||
= f.label :description, 'Description', class: 'control-label'
|
= f.label :description, 'Description', class: 'control-label'
|
||||||
.col-sm-10
|
.col-sm-10
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
%li.project-row{ class: css_class }
|
%li.project-row{ class: css_class }
|
||||||
= cache(cache_key) do
|
= cache(cache_key) do
|
||||||
.controls
|
.controls
|
||||||
|
- if project.archived
|
||||||
|
%span.label.label-warning archived
|
||||||
- if project.commit.try(:status)
|
- if project.commit.try(:status)
|
||||||
%span
|
%span
|
||||||
= render_commit_status(project.commit)
|
= render_commit_status(project.commit)
|
||||||
|
|
|
@ -29,49 +29,56 @@
|
||||||
= f.label :push_events, class: 'list-label' do
|
= f.label :push_events, class: 'list-label' do
|
||||||
%strong Push events
|
%strong Push events
|
||||||
%p.light
|
%p.light
|
||||||
This url will be triggered by a push to the repository
|
This URL will be triggered by a push to the repository
|
||||||
%li
|
%li
|
||||||
= f.check_box :tag_push_events, class: 'pull-left'
|
= f.check_box :tag_push_events, class: 'pull-left'
|
||||||
.prepend-left-20
|
.prepend-left-20
|
||||||
= f.label :tag_push_events, class: 'list-label' do
|
= f.label :tag_push_events, class: 'list-label' do
|
||||||
%strong Tag push events
|
%strong Tag push events
|
||||||
%p.light
|
%p.light
|
||||||
This url will be triggered when a new tag is pushed to the repository
|
This URL will be triggered when a new tag is pushed to the repository
|
||||||
%li
|
%li
|
||||||
= f.check_box :note_events, class: 'pull-left'
|
= f.check_box :note_events, class: 'pull-left'
|
||||||
.prepend-left-20
|
.prepend-left-20
|
||||||
= f.label :note_events, class: 'list-label' do
|
= f.label :note_events, class: 'list-label' do
|
||||||
%strong Comments
|
%strong Comments
|
||||||
%p.light
|
%p.light
|
||||||
This url will be triggered when someone adds a comment
|
This URL will be triggered when someone adds a comment
|
||||||
%li
|
%li
|
||||||
= f.check_box :issues_events, class: 'pull-left'
|
= f.check_box :issues_events, class: 'pull-left'
|
||||||
.prepend-left-20
|
.prepend-left-20
|
||||||
= f.label :issues_events, class: 'list-label' do
|
= f.label :issues_events, class: 'list-label' do
|
||||||
%strong Issues events
|
%strong Issues events
|
||||||
%p.light
|
%p.light
|
||||||
This url will be triggered when an issue is created/updated/merged
|
This URL will be triggered when an issue is created/updated/merged
|
||||||
%li
|
%li
|
||||||
= f.check_box :merge_requests_events, class: 'pull-left'
|
= f.check_box :merge_requests_events, class: 'pull-left'
|
||||||
.prepend-left-20
|
.prepend-left-20
|
||||||
= f.label :merge_requests_events, class: 'list-label' do
|
= f.label :merge_requests_events, class: 'list-label' do
|
||||||
%strong Merge Request events
|
%strong Merge Request events
|
||||||
%p.light
|
%p.light
|
||||||
This url will be triggered when a merge request is created/updated/merged
|
This URL will be triggered when a merge request is created/updated/merged
|
||||||
%li
|
%li
|
||||||
= f.check_box :build_events, class: 'pull-left'
|
= f.check_box :build_events, class: 'pull-left'
|
||||||
.prepend-left-20
|
.prepend-left-20
|
||||||
= f.label :build_events, class: 'list-label' do
|
= f.label :build_events, class: 'list-label' do
|
||||||
%strong Build events
|
%strong Build events
|
||||||
%p.light
|
%p.light
|
||||||
This url will be triggered when the build status changes
|
This URL will be triggered when the build status changes
|
||||||
|
%li
|
||||||
|
= f.check_box :pipeline_events, class: 'pull-left'
|
||||||
|
.prepend-left-20
|
||||||
|
= f.label :pipeline_events, class: 'list-label' do
|
||||||
|
%strong Pipeline events
|
||||||
|
%p.light
|
||||||
|
This URL will be triggered when the pipeline status changes
|
||||||
%li
|
%li
|
||||||
= f.check_box :wiki_page_events, class: 'pull-left'
|
= f.check_box :wiki_page_events, class: 'pull-left'
|
||||||
.prepend-left-20
|
.prepend-left-20
|
||||||
= f.label :wiki_page_events, class: 'list-label' do
|
= f.label :wiki_page_events, class: 'list-label' do
|
||||||
%strong Wiki Page events
|
%strong Wiki Page events
|
||||||
%p.light
|
%p.light
|
||||||
This url will be triggered when a wiki page is created/updated
|
This URL will be triggered when a wiki page is created/updated
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :enable_ssl_verification, "SSL verification", class: 'label-light checkbox'
|
= f.label :enable_ssl_verification, "SSL verification", class: 'label-light checkbox'
|
||||||
.checkbox
|
.checkbox
|
||||||
|
|
|
@ -252,7 +252,11 @@ Rails.application.routes.draw do
|
||||||
resource :impersonation, only: :destroy
|
resource :impersonation, only: :destroy
|
||||||
|
|
||||||
resources :abuse_reports, only: [:index, :destroy]
|
resources :abuse_reports, only: [:index, :destroy]
|
||||||
resources :spam_logs, only: [:index, :destroy]
|
resources :spam_logs, only: [:index, :destroy] do
|
||||||
|
member do
|
||||||
|
post :mark_as_ham
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
resources :applications
|
resources :applications
|
||||||
|
|
||||||
|
@ -524,6 +528,11 @@ Rails.application.routes.draw do
|
||||||
put '/update/*id', to: 'blob#update', constraints: { id: /.+/ }, as: 'update_blob'
|
put '/update/*id', to: 'blob#update', constraints: { id: /.+/ }, as: 'update_blob'
|
||||||
post '/preview/*id', to: 'blob#preview', constraints: { id: /.+/ }, as: 'preview_blob'
|
post '/preview/*id', to: 'blob#preview', constraints: { id: /.+/ }, as: 'preview_blob'
|
||||||
|
|
||||||
|
#
|
||||||
|
# Templates
|
||||||
|
#
|
||||||
|
get '/templates/:template_type/:key' => 'templates#show', as: :template
|
||||||
|
|
||||||
scope do
|
scope do
|
||||||
get(
|
get(
|
||||||
'/blob/*id/diff',
|
'/blob/*id/diff',
|
||||||
|
@ -813,6 +822,7 @@ Rails.application.routes.draw do
|
||||||
member do
|
member do
|
||||||
post :toggle_subscription
|
post :toggle_subscription
|
||||||
post :toggle_award_emoji
|
post :toggle_award_emoji
|
||||||
|
post :mark_as_spam
|
||||||
get :referenced_merge_requests
|
get :referenced_merge_requests
|
||||||
get :related_branches
|
get :related_branches
|
||||||
get :can_create_branch
|
get :can_create_branch
|
||||||
|
@ -869,7 +879,10 @@ Rails.application.routes.draw do
|
||||||
resources :badges, only: [:index] do
|
resources :badges, only: [:index] do
|
||||||
collection do
|
collection do
|
||||||
scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do
|
scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do
|
||||||
get :build, constraints: { format: /svg/ }
|
constraints format: /svg/ do
|
||||||
|
get :build
|
||||||
|
get :coverage
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,21 @@
|
||||||
class Gitlab::Seeder::Builds
|
class Gitlab::Seeder::Builds
|
||||||
STAGES = %w[build notify_build test notify_test deploy notify_deploy]
|
STAGES = %w[build notify_build test notify_test deploy notify_deploy]
|
||||||
|
BUILDS = [
|
||||||
|
{ name: 'build:linux', stage: 'build', status: :success },
|
||||||
|
{ name: 'build:osx', stage: 'build', status: :success },
|
||||||
|
{ name: 'slack post build', stage: 'notify_build', status: :success },
|
||||||
|
{ name: 'rspec:linux', stage: 'test', status: :success },
|
||||||
|
{ name: 'rspec:windows', stage: 'test', status: :success },
|
||||||
|
{ name: 'rspec:windows', stage: 'test', status: :success },
|
||||||
|
{ name: 'rspec:osx', stage: 'test', status_event: :success },
|
||||||
|
{ name: 'spinach:linux', stage: 'test', status: :pending },
|
||||||
|
{ name: 'spinach:osx', stage: 'test', status: :canceled },
|
||||||
|
{ name: 'cucumber:linux', stage: 'test', status: :running },
|
||||||
|
{ name: 'cucumber:osx', stage: 'test', status: :failed },
|
||||||
|
{ name: 'slack post test', stage: 'notify_test', status: :success },
|
||||||
|
{ name: 'staging', stage: 'deploy', environment: 'staging', status: :success },
|
||||||
|
{ name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :success },
|
||||||
|
]
|
||||||
|
|
||||||
def initialize(project)
|
def initialize(project)
|
||||||
@project = project
|
@project = project
|
||||||
|
@ -8,25 +24,7 @@ class Gitlab::Seeder::Builds
|
||||||
def seed!
|
def seed!
|
||||||
pipelines.each do |pipeline|
|
pipelines.each do |pipeline|
|
||||||
begin
|
begin
|
||||||
build_create!(pipeline, name: 'build:linux', stage: 'build', status_event: :success)
|
BUILDS.each { |opts| build_create!(pipeline, opts) }
|
||||||
build_create!(pipeline, name: 'build:osx', stage: 'build', status_event: :success)
|
|
||||||
|
|
||||||
build_create!(pipeline, name: 'slack post build', stage: 'notify_build', status_event: :success)
|
|
||||||
|
|
||||||
build_create!(pipeline, name: 'rspec:linux', stage: 'test', status_event: :success)
|
|
||||||
build_create!(pipeline, name: 'rspec:windows', stage: 'test', status_event: :success)
|
|
||||||
build_create!(pipeline, name: 'rspec:windows', stage: 'test', status_event: :success)
|
|
||||||
build_create!(pipeline, name: 'rspec:osx', stage: 'test', status_event: :success)
|
|
||||||
build_create!(pipeline, name: 'spinach:linux', stage: 'test', status: :pending)
|
|
||||||
build_create!(pipeline, name: 'spinach:osx', stage: 'test', status_event: :cancel)
|
|
||||||
build_create!(pipeline, name: 'cucumber:linux', stage: 'test', status_event: :run)
|
|
||||||
build_create!(pipeline, name: 'cucumber:osx', stage: 'test', status_event: :drop)
|
|
||||||
|
|
||||||
build_create!(pipeline, name: 'slack post test', stage: 'notify_test', status_event: :success)
|
|
||||||
|
|
||||||
build_create!(pipeline, name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success)
|
|
||||||
build_create!(pipeline, name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :success)
|
|
||||||
|
|
||||||
commit_status_create!(pipeline, name: 'jenkins', status: :success)
|
commit_status_create!(pipeline, name: 'jenkins', status: :success)
|
||||||
|
|
||||||
print '.'
|
print '.'
|
||||||
|
@ -48,22 +46,23 @@ class Gitlab::Seeder::Builds
|
||||||
|
|
||||||
def build_create!(pipeline, opts = {})
|
def build_create!(pipeline, opts = {})
|
||||||
attributes = build_attributes_for(pipeline, opts)
|
attributes = build_attributes_for(pipeline, opts)
|
||||||
build = Ci::Build.create!(attributes)
|
|
||||||
|
|
||||||
if opts[:name].start_with?('build')
|
Ci::Build.create!(attributes) do |build|
|
||||||
artifacts_cache_file(artifacts_archive_path) do |file|
|
if opts[:name].start_with?('build')
|
||||||
build.artifacts_file = file
|
artifacts_cache_file(artifacts_archive_path) do |file|
|
||||||
|
build.artifacts_file = file
|
||||||
|
end
|
||||||
|
|
||||||
|
artifacts_cache_file(artifacts_metadata_path) do |file|
|
||||||
|
build.artifacts_metadata = file
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
artifacts_cache_file(artifacts_metadata_path) do |file|
|
if %w(running success failed).include?(build.status)
|
||||||
build.artifacts_metadata = file
|
# We need to set build trace after saving a build (id required)
|
||||||
|
build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if %w(running success failed).include?(build.status)
|
|
||||||
# We need to set build trace after saving a build (id required)
|
|
||||||
build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def commit_status_create!(pipeline, opts = {})
|
def commit_status_create!(pipeline, opts = {})
|
||||||
|
|
18
db/migrate/20160727163552_create_user_agent_details.rb
Normal file
18
db/migrate/20160727163552_create_user_agent_details.rb
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
class CreateUserAgentDetails < ActiveRecord::Migration
|
||||||
|
include Gitlab::Database::MigrationHelpers
|
||||||
|
|
||||||
|
# Set this constant to true if this migration requires downtime.
|
||||||
|
DOWNTIME = false
|
||||||
|
|
||||||
|
def change
|
||||||
|
create_table :user_agent_details do |t|
|
||||||
|
t.string :user_agent, null: false
|
||||||
|
t.string :ip_address, null: false
|
||||||
|
t.integer :subject_id, null: false
|
||||||
|
t.string :subject_type, null: false
|
||||||
|
t.boolean :submitted, default: false, null: false
|
||||||
|
|
||||||
|
t.timestamps null: false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,16 @@
|
||||||
|
class AddPipelineEventsToWebHooks < ActiveRecord::Migration
|
||||||
|
include Gitlab::Database::MigrationHelpers
|
||||||
|
|
||||||
|
DOWNTIME = false
|
||||||
|
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_column_with_default(:web_hooks, :pipeline_events, :boolean,
|
||||||
|
default: false, allow_null: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_column(:web_hooks, :pipeline_events)
|
||||||
|
end
|
||||||
|
end
|
16
db/migrate/20160728103734_add_pipeline_events_to_services.rb
Normal file
16
db/migrate/20160728103734_add_pipeline_events_to_services.rb
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
class AddPipelineEventsToServices < ActiveRecord::Migration
|
||||||
|
include Gitlab::Database::MigrationHelpers
|
||||||
|
|
||||||
|
DOWNTIME = false
|
||||||
|
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_column_with_default(:services, :pipeline_events, :boolean,
|
||||||
|
default: false, allow_null: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_column(:services, :pipeline_events)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,29 @@
|
||||||
|
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
|
||||||
|
# for more information on how to write migrations for GitLab.
|
||||||
|
|
||||||
|
class RemoveProjectIdFromSpamLogs < ActiveRecord::Migration
|
||||||
|
include Gitlab::Database::MigrationHelpers
|
||||||
|
|
||||||
|
# Set this constant to true if this migration requires downtime.
|
||||||
|
DOWNTIME = true
|
||||||
|
|
||||||
|
# When a migration requires downtime you **must** uncomment the following
|
||||||
|
# constant and define a short and easy to understand explanation as to why the
|
||||||
|
# migration requires downtime.
|
||||||
|
DOWNTIME_REASON = 'Removing a column that contains data that is not used anywhere.'
|
||||||
|
|
||||||
|
# When using the methods "add_concurrent_index" or "add_column_with_default"
|
||||||
|
# you must disable the use of transactions as these methods can not run in an
|
||||||
|
# existing transaction. When using "add_concurrent_index" make sure that this
|
||||||
|
# method is the _only_ method called in the migration, any other changes
|
||||||
|
# should go in a separate migration. This ensures that upon failure _only_ the
|
||||||
|
# index creation fails and can be retried or reverted easily.
|
||||||
|
#
|
||||||
|
# To disable transactions uncomment the following line and remove these
|
||||||
|
# comments:
|
||||||
|
# disable_ddl_transaction!
|
||||||
|
|
||||||
|
def change
|
||||||
|
remove_column :spam_logs, :project_id, :integer
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,20 @@
|
||||||
|
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
|
||||||
|
# for more information on how to write migrations for GitLab.
|
||||||
|
|
||||||
|
class AddSubmittedAsHamToSpamLogs < ActiveRecord::Migration
|
||||||
|
include Gitlab::Database::MigrationHelpers
|
||||||
|
|
||||||
|
# Set this constant to true if this migration requires downtime.
|
||||||
|
DOWNTIME = false
|
||||||
|
|
||||||
|
# When a migration requires downtime you **must** uncomment the following
|
||||||
|
# constant and define a short and easy to understand explanation as to why the
|
||||||
|
# migration requires downtime.
|
||||||
|
# DOWNTIME_REASON = ''
|
||||||
|
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def change
|
||||||
|
add_column_with_default :spam_logs, :submitted_as_ham, :boolean, default: false
|
||||||
|
end
|
||||||
|
end
|
16
db/schema.rb
16
db/schema.rb
|
@ -589,12 +589,12 @@ ActiveRecord::Schema.define(version: 20160810142633) do
|
||||||
t.datetime "locked_at"
|
t.datetime "locked_at"
|
||||||
t.integer "updated_by_id"
|
t.integer "updated_by_id"
|
||||||
t.string "merge_error"
|
t.string "merge_error"
|
||||||
t.text "merge_params"
|
|
||||||
t.boolean "merge_when_build_succeeds", default: false, null: false
|
t.boolean "merge_when_build_succeeds", default: false, null: false
|
||||||
t.integer "merge_user_id"
|
t.integer "merge_user_id"
|
||||||
t.string "merge_commit_sha"
|
t.string "merge_commit_sha"
|
||||||
t.datetime "deleted_at"
|
t.datetime "deleted_at"
|
||||||
t.string "in_progress_merge_commit_sha"
|
t.string "in_progress_merge_commit_sha"
|
||||||
|
t.text "merge_params"
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
|
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
|
||||||
|
@ -897,6 +897,7 @@ ActiveRecord::Schema.define(version: 20160810142633) do
|
||||||
t.string "category", default: "common", null: false
|
t.string "category", default: "common", null: false
|
||||||
t.boolean "default", default: false
|
t.boolean "default", default: false
|
||||||
t.boolean "wiki_page_events", default: true
|
t.boolean "wiki_page_events", default: true
|
||||||
|
t.boolean "pipeline_events", default: false, null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
|
add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
|
||||||
|
@ -926,12 +927,12 @@ ActiveRecord::Schema.define(version: 20160810142633) do
|
||||||
t.string "source_ip"
|
t.string "source_ip"
|
||||||
t.string "user_agent"
|
t.string "user_agent"
|
||||||
t.boolean "via_api"
|
t.boolean "via_api"
|
||||||
t.integer "project_id"
|
|
||||||
t.string "noteable_type"
|
t.string "noteable_type"
|
||||||
t.string "title"
|
t.string "title"
|
||||||
t.text "description"
|
t.text "description"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
t.boolean "submitted_as_ham", default: false, null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "subscriptions", force: :cascade do |t|
|
create_table "subscriptions", force: :cascade do |t|
|
||||||
|
@ -999,6 +1000,16 @@ ActiveRecord::Schema.define(version: 20160810142633) do
|
||||||
add_index "u2f_registrations", ["key_handle"], name: "index_u2f_registrations_on_key_handle", using: :btree
|
add_index "u2f_registrations", ["key_handle"], name: "index_u2f_registrations_on_key_handle", using: :btree
|
||||||
add_index "u2f_registrations", ["user_id"], name: "index_u2f_registrations_on_user_id", using: :btree
|
add_index "u2f_registrations", ["user_id"], name: "index_u2f_registrations_on_user_id", using: :btree
|
||||||
|
|
||||||
|
create_table "user_agent_details", force: :cascade do |t|
|
||||||
|
t.string "user_agent", null: false
|
||||||
|
t.string "ip_address", null: false
|
||||||
|
t.integer "subject_id", null: false
|
||||||
|
t.string "subject_type", null: false
|
||||||
|
t.boolean "submitted", default: false, null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
end
|
||||||
|
|
||||||
create_table "users", force: :cascade do |t|
|
create_table "users", force: :cascade do |t|
|
||||||
t.string "email", default: "", null: false
|
t.string "email", default: "", null: false
|
||||||
t.string "encrypted_password", default: "", null: false
|
t.string "encrypted_password", default: "", null: false
|
||||||
|
@ -1100,6 +1111,7 @@ ActiveRecord::Schema.define(version: 20160810142633) do
|
||||||
t.boolean "build_events", default: false, null: false
|
t.boolean "build_events", default: false, null: false
|
||||||
t.boolean "wiki_page_events", default: false, null: false
|
t.boolean "wiki_page_events", default: false, null: false
|
||||||
t.string "token"
|
t.string "token"
|
||||||
|
t.boolean "pipeline_events", default: false, null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
|
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
|
||||||
|
|
|
@ -355,7 +355,7 @@ PUT /projects/:id/services/gemnasium
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
|
|
||||||
- `api_key` (**required**) - Your personal API KEY on gemnasium.com
|
- `api_key` (**required**) - Your personal API KEY on gemnasium.com
|
||||||
- `token` (**required**) - The project's slug on gemnasium.com
|
- `token` (**required**) - The project's slug on gemnasium.com
|
||||||
|
|
||||||
### Delete Gemnasium service
|
### Delete Gemnasium service
|
||||||
|
@ -503,6 +503,7 @@ PUT /projects/:id/services/pivotaltracker
|
||||||
Parameters:
|
Parameters:
|
||||||
|
|
||||||
- `token` (**required**)
|
- `token` (**required**)
|
||||||
|
- `restrict_to_branch` (optional) - Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.
|
||||||
|
|
||||||
### Delete PivotalTracker service
|
### Delete PivotalTracker service
|
||||||
|
|
||||||
|
@ -661,4 +662,3 @@ Get JetBrains TeamCity CI service settings for a project.
|
||||||
```
|
```
|
||||||
GET /projects/:id/services/teamcity
|
GET /projects/:id/services/teamcity
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue