Commit 78757a0d authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'master' into 22604-manual-actions

* master: (28 commits)
  Merge branch 'jej-23867-use-mr-finder-instead-of-access-check' ...
  Merge branch 'html-safe-diff-line-content' into 'security'
  Merge branch 'rs-filter-authentication_token' into 'security'
  Merge branch 'destroy-session' into 'security'
  Updating reference to database password
  Update CHANGELOG.md for 8.13.9
  Update CHANGELOG.md for 8.14.4
  Don't check if stage name doesn't exist
  Various small emoji positioning adjustments
  Add nested groups support on data level
  Update factory_girl_rails to 4.7.0
  Correct previous stable branch used in 8.14 to 8.15 update guide
  Enable display of admonition icons in Asciidoc.
  add link_to_if helper on target_branch link on Merge Request ...
  Fade out should be white instead of gray
  Do nothing if file is undefined
  fix: 24982- Remove'Signed in successfully' message After this ...
  adds impersonator variable and makes sudo usage overall more clear
  Reenables /user API request to return private-token if user is ...
  Fixed timeago re-rendering every element
  ...
parents b64cf840 1413c94a
...@@ -8,7 +8,8 @@ ...@@ -8,7 +8,8 @@
"globals": { "globals": {
"_": false, "_": false,
"gl": false, "gl": false,
"gon": false "gon": false,
"localStorage": false
}, },
"plugins": [ "plugins": [
"filenames" "filenames"
......
...@@ -2,6 +2,19 @@ ...@@ -2,6 +2,19 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 8.14.4 (2016-12-08)
- Fix diff view permalink highlighting. !7090
- Fix pipeline author for Slack and use pipeline id for pipeline link. !7506
- Fix compatibility with Internet Explorer 11 for merge requests. !7525 (Steffen Rauh)
- Reenables /user API request to return private-token if user is admin and request is made with sudo. !7615
- Fix Cicking on tabs on pipeline page should set URL. !7709
- Authorize users into imported GitLab project.
- Destroy a user's session when they delete their own account.
- Don't accidentally mark unsafe diff lines as HTML safe.
- Replace MR access checks with use of MergeRequestsFinder.
- Remove visible content caching.
## 8.14.3 (2016-12-02) ## 8.14.3 (2016-12-02)
- Pass commit data to ProcessCommitWorker to reduce Git overhead. !7744 - Pass commit data to ProcessCommitWorker to reduce Git overhead. !7744
...@@ -251,6 +264,11 @@ entry. ...@@ -251,6 +264,11 @@ entry.
- Fix "Without projects" filter. !6611 (Ben Bodenmiller) - Fix "Without projects" filter. !6611 (Ben Bodenmiller)
- Fix 404 when visit /projects page - Fix 404 when visit /projects page
## 8.13.9 (2016-12-08)
- Reenables /user API request to return private-token if user is admin and request is made with sudo. !7615
- Replace MR access checks with use of MergeRequestsFinder.
## 8.13.8 (2016-12-02) ## 8.13.8 (2016-12-02)
- Pass tag SHA to post-receive hook when tag is created via UI. !7700 - Pass tag SHA to post-receive hook when tag is created via UI. !7700
......
...@@ -271,7 +271,7 @@ group :development, :test do ...@@ -271,7 +271,7 @@ group :development, :test do
gem 'fuubar', '~> 2.0.0' gem 'fuubar', '~> 2.0.0'
gem 'database_cleaner', '~> 1.5.0' gem 'database_cleaner', '~> 1.5.0'
gem 'factory_girl_rails', '~> 4.6.0' gem 'factory_girl_rails', '~> 4.7.0'
gem 'rspec-rails', '~> 3.5.0' gem 'rspec-rails', '~> 3.5.0'
gem 'rspec-retry', '~> 0.4.5' gem 'rspec-retry', '~> 0.4.5'
gem 'spinach-rails', '~> 0.2.1' gem 'spinach-rails', '~> 0.2.1'
......
...@@ -177,10 +177,10 @@ GEM ...@@ -177,10 +177,10 @@ GEM
excon (0.52.0) excon (0.52.0)
execjs (2.6.0) execjs (2.6.0)
expression_parser (0.9.0) expression_parser (0.9.0)
factory_girl (4.5.0) factory_girl (4.7.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
factory_girl_rails (4.6.0) factory_girl_rails (4.7.0)
factory_girl (~> 4.5.0) factory_girl (~> 4.7.0)
railties (>= 3.0.0) railties (>= 3.0.0)
faraday (0.9.2) faraday (0.9.2)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
...@@ -819,7 +819,7 @@ DEPENDENCIES ...@@ -819,7 +819,7 @@ DEPENDENCIES
dropzonejs-rails (~> 0.7.1) dropzonejs-rails (~> 0.7.1)
email_reply_parser (~> 0.5.8) email_reply_parser (~> 0.5.8)
email_spec (~> 1.6.0) email_spec (~> 1.6.0)
factory_girl_rails (~> 4.6.0) factory_girl_rails (~> 4.7.0)
ffaker (~> 2.0.0) ffaker (~> 2.0.0)
flay (~> 2.6.1) flay (~> 2.6.1)
fog-aws (~> 0.9) fog-aws (~> 0.9)
......
...@@ -70,6 +70,8 @@ ...@@ -70,6 +70,8 @@
// e.g. // e.g.
// Api.gitignoreText item.name, @requestFileSuccess.bind(@) // Api.gitignoreText item.name, @requestFileSuccess.bind(@)
requestFileSuccess(file, { skipFocus } = {}) { requestFileSuccess(file, { skipFocus } = {}) {
if (!file) return;
const oldValue = this.editor.getValue(); const oldValue = this.editor.getValue();
let newValue = file.content; let newValue = file.content;
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
switch (page) { switch (page) {
case 'sessions:new': case 'sessions:new':
new UsernameValidator(); new UsernameValidator();
new ActiveTabMemoizer();
break; break;
case 'projects:boards:show': case 'projects:boards:show':
case 'projects:boards:index': case 'projects:boards:index':
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
setTimeago = true; setTimeago = true;
} }
$timeagoEls.each(function() { $timeagoEls.filter(':not([data-timeago-rendered])').each(function() {
var $el = $(this); var $el = $(this);
$el.attr('title', gl.utils.formatDate($el.attr('datetime'))); $el.attr('title', gl.utils.formatDate($el.attr('datetime')));
...@@ -39,6 +39,8 @@ ...@@ -39,6 +39,8 @@
template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>' template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
}); });
} }
$el.attr('data-timeago-rendered', true);
gl.utils.renderTimeago($el); gl.utils.renderTimeago($el);
}); });
}; };
......
/* eslint no-param-reassign: ["error", { "props": false }]*/
/* eslint no-new: "off" */
((global) => {
/**
* Memorize the last selected tab after reloading a page.
* Does that setting the current selected tab in the localStorage
*/
class ActiveTabMemoizer {
constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
this.currentTabKey = currentTabKey;
this.tabSelector = tabSelector;
this.bootstrap();
}
bootstrap() {
const tabs = document.querySelectorAll(this.tabSelector);
if (tabs.length > 0) {
tabs[0].addEventListener('click', (e) => {
if (e.target && e.target.nodeName === 'A') {
const anchorName = e.target.getAttribute('href');
this.saveData(anchorName);
}
});
}
this.showTab();
}
showTab() {
const anchorName = this.readData();
if (anchorName) {
const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`);
if (tab) {
tab.click();
}
}
}
saveData(val) {
localStorage.setItem(this.currentTabKey, val);
}
readData() {
return localStorage.getItem(this.currentTabKey);
}
}
global.ActiveTabMemoizer = ActiveTabMemoizer;
})(window);
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
@import "framework/animations.scss"; @import "framework/animations.scss";
@import "framework/avatar.scss"; @import "framework/avatar.scss";
@import "framework/asciidoctor.scss";
@import "framework/blocks.scss"; @import "framework/blocks.scss";
@import "framework/buttons.scss"; @import "framework/buttons.scss";
@import "framework/calendar.scss"; @import "framework/calendar.scss";
......
.admonitionblock td.icon {
width: 1%;
[class^="fa icon-"] {
@extend .fa-2x;
}
.icon-note {
@extend .fa-thumb-tack;
}
.icon-tip {
@extend .fa-lightbulb-o;
}
.icon-warning {
@extend .fa-exclamation-triangle;
}
.icon-caution {
@extend .fa-fire;
}
.icon-important {
@extend .fa-exclamation-circle;
}
}
.awards { .awards {
.emoji-icon { .emoji-icon {
width: 19px; width: 20px;
height: 19px; height: 20px;
} }
} }
...@@ -136,5 +136,6 @@ ...@@ -136,5 +136,6 @@
.award-control-icon { .award-control-icon {
color: $award-emoji-new-btn-icon-color; color: $award-emoji-new-btn-icon-color;
margin-top: 1px;
} }
} }
...@@ -255,6 +255,7 @@ img.emoji { ...@@ -255,6 +255,7 @@ img.emoji {
height: 20px; height: 20px;
vertical-align: top; vertical-align: top;
width: 20px; width: 20px;
margin-top: 1px;
} }
.chart { .chart {
......
...@@ -124,7 +124,7 @@ ul.notes { ...@@ -124,7 +124,7 @@ ul.notes {
position: absolute; position: absolute;
left: 0; left: 0;
bottom: 0; bottom: 0;
background: linear-gradient(rgba($gray-light, 0.1) -100px, $white-light 100%); background: linear-gradient(rgba($white-light, 0.1) -100px, $white-light 100%);
} }
&.hide-shade { &.hide-shade {
...@@ -413,7 +413,6 @@ ul.notes { ...@@ -413,7 +413,6 @@ ul.notes {
.fa { .fa {
color: $notes-action-color; color: $notes-action-color;
position: relative; position: relative;
top: 1px;
font-size: 17px; font-size: 17px;
} }
......
...@@ -56,7 +56,7 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -56,7 +56,7 @@ class Admin::GroupsController < Admin::ApplicationController
private private
def group def group
@group ||= Group.find_by(path: params[:id]) @group ||= Group.find_by_full_path(params[:id])
end end
def group_params def group_params
......
...@@ -81,10 +81,8 @@ module CreatesCommit ...@@ -81,10 +81,8 @@ module CreatesCommit
def merge_request_exists? def merge_request_exists?
return @merge_request if defined?(@merge_request) return @merge_request if defined?(@merge_request)
@merge_request = @mr_target_project.merge_requests.opened.find_by( @merge_request = MergeRequestsFinder.new(current_user, project_id: @mr_target_project.id).execute.opened.
source_branch: @mr_source_branch, find_by(source_branch: @mr_source_branch, target_branch: @mr_target_branch)
target_branch: @mr_target_branch
)
end end
def different_project? def different_project?
......
...@@ -9,7 +9,7 @@ class Groups::ApplicationController < ApplicationController ...@@ -9,7 +9,7 @@ class Groups::ApplicationController < ApplicationController
def group def group
unless @group unless @group
id = params[:group_id] || params[:id] id = params[:group_id] || params[:id]
@group = Group.find_by(path: id) @group = Group.find_by_full_path(id)
unless @group && can?(current_user, :read_group, @group) unless @group && can?(current_user, :read_group, @group)
@group = nil @group = nil
......
...@@ -65,7 +65,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -65,7 +65,7 @@ class Projects::CommitController < Projects::ApplicationController
return render_404 if @target_branch.blank? return render_404 if @target_branch.blank?
create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title} has been successfully reverted.", create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully reverted.",
success_path: successful_change_path, failure_path: failed_change_path) success_path: successful_change_path, failure_path: failed_change_path)
end end
...@@ -74,26 +74,24 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -74,26 +74,24 @@ class Projects::CommitController < Projects::ApplicationController
return render_404 if @target_branch.blank? return render_404 if @target_branch.blank?
create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title} has been successfully cherry-picked.", create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully cherry-picked.",
success_path: successful_change_path, failure_path: failed_change_path) success_path: successful_change_path, failure_path: failed_change_path)
end end
private private
def successful_change_path def successful_change_path
return referenced_merge_request_url if @commit.merged_merge_request referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @target_branch)
namespace_project_commits_url(@project.namespace, @project, @target_branch)
end end
def failed_change_path def failed_change_path
return referenced_merge_request_url if @commit.merged_merge_request referenced_merge_request_url || namespace_project_commit_url(@project.namespace, @project, params[:id])
namespace_project_commit_url(@project.namespace, @project, params[:id])
end end
def referenced_merge_request_url def referenced_merge_request_url
namespace_project_merge_request_url(@project.namespace, @project, @commit.merged_merge_request) if merge_request = @commit.merged_merge_request(current_user)
namespace_project_merge_request_url(@project.namespace, @project, merge_request)
end
end end
def commit def commit
......
...@@ -21,7 +21,7 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -21,7 +21,7 @@ class Projects::CommitsController < Projects::ApplicationController
@note_counts = project.notes.where(commit_id: @commits.map(&:id)). @note_counts = project.notes.where(commit_id: @commits.map(&:id)).
group(:commit_id).count group(:commit_id).count
@merge_request = @project.merge_requests.opened. @merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.
find_by(source_project: @project, source_branch: @ref, target_branch: @repository.root_ref) find_by(source_project: @project, source_branch: @ref, target_branch: @repository.root_ref)
respond_to do |format| respond_to do |format|
......
...@@ -53,7 +53,7 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -53,7 +53,7 @@ class Projects::CompareController < Projects::ApplicationController
end end
def merge_request def merge_request
@merge_request ||= @project.merge_requests.opened. @merge_request ||= MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.
find_by(source_project: @project, source_branch: @head_ref, target_branch: @start_ref) find_by(source_project: @project, source_branch: @head_ref, target_branch: @start_ref)
end end
end end
...@@ -24,7 +24,7 @@ class Projects::DiscussionsController < Projects::ApplicationController ...@@ -24,7 +24,7 @@ class Projects::DiscussionsController < Projects::ApplicationController
private private
def merge_request def merge_request
@merge_request ||= @project.merge_requests.find_by!(iid: params[:merge_request_id]) @merge_request ||= MergeRequestsFinder.new(current_user, project_id: @project.id).find_by!(iid: params[:merge_request_id])
end end
def discussion def discussion
......
...@@ -10,14 +10,38 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -10,14 +10,38 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@project_members = @project.project_members @project_members = @project.project_members
@project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project)
group = @project.group
if group
# We need `.where.not(user_id: nil)` here otherwise when a group has an
# invitee, it would make the following query return 0 rows since a NULL
# user_id would be present in the subquery
# See http://stackoverflow.com/questions/129077/not-in-clause-and-null-values
# FIXME: This whole logic should be moved to a finder!
non_null_user_ids = @project_members.where.not(user_id: nil).select(:user_id)
group_members = group.group_members.where.not(user_id: non_null_user_ids)
group_members = group_members.non_invite unless can?(current_user, :admin_group, @group)
end
if params[:search].present? if params[:search].present?
users = @project.users.search(params[:search]).to_a user_ids = @project.users.search(params[:search]).select(:id)
@project_members = @project_members.where(user_id: users) @project_members = @project_members.where(user_id: user_ids)
if group_members
user_ids = group.users.search(params[:search]).select(:id)
group_members = group_members.where(user_id: user_ids)
end
@group_links = @project.project_group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id)) @group_links = @project.project_group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
end end
@project_members = @project_members.order(access_level: :desc).page(params[:page]) member_ids = @project_members.pluck(:id)
if group_members
member_ids += group_members.pluck(:id)
end
@project_members = Member.where(id: member_ids).order(access_level: :desc).page(params[:page])
@requesters = AccessRequestsFinder.new(@project).execute(current_user) @requesters = AccessRequestsFinder.new(@project).execute(current_user)
......
...@@ -18,7 +18,7 @@ class Projects::TodosController < Projects::ApplicationController ...@@ -18,7 +18,7 @@ class Projects::TodosController < Projects::ApplicationController
when "issue" when "issue"
IssuesFinder.new(current_user, project_id: @project.id).find(params[:issuable_id]) IssuesFinder.new(current_user, project_id: @project.id).find(params[:issuable_id])
when "merge_request" when "merge_request"
@project.merge_requests.find(params[:issuable_id]) MergeRequestsFinder.new(current_user, project_id: @project.id).find(params[:issuable_id])
end end
end end
end end
......
...@@ -27,7 +27,10 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -27,7 +27,10 @@ class RegistrationsController < Devise::RegistrationsController
DeleteUserService.new(current_user).execute(current_user) DeleteUserService.new(current_user).execute(current_user)
respond_to do |format| respond_to do |format|
format.html { redirect_to new_user_session_path, notice: "Account successfully removed." } format.html do
session.try(:destroy)
redirect_to new_user_session_path, notice: "Account successfully removed."
end
end end
end end
......
...@@ -31,6 +31,8 @@ class SessionsController < Devise::SessionsController ...@@ -31,6 +31,8 @@ class SessionsController < Devise::SessionsController
resource.update_attributes(reset_password_token: nil, resource.update_attributes(reset_password_token: nil,
reset_password_sent_at: nil) reset_password_sent_at: nil)
end end
# hide the signed-in notification
flash[:notice] = nil
log_audit_event(current_user, with: authentication_method) log_audit_event(current_user, with: authentication_method)
end end
end end
......
...@@ -77,6 +77,10 @@ class IssuableFinder ...@@ -77,6 +77,10 @@ class IssuableFinder
counts counts
end end
def find_by!(*params)
execute.find_by!(*params)
end
def group def group
return @group if defined?(@group) return @group if defined?(@group)
......
...@@ -14,7 +14,7 @@ class NotesFinder ...@@ -14,7 +14,7 @@ class NotesFinder
when "issue" when "issue"
IssuesFinder.new(current_user, project_id: project.id).find(target_id).notes.inc_author IssuesFinder.new(current_user, project_id: project.id).find(target_id).notes.inc_author
when "merge_request" when "merge_request"
project.merge_requests.find(target_id).mr_and_commit_notes.inc_author MergeRequestsFinder.new(current_user, project_id: project.id).find(target_id).mr_and_commit_notes.inc_author
when "snippet", "project_snippet" when "snippet", "project_snippet"
project.snippets.find(target_id).notes project.snippets.find(target_id).notes
else else
......
...@@ -130,7 +130,7 @@ module CommitsHelper ...@@ -130,7 +130,7 @@ module CommitsHelper
def revert_commit_link(commit, continue_to_path, btn_class: nil, has_tooltip: true) def revert_commit_link(commit, continue_to_path, btn_class: nil, has_tooltip: true)
return unless current_user return unless current_user
tooltip = "Revert this #{commit.change_type_title} in a new merge request" if has_tooltip tooltip = "Revert this #{commit.change_type_title(current_user)} in a new merge request" if has_tooltip
if can_collaborate_with_project? if can_collaborate_with_project?
btn_class = "btn btn-warning btn-#{btn_class}" unless btn_class.nil? btn_class = "btn btn-warning btn-#{btn_class}" unless btn_class.nil?
...@@ -154,7 +154,7 @@ module CommitsHelper ...@@ -154,7 +154,7 @@ module CommitsHelper
def cherry_pick_commit_link(commit, continue_to_path, btn_class: nil, has_tooltip: true) def cherry_pick_commit_link(commit, continue_to_path, btn_class: nil, has_tooltip: true)
return unless current_user return unless current_user
tooltip = "Cherry-pick this #{commit.change_type_title} in a new merge request" tooltip = "Cherry-pick this #{commit.change_type_title(current_user)} in a new merge request"
if can_collaborate_with_project? if can_collaborate_with_project?
btn_class = "btn btn-default btn-#{btn_class}" unless btn_class.nil? btn_class = "btn btn-default btn-#{btn_class}" unless btn_class.nil?
......
...@@ -55,7 +55,9 @@ module DiffHelper ...@@ -55,7 +55,9 @@ module DiffHelper
if line.blank? if line.blank?
"&nbsp;".html_safe "&nbsp;".html_safe
else else
line.sub(/^[\-+ ]/, '').html_safe # We can't use `sub` because the HTML-safeness of `line` will not survive.
line[0] = '' if line.start_with?('+', '-', ' ')
line
end end
end end
......
...@@ -5,7 +5,7 @@ module GroupsHelper ...@@ -5,7 +5,7 @@ module GroupsHelper
def group_icon(group) def group_icon(group)
if group.is_a?(String) if group.is_a?(String)
group = Group.find_by(path: group) group = Group.find_by_full_path(group)
end end
group.try(:avatar_url) || image_path('no_group_avatar.png') group.try(:avatar_url) || image_path('no_group_avatar.png')
......
...@@ -245,44 +245,47 @@ class Commit ...@@ -245,44 +245,47 @@ class Commit
project.repository.next_branch("cherry-pick-#{short_id}", mild: true) project.repository.next_branch("cherry-pick-#{short_id}", mild: true)
end end
def revert_description def revert_description(user)
if merged_merge_request if merged_merge_request?(user)
"This reverts merge request #{merged_merge_request.to_reference}" "This reverts merge request #{merged_merge_request(user).to_reference}"
else else
"This reverts commit #{sha}" "This reverts commit #{sha}"
end end
end end
def revert_message def revert_message(user)
%Q{Revert "#{title.strip}"\n\n#{revert_description}} %Q{Revert "#{title.strip}"\n\n#{revert_description(user)}}
end end
def reverts_commit?(commit) def reverts_commit?(commit, user)
description? && description.include?(commit.revert_description) description? && description.include?(commit.revert_description(user))
end end
def merge_commit? def merge_commit?
parents.size > 1 parents.size > 1
end end
def merged_merge_request def merged_merge_request(current_user)
return @merged_merge_request if defined?(@merged_merge_request) # Memoize with per-user access check
@merged_merge_request_hash ||= Hash.new do |hash, user|
hash[user] = merged_merge_request_no_cache(user)
end
@merged_merge_request = project.merge_requests.find_by(merge_commit_sha: id) if merge_commit? @merged_merge_request_hash[current_user]
end end
def has_been_reverted?(current_user = nil, noteable = self) def has_been_reverted?(current_user, noteable = self)
ext = all_references(current_user) ext = all_references(current_user)
noteable.notes_with_associations.system.each do |note| noteable.notes_with_associations.system.each do |note|
note.all_references(current_user, extractor: ext) note.all_references(current_user, extractor: ext)
end end
ext.commits.any? { |commit_ref| commit_ref.reverts_commit?(self) } ext.commits.any? { |commit_ref| commit_ref.reverts_commit?(self, current_user) }
end end
def change_type_title def change_type_title(user)
merged_merge_request ? 'merge request' : 'commit' merged_merge_request?(user) ? 'merge request' : 'commit'
end end
# Get the URI type of the given path # Get the URI type of the given path
...@@ -350,4 +353,12 @@ class Commit ...@@ -350,4 +353,12 @@ class Commit
changes changes
end end
def merged_merge_request?(user)
!!merged_merge_request(user)
end
def merged_merge_request_no_cache(user)
MergeRequestsFinder.new(user, project_id: project.id).find_by(merge_commit_sha: id) if merge_commit?
end
end end
module Milestoneish module Milestoneish
def closed_items_count(user = nil) def closed_items_count(user)
issues_visible_to_user(user).closed.size + merge_requests.closed_and_merged.size issues_visible_to_user(user).closed.size + merge_requests.closed_and_merged.size
end end
def total_items_count(user = nil) def total_items_count(user)
issues_visible_to_user(user).size + merge_requests.size issues_visible_to_user(user).size + merge_requests.size
end end
def complete?(user = nil) def complete?(user)
total_items_count(user) > 0 && total_items_count(user) == closed_items_count(user) total_items_count(user) > 0 && total_items_count(user) == closed_items_count(user)
end end
def percent_complete(user = nil) def percent_complete(user)
((closed_items_count(user) * 100) / total_items_count(user)).abs ((closed_items_count(user) * 100) / total_items_count(user)).abs
rescue ZeroDivisionError rescue ZeroDivisionError
0 0
...@@ -29,7 +29,7 @@ module Milestoneish ...@@ -29,7 +29,7 @@ module Milestoneish
(Date.today - start_date).to_i (Date.today - start_date).to_i
end end
def issues_visible_to_user(user = nil) def issues_visible_to_user(user)
issues.visible_to_user(user) issues.visible_to_user(user)
end end
......
# Store object full path in separate table for easy lookup and uniq validation
# Object must have path db field and respond to full_path and full_path_changed? methods.
module Routable
extend ActiveSupport::Concern
included do
has_one :route, as: :source, autosave: true, dependent: :destroy
validates_associated :route
before_validation :update_route_path, if: :full_path_changed?
end
class_methods do
# Finds a single object by full path match in routes table.
#
# Usage:
#
# Klass.find_by_full_path('gitlab-org/gitlab-ce')
#
# Returns a single object, or nil.
def find_by_full_path(path)
# On MySQL we want to ensure the ORDER BY uses a case-sensitive match so
# any literal matches come first, for this we have to use "BINARY".
# Without this there's still no guarantee in what order MySQL will return
# rows.
binary = Gitlab::Database.mysql? ? 'BINARY' : ''
order_sql = "(CASE WHEN #{binary} routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)"
where_paths_in([path]).reorder(order_sql).take
end
# Builds a relation to find multiple objects by their full paths.
#
# Usage:
#
# Klass.where_paths_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee})
#
# Returns an ActiveRecord::Relation.
def where_paths_in(paths)
wheres = []
cast_lower = Gitlab::Database.postgresql?
paths.each do |path|
path = connection.quote(path)
where = "(routes.path = #{path})"
if cast_lower
where = "(#{where} OR (LOWER(routes.path) = LOWER(#{path})))"
end
wheres << where
end
if wheres.empty?
none
else
joins(:route).where(wheres.join(' OR '))
end
end
end
private
def update_route_path
route || build_route(source: self)
route.path = full_path
end
end
...@@ -805,7 +805,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -805,7 +805,7 @@ class MergeRequest < ActiveRecord::Base
@merge_commit ||= project.commit(merge_commit_sha) if merge_commit_sha @merge_commit ||= project.commit(merge_commit_sha) if merge_commit_sha
end end
def can_be_reverted?(current_user = nil) def can_be_reverted?(current_user)
merge_commit && !merge_commit.has_been_reverted?(current_user, self) merge_commit && !merge_commit.has_been_reverted?(current_user, self)
end end
......
...@@ -4,12 +4,16 @@ class Namespace < ActiveRecord::Base ...@@ -4,12 +4,16 @@ class Namespace < ActiveRecord::Base
include CacheMarkdownField include CacheMarkdownField
include Sortable include Sortable
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
include Routable
cache_markdown_field :description, pipeline: :description cache_markdown_field :description, pipeline: :description
has_many :projects, dependent: :destroy has_many :projects, dependent: :destroy
belongs_to :owner, class_name: "User" belongs_to :owner, class_name: "User"
belongs_to :parent, class_name: "Namespace"
has_many :children, class_name: "Namespace", foreign_key: :parent_id
validates :owner, presence: true, unless: ->(n) { n.type == "Group" } validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
validates :name, validates :name,
presence: true, presence: true,
...@@ -86,7 +90,7 @@ class Namespace < ActiveRecord::Base ...@@ -86,7 +90,7 @@ class Namespace < ActiveRecord::Base
end end
def to_param def to_param
path full_path
end end
def human_name def human_name
...@@ -150,6 +154,14 @@ class Namespace < ActiveRecord::Base ...@@ -150,6 +154,14 @@ class Namespace < ActiveRecord::Base
Gitlab.config.lfs.enabled Gitlab.config.lfs.enabled
end end
def full_path
if parent
parent.full_path + '/' + path
else
path
end
end
private private
def repository_storage_paths def repository_storage_paths
...@@ -185,4 +197,8 @@ class Namespace < ActiveRecord::Base ...@@ -185,4 +197,8 @@ class Namespace < ActiveRecord::Base
where(projects: { namespace_id: id }). where(projects: { namespace_id: id }).
find_each(&:refresh_members_authorized_projects) find_each(&:refresh_members_authorized_projects)
end end
def full_path_changed?
path_changed? || parent_id_changed?
end
end end
...@@ -14,6 +14,7 @@ class Project < ActiveRecord::Base ...@@ -14,6 +14,7 @@ class Project < ActiveRecord::Base
include TokenAuthenticatable include TokenAuthenticatable
include ProjectFeaturesCompatibility include ProjectFeaturesCompatibility
include SelectForProjectAuthorization include SelectForProjectAuthorization
include Routable
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
...@@ -324,87 +325,6 @@ class Project < ActiveRecord::Base ...@@ -324,87 +325,6 @@ class Project < ActiveRecord::Base
non_archived.where(table[:name].matches(pattern)) non_archived.where(table[:name].matches(pattern))
end end
# Finds a single project for the given path.
#
# path - The full project path (including namespace path).
#
# Returns a Project, or nil if no project could be found.
def find_with_namespace(path)
namespace_path, project_path = path.split('/', 2)
return unless namespace_path && project_path
namespace_path = connection.quote(namespace_path)
project_path = connection.quote(project_path)
# On MySQL we want to ensure the ORDER BY uses a case-sensitive match so
# any literal matches come first, for this we have to use "BINARY".
# Without this there's still no guarantee in what order MySQL will return
# rows.
binary = Gitlab::Database.mysql? ? 'BINARY' : ''
order_sql = "(CASE WHEN #{binary} namespaces.path = #{namespace_path} " \
"AND #{binary} projects.path = #{project_path} THEN 0 ELSE 1 END)"
where_paths_in([path]).reorder(order_sql).take
end
# Builds a relation to find multiple projects by their full paths.
#
# Each path must be in the following format:
#
# namespace_path/project_path
#
# For example:
#
# gitlab-org/gitlab-ce
#
# Usage:
#
# Project.where_paths_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee})
#
# This would return the projects with the full paths matching the values
# given.
#
# paths - An Array of full paths (namespace path + project path) for which
# to find the projects.
#
# Returns an ActiveRecord::Relation.
def where_paths_in(paths)
wheres = []
cast_lower = Gitlab::Database.postgresql?
paths.each do |path|
namespace_path, project_path = path.split('/', 2)
next unless namespace_path && project_path
namespace_path = connection.quote(namespace_path)
project_path = connection.quote(project_path)
where = "(namespaces.path = #{namespace_path}
AND projects.path = #{project_path})"
if cast_lower
where = "(
#{where}
OR (
LOWER(namespaces.path) = LOWER(#{namespace_path})
AND LOWER(projects.path) = LOWER(#{project_path})
)
)"
end
wheres << where
end
if wheres.empty?
none
else
joins(:namespace).where(wheres.join(' OR '))
end
end
def visibility_levels def visibility_levels
Gitlab::VisibilityLevel.options Gitlab::VisibilityLevel.options
end end
...@@ -440,6 +360,10 @@ class Project < ActiveRecord::Base ...@@ -440,6 +360,10 @@ class Project < ActiveRecord::Base
def group_ids def group_ids
joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id) joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)
end end
# Add alias for Routable method for compatibility with old code.
# In future all calls `find_with_namespace` should be replaced with `find_by_full_path`
alias_method :find_with_namespace, :find_by_full_path
end end
def lfs_enabled? def lfs_enabled?
...@@ -879,13 +803,14 @@ class Project < ActiveRecord::Base ...@@ -879,13 +803,14 @@ class Project < ActiveRecord::Base
end end
alias_method :human_name, :name_with_namespace alias_method :human_name, :name_with_namespace
def path_with_namespace def full_path
if namespace if namespace && path
namespace.path + '/' + path namespace.full_path + '/' + path
else else
path path
end end
end end
alias_method :path_with_namespace, :full_path
def execute_hooks(data, hooks_scope = :push_hooks) def execute_hooks(data, hooks_scope = :push_hooks)
hooks.send(hooks_scope).each do |hook| hooks.send(hooks_scope).each do |hook|
...@@ -1373,4 +1298,8 @@ class Project < ActiveRecord::Base ...@@ -1373,4 +1298,8 @@ class Project < ActiveRecord::Base
def validate_board_limit(board) def validate_board_limit(board)
raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS
end end
def full_path_changed?
path_changed? || namespace_id_changed?
end
end end
...@@ -950,7 +950,7 @@ class Repository ...@@ -950,7 +950,7 @@ class Repository
update_branch_with_hooks(user, base_branch) do update_branch_with_hooks(user, base_branch) do
committer = user_to_committer(user) committer = user_to_committer(user)
source_sha = Rugged::Commit.create(rugged, source_sha = Rugged::Commit.create(rugged,
message: commit.revert_message, message: commit.revert_message(user),
author: committer, author: committer,
committer: committer, committer: committer,
tree: revert_tree_id, tree: revert_tree_id,
......
class Route < ActiveRecord::Base
belongs_to :source, polymorphic: true
validates :source, presence: true
validates :path,
length: { within: 1..255 },
presence: true,
uniqueness: { case_sensitive: false }
after_update :rename_children, if: :path_changed?
def rename_children
# We update each row separately because MySQL does not have regexp_replace.
# rubocop:disable Rails/FindEach
Route.where('path LIKE ?', "#{path_was}%").each do |route|
# Note that update column skips validation and callbacks.
# We need this to avoid recursive call of rename_children method
route.update_column(:path, route.path.sub(path_was, path))
end
end
end
...@@ -34,7 +34,7 @@ module Commits ...@@ -34,7 +34,7 @@ module Commits
repository.public_send(action, current_user, @commit, into, tree_id) repository.public_send(action, current_user, @commit, into, tree_id)
success success
else else
error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title} automatically. error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title(current_user)} automatically.
It may have already been #{action.to_s.dasherize}, or a more recent commit may have updated some of its content." It may have already been #{action.to_s.dasherize}, or a more recent commit may have updated some of its content."
raise ChangeError, error_msg raise ChangeError, error_msg
end end
......
...@@ -20,6 +20,10 @@ class DestroyGroupService ...@@ -20,6 +20,10 @@ class DestroyGroupService
::Projects::DestroyService.new(project, current_user, skip_repo: true).execute ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
end end
group.children.each do |group|
DestroyGroupService.new(group, current_user).async_execute
end
group.really_destroy! group.really_destroy!
end end
end end
- page_title "Sign in" - page_title "Sign in"
%div %div
- if form_based_providers.any? - if form_based_providers.any?
= render 'devise/shared/tabs_ldap' = render 'devise/shared/tabs_ldap'
......
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
= link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
%span %span
Merge Requests Merge Requests
%span.badge.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count) %span.badge.count.merge_counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count)
- if project_nav_tab? :wiki - if project_nav_tab? :wiki
= nav_link(controller: :wikis) do = nav_link(controller: :wikis) do
......
- form = local_assigns.fetch(:form)
.form-group
.checkbox.builds-feature
= form.label :only_allow_merge_if_build_succeeds do
= form.check_box :only_allow_merge_if_build_succeeds
%strong Only allow merge requests to be merged if the build succeeds
%br
%span.descr
Builds need to be configured to enable this feature.
= link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
.checkbox
= form.label :only_allow_merge_if_all_discussions_are_resolved do
= form.check_box :only_allow_merge_if_all_discussions_are_resolved
%strong Only allow merge requests to be merged if all discussions are resolved
.merge-requests-feature - form = local_assigns.fetch(:form)
%fieldset.builds-feature
%fieldset.features.merge-requests-feature.append-bottom-default
%hr %hr
%h5.prepend-top-0 %h5.prepend-top-0
Merge Requests Merge Requests
.form-group
.checkbox = render 'projects/merge_request_merge_settings', form: form
= f.label :only_allow_merge_if_build_succeeds do
= f.check_box :only_allow_merge_if_build_succeeds
%strong Only allow merge requests to be merged if the build succeeds
%br
%span.descr
Builds need to be configured to enable this feature.
= link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
.checkbox
= f.label :only_allow_merge_if_all_discussions_are_resolved do
= f.check_box :only_allow_merge_if_all_discussions_are_resolved
%strong Only allow merge requests to be merged if all discussions are resolved
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
.modal-content .modal-content
.modal-header .modal-header
%a.close{href: "#", "data-dismiss" => "modal"} × %a.close{href: "#", "data-dismiss" => "modal"} ×
%h3.page-title== #{label} this #{commit.change_type_title} %h3.page-title== #{label} this #{commit.change_type_title(current_user)}
.modal-body .modal-body
= form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: 'form-horizontal js-#{type}-form js-requires-input' do = form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: 'form-horizontal js-#{type}-form js-requires-input' do
.form-group.branch .form-group.branch
......
...@@ -112,7 +112,8 @@ ...@@ -112,7 +112,8 @@
%span.descr Enable Container Registry for this project %span.descr Enable Container Registry for this project
= link_to icon('question-circle'), help_page_path('user/project/container_registry'), target: '_blank' = link_to icon('question-circle'), help_page_path('user/project/container_registry'), target: '_blank'
= render 'merge_request_settings', f: f = render 'merge_request_settings', form: f
%hr %hr
%fieldset.features.append-bottom-default %fieldset.features.append-bottom-default
%h5.prepend-top-0 %h5.prepend-top-0
......
...@@ -13,7 +13,11 @@ ...@@ -13,7 +13,11 @@
- if @forked_project && @forked_project.errors.any? - if @forked_project && @forked_project.errors.any?
%p %p
&ndash; &ndash;
= @forked_project.errors.full_messages.first - error = @forked_project.errors.full_messages.first
- if error.include?("already been taken")
Name has already been taken
- else
= error
%p %p
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork", class: "btn" do = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork", class: "btn" do
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
%span.label-branch= source_branch_with_namespace(@merge_request) %span.label-branch= source_branch_with_namespace(@merge_request)
%span into %span into
%span.label-branch %span.label-branch
= link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch) = link_to_if @merge_request.target_branch_exists?, @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch)
- if @merge_request.open? && @merge_request.diverged_from_target_branch? - if @merge_request.open? && @merge_request.diverged_from_target_branch?
%span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind) %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
%li.stage-column %li.stage-column
.stage-name .stage-name
%a{ name: stage.name } %a{ name: stage.name }
- if stage.name
= stage.name.titleize = stage.name.titleize
.builds-container .builds-container
%ul %ul
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
%a{ name: stage.name } %a{ name: stage.name }
%span{class: "ci-status-link ci-status-icon-#{stage.status}"} %span{class: "ci-status-link ci-status-icon-#{stage.status}"}
= ci_icon_for_status(stage.status) = ci_icon_for_status(stage.status)
- if stage.name
&nbsp; &nbsp;
= stage.name.titleize = stage.name.titleize
= render stage.statuses.latest_ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, allow_retry: true = render stage.statuses.latest_ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, allow_retry: true
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
= event_filter_link EventFilter.push, 'Push events' = event_filter_link EventFilter.push, 'Push events'
- if event_filter_visible(:merge_requests) - if event_filter_visible(:merge_requests)
= event_filter_link EventFilter.merged, 'Merge events' = event_filter_link EventFilter.merged, 'Merge events'
- if event_filter_visible(:issues)
= event_filter_link EventFilter.issue, 'Issue events'
- if event_filter_visible(:issues) - if event_filter_visible(:issues)
= event_filter_link EventFilter.comments, 'Comments' = event_filter_link EventFilter.comments, 'Comments'
= event_filter_link EventFilter.team, 'Team' = event_filter_link EventFilter.team, 'Team'
...@@ -20,8 +20,8 @@ ...@@ -20,8 +20,8 @@
%strong Blocked %strong Blocked
- if source.instance_of?(Group) && !@group - if source.instance_of?(Group) && !@group
= link_to source, class: "member-group-link prepend-left-5" do &middot;
= #{source.name}" = link_to source.name, source, class: "member-group-link"
.hidden-xs.cgray .hidden-xs.cgray
- if member.request? - if member.request?
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
= time_ago_with_tooltip(member.created_at) = time_ago_with_tooltip(member.created_at)
- if show_roles - if show_roles
.controls.member-controls .controls.member-controls
- if show_controls - if show_controls && (member.respond_to?(:group) && @group) || (member.respond_to?(:project) && @project)
- if user != current_user - if user != current_user
= form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f| = form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f|
= f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can_admin_member = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can_admin_member
......
---
title: Fix wrong tab selected when loggin fails and multiple login tabs exists
merge_request: 7314
author: Jacopo Beschi @jacopo-beschi
---
title: Fix diff view permalink highlighting
merge_request: 7090
author:
---
title: 'Remove unnecessary target branch link from MR page in case of deleted target branch'
merge_request: 7916
author: Rydkin Maxim
---
title: Fix Cicking on tabs on pipeline page should set URL
merge_request: 7709
author:
---
title: 'fix: 24982- Remove''Signed in successfully'' message After this change the
sign-in-success flash message will not be shown'
merge_request: 7837
author: jnoortheen
---
title: Remove wrong '.builds-feature' class from the MR settings fieldset
merge_request: 7930
author:
---
title: Destroy a user's session when they delete their own account
merge_request:
author:
--- ---
title: Remove visible content caching title: Add nested groups support on data level
merge_request: merge_request:
author: author:
---
title: Enable AsciiDoctor admonition icons
merge_request: 7812
author: Horacio Sanson
---
title: Fix compatibility with Internet Explorer 11 for merge requests
merge_request: 7525
author: Steffen Rauh
---
title: Fix pipeline author for Slack and use pipeline id for pipeline link
merge_request: 7506
author:
--- ---
title: Authorize users into imported GitLab project title: Shows group members in project members list
merge_request: merge_request:
author: author:
---
title: Don't accidentally mark unsafe diff lines as HTML safe
merge_request:
author:
---
title: Add issue events filter and make all really show all events
merge_request: 7673
author: Oxan van Leeuwen
---
title: Replace MR access checks with use of MergeRequestsFinder
merge_request:
author:
---
title: Allow public access to some Tag API endpoints
merge_request:
author:
---
title: Various small emoji positioning adjustments
merge_request:
author:
---
title: Fixed timeago re-rendering every timeago
merge_request:
author:
...@@ -45,7 +45,7 @@ module Gitlab ...@@ -45,7 +45,7 @@ module Gitlab
# #
# Parameters filtered: # Parameters filtered:
# - Password (:password, :password_confirmation) # - Password (:password, :password_confirmation)
# - Private tokens (:private_token) # - Private tokens (:private_token, :authentication_token)
# - Two-factor tokens (:otp_attempt) # - Two-factor tokens (:otp_attempt)
# - Repo/Project Import URLs (:import_url) # - Repo/Project Import URLs (:import_url)
# - Build variables (:variables) # - Build variables (:variables)
...@@ -55,6 +55,7 @@ module Gitlab ...@@ -55,6 +55,7 @@ module Gitlab
# - Sentry DSN (:sentry_dsn) # - Sentry DSN (:sentry_dsn)
# - Deploy keys (:key) # - Deploy keys (:key)
config.filter_parameters += %i( config.filter_parameters += %i(
authentication_token
certificate certificate
encrypted_key encrypted_key
hook hook
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddParentIdToNamespace < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column(:namespaces, :parent_id, :integer)
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddIndexToParentId < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def change
add_concurrent_index(:namespaces, [:parent_id, :id], unique: true)
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddRoutesTable < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :routes do |t|
t.integer :source_id, null: false
t.string :source_type, null: false
t.string :path, null: false
t.timestamps
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 FillRoutesTable < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = true
DOWNTIME_REASON = 'No new namespaces should be created during data copy'
def up
execute <<-EOF
INSERT INTO routes
(source_id, source_type, path)
(SELECT id, 'Namespace', path FROM namespaces)
EOF
end
def down
Route.delete_all(source_type: 'Namespace')
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class FillProjectsRoutesTable < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = true
DOWNTIME_REASON = 'No new projects should be created during data copy'
def up
execute <<-EOF
INSERT INTO routes
(source_id, source_type, path)
(SELECT projects.id, 'Project', concat(namespaces.path, '/', projects.path) FROM projects
INNER JOIN namespaces ON projects.namespace_id = namespaces.id)
EOF
end
def down
Route.delete_all(source_type: 'Project')
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class RemoveDuplicatesFromRoutes < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
select_all("SELECT path FROM #{quote_table_name(:routes)} GROUP BY path HAVING COUNT(*) > 1").each do |row|
path = connection.quote(row['path'])
execute(%Q{
DELETE FROM #{quote_table_name(:routes)}
WHERE path = #{path}
AND id != (
SELECT id FROM (
SELECT max(id) AS id
FROM #{quote_table_name(:routes)}
WHERE path = #{path}
) max_ids
)
})
end
end
def down
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddIndexToRoutes < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
disable_ddl_transaction!
def change
add_concurrent_index(:routes, :path, unique: true)
add_concurrent_index(:routes, [:source_type, :source_id], unique: true)
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: 20161128161412) do ActiveRecord::Schema.define(version: 20161202152035) 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"
...@@ -98,14 +98,14 @@ ActiveRecord::Schema.define(version: 20161128161412) do ...@@ -98,14 +98,14 @@ ActiveRecord::Schema.define(version: 20161128161412) do
t.text "help_page_text_html" t.text "help_page_text_html"
t.text "shared_runners_text_html" t.text "shared_runners_text_html"
t.text "after_sign_up_text_html" t.text "after_sign_up_text_html"
t.boolean "sidekiq_throttling_enabled", default: false
t.string "sidekiq_throttling_queues"
t.decimal "sidekiq_throttling_factor"
t.boolean "housekeeping_enabled", default: true, null: false t.boolean "housekeeping_enabled", default: true, null: false
t.boolean "housekeeping_bitmaps_enabled", default: true, null: false t.boolean "housekeeping_bitmaps_enabled", default: true, null: false
t.integer "housekeeping_incremental_repack_period", default: 10, null: false t.integer "housekeeping_incremental_repack_period", default: 10, null: false
t.integer "housekeeping_full_repack_period", default: 50, null: false t.integer "housekeeping_full_repack_period", default: 50, null: false
t.integer "housekeeping_gc_period", default: 200, null: false t.integer "housekeeping_gc_period", default: 200, null: false
t.boolean "sidekiq_throttling_enabled", default: false
t.string "sidekiq_throttling_queues"
t.decimal "sidekiq_throttling_factor"
t.boolean "html_emails_enabled", default: true t.boolean "html_emails_enabled", default: true
end end
...@@ -737,8 +737,9 @@ ActiveRecord::Schema.define(version: 20161128161412) do ...@@ -737,8 +737,9 @@ ActiveRecord::Schema.define(version: 20161128161412) do
t.integer "visibility_level", default: 20, null: false t.integer "visibility_level", default: 20, null: false
t.boolean "request_access_enabled", default: false, null: false t.boolean "request_access_enabled", default: false, null: false
t.datetime "deleted_at" t.datetime "deleted_at"
t.boolean "lfs_enabled"
t.text "description_html" t.text "description_html"
t.boolean "lfs_enabled"
t.integer "parent_id"
end end
add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree
...@@ -746,6 +747,7 @@ ActiveRecord::Schema.define(version: 20161128161412) do ...@@ -746,6 +747,7 @@ ActiveRecord::Schema.define(version: 20161128161412) do
add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree
add_index "namespaces", ["name"], name: "index_namespaces_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} add_index "namespaces", ["name"], name: "index_namespaces_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree
add_index "namespaces", ["parent_id", "id"], name: "index_namespaces_on_parent_id_and_id", unique: true, using: :btree
add_index "namespaces", ["path"], name: "index_namespaces_on_path", unique: true, using: :btree add_index "namespaces", ["path"], name: "index_namespaces_on_path", unique: true, using: :btree
add_index "namespaces", ["path"], name: "index_namespaces_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"} add_index "namespaces", ["path"], name: "index_namespaces_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"}
add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree
...@@ -991,6 +993,17 @@ ActiveRecord::Schema.define(version: 20161128161412) do ...@@ -991,6 +993,17 @@ ActiveRecord::Schema.define(version: 20161128161412) do
add_index "releases", ["project_id", "tag"], name: "index_releases_on_project_id_and_tag", using: :btree add_index "releases", ["project_id", "tag"], name: "index_releases_on_project_id_and_tag", using: :btree
add_index "releases", ["project_id"], name: "index_releases_on_project_id", using: :btree add_index "releases", ["project_id"], name: "index_releases_on_project_id", using: :btree
create_table "routes", force: :cascade do |t|
t.integer "source_id", null: false
t.string "source_type", null: false
t.string "path", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "routes", ["path"], name: "index_routes_on_path", unique: true, using: :btree
add_index "routes", ["source_type", "source_id"], name: "index_routes_on_source_type_and_source_id", unique: true, using: :btree
create_table "sent_notifications", force: :cascade do |t| create_table "sent_notifications", force: :cascade do |t|
t.integer "project_id" t.integer "project_id"
t.integer "noteable_id" t.integer "noteable_id"
...@@ -1206,8 +1219,8 @@ ActiveRecord::Schema.define(version: 20161128161412) do ...@@ -1206,8 +1219,8 @@ ActiveRecord::Schema.define(version: 20161128161412) do
t.datetime "otp_grace_period_started_at" t.datetime "otp_grace_period_started_at"
t.boolean "ldap_email", default: false, null: false t.boolean "ldap_email", default: false, null: false
t.boolean "external", default: false t.boolean "external", default: false
t.string "organization"
t.string "incoming_email_token" t.string "incoming_email_token"
t.string "organization"
t.boolean "authorized_projects_populated" t.boolean "authorized_projects_populated"
end end
......
...@@ -41,7 +41,7 @@ If you use a cloud-managed service, or provide your own PostgreSQL: ...@@ -41,7 +41,7 @@ If you use a cloud-managed service, or provide your own PostgreSQL:
mailroom['enable'] = false mailroom['enable'] = false
# PostgreSQL configuration # PostgreSQL configuration
postgresql['sql_password'] = 'DB password' gitlab_rails['db_password'] = 'DB password'
postgresql['md5_auth_cidr_addresses'] = ['0.0.0.0/0'] postgresql['md5_auth_cidr_addresses'] = ['0.0.0.0/0']
postgresql['listen_address'] = '0.0.0.0' postgresql['listen_address'] = '0.0.0.0'
``` ```
...@@ -80,7 +80,7 @@ If you use a cloud-managed service, or provide your own PostgreSQL: ...@@ -80,7 +80,7 @@ If you use a cloud-managed service, or provide your own PostgreSQL:
1. Similarly, set the password for the `gitlab` database user. Use the same 1. Similarly, set the password for the `gitlab` database user. Use the same
password that you specified in the `/etc/gitlab/gitlab.rb` file for password that you specified in the `/etc/gitlab/gitlab.rb` file for
`postgresql['sql_password']`. `gitlab_rails['db_password']`.
``` ```
\password gitlab \password gitlab
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
## List project repository tags ## List project repository tags
Get a list of repository tags from a project, sorted by name in reverse alphabetical order. Get a list of repository tags from a project, sorted by name in reverse
alphabetical order. This endpoint can be accessed without authentication if the
repository is publicly accessible.
``` ```
GET /projects/:id/repository/tags GET /projects/:id/repository/tags
...@@ -40,7 +42,8 @@ Parameters: ...@@ -40,7 +42,8 @@ Parameters:
## Get a single repository tag ## Get a single repository tag
Get a specific repository tag determined by its name. Get a specific repository tag determined by its name. This endpoint can be
accessed without authentication if the repository is publicly accessible.
``` ```
GET /projects/:id/repository/tags/:tag_name GET /projects/:id/repository/tags/:tag_name
......
...@@ -291,7 +291,9 @@ Parameters: ...@@ -291,7 +291,9 @@ Parameters:
- `id` (required) - The ID of the user - `id` (required) - The ID of the user
## Current user ## User
### For normal users
Gets currently authenticated user. Gets currently authenticated user.
...@@ -335,6 +337,53 @@ GET /user ...@@ -335,6 +337,53 @@ GET /user
} }
``` ```
### For admins
Parameters:
- `sudo` (required) - the ID of a user
```
GET /user
```
```json
{
"id": 1,
"username": "john_smith",
"email": "john@example.com",
"name": "John Smith",
"state": "active",
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
"web_url": "http://localhost:3000/john_smith",
"created_at": "2012-05-23T08:00:58Z",
"is_admin": false,
"bio": null,
"location": null,
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
"organization": "",
"last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z",
"theme_id": 1,
"color_scheme_id": 2,
"projects_limit": 100,
"current_sign_in_at": "2012-06-02T06:36:55Z",
"identities": [
{"provider": "github", "extern_uid": "2435223452345"},
{"provider": "bitbucket", "extern_uid": "john_smith"},
{"provider": "google_oauth2", "extern_uid": "8776128412476123468721346"}
],
"can_create_group": true,
"can_create_project": true,
"two_factor_enabled": true,
"external": false,
"private_token": "dd34asd13as"
}
```
## List SSH keys ## List SSH keys
Get a list of currently authenticated user's SSH keys. Get a list of currently authenticated user's SSH keys.
......
...@@ -113,7 +113,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS ...@@ -113,7 +113,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`: There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
```sh ```sh
git diff origin/8-13-stable:config/gitlab.yml.example origin/8-15-stable:config/gitlab.yml.example git diff origin/8-14-stable:config/gitlab.yml.example origin/8-15-stable:config/gitlab.yml.example
``` ```
#### Git configuration #### Git configuration
...@@ -131,10 +131,10 @@ Ensure you're still up-to-date with the latest NGINX configuration changes: ...@@ -131,10 +131,10 @@ Ensure you're still up-to-date with the latest NGINX configuration changes:
```sh ```sh
# For HTTPS configurations # For HTTPS configurations
git diff origin/8-13-stable:lib/support/nginx/gitlab-ssl origin/8-15-stable:lib/support/nginx/gitlab-ssl git diff origin/8-14-stable:lib/support/nginx/gitlab-ssl origin/8-15-stable:lib/support/nginx/gitlab-ssl
# For HTTP configurations # For HTTP configurations
git diff origin/8-13-stable:lib/support/nginx/gitlab origin/8-15-stable:lib/support/nginx/gitlab git diff origin/8-14-stable:lib/support/nginx/gitlab origin/8-15-stable:lib/support/nginx/gitlab
``` ```
If you are using Apache instead of NGINX please see the updated [Apache templates]. If you are using Apache instead of NGINX please see the updated [Apache templates].
......
...@@ -22,7 +22,7 @@ module API ...@@ -22,7 +22,7 @@ module API
expose :provider, :extern_uid expose :provider, :extern_uid
end end
class UserFull < User class UserPublic < User
expose :last_sign_in_at expose :last_sign_in_at
expose :confirmed_at expose :confirmed_at
expose :email expose :email
...@@ -34,7 +34,7 @@ module API ...@@ -34,7 +34,7 @@ module API
expose :external expose :external
end end
class UserLogin < UserFull class UserWithPrivateToken < UserPublic
expose :private_token expose :private_token
end end
...@@ -289,7 +289,7 @@ module API ...@@ -289,7 +289,7 @@ module API
end end
class SSHKeyWithUser < SSHKey class SSHKeyWithUser < SSHKey
expose :user, using: Entities::UserFull expose :user, using: Entities::UserPublic
end end
class Note < Grape::Entity class Note < Grape::Entity
......
...@@ -44,11 +44,14 @@ module API ...@@ -44,11 +44,14 @@ module API
return nil return nil
end end
identifier = sudo_identifier() identifier = sudo_identifier
# If the sudo is the current user do nothing if identifier
if identifier && !(@current_user.id == identifier || @current_user.username == identifier) # We check for private_token because we cannot allow PAT to be used
forbidden!('Must be admin to use sudo') unless @current_user.is_admin? forbidden!('Must be admin to use sudo') unless @current_user.is_admin?
forbidden!('Private token must be specified in order to use sudo') unless private_token_used?
@impersonator = @current_user
@current_user = User.by_username_or_id(identifier) @current_user = User.by_username_or_id(identifier)
not_found!("No user id or username for: #{identifier}") if @current_user.nil? not_found!("No user id or username for: #{identifier}") if @current_user.nil?
end end
...@@ -108,7 +111,7 @@ module API ...@@ -108,7 +111,7 @@ module API
if id =~ /^\d+$/ if id =~ /^\d+$/
Group.find_by(id: id) Group.find_by(id: id)
else else
Group.find_by(path: id) Group.find_by_full_path(id)
end end
end end
...@@ -383,6 +386,10 @@ module API ...@@ -383,6 +386,10 @@ module API
links.join(', ') links.join(', ')
end end
def private_token_used?
private_token == @current_user.private_token
end
def secret_token def secret_token
Gitlab::Shell.secret_token Gitlab::Shell.secret_token
end end
......
module API module API
class Session < Grape::API class Session < Grape::API
desc 'Login to get token' do desc 'Login to get token' do
success Entities::UserLogin success Entities::UserWithPrivateToken
end end
params do params do
optional :login, type: String, desc: 'The username' optional :login, type: String, desc: 'The username'
...@@ -14,7 +14,7 @@ module API ...@@ -14,7 +14,7 @@ module API
return unauthorized! unless user return unauthorized! unless user
return render_api_error!('401 Unauthorized. You have 2FA enabled. Please use a personal access token to access the API', 401) if user.two_factor_enabled? return render_api_error!('401 Unauthorized. You have 2FA enabled. Please use a personal access token to access the API', 401) if user.two_factor_enabled?
present user, with: Entities::UserLogin present user, with: Entities::UserWithPrivateToken
end end
end end
end end
module API module API
# Git Tags API # Git Tags API
class Tags < Grape::API class Tags < Grape::API
before { authenticate! }
before { authorize! :download_code, user_project } before { authorize! :download_code, user_project }
params do params do
......
...@@ -51,7 +51,7 @@ module API ...@@ -51,7 +51,7 @@ module API
users = users.external if params[:external] && current_user.is_admin? users = users.external if params[:external] && current_user.is_admin?
end end
entity = current_user.is_admin? ? Entities::UserFull : Entities::UserBasic entity = current_user.is_admin? ? Entities::UserPublic : Entities::UserBasic
present paginate(users), with: entity present paginate(users), with: entity
end end
...@@ -66,7 +66,7 @@ module API ...@@ -66,7 +66,7 @@ module API
not_found!('User') unless user not_found!('User') unless user
if current_user && current_user.is_admin? if current_user && current_user.is_admin?
present user, with: Entities::UserFull present user, with: Entities::UserPublic
elsif can?(current_user, :read_user, user) elsif can?(current_user, :read_user, user)
present user, with: Entities::User present user, with: Entities::User
else else
...@@ -75,7 +75,7 @@ module API ...@@ -75,7 +75,7 @@ module API
end end
desc 'Create a user. Available only for admins.' do desc 'Create a user. Available only for admins.' do
success Entities::UserFull success Entities::UserPublic
end end
params do params do
requires :email, type: String, desc: 'The email of the user' requires :email, type: String, desc: 'The email of the user'
...@@ -99,7 +99,7 @@ module API ...@@ -99,7 +99,7 @@ module API
end end
if user.save if user.save
present user, with: Entities::UserFull present user, with: Entities::UserPublic
else else
conflict!('Email has already been taken') if User. conflict!('Email has already been taken') if User.
where(email: user.email). where(email: user.email).
...@@ -114,7 +114,7 @@ module API ...@@ -114,7 +114,7 @@ module API
end end
desc 'Update a user. Available only for admins.' do desc 'Update a user. Available only for admins.' do
success Entities::UserFull success Entities::UserPublic
end end
params do params do
requires :id, type: Integer, desc: 'The ID of the user' requires :id, type: Integer, desc: 'The ID of the user'
...@@ -161,7 +161,7 @@ module API ...@@ -161,7 +161,7 @@ module API
user_params.delete(:provider) user_params.delete(:provider)
if user.update_attributes(user_params) if user.update_attributes(user_params)
present user, with: Entities::UserFull present user, with: Entities::UserPublic
else else
render_validation_error!(user) render_validation_error!(user)
end end
...@@ -350,10 +350,10 @@ module API ...@@ -350,10 +350,10 @@ module API
resource :user do resource :user do
desc 'Get the currently authenticated user' do desc 'Get the currently authenticated user' do
success Entities::UserFull success Entities::UserPublic
end end
get do get do
present current_user, with: Entities::UserFull present current_user, with: @impersonator ? Entities::UserWithPrivateToken : Entities::UserPublic
end end
desc "Get the currently authenticated user's SSH keys" do desc "Get the currently authenticated user's SSH keys" do
......
...@@ -4,7 +4,7 @@ class GroupUrlConstrainer ...@@ -4,7 +4,7 @@ class GroupUrlConstrainer
return false unless valid?(id) return false unless valid?(id)
Group.find_by(path: id).present? Group.find_by_full_path(id).present?
end end
private private
......
...@@ -14,6 +14,10 @@ class EventFilter ...@@ -14,6 +14,10 @@ class EventFilter
'merged' 'merged'
end end
def issue
'issue'
end
def comments def comments
'comments' 'comments'
end end
...@@ -32,32 +36,20 @@ class EventFilter ...@@ -32,32 +36,20 @@ class EventFilter
end end
def apply_filter(events) def apply_filter(events)
return events unless params.present? return events if params.blank? || params == EventFilter.all
filter = params.dup
actions = []
case filter case params
when EventFilter.push when EventFilter.push
actions = [Event::PUSHED] events.where(action: Event::PUSHED)
when EventFilter.merged when EventFilter.merged
actions = [Event::MERGED] events.where(action: Event::MERGED)
when EventFilter.comments when EventFilter.comments
actions = [Event::COMMENTED] events.where(action: Event::COMMENTED)
when EventFilter.team when EventFilter.team
actions = [Event::JOINED, Event::LEFT, Event::EXPIRED] events.where(action: [Event::JOINED, Event::LEFT, Event::EXPIRED])
when EventFilter.all when EventFilter.issue
actions = [ events.where(action: [Event::CREATED, Event::UPDATED, Event::CLOSED, Event::REOPENED])
Event::PUSHED,
Event::MERGED,
Event::COMMENTED,
Event::JOINED,
Event::LEFT,
Event::EXPIRED
]
end end
events.where(action: actions)
end end
def options(key) def options(key)
...@@ -73,6 +65,10 @@ class EventFilter ...@@ -73,6 +65,10 @@ class EventFilter
end end
def active?(key) def active?(key)
if params.present?
params.include? key params.include? key
else
key == EventFilter.all
end
end end
end end
...@@ -6,7 +6,7 @@ module Gitlab ...@@ -6,7 +6,7 @@ module Gitlab
module Asciidoc module Asciidoc
DEFAULT_ADOC_ATTRS = [ DEFAULT_ADOC_ATTRS = [
'showtitle', 'idprefix=user-content-', 'idseparator=-', 'env=gitlab', 'showtitle', 'idprefix=user-content-', 'idseparator=-', 'env=gitlab',
'env-gitlab', 'source-highlighter=html-pipeline' 'env-gitlab', 'source-highlighter=html-pipeline', 'icons=font'
].freeze ].freeze
# Public: Converts the provided Asciidoc markup into HTML. # Public: Converts the provided Asciidoc markup into HTML.
......
...@@ -68,7 +68,7 @@ module Gitlab ...@@ -68,7 +68,7 @@ module Gitlab
end end
def merge_requests def merge_requests
merge_requests = MergeRequest.in_projects(project_ids_relation) merge_requests = MergeRequestsFinder.new(current_user).execute.in_projects(project_ids_relation)
if query =~ /[#!](\d+)\z/ if query =~ /[#!](\d+)\z/
merge_requests = merge_requests.where(iid: $1) merge_requests = merge_requests.where(iid: $1)
else else
......
...@@ -110,7 +110,7 @@ describe Projects::TodosController do ...@@ -110,7 +110,7 @@ describe Projects::TodosController do
end end
end end
context 'when not authorized' do context 'when not authorized for project' do
it 'does not create todo for merge request user has no access to' do it 'does not create todo for merge request user has no access to' do
sign_in(user) sign_in(user)
expect do expect do
...@@ -128,6 +128,19 @@ describe Projects::TodosController do ...@@ -128,6 +128,19 @@ describe Projects::TodosController do
expect(response).to have_http_status(302) expect(response).to have_http_status(302)
end end
end end
context 'when not authorized for merge_request' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE)
sign_in(user)
end
it "doesn't create todo" do
expect{ go }.not_to change { user.todos.count }
expect(response).to have_http_status(404)
end
end
end end
end end
end end
...@@ -22,7 +22,6 @@ describe SessionsController do ...@@ -22,7 +22,6 @@ describe SessionsController do
it 'authenticates user correctly' do it 'authenticates user correctly' do
post(:create, user: { login: user.username, password: user.password }) post(:create, user: { login: user.username, password: user.password })
expect(response).to set_flash.to /Signed in successfully/
expect(subject.current_user). to eq user expect(subject.current_user). to eq user
end end
......
require 'spec_helper'
describe 'Target branch', feature: true do
let(:user) { create(:user) }
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.project }
def path_to_merge_request
namespace_project_merge_request_path(
project.namespace,
project, merge_request
)
end
before do
login_as user
project.team << [user, :master]
end
it 'shows link to target branch' do
visit path_to_merge_request
expect(page).to have_link('feature', href: namespace_project_commits_path(project.namespace, project, merge_request.target_branch))
end
context 'when branch was deleted' do
before do
DeleteBranchService.new(project, user).execute('feature')
visit path_to_merge_request
end
it 'shows a message about missing target branch' do
expect(page).to have_content(
'Target branch feature does not exist'
)
end
it 'does not show link to target branch' do
expect(page).not_to have_link('feature')
end
end
end
require 'spec_helper'
feature 'Projects members', feature: true do
let(:user) { create(:user) }
let(:developer) { create(:user) }
let(:group) { create(:group, :public, :access_requestable) }
let(:project) { create(:empty_project, :public, :access_requestable, creator: user, group: group) }
let(:project_invitee) { create(:project_member, project: project, invite_token: '123', invite_email: 'test1@abc.com', user: nil) }
let(:group_invitee) { create(:group_member, group: group, invite_token: '123', invite_email: 'test2@abc.com', user: nil) }
let(:project_requester) { create(:user) }
let(:group_requester) { create(:user) }
background do
project.team << [developer, :developer]
group.add_owner(user)
login_as(user)
end
context 'with a group invitee' do
before do
group_invitee
visit namespace_project_project_members_path(project.namespace, project)
end
scenario 'does not appear in the project members page' do
page.within first('.content-list') do
expect(page).not_to have_content('test2@abc.com')
end
end
end
context 'with a group and a project invitee' do
before do
group_invitee
project_invitee
visit namespace_project_project_members_path(project.namespace, project)
end
scenario 'shows the project invitee, the project developer, and the group owner' do
page.within first('.content-list') do
expect(page).to have_content('test1@abc.com')
expect(page).not_to have_content('test2@abc.com')
# Project developer
expect(page).to have_content(developer.name)
# Group owner
expect(page).to have_content(user.name)
expect(page).to have_content(group.name)
end
end
end
context 'with a group requester' do
before do
group.request_access(group_requester)
visit namespace_project_project_members_path(project.namespace, project)
end
scenario 'does not appear in the project members page' do
page.within first('.content-list') do
expect(page).not_to have_content(group_requester.name)
end
end
end
context 'with a group and a project requesters' do
before do
group.request_access(group_requester)
project.request_access(project_requester)
visit namespace_project_project_members_path(project.namespace, project)
end
scenario 'shows the project requester, the project developer, and the group owner' do
page.within first('.content-list') do
expect(page).to have_content(project_requester.name)
expect(page).not_to have_content(group_requester.name)
end
page.within all('.content-list').last do
# Project developer
expect(page).to have_content(developer.name)
# Group owner
expect(page).to have_content(user.name)
expect(page).to have_content(group.name)
end
end
end
end
require 'spec_helper'
feature 'Project settings > Merge Requests', feature: true, js: true do
include GitlabRoutingHelper
let(:project) { create(:empty_project, :public) }
let(:user) { create(:user) }
background do
project.team << [user, :master]
login_as(user)
end
context 'when Merge Request and Builds are initially enabled' do
before do
project.project_feature.update_attribute('merge_requests_access_level', ProjectFeature::ENABLED)
end
context 'when Builds are initially enabled' do
before do
project.project_feature.update_attribute('builds_access_level', ProjectFeature::ENABLED)
visit edit_project_path(project)
end
scenario 'shows the Merge Requests settings' do
expect(page).to have_content('Only allow merge requests to be merged if the build succeeds')
expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
select 'Disabled', from: "project_project_feature_attributes_merge_requests_access_level"
expect(page).not_to have_content('Only allow merge requests to be merged if the build succeeds')
expect(page).not_to have_content('Only allow merge requests to be merged if all discussions are resolved')
end
end
context 'when Builds are initially disabled' do
before do
project.project_feature.update_attribute('builds_access_level', ProjectFeature::DISABLED)
visit edit_project_path(project)
end
scenario 'shows the Merge Requests settings that do not depend on Builds feature' do
expect(page).not_to have_content('Only allow merge requests to be merged if the build succeeds')
expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
select 'Everyone with access', from: "project_project_feature_attributes_builds_access_level"
expect(page).to have_content('Only allow merge requests to be merged if the build succeeds')
expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
end
end
end
context 'when Merge Request are initially disabled' do
before do
project.project_feature.update_attribute('merge_requests_access_level', ProjectFeature::DISABLED)
visit edit_project_path(project)
end
scenario 'does not show the Merge Requests settings' do
expect(page).not_to have_content('Only allow merge requests to be merged if the build succeeds')
expect(page).not_to have_content('Only allow merge requests to be merged if all discussions are resolved')
select 'Everyone with access', from: "project_project_feature_attributes_merge_requests_access_level"
expect(page).to have_content('Only allow merge requests to be merged if the build succeeds')
expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
end
end
end
...@@ -163,8 +163,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: ...@@ -163,8 +163,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
click_on "Sign in via U2F device" click_on "Sign in via U2F device"
expect(page.body).to match('We heard back from your U2F device') expect(page.body).to match('We heard back from your U2F device')
click_on "Authenticate via U2F Device" click_on "Authenticate via U2F Device"
expect(page.body).to match('href="/users/sign_out"')
expect(page.body).to match('Signed in successfully')
end end
end end
...@@ -178,7 +177,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: ...@@ -178,7 +177,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
expect(page.body).to match('We heard back from your U2F device') expect(page.body).to match('We heard back from your U2F device')
click_on "Authenticate via U2F Device" click_on "Authenticate via U2F Device"
expect(page.body).to match('Signed in successfully') expect(page.body).to match('href="/users/sign_out"')
end end
end end
...@@ -234,7 +233,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: ...@@ -234,7 +233,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
expect(page.body).to match('We heard back from your U2F device') expect(page.body).to match('We heard back from your U2F device')
click_on "Authenticate via U2F Device" click_on "Authenticate via U2F Device"
expect(page.body).to match('Signed in successfully') expect(page.body).to match('href="/users/sign_out"')
end end
end end
end end
...@@ -275,7 +274,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: ...@@ -275,7 +274,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
expect(page.body).to match('We heard back from your U2F device') expect(page.body).to match('We heard back from your U2F device')
click_on "Authenticate via U2F Device" click_on "Authenticate via U2F Device"
expect(page.body).to match('Signed in successfully') expect(page.body).to match('href="/users/sign_out"')
logout logout
end end
......
{
"type": "object",
"required": [
"id",
"username",
"email",
"name",
"state",
"avatar_url",
"web_url",
"created_at",
"is_admin",
"bio",
"location",
"skype",
"linkedin",
"twitter",
"website_url",
"organization",
"last_sign_in_at",
"confirmed_at",
"theme_id",
"color_scheme_id",
"projects_limit",
"current_sign_in_at",
"identities",
"can_create_group",
"can_create_project",
"two_factor_enabled",
"external",
"private_token"
],
"properties": {
"$ref": "full.json",
"private_token": { "type": "string" }
}
}
{
"type": "object",
"required": [
"id",
"username",
"email",
"name",
"state",
"avatar_url",
"web_url",
"created_at",
"is_admin",
"bio",
"location",
"skype",
"linkedin",
"twitter",
"website_url",
"organization",
"last_sign_in_at",
"confirmed_at",
"theme_id",
"color_scheme_id",
"projects_limit",
"current_sign_in_at",
"identities",
"can_create_group",
"can_create_project",
"two_factor_enabled",
"external"
],
"properties": {
"id": { "type": "integer" },
"username": { "type": "string" },
"email": {
"type": "string",
"pattern": "^[^@]+@[^@]+$"
},
"name": { "type": "string" },
"state": {
"type": "string",
"enum": ["active", "blocked"]
},
"avatar_url": { "type": "string" },
"web_url": { "type": "string" },
"created_at": { "type": "date" },
"is_admin": { "type": "boolean" },
"bio": { "type": ["string", "null"] },
"location": { "type": ["string", "null"] },
"skype": { "type": "string" },
"linkedin": { "type": "string" },
"twitter": { "type": "string "},
"website_url": { "type": "string" },
"organization": { "type": ["string", "null"] },
"last_sign_in_at": { "type": "date" },
"confirmed_at": { "type": ["date", "null"] },
"theme_id": { "type": "integer" },
"color_scheme_id": { "type": "integer" },
"projects_limit": { "type": "integer" },
"current_sign_in_at": { "type": "date" },
"identities": {
"type": "array",
"items": {
"type": "object",
"properties": {
"provider": {
"type": "string",
"enum": ["github", "bitbucket", "google_oauth2"]
},
"extern_uid": { "type": ["number", "string"] }
}
}
},
"can_create_group": { "type": "boolean" },
"can_create_project": { "type": "boolean" },
"two_factor_enabled": { "type": "boolean" },
"external": { "type": "boolean" }
}
}
...@@ -60,15 +60,58 @@ describe DiffHelper do ...@@ -60,15 +60,58 @@ describe DiffHelper do
end end
describe '#diff_line_content' do describe '#diff_line_content' do
it 'returns non breaking space when line is empty' do context 'when the line is empty' do
it 'returns a non breaking space' do
expect(diff_line_content(nil)).to eq('&nbsp;') expect(diff_line_content(nil)).to eq('&nbsp;')
end end
it 'returns the line itself' do it 'returns an HTML-safe string' do
expect(diff_line_content(diff_file.diff_lines.first.text)). expect(diff_line_content(nil)).to be_html_safe
to eq('@@ -6,12 +6,18 @@ module Popen') end
expect(diff_line_content(diff_file.diff_lines.first.type)).to eq('match') end
expect(diff_file.diff_lines.first.new_pos).to eq(6)
context 'when the line is not empty' do
context 'when the line starts with +, -, or a space' do
it 'strips the first character' do
expect(diff_line_content('+new line')).to eq('new line')
expect(diff_line_content('-new line')).to eq('new line')
expect(diff_line_content(' new line')).to eq('new line')
end
context 'when the line is HTML-safe' do
it 'returns an HTML-safe string' do
expect(diff_line_content('+new line'.html_safe)).to be_html_safe
expect(diff_line_content('-new line'.html_safe)).to be_html_safe
expect(diff_line_content(' new line'.html_safe)).to be_html_safe
end
end
context 'when the line is not HTML-safe' do
it 'returns a non-HTML-safe string' do
expect(diff_line_content('+new line')).not_to be_html_safe
expect(diff_line_content('-new line')).not_to be_html_safe
expect(diff_line_content(' new line')).not_to be_html_safe
end
end
end
context 'when the line does not start with a +, -, or a space' do
it 'returns the string' do
expect(diff_line_content('@@ -6,12 +6,18 @@ module Popen')).to eq('@@ -6,12 +6,18 @@ module Popen')
end
context 'when the line is HTML-safe' do
it 'returns an HTML-safe string' do
expect(diff_line_content('@@ -6,12 +6,18 @@ module Popen'.html_safe)).to be_html_safe
end
end
context 'when the line is not HTML-safe' do
it 'returns a non-HTML-safe string' do
expect(diff_line_content('@@ -6,12 +6,18 @@ module Popen')).not_to be_html_safe
end
end
end
end end
end end
......
...@@ -11,6 +11,10 @@ ...@@ -11,6 +11,10 @@
%a.event-filter-link{ id: "merged_event_filter", title: "Filter by merge events", href: "/dashboard/activity"} %a.event-filter-link{ id: "merged_event_filter", title: "Filter by merge events", href: "/dashboard/activity"}
%span %span
Merge events Merge events
%li
%a.event-filter-link{ id: "issue_event_filter", title: "Filter by issue events", href: "/dashboard/activity"}
%span
Issue events
%li %li
%a.event-filter-link{ id: "comments_event_filter", title: "Filter by comments", href: "/dashboard/activity"} %a.event-filter-link{ id: "comments_event_filter", title: "Filter by comments", href: "/dashboard/activity"}
%span %span
......
%ul.nav-tabs
%li
%a.active{ id: 'standard', href: '#standard'} Standard
%li
%a{ id: 'ldap', href: '#ldap'} Ldap
/*= require signin_tabs_memoizer */
((global) => {
describe('SigninTabsMemoizer', () => {
const fixtureTemplate = 'signin_tabs.html';
const tabSelector = 'ul.nav-tabs';
const currentTabKey = 'current_signin_tab';
let memo;
function createMemoizer() {
memo = new global.ActiveTabMemoizer({
currentTabKey,
tabSelector,
});
return memo;
}
fixture.preload(fixtureTemplate);
beforeEach(() => {
fixture.load(fixtureTemplate);
});
it('does nothing if no tab was previously selected', () => {
createMemoizer();
expect(document.querySelector('li a.active').getAttribute('id')).toEqual('standard');
});
it('shows last selected tab on boot', () => {
createMemoizer().saveData('#ldap');
const fakeTab = {
click: () => {},
};
spyOn(document, 'querySelector').and.returnValue(fakeTab);
spyOn(fakeTab, 'click');
memo.bootstrap();
// verify that triggers click on the last selected tab
expect(document.querySelector).toHaveBeenCalledWith(`${tabSelector} a[href="#ldap"]`);
expect(fakeTab.click).toHaveBeenCalled();
});
it('saves last selected tab on change', () => {
createMemoizer();
document.getElementById('standard').click();
expect(memo.readData()).toEqual('#standard');
});
});
})(window);
...@@ -10,6 +10,13 @@ describe GroupUrlConstrainer, lib: true do ...@@ -10,6 +10,13 @@ describe GroupUrlConstrainer, lib: true do
it { expect(subject.matches?(request)).to be_truthy } it { expect(subject.matches?(request)).to be_truthy }
end end
context 'valid request for nested group' do
let!(:nested_group) { create(:group, path: 'nested', parent: group) }
let!(:request) { build_request('gitlab/nested') }
it { expect(subject.matches?(request)).to be_truthy }
end
context 'invalid request' do context 'invalid request' do
let(:request) { build_request('foo') } let(:request) { build_request('foo') }
......
...@@ -7,6 +7,10 @@ describe EventFilter, lib: true do ...@@ -7,6 +7,10 @@ describe EventFilter, lib: true do
let!(:push_event) { create(:event, action: Event::PUSHED, project: public_project, target: public_project, author: source_user) } let!(:push_event) { create(:event, action: Event::PUSHED, project: public_project, target: public_project, author: source_user) }
let!(:merged_event) { create(:event, action: Event::MERGED, project: public_project, target: public_project, author: source_user) } let!(:merged_event) { create(:event, action: Event::MERGED, project: public_project, target: public_project, author: source_user) }
let!(:created_event) { create(:event, action: Event::CREATED, project: public_project, target: public_project, author: source_user) }
let!(:updated_event) { create(:event, action: Event::UPDATED, project: public_project, target: public_project, author: source_user) }
let!(:closed_event) { create(:event, action: Event::CLOSED, project: public_project, target: public_project, author: source_user) }
let!(:reopened_event) { create(:event, action: Event::REOPENED, project: public_project, target: public_project, author: source_user) }
let!(:comments_event) { create(:event, action: Event::COMMENTED, project: public_project, target: public_project, author: source_user) } let!(:comments_event) { create(:event, action: Event::COMMENTED, project: public_project, target: public_project, author: source_user) }
let!(:joined_event) { create(:event, action: Event::JOINED, project: public_project, target: public_project, author: source_user) } let!(:joined_event) { create(:event, action: Event::JOINED, project: public_project, target: public_project, author: source_user) }
let!(:left_event) { create(:event, action: Event::LEFT, project: public_project, target: public_project, author: source_user) } let!(:left_event) { create(:event, action: Event::LEFT, project: public_project, target: public_project, author: source_user) }
...@@ -21,6 +25,11 @@ describe EventFilter, lib: true do ...@@ -21,6 +25,11 @@ describe EventFilter, lib: true do
expect(events).to contain_exactly(merged_event) expect(events).to contain_exactly(merged_event)
end end
it 'applies issue filter' do
events = EventFilter.new(EventFilter.issue).apply_filter(Event.all)
expect(events).to contain_exactly(created_event, updated_event, closed_event, reopened_event)
end
it 'applies comments filter' do it 'applies comments filter' do
events = EventFilter.new(EventFilter.comments).apply_filter(Event.all) events = EventFilter.new(EventFilter.comments).apply_filter(Event.all)
expect(events).to contain_exactly(comments_event) expect(events).to contain_exactly(comments_event)
...@@ -33,17 +42,17 @@ describe EventFilter, lib: true do ...@@ -33,17 +42,17 @@ describe EventFilter, lib: true do
it 'applies all filter' do it 'applies all filter' do
events = EventFilter.new(EventFilter.all).apply_filter(Event.all) events = EventFilter.new(EventFilter.all).apply_filter(Event.all)
expect(events).to contain_exactly(push_event, merged_event, comments_event, joined_event, left_event) expect(events).to contain_exactly(push_event, merged_event, created_event, updated_event, closed_event, reopened_event, comments_event, joined_event, left_event)
end end
it 'applies no filter' do it 'applies no filter' do
events = EventFilter.new(nil).apply_filter(Event.all) events = EventFilter.new(nil).apply_filter(Event.all)
expect(events).to contain_exactly(push_event, merged_event, comments_event, joined_event, left_event) expect(events).to contain_exactly(push_event, merged_event, created_event, updated_event, closed_event, reopened_event, comments_event, joined_event, left_event)
end end
it 'applies unknown filter' do it 'applies unknown filter' do
events = EventFilter.new('').apply_filter(Event.all) events = EventFilter.new('').apply_filter(Event.all)
expect(events).to contain_exactly(push_event, merged_event, comments_event, joined_event, left_event) expect(events).to contain_exactly(push_event, merged_event, created_event, updated_event, closed_event, reopened_event, comments_event, joined_event, left_event)
end end
end end
end end
...@@ -188,6 +188,7 @@ project: ...@@ -188,6 +188,7 @@ project:
- project_feature - project_feature
- authorized_users - authorized_users
- project_authorizations - project_authorizations
- route
award_emoji: award_emoji:
- awardable - awardable
- user - user
......
...@@ -40,6 +40,15 @@ describe Gitlab::SearchResults do ...@@ -40,6 +40,15 @@ describe Gitlab::SearchResults do
expect(results.milestones_count).to eq(1) expect(results.milestones_count).to eq(1)
end end
end end
it 'includes merge requests from source and target projects' do
forked_project = create(:empty_project, forked_from_project: project)
merge_request_2 = create(:merge_request, target_project: project, source_project: forked_project, title: 'foo')
results = described_class.new(user, Project.where(id: forked_project.id), 'foo')
expect(results.objects('merge_requests')).to include merge_request_2
end
end end
it 'does not list issues on private projects' do it 'does not list issues on private projects' do
...@@ -152,4 +161,11 @@ describe Gitlab::SearchResults do ...@@ -152,4 +161,11 @@ describe Gitlab::SearchResults do
expect(results.issues_count).to eq 5 expect(results.issues_count).to eq 5
end end
end end
it 'does not list merge requests on projects with limited access' do
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE)
expect(results.objects('merge_requests')).not_to include merge_request
end
end end
...@@ -137,26 +137,25 @@ describe CommitRange, models: true do ...@@ -137,26 +137,25 @@ describe CommitRange, models: true do
end end
describe '#has_been_reverted?' do describe '#has_been_reverted?' do
it 'returns true if the commit has been reverted' do let(:issue) { create(:issue) }
issue = create(:issue) let(:user) { issue.author }
it 'returns true if the commit has been reverted' do
create(:note_on_issue, create(:note_on_issue,
noteable: issue, noteable: issue,
system: true, system: true,
note: commit1.revert_description, note: commit1.revert_description(user),
project: issue.project) project: issue.project)
expect_any_instance_of(Commit).to receive(:reverts_commit?). expect_any_instance_of(Commit).to receive(:reverts_commit?).
with(commit1). with(commit1, user).
and_return(true) and_return(true)
expect(commit1.has_been_reverted?(nil, issue)).to eq(true) expect(commit1.has_been_reverted?(user, issue)).to eq(true)
end end
it 'returns false a commit has not been reverted' do it 'returns false a commit has not been reverted' do
issue = create(:issue) expect(commit1.has_been_reverted?(user, issue)).to eq(false)
expect(commit1.has_been_reverted?(nil, issue)).to eq(false)
end end
end end
end end
...@@ -179,25 +179,26 @@ eos ...@@ -179,25 +179,26 @@ eos
describe '#reverts_commit?' do describe '#reverts_commit?' do
let(:another_commit) { double(:commit, revert_description: "This reverts commit #{commit.sha}") } let(:another_commit) { double(:commit, revert_description: "This reverts commit #{commit.sha}") }
let(:user) { commit.author }
it { expect(commit.reverts_commit?(another_commit)).to be_falsy } it { expect(commit.reverts_commit?(another_commit, user)).to be_falsy }
context 'commit has no description' do context 'commit has no description' do
before { allow(commit).to receive(:description?).and_return(false) } before { allow(commit).to receive(:description?).and_return(false) }
it { expect(commit.reverts_commit?(another_commit)).to be_falsy } it { expect(commit.reverts_commit?(another_commit, user)).to be_falsy }
end end
context "another_commit's description does not revert commit" do context "another_commit's description does not revert commit" do
before { allow(commit).to receive(:description).and_return("Foo Bar") } before { allow(commit).to receive(:description).and_return("Foo Bar") }
it { expect(commit.reverts_commit?(another_commit)).to be_falsy } it { expect(commit.reverts_commit?(another_commit, user)).to be_falsy }
end end
context "another_commit's description reverts commit" do context "another_commit's description reverts commit" do
before { allow(commit).to receive(:description).and_return("Foo #{another_commit.revert_description} Bar") } before { allow(commit).to receive(:description).and_return("Foo #{another_commit.revert_description} Bar") }
it { expect(commit.reverts_commit?(another_commit)).to be_truthy } it { expect(commit.reverts_commit?(another_commit, user)).to be_truthy }
end end
context "another_commit's description reverts merged merge request" do context "another_commit's description reverts merged merge request" do
...@@ -207,7 +208,7 @@ eos ...@@ -207,7 +208,7 @@ eos
allow(commit).to receive(:description).and_return("Foo #{another_commit.revert_description} Bar") allow(commit).to receive(:description).and_return("Foo #{another_commit.revert_description} Bar")
end end
it { expect(commit.reverts_commit?(another_commit)).to be_truthy } it { expect(commit.reverts_commit?(another_commit, user)).to be_truthy }
end end
end end
......
require 'spec_helper'
describe Group, 'Routable' do
let!(:group) { create(:group) }
describe 'Associations' do
it { is_expected.to have_one(:route).dependent(:destroy) }
end
describe 'Callbacks' do
it 'creates route record on create' do
expect(group.route.path).to eq(group.path)
end
it 'updates route record on path change' do
group.update_attributes(path: 'wow')
expect(group.route.path).to eq('wow')
end
it 'ensure route path uniqueness across different objects' do
create(:group, parent: group, path: 'xyz')
duplicate = build(:project, namespace: group, path: 'xyz')
expect { duplicate.save! }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Route path has already been taken, Route is invalid')
end
end
describe '.find_by_full_path' do
let!(:nested_group) { create(:group, parent: group) }
it { expect(described_class.find_by_full_path(group.to_param)).to eq(group) }
it { expect(described_class.find_by_full_path(group.to_param.upcase)).to eq(group) }
it { expect(described_class.find_by_full_path(nested_group.to_param)).to eq(nested_group) }
it { expect(described_class.find_by_full_path('unknown')).to eq(nil) }
end
describe '.where_paths_in' do
context 'without any paths' do
it 'returns an empty relation' do
expect(described_class.where_paths_in([])).to eq([])
end
end
context 'without any valid paths' do
it 'returns an empty relation' do
expect(described_class.where_paths_in(%w[unknown])).to eq([])
end
end
context 'with valid paths' do
let!(:nested_group) { create(:group, parent: group) }
it 'returns the projects matching the paths' do
result = described_class.where_paths_in([group.to_param, nested_group.to_param])
expect(result).to contain_exactly(group, nested_group)
end
it 'returns projects regardless of the casing of paths' do
result = described_class.where_paths_in([group.to_param.upcase, nested_group.to_param.upcase])
expect(result).to contain_exactly(group, nested_group)
end
end
end
end
...@@ -124,4 +124,12 @@ describe Namespace, models: true do ...@@ -124,4 +124,12 @@ describe Namespace, models: true do
expect(Namespace.clean_path("--%+--valid_*&%name=.git.%.atom.atom.@email.com")).to eq("valid_name") expect(Namespace.clean_path("--%+--valid_*&%name=.git.%.atom.atom.@email.com")).to eq("valid_name")
end end
end end
describe '#full_path' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
it { expect(group.full_path).to eq(group.path) }
it { expect(nested_group.full_path).to eq("#{group.path}/#{nested_group.path}") }
end
end end
...@@ -478,35 +478,6 @@ describe Project, models: true do ...@@ -478,35 +478,6 @@ describe Project, models: true do
end end
end end
describe '.find_with_namespace' do
context 'with namespace' do
before do
@group = create :group, name: 'gitlab'
@project = create(:project, name: 'gitlabhq', namespace: @group)
end
it { expect(Project.find_with_namespace('gitlab/gitlabhq')).to eq(@project) }
it { expect(Project.find_with_namespace('GitLab/GitlabHQ')).to eq(@project) }
it { expect(Project.find_with_namespace('gitlab-ci')).to be_nil }
end
context 'when multiple projects using a similar name exist' do
let(:group) { create(:group, name: 'gitlab') }
let!(:project1) do
create(:empty_project, name: 'gitlab1', path: 'gitlab', namespace: group)
end
let!(:project2) do
create(:empty_project, name: 'gitlab2', path: 'GITLAB', namespace: group)
end
it 'returns the row where the path matches literally' do
expect(Project.find_with_namespace('gitlab/GITLAB')).to eq(project2)
end
end
end
describe '#to_param' do describe '#to_param' do
context 'with namespace' do context 'with namespace' do
before do before do
...@@ -1548,39 +1519,6 @@ describe Project, models: true do ...@@ -1548,39 +1519,6 @@ describe Project, models: true do
end end
end end
describe '.where_paths_in' do
context 'without any paths' do
it 'returns an empty relation' do
expect(Project.where_paths_in([])).to eq([])
end
end
context 'without any valid paths' do
it 'returns an empty relation' do
expect(Project.where_paths_in(%w[foo])).to eq([])
end
end
context 'with valid paths' do
let!(:project1) { create(:project) }
let!(:project2) { create(:project) }
it 'returns the projects matching the paths' do
projects = Project.where_paths_in([project1.path_with_namespace,
project2.path_with_namespace])
expect(projects).to contain_exactly(project1, project2)
end
it 'returns projects regardless of the casing of paths' do
projects = Project.where_paths_in([project1.path_with_namespace.upcase,
project2.path_with_namespace.upcase])
expect(projects).to contain_exactly(project1, project2)
end
end
end
describe 'change_head' do describe 'change_head' do
let(:project) { create(:project) } let(:project) { create(:project) }
......
require 'spec_helper'
describe Route, models: true do
let!(:group) { create(:group) }
let!(:route) { group.route }
describe 'relationships' do
it { is_expected.to belong_to(:source) }
end
describe 'validations' do
it { is_expected.to validate_presence_of(:source) }
it { is_expected.to validate_presence_of(:path) }
it { is_expected.to validate_uniqueness_of(:path) }
end
describe '#rename_children' do
let!(:nested_group) { create(:group, path: "test", parent: group) }
let!(:deep_nested_group) { create(:group, path: "foo", parent: nested_group) }
it "updates children routes with new path" do
route.update_attributes(path: 'bar')
expect(described_class.exists?(path: 'bar')).to be_truthy
expect(described_class.exists?(path: 'bar/test')).to be_truthy
expect(described_class.exists?(path: 'bar/test/foo')).to be_truthy
end
end
end
...@@ -153,85 +153,144 @@ describe API::Helpers, api: true do ...@@ -153,85 +153,144 @@ describe API::Helpers, api: true do
end end
end end
it "changes current user to sudo when admin" do context 'sudo usage' do
context 'with admin' do
context 'with header' do
context 'with id' do
it 'changes current_user to sudo' do
set_env(admin, user.id) set_env(admin, user.id)
expect(current_user).to eq(user)
set_param(admin, user.id)
expect(current_user).to eq(user)
set_env(admin, user.username)
expect(current_user).to eq(user)
set_param(admin, user.username)
expect(current_user).to eq(user) expect(current_user).to eq(user)
end end
it "throws an error when the current user is not an admin and attempting to sudo" do it 'handles sudo to oneself' do
set_env(user, admin.id) set_env(admin, admin.id)
expect { current_user }.to raise_error(Exception)
set_param(user, admin.id) expect(current_user).to eq(admin)
expect { current_user }.to raise_error(Exception)
set_env(user, admin.username)
expect { current_user }.to raise_error(Exception)
set_param(user, admin.username)
expect { current_user }.to raise_error(Exception)
end end
it "throws an error when the user cannot be found for a given id" do it 'throws an error when user cannot be found' do
id = user.id + admin.id id = user.id + admin.id
expect(user.id).not_to eq(id) expect(user.id).not_to eq(id)
expect(admin.id).not_to eq(id) expect(admin.id).not_to eq(id)
set_env(admin, id) set_env(admin, id)
expect { current_user }.to raise_error(Exception)
set_param(admin, id)
expect { current_user }.to raise_error(Exception) expect { current_user }.to raise_error(Exception)
end end
end
context 'with username' do
it 'changes current_user to sudo' do
set_env(admin, user.username)
expect(current_user).to eq(user)
end
it 'handles sudo to oneself' do
set_env(admin, admin.username)
expect(current_user).to eq(admin)
end
it "throws an error when the user cannot be found for a given username" do it "throws an error when the user cannot be found for a given username" do
username = "#{user.username}#{admin.username}" username = "#{user.username}#{admin.username}"
expect(user.username).not_to eq(username) expect(user.username).not_to eq(username)
expect(admin.username).not_to eq(username) expect(admin.username).not_to eq(username)
set_env(admin, username) set_env(admin, username)
expect { current_user }.to raise_error(Exception)
set_param(admin, username)
expect { current_user }.to raise_error(Exception) expect { current_user }.to raise_error(Exception)
end end
end
end
it "handles sudo's to oneself" do context 'with param' do
set_env(admin, admin.id) context 'with id' do
expect(current_user).to eq(admin) it 'changes current_user to sudo' do
set_param(admin, user.id)
expect(current_user).to eq(user)
end
it 'handles sudo to oneself' do
set_param(admin, admin.id) set_param(admin, admin.id)
expect(current_user).to eq(admin)
set_env(admin, admin.username)
expect(current_user).to eq(admin)
set_param(admin, admin.username)
expect(current_user).to eq(admin) expect(current_user).to eq(admin)
end end
it "handles multiple sudo's to oneself" do it 'handles sudo to oneself using string' do
set_env(admin, user.id) set_env(admin, user.id.to_s)
expect(current_user).to eq(user)
expect(current_user).to eq(user)
set_env(admin, user.username)
expect(current_user).to eq(user)
expect(current_user).to eq(user)
set_param(admin, user.id)
expect(current_user).to eq(user)
expect(current_user).to eq(user) expect(current_user).to eq(user)
end
it 'throws an error when user cannot be found' do
id = user.id + admin.id
expect(user.id).not_to eq(id)
expect(admin.id).not_to eq(id)
set_param(admin, id)
expect { current_user }.to raise_error(Exception)
end
end
context 'with username' do
it 'changes current_user to sudo' do
set_param(admin, user.username) set_param(admin, user.username)
expect(current_user).to eq(user)
expect(current_user).to eq(user) expect(current_user).to eq(user)
end end
it "handles multiple sudo's to oneself using string ids" do it 'handles sudo to oneself' do
set_env(admin, user.id.to_s) set_param(admin, admin.username)
expect(current_user).to eq(user)
expect(current_user).to eq(user)
set_param(admin, user.id.to_s) expect(current_user).to eq(admin)
expect(current_user).to eq(user) end
expect(current_user).to eq(user)
it "throws an error when the user cannot be found for a given username" do
username = "#{user.username}#{admin.username}"
expect(user.username).not_to eq(username)
expect(admin.username).not_to eq(username)
set_param(admin, username)
expect { current_user }.to raise_error(Exception)
end
end
end
end
context 'with regular user' do
context 'with env' do
it 'changes current_user to sudo when admin and user id' do
set_env(user, admin.id)
expect { current_user }.to raise_error(Exception)
end
it 'changes current_user to sudo when admin and user username' do
set_env(user, admin.username)
expect { current_user }.to raise_error(Exception)
end
end
context 'with params' do
it 'changes current_user to sudo when admin and user id' do
set_param(user, admin.id)
expect { current_user }.to raise_error(Exception)
end
it 'changes current_user to sudo when admin and user username' do
set_param(user, admin.username)
expect { current_user }.to raise_error(Exception)
end
end
end
end end
end end
......
...@@ -15,6 +15,31 @@ describe API::Tags, api: true do ...@@ -15,6 +15,31 @@ describe API::Tags, api: true do
let(:tag_name) { project.repository.tag_names.sort.reverse.first } let(:tag_name) { project.repository.tag_names.sort.reverse.first }
let(:description) { 'Awesome release!' } let(:description) { 'Awesome release!' }
shared_examples_for 'repository tags' do
it 'returns the repository tags' do
get api("/projects/#{project.id}/repository/tags", current_user)
expect(response).to have_http_status(200)
first_tag = json_response.first
expect(first_tag['name']).to eq(tag_name)
end
end
context 'when unauthenticated' do
it_behaves_like 'repository tags' do
let(:project) { create(:project, :public) }
let(:current_user) { nil }
end
end
context 'when authenticated' do
it_behaves_like 'repository tags' do
let(:current_user) { user }
end
end
context 'without releases' do context 'without releases' do
it "returns an array of project tags" do it "returns an array of project tags" do
get api("/projects/#{project.id}/repository/tags", user) get api("/projects/#{project.id}/repository/tags", user)
...@@ -45,20 +70,36 @@ describe API::Tags, api: true do ...@@ -45,20 +70,36 @@ describe API::Tags, api: true do
describe 'GET /projects/:id/repository/tags/:tag_name' do describe 'GET /projects/:id/repository/tags/:tag_name' do
let(:tag_name) { project.repository.tag_names.sort.reverse.first } let(:tag_name) { project.repository.tag_names.sort.reverse.first }
it 'returns a specific tag' do shared_examples_for 'repository tag' do
get api("/projects/#{project.id}/repository/tags/#{tag_name}", user) it 'returns the repository tag' do
get api("/projects/#{project.id}/repository/tags/#{tag_name}", current_user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['name']).to eq(tag_name) expect(json_response['name']).to eq(tag_name)
end end
it 'returns 404 for an invalid tag name' do it 'returns 404 for an invalid tag name' do
get api("/projects/#{project.id}/repository/tags/foobar", user) get api("/projects/#{project.id}/repository/tags/foobar", current_user)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
end end
context 'when unauthenticated' do
it_behaves_like 'repository tag' do
let(:project) { create(:project, :public) }
let(:current_user) { nil }
end
end
context 'when authenticated' do
it_behaves_like 'repository tag' do
let(:current_user) { user }
end
end
end
describe 'POST /projects/:id/repository/tags' do describe 'POST /projects/:id/repository/tags' do
context 'lightweight tags' do context 'lightweight tags' do
it 'creates a new tag' do it 'creates a new tag' do
......
...@@ -651,22 +651,77 @@ describe API::Users, api: true do ...@@ -651,22 +651,77 @@ describe API::Users, api: true do
end end
describe "GET /user" do describe "GET /user" do
it "returns current user" do let(:personal_access_token) { create(:personal_access_token, user: user) }
let(:private_token) { user.private_token }
context 'with regular user' do
context 'with personal access token' do
it 'returns 403 without private token when sudo is defined' do
get api("/user?private_token=#{personal_access_token.token}&sudo=#{user.id}")
expect(response).to have_http_status(403)
end
end
context 'with private token' do
it 'returns 403 without private token when sudo defined' do
get api("/user?private_token=#{private_token}&sudo=#{user.id}")
expect(response).to have_http_status(403)
end
end
it 'returns current user without private token when sudo not defined' do
get api("/user", user) get api("/user", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['email']).to eq(user.email) expect(response).to match_response_schema('user/public')
expect(json_response['is_admin']).to eq(user.is_admin?) end
expect(json_response['can_create_project']).to eq(user.can_create_project?) end
expect(json_response['can_create_group']).to eq(user.can_create_group?)
expect(json_response['projects_limit']).to eq(user.projects_limit) context 'with admin' do
expect(json_response['private_token']).to be_blank let(:user) { create(:admin) }
context 'with personal access token' do
it 'returns 403 without private token when sudo defined' do
get api("/user?private_token=#{personal_access_token.token}&sudo=#{user.id}")
expect(response).to have_http_status(403)
end
it 'returns current user without private token when sudo not defined' do
get api("/user?private_token=#{personal_access_token.token}")
expect(response).to have_http_status(200)
expect(response).to match_response_schema('user/public')
end
end
context 'with private token' do
it 'returns current user with private token when sudo defined' do
get api("/user?private_token=#{private_token}&sudo=#{user.id}")
expect(response).to have_http_status(200)
expect(response).to match_response_schema('user/login')
end end
it 'returns current user without private token when sudo not defined' do
get api("/user?private_token=#{private_token}")
expect(response).to have_http_status(200)
expect(response).to match_response_schema('user/public')
end
end
end
context 'with unauthenticated user' do
it "returns 401 error if user is unauthenticated" do it "returns 401 error if user is unauthenticated" do
get api("/user") get api("/user")
expect(response).to have_http_status(401) expect(response).to have_http_status(401)
end end
end end
end
describe "GET /user/keys" do describe "GET /user/keys" do
context "when unauthenticated" do context "when unauthenticated" do
......
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