Commit 283b6fb4 authored by Valery Sizov's avatar Valery Sizov

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

parents 82fb4ac0 b2bf01f4
...@@ -5,6 +5,23 @@ v 8.11.2 (unreleased) ...@@ -5,6 +5,23 @@ v 8.11.2 (unreleased)
v 8.11.1 v 8.11.1
- Fix file links on project page when default view is Files !5933 - Fix file links on project page when default view is Files !5933
- Fixed enter key in search input not working !5888 - Fixed enter key in search input not working !5888
v 8.12.0 (unreleased)
- Add two-factor recovery endpoint to internal API !5510
- Change merge_error column from string to text type
- Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel)
- Optimistic locking for Issues and Merge Requests (title and description overriding prevention)
- Add `wiki_page_events` to project hook APIs (Ben Boeckel)
- Add Sentry logging to API calls
- Added tests for diff notes
- Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck)
v 8.11.2 (unreleased)
- Show "Create Merge Request" widget for push events to fork projects on the source project
v 8.11.1 (unreleased)
- Does not halt the GitHub import process when an error occurs
- Fix file links on project page when default view is Files !5933
- Change using size to use count and caching it for number of group members
v 8.11.0 v 8.11.0
- Use test coverage value from the latest successful pipeline in badge. !5862 - Use test coverage value from the latest successful pipeline in badge. !5862
...@@ -29,6 +46,7 @@ v 8.11.0 ...@@ -29,6 +46,7 @@ v 8.11.0
- Use long options for curl examples in documentation !5703 (winniehell) - Use long options for curl examples in documentation !5703 (winniehell)
- Added tooltip listing label names to the labels value in the collapsed issuable sidebar - Added tooltip listing label names to the labels value in the collapsed issuable sidebar
- Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell) - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell)
- Fix badge count alignment (ClemMakesApps)
- GitLab Performance Monitoring can now track custom events such as the number of tags pushed to a repository - GitLab Performance Monitoring can now track custom events such as the number of tags pushed to a repository
- Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell) - Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell)
- Allow naming U2F devices !5833 - Allow naming U2F devices !5833
...@@ -69,6 +87,7 @@ v 8.11.0 ...@@ -69,6 +87,7 @@ v 8.11.0
- Enforce 2FA restrictions on API authentication endpoints !5820 - Enforce 2FA restrictions on API authentication endpoints !5820
- Limit git rev-list output count to one in forced push check - Limit git rev-list output count to one in forced push check
- Show deployment status on merge requests with external URLs - Show deployment status on merge requests with external URLs
- Fix branch title trailing space on hover (ClemMakesApps)
- Clean up unused routes (Josef Strzibny) - Clean up unused routes (Josef Strzibny)
- Fix issue on empty project to allow developers to only push to protected branches if given permission - Fix issue on empty project to allow developers to only push to protected branches if given permission
- API: Add enpoints for pipelines - API: Add enpoints for pipelines
...@@ -85,6 +104,7 @@ v 8.11.0 ...@@ -85,6 +104,7 @@ v 8.11.0
- Fix devise deprecation warnings. - Fix devise deprecation warnings.
- Check for 2FA when using Git over HTTP and only allow PersonalAccessTokens as password in that case !5764 - Check for 2FA when using Git over HTTP and only allow PersonalAccessTokens as password in that case !5764
- Update version_sorter and use new interface for faster tag sorting - Update version_sorter and use new interface for faster tag sorting
- Load branches asynchronously in Cherry Pick and Revert dialogs.
- Optimize checking if a user has read access to a list of issues !5370 - Optimize checking if a user has read access to a list of issues !5370
- Store all DB secrets in secrets.yml, under descriptive names !5274 - Store all DB secrets in secrets.yml, under descriptive names !5274
- Fix syntax highlighting in file editor - Fix syntax highlighting in file editor
...@@ -118,12 +138,14 @@ v 8.11.0 ...@@ -118,12 +138,14 @@ v 8.11.0
- Fix search for notes which belongs to deleted objects - Fix search for notes which belongs to deleted objects
- Allow Akismet to be trained by submitting issues as spam or ham !5538 - Allow Akismet to be trained by submitting issues as spam or ham !5538
- Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska)
- Fix spacing and vertical alignment on build status icon on commits page (ClemMakesApps)
- Allow branch names ending with .json for graph and network page !5579 (winniehell) - Allow branch names ending with .json for graph and network page !5579 (winniehell)
- Add the `sprockets-es6` gem - Add the `sprockets-es6` gem
- Improve OAuth2 client documentation (muteor) - Improve OAuth2 client documentation (muteor)
- Fix diff comments inverted toggle bug (ClemMakesApps) - Fix diff comments inverted toggle bug (ClemMakesApps)
- Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska)
- Profile requests when a header is passed - Profile requests when a header is passed
- Fix button missing type (ClemMakesApps)
- Avoid calculation of line_code and position for _line partial when showing diff notes on discussion tab. - Avoid calculation of line_code and position for _line partial when showing diff notes on discussion tab.
- Speedup DiffNote#active? on discussions, preloading noteables and avoid touching git repository to return diff_refs when possible - Speedup DiffNote#active? on discussions, preloading noteables and avoid touching git repository to return diff_refs when possible
- Add commit stats in commit api. !5517 (dixpac) - Add commit stats in commit api. !5517 (dixpac)
...@@ -132,6 +154,7 @@ v 8.11.0 ...@@ -132,6 +154,7 @@ v 8.11.0
- edit_blob_link will use blob passed onto the options parameter - edit_blob_link will use blob passed onto the options parameter
- Make error pages responsive (Takuya Noguchi) - Make error pages responsive (Takuya Noguchi)
- The performance of the project dropdown used for moving issues has been improved - The performance of the project dropdown used for moving issues has been improved
- Move to project dropdown with infinite scroll for better performance
- Fix skip_repo parameter being ignored when destroying a namespace - Fix skip_repo parameter being ignored when destroying a namespace
- Add all builds into stage/job dropdowns on builds page - Add all builds into stage/job dropdowns on builds page
- Change requests_profiles resource constraint to catch virtually any file - Change requests_profiles resource constraint to catch virtually any file
...@@ -163,6 +186,11 @@ v 8.11.0 ...@@ -163,6 +186,11 @@ v 8.11.0
- Eliminate unneeded calls to Repository#blob_at when listing commits with no path - Eliminate unneeded calls to Repository#blob_at when listing commits with no path
- Update gitlab_git gem to 10.4.7 - Update gitlab_git gem to 10.4.7
- Simplify SQL queries of marking a todo as done - Simplify SQL queries of marking a todo as done
- Update merge_requests.md with a simpler way to check out a merge request. !5944
v 8.10.7
- Upgrade Hamlit to 2.6.1. !5873
- Upgrade Doorkeeper to 4.2.0. !5881
v 8.10.6 v 8.10.6
- Upgrade Rails to 4.2.7.1 for security fixes. !5781 - Upgrade Rails to 4.2.7.1 for security fixes. !5781
...@@ -192,8 +220,6 @@ v 8.10.3 ...@@ -192,8 +220,6 @@ v 8.10.3
- Trim extra displayed carriage returns in diffs and files with CRLFs. !5588 - Trim extra displayed carriage returns in diffs and files with CRLFs. !5588
- Fix label already exist error message in the right sidebar. - Fix label already exist error message in the right sidebar.
v 8.10.3 (unreleased)
v 8.10.2 v 8.10.2
- User can now search branches by name. !5144 - User can now search branches by name. !5144
- Page is now properly rendered after committing the first file and creating the first branch. !5399 - Page is now properly rendered after committing the first file and creating the first branch. !5399
...@@ -391,6 +417,9 @@ v 8.10.0 ...@@ -391,6 +417,9 @@ v 8.10.0
- Fix migration corrupting import data for old version upgrades - Fix migration corrupting import data for old version upgrades
- Show tooltip on GitLab export link in new project page - Show tooltip on GitLab export link in new project page
v 8.9.8
- Upgrade Doorkeeper to 4.2.0. !5881
v 8.9.7 v 8.9.7
- Upgrade Rails to 4.2.7.1 for security fixes. !5781 - Upgrade Rails to 4.2.7.1 for security fixes. !5781
- Require administrator privileges to perform a project import. - Require administrator privileges to perform a project import.
...@@ -663,6 +692,9 @@ v 8.9.0 ...@@ -663,6 +692,9 @@ v 8.9.0
- Add tooltip to pin/unpin navbar - Add tooltip to pin/unpin navbar
- Add new sub nav style to Wiki and Graphs sub navigation - Add new sub nav style to Wiki and Graphs sub navigation
v 8.8.9
- Upgrade Doorkeeper to 4.2.0. !5881
v 8.8.8 v 8.8.8
- Upgrade Rails to 4.2.7.1 for security fixes. !5781 - Upgrade Rails to 4.2.7.1 for security fixes. !5781
......
...@@ -387,7 +387,8 @@ description area. Copy-paste it to retain the markdown format. ...@@ -387,7 +387,8 @@ description area. Copy-paste it to retain the markdown format.
1. The change is as small as possible 1. The change is as small as possible
1. Include proper tests and make all tests pass (unless it contains a test 1. Include proper tests and make all tests pass (unless it contains a test
exposing a bug in existing code) exposing a bug in existing code). Every new class should have corresponding
unit tests, even if the class is exercised at a higher level, such as a feature test.
1. If you suspect a failing CI build is unrelated to your contribution, you may 1. If you suspect a failing CI build is unrelated to your contribution, you may
try and restart the failing CI job or ask a developer to fix the try and restart the failing CI job or ask a developer to fix the
aforementioned failing test aforementioned failing test
......
# GitLab # GitLab
[![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) [![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
[![coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
## Canonical source ## Canonical source
...@@ -99,7 +100,7 @@ Instructions on how to start GitLab and how to run the tests can be found in the ...@@ -99,7 +100,7 @@ Instructions on how to start GitLab and how to run the tests can be found in the
GitLab is a Ruby on Rails application that runs on the following software: GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL - Ubuntu/Debian/CentOS/RHEL
- Ruby (MRI) 2.1 - Ruby (MRI) 2.3
- Git 2.7.4+ - Git 2.7.4+
- Redis 2.8+ - Redis 2.8+
- MySQL or PostgreSQL - MySQL or PostgreSQL
......
8.11.0-ee 8.12.0-ee-pre
...@@ -154,7 +154,9 @@ ...@@ -154,7 +154,9 @@
}); });
}); });
$('.remove-row').bind('ajax:success', function() { $('.remove-row').bind('ajax:success', function() {
return $(this).closest('li').fadeOut(); $(this).tooltip('destroy')
.closest('li')
.fadeOut();
}); });
$('.js-remove-tr').bind('ajax:before', function() { $('.js-remove-tr').bind('ajax:before', function() {
return $(this).hide(); return $(this).hide();
......
File mode changed from 100755 to 100644
...@@ -39,12 +39,13 @@ ...@@ -39,12 +39,13 @@
FilesCommentButton.prototype.render = function(e) { FilesCommentButton.prototype.render = function(e) {
var $currentTarget, buttonParentElement, lineContentElement, textFileElement; var $currentTarget, buttonParentElement, lineContentElement, textFileElement;
$currentTarget = $(e.currentTarget); $currentTarget = $(e.currentTarget);
buttonParentElement = this.getButtonParent($currentTarget); buttonParentElement = this.getButtonParent($currentTarget);
if (!this.shouldRender(e, buttonParentElement)) { if (!this.validateButtonParent(buttonParentElement)) return;
return;
}
textFileElement = this.getTextFileElement($currentTarget);
lineContentElement = this.getLineContent($currentTarget); lineContentElement = this.getLineContent($currentTarget);
if (!this.validateLineContent(lineContentElement)) return;
textFileElement = this.getTextFileElement($currentTarget);
buttonParentElement.append(this.buildButton({ buttonParentElement.append(this.buildButton({
noteableType: textFileElement.attr('data-noteable-type'), noteableType: textFileElement.attr('data-noteable-type'),
noteableID: textFileElement.attr('data-noteable-id'), noteableID: textFileElement.attr('data-noteable-id'),
...@@ -119,10 +120,14 @@ ...@@ -119,10 +120,14 @@
return newButtonParent.is(this.getButtonParent($(e.currentTarget))); return newButtonParent.is(this.getButtonParent($(e.currentTarget)));
}; };
FilesCommentButton.prototype.shouldRender = function(e, buttonParentElement) { FilesCommentButton.prototype.validateButtonParent = function(buttonParentElement) {
return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS) && $(COMMENT_BUTTON_CLASS, buttonParentElement).length === 0; return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS) && $(COMMENT_BUTTON_CLASS, buttonParentElement).length === 0;
}; };
FilesCommentButton.prototype.validateLineContent = function(lineContentElement) {
return lineContentElement.attr('data-discussion-id') && lineContentElement.attr('data-discussion-id') !== '';
};
return FilesCommentButton; return FilesCommentButton;
})(); })();
......
...@@ -102,20 +102,34 @@ ...@@ -102,20 +102,34 @@
}; };
IssuableForm.prototype.initMoveDropdown = function() { IssuableForm.prototype.initMoveDropdown = function() {
var $moveDropdown; var $moveDropdown, pageSize;
$moveDropdown = $('.js-move-dropdown'); $moveDropdown = $('.js-move-dropdown');
if ($moveDropdown.length) { if ($moveDropdown.length) {
pageSize = $moveDropdown.data('page-size');
return $('.js-move-dropdown').select2({ return $('.js-move-dropdown').select2({
ajax: { ajax: {
url: $moveDropdown.data('projects-url'), url: $moveDropdown.data('projects-url'),
results: function(data) { quietMillis: 125,
data: function(term, page, context) {
return { return {
results: data search: term,
offset_id: context
}; };
}, },
data: function(query) { results: function(data) {
var context,
more;
if (data.length >= pageSize)
more = true;
if (data[data.length - 1])
context = data[data.length - 1].id;
return { return {
search: query results: data,
more: more,
context: context
}; };
} }
}, },
......
...@@ -65,7 +65,8 @@ ...@@ -65,7 +65,8 @@
url: $dropdown.data('refs-url'), url: $dropdown.data('refs-url'),
data: { data: {
ref: $dropdown.data('ref') ref: $dropdown.data('ref')
} },
dataType: "json"
}).done(function(refs) { }).done(function(refs) {
return callback(refs); return callback(refs);
}); });
...@@ -73,7 +74,7 @@ ...@@ -73,7 +74,7 @@
selectable: true, selectable: true,
filterable: true, filterable: true,
filterByText: true, filterByText: true,
fieldName: 'ref', fieldName: $dropdown.data('field-name'),
renderRow: function(ref) { renderRow: function(ref) {
var link; var link;
if (ref.header != null) { if (ref.header != null) {
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
$dropdown.glDropdown({ $dropdown.glDropdown({
selectable: true, selectable: true,
<<<<<<< HEAD
filterable: true, filterable: true,
filterRemote: true, filterRemote: true,
data: this.getData.bind(this), data: this.getData.bind(this),
...@@ -34,6 +35,16 @@ ...@@ -34,6 +35,16 @@
setActiveIds() { setActiveIds() {
// Needed for pre select options // Needed for pre select options
this.activeIds = self.getActiveIds(); this.activeIds = self.getActiveIds();
=======
inputId: $dropdown.data('input-id'),
fieldName: $dropdown.data('field-name'),
toggleLabel(item, el) {
if (el.is('.is-active')) {
return item.text;
} else {
return 'Select';
}
>>>>>>> b2bf01f4c271be66e93ed6f4b48a1da4d50e558d
}, },
clicked(item, $el, e) { clicked(item, $el, e) {
e.preventDefault(); e.preventDefault();
......
...@@ -48,7 +48,11 @@ ...@@ -48,7 +48,11 @@
const $allowedToMergeInputs = this.$wrap.find('input[name^="protected_branch[merge_access_levels_attributes]"]'); const $allowedToMergeInputs = this.$wrap.find('input[name^="protected_branch[merge_access_levels_attributes]"]');
const $allowedToPushInputs = this.$wrap.find('input[name^="protected_branch[push_access_levels_attributes]"]'); const $allowedToPushInputs = this.$wrap.find('input[name^="protected_branch[push_access_levels_attributes]"]');
<<<<<<< HEAD
this.$form.find('input[type="submit"]').attr('disabled', !($branchInput.val() && $allowedToMergeInputs.length && $allowedToPushInputs.length)); this.$form.find('input[type="submit"]').attr('disabled', !($branchInput.val() && $allowedToMergeInputs.length && $allowedToPushInputs.length));
=======
this.$form.find('input[type="submit"]').attr('disabled', !($branchInput.val() && $allowedToMergeInput.length && $allowedToPushInput.length));
>>>>>>> b2bf01f4c271be66e93ed6f4b48a1da4d50e558d
} }
} }
......
...@@ -77,6 +77,7 @@ ...@@ -77,6 +77,7 @@
onDropdownHide() { onDropdownHide() {
if (!this.hasChanges) return; if (!this.hasChanges) return;
<<<<<<< HEAD
this.hasChanges = true; this.hasChanges = true;
this.updatePermissions(); this.updatePermissions();
...@@ -90,6 +91,12 @@ ...@@ -90,6 +91,12 @@
} }
return $.ajax({ return $.ajax({
=======
// Do not update if one dropdown has not selected any option
if (!($allowedToMergeInput.length && $allowedToPushInput.length)) return;
$.ajax({
>>>>>>> b2bf01f4c271be66e93ed6f4b48a1da4d50e558d
type: 'POST', type: 'POST',
url: this.$wrap.data('url'), url: this.$wrap.data('url'),
dataType: 'json', dataType: 'json',
......
...@@ -84,6 +84,15 @@ ...@@ -84,6 +84,15 @@
width: 100%; width: 100%;
} }
} }
// Allows dynamic-width text in the dropdown toggle.
// Resizes to allow long text without overflowing the container.
&.dynamic {
width: auto;
min-width: 160px;
max-width: 100%;
padding-right: 25px;
}
} }
.dropdown-menu, .dropdown-menu,
......
...@@ -63,9 +63,10 @@ ...@@ -63,9 +63,10 @@
&.image_file { &.image_file {
background: #eee; background: #eee;
text-align: center; text-align: center;
img { img {
padding: 100px; padding: 20px;
max-width: 50%; max-width: 80%;
} }
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* Styles that apply to all GFM related forms. * Styles that apply to all GFM related forms.
*/ */
.gfm-commit, .gfm-commit_range { .gfm-commit_range {
font-family: $monospace_font; font-family: $monospace_font;
font-size: 90%; font-size: 90%;
} }
.modal-body { .modal-body {
position: relative; position: relative;
overflow-y: auto;
padding: 15px; padding: 15px;
.form-actions { .form-actions {
......
...@@ -72,6 +72,7 @@ ...@@ -72,6 +72,7 @@
font-weight: normal; font-weight: normal;
background-color: #eee; background-color: #eee;
color: #78a; color: #78a;
vertical-align: baseline;
} }
} }
......
...@@ -45,7 +45,8 @@ ...@@ -45,7 +45,8 @@
min-width: 175px; min-width: 175px;
} }
.select2-results .select2-result-label { .select2-results .select2-result-label,
.select2-more-results {
padding: 10px 15px; padding: 10px 15px;
} }
......
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
* { * {
// !important to make sure no style can override this when dragging // !important to make sure no style can override this when dragging
cursor: -webkit-grabbing; cursor: -webkit-grabbing!important;
cursor: grabbing; cursor: grabbing!important;
} }
} }
......
...@@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base ...@@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base
include Gitlab::GonHelper include Gitlab::GonHelper
include GitlabRoutingHelper include GitlabRoutingHelper
include PageLayoutHelper include PageLayoutHelper
include SentryHelper
include WorkhorseHelper include WorkhorseHelper
before_action :authenticate_user_from_private_token! before_action :authenticate_user_from_private_token!
...@@ -46,28 +47,6 @@ class ApplicationController < ActionController::Base ...@@ -46,28 +47,6 @@ class ApplicationController < ActionController::Base
protected protected
def sentry_context
if Rails.env.production? && current_application_settings.sentry_enabled
if current_user
Raven.user_context(
id: current_user.id,
email: current_user.email,
username: current_user.username,
)
end
Raven.tags_context(program: sentry_program_context)
end
end
def sentry_program_context
if Sidekiq.server?
'sidekiq'
else
'rails'
end
end
# This filter handles both private tokens and personal access tokens # This filter handles both private tokens and personal access tokens
def authenticate_user_from_private_token! def authenticate_user_from_private_token!
token_string = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence token_string = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
......
...@@ -21,10 +21,8 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -21,10 +21,8 @@ class Groups::GroupMembersController < Groups::ApplicationController
end end
def create def create
user_ids = params[:user_ids].split(',')
@group.add_users( @group.add_users(
user_ids, params[:user_ids].split(','),
params[:access_level], params[:access_level],
current_user: current_user, current_user: current_user,
expires_at: params[:expires_at] expires_at: params[:expires_at]
......
...@@ -15,6 +15,13 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -15,6 +15,13 @@ class Projects::BranchesController < Projects::ApplicationController
diverging_commit_counts = repository.diverging_commit_counts(branch) diverging_commit_counts = repository.diverging_commit_counts(branch)
[memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max [memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
end end
respond_to do |format|
format.html
format.json do
render json: @repository.branch_names
end
end
end end
def recent def recent
......
...@@ -131,6 +131,10 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -131,6 +131,10 @@ class Projects::IssuesController < Projects::ApplicationController
render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }) render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end end
end end
rescue ActiveRecord::StaleObjectError
@conflict = true
render :edit
end end
def referenced_merge_requests def referenced_merge_requests
...@@ -236,7 +240,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -236,7 +240,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue_params def issue_params
params.require(:issue).permit( params.require(:issue).permit(
:title, :assignee_id, :position, :description, :confidential, :weight, :title, :assignee_id, :position, :description, :confidential, :weight,
:milestone_id, :due_date, :state_event, :task_num, label_ids: [] :milestone_id, :due_date, :state_event, :task_num, :lock_version, label_ids: []
) )
end end
......
...@@ -271,6 +271,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -271,6 +271,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
render "edit" render "edit"
end end
rescue ActiveRecord::StaleObjectError
@conflict = true
render :edit
end end
def remove_wip def remove_wip
...@@ -541,7 +544,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -541,7 +544,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
:title, :assignee_id, :source_project_id, :source_branch, :title, :assignee_id, :source_project_id, :source_branch,
:target_project_id, :target_branch, :milestone_id, :approver_ids, :target_project_id, :target_branch, :milestone_id, :approver_ids,
:state_event, :description, :task_num, :force_remove_source_branch, :state_event, :description, :task_num, :force_remove_source_branch,
:approvals_before_merge, label_ids: [] :approvals_before_merge, :lock_version, label_ids: []
) )
end end
......
class MoveToProjectFinder class MoveToProjectFinder
PAGE_SIZE = 50
def initialize(user) def initialize(user)
@user = user @user = user
end end
...@@ -8,6 +10,10 @@ class MoveToProjectFinder ...@@ -8,6 +10,10 @@ class MoveToProjectFinder
projects = projects.search(search) if search.present? projects = projects.search(search) if search.present?
projects = projects.excluding_project(from_project) projects = projects.excluding_project(from_project)
# infinite scroll using offset
projects = projects.where('projects.id < ?', offset_id) if offset_id.present?
projects = projects.limit(PAGE_SIZE)
# to ask for Project#name_with_namespace # to ask for Project#name_with_namespace
projects.includes(namespace: :owner) projects.includes(namespace: :owner)
end end
......
...@@ -116,6 +116,17 @@ module ProjectsHelper ...@@ -116,6 +116,17 @@ module ProjectsHelper
license.nickname || license.name license.nickname || license.name
end end
def last_push_event
return unless current_user
project_ids = [@project.id]
if fork = current_user.fork_of(@project)
project_ids << fork.id
end
current_user.recent_push(project_ids)
end
private private
def get_project_nav_tabs(project, current_user) def get_project_nav_tabs(project, current_user)
...@@ -368,16 +379,6 @@ module ProjectsHelper ...@@ -368,16 +379,6 @@ module ProjectsHelper
namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'LICENSE') namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'LICENSE')
end end
def last_push_event
return unless current_user
if fork = current_user.fork_of(@project)
current_user.recent_push(fork.id)
else
current_user.recent_push(@project.id)
end
end
def readme_cache_key def readme_cache_key
sha = @project.commit.try(:sha) || 'nil' sha = @project.commit.try(:sha) || 'nil'
[@project.path_with_namespace, sha, "readme"].join('-') [@project.path_with_namespace, sha, "readme"].join('-')
......
module SentryHelper
def sentry_enabled?
Rails.env.production? && current_application_settings.sentry_enabled?
end
def sentry_context
return unless sentry_enabled?
if current_user
Raven.user_context(
id: current_user.id,
email: current_user.email,
username: current_user.username,
)
end
Raven.tags_context(program: sentry_program_context)
end
def sentry_program_context
if Sidekiq.server?
'sidekiq'
else
'rails'
end
end
end
...@@ -193,8 +193,6 @@ class Ability ...@@ -193,8 +193,6 @@ class Ability
# Push abilities on the users team role # Push abilities on the users team role
rules.push(*project_team_rules(project.team, user)) rules.push(*project_team_rules(project.team, user))
rules << :change_repository_storage if user.admin?
owner = user.admin? || owner = user.admin? ||
project.owner == user || project.owner == user ||
(project.group && project.group.has_owner?(user)) (project.group && project.group.has_owner?(user))
......
...@@ -90,6 +90,12 @@ module Issuable ...@@ -90,6 +90,12 @@ module Issuable
User.find(assignee_id_was).update_cache_counts if assignee_id_was User.find(assignee_id_was).update_cache_counts if assignee_id_was
assignee.update_cache_counts if assignee assignee.update_cache_counts if assignee
end end
# We want to use optimistic lock for cases when only title or description are involved
# http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
def locking_enabled?
title_changed? || description_changed?
end
end end
module ClassMethods module ClassMethods
......
...@@ -501,8 +501,6 @@ class Project < ActiveRecord::Base ...@@ -501,8 +501,6 @@ class Project < ActiveRecord::Base
end end
def reset_cache_and_import_attrs def reset_cache_and_import_attrs
update(import_error: nil)
ProjectCacheWorker.perform_async(self.id) ProjectCacheWorker.perform_async(self.id)
self.import_data.destroy if !mirror? && self.import_data self.import_data.destroy if !mirror? && self.import_data
......
...@@ -526,10 +526,10 @@ class User < ActiveRecord::Base ...@@ -526,10 +526,10 @@ class User < ActiveRecord::Base
(personal_projects.count.to_f / projects_limit) * 100 (personal_projects.count.to_f / projects_limit) * 100
end end
def recent_push(project_id = nil) def recent_push(project_ids = nil)
# Get push events not earlier than 2 hours ago # Get push events not earlier than 2 hours ago
events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours) events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours)
events = events.where(project_id: project_id) if project_id events = events.where(project_id: project_ids) if project_ids
# Use the latest event that has not been pushed or merged recently # Use the latest event that has not been pushed or merged recently
events.recent.find do |event| events.recent.find do |event|
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
.panel-heading .panel-heading
%strong #{@group.name} %strong #{@group.name}
group members group members
%span.badge= @members.size %span.badge= @members.total_count
.controls .controls
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
.form-group .form-group
......
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
- number_commits_ahead = diverging_commit_counts[:ahead] - number_commits_ahead = diverging_commit_counts[:ahead]
%li(class="js-branch-#{branch.name}") %li(class="js-branch-#{branch.name}")
%div %div
= link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do = link_to namespace_project_tree_path(@project.namespace, @project, branch.name), class: 'item-title str-truncated' do
%span.item-title.str-truncated= branch.name = branch.name
&nbsp; &nbsp;
- if branch.name == @repository.root_ref - if branch.name == @repository.root_ref
%span.label.label-primary default %span.label.label-primary default
......
...@@ -17,7 +17,9 @@ ...@@ -17,7 +17,9 @@
.form-group.branch .form-group.branch
= label_tag 'target_branch', target_label, class: 'control-label' = label_tag 'target_branch', target_label, class: 'control-label'
.col-sm-10 .col-sm-10
= select_tag "target_branch", project_branches, class: "select2 select2-sm js-target-branch" = hidden_field_tag :target_branch, @project.default_branch, id: 'target_branch'
= dropdown_tag(@project.default_branch, options: { title: "Switch branch", filter: true, placeholder: "Search branches", toggle_class: 'js-project-refs-dropdown js-target-branch dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "target_branch", selected: @project.default_branch, target_branch: @project.default_branch, refs_url: namespace_project_branches_path(@project.namespace, @project), submit_form_on_click: false }})
- if can?(current_user, :push_code, @project) - if can?(current_user, :push_code, @project)
.js-create-merge-request-container .js-create-merge-request-container
.checkbox .checkbox
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
- if can?(current_user, :create_issue, @project) || can?(current_user, :update_issue, @issue) - if can?(current_user, :create_issue, @project) || can?(current_user, :update_issue, @issue)
.issuable-actions .issuable-actions
.clearfix.issue-btn-group.dropdown .clearfix.issue-btn-group.dropdown
%button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } } %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } }
%span.caret %span.caret
Options Options
.dropdown-menu.dropdown-menu-align-right.hidden-lg .dropdown-menu.dropdown-menu-align-right.hidden-lg
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
- if can?(current_user, :update_merge_request, @merge_request) - if can?(current_user, :update_merge_request, @merge_request)
.issuable-actions .issuable-actions
.clearfix.issue-btn-group.dropdown .clearfix.issue-btn-group.dropdown
%button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } } %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } }
%span.caret %span.caret
Options Options
.dropdown-menu.dropdown-menu-align-right.hidden-lg .dropdown-menu.dropdown-menu-align-right.hidden-lg
......
...@@ -24,16 +24,30 @@ ...@@ -24,16 +24,30 @@
.col-md-10 .col-md-10
.merge_access_levels-container .merge_access_levels-container
= dropdown_tag('Select', = dropdown_tag('Select',
options: { toggle_class: 'js-allowed-to-merge js-multiselect wide', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable', filter: true, <<<<<<< HEAD
options: { toggle_class: 'js-allowed-to-merge js-multiselect wide',
dropdown_class: 'dropdown-menu-user dropdown-menu-selectable', filter: true,
data: { input_id: 'merge_access_levels_attributes', default_label: 'Select' } }) data: { input_id: 'merge_access_levels_attributes', default_label: 'Select' } })
=======
options: { toggle_class: 'js-allowed-to-merge wide',
dropdown_class: 'dropdown-menu-selectable',
data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }})
>>>>>>> b2bf01f4c271be66e93ed6f4b48a1da4d50e558d
.form-group .form-group
%label.col-md-2.text-right{ for: 'push_access_levels_attributes' } %label.col-md-2.text-right{ for: 'push_access_levels_attributes' }
Allowed to push: Allowed to push:
.col-md-10 .col-md-10
.push_access_levels-container .push_access_levels-container
= dropdown_tag('Select', = dropdown_tag('Select',
options: { toggle_class: 'js-allowed-to-push js-multiselect wide', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable', filter: true, <<<<<<< HEAD
options: { toggle_class: 'js-allowed-to-push js-multiselect wide',
dropdown_class: 'dropdown-menu-user dropdown-menu-selectable', filter: true,
data: { input_id: 'push_access_levels_attributes', default_label: 'Select' } }) data: { input_id: 'push_access_levels_attributes', default_label: 'Select' } })
=======
options: { toggle_class: 'js-allowed-to-push wide',
dropdown_class: 'dropdown-menu-selectable',
data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }})
>>>>>>> b2bf01f4c271be66e93ed6f4b48a1da4d50e558d
.panel-footer .panel-footer
= f.submit 'Protect', class: 'btn-create btn', disabled: true = f.submit 'Protect', class: 'btn-create btn', disabled: true
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
- @options && @options.each do |key, value| - @options && @options.each do |key, value|
= hidden_field_tag key, value, id: nil = hidden_field_tag key, value, id: nil
.dropdown .dropdown
= dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_namespace_project_path(@project.namespace, @project) }, { toggle_class: "js-project-refs-dropdown" } = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_namespace_project_path(@project.namespace, @project), field_name: 'ref', submit_form_on_click: true }, { toggle_class: "js-project-refs-dropdown" }
.dropdown-menu.dropdown-menu-selectable{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) } .dropdown-menu.dropdown-menu-selectable{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
= dropdown_title "Switch branch/tag" = dropdown_title "Switch branch/tag"
= dropdown_filter "Search branches and tags" = dropdown_filter "Search branches and tags"
......
= form_errors(issuable) = form_errors(issuable)
- if @conflict
.alert.alert-danger
Someone edited the #{issuable.class.model_name.human.downcase} the same time you did.
Please check out
= link_to "the #{issuable.class.model_name.human.downcase}", polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), target: "_blank"
and make sure your changes will not unintentionally remove theirs
.form-group .form-group
= f.label :title, class: 'control-label' = f.label :title, class: 'control-label'
...@@ -129,7 +136,7 @@ ...@@ -129,7 +136,7 @@
= label_tag :move_to_project_id, 'Move', class: 'control-label' = label_tag :move_to_project_id, 'Move', class: 'control-label'
.col-sm-10 .col-sm-10
.issuable-form-select-holder .issuable-form-select-holder
= hidden_field_tag :move_to_project_id, nil, class: 'js-move-dropdown', data: { placeholder: 'Select project', projects_url: autocomplete_projects_path(project_id: @project.id) } = hidden_field_tag :move_to_project_id, nil, class: 'js-move-dropdown', data: { placeholder: 'Select project', projects_url: autocomplete_projects_path(project_id: @project.id), page_size: MoveToProjectFinder::PAGE_SIZE }
&nbsp; &nbsp;
%span{ data: { toggle: 'tooltip', placement: 'auto top' }, style: 'cursor: default', %span{ data: { toggle: 'tooltip', placement: 'auto top' }, style: 'cursor: default',
title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' } title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' }
...@@ -234,3 +241,5 @@ ...@@ -234,3 +241,5 @@
= link_to "#", data: { confirm: "Are you sure you want to remove approver {approver_name}"}, class: "btn-xs btn btn-remove", title: 'Remove approver' do = link_to "#", data: { confirm: "Are you sure you want to remove approver {approver_name}"}, class: "btn-xs btn btn-remove", title: 'Remove approver' do
= icon("sign-out") = icon("sign-out")
Remove Remove
= f.hidden_field :lock_version
...@@ -14,6 +14,8 @@ class RepositoryImportWorker ...@@ -14,6 +14,8 @@ class RepositoryImportWorker
import_url: @project.import_url, import_url: @project.import_url,
path: @project.path_with_namespace) path: @project.path_with_namespace)
project.update_column(:import_error, nil)
result = Projects::ImportService.new(project, current_user).execute result = Projects::ImportService.new(project, current_user).execute
if result[:status] == :error if result[:status] == :error
......
# rubocop:disable Lint/RescueException
# This patch fixes https://github.com/rails/rails/issues/26024
# TODO: Remove it when it's no longer necessary
module ActiveRecord
module Locking
module Optimistic
# We overwrite this method because we don't want to have default value
# for newly created records
def _create_record(attribute_names = self.attribute_names, *) # :nodoc:
super
end
def _update_record(attribute_names = self.attribute_names) #:nodoc:
return super unless locking_enabled?
return 0 if attribute_names.empty?
lock_col = self.class.locking_column
previous_lock_value = send(lock_col).to_i
# This line is added as a patch
previous_lock_value = nil if previous_lock_value == '0' || previous_lock_value == 0
increment_lock
attribute_names += [lock_col]
attribute_names.uniq!
begin
relation = self.class.unscoped
affected_rows = relation.where(
self.class.primary_key => id,
lock_col => previous_lock_value,
).update_all(
attributes_for_update(attribute_names).map do |name|
[name, _read_attribute(name)]
end.to_h
)
unless affected_rows == 1
raise ActiveRecord::StaleObjectError.new(self, "update")
end
affected_rows
# If something went wrong, revert the version.
rescue Exception
send(lock_col + '=', previous_lock_value)
raise
end
end
end
end
end
class Gitlab::Seeder::Builds class Gitlab::Seeder::Pipelines
STAGES = %w[build test deploy notify] STAGES = %w[build test deploy notify]
BUILDS = [ BUILDS = [
{ name: 'build:linux', stage: 'build', status: :success }, { name: 'build:linux', stage: 'build', status: :success },
...@@ -7,11 +7,12 @@ class Gitlab::Seeder::Builds ...@@ -7,11 +7,12 @@ class Gitlab::Seeder::Builds
{ name: 'rspec:windows', stage: 'test', status: :success }, { name: 'rspec:windows', 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: 'rspec:osx', stage: 'test', status_event: :success },
{ name: 'spinach:linux', stage: 'test', status: :pending }, { name: 'spinach:linux', stage: 'test', status: :success },
{ name: 'spinach:osx', stage: 'test', status: :canceled }, { name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true},
{ name: 'cucumber:linux', stage: 'test', status: :running }, { name: 'env:alpha', stage: 'deploy', environment: 'alpha', status: :pending },
{ name: 'cucumber:osx', stage: 'test', status: :failed }, { name: 'env:beta', stage: 'deploy', environment: 'beta', status: :running },
{ name: 'staging', stage: 'deploy', environment: 'staging', status: :success }, { name: 'env:gamma', stage: 'deploy', environment: 'gamma', status: :canceled },
{ name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success },
{ name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :skipped }, { name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :skipped },
{ name: 'slack', stage: 'notify', when: 'manual', status: :created }, { name: 'slack', stage: 'notify', when: 'manual', status: :created },
] ]
...@@ -34,40 +35,58 @@ class Gitlab::Seeder::Builds ...@@ -34,40 +35,58 @@ class Gitlab::Seeder::Builds
end end
end end
private
def pipelines def pipelines
master_pipelines + merge_request_pipelines create_master_pipelines + create_merge_request_pipelines
end end
def master_pipelines def create_master_pipelines
create_pipelines_for(@project, 'master') @project.repository.commits('master', limit: 4).map do |commit|
create_pipeline!(@project, 'master', commit)
end
rescue rescue
[] []
end end
def merge_request_pipelines def create_merge_request_pipelines
@project.merge_requests.last(5).map do |merge_request| pipelines = @project.merge_requests.first(3).map do |merge_request|
create_pipelines(merge_request.source_project, merge_request.source_branch, merge_request.commits.last(5)) project = merge_request.source_project
end.flatten branch = merge_request.source_branch
merge_request.commits.last(4).map do |commit|
create_pipeline!(project, branch, commit)
end
end
pipelines.flatten
rescue rescue
[] []
end end
def create_pipelines_for(project, ref)
commits = project.repository.commits(ref, limit: 5)
create_pipelines(project, ref, commits)
end
def create_pipelines(project, ref, commits) def create_pipeline!(project, ref, commit)
commits.map do |commit|
project.pipelines.create(sha: commit.id, ref: ref) project.pipelines.create(sha: commit.id, ref: ref)
end end
end
def build_create!(pipeline, opts = {}) def build_create!(pipeline, opts = {})
attributes = build_attributes_for(pipeline, opts) attributes = job_attributes(pipeline, opts)
.merge(commands: '$ build command')
Ci::Build.create!(attributes).tap do |build|
# We need to set build trace and artifacts after saving a build
# (id required), that is why we need `#tap` method instead of passing
# block directly to `Ci::Build#create!`.
setup_artifacts(build)
setup_build_log(build)
build.save
end
end
def setup_artifacts(build)
return unless %w[build test].include?(build.stage)
Ci::Build.create!(attributes) do |build|
if opts[:name].start_with?('build')
artifacts_cache_file(artifacts_archive_path) do |file| artifacts_cache_file(artifacts_archive_path) do |file|
build.artifacts_file = file build.artifacts_file = file
end end
...@@ -77,29 +96,25 @@ class Gitlab::Seeder::Builds ...@@ -77,29 +96,25 @@ class Gitlab::Seeder::Builds
end end
end end
def setup_build_log(build)
if %w(running success failed).include?(build.status) 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") build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
end end
end end
end
def commit_status_create!(pipeline, opts = {}) def commit_status_create!(pipeline, opts = {})
attributes = commit_status_attributes_for(pipeline, opts) attributes = job_attributes(pipeline, opts)
GenericCommitStatus.create!(attributes) GenericCommitStatus.create!(attributes)
end end
def commit_status_attributes_for(pipeline, opts) def job_attributes(pipeline, opts)
{ name: 'test build', stage: 'test', stage_idx: stage_index(opts[:stage]), { name: 'test build', stage: 'test', stage_idx: stage_index(opts[:stage]),
ref: 'master', tag: false, user: build_user, project: @project, pipeline: pipeline, ref: 'master', tag: false, user: build_user, project: @project, pipeline: pipeline,
created_at: Time.now, updated_at: Time.now created_at: Time.now, updated_at: Time.now
}.merge(opts) }.merge(opts)
end end
def build_attributes_for(pipeline, opts)
commit_status_attributes_for(pipeline, opts).merge(commands: '$ build command')
end
def build_user def build_user
@project.team.users.sample @project.team.users.sample
end end
...@@ -131,8 +146,8 @@ class Gitlab::Seeder::Builds ...@@ -131,8 +146,8 @@ class Gitlab::Seeder::Builds
end end
Gitlab::Seeder.quiet do Gitlab::Seeder.quiet do
Project.all.sample(10).each do |project| Project.all.sample(5).each do |project|
project_builds = Gitlab::Seeder::Builds.new(project) project_builds = Gitlab::Seeder::Pipelines.new(project)
project_builds.seed! project_builds.seed!
end end
end end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddLockToIssuables < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
add_column :issues, :lock_version, :integer
add_column :merge_requests, :lock_version, :integer
end
def down
remove_column :issues, :lock_version
remove_column :merge_requests, :lock_version
end
end
class ChangeMergeErrorToText < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = true
DOWNTIME_REASON = 'This migration requires downtime because it alters a column from varchar(255) to text.'
def change
change_column :merge_requests, :merge_error, :text, limit: 65535
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160819221833) do ActiveRecord::Schema.define(version: 20160823081327) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -531,6 +531,7 @@ ActiveRecord::Schema.define(version: 20160819221833) do ...@@ -531,6 +531,7 @@ ActiveRecord::Schema.define(version: 20160819221833) do
t.datetime "deleted_at" t.datetime "deleted_at"
t.date "due_date" t.date "due_date"
t.integer "moved_to_id" t.integer "moved_to_id"
t.integer "lock_version"
end end
add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
...@@ -687,7 +688,7 @@ ActiveRecord::Schema.define(version: 20160819221833) do ...@@ -687,7 +688,7 @@ ActiveRecord::Schema.define(version: 20160819221833) do
t.integer "position", default: 0 t.integer "position", default: 0
t.datetime "locked_at" t.datetime "locked_at"
t.integer "updated_by_id" t.integer "updated_by_id"
t.string "merge_error" t.text "merge_error"
t.text "merge_params" t.text "merge_params"
t.boolean "merge_when_build_succeeds", default: false, null: false t.boolean "merge_when_build_succeeds", default: false, null: false
t.integer "merge_user_id" t.integer "merge_user_id"
...@@ -696,6 +697,7 @@ ActiveRecord::Schema.define(version: 20160819221833) do ...@@ -696,6 +697,7 @@ ActiveRecord::Schema.define(version: 20160819221833) do
t.integer "approvals_before_merge" t.integer "approvals_before_merge"
t.string "rebase_commit_sha" t.string "rebase_commit_sha"
t.string "in_progress_merge_commit_sha" t.string "in_progress_merge_commit_sha"
t.integer "lock_version"
end end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
## User documentation ## User documentation
- [Account Security](user/account/security.md) Securing your account via two-factor authentication, etc.
- [API](api/README.md) Automate GitLab via a simple and powerful API. - [API](api/README.md) Automate GitLab via a simple and powerful API.
- [CI/CD](ci/README.md) GitLab Continuous Integration (CI) and Continuous Delivery (CD) getting started, `.gitlab-ci.yml` options, and examples. - [CI/CD](ci/README.md) GitLab Continuous Integration (CI) and Continuous Delivery (CD) getting started, `.gitlab-ci.yml` options, and examples.
- [Custom templates for issues and merge requests](customization/issue_and_merge_request_template.md) Pre-fill the description of issues and merge requests to your liking. - [Custom templates for issues and merge requests](customization/issue_and_merge_request_template.md) Pre-fill the description of issues and merge requests to your liking.
...@@ -22,6 +23,7 @@ ...@@ -22,6 +23,7 @@
- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects. - [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
- [Webhooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project. - [Webhooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN. - [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
- [Koding](user/project/koding.md) Learn how to use Koding, the online IDE.
## Administrator documentation ## Administrator documentation
......
...@@ -79,7 +79,8 @@ Example response: ...@@ -79,7 +79,8 @@ Example response:
"labels" : [], "labels" : [],
"subscribed" : false, "subscribed" : false,
"user_notes_count": 1, "user_notes_count": 1,
"due_date": "2016-07-22" "due_date": "2016-07-22",
"web_url": "http://example.com/example/example/issues/6"
} }
] ]
``` ```
...@@ -156,7 +157,8 @@ Example response: ...@@ -156,7 +157,8 @@ Example response:
"created_at" : "2016-01-04T15:31:46.176Z", "created_at" : "2016-01-04T15:31:46.176Z",
"subscribed" : false, "subscribed" : false,
"user_notes_count": 1, "user_notes_count": 1,
"due_date": null "due_date": null,
"web_url": "http://example.com/example/example/issues/1"
} }
] ]
``` ```
...@@ -235,7 +237,8 @@ Example response: ...@@ -235,7 +237,8 @@ Example response:
"created_at" : "2016-01-04T15:31:46.176Z", "created_at" : "2016-01-04T15:31:46.176Z",
"subscribed" : false, "subscribed" : false,
"user_notes_count": 1, "user_notes_count": 1,
"due_date": "2016-07-22" "due_date": "2016-07-22",
"web_url": "http://example.com/example/example/issues/1"
} }
] ]
``` ```
...@@ -299,7 +302,8 @@ Example response: ...@@ -299,7 +302,8 @@ Example response:
"created_at" : "2016-01-04T15:31:46.176Z", "created_at" : "2016-01-04T15:31:46.176Z",
"subscribed": false, "subscribed": false,
"user_notes_count": 1, "user_notes_count": 1,
"due_date": null "due_date": null,
"web_url": "http://example.com/example/example/issues/1"
} }
``` ```
...@@ -323,7 +327,7 @@ POST /projects/:id/issues ...@@ -323,7 +327,7 @@ POST /projects/:id/issues
| `assignee_id` | integer | no | The ID of a user to assign issue | | `assignee_id` | integer | no | The ID of a user to assign issue |
| `milestone_id` | integer | no | The ID of a milestone to assign issue | | `milestone_id` | integer | no | The ID of a milestone to assign issue |
| `labels` | string | no | Comma-separated label names for an issue | | `labels` | string | no | Comma-separated label names for an issue |
| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` | | `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) |
| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` | | `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
```bash ```bash
...@@ -357,7 +361,8 @@ Example response: ...@@ -357,7 +361,8 @@ Example response:
"milestone" : null, "milestone" : null,
"subscribed" : true, "subscribed" : true,
"user_notes_count": 0, "user_notes_count": 0,
"due_date": null "due_date": null,
"web_url": "http://example.com/example/example/issues/14"
} }
``` ```
...@@ -384,7 +389,7 @@ PUT /projects/:id/issues/:issue_id ...@@ -384,7 +389,7 @@ PUT /projects/:id/issues/:issue_id
| `milestone_id` | integer | no | The ID of a milestone to assign the issue to | | `milestone_id` | integer | no | The ID of a milestone to assign the issue to |
| `labels` | string | no | Comma-separated label names for an issue | | `labels` | string | no | Comma-separated label names for an issue |
| `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it | | `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it |
| `updated_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` | | `updated_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) |
| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` | | `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
```bash ```bash
...@@ -418,7 +423,8 @@ Example response: ...@@ -418,7 +423,8 @@ Example response:
"milestone" : null, "milestone" : null,
"subscribed" : true, "subscribed" : true,
"user_notes_count": 0, "user_notes_count": 0,
"due_date": "2016-07-22" "due_date": "2016-07-22",
"web_url": "http://example.com/example/example/issues/15"
} }
``` ```
...@@ -496,7 +502,8 @@ Example response: ...@@ -496,7 +502,8 @@ Example response:
"avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon", "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon",
"web_url": "https://gitlab.example.com/u/solon.cremin" "web_url": "https://gitlab.example.com/u/solon.cremin"
}, },
"due_date": null "due_date": null,
"web_url": "http://example.com/example/example/issues/11"
} }
``` ```
...@@ -551,7 +558,8 @@ Example response: ...@@ -551,7 +558,8 @@ Example response:
"avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon", "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon",
"web_url": "https://gitlab.example.com/u/solon.cremin" "web_url": "https://gitlab.example.com/u/solon.cremin"
}, },
"due_date": null "due_date": null,
"web_url": "http://example.com/example/example/issues/11"
} }
``` ```
...@@ -607,7 +615,8 @@ Example response: ...@@ -607,7 +615,8 @@ Example response:
"web_url": "https://gitlab.example.com/u/orville" "web_url": "https://gitlab.example.com/u/orville"
}, },
"subscribed": false, "subscribed": false,
"due_date": null "due_date": null,
"web_url": "http://example.com/example/example/issues/12"
} }
``` ```
...@@ -693,7 +702,9 @@ Example response: ...@@ -693,7 +702,9 @@ Example response:
"subscribed": true, "subscribed": true,
"user_notes_count": 7, "user_notes_count": 7,
"upvotes": 0, "upvotes": 0,
"downvotes": 0 "downvotes": 0,
"due_date": null,
"web_url": "http://example.com/example/example/issues/110"
}, },
"target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/issues/10", "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/issues/10",
"body": "Vel voluptas atque dicta mollitia adipisci qui at.", "body": "Vel voluptas atque dicta mollitia adipisci qui at.",
......
...@@ -71,7 +71,8 @@ Parameters: ...@@ -71,7 +71,8 @@ Parameters:
"user_notes_count": 1, "user_notes_count": 1,
"approvals_before_merge": null "approvals_before_merge": null
"should_remove_source_branch": true, "should_remove_source_branch": true,
"force_remove_source_branch": false "force_remove_source_branch": false,
"web_url": "http://example.com/example/example/merge_requests/1"
} }
] ]
``` ```
...@@ -138,7 +139,8 @@ Parameters: ...@@ -138,7 +139,8 @@ Parameters:
"user_notes_count": 1, "user_notes_count": 1,
"approvals_before_merge": null "approvals_before_merge": null
"should_remove_source_branch": true, "should_remove_source_branch": true,
"force_remove_source_branch": false "force_remove_source_branch": false,
"web_url": "http://example.com/example/example/merge_requests/1"
} }
``` ```
...@@ -242,6 +244,7 @@ Parameters: ...@@ -242,6 +244,7 @@ Parameters:
"approvals_before_merge": null, "approvals_before_merge": null,
"should_remove_source_branch": true, "should_remove_source_branch": true,
"force_remove_source_branch": false, "force_remove_source_branch": false,
"web_url": "http://example.com/example/example/merge_requests/1",
"changes": [ "changes": [
{ {
"old_path": "VERSION", "old_path": "VERSION",
...@@ -335,7 +338,8 @@ order for it to take effect: ...@@ -335,7 +338,8 @@ order for it to take effect:
"user_notes_count": 0, "user_notes_count": 0,
"approvals_before_merge": null "approvals_before_merge": null
"should_remove_source_branch": true, "should_remove_source_branch": true,
"force_remove_source_branch": false "force_remove_source_branch": false,
"web_url": "http://example.com/example/example/merge_requests/1"
} }
``` ```
...@@ -410,7 +414,8 @@ Parameters: ...@@ -410,7 +414,8 @@ Parameters:
"user_notes_count": 1, "user_notes_count": 1,
"approvals_before_merge": null "approvals_before_merge": null
"should_remove_source_branch": true, "should_remove_source_branch": true,
"force_remove_source_branch": false "force_remove_source_branch": false,
"web_url": "http://example.com/example/example/merge_requests/1"
} }
``` ```
...@@ -512,7 +517,8 @@ Parameters: ...@@ -512,7 +517,8 @@ Parameters:
"user_notes_count": 1, "user_notes_count": 1,
"approvals_before_merge": null "approvals_before_merge": null
"should_remove_source_branch": true, "should_remove_source_branch": true,
"force_remove_source_branch": false "force_remove_source_branch": false,
"web_url": "http://example.com/example/example/merge_requests/1"
} }
``` ```
...@@ -684,7 +690,8 @@ Parameters: ...@@ -684,7 +690,8 @@ Parameters:
"user_notes_count": 1, "user_notes_count": 1,
"approvals_before_merge": null "approvals_before_merge": null
"should_remove_source_branch": true, "should_remove_source_branch": true,
"force_remove_source_branch": false "force_remove_source_branch": false,
"web_url": "http://example.com/example/example/merge_requests/1"
} }
``` ```
...@@ -1006,7 +1013,8 @@ Example response: ...@@ -1006,7 +1013,8 @@ Example response:
"subscribed": true, "subscribed": true,
"user_notes_count": 7, "user_notes_count": 7,
"should_remove_source_branch": true, "should_remove_source_branch": true,
"force_remove_source_branch": false "force_remove_source_branch": false,
"web_url": "http://example.com/example/example/merge_requests/1"
}, },
"target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/merge_requests/7", "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/merge_requests/7",
"body": "Et voluptas laudantium minus nihil recusandae ut accusamus earum aut non.", "body": "Et voluptas laudantium minus nihil recusandae ut accusamus earum aut non.",
......
...@@ -53,7 +53,8 @@ Parameters: ...@@ -53,7 +53,8 @@ Parameters:
}, },
"expires_at": null, "expires_at": null,
"updated_at": "2012-06-28T10:52:04Z", "updated_at": "2012-06-28T10:52:04Z",
"created_at": "2012-06-28T10:52:04Z" "created_at": "2012-06-28T10:52:04Z",
"web_url": "http://example.com/example/example/snippets/1"
} }
``` ```
......
...@@ -84,7 +84,8 @@ Parameters: ...@@ -84,7 +84,8 @@ Parameters:
"star_count": 0, "star_count": 0,
"runners_token": "b8547b1dc37721d05889db52fa2f02", "runners_token": "b8547b1dc37721d05889db52fa2f02",
"public_builds": true, "public_builds": true,
"shared_with_groups": [] "shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false
}, },
{ {
"id": 6, "id": 6,
...@@ -144,7 +145,8 @@ Parameters: ...@@ -144,7 +145,8 @@ Parameters:
"star_count": 0, "star_count": 0,
"runners_token": "b8547b1dc37721d05889db52fa2f02", "runners_token": "b8547b1dc37721d05889db52fa2f02",
"public_builds": true, "public_builds": true,
"shared_with_groups": [] "shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false
} }
] ]
``` ```
...@@ -282,6 +284,7 @@ Parameters: ...@@ -282,6 +284,7 @@ Parameters:
} }
], ],
"repository_storage": "default" "repository_storage": "default"
"only_allow_merge_if_build_succeeds": false
} }
``` ```
...@@ -450,6 +453,7 @@ Parameters: ...@@ -450,6 +453,7 @@ Parameters:
- `import_url` (optional) - `import_url` (optional)
- `public_builds` (optional) - `public_builds` (optional)
- `repository_storage` (optional, available only for admins) - `repository_storage` (optional, available only for admins)
- `only_allow_merge_if_build_succeeds` (optional)
### Create project for user ### Create project for user
...@@ -476,6 +480,7 @@ Parameters: ...@@ -476,6 +480,7 @@ Parameters:
- `import_url` (optional) - `import_url` (optional)
- `public_builds` (optional) - `public_builds` (optional)
- `repository_storage` (optional, available only for admins) - `repository_storage` (optional, available only for admins)
- `only_allow_merge_if_build_succeeds` (optional)
### Edit project ### Edit project
...@@ -503,6 +508,7 @@ Parameters: ...@@ -503,6 +508,7 @@ Parameters:
- `visibility_level` (optional) - `visibility_level` (optional)
- `public_builds` (optional) - `public_builds` (optional)
- `repository_storage` (optional, available only for admins) - `repository_storage` (optional, available only for admins)
- `only_allow_merge_if_build_succeeds` (optional)
On success, method returns 200 with the updated project. If parameters are On success, method returns 200 with the updated project. If parameters are
invalid, 400 is returned. invalid, 400 is returned.
...@@ -581,7 +587,8 @@ Example response: ...@@ -581,7 +587,8 @@ Example response:
"forks_count": 0, "forks_count": 0,
"star_count": 1, "star_count": 1,
"public_builds": true, "public_builds": true,
"shared_with_groups": [] "shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false
} }
``` ```
...@@ -647,7 +654,8 @@ Example response: ...@@ -647,7 +654,8 @@ Example response:
"forks_count": 0, "forks_count": 0,
"star_count": 0, "star_count": 0,
"public_builds": true, "public_builds": true,
"shared_with_groups": [] "shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false
} }
``` ```
...@@ -733,7 +741,8 @@ Example response: ...@@ -733,7 +741,8 @@ Example response:
"star_count": 0, "star_count": 0,
"runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b",
"public_builds": true, "public_builds": true,
"shared_with_groups": [] "shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false
} }
``` ```
...@@ -819,7 +828,8 @@ Example response: ...@@ -819,7 +828,8 @@ Example response:
"star_count": 0, "star_count": 0,
"runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b",
"public_builds": true, "public_builds": true,
"shared_with_groups": [] "shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false
} }
``` ```
...@@ -918,7 +928,11 @@ Parameters: ...@@ -918,7 +928,11 @@ Parameters:
"push_events": true, "push_events": true,
"issues_events": true, "issues_events": true,
"merge_requests_events": true, "merge_requests_events": true,
"tag_push_events": true,
"note_events": true, "note_events": true,
"build_events": true,
"pipeline_events": true,
"wiki_page_events": true,
"enable_ssl_verification": true, "enable_ssl_verification": true,
"created_at": "2012-10-12T17:04:47Z" "created_at": "2012-10-12T17:04:47Z"
} }
...@@ -941,6 +955,9 @@ Parameters: ...@@ -941,6 +955,9 @@ Parameters:
- `merge_requests_events` - Trigger hook on merge_requests events - `merge_requests_events` - Trigger hook on merge_requests events
- `tag_push_events` - Trigger hook on push_tag events - `tag_push_events` - Trigger hook on push_tag events
- `note_events` - Trigger hook on note events - `note_events` - Trigger hook on note events
- `build_events` - Trigger hook on build events
- `pipeline_events` - Trigger hook on pipeline events
- `wiki_page_events` - Trigger hook on wiki page events
- `enable_ssl_verification` - Do SSL verification when triggering the hook - `enable_ssl_verification` - Do SSL verification when triggering the hook
### Edit project hook ### Edit project hook
...@@ -961,6 +978,9 @@ Parameters: ...@@ -961,6 +978,9 @@ Parameters:
- `merge_requests_events` - Trigger hook on merge_requests events - `merge_requests_events` - Trigger hook on merge_requests events
- `tag_push_events` - Trigger hook on push_tag events - `tag_push_events` - Trigger hook on push_tag events
- `note_events` - Trigger hook on note events - `note_events` - Trigger hook on note events
- `build_events` - Trigger hook on build events
- `pipeline_events` - Trigger hook on pipeline events
- `wiki_page_events` - Trigger hook on wiki page events
- `enable_ssl_verification` - Do SSL verification when triggering the hook - `enable_ssl_verification` - Do SSL verification when triggering the hook
### Delete project hook ### Delete project hook
......
...@@ -400,7 +400,7 @@ If you are not using Linux you may have to run `gmake` instead of ...@@ -400,7 +400,7 @@ If you are not using Linux you may have to run `gmake` instead of
cd /home/git cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse cd gitlab-workhorse
sudo -u git -H git checkout v0.7.10 sudo -u git -H git checkout v0.7.11
sudo -u git -H make sudo -u git -H make
### Initialize Database and Activate Advanced Features ### Initialize Database and Activate Advanced Features
......
...@@ -32,7 +32,7 @@ Please consider using a virtual machine to run GitLab. ...@@ -32,7 +32,7 @@ Please consider using a virtual machine to run GitLab.
## Ruby versions ## Ruby versions
GitLab requires Ruby (MRI) 2.1.x and currently does not work with versions 2.2 or 2.3. GitLab requires Ruby (MRI) 2.3. Support for Ruby versions below 2.3 (2.1, 2.2) will stop with GitLab 8.13.
You will have to use the standard MRI implementation of Ruby. You will have to use the standard MRI implementation of Ruby.
We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
......
# Account Security
- [Two-Factor Authentication](two_factor_authentication.md)
# Two-Factor Authentication
## Recovery options
If you lose your code generation device (such as your mobile phone) and you need
to disable two-factor authentication on your account, you have several options.
### Use a saved recovery code
When you enabled two-factor authentication for your account, a series of
recovery codes were generated. If you saved those codes somewhere safe, you
may use one to sign in.
First, enter your username/email and password on the GitLab sign in page. When
prompted for a two-factor code, enter one of the recovery codes you saved
previously.
> **Note:** Once a particular recovery code has been used, it cannot be used again.
You may still use the other saved recovery codes at a later time.
### Generate new recovery codes using SSH
It's not uncommon for users to forget to save the recovery codes when enabling
two-factor authentication. If you have an SSH key added to your GitLab account,
you can generate a new set of recovery codes using SSH.
Run `ssh git@gitlab.example.com 2fa_recovery_codes`. You will be prompted to
confirm that you wish to generate new codes. If you choose to continue, any
previously saved codes will be invalidated.
```bash
$ ssh git@gitlab.example.com 2fa_recovery_codes
Are you sure you want to generate new two-factor recovery codes?
Any existing recovery codes you saved will be invalidated. (yes/no)
yes
Your two-factor authentication recovery codes are:
119135e5a3ebce8e
11f6v2a498810dcd
3924c7ab2089c902
e79a3398bfe4f224
34bd7b74adbc8861
f061691d5107df1a
169bf32a18e63e7f
b510e7422e81c947
20dbed24c5e74663
df9d3b9403b9c9f0
During sign in, use one of the codes above when prompted for
your two-factor code. Then, visit your Profile Settings and add
a new device so you do not lose access to your account again.
```
Next, go to the GitLab sign in page and enter your username/email and password.
When prompted for a two-factor code, enter one of the recovery codes obtained
from the command line output.
> **Note:** After signing in, you should immediately visit your **Profile Settings
-> Account** to set up two-factor authentication with a new device.
### Ask a GitLab administrator to disable two-factor on your account
If the above two methods are not possible, you may ask a GitLab global
administrator to disable two-factor authentication for your account. Please
be aware that this will temporarily leave your account in a less secure state.
You should sign in and re-enable two-factor authentication as soon as possible
after the administrator disables it.
...@@ -15,6 +15,25 @@ Please note that you need to have builds configured to enable this feature. ...@@ -15,6 +15,25 @@ Please note that you need to have builds configured to enable this feature.
## Checkout merge requests locally ## Checkout merge requests locally
### By adding a git alias
Add the following alias to your `~/.gitconfig`:
```
[alias]
mr = !sh -c 'git fetch $1 merge-requests/$2/head:mr-$1-$2 && git checkout mr-$1-$2' -
```
Now you can check out a particular merge request from any repository and any remote, e.g. to check out a merge request number 5 as shown in GitLab from the `upstream` remote, do:
```
$ git mr upstream 5
```
This will fetch the merge request into a local `mr-upstream-5` branch and check it out.
### By modifying `.git/config` for a given repository
Locate the section for your GitLab remote in the `.git/config` file. It looks like this: Locate the section for your GitLab remote in the `.git/config` file. It looks like this:
``` ```
...@@ -34,7 +53,7 @@ It should look like this: ...@@ -34,7 +53,7 @@ It should look like this:
fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/* fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*
``` ```
Now you can fetch all the merge requests requests: Now you can fetch all the merge requests:
``` ```
$ git fetch origin $ git fetch origin
......
...@@ -89,7 +89,7 @@ Feature: Project Merge Requests ...@@ -89,7 +89,7 @@ Feature: Project Merge Requests
Then The list should be sorted by "Oldest updated" Then The list should be sorted by "Oldest updated"
@javascript @javascript
Scenario: Visiting Merge Requests from a differente Project after sorting Scenario: Visiting Merge Requests from a different Project after sorting
Given I visit project "Shop" merge requests page Given I visit project "Shop" merge requests page
And I sort the list by "Oldest updated" And I sort the list by "Oldest updated"
And I visit dashboard merge requests page And I visit dashboard merge requests page
......
...@@ -18,22 +18,14 @@ module API ...@@ -18,22 +18,14 @@ module API
end end
rescue_from :all do |exception| rescue_from :all do |exception|
# lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60 handle_api_exception(exception)
# why is this not wrapped in something reusable?
trace = exception.backtrace
message = "\n#{exception.class} (#{exception.message}):\n"
message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
message << " " << trace.join("\n ")
API.logger.add Logger::FATAL, message
rack_response({ 'message' => '500 Internal Server Error' }.to_json, 500)
end end
format :json format :json
content_type :txt, "text/plain" content_type :txt, "text/plain"
# Ensure the namespace is right, otherwise we might load Grape::API::Helpers # Ensure the namespace is right, otherwise we might load Grape::API::Helpers
helpers ::SentryHelper
helpers ::API::Helpers helpers ::API::Helpers
mount ::API::AccessRequests mount ::API::AccessRequests
......
...@@ -49,7 +49,7 @@ module API ...@@ -49,7 +49,7 @@ module API
class ProjectHook < Hook class ProjectHook < Hook
expose :project_id, :push_events expose :project_id, :push_events
expose :issues_events, :merge_requests_events, :tag_push_events expose :issues_events, :merge_requests_events, :tag_push_events
expose :note_events, :build_events, :pipeline_events expose :note_events, :build_events, :pipeline_events, :wiki_page_events
expose :enable_ssl_verification expose :enable_ssl_verification
end end
...@@ -96,6 +96,7 @@ module API ...@@ -96,6 +96,7 @@ module API
SharedGroup.represent(project.project_group_links.all, options) SharedGroup.represent(project.project_group_links.all, options)
end end
expose :repository_storage, if: lambda { |_project, options| options[:user].try(:admin?) } expose :repository_storage, if: lambda { |_project, options| options[:user].try(:admin?) }
expose :only_allow_merge_if_build_succeeds
end end
class Member < UserBasic class Member < UserBasic
...@@ -193,6 +194,10 @@ module API ...@@ -193,6 +194,10 @@ module API
# TODO (rspeicher): Deprecated; remove in 9.0 # TODO (rspeicher): Deprecated; remove in 9.0
expose(:expires_at) { |snippet| nil } expose(:expires_at) { |snippet| nil }
expose :web_url do |snippet, options|
Gitlab::UrlBuilder.build(snippet)
end
end end
class ProjectEntity < Grape::Entity class ProjectEntity < Grape::Entity
...@@ -222,6 +227,10 @@ module API ...@@ -222,6 +227,10 @@ module API
expose :user_notes_count expose :user_notes_count
expose :upvotes, :downvotes expose :upvotes, :downvotes
expose :due_date expose :due_date
expose :web_url do |issue, options|
Gitlab::UrlBuilder.build(issue)
end
end end
class ExternalIssue < Grape::Entity class ExternalIssue < Grape::Entity
...@@ -246,6 +255,10 @@ module API ...@@ -246,6 +255,10 @@ module API
expose :approvals_before_merge expose :approvals_before_merge
expose :should_remove_source_branch?, as: :should_remove_source_branch expose :should_remove_source_branch?, as: :should_remove_source_branch
expose :force_remove_source_branch?, as: :force_remove_source_branch expose :force_remove_source_branch?, as: :force_remove_source_branch
expose :web_url do |merge_request, options|
Gitlab::UrlBuilder.build(merge_request)
end
end end
class MergeRequestChanges < MergeRequest class MergeRequestChanges < MergeRequest
...@@ -558,13 +571,12 @@ module API ...@@ -558,13 +571,12 @@ module API
expose :duration expose :duration
end end
class Environment < Grape::Entity class EnvironmentBasic < Grape::Entity
expose :id, :name, :external_url expose :id, :name, :external_url
expose :project, using: Entities::Project
end end
class EnvironmentBasic < Grape::Entity class Environment < EnvironmentBasic
expose :id, :name, :external_url expose :project, using: Entities::Project
end end
class Deployment < Grape::Entity class Deployment < Grape::Entity
......
...@@ -290,6 +290,24 @@ module API ...@@ -290,6 +290,24 @@ module API
error!({ 'message' => message }, status) error!({ 'message' => message }, status)
end end
def handle_api_exception(exception)
if sentry_enabled? && report_exception?(exception)
define_params_for_grape_middleware
sentry_context
Raven.capture_exception(exception)
end
# lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
trace = exception.backtrace
message = "\n#{exception.class} (#{exception.message}):\n"
message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
message << " " << trace.join("\n ")
API.logger.add Logger::FATAL, message
rack_response({ 'message' => '500 Internal Server Error' }.to_json, 500)
end
# Projects helpers # Projects helpers
def filter_projects(projects) def filter_projects(projects)
...@@ -434,5 +452,19 @@ module API ...@@ -434,5 +452,19 @@ module API
Entities::Issue Entities::Issue
end end
end end
# The Grape Error Middleware only has access to env but no params. We workaround this by
# defining a method that returns the right value.
def define_params_for_grape_middleware
self.define_singleton_method(:params) { Rack::Request.new(env).params.symbolize_keys }
end
# We could get a Grape or a standard Ruby exception. We should only report anything that
# is clearly an error.
def report_exception?(exception)
return true unless exception.respond_to?(:status)
exception.status == 500
end
end end
end end
...@@ -113,6 +113,31 @@ module API ...@@ -113,6 +113,31 @@ module API
{} {}
end end
end end
post '/two_factor_recovery_codes' do
status 200
key = Key.find(params[:key_id])
user = key.user
# Make sure this isn't a deploy key
unless key.type.nil?
return { success: false, message: 'Deploy keys cannot be used to retrieve recovery codes' }
end
unless user.present?
return { success: false, message: 'Could not find a user for the given key' }
end
unless user.two_factor_enabled?
return { success: false, message: 'Two-factor authentication is not enabled for this user' }
end
codes = user.generate_otp_backup_codes!
user.save!
{ success: true, recovery_codes: codes }
end
end end
end end
end end
...@@ -46,6 +46,7 @@ module API ...@@ -46,6 +46,7 @@ module API
:note_events, :note_events,
:build_events, :build_events,
:pipeline_events, :pipeline_events,
:wiki_page_events,
:enable_ssl_verification :enable_ssl_verification
] ]
@hook = user_project.hooks.new(attrs) @hook = user_project.hooks.new(attrs)
...@@ -80,6 +81,7 @@ module API ...@@ -80,6 +81,7 @@ module API
:note_events, :note_events,
:build_events, :build_events,
:pipeline_events, :pipeline_events,
:wiki_page_events,
:enable_ssl_verification :enable_ssl_verification
] ]
......
...@@ -125,7 +125,8 @@ module API ...@@ -125,7 +125,8 @@ module API
:visibility_level, :visibility_level,
:import_url, :import_url,
:public_builds, :public_builds,
:repository_storage] :repository_storage,
:only_allow_merge_if_build_succeeds]
attrs = map_public_to_visibility_level(attrs) attrs = map_public_to_visibility_level(attrs)
@project = ::Projects::CreateService.new(current_user, attrs).execute @project = ::Projects::CreateService.new(current_user, attrs).execute
if @project.saved? if @project.saved?
...@@ -176,7 +177,8 @@ module API ...@@ -176,7 +177,8 @@ module API
:visibility_level, :visibility_level,
:import_url, :import_url,
:public_builds, :public_builds,
:repository_storage] :repository_storage,
:only_allow_merge_if_build_succeeds]
attrs = map_public_to_visibility_level(attrs) attrs = map_public_to_visibility_level(attrs)
@project = ::Projects::CreateService.new(user, attrs).execute @project = ::Projects::CreateService.new(user, attrs).execute
if @project.saved? if @project.saved?
...@@ -240,7 +242,8 @@ module API ...@@ -240,7 +242,8 @@ module API
:public, :public,
:visibility_level, :visibility_level,
:public_builds, :public_builds,
:repository_storage] :repository_storage,
:only_allow_merge_if_build_succeeds]
attrs = map_public_to_visibility_level(attrs) attrs = map_public_to_visibility_level(attrs)
authorize_admin_project authorize_admin_project
authorize! :rename_project, user_project if attrs[:name].present? authorize! :rename_project, user_project if attrs[:name].present?
......
...@@ -9,22 +9,14 @@ module Ci ...@@ -9,22 +9,14 @@ module Ci
end end
rescue_from :all do |exception| rescue_from :all do |exception|
# lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60 handle_api_exception(exception)
# why is this not wrapped in something reusable?
trace = exception.backtrace
message = "\n#{exception.class} (#{exception.message}):\n"
message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
message << " " << trace.join("\n ")
API.logger.add Logger::FATAL, message
rack_response({ 'message' => '500 Internal Server Error' }, 500)
end end
content_type :txt, 'text/plain' content_type :txt, 'text/plain'
content_type :json, 'application/json' content_type :json, 'application/json'
format :json format :json
helpers ::SentryHelper
helpers ::Ci::API::Helpers helpers ::Ci::API::Helpers
helpers ::API::Helpers helpers ::API::Helpers
helpers Gitlab::CurrentSettings helpers Gitlab::CurrentSettings
......
...@@ -94,9 +94,7 @@ module ExtractsPath ...@@ -94,9 +94,7 @@ module ExtractsPath
@options = params.select {|key, value| allowed_options.include?(key) && !value.blank? } @options = params.select {|key, value| allowed_options.include?(key) && !value.blank? }
@options = HashWithIndifferentAccess.new(@options) @options = HashWithIndifferentAccess.new(@options)
@id = params[:id] || params[:ref] @id = get_id
@id += "/" + params[:path] unless params[:path].blank?
@ref, @path = extract_ref(@id) @ref, @path = extract_ref(@id)
@repo = @project.repository @repo = @project.repository
if @options[:extended_sha1].blank? if @options[:extended_sha1].blank?
...@@ -118,4 +116,13 @@ module ExtractsPath ...@@ -118,4 +116,13 @@ module ExtractsPath
def tree def tree
@tree ||= @repo.tree(@commit.id, @path) @tree ||= @repo.tree(@commit.id, @path)
end end
private
# overriden in subclasses, do not remove
def get_id
id = params[:id] || params[:ref]
id += "/" + params[:path] unless params[:path].blank?
id
end
end end
...@@ -3,12 +3,13 @@ module Gitlab ...@@ -3,12 +3,13 @@ module Gitlab
class Importer class Importer
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
attr_reader :client, :project, :repo, :repo_url attr_reader :client, :errors, :project, :repo, :repo_url
def initialize(project) def initialize(project)
@project = project @project = project
@repo = project.import_source @repo = project.import_source
@repo_url = project.import_url @repo_url = project.import_url
@errors = []
if credentials if credentials
@client = Client.new(credentials[:user]) @client = Client.new(credentials[:user])
...@@ -18,8 +19,14 @@ module Gitlab ...@@ -18,8 +19,14 @@ module Gitlab
end end
def execute def execute
import_labels && import_milestones && import_issues && import_labels
import_pull_requests && import_wiki import_milestones
import_issues
import_pull_requests
import_wiki
handle_errors
true
end end
private private
...@@ -28,22 +35,37 @@ module Gitlab ...@@ -28,22 +35,37 @@ module Gitlab
@credentials ||= project.import_data.credentials if project.import_data @credentials ||= project.import_data.credentials if project.import_data
end end
def handle_errors
return unless errors.any?
project.update_column(:import_error, {
message: 'The remote data could not be fully imported.',
errors: errors
}.to_json)
end
def import_labels def import_labels
labels = client.labels(repo, per_page: 100) labels = client.labels(repo, per_page: 100)
labels.each { |raw| LabelFormatter.new(project, raw).create! }
true labels.each do |raw|
rescue ActiveRecord::RecordInvalid => e begin
raise Projects::ImportService::Error, e.message LabelFormatter.new(project, raw).create!
rescue => e
errors << { type: :label, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
end
end
end end
def import_milestones def import_milestones
milestones = client.milestones(repo, state: :all, per_page: 100) milestones = client.milestones(repo, state: :all, per_page: 100)
milestones.each { |raw| MilestoneFormatter.new(project, raw).create! }
true milestones.each do |raw|
rescue ActiveRecord::RecordInvalid => e begin
raise Projects::ImportService::Error, e.message MilestoneFormatter.new(project, raw).create!
rescue => e
errors << { type: :milestone, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
end
end
end end
def import_issues def import_issues
...@@ -53,15 +75,15 @@ module Gitlab ...@@ -53,15 +75,15 @@ module Gitlab
gh_issue = IssueFormatter.new(project, raw) gh_issue = IssueFormatter.new(project, raw)
if gh_issue.valid? if gh_issue.valid?
begin
issue = gh_issue.create! issue = gh_issue.create!
apply_labels(issue) apply_labels(issue)
import_comments(issue) if gh_issue.has_comments? import_comments(issue) if gh_issue.has_comments?
rescue => e
errors << { type: :issue, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
end
end end
end end
true
rescue ActiveRecord::RecordInvalid => e
raise Projects::ImportService::Error, e.message
end end
def import_pull_requests def import_pull_requests
...@@ -77,14 +99,12 @@ module Gitlab ...@@ -77,14 +99,12 @@ module Gitlab
apply_labels(merge_request) apply_labels(merge_request)
import_comments(merge_request) import_comments(merge_request)
import_comments_on_diff(merge_request) import_comments_on_diff(merge_request)
rescue ActiveRecord::RecordInvalid => e rescue => e
raise Projects::ImportService::Error, e.message errors << { type: :pull_request, url: Gitlab::UrlSanitizer.sanitize(pull_request.url), errors: e.message }
ensure ensure
clean_up_restored_branches(pull_request) clean_up_restored_branches(pull_request)
end end
end end
true
end end
def restore_source_branch(pull_request) def restore_source_branch(pull_request)
...@@ -98,7 +118,7 @@ module Gitlab ...@@ -98,7 +118,7 @@ module Gitlab
def remove_branch(name) def remove_branch(name)
project.repository.delete_branch(name) project.repository.delete_branch(name)
rescue Rugged::ReferenceError rescue Rugged::ReferenceError
nil errors << { type: :remove_branch, name: name }
end end
def clean_up_restored_branches(pull_request) def clean_up_restored_branches(pull_request)
...@@ -112,9 +132,10 @@ module Gitlab ...@@ -112,9 +132,10 @@ module Gitlab
issue = client.issue(repo, issuable.iid) issue = client.issue(repo, issuable.iid)
if issue.labels.count > 0 if issue.labels.count > 0
label_ids = issue.labels.map do |raw| label_ids = issue.labels
Label.find_by(LabelFormatter.new(project, raw).attributes).try(:id) .map { |raw| LabelFormatter.new(project, raw).attributes }
end .map { |attrs| Label.find_by(attrs).try(:id) }
.compact
issuable.update_attribute(:label_ids, label_ids) issuable.update_attribute(:label_ids, label_ids)
end end
...@@ -132,8 +153,12 @@ module Gitlab ...@@ -132,8 +153,12 @@ module Gitlab
def create_comments(issuable, comments) def create_comments(issuable, comments)
comments.each do |raw| comments.each do |raw|
begin
comment = CommentFormatter.new(project, raw) comment = CommentFormatter.new(project, raw)
issuable.notes.create!(comment.attributes) issuable.notes.create!(comment.attributes)
rescue => e
errors << { type: :comment, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
end
end end
end end
...@@ -143,16 +168,12 @@ module Gitlab ...@@ -143,16 +168,12 @@ module Gitlab
gitlab_shell.import_repository(project.repository_storage_path, wiki.path_with_namespace, wiki.import_url) gitlab_shell.import_repository(project.repository_storage_path, wiki.path_with_namespace, wiki.import_url)
project.update_attribute(:wiki_enabled, true) project.update_attribute(:wiki_enabled, true)
end end
true
rescue Gitlab::Shell::Error => e rescue Gitlab::Shell::Error => e
# GitHub error message when the wiki repo has not been created, # GitHub error message when the wiki repo has not been created,
# this means that repo has wiki enabled, but have no pages. So, # this means that repo has wiki enabled, but have no pages. So,
# we can skip the import. # we can skip the import.
if e.message !~ /repository not exported/ if e.message !~ /repository not exported/
raise Projects::ImportService::Error, e.message errors << { type: :wiki, errors: e.message }
else
true
end end
end end
end end
......
...@@ -56,6 +56,10 @@ module Gitlab ...@@ -56,6 +56,10 @@ module Gitlab
end end
end end
def url
raw_data.url
end
private private
def assigned? def assigned?
......
...@@ -22,6 +22,8 @@ module Gitlab ...@@ -22,6 +22,8 @@ module Gitlab
note_url note_url
when WikiPage when WikiPage
wiki_page_url wiki_page_url
when ProjectSnippet
project_snippet_url(object)
else else
raise NotImplementedError.new("No URL builder defined for #{object.class}") raise NotImplementedError.new("No URL builder defined for #{object.class}")
end end
......
...@@ -265,6 +265,56 @@ describe AutocompleteController do ...@@ -265,6 +265,56 @@ describe AutocompleteController do
end end
end end
context 'authorized projects apply limit' do
before do
authorized_project2 = create(:project)
authorized_project3 = create(:project)
authorized_project.team << [user, :master]
authorized_project2.team << [user, :master]
authorized_project3.team << [user, :master]
stub_const 'MoveToProjectFinder::PAGE_SIZE', 2
end
describe 'GET #projects with project ID' do
before do
get(:projects, project_id: project.id)
end
let(:body) { JSON.parse(response.body) }
it do
expect(body).to be_kind_of(Array)
expect(body.size).to eq 3 # Of a total of 4
end
end
end
context 'authorized projects with offset' do
before do
authorized_project2 = create(:project)
authorized_project3 = create(:project)
authorized_project.team << [user, :master]
authorized_project2.team << [user, :master]
authorized_project3.team << [user, :master]
end
describe 'GET #projects with project ID and offset_id' do
before do
get(:projects, project_id: project.id, offset_id: authorized_project.id)
end
let(:body) { JSON.parse(response.body) }
it do
expect(body.detect { |item| item['id'] == 0 }).to be_nil # 'No project' is not there
expect(body.detect { |item| item['id'] == authorized_project.id }).to be_nil # Offset project is not there either
end
end
end
context 'authorized projects without admin_issue ability' do context 'authorized projects without admin_issue ability' do
before(:each) do before(:each) do
authorized_project.team << [user, :guest] authorized_project.team << [user, :guest]
......
...@@ -14,6 +14,7 @@ FactoryGirl.define do ...@@ -14,6 +14,7 @@ FactoryGirl.define do
note_events true note_events true
build_events true build_events true
pipeline_events true pipeline_events true
wiki_page_events true
end end
end end
end end
...@@ -122,6 +122,17 @@ describe 'Issues', feature: true do ...@@ -122,6 +122,17 @@ describe 'Issues', feature: true do
expect(page).to have_content date.to_s(:medium) expect(page).to have_content date.to_s(:medium)
end end
end end
it 'warns about version conflict' do
issue.update(title: "New title")
fill_in 'issue_title', with: 'bug 345'
fill_in 'issue_description', with: 'bug description'
click_button 'Save changes'
expect(page).to have_content 'Someone edited the issue the same time you did'
end
end end
end end
......
require 'spec_helper'
feature 'Diff notes', js: true, feature: true do
include WaitForAjax
before do
login_as :admin
@merge_request = create(:merge_request)
@project = @merge_request.source_project
end
context 'merge request diffs' do
let(:comment_button_class) { '.add-diff-note' }
let(:notes_holder_input_class) { 'js-temp-notes-holder' }
let(:notes_holder_input_xpath) { './following-sibling::*[contains(concat(" ", @class, " "), " notes_holder ")]' }
let(:test_note_comment) { 'this is a test note!' }
context 'when hovering over a parallel view diff file' do
before(:each) do
visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'parallel')
end
context 'with an old line on the left and no line on the right' do
it 'should allow commenting on the left side' do
should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..'), 'left')
end
it 'should not allow commenting on the right side' do
should_not_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..'), 'right')
end
end
context 'with no line on the left and a new line on the right' do
it 'should not allow commenting on the left side' do
should_not_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'left')
end
it 'should allow commenting on the right side' do
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'right')
end
end
context 'with an old line on the left and a new line on the right' do
it 'should allow commenting on the left side' do
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'left')
end
it 'should allow commenting on the right side' do
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'right')
end
end
context 'with an unchanged line on the left and an unchanged line on the right' do
it 'should allow commenting on the left side' do
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'left')
end
it 'should allow commenting on the right side' do
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'right')
end
end
context 'with a match line' do
it 'should not allow commenting on the left side' do
should_not_allow_commenting(find('.match', match: :first).find(:xpath, '..'), 'left')
end
it 'should not allow commenting on the right side' do
should_not_allow_commenting(find('.match', match: :first).find(:xpath, '..'), 'right')
end
end
context 'with an unfolded line' do
before(:each) do
find('.js-unfold', match: :first).click
wait_for_ajax
end
# The first `.js-unfold` unfolds upwards, therefore the first
# `.line_holder` will be an unfolded line.
let(:line_holder) { first('.line_holder[id="1"]') }
it 'should not allow commenting on the left side' do
should_not_allow_commenting(line_holder, 'left')
end
it 'should not allow commenting on the right side' do
should_not_allow_commenting(line_holder, 'right')
end
end
end
context 'when hovering over an inline view diff file' do
before do
visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline')
end
context 'with a new line' do
it 'should allow commenting' do
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
end
end
context 'with an old line' do
it 'should allow commenting' do
should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'))
end
end
context 'with an unchanged line' do
it 'should allow commenting' do
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]'))
end
end
context 'with a match line' do
it 'should not allow commenting' do
should_not_allow_commenting(find('.match', match: :first))
end
end
context 'with an unfolded line' do
before(:each) do
find('.js-unfold', match: :first).click
wait_for_ajax
end
# The first `.js-unfold` unfolds upwards, therefore the first
# `.line_holder` will be an unfolded line.
let(:line_holder) { first('.line_holder[id="1"]') }
it 'should not allow commenting' do
should_not_allow_commenting line_holder
end
end
context 'when hovering over a diff discussion' do
before do
visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline')
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]'))
visit namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
end
it 'should not allow commenting' do
should_not_allow_commenting(find('.line_holder', match: :first))
end
end
end
def should_allow_commenting(line_holder, diff_side = nil)
line = get_line_components(line_holder, diff_side)
line[:content].hover
expect(line[:num]).to have_css comment_button_class
comment_on_line(line_holder, line)
assert_comment_persistence(line_holder)
end
def should_not_allow_commenting(line_holder, diff_side = nil)
line = get_line_components(line_holder, diff_side)
line[:content].hover
expect(line[:num]).not_to have_css comment_button_class
end
def get_line_components(line_holder, diff_side = nil)
if diff_side.nil?
get_inline_line_components(line_holder)
else
get_parallel_line_components(line_holder, diff_side)
end
end
def get_inline_line_components(line_holder)
{ content: line_holder.find('.line_content', match: :first), num: line_holder.find('.diff-line-num', match: :first) }
end
def get_parallel_line_components(line_holder, diff_side = nil)
side_index = diff_side == 'left' ? 0 : 1
# Wait for `.line_content`
line_holder.find('.line_content', match: :first)
# Wait for `.diff-line-num`
line_holder.find('.diff-line-num', match: :first)
{ content: line_holder.all('.line_content')[side_index], num: line_holder.all('.diff-line-num')[side_index] }
end
def comment_on_line(line_holder, line)
line[:num].find(comment_button_class).trigger 'click'
line_holder.find(:xpath, notes_holder_input_xpath)
notes_holder_input = line_holder.find(:xpath, notes_holder_input_xpath)
expect(notes_holder_input[:class]).to include(notes_holder_input_class)
notes_holder_input.fill_in 'note[note]', with: test_note_comment
click_button 'Comment'
wait_for_ajax
end
def assert_comment_persistence(line_holder)
expect(line_holder).to have_xpath notes_holder_input_xpath
notes_holder_saved = line_holder.find(:xpath, notes_holder_input_xpath)
expect(notes_holder_saved[:class]).not_to include(notes_holder_input_class)
expect(notes_holder_saved).to have_content test_note_comment
end
end
end
...@@ -15,6 +15,17 @@ feature 'Edit Merge Request', feature: true do ...@@ -15,6 +15,17 @@ feature 'Edit Merge Request', feature: true do
it 'has class js-quick-submit in form' do it 'has class js-quick-submit in form' do
expect(page).to have_selector('.js-quick-submit') expect(page).to have_selector('.js-quick-submit')
end end
it 'warns about version conflict' do
merge_request.update(title: "New title")
fill_in 'merge_request_title', with: 'bug 345'
fill_in 'merge_request_description', with: 'bug description'
click_button 'Save changes'
expect(page).to have_content 'Someone edited the merge request the same time you did'
end
end end
context 'saving the MR that needs approvals' do context 'saving the MR that needs approvals' do
......
require 'spec_helper'
feature 'Delete branch', feature: true, js: true do
include WaitForAjax
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
project.team << [user, :master]
login_as user
visit namespace_project_branches_path(project.namespace, project)
end
it 'destroys tooltip' do
first('.remove-row').hover
expect(page).to have_selector('.tooltip')
first('.remove-row').click
wait_for_ajax
expect(page).not_to have_selector('.tooltip')
end
end
...@@ -20,7 +20,7 @@ describe 'Branches', feature: true do ...@@ -20,7 +20,7 @@ describe 'Branches', feature: true do
describe 'Find branches' do describe 'Find branches' do
it 'shows filtered branches', js: true do it 'shows filtered branches', js: true do
visit namespace_project_branches_path(project.namespace, project, project.id) visit namespace_project_branches_path(project.namespace, project)
fill_in 'branch-search', with: 'fix' fill_in 'branch-search', with: 'fix'
find('#branch-search').native.send_keys(:enter) find('#branch-search').native.send_keys(:enter)
......
require 'spec_helper' require 'spec_helper'
include WaitForAjax
describe 'Cherry-pick Commits' do describe 'Cherry-pick Commits' do
let(:project) { create(:project) } let(:project) { create(:project) }
...@@ -8,12 +9,11 @@ describe 'Cherry-pick Commits' do ...@@ -8,12 +9,11 @@ describe 'Cherry-pick Commits' do
before do before do
login_as :user login_as :user
project.team << [@user, :master] project.team << [@user, :master]
visit namespace_project_commits_path(project.namespace, project, project.repository.root_ref, { limit: 5 }) visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
end end
context "I cherry-pick a commit" do context "I cherry-pick a commit" do
it do it do
visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
find("a[href='#modal-cherry-pick-commit']").click find("a[href='#modal-cherry-pick-commit']").click
expect(page).not_to have_content('v1.0.0') # Only branches, not tags expect(page).not_to have_content('v1.0.0') # Only branches, not tags
page.within('#modal-cherry-pick-commit') do page.within('#modal-cherry-pick-commit') do
...@@ -26,7 +26,6 @@ describe 'Cherry-pick Commits' do ...@@ -26,7 +26,6 @@ describe 'Cherry-pick Commits' do
context "I cherry-pick a merge commit" do context "I cherry-pick a merge commit" do
it do it do
visit namespace_project_commit_path(project.namespace, project, master_pickable_merge.id)
find("a[href='#modal-cherry-pick-commit']").click find("a[href='#modal-cherry-pick-commit']").click
page.within('#modal-cherry-pick-commit') do page.within('#modal-cherry-pick-commit') do
uncheck 'create_merge_request' uncheck 'create_merge_request'
...@@ -38,7 +37,6 @@ describe 'Cherry-pick Commits' do ...@@ -38,7 +37,6 @@ describe 'Cherry-pick Commits' do
context "I cherry-pick a commit that was previously cherry-picked" do context "I cherry-pick a commit that was previously cherry-picked" do
it do it do
visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
find("a[href='#modal-cherry-pick-commit']").click find("a[href='#modal-cherry-pick-commit']").click
page.within('#modal-cherry-pick-commit') do page.within('#modal-cherry-pick-commit') do
uncheck 'create_merge_request' uncheck 'create_merge_request'
...@@ -56,7 +54,6 @@ describe 'Cherry-pick Commits' do ...@@ -56,7 +54,6 @@ describe 'Cherry-pick Commits' do
context "I cherry-pick a commit in a new merge request" do context "I cherry-pick a commit in a new merge request" do
it do it do
visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
find("a[href='#modal-cherry-pick-commit']").click find("a[href='#modal-cherry-pick-commit']").click
page.within('#modal-cherry-pick-commit') do page.within('#modal-cherry-pick-commit') do
click_button 'Cherry-pick' click_button 'Cherry-pick'
...@@ -64,4 +61,28 @@ describe 'Cherry-pick Commits' do ...@@ -64,4 +61,28 @@ describe 'Cherry-pick Commits' do
expect(page).to have_content('The commit has been successfully cherry-picked. You can now submit a merge request to get this change into the original branch.') expect(page).to have_content('The commit has been successfully cherry-picked. You can now submit a merge request to get this change into the original branch.')
end end
end end
context "I cherry-pick a commit from a different branch", js: true do
it do
find('.commit-action-buttons a.dropdown-toggle').click
find(:css, "a[href='#modal-cherry-pick-commit']").click
page.within('#modal-cherry-pick-commit') do
click_button 'master'
end
wait_for_ajax
page.within('#modal-cherry-pick-commit .dropdown-menu .dropdown-content') do
click_link 'feature'
end
page.within('#modal-cherry-pick-commit') do
uncheck 'create_merge_request'
click_button 'Cherry-pick'
end
expect(page).to have_content('The commit has been successfully cherry-picked.')
end
end
end end
...@@ -51,6 +51,28 @@ describe MoveToProjectFinder do ...@@ -51,6 +51,28 @@ describe MoveToProjectFinder do
expect(subject.execute(project).to_a).to eq([other_reporter_project]) expect(subject.execute(project).to_a).to eq([other_reporter_project])
end end
it 'returns a page of projects ordered by id in descending order' do
stub_const 'MoveToProjectFinder::PAGE_SIZE', 2
reporter_project.team << [user, :reporter]
developer_project.team << [user, :developer]
master_project.team << [user, :master]
expect(subject.execute(project).to_a).to eq([master_project, developer_project])
end
it 'returns projects after the given offset id' do
stub_const 'MoveToProjectFinder::PAGE_SIZE', 2
reporter_project.team << [user, :reporter]
developer_project.team << [user, :developer]
master_project.team << [user, :master]
expect(subject.execute(project, search: nil, offset_id: master_project.id).to_a).to eq([developer_project, reporter_project])
expect(subject.execute(project, search: nil, offset_id: developer_project.id).to_a).to eq([reporter_project])
expect(subject.execute(project, search: nil, offset_id: reporter_project.id).to_a).to be_empty
end
end end
context 'search' do context 'search' do
......
...@@ -145,4 +145,42 @@ describe ProjectsHelper do ...@@ -145,4 +145,42 @@ describe ProjectsHelper do
expect(sanitize_repo_path(project, import_error)).to eq('Could not clone [REPOS PATH]/namespace/test.git') expect(sanitize_repo_path(project, import_error)).to eq('Could not clone [REPOS PATH]/namespace/test.git')
end end
end end
describe '#last_push_event' do
let(:user) { double(:user, fork_of: nil) }
let(:project) { double(:project, id: 1) }
before do
allow(helper).to receive(:current_user).and_return(user)
helper.instance_variable_set(:@project, project)
end
context 'when there is no current_user' do
let(:user) { nil }
it 'returns nil' do
expect(helper.last_push_event).to eq(nil)
end
end
it 'returns recent push on the current project' do
event = double(:event)
expect(user).to receive(:recent_push).with([project.id]).and_return(event)
expect(helper.last_push_event).to eq(event)
end
context 'when current user has a fork of the current project' do
let(:fork) { double(:fork, id: 2) }
it 'returns recent push considering fork events' do
expect(user).to receive(:fork_of).with(project).and_return(fork)
event_on_fork = double(:event)
expect(user).to receive(:recent_push).with([project.id, fork.id]).and_return(event_on_fork)
expect(helper.last_push_event).to eq(event_on_fork)
end
end
end
end end
This diff is collapsed.
...@@ -51,6 +51,26 @@ describe ExtractsPath, lib: true do ...@@ -51,6 +51,26 @@ describe ExtractsPath, lib: true do
expect(@path).to eq(params[:path]) expect(@path).to eq(params[:path])
end end
end end
context 'subclass overrides get_id' do
it 'uses ref returned by get_id' do
allow_any_instance_of(self.class).to receive(:get_id){ '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' }
assign_ref_vars
expect(@id).to eq(get_id)
end
end
context 'path contains space' do
let(:params) { { path: 'with space', ref: '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' } }
it 'is not converted to %20 in @path' do
assign_ref_vars
expect(@path).to eq(params[:path])
end
end
end end
describe '#extract_ref' do describe '#extract_ref' do
......
require 'spec_helper'
describe Gitlab::GithubImport::Importer, lib: true do
describe '#execute' do
context 'when an error occurs' do
let(:project) { create(:project, import_url: 'https://github.com/octocat/Hello-World.git', wiki_enabled: false) }
let(:octocat) { double(id: 123456, login: 'octocat') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
let(:repository) { double(id: 1, fork: false) }
let(:source_sha) { create(:commit, project: project).id }
let(:source_branch) { double(ref: 'feature', repo: repository, sha: source_sha) }
let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id }
let(:target_branch) { double(ref: 'master', repo: repository, sha: target_sha) }
let(:label) do
double(
name: 'Bug',
color: 'ff0000',
url: 'https://api.github.com/repos/octocat/Hello-World/labels/bug'
)
end
let(:milestone) do
double(
number: 1347,
state: 'open',
title: '1.0',
description: 'Version 1.0',
due_on: nil,
created_at: created_at,
updated_at: updated_at,
closed_at: nil,
url: 'https://api.github.com/repos/octocat/Hello-World/milestones/1'
)
end
let(:issue1) do
double(
number: 1347,
milestone: nil,
state: 'open',
title: 'Found a bug',
body: "I'm having a problem with this.",
assignee: nil,
user: octocat,
comments: 0,
pull_request: nil,
created_at: created_at,
updated_at: updated_at,
closed_at: nil,
url: 'https://api.github.com/repos/octocat/Hello-World/issues/1347'
)
end
let(:issue2) do
double(
number: 1348,
milestone: nil,
state: 'open',
title: nil,
body: "I'm having a problem with this.",
assignee: nil,
user: octocat,
comments: 0,
pull_request: nil,
created_at: created_at,
updated_at: updated_at,
closed_at: nil,
url: 'https://api.github.com/repos/octocat/Hello-World/issues/1348'
)
end
let(:pull_request) do
double(
number: 1347,
milestone: nil,
state: 'open',
title: 'New feature',
body: 'Please pull these awesome changes',
head: source_branch,
base: target_branch,
assignee: nil,
user: octocat,
created_at: created_at,
updated_at: updated_at,
closed_at: nil,
merged_at: nil,
url: 'https://api.github.com/repos/octocat/Hello-World/pulls/1347'
)
end
before do
allow(project).to receive(:import_data).and_return(double.as_null_object)
allow_any_instance_of(Octokit::Client).to receive(:rate_limit!).and_raise(Octokit::NotFound)
allow_any_instance_of(Octokit::Client).to receive(:labels).and_return([label, label])
allow_any_instance_of(Octokit::Client).to receive(:milestones).and_return([milestone, milestone])
allow_any_instance_of(Octokit::Client).to receive(:issues).and_return([issue1, issue2])
allow_any_instance_of(Octokit::Client).to receive(:pull_requests).and_return([pull_request, pull_request])
allow_any_instance_of(Octokit::Client).to receive(:last_response).and_return(double(rels: { next: nil }))
allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_raise(Gitlab::Shell::Error)
end
it 'returns true' do
expect(described_class.new(project).execute).to eq true
end
it 'does not raise an error' do
expect { described_class.new(project).execute }.not_to raise_error
end
it 'stores error messages' do
error = {
message: 'The remote data could not be fully imported.',
errors: [
{ type: :label, url: "https://api.github.com/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title has already been taken" },
{ type: :milestone, url: "https://api.github.com/repos/octocat/Hello-World/milestones/1", errors: "Validation failed: Title has already been taken" },
{ type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1347", errors: "Invalid Repository. Use user/repo format." },
{ type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank, Title is too short (minimum is 0 characters)" },
{ type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Invalid Repository. Use user/repo format." },
{ type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Validation failed: Validate branches Cannot Create: This merge request already exists: [\"New feature\"]" },
{ type: :wiki, errors: "Gitlab::Shell::Error" }
]
}
described_class.new(project).execute
expect(project.import_error).to eq error.to_json
end
end
end
end
...@@ -27,7 +27,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -27,7 +27,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
created_at: created_at, created_at: created_at,
updated_at: updated_at, updated_at: updated_at,
closed_at: nil, closed_at: nil,
merged_at: nil merged_at: nil,
url: 'https://api.github.com/repos/octocat/Hello-World/pulls/1347'
} }
end end
...@@ -229,4 +230,12 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -229,4 +230,12 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end end
end end
end end
describe '#url' do
let(:raw_data) { double(base_data) }
it 'return raw url' do
expect(pull_request.url).to eq 'https://api.github.com/repos/octocat/Hello-World/pulls/1347'
end
end
end end
...@@ -192,13 +192,13 @@ describe Ability, lib: true do ...@@ -192,13 +192,13 @@ describe Ability, lib: true do
results = described_class.project_abilities(admin, project) results = described_class.project_abilities(admin, project)
expect(results.count).to eq(74) expect(results.count).to eq(68)
end end
it 'returns permissions for an owner' do it 'returns permissions for an owner' do
results = described_class.project_abilities(project.owner, project) results = described_class.project_abilities(project.owner, project)
expect(results.count).to eq(73) expect(results.count).to eq(68)
end end
it 'returns permissions for a master' do it 'returns permissions for a master' do
...@@ -206,7 +206,7 @@ describe Ability, lib: true do ...@@ -206,7 +206,7 @@ describe Ability, lib: true do
results = described_class.project_abilities(user, project) results = described_class.project_abilities(user, project)
expect(results.count).to eq(64) expect(results.count).to eq(60)
end end
it 'returns permissions for a developer' do it 'returns permissions for a developer' do
......
...@@ -955,6 +955,16 @@ describe User, models: true do ...@@ -955,6 +955,16 @@ describe User, models: true do
expect(subject.recent_push).to eq(nil) expect(subject.recent_push).to eq(nil)
end end
it "includes push events on any of the provided projects" do
expect(subject.recent_push(project1)).to eq(nil)
expect(subject.recent_push(project2)).to eq(push_event)
push_data1 = Gitlab::DataBuilder::Push.build_sample(project1, subject)
push_event1 = create(:event, action: Event::PUSHED, project: project1, target: project1, author: subject, data: push_data1)
expect(subject.recent_push([project1, project2])).to eq(push_event1) # Newest
end
end end
describe '#authorized_groups' do describe '#authorized_groups' do
......
...@@ -3,6 +3,7 @@ require 'spec_helper' ...@@ -3,6 +3,7 @@ require 'spec_helper'
describe API::Helpers, api: true do describe API::Helpers, api: true do
include API::Helpers include API::Helpers
include ApiHelpers include ApiHelpers
include SentryHelper
let(:user) { create(:user) } let(:user) { create(:user) }
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
...@@ -234,4 +235,30 @@ describe API::Helpers, api: true do ...@@ -234,4 +235,30 @@ describe API::Helpers, api: true do
expect(to_boolean(nil)).to be_nil expect(to_boolean(nil)).to be_nil
end end
end end
describe '.handle_api_exception' do
before do
allow_any_instance_of(self.class).to receive(:sentry_enabled?).and_return(true)
allow_any_instance_of(self.class).to receive(:rack_response)
end
it 'does not report a MethodNotAllowed exception to Sentry' do
exception = Grape::Exceptions::MethodNotAllowed.new({ 'X-GitLab-Test' => '1' })
allow(exception).to receive(:backtrace).and_return(caller)
expect(Raven).not_to receive(:capture_exception).with(exception)
handle_api_exception(exception)
end
it 'does report RuntimeError to Sentry' do
exception = RuntimeError.new('test error')
allow(exception).to receive(:backtrace).and_return(caller)
expect_any_instance_of(self.class).to receive(:sentry_context)
expect(Raven).to receive(:capture_exception).with(exception)
handle_api_exception(exception)
end
end
end end
...@@ -38,6 +38,68 @@ describe API::API, api: true do ...@@ -38,6 +38,68 @@ describe API::API, api: true do
end end
end end
describe 'GET /internal/two_factor_recovery_codes' do
it 'returns an error message when the key does not exist' do
post api('/internal/two_factor_recovery_codes'),
secret_token: secret_token,
key_id: 12345
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Not found')
end
it 'returns an error message when the key is a deploy key' do
deploy_key = create(:deploy_key)
post api('/internal/two_factor_recovery_codes'),
secret_token: secret_token,
key_id: deploy_key.id
expect(json_response['success']).to be_falsey
expect(json_response['message']).to eq('Deploy keys cannot be used to retrieve recovery codes')
end
it 'returns an error message when the user does not exist' do
key_without_user = create(:key, user: nil)
post api('/internal/two_factor_recovery_codes'),
secret_token: secret_token,
key_id: key_without_user.id
expect(json_response['success']).to be_falsey
expect(json_response['message']).to eq('Could not find a user for the given key')
expect(json_response['recovery_codes']).to be_nil
end
context 'when two-factor is enabled' do
it 'returns new recovery codes when the user exists' do
allow_any_instance_of(User).to receive(:two_factor_enabled?).and_return(true)
allow_any_instance_of(User)
.to receive(:generate_otp_backup_codes!).and_return(%w(119135e5a3ebce8e 34bd7b74adbc8861))
post api('/internal/two_factor_recovery_codes'),
secret_token: secret_token,
key_id: key.id
expect(json_response['success']).to be_truthy
expect(json_response['recovery_codes']).to match_array(%w(119135e5a3ebce8e 34bd7b74adbc8861))
end
end
context 'when two-factor is not enabled' do
it 'returns an error message' do
allow_any_instance_of(User).to receive(:two_factor_enabled?).and_return(false)
post api('/internal/two_factor_recovery_codes'),
secret_token: secret_token,
key_id: key.id
expect(json_response['success']).to be_falsey
expect(json_response['recovery_codes']).to be_nil
end
end
end
describe "GET /internal/discover" do describe "GET /internal/discover" do
it do it do
get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) get(api("/internal/discover"), key_id: key.id, secret_token: secret_token)
......
...@@ -61,6 +61,7 @@ describe API::API, api: true do ...@@ -61,6 +61,7 @@ describe API::API, api: true do
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(issue.title) expect(json_response.first['title']).to eq(issue.title)
expect(json_response.last).to have_key('web_url')
end end
it "adds pagination headers and keep query params" do it "adds pagination headers and keep query params" do
......
...@@ -33,6 +33,7 @@ describe API::API, api: true do ...@@ -33,6 +33,7 @@ describe API::API, api: true do
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.length).to eq(3) expect(json_response.length).to eq(3)
expect(json_response.last['title']).to eq(merge_request.title) expect(json_response.last['title']).to eq(merge_request.title)
expect(json_response.last).to have_key('web_url')
end end
it "returns an array of all merge_requests" do it "returns an array of all merge_requests" do
......
...@@ -34,6 +34,7 @@ describe API::API, 'ProjectHooks', api: true do ...@@ -34,6 +34,7 @@ describe API::API, 'ProjectHooks', api: true do
expect(json_response.first['note_events']).to eq(true) expect(json_response.first['note_events']).to eq(true)
expect(json_response.first['build_events']).to eq(true) expect(json_response.first['build_events']).to eq(true)
expect(json_response.first['pipeline_events']).to eq(true) expect(json_response.first['pipeline_events']).to eq(true)
expect(json_response.first['wiki_page_events']).to eq(true)
expect(json_response.first['enable_ssl_verification']).to eq(true) expect(json_response.first['enable_ssl_verification']).to eq(true)
end end
end end
...@@ -57,6 +58,9 @@ describe API::API, 'ProjectHooks', api: true do ...@@ -57,6 +58,9 @@ describe API::API, 'ProjectHooks', api: true do
expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events) expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
expect(json_response['tag_push_events']).to eq(hook.tag_push_events) expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
expect(json_response['note_events']).to eq(hook.note_events) expect(json_response['note_events']).to eq(hook.note_events)
expect(json_response['build_events']).to eq(hook.build_events)
expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification) expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
end end
...@@ -93,6 +97,7 @@ describe API::API, 'ProjectHooks', api: true do ...@@ -93,6 +97,7 @@ describe API::API, 'ProjectHooks', api: true do
expect(json_response['note_events']).to eq(false) expect(json_response['note_events']).to eq(false)
expect(json_response['build_events']).to eq(false) expect(json_response['build_events']).to eq(false)
expect(json_response['pipeline_events']).to eq(false) expect(json_response['pipeline_events']).to eq(false)
expect(json_response['wiki_page_events']).to eq(false)
expect(json_response['enable_ssl_verification']).to eq(true) expect(json_response['enable_ssl_verification']).to eq(true)
end end
...@@ -118,6 +123,9 @@ describe API::API, 'ProjectHooks', api: true do ...@@ -118,6 +123,9 @@ describe API::API, 'ProjectHooks', api: true do
expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events) expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
expect(json_response['tag_push_events']).to eq(hook.tag_push_events) expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
expect(json_response['note_events']).to eq(hook.note_events) expect(json_response['note_events']).to eq(hook.note_events)
expect(json_response['build_events']).to eq(hook.build_events)
expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification) expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
end end
......
...@@ -30,6 +30,7 @@ describe API::API, api: true do ...@@ -30,6 +30,7 @@ describe API::API, api: true do
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response.size).to eq(3) expect(json_response.size).to eq(3)
expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id) expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id)
expect(json_response.last).to have_key('web_url')
end end
it 'hides private snippets from regular user' do it 'hides private snippets from regular user' do
......
...@@ -224,7 +224,8 @@ describe API::API, api: true do ...@@ -224,7 +224,8 @@ describe API::API, api: true do
description: FFaker::Lorem.sentence, description: FFaker::Lorem.sentence,
issues_enabled: false, issues_enabled: false,
merge_requests_enabled: false, merge_requests_enabled: false,
wiki_enabled: false wiki_enabled: false,
only_allow_merge_if_build_succeeds: false
}) })
post api('/projects', user), project post api('/projects', user), project
...@@ -276,6 +277,18 @@ describe API::API, api: true do ...@@ -276,6 +277,18 @@ describe API::API, api: true do
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
end end
it 'sets a project as allowing merge even if build fails' do
project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false })
post api('/projects', user), project
expect(json_response['only_allow_merge_if_build_succeeds']).to be_falsey
end
it 'sets a project as allowing merge only if build succeeds' do
project = attributes_for(:project, { only_allow_merge_if_build_succeeds: true })
post api('/projects', user), project
expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy
end
context 'when a visibility level is restricted' do context 'when a visibility level is restricted' do
before do before do
@project = attributes_for(:project, { public: true }) @project = attributes_for(:project, { public: true })
...@@ -384,6 +397,18 @@ describe API::API, api: true do ...@@ -384,6 +397,18 @@ describe API::API, api: true do
expect(json_response['public']).to be_falsey expect(json_response['public']).to be_falsey
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
end end
it 'sets a project as allowing merge even if build fails' do
project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false })
post api("/projects/user/#{user.id}", admin), project
expect(json_response['only_allow_merge_if_build_succeeds']).to be_falsey
end
it 'sets a project as allowing merge only if build succeeds' do
project = attributes_for(:project, { only_allow_merge_if_build_succeeds: true })
post api("/projects/user/#{user.id}", admin), project
expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy
end
end end
describe "POST /projects/:id/uploads" do describe "POST /projects/:id/uploads" do
...@@ -444,6 +469,7 @@ describe API::API, api: true do ...@@ -444,6 +469,7 @@ describe API::API, api: true do
expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id) expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name) expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name)
expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
expect(json_response['only_allow_merge_if_build_succeeds']).to eq(project.only_allow_merge_if_build_succeeds)
end end
it 'returns a project by path name' do it 'returns a project by path name' do
......
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment