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_BUNDLE_INSTALL: "true"
|
||||
GIT_DEPTH: "20"
|
||||
PHANTOMJS_VERSION: "2.1.1"
|
||||
|
||||
before_script:
|
||||
- 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.
|
||||
|
||||
v 8.11.0 (unreleased)
|
||||
- Add test coverage report badge. !5708
|
||||
- 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 rename `add_users_into_project` and `projects_ids`. !20512 (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
|
||||
- Improve performance of AutolinkFilter#text_parse by using XPath
|
||||
- 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)
|
||||
- Environments have an url to link to
|
||||
- Various redundant database indexes have been removed
|
||||
- Update `timeago` plugin to use multiple string/locale settings
|
||||
- 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
|
||||
- Show deployment status on merge requests with external URLs
|
||||
- Clean up unused routes (Josef Strzibny)
|
||||
- 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)
|
||||
|
@ -55,6 +60,7 @@ v 8.11.0 (unreleased)
|
|||
- 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
|
||||
- Nokogiri's various parsing methods are now instrumented
|
||||
- Add archived badge to project list !5798
|
||||
- Add simple identifier to public SSH keys (muteor)
|
||||
- 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
|
||||
|
@ -69,6 +75,7 @@ v 8.11.0 (unreleased)
|
|||
- 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)
|
||||
- Load project invited groups and members eagerly in `ProjectTeam#fetch_members`
|
||||
- Add pipeline events hook
|
||||
- Bump gitlab_git to speedup DiffCollection iterations
|
||||
- Rewrite description of a blocked user in admin settings. (Elias Werberich)
|
||||
- Make branches sortable without push permission !5462 (winniehell)
|
||||
|
@ -78,6 +85,7 @@ v 8.11.0 (unreleased)
|
|||
- Make "New issue" button in Issue page less obtrusive !5457 (winniehell)
|
||||
- Gitlab::Metrics.current_transaction needs to be public for RailsQueueDuration
|
||||
- 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)
|
||||
- Allow branch names ending with .json for graph and network page !5579 (winniehell)
|
||||
- Add the `sprockets-es6` gem
|
||||
|
@ -108,6 +116,12 @@ v 8.11.0 (unreleased)
|
|||
- Sort folders with submodules in Files view !5521
|
||||
- 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)
|
||||
- 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
|
||||
- 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)
|
||||
- Instrument Rinku usage
|
||||
- Be explicit to define merge request discussion variables
|
||||
- Use cache for todos counter calling TodoService
|
||||
- Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab
|
||||
- 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)
|
||||
- Made project list visibility icon fixed width
|
||||
- Set import_url validation to be more strict
|
||||
- Memoize MR merged/closed events retrieval
|
||||
- Don't render discussion notes when requesting diff tab through AJAX
|
||||
|
|
|
@ -339,7 +339,7 @@ GEM
|
|||
httparty (0.13.7)
|
||||
json (~> 1.8)
|
||||
multi_xml (>= 0.5.2)
|
||||
httpclient (2.7.0.1)
|
||||
httpclient (2.8.2)
|
||||
i18n (0.7.0)
|
||||
ice_nine (0.11.1)
|
||||
influxdb (0.2.3)
|
||||
|
|
|
@ -9,10 +9,11 @@
|
|||
licensePath: "/api/:version/licenses/:key",
|
||||
gitignorePath: "/api/:version/gitignores/:key",
|
||||
gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key",
|
||||
issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key",
|
||||
|
||||
group: function(group_id, callback) {
|
||||
var url;
|
||||
url = Api.buildUrl(Api.groupPath);
|
||||
url = url.replace(':id', group_id);
|
||||
var url = Api.buildUrl(Api.groupPath)
|
||||
.replace(':id', group_id);
|
||||
return $.ajax({
|
||||
url: url,
|
||||
data: {
|
||||
|
@ -24,8 +25,7 @@
|
|||
});
|
||||
},
|
||||
groups: function(query, skip_ldap, callback) {
|
||||
var url;
|
||||
url = Api.buildUrl(Api.groupsPath);
|
||||
var url = Api.buildUrl(Api.groupsPath);
|
||||
return $.ajax({
|
||||
url: url,
|
||||
data: {
|
||||
|
@ -39,8 +39,7 @@
|
|||
});
|
||||
},
|
||||
namespaces: function(query, callback) {
|
||||
var url;
|
||||
url = Api.buildUrl(Api.namespacesPath);
|
||||
var url = Api.buildUrl(Api.namespacesPath);
|
||||
return $.ajax({
|
||||
url: url,
|
||||
data: {
|
||||
|
@ -54,8 +53,7 @@
|
|||
});
|
||||
},
|
||||
projects: function(query, order, callback) {
|
||||
var url;
|
||||
url = Api.buildUrl(Api.projectsPath);
|
||||
var url = Api.buildUrl(Api.projectsPath);
|
||||
return $.ajax({
|
||||
url: url,
|
||||
data: {
|
||||
|
@ -70,9 +68,8 @@
|
|||
});
|
||||
},
|
||||
newLabel: function(project_id, data, callback) {
|
||||
var url;
|
||||
url = Api.buildUrl(Api.labelsPath);
|
||||
url = url.replace(':id', project_id);
|
||||
var url = Api.buildUrl(Api.labelsPath)
|
||||
.replace(':id', project_id);
|
||||
data.private_token = gon.api_token;
|
||||
return $.ajax({
|
||||
url: url,
|
||||
|
@ -86,9 +83,8 @@
|
|||
});
|
||||
},
|
||||
groupProjects: function(group_id, query, callback) {
|
||||
var url;
|
||||
url = Api.buildUrl(Api.groupProjectsPath);
|
||||
url = url.replace(':id', group_id);
|
||||
var url = Api.buildUrl(Api.groupProjectsPath)
|
||||
.replace(':id', group_id);
|
||||
return $.ajax({
|
||||
url: url,
|
||||
data: {
|
||||
|
@ -102,8 +98,8 @@
|
|||
});
|
||||
},
|
||||
licenseText: function(key, data, callback) {
|
||||
var url;
|
||||
url = Api.buildUrl(Api.licensePath).replace(':key', key);
|
||||
var url = Api.buildUrl(Api.licensePath)
|
||||
.replace(':key', key);
|
||||
return $.ajax({
|
||||
url: url,
|
||||
data: data
|
||||
|
@ -112,19 +108,32 @@
|
|||
});
|
||||
},
|
||||
gitignoreText: function(key, callback) {
|
||||
var url;
|
||||
url = Api.buildUrl(Api.gitignorePath).replace(':key', key);
|
||||
var url = Api.buildUrl(Api.gitignorePath)
|
||||
.replace(':key', key);
|
||||
return $.get(url, function(gitignore) {
|
||||
return callback(gitignore);
|
||||
});
|
||||
},
|
||||
gitlabCiYml: function(key, callback) {
|
||||
var url;
|
||||
url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key);
|
||||
var url = Api.buildUrl(Api.gitlabCiYmlPath)
|
||||
.replace(':key', key);
|
||||
return $.get(url, function(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) {
|
||||
if (gon.relative_url_root != null) {
|
||||
url = gon.relative_url_root + url;
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
/*= require date.format */
|
||||
/*= require_directory ./behaviors */
|
||||
/*= require_directory ./blob */
|
||||
/*= require_directory ./templates */
|
||||
/*= require_directory ./commit */
|
||||
/*= require_directory ./extensions */
|
||||
/*= require_directory ./lib/utils */
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
}
|
||||
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.dropdownIcon = $('.fa-chevron-down', this.dropdown);
|
||||
this.buildDropdown();
|
||||
this.bindEvents();
|
||||
this.onFilenameUpdate();
|
||||
|
@ -60,11 +61,26 @@
|
|||
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);
|
||||
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;
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
shortcut_handler = new ShortcutsNavigation();
|
||||
new GLForm($('.issue-form'));
|
||||
new IssuableForm($('.issue-form'));
|
||||
new IssuableTemplateSelectors();
|
||||
break;
|
||||
case 'projects:merge_requests:new':
|
||||
case 'projects:merge_requests:edit':
|
||||
|
@ -62,6 +63,7 @@
|
|||
shortcut_handler = new ShortcutsNavigation();
|
||||
new GLForm($('.merge-request-form'));
|
||||
new IssuableForm($('.merge-request-form'));
|
||||
new IssuableTemplateSelectors();
|
||||
break;
|
||||
case 'projects:tags:new':
|
||||
new ZenMode();
|
||||
|
|
|
@ -5,13 +5,10 @@
|
|||
|
||||
this.Issuable = {
|
||||
init: function() {
|
||||
if (!issuable_created) {
|
||||
issuable_created = true;
|
||||
Issuable.initTemplates();
|
||||
Issuable.initSearch();
|
||||
Issuable.initChecks();
|
||||
return Issuable.initLabelFilterRemove();
|
||||
}
|
||||
Issuable.initTemplates();
|
||||
Issuable.initSearch();
|
||||
Issuable.initChecks();
|
||||
return Issuable.initLabelFilterRemove();
|
||||
},
|
||||
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> <% }); %>');
|
||||
|
|
|
@ -44,8 +44,8 @@
|
|||
|
||||
// Enable submit button
|
||||
const $branchInput = this.$wrap.find('input[name="protected_branch[name]"]');
|
||||
const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_level_attributes][access_level]"]');
|
||||
const $allowedToPushInput = this.$wrap.find('input[name="protected_branch[push_access_level_attributes][access_level]"]');
|
||||
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_levels_attributes][0][access_level]"]');
|
||||
|
||||
if ($branchInput.val() && $allowedToMergeInput.val() && $allowedToPushInput.val()){
|
||||
this.$form.find('input[type="submit"]').removeAttr('disabled');
|
||||
|
|
|
@ -39,12 +39,14 @@
|
|||
_method: 'PATCH',
|
||||
id: this.$wrap.data('banchId'),
|
||||
protected_branch: {
|
||||
merge_access_level_attributes: {
|
||||
merge_access_levels_attributes: [{
|
||||
id: this.$allowedToMergeDropdown.data('access-level-id'),
|
||||
access_level: $allowedToMergeInput.val()
|
||||
},
|
||||
push_access_level_attributes: {
|
||||
}],
|
||||
push_access_levels_attributes: [{
|
||||
id: this.$allowedToPushDropdown.data('access-level-id'),
|
||||
access_level: $allowedToPushInput.val()
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
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);
|
||||
}
|
||||
|
||||
&.btn-spam {
|
||||
@include btn-outline($white-light, $red-normal, $red-normal, $red-light, $white-light, $red-light);
|
||||
}
|
||||
|
||||
&.btn-danger,
|
||||
&.btn-remove,
|
||||
&.btn-red {
|
||||
|
|
|
@ -56,9 +56,13 @@
|
|||
position: absolute;
|
||||
top: 50%;
|
||||
right: 6px;
|
||||
margin-top: -4px;
|
||||
margin-top: -6px;
|
||||
color: $dropdown-toggle-icon-color;
|
||||
font-size: 10px;
|
||||
&.fa-spinner {
|
||||
font-size: 16px;
|
||||
margin-top: -8px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover, {
|
||||
|
@ -406,6 +410,7 @@
|
|||
font-size: 14px;
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
table-layout: fixed;
|
||||
|
||||
pre {
|
||||
padding: 10px;
|
||||
padding: 10px 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
font-family: $monospace_font;
|
||||
font-size: $code_font_size !important;
|
||||
font-size: $code_font_size;
|
||||
line-height: $code_line_height !important;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
|
@ -20,13 +20,20 @@
|
|||
border-left: 1px solid;
|
||||
|
||||
code {
|
||||
display: inline-block;
|
||||
min-width: 100%;
|
||||
font-family: $monospace_font;
|
||||
white-space: pre;
|
||||
white-space: normal;
|
||||
word-wrap: normal;
|
||||
padding: 0;
|
||||
|
||||
.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;
|
||||
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 {
|
||||
color: $gl-success;
|
||||
|
||||
a.environment {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
&.ci-success_with_warnings {
|
||||
|
@ -126,7 +130,6 @@
|
|||
&.has-conflicts .fa-exclamation-triangle {
|
||||
color: $gl-warning;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
|
|
|
@ -14,4 +14,14 @@ class Admin::SpamLogsController < Admin::ApplicationController
|
|||
head :ok
|
||||
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
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
class AutocompleteController < ApplicationController
|
||||
skip_before_action :authenticate_user!, only: [:users]
|
||||
before_action :load_project, only: [:users]
|
||||
before_action :find_users, only: [:users]
|
||||
|
||||
def users
|
||||
|
@ -55,11 +56,8 @@ class AutocompleteController < ApplicationController
|
|||
|
||||
def find_users
|
||||
@users =
|
||||
if params[:project_id].present?
|
||||
project = Project.find(params[:project_id])
|
||||
return render_404 unless can?(current_user, :read_project, project)
|
||||
|
||||
project.team.users
|
||||
if @project
|
||||
@project.team.users
|
||||
elsif params[:group_id].present?
|
||||
group = Group.find(params[:group_id])
|
||||
return render_404 unless can?(current_user, :read_group, group)
|
||||
|
@ -71,4 +69,14 @@ class AutocompleteController < ApplicationController
|
|||
User.none
|
||||
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
|
||||
|
|
|
@ -7,11 +7,16 @@ module ServiceParams
|
|||
:build_key, :server, :teamcity_url, :drone_url, :build_type,
|
||||
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
|
||||
:colorize_messages, :channels,
|
||||
:push_events, :issues_events, :merge_requests_events, :tag_push_events,
|
||||
:note_events, :build_events, :wiki_page_events,
|
||||
:notify_only_broken_builds, :add_pusher,
|
||||
:send_from_committer_email, :disable_diffs, :external_wiki_url,
|
||||
:notify, :color,
|
||||
# We're using `issues_events` and `merge_requests_events`
|
||||
# in the view so we still need to explicitly state them
|
||||
# here. `Service#event_names` would only give
|
||||
# `issue_events` and `merge_request_events` (singular!)
|
||||
# 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,
|
||||
:jira_issue_transition_id]
|
||||
|
||||
|
@ -19,9 +24,7 @@ module ServiceParams
|
|||
FILTER_BLANK_PARAMS = [:password]
|
||||
|
||||
def service_params
|
||||
dynamic_params = []
|
||||
dynamic_params.concat(@service.event_channel_names)
|
||||
|
||||
dynamic_params = @service.event_channel_names + @service.event_names
|
||||
service_params = params.permit(:id, service: ALLOWED_PARAMS + dynamic_params)
|
||||
|
||||
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
|
||||
{
|
||||
count: TodosFinder.new(current_user, state: :pending).execute.count,
|
||||
done_count: TodosFinder.new(current_user, state: :done).execute.count
|
||||
count: current_user.todos_pending_count,
|
||||
done_count: current_user.todos_done_count
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
class Import::GitlabProjectsController < Import::BaseController
|
||||
before_action :verify_gitlab_project_import_enabled
|
||||
before_action :authenticate_admin!
|
||||
|
||||
def new
|
||||
@namespace_id = project_params[:namespace_id]
|
||||
|
@ -47,4 +48,8 @@ class Import::GitlabProjectsController < Import::BaseController
|
|||
:path, :namespace_id, :file
|
||||
)
|
||||
end
|
||||
|
||||
def authenticate_admin!
|
||||
render_404 unless current_user.is_admin?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,11 +4,24 @@ class Projects::BadgesController < Projects::ApplicationController
|
|||
before_action :no_cache_headers, except: [:index]
|
||||
|
||||
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|
|
||||
format.html { render_404 }
|
||||
|
||||
format.svg do
|
||||
render 'badge', locals: { badge: badge.template }
|
||||
end
|
||||
|
|
|
@ -17,6 +17,7 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
before_action :require_branch_head, only: [:edit, :update]
|
||||
before_action :editor_variables, except: [:show, :preview, :diff]
|
||||
before_action :validate_diff_params, only: :diff
|
||||
before_action :set_last_commit_sha, only: [:edit, :update]
|
||||
|
||||
def new
|
||||
commit unless @repository.empty?
|
||||
|
@ -33,7 +34,6 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def edit
|
||||
@last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
|
||||
blob.load_all_data!(@repository)
|
||||
end
|
||||
|
||||
|
@ -55,6 +55,10 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
create_commit(Files::UpdateService, success_path: after_edit_path,
|
||||
failure_view: :edit,
|
||||
failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
|
||||
|
||||
rescue Files::UpdateService::FileChangedError
|
||||
@conflict = true
|
||||
render :edit
|
||||
end
|
||||
|
||||
def preview
|
||||
|
@ -152,7 +156,8 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
file_path: @file_path,
|
||||
commit_message: params[:commit_message],
|
||||
file_content: params[:content],
|
||||
file_content_encoding: params[:encoding]
|
||||
file_content_encoding: params[:encoding],
|
||||
last_commit_sha: params[:last_commit_sha]
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -161,4 +166,9 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
render nothing: true
|
||||
end
|
||||
end
|
||||
|
||||
def set_last_commit_sha
|
||||
@last_commit_sha = Gitlab::Git::Commit.
|
||||
last_for_path(@repository, @ref, @path).sha
|
||||
end
|
||||
end
|
||||
|
|
|
@ -56,6 +56,7 @@ class Projects::HooksController < Projects::ApplicationController
|
|||
def hook_params
|
||||
params.require(:hook).permit(
|
||||
:build_events,
|
||||
:pipeline_events,
|
||||
:enable_ssl_verification,
|
||||
:issues_events,
|
||||
:merge_requests_events,
|
||||
|
|
|
@ -4,6 +4,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
include IssuableActions
|
||||
include ToggleAwardEmoji
|
||||
include IssuableCollections
|
||||
include SpammableActions
|
||||
|
||||
before_action :redirect_to_external_issue_tracker, only: [:index, :new]
|
||||
before_action :module_enabled
|
||||
|
@ -181,6 +182,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
alias_method :subscribable_resource, :issue
|
||||
alias_method :issuable, :issue
|
||||
alias_method :awardable, :issue
|
||||
alias_method :spammable, :issue
|
||||
|
||||
def authorize_read_issue!
|
||||
return render_404 unless can?(current_user, :read_issue, @issue)
|
||||
|
|
|
@ -3,7 +3,13 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
|
|||
|
||||
def show
|
||||
@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
|
||||
|
||||
def update
|
||||
|
|
|
@ -9,16 +9,16 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
|
|||
|
||||
def index
|
||||
@protected_branch = @project.protected_branches.new
|
||||
load_protected_branches_gon_variables
|
||||
load_gon_index
|
||||
end
|
||||
|
||||
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?
|
||||
redirect_to namespace_project_protected_branches_path(@project.namespace, @project)
|
||||
else
|
||||
load_protected_branches
|
||||
load_protected_branches_gon_variables
|
||||
load_gon_index
|
||||
render :index
|
||||
end
|
||||
end
|
||||
|
@ -28,7 +28,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
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?
|
||||
respond_to do |format|
|
||||
|
@ -58,17 +58,23 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
|
|||
|
||||
def protected_branch_params
|
||||
params.require(:protected_branch).permit(:name,
|
||||
merge_access_level_attributes: [:access_level],
|
||||
push_access_level_attributes: [:access_level])
|
||||
merge_access_levels_attributes: [:access_level, :id],
|
||||
push_access_levels_attributes: [:access_level, :id])
|
||||
end
|
||||
|
||||
def load_protected_branches
|
||||
@protected_branches = @project.protected_branches.order(:name).page(params[:page])
|
||||
end
|
||||
|
||||
def load_protected_branches_gon_variables
|
||||
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 } },
|
||||
merge_access_levels: ProtectedBranch::MergeAccessLevel.human_access_levels.map { |id, text| { id: id, text: text } } })
|
||||
def access_levels_options
|
||||
{
|
||||
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, 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
|
||||
|
|
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
|
||||
def execute(current_user = nil, options = {})
|
||||
def execute(current_user = nil, project_ids_relation = nil)
|
||||
segments = all_projects(current_user)
|
||||
segments.map! { |s| s.where(id: project_ids_relation) } if project_ids_relation
|
||||
|
||||
find_union(segments, Project)
|
||||
end
|
||||
|
|
|
@ -27,9 +27,11 @@ class TodosFinder
|
|||
items = by_action_id(items)
|
||||
items = by_action(items)
|
||||
items = by_author(items)
|
||||
items = by_project(items)
|
||||
items = by_state(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)
|
||||
end
|
||||
|
@ -91,14 +93,9 @@ class TodosFinder
|
|||
@project
|
||||
end
|
||||
|
||||
def projects
|
||||
return @projects if defined?(@projects)
|
||||
|
||||
if project?
|
||||
@projects = project
|
||||
else
|
||||
@projects = ProjectsFinder.new.execute(current_user)
|
||||
end
|
||||
def projects(items)
|
||||
item_project_ids = items.reorder(nil).select(:project_id)
|
||||
ProjectsFinder.new.execute(current_user, item_project_ids)
|
||||
end
|
||||
|
||||
def type?
|
||||
|
@ -136,8 +133,9 @@ class TodosFinder
|
|||
def by_project(items)
|
||||
if project?
|
||||
items = items.where(project: project)
|
||||
elsif projects
|
||||
items = items.merge(projects).joins(:project)
|
||||
else
|
||||
item_projects = projects(items)
|
||||
items = items.merge(item_projects).joins(:project)
|
||||
end
|
||||
|
||||
items
|
||||
|
|
|
@ -182,17 +182,42 @@ module BlobHelper
|
|||
}
|
||||
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
|
||||
@gitignore_names ||=
|
||||
Gitlab::Template::Gitignore.categories.keys.map do |k|
|
||||
[k, Gitlab::Template::Gitignore.by_category(k).map { |t| { name: t.name } }]
|
||||
end.to_h
|
||||
@gitignore_names ||= Gitlab::Template::GitignoreTemplate.dropdown_names
|
||||
end
|
||||
|
||||
def gitlab_ci_ymls
|
||||
@gitlab_ci_ymls ||=
|
||||
Gitlab::Template::GitlabCiYml.categories.keys.map do |k|
|
||||
[k, Gitlab::Template::GitlabCiYml.by_category(k).map { |t| { name: t.name } }]
|
||||
end.to_h
|
||||
@gitlab_ci_ymls ||= Gitlab::Template::GitlabCiYmlTemplate.dropdown_names
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,13 +20,19 @@ module SortingHelper
|
|||
end
|
||||
|
||||
def projects_sort_options_hash
|
||||
{
|
||||
options = {
|
||||
sort_value_name => sort_title_name,
|
||||
sort_value_recently_updated => sort_title_recently_updated,
|
||||
sort_value_oldest_updated => sort_title_oldest_updated,
|
||||
sort_value_recently_created => sort_title_recently_created,
|
||||
sort_value_oldest_created => sort_title_oldest_created,
|
||||
}
|
||||
|
||||
if current_controller?('admin/projects')
|
||||
options.merge!(sort_value_largest_repo => sort_title_largest_repo)
|
||||
end
|
||||
|
||||
options
|
||||
end
|
||||
|
||||
def sort_title_priority
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
module TodosHelper
|
||||
def todos_pending_count
|
||||
@todos_pending_count ||= TodosFinder.new(current_user, state: :pending).execute.count
|
||||
@todos_pending_count ||= current_user.todos_pending_count
|
||||
end
|
||||
|
||||
def todos_done_count
|
||||
@todos_done_count ||= TodosFinder.new(current_user, state: :done).execute.count
|
||||
@todos_done_count ||= current_user.todos_done_count
|
||||
end
|
||||
|
||||
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_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
|
||||
#
|
||||
# This method prevents the decorated object from evaluating to "truthy" when
|
||||
|
@ -31,6 +34,10 @@ class Blob < SimpleDelegator
|
|||
text? && language && language.name == 'SVG'
|
||||
end
|
||||
|
||||
def size_within_svg_limits?
|
||||
size <= MAXIMUM_SVG_SIZE
|
||||
end
|
||||
|
||||
def video?
|
||||
UploaderHelper::VIDEO_EXT.include?(extname.downcase.delete('.'))
|
||||
end
|
||||
|
|
|
@ -344,7 +344,7 @@ module Ci
|
|||
|
||||
def execute_hooks
|
||||
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_services(build_data.dup, :build_hooks)
|
||||
project.running_or_pending_build_count(force: true)
|
||||
|
|
|
@ -19,6 +19,8 @@ module Ci
|
|||
|
||||
after_save :keep_around_commits
|
||||
|
||||
delegate :stages, to: :statuses
|
||||
|
||||
state_machine :status, initial: :created do
|
||||
event :enqueue do
|
||||
transition created: :pending
|
||||
|
@ -56,6 +58,10 @@ module Ci
|
|||
before_transition do |pipeline|
|
||||
pipeline.update_duration
|
||||
end
|
||||
|
||||
after_transition do |pipeline, transition|
|
||||
pipeline.execute_hooks unless transition.loopback?
|
||||
end
|
||||
end
|
||||
|
||||
# ref can't be HEAD or SHA, can only be branch/tag name
|
||||
|
@ -243,8 +249,18 @@ module Ci
|
|||
self.duration = statuses.latest.duration
|
||||
end
|
||||
|
||||
def execute_hooks
|
||||
data = pipeline_data
|
||||
project.execute_hooks(data, :pipeline_hooks)
|
||||
project.execute_services(data, :pipeline_hooks)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def pipeline_data
|
||||
Gitlab::DataBuilder::Pipeline.build(self)
|
||||
end
|
||||
|
||||
def latest_builds_status
|
||||
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
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
module ClassMethods
|
||||
def attr_spammable(attr, options = {})
|
||||
spammable_attrs << [attr.to_s, options]
|
||||
end
|
||||
end
|
||||
|
||||
included do
|
||||
has_one :user_agent_detail, as: :subject, dependent: :destroy
|
||||
|
||||
attr_accessor :spam
|
||||
|
||||
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
|
||||
|
||||
def spam?
|
||||
|
@ -13,4 +36,33 @@ module Spammable
|
|||
def check_for_spam
|
||||
self.errors.add(:base, "Your #{self.class.name.underscore} has been recognized as spam and has been discarded.") if spam?
|
||||
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
|
||||
|
|
|
@ -36,4 +36,10 @@ class Deployment < ActiveRecord::Base
|
|||
def manual_actions
|
||||
deployable.try(:other_actions)
|
||||
end
|
||||
|
||||
def includes_commit?(commit)
|
||||
return false unless commit
|
||||
|
||||
project.repository.is_ancestor?(commit.id, sha)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -25,4 +25,10 @@ class Environment < ActiveRecord::Base
|
|||
def nullify_external_url
|
||||
self.external_url = nil if self.external_url.blank?
|
||||
end
|
||||
|
||||
def includes_commit?(commit)
|
||||
return false unless last_deployment
|
||||
|
||||
last_deployment.includes_commit?(commit)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,5 +5,6 @@ class ProjectHook < WebHook
|
|||
scope :note_hooks, -> { where(note_events: true) }
|
||||
scope :merge_request_hooks, -> { where(merge_requests_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) }
|
||||
end
|
||||
|
|
|
@ -8,6 +8,7 @@ class WebHook < ActiveRecord::Base
|
|||
default_value_for :merge_requests_events, false
|
||||
default_value_for :tag_push_events, false
|
||||
default_value_for :build_events, false
|
||||
default_value_for :pipeline_events, false
|
||||
default_value_for :enable_ssl_verification, 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_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
|
||||
event :close do
|
||||
transition [:reopened, :opened] => :closed
|
||||
|
@ -262,4 +265,9 @@ class Issue < ActiveRecord::Base
|
|||
def overdue?
|
||||
due_date.try(:past?) || false
|
||||
end
|
||||
|
||||
# Only issues on public projects should be checked for spam
|
||||
def check_for_spam?
|
||||
project.public?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -591,6 +591,14 @@ class MergeRequest < ActiveRecord::Base
|
|||
!pipeline || pipeline.success?
|
||||
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
|
||||
if merged?
|
||||
"Merged"
|
||||
|
|
|
@ -51,8 +51,7 @@ class BuildsEmailService < Service
|
|||
end
|
||||
|
||||
def test_data(project = nil, user = nil)
|
||||
build = project.builds.last
|
||||
Gitlab::BuildDataBuilder.build(build)
|
||||
Gitlab::DataBuilder::Build.build(project.builds.last)
|
||||
end
|
||||
|
||||
def fields
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
class PivotaltrackerService < Service
|
||||
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?
|
||||
|
||||
def title
|
||||
|
@ -18,7 +20,17 @@ class PivotaltrackerService < Service
|
|||
|
||||
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
|
||||
|
||||
|
@ -28,8 +40,8 @@ class PivotaltrackerService < Service
|
|||
|
||||
def execute(data)
|
||||
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|
|
||||
message = {
|
||||
'source_commit' => {
|
||||
|
@ -40,7 +52,7 @@ class PivotaltrackerService < Service
|
|||
}
|
||||
}
|
||||
PivotaltrackerService.post(
|
||||
url,
|
||||
API_ENDPOINT,
|
||||
body: message.to_json,
|
||||
headers: {
|
||||
'Content-Type' => 'application/json',
|
||||
|
@ -49,4 +61,15 @@ class PivotaltrackerService < Service
|
|||
)
|
||||
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
|
||||
|
|
|
@ -56,6 +56,10 @@ class ProjectWiki
|
|||
end
|
||||
end
|
||||
|
||||
def repository_exists?
|
||||
!!repository.exists?
|
||||
end
|
||||
|
||||
def empty?
|
||||
pages.empty?
|
||||
end
|
||||
|
|
|
@ -5,11 +5,14 @@ class ProtectedBranch < ActiveRecord::Base
|
|||
validates :name, presence: true
|
||||
validates :project, presence: true
|
||||
|
||||
has_one :merge_access_level, dependent: :destroy
|
||||
has_one :push_access_level, dependent: :destroy
|
||||
has_many :merge_access_levels, dependent: :destroy
|
||||
has_many :push_access_levels, dependent: :destroy
|
||||
|
||||
accepts_nested_attributes_for :push_access_level
|
||||
accepts_nested_attributes_for :merge_access_level
|
||||
validates_length_of :merge_access_levels, is: 1, message: "are restricted to a single instance per protected branch."
|
||||
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
|
||||
project.commit(self.name)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base
|
||||
include ProtectedBranchAccess
|
||||
|
||||
belongs_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
|
||||
end
|
||||
|
||||
def humanize
|
||||
self.class.human_access_levels[self.access_level]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class ProtectedBranch::PushAccessLevel < ActiveRecord::Base
|
||||
include ProtectedBranchAccess
|
||||
|
||||
belongs_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
|
||||
end
|
||||
|
||||
def humanize
|
||||
self.class.human_access_levels[self.access_level]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -36,6 +36,7 @@ class Service < ActiveRecord::Base
|
|||
scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) }
|
||||
scope :note_hooks, -> { where(note_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 :external_issue_trackers, -> { issue_trackers.active.without_defaults }
|
||||
|
||||
|
@ -79,13 +80,17 @@ class Service < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def test_data(project, user)
|
||||
Gitlab::PushDataBuilder.build_sample(project, user)
|
||||
Gitlab::DataBuilder::Push.build_sample(project, user)
|
||||
end
|
||||
|
||||
def event_channel_names
|
||||
[]
|
||||
end
|
||||
|
||||
def event_names
|
||||
supported_events.map { |event| "#{event}_events" }
|
||||
end
|
||||
|
||||
def event_field(event)
|
||||
nil
|
||||
end
|
||||
|
|
|
@ -7,4 +7,8 @@ class SpamLog < ActiveRecord::Base
|
|||
user.block
|
||||
user.destroy
|
||||
end
|
||||
|
||||
def text
|
||||
[title, description].join("\n")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -809,13 +809,13 @@ class User < ActiveRecord::Base
|
|||
|
||||
def todos_done_count(force: false)
|
||||
Rails.cache.fetch(['users', id, 'todos_done_count'], force: force) do
|
||||
todos.done.count
|
||||
TodosFinder.new(self, state: :done).execute.count
|
||||
end
|
||||
end
|
||||
|
||||
def todos_pending_count(force: false)
|
||||
Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force) do
|
||||
todos.pending.count
|
||||
TodosFinder.new(self, state: :pending).execute.count
|
||||
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
|
||||
|
||||
def build_push_data(branch)
|
||||
Gitlab::PushDataBuilder
|
||||
.build(project, current_user, branch.target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", [])
|
||||
Gitlab::DataBuilder::Push.build(
|
||||
project,
|
||||
current_user,
|
||||
branch.target.sha,
|
||||
Gitlab::Git::BLANK_SHA,
|
||||
"#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}",
|
||||
[])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -33,7 +33,12 @@ class DeleteTagService < BaseService
|
|||
end
|
||||
|
||||
def build_push_data(tag)
|
||||
Gitlab::PushDataBuilder
|
||||
.build(project, current_user, tag.target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", [])
|
||||
Gitlab::DataBuilder::Push.build(
|
||||
project,
|
||||
current_user,
|
||||
tag.target.sha,
|
||||
Gitlab::Git::BLANK_SHA,
|
||||
"#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}",
|
||||
[])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,6 +15,7 @@ module Files
|
|||
else
|
||||
params[:file_content]
|
||||
end
|
||||
@last_commit_sha = params[:last_commit_sha]
|
||||
|
||||
# Validate parameters
|
||||
validate
|
||||
|
|
|
@ -2,11 +2,34 @@ require_relative "base_service"
|
|||
|
||||
module Files
|
||||
class UpdateService < Files::BaseService
|
||||
class FileChangedError < StandardError; end
|
||||
|
||||
def commit
|
||||
repository.update_file(current_user, @file_path, @file_content,
|
||||
branch: @target_branch,
|
||||
previous_path: @previous_path,
|
||||
message: @commit_message)
|
||||
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
|
||||
|
|
|
@ -91,12 +91,12 @@ class GitPushService < BaseService
|
|||
|
||||
params = {
|
||||
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
|
||||
},
|
||||
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
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
ProtectedBranches::CreateService.new(@project, current_user, params).execute
|
||||
|
@ -138,13 +138,23 @@ class GitPushService < BaseService
|
|||
end
|
||||
|
||||
def build_push_data
|
||||
@push_data ||= Gitlab::PushDataBuilder.
|
||||
build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], push_commits)
|
||||
@push_data ||= Gitlab::DataBuilder::Push.build(
|
||||
@project,
|
||||
current_user,
|
||||
params[:oldrev],
|
||||
params[:newrev],
|
||||
params[:ref],
|
||||
push_commits)
|
||||
end
|
||||
|
||||
def build_push_data_system_hook
|
||||
@push_data_system ||= Gitlab::PushDataBuilder.
|
||||
build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], [])
|
||||
@push_data_system ||= Gitlab::DataBuilder::Push.build(
|
||||
@project,
|
||||
current_user,
|
||||
params[:oldrev],
|
||||
params[:newrev],
|
||||
params[:ref],
|
||||
[])
|
||||
end
|
||||
|
||||
def push_to_existing_branch?
|
||||
|
|
|
@ -34,12 +34,24 @@ class GitTagPushService < BaseService
|
|||
end
|
||||
end
|
||||
|
||||
Gitlab::PushDataBuilder.
|
||||
build(project, current_user, params[:oldrev], params[:newrev], params[:ref], commits, message)
|
||||
Gitlab::DataBuilder::Push.build(
|
||||
project,
|
||||
current_user,
|
||||
params[:oldrev],
|
||||
params[:newrev],
|
||||
params[:ref],
|
||||
commits,
|
||||
message)
|
||||
end
|
||||
|
||||
def build_system_push_data
|
||||
Gitlab::PushDataBuilder.
|
||||
build(project, current_user, params[:oldrev], params[:newrev], params[:ref], [], '')
|
||||
Gitlab::DataBuilder::Push.build(
|
||||
project,
|
||||
current_user,
|
||||
params[:oldrev],
|
||||
params[:newrev],
|
||||
params[:ref],
|
||||
[],
|
||||
'')
|
||||
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
|
||||
class CreateService < Issues::BaseService
|
||||
def execute
|
||||
issue = project.issues.new
|
||||
request = params.delete(:request)
|
||||
api = params.delete(:api)
|
||||
@request = params.delete(:request)
|
||||
@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
|
||||
|
||||
def handle_creation(issuable)
|
||||
event_service.open_issue(issuable, current_user)
|
||||
notification_service.new_issue(issuable, current_user)
|
||||
todo_service.new_issue(issuable, current_user)
|
||||
user_agent_detail_service.create
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def spam_check_service
|
||||
SpamCheckService.new(project, current_user, params)
|
||||
def spam_service
|
||||
SpamService.new(@issue, @request)
|
||||
end
|
||||
|
||||
def user_agent_detail_service
|
||||
UserAgentDetailService.new(@issue, @request)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,10 +30,21 @@ module MergeRequests
|
|||
end
|
||||
|
||||
def get_branches(changes)
|
||||
return [] if project.empty_repo?
|
||||
return [] unless project.merge_requests_enabled
|
||||
|
||||
changes_list = Gitlab::ChangesList.new(changes)
|
||||
changes_list.map do |change|
|
||||
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
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ module Notes
|
|||
end
|
||||
|
||||
def hook_data
|
||||
Gitlab::NoteDataBuilder.build(@note, @note.author)
|
||||
Gitlab::DataBuilder::Note.build(@note, @note.author)
|
||||
end
|
||||
|
||||
def execute_note_hooks
|
||||
|
|
|
@ -5,23 +5,7 @@ module ProtectedBranches
|
|||
def execute
|
||||
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project)
|
||||
|
||||
protected_branch = project.protected_branches.new(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
|
||||
project.protected_branches.create(params)
|
||||
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
|
||||
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')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -144,8 +144,9 @@ class TodoService
|
|||
def mark_todos_as_done(todos, current_user)
|
||||
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
|
||||
marked_todos
|
||||
end
|
||||
|
||||
# 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),
|
||||
data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove"
|
||||
%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?
|
||||
= 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
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
.encoding-selector
|
||||
= 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]}
|
||||
- if local_assigns[:path]
|
||||
.js-edit-mode-pane#preview.hide
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
.file-content.image_file
|
||||
- if blob.svg?
|
||||
- # We need to scrub SVG but we cannot do so in the RawController: it would
|
||||
- # be wrong/strange if RawController modified the data.
|
||||
- blob.load_all_data!(@repository)
|
||||
- blob = sanitize_svg(blob)
|
||||
%img{src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"}
|
||||
- if blob.size_within_svg_limits?
|
||||
- # We need to scrub SVG but we cannot do so in the RawController: it would
|
||||
- # be wrong/strange if RawController modified the data.
|
||||
- blob.load_all_data!(@repository)
|
||||
- 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
|
||||
%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
|
||||
|
||||
- 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
|
||||
%ul.nav-links.no-bottom.js-edit-mode
|
||||
%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
|
||||
= render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
|
||||
= render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
|
||||
|
||||
= hidden_field_tag 'last_commit', @last_commit
|
||||
= hidden_field_tag 'last_commit_sha', @last_commit_sha
|
||||
= hidden_field_tag 'content', '', id: "file-content"
|
||||
= 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)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
.col-md-8.col-lg-7
|
||||
%strong.light-header= hook.url
|
||||
%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)
|
||||
%span.label.label-gray.deploy-project-label= trigger.titleize
|
||||
.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'
|
||||
%li
|
||||
= 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)
|
||||
= 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
|
||||
- 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 '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
|
||||
Edit
|
||||
- if @issue.submittable_as_spam? && current_user.admin?
|
||||
= 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
|
||||
|
|
|
@ -42,3 +42,16 @@
|
|||
.ci_widget.ci-error{style: "display:none"}
|
||||
= icon("times-circle")
|
||||
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
|
||||
= icon('git', text: 'Repo by URL')
|
||||
%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
|
||||
= 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
|
||||
|
||||
.row.prepend-top-default
|
||||
.col-lg-3.profile-settings-sidebar
|
||||
%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)
|
||||
= render partial: 'badge', collection: @badges
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
Protect a branch
|
||||
.panel-body
|
||||
.form-horizontal
|
||||
= form_errors(@protected_branch)
|
||||
.form-group
|
||||
= f.label :name, class: 'col-md-2 text-right' do
|
||||
Branch:
|
||||
|
@ -18,19 +19,19 @@
|
|||
%code production/*
|
||||
are supported
|
||||
.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:
|
||||
.col-md-10
|
||||
= dropdown_tag('Select',
|
||||
options: { toggle_class: 'js-allowed-to-merge wide',
|
||||
data: { field_name: 'protected_branch[merge_access_level_attributes][access_level]', input_id: 'merge_access_level_attributes' }})
|
||||
data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }})
|
||||
.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:
|
||||
.col-md-10
|
||||
= dropdown_tag('Select',
|
||||
options: { toggle_class: 'js-allowed-to-push wide',
|
||||
data: { field_name: 'protected_branch[push_access_level_attributes][access_level]', input_id: 'push_access_level_attributes' }})
|
||||
data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }})
|
||||
|
||||
.panel-footer
|
||||
= f.submit 'Protect', class: 'btn-create btn', disabled: true
|
||||
|
|
|
@ -13,16 +13,9 @@
|
|||
= time_ago_with_tooltip(commit.committed_date)
|
||||
- else
|
||||
(branch was removed from repository)
|
||||
%td
|
||||
= hidden_field_tag "allowed_to_merge_#{protected_branch.id}", protected_branch.merge_access_level.access_level
|
||||
= 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}" }})
|
||||
|
||||
= render partial: 'update_protected_branch', locals: { protected_branch: protected_branch }
|
||||
|
||||
- if can_admin_project
|
||||
%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'
|
||||
|
|
|
@ -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
|
||||
= 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
|
||||
= 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
|
||||
= 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
|
||||
|
|
|
@ -2,7 +2,22 @@
|
|||
|
||||
.form-group
|
||||
= 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',
|
||||
class: 'form-control pad', required: true
|
||||
|
||||
|
@ -23,6 +38,13 @@
|
|||
to prevent a
|
||||
%strong Work In Progress
|
||||
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
|
||||
= f.label :description, 'Description', class: 'control-label'
|
||||
.col-sm-10
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
%li.project-row{ class: css_class }
|
||||
= cache(cache_key) do
|
||||
.controls
|
||||
- if project.archived
|
||||
%span.label.label-warning archived
|
||||
- if project.commit.try(:status)
|
||||
%span
|
||||
= render_commit_status(project.commit)
|
||||
|
|
|
@ -29,49 +29,56 @@
|
|||
= f.label :push_events, class: 'list-label' do
|
||||
%strong Push events
|
||||
%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
|
||||
= f.check_box :tag_push_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= f.label :tag_push_events, class: 'list-label' do
|
||||
%strong Tag push events
|
||||
%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
|
||||
= f.check_box :note_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= f.label :note_events, class: 'list-label' do
|
||||
%strong Comments
|
||||
%p.light
|
||||
This url will be triggered when someone adds a comment
|
||||
This URL will be triggered when someone adds a comment
|
||||
%li
|
||||
= f.check_box :issues_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= f.label :issues_events, class: 'list-label' do
|
||||
%strong Issues events
|
||||
%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
|
||||
= f.check_box :merge_requests_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= f.label :merge_requests_events, class: 'list-label' do
|
||||
%strong Merge Request events
|
||||
%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
|
||||
= f.check_box :build_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= f.label :build_events, class: 'list-label' do
|
||||
%strong Build events
|
||||
%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
|
||||
= f.check_box :wiki_page_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= f.label :wiki_page_events, class: 'list-label' do
|
||||
%strong Wiki Page events
|
||||
%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
|
||||
= f.label :enable_ssl_verification, "SSL verification", class: 'label-light checkbox'
|
||||
.checkbox
|
||||
|
|
|
@ -252,7 +252,11 @@ Rails.application.routes.draw do
|
|||
resource :impersonation, only: :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
|
||||
|
||||
|
@ -524,6 +528,11 @@ Rails.application.routes.draw do
|
|||
put '/update/*id', to: 'blob#update', constraints: { id: /.+/ }, as: 'update_blob'
|
||||
post '/preview/*id', to: 'blob#preview', constraints: { id: /.+/ }, as: 'preview_blob'
|
||||
|
||||
#
|
||||
# Templates
|
||||
#
|
||||
get '/templates/:template_type/:key' => 'templates#show', as: :template
|
||||
|
||||
scope do
|
||||
get(
|
||||
'/blob/*id/diff',
|
||||
|
@ -813,6 +822,7 @@ Rails.application.routes.draw do
|
|||
member do
|
||||
post :toggle_subscription
|
||||
post :toggle_award_emoji
|
||||
post :mark_as_spam
|
||||
get :referenced_merge_requests
|
||||
get :related_branches
|
||||
get :can_create_branch
|
||||
|
@ -869,7 +879,10 @@ Rails.application.routes.draw do
|
|||
resources :badges, only: [:index] do
|
||||
collection 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
|
||||
|
|
|
@ -1,5 +1,21 @@
|
|||
class Gitlab::Seeder::Builds
|
||||
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)
|
||||
@project = project
|
||||
|
@ -8,25 +24,7 @@ class Gitlab::Seeder::Builds
|
|||
def seed!
|
||||
pipelines.each do |pipeline|
|
||||
begin
|
||||
build_create!(pipeline, name: 'build:linux', stage: 'build', status_event: :success)
|
||||
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)
|
||||
|
||||
BUILDS.each { |opts| build_create!(pipeline, opts) }
|
||||
commit_status_create!(pipeline, name: 'jenkins', status: :success)
|
||||
|
||||
print '.'
|
||||
|
@ -48,22 +46,23 @@ class Gitlab::Seeder::Builds
|
|||
|
||||
def build_create!(pipeline, opts = {})
|
||||
attributes = build_attributes_for(pipeline, opts)
|
||||
build = Ci::Build.create!(attributes)
|
||||
|
||||
if opts[:name].start_with?('build')
|
||||
artifacts_cache_file(artifacts_archive_path) do |file|
|
||||
build.artifacts_file = file
|
||||
Ci::Build.create!(attributes) do |build|
|
||||
if opts[:name].start_with?('build')
|
||||
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
|
||||
|
||||
artifacts_cache_file(artifacts_metadata_path) do |file|
|
||||
build.artifacts_metadata = file
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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.integer "updated_by_id"
|
||||
t.string "merge_error"
|
||||
t.text "merge_params"
|
||||
t.boolean "merge_when_build_succeeds", default: false, null: false
|
||||
t.integer "merge_user_id"
|
||||
t.string "merge_commit_sha"
|
||||
t.datetime "deleted_at"
|
||||
t.string "in_progress_merge_commit_sha"
|
||||
t.text "merge_params"
|
||||
end
|
||||
|
||||
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.boolean "default", default: false
|
||||
t.boolean "wiki_page_events", default: true
|
||||
t.boolean "pipeline_events", default: false, null: false
|
||||
end
|
||||
|
||||
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 "user_agent"
|
||||
t.boolean "via_api"
|
||||
t.integer "project_id"
|
||||
t.string "noteable_type"
|
||||
t.string "title"
|
||||
t.text "description"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.boolean "submitted_as_ham", default: false, null: false
|
||||
end
|
||||
|
||||
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", ["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|
|
||||
t.string "email", 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 "wiki_page_events", default: false, null: false
|
||||
t.string "token"
|
||||
t.boolean "pipeline_events", default: false, null: false
|
||||
end
|
||||
|
||||
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
|
||||
|
|
|
@ -503,6 +503,7 @@ PUT /projects/:id/services/pivotaltracker
|
|||
Parameters:
|
||||
|
||||
- `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
|
||||
|
||||
|
@ -661,4 +662,3 @@ Get JetBrains TeamCity CI service settings for a project.
|
|||
```
|
||||
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