Commit 1e878fd7 authored by Brian Neel's avatar Brian Neel

Merge branch '9-5-stable' into 'security-9-5'

Merge 9-5-stable into security-9-5

See merge request gitlab/gitlabhq!2184
parents f161f1ad 789cc678
...@@ -2,6 +2,24 @@ ...@@ -2,6 +2,24 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 9.5.3 (2017-09-03)
- [SECURITY] Filter additional secrets from Rails logs.
- [FIXED] Make username update fail if the namespace update fails. !13642
- [FIXED] Fix failure when issue is authored by a deleted user. !13807
- [FIXED] Reverts changes made to signin_enabled. !13956
- [FIXED] Fix Merge when pipeline succeeds button dropdown caret icon horizontal alignment.
- [FIXED] Fixed diff changes bar buttons from showing/hiding whilst scrolling.
- [FIXED] Fix events error importing GitLab projects.
- [FIXED] Fix pipeline trigger via API fails with 500 Internal Server Error in 9.5.
- [FIXED] Fixed fly-out nav flashing in & out.
- [FIXED] Remove closing external issues by reference error.
- [FIXED] Re-allow appearances.description_html to be NULL.
- [CHANGED] Update and fix resolvable note icons for easier recognition.
- [OTHER] Eager load head pipeline projects for MRs index.
- [OTHER] Instrument MergeRequest#fetch_ref.
- [OTHER] Instrument MergeRequest#ensure_ref_fetched.
## 9.5.2 (2017-08-28) ## 9.5.2 (2017-08-28)
- [FIXED] Fix signing in using LDAP when attribute mapping uses simple strings instead of arrays. - [FIXED] Fix signing in using LDAP when attribute mapping uses simple strings instead of arrays.
......
...@@ -289,7 +289,7 @@ group :metrics do ...@@ -289,7 +289,7 @@ group :metrics do
gem 'influxdb', '~> 0.2', require: false gem 'influxdb', '~> 0.2', require: false
# Prometheus # Prometheus
gem 'prometheus-client-mmap', '~>0.7.0.beta11' gem 'prometheus-client-mmap', '~>0.7.0.beta14'
gem 'raindrops', '~> 0.18' gem 'raindrops', '~> 0.18'
end end
......
...@@ -610,7 +610,12 @@ GEM ...@@ -610,7 +610,12 @@ GEM
premailer-rails (1.9.7) premailer-rails (1.9.7)
actionmailer (>= 3, < 6) actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9) premailer (~> 1.7, >= 1.7.9)
prometheus-client-mmap (0.7.0.beta11) proc_to_ast (0.1.0)
coderay
parser
unparser
procto (0.0.3)
prometheus-client-mmap (0.7.0.beta14)
mmap2 (~> 2.2, >= 2.2.7) mmap2 (~> 2.2, >= 2.2.7)
pry (0.10.4) pry (0.10.4)
coderay (~> 1.1.0) coderay (~> 1.1.0)
...@@ -1066,7 +1071,7 @@ DEPENDENCIES ...@@ -1066,7 +1071,7 @@ DEPENDENCIES
pg (~> 0.18.2) pg (~> 0.18.2)
poltergeist (~> 1.9.0) poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.7) premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.7.0.beta11) prometheus-client-mmap (~> 0.7.0.beta14)
pry-byebug (~> 3.4.1) pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4) pry-rails (~> 0.3.4)
rack-attack (~> 4.4.1) rack-attack (~> 4.4.1)
......
import bp from './breakpoints'; import bp from './breakpoints';
let headerHeight = 50; const HIDE_INTERVAL_TIMEOUT = 300;
const IS_OVER_CLASS = 'is-over';
const IS_ABOVE_CLASS = 'is-above';
const IS_SHOWING_FLY_OUT_CLASS = 'is-showing-fly-out';
let currentOpenMenu = null;
let menuCornerLocs;
let timeoutId;
let sidebar; let sidebar;
export const mousePos = [];
export const setSidebar = (el) => { sidebar = el; }; export const setSidebar = (el) => { sidebar = el; };
export const getOpenMenu = () => currentOpenMenu;
export const setOpenMenu = (menu = null) => { currentOpenMenu = menu; };
export const slope = (a, b) => (b.y - a.y) / (b.x - a.x);
let headerHeight = 50;
export const getHeaderHeight = () => headerHeight; export const getHeaderHeight = () => headerHeight;
...@@ -14,8 +28,28 @@ export const canShowActiveSubItems = (el) => { ...@@ -14,8 +28,28 @@ export const canShowActiveSubItems = (el) => {
return true; return true;
}; };
export const canShowSubItems = () => bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md' || bp.getBreakpointSize() === 'lg'; export const canShowSubItems = () => bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md' || bp.getBreakpointSize() === 'lg';
export const getHideSubItemsInterval = () => {
if (!currentOpenMenu) return 0;
const currentMousePos = mousePos[mousePos.length - 1];
const prevMousePos = mousePos[0];
const currentMousePosY = currentMousePos.y;
const [menuTop, menuBottom] = menuCornerLocs;
if (currentMousePosY < menuTop.y ||
currentMousePosY > menuBottom.y) return 0;
if (slope(prevMousePos, menuBottom) < slope(currentMousePos, menuBottom) &&
slope(prevMousePos, menuTop) > slope(currentMousePos, menuTop)) {
return HIDE_INTERVAL_TIMEOUT;
}
return 0;
};
export const calculateTop = (boundingRect, outerHeight) => { export const calculateTop = (boundingRect, outerHeight) => {
const windowHeight = window.innerHeight; const windowHeight = window.innerHeight;
const bottomOverflow = windowHeight - (boundingRect.top + outerHeight); const bottomOverflow = windowHeight - (boundingRect.top + outerHeight);
...@@ -24,51 +58,125 @@ export const calculateTop = (boundingRect, outerHeight) => { ...@@ -24,51 +58,125 @@ export const calculateTop = (boundingRect, outerHeight) => {
boundingRect.top; boundingRect.top;
}; };
export const showSubLevelItems = (el) => { export const hideMenu = (el) => {
const subItems = el.querySelector('.sidebar-sub-level-items'); if (!el) return;
if (!subItems || !canShowSubItems() || !canShowActiveSubItems(el)) return; const parentEl = el.parentNode;
subItems.style.display = 'block'; el.style.display = ''; // eslint-disable-line no-param-reassign
el.classList.add('is-showing-fly-out'); el.style.transform = ''; // eslint-disable-line no-param-reassign
el.classList.add('is-over'); el.classList.remove(IS_ABOVE_CLASS);
parentEl.classList.remove(IS_OVER_CLASS);
parentEl.classList.remove(IS_SHOWING_FLY_OUT_CLASS);
setOpenMenu();
};
export const moveSubItemsToPosition = (el, subItems) => {
const boundingRect = el.getBoundingClientRect(); const boundingRect = el.getBoundingClientRect();
const top = calculateTop(boundingRect, subItems.offsetHeight); const top = calculateTop(boundingRect, subItems.offsetHeight);
const isAbove = top < boundingRect.top; const isAbove = top < boundingRect.top;
subItems.classList.add('fly-out-list'); subItems.classList.add('fly-out-list');
subItems.style.transform = `translate3d(0, ${Math.floor(top) - headerHeight}px, 0)`; subItems.style.transform = `translate3d(0, ${Math.floor(top) - headerHeight}px, 0)`; // eslint-disable-line no-param-reassign
const subItemsRect = subItems.getBoundingClientRect();
menuCornerLocs = [
{
x: subItemsRect.left, // left position of the sub items
y: subItemsRect.top, // top position of the sub items
},
{
x: subItemsRect.left, // left position of the sub items
y: subItemsRect.top + subItemsRect.height, // bottom position of the sub items
},
];
if (isAbove) { if (isAbove) {
subItems.classList.add('is-above'); subItems.classList.add(IS_ABOVE_CLASS);
} }
}; };
export const hideSubLevelItems = (el) => { export const showSubLevelItems = (el) => {
const subItems = el.querySelector('.sidebar-sub-level-items'); const subItems = el.querySelector('.sidebar-sub-level-items');
if (!subItems || !canShowSubItems() || !canShowActiveSubItems(el)) return; if (!canShowSubItems() || !canShowActiveSubItems(el)) return;
el.classList.remove('is-showing-fly-out'); el.classList.add(IS_OVER_CLASS);
el.classList.remove('is-over');
subItems.style.display = ''; if (!subItems) return;
subItems.style.transform = '';
subItems.classList.remove('is-above'); subItems.style.display = 'block';
el.classList.add(IS_SHOWING_FLY_OUT_CLASS);
setOpenMenu(subItems);
moveSubItemsToPosition(el, subItems);
}; };
export default () => { export const mouseEnterTopItems = (el) => {
const items = [...document.querySelectorAll('.sidebar-top-level-items > li')] clearTimeout(timeoutId);
.filter(el => el.querySelector('.sidebar-sub-level-items'));
timeoutId = setTimeout(() => {
if (currentOpenMenu) hideMenu(currentOpenMenu);
showSubLevelItems(el);
}, getHideSubItemsInterval());
};
export const mouseLeaveTopItem = (el) => {
const subItems = el.querySelector('.sidebar-sub-level-items');
if (!canShowSubItems() || !canShowActiveSubItems(el) ||
(subItems && subItems === currentOpenMenu)) return;
el.classList.remove(IS_OVER_CLASS);
};
export const documentMouseMove = (e) => {
mousePos.push({
x: e.clientX,
y: e.clientY,
});
if (mousePos.length > 6) mousePos.shift();
};
export const subItemsMouseLeave = (relatedTarget) => {
clearTimeout(timeoutId);
if (!relatedTarget.closest(`.${IS_OVER_CLASS}`)) {
hideMenu(currentOpenMenu);
}
};
export default () => {
sidebar = document.querySelector('.nav-sidebar'); sidebar = document.querySelector('.nav-sidebar');
if (sidebar) { if (!sidebar) return;
headerHeight = sidebar.offsetTop;
items.forEach((el) => { const items = [...sidebar.querySelectorAll('.sidebar-top-level-items > li')];
el.addEventListener('mouseenter', e => showSubLevelItems(e.currentTarget));
el.addEventListener('mouseleave', e => hideSubLevelItems(e.currentTarget)); sidebar.querySelector('.sidebar-top-level-items').addEventListener('mouseleave', () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
if (currentOpenMenu) hideMenu(currentOpenMenu);
}, getHideSubItemsInterval());
}); });
headerHeight = document.querySelector('.nav-sidebar').offsetTop;
items.forEach((el) => {
const subItems = el.querySelector('.sidebar-sub-level-items');
if (subItems) {
subItems.addEventListener('mouseleave', e => subItemsMouseLeave(e.relatedTarget));
} }
el.addEventListener('mouseenter', e => mouseEnterTopItems(e.currentTarget));
el.addEventListener('mouseleave', e => mouseLeaveTopItem(e.currentTarget));
});
document.addEventListener('mousemove', documentMouseMove);
}; };
export const isSticky = (el, scrollY, stickyTop) => { export const isSticky = (el, scrollY, stickyTop) => {
const top = el.offsetTop - scrollY; const top = Math.floor(el.offsetTop - scrollY);
if (top <= stickyTop) { if (top <= stickyTop) {
el.classList.add('is-stuck'); el.classList.add('is-stuck');
......
...@@ -253,6 +253,7 @@ import bp from './breakpoints'; ...@@ -253,6 +253,7 @@ import bp from './breakpoints';
loadDiff(source) { loadDiff(source) {
if (this.diffsLoaded) { if (this.diffsLoaded) {
document.dispatchEvent(new CustomEvent('scroll'));
return; return;
} }
......
...@@ -47,7 +47,6 @@ export default class NewNavSidebar { ...@@ -47,7 +47,6 @@ export default class NewNavSidebar {
if (this.$sidebar.length) { if (this.$sidebar.length) {
this.$sidebar.toggleClass('sidebar-icons-only', collapsed); this.$sidebar.toggleClass('sidebar-icons-only', collapsed);
this.$page.toggleClass('page-with-new-sidebar', !collapsed);
this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed); this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed);
} }
NewNavSidebar.setCollapsedCookie(collapsed); NewNavSidebar.setCollapsedCookie(collapsed);
......
...@@ -57,10 +57,10 @@ export default class ProjectSelectComboButton { ...@@ -57,10 +57,10 @@ export default class ProjectSelectComboButton {
setNewItemBtnAttributes(project) { setNewItemBtnAttributes(project) {
if (project) { if (project) {
this.newItemBtn.attr('href', project.url); this.newItemBtn.attr('href', project.url);
this.newItemBtn.text(`${this.newItemBtnBaseText} in ${project.name}`); this.newItemBtn.text(`New ${this.deriveItemTypeFromLabel()} in ${project.name}`);
this.newItemBtn.enable(); this.newItemBtn.enable();
} else { } else {
this.newItemBtn.text(`Select project to create ${this.itemType}`); this.newItemBtn.text(`Select project to create ${this.deriveItemTypeFromLabel()}`);
this.newItemBtn.disable(); this.newItemBtn.disable();
} }
} }
......
...@@ -189,7 +189,7 @@ ...@@ -189,7 +189,7 @@
width: auto; width: auto;
top: 100%; top: 100%;
left: 0; left: 0;
z-index: 9; z-index: 200;
min-width: 240px; min-width: 240px;
max-width: 500px; max-width: 500px;
margin-top: 2px; margin-top: 2px;
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
min-width: inherit; min-width: inherit;
min-height: inherit; min-height: inherit;
background-color: inherit; background-color: inherit;
max-width: 100%;
} }
p a:not(.no-attachment-icon) img { p a:not(.no-attachment-icon) img {
......
...@@ -118,6 +118,7 @@ $gl-text-color-quaternary: #d6d6d6; ...@@ -118,6 +118,7 @@ $gl-text-color-quaternary: #d6d6d6;
$gl-text-color-inverted: rgba(255, 255, 255, 1.0); $gl-text-color-inverted: rgba(255, 255, 255, 1.0);
$gl-text-color-secondary-inverted: rgba(255, 255, 255, .85); $gl-text-color-secondary-inverted: rgba(255, 255, 255, .85);
$gl-text-green: $green-600; $gl-text-green: $green-600;
$gl-text-green-hover: $green-700;
$gl-text-red: $red-500; $gl-text-red: $red-500;
$gl-text-orange: $orange-600; $gl-text-orange: $orange-600;
$gl-link-color: $blue-600; $gl-link-color: $blue-600;
......
...@@ -70,7 +70,8 @@ $new-sidebar-collapsed-width: 50px; ...@@ -70,7 +70,8 @@ $new-sidebar-collapsed-width: 50px;
background-color: $white-light; background-color: $white-light;
} }
.sidebar-context-title { .project-title,
.group-title {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
...@@ -96,29 +97,21 @@ $new-sidebar-collapsed-width: 50px; ...@@ -96,29 +97,21 @@ $new-sidebar-collapsed-width: 50px;
top: $header-height; top: $header-height;
bottom: 0; bottom: 0;
left: 0; left: 0;
overflow: auto;
background-color: $gray-normal; background-color: $gray-normal;
box-shadow: inset -2px 0 0 $border-color; box-shadow: inset -2px 0 0 $border-color;
transform: translate3d(0, 0, 0);
&.sidebar-icons-only { &.sidebar-icons-only {
width: $new-sidebar-collapsed-width; width: $new-sidebar-collapsed-width;
overflow-x: hidden; overflow-x: hidden;
.nav-sidebar-inner-scroll {
overflow-x: hidden;
}
.badge, .badge,
.sidebar-context-title { .project-title {
display: none; display: none;
} }
.nav-item-name { .nav-item-name {
display: none; opacity: 0;
}
.sidebar-top-level-items > li > a {
min-height: 44px;
} }
} }
...@@ -183,12 +176,6 @@ $new-sidebar-collapsed-width: 50px; ...@@ -183,12 +176,6 @@ $new-sidebar-collapsed-width: 50px;
} }
} }
.nav-sidebar-inner-scroll {
height: 100%;
width: 100%;
overflow: auto;
}
.with-performance-bar .nav-sidebar { .with-performance-bar .nav-sidebar {
top: $header-height + $performance-bar-height; top: $header-height + $performance-bar-height;
} }
...@@ -263,32 +250,13 @@ $new-sidebar-collapsed-width: 50px; ...@@ -263,32 +250,13 @@ $new-sidebar-collapsed-width: 50px;
position: absolute; position: absolute;
top: -30px; top: -30px;
bottom: -30px; bottom: -30px;
left: 0; left: -10px;
right: -30px; right: -30px;
z-index: -1; z-index: -1;
} }
&::after {
content: "";
position: absolute;
top: 44px;
left: -30px;
right: 35px;
bottom: 0;
height: 100%;
max-height: 150px;
z-index: -1;
transform: skew(33deg);
}
&.is-above { &.is-above {
margin-top: 1px; margin-top: 1px;
&::after {
top: auto;
bottom: 44px;
transform: skew(-30deg);
}
} }
> .active { > .active {
...@@ -335,8 +303,7 @@ $new-sidebar-collapsed-width: 50px; ...@@ -335,8 +303,7 @@ $new-sidebar-collapsed-width: 50px;
} }
} }
&:not(.active):hover > a, &.active > a:hover,
> a:hover,
&.is-over > a { &.is-over > a {
background-color: $white-light; background-color: $white-light;
} }
......
...@@ -291,6 +291,7 @@ ...@@ -291,6 +291,7 @@
.dropdown-toggle { .dropdown-toggle {
.fa { .fa {
margin-left: 0;
color: inherit; color: inherit;
} }
} }
......
...@@ -766,17 +766,25 @@ ul.notes { ...@@ -766,17 +766,25 @@ ul.notes {
background-color: transparent; background-color: transparent;
border: none; border: none;
outline: 0; outline: 0;
transition: color $general-hover-transition-duration $general-hover-transition-curve;
&.is-disabled { &.is-disabled {
cursor: default; cursor: default;
} }
&:not(.is-disabled):hover, &:not(.is-disabled) {
&:hover,
&:focus {
color: $gl-text-green;
}
}
&.is-active { &.is-active {
color: $gl-text-green; color: $gl-text-green;
svg { &:hover,
fill: $gl-text-green; &:focus {
color: $gl-text-green-hover;
} }
} }
......
...@@ -202,7 +202,7 @@ class ApplicationController < ActionController::Base ...@@ -202,7 +202,7 @@ class ApplicationController < ActionController::Base
end end
def check_password_expiration def check_password_expiration
if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && current_user.allow_password_authentication? if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user?
return redirect_to new_profile_password_path return redirect_to new_profile_password_path
end end
end end
......
...@@ -15,7 +15,17 @@ module IssuableCollections ...@@ -15,7 +15,17 @@ module IssuableCollections
end end
def merge_requests_collection def merge_requests_collection
merge_requests_finder.execute.preload(:source_project, :target_project, :author, :assignee, :labels, :milestone, :head_pipeline, target_project: :namespace, merge_request_diff: :merge_request_diff_commits) merge_requests_finder.execute.preload(
:source_project,
:target_project,
:author,
:assignee,
:labels,
:milestone,
head_pipeline: :project,
target_project: :namespace,
merge_request_diff: :merge_request_diff_commits
)
end end
def issues_finder def issues_finder
......
class PasswordsController < Devise::PasswordsController class PasswordsController < Devise::PasswordsController
include Gitlab::CurrentSettings
before_action :resource_from_email, only: [:create] before_action :resource_from_email, only: [:create]
before_action :check_password_authentication_available, only: [:create] before_action :prevent_ldap_reset, only: [:create]
before_action :throttle_reset, only: [:create] before_action :throttle_reset, only: [:create]
def edit def edit
...@@ -40,11 +38,11 @@ class PasswordsController < Devise::PasswordsController ...@@ -40,11 +38,11 @@ class PasswordsController < Devise::PasswordsController
self.resource = resource_class.find_by_email(email) self.resource = resource_class.find_by_email(email)
end end
def check_password_authentication_available def prevent_ldap_reset
return if current_application_settings.password_authentication_enabled? && (resource.nil? || resource.allow_password_authentication?) return unless resource&.ldap_user?
redirect_to after_sending_reset_password_instructions_path_for(resource_name), redirect_to after_sending_reset_password_instructions_path_for(resource_name),
alert: "Password authentication is unavailable." alert: "Cannot reset password for LDAP user."
end end
def throttle_reset def throttle_reset
......
...@@ -77,7 +77,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController ...@@ -77,7 +77,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController
end end
def authorize_change_password! def authorize_change_password!
render_404 unless @user.allow_password_authentication? render_404 if @user.ldap_user?
end end
def user_params def user_params
......
...@@ -55,7 +55,8 @@ class Commit ...@@ -55,7 +55,8 @@ class Commit
end end
def from_hash(hash, project) def from_hash(hash, project)
new(Gitlab::Git::Commit.new(hash), project) raw_commit = Gitlab::Git::Commit.new(project.repository.raw, hash)
new(raw_commit, project)
end end
def valid_hash?(key) def valid_hash?(key)
......
...@@ -598,6 +598,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -598,6 +598,8 @@ class MergeRequest < ActiveRecord::Base
self.merge_requests_closing_issues.delete_all self.merge_requests_closing_issues.delete_all
closes_issues(current_user).each do |issue| closes_issues(current_user).each do |issue|
next if issue.is_a?(ExternalIssue)
self.merge_requests_closing_issues.create!(issue: issue) self.merge_requests_closing_issues.create!(issue: issue)
end end
end end
......
...@@ -282,9 +282,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -282,9 +282,7 @@ class MergeRequestDiff < ActiveRecord::Base
def load_commits def load_commits
commits = st_commits.presence || merge_request_diff_commits commits = st_commits.presence || merge_request_diff_commits
commits.map do |commit| commits.map { |commit| Commit.from_hash(commit.to_hash, project) }
Commit.new(Gitlab::Git::Commit.new(commit.to_hash), merge_request.source_project)
end
end end
def save_diffs def save_diffs
......
...@@ -19,46 +19,45 @@ class NotificationRecipient ...@@ -19,46 +19,45 @@ class NotificationRecipient
@notification_setting ||= find_notification_setting @notification_setting ||= find_notification_setting
end end
def raw_notification_level
notification_setting&.level&.to_sym
end
def notification_level def notification_level
# custom is treated the same as watch if it's enabled - otherwise it's @notification_level ||= notification_setting&.level&.to_sym
# set to :custom, meaning to send exactly when our type is :participating
# or :mention.
@notification_level ||=
case raw_notification_level
when :custom
if @custom_action && notification_setting&.event_enabled?(@custom_action)
:watch
else
:custom
end
else
raw_notification_level
end
end end
def notifiable? def notifiable?
return false unless has_access? return false unless has_access?
return false if own_activity? return false if own_activity?
return true if @type == :subscription # even users with :disabled notifications receive manual subscriptions
return !unsubscribed? if @type == :subscription
return false if notification_level.nil? || notification_level == :disabled
return %i[participating mention].include?(@type) if notification_level == :custom
return false if %i[watch participating].include?(notification_level) && excluded_watcher_action? return false unless suitable_notification_level?
return false unless NotificationSetting.levels[notification_level] <= NotificationSetting.levels[@type]
# check this last because it's expensive
# nobody should receive notifications if they've specifically unsubscribed
return false if unsubscribed? return false if unsubscribed?
true true
end end
def suitable_notification_level?
case notification_level
when :disabled, nil
false
when :custom
custom_enabled? || %i[participating mention].include?(@type)
when :watch, :participating
!excluded_watcher_action?
when :mention
@type == :mention
else
false
end
end
def custom_enabled?
@custom_action && notification_setting&.event_enabled?(@custom_action)
end
def unsubscribed? def unsubscribed?
return false unless @target return false unless @target
return false unless @target.respond_to?(:subscriptions) return false unless @target.respond_to?(:subscriptions)
...@@ -88,7 +87,7 @@ class NotificationRecipient ...@@ -88,7 +87,7 @@ class NotificationRecipient
def excluded_watcher_action? def excluded_watcher_action?
return false unless @custom_action return false unless @custom_action
return false if raw_notification_level == :custom return false if notification_level == :custom
NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(@custom_action) NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(@custom_action)
end end
......
...@@ -64,6 +64,8 @@ class Repository ...@@ -64,6 +64,8 @@ class Repository
@raw_repository ||= initialize_raw_repository @raw_repository ||= initialize_raw_repository
end end
alias_method :raw, :raw_repository
# Return absolute path to repository # Return absolute path to repository
def path_to_repo def path_to_repo
@path_to_repo ||= File.expand_path( @path_to_repo ||= File.expand_path(
......
...@@ -598,7 +598,7 @@ class User < ActiveRecord::Base ...@@ -598,7 +598,7 @@ class User < ActiveRecord::Base
end end
def require_personal_access_token_creation_for_git_auth? def require_personal_access_token_creation_for_git_auth?
return false if allow_password_authentication? || ldap_user? return false if current_application_settings.password_authentication_enabled? || ldap_user?
PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none? PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none?
end end
...@@ -834,7 +834,12 @@ class User < ActiveRecord::Base ...@@ -834,7 +834,12 @@ class User < ActiveRecord::Base
create_namespace!(path: username, name: username) unless namespace create_namespace!(path: username, name: username) unless namespace
if username_changed? if username_changed?
namespace.update_attributes(path: username, name: username) unless namespace.update_attributes(path: username, name: username)
namespace.errors.each do |attribute, message|
self.errors.add(:"namespace_#{attribute}", message)
end
raise ActiveRecord::RecordInvalid.new(namespace)
end
end end
end end
......
...@@ -15,7 +15,7 @@ module Ci ...@@ -15,7 +15,7 @@ module Ci
pipeline_schedule: schedule pipeline_schedule: schedule
) )
result = validate(current_user || trigger_request.trigger.owner, result = validate(current_user,
ignore_skip_ci: ignore_skip_ci, ignore_skip_ci: ignore_skip_ci,
save_on_errors: save_on_errors) save_on_errors: save_on_errors)
......
...@@ -95,7 +95,7 @@ module NotificationRecipientService ...@@ -95,7 +95,7 @@ module NotificationRecipientService
def add_participants(user) def add_participants(user)
return unless target.respond_to?(:participants) return unless target.respond_to?(:participants)
self << [target.participants(user), :watch] self << [target.participants(user), :participating]
end end
# Get project/group users with CUSTOM notification level # Get project/group users with CUSTOM notification level
......
...@@ -2,47 +2,16 @@ module TestHooks ...@@ -2,47 +2,16 @@ module TestHooks
class SystemService < TestHooks::BaseService class SystemService < TestHooks::BaseService
private private
def project
@project ||= begin
project = Project.first
throw(:validation_error, 'Ensure that at least one project exists.') unless project
project
end
end
def push_events_data def push_events_data
if project.empty_repo? Gitlab::DataBuilder::Push.sample_data
throw(:validation_error, "Ensure project \"#{project.human_name}\" has commits.")
end
Gitlab::DataBuilder::Push.build_sample(project, current_user)
end end
def tag_push_events_data def tag_push_events_data
if project.repository.tags.empty? Gitlab::DataBuilder::Push.sample_data
throw(:validation_error, "Ensure project \"#{project.human_name}\" has tags.")
end
Gitlab::DataBuilder::Push.build_sample(project, current_user)
end end
def repository_update_events_data def repository_update_events_data
commit = project.commit Gitlab::DataBuilder::Repository.sample_data
ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}"
unless commit
throw(:validation_error, "Ensure project \"#{project.human_name}\" has commits.")
end
change = Gitlab::DataBuilder::Repository.single_change(
commit.parent_id || Gitlab::Git::BLANK_SHA,
commit.id,
ref
)
Gitlab::DataBuilder::Repository.update(project, current_user, [change], [ref])
end end
end end
end end
...@@ -147,7 +147,7 @@ ...@@ -147,7 +147,7 @@
.checkbox .checkbox
= f.label :password_authentication_enabled do = f.label :password_authentication_enabled do
= f.check_box :password_authentication_enabled = f.check_box :password_authentication_enabled
Password authentication enabled Sign-in enabled
- if omniauth_enabled? && button_based_providers.any? - if omniauth_enabled? && button_based_providers.any?
.form-group .form-group
= f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'control-label col-sm-2' = f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'control-label col-sm-2'
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
= link_to profile_emails_path, title: 'Emails' do = link_to profile_emails_path, title: 'Emails' do
%span %span
Emails Emails
- if current_user.allow_password_authentication? - unless current_user.ldap_user?
= nav_link(controller: :passwords) do = nav_link(controller: :passwords) do
= link_to edit_profile_password_path, title: 'Password' do = link_to edit_profile_password_path, title: 'Password' do
%span %span
......
...@@ -32,7 +32,8 @@ ...@@ -32,7 +32,8 @@
%ul %ul
- if can_update_issue - if can_update_issue
%li= link_to 'Edit', edit_project_issue_path(@project, @issue) %li= link_to 'Edit', edit_project_issue_path(@project, @issue)
- unless current_user == @issue.author / TODO: simplify condition back #36860
- if @issue.author && current_user != @issue.author
%li= link_to 'Report abuse', new_abuse_report_path(user_id: @issue.author.id, ref_url: issue_url(@issue)) %li= link_to 'Report abuse', new_abuse_report_path(user_id: @issue.author.id, ref_url: issue_url(@issue))
- if can_update_issue - if can_update_issue
%li= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' %li= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
......
...@@ -26,8 +26,12 @@ ...@@ -26,8 +26,12 @@
":title" => "buttonText", ":title" => "buttonText",
":ref" => "'button'" } ":ref" => "'button'" }
= icon('spin spinner', 'v-show' => 'loading', class: 'loading', 'aria-hidden' => 'true', 'aria-label' => 'Loading') = icon('spin spinner', 'v-if' => 'loading', class: 'loading', 'aria-hidden' => 'true', 'aria-label' => 'Loading')
%div{ 'v-show' => '!loading' }= render 'shared/icons/icon_status_success.svg' %div{ 'v-else' => '' }
%template{ 'v-if' => 'isResolved' }
= render 'shared/icons/icon_status_success_solid.svg'
%template{ 'v-else' => '' }
= render 'shared/icons/icon_status_success.svg'
- if current_user - if current_user
- if note.emoji_awardable? - if note.emoji_awardable?
......
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M6.278 7.697L5.045 6.464a.296.296 0 0 0-.42-.002l-.613.614a.298.298 0 0 0 .002.42l1.91 1.909a.5.5 0 0 0 .703.005l.265-.265L9.997 6.04a.291.291 0 0 0-.009-.408l-.614-.614a.29.29 0 0 0-.408-.009L6.278 7.697z"/></g></svg> <svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill-rule="evenodd"/><path d="M6.278 7.697L5.045 6.464a.296.296 0 0 0-.42-.002l-.613.614a.298.298 0 0 0 .002.42l1.91 1.909a.5.5 0 0 0 .703.005l.265-.265L9.997 6.04a.291.291 0 0 0-.009-.408l-.614-.614a.29.29 0 0 0-.408-.009L6.278 7.697z"/></svg>
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z M6.278 7.697L5.045 6.464a.296.296 0 0 0-.42-.002l-.613.614a.298.298 0 0 0 .002.42l1.91 1.909a.5.5 0 0 0 .703.005l.265-.265L9.997 6.04a.291.291 0 0 0-.009-.408l-.614-.614a.29.29 0 0 0-.408-.009L6.278 7.697z" fill-rule="evenodd"/></svg>
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}" class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}"
- elsif can_update && !is_current_user - elsif can_update && !is_current_user
= render 'shared/issuable/close_reopen_report_toggle', issuable: issuable = render 'shared/issuable/close_reopen_report_toggle', issuable: issuable
- else - elsif issuable.author
/ TODO: change back to else #36860
= link_to 'Report abuse', new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)), = link_to 'Report abuse', new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
class: 'hidden-xs hidden-sm btn btn-grouped btn-close-color', title: 'Report abuse' class: 'hidden-xs hidden-sm btn btn-grouped btn-close-color', title: 'Report abuse'
...@@ -37,6 +37,8 @@ ...@@ -37,6 +37,8 @@
%li.divider.droplab-item-ignore %li.divider.droplab-item-ignore
/ TODO: remove condition #36860
- if issuable.author
%li.report-item{ data: { text: 'Report abuse', url: new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)), %li.report-item{ data: { text: 'Report abuse', url: new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
button_class: "#{button_class} btn-close-color", toggle_class: "#{toggle_class} btn-close-color", method: '' } } button_class: "#{button_class} btn-close-color", toggle_class: "#{toggle_class} btn-close-color", method: '' } }
%button.btn.btn-transparent %button.btn.btn-transparent
......
...@@ -51,31 +51,24 @@ module Gitlab ...@@ -51,31 +51,24 @@ module Gitlab
# Configure sensitive parameters which will be filtered from the log file. # Configure sensitive parameters which will be filtered from the log file.
# #
# Parameters filtered: # Parameters filtered:
# - Password (:password, :password_confirmation) # - Any parameter ending with `_token`
# - Private tokens # - Any parameter containing `password`
# - Any parameter containing `secret`
# - 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)
# - GitLab Pages SSL cert/key info (:certificate, :encrypted_key) # - GitLab Pages SSL cert/key info (:certificate, :encrypted_key)
# - Webhook URLs (:hook) # - Webhook URLs (:hook)
# - GitLab-shell secret token (:secret_token)
# - Sentry DSN (:sentry_dsn) # - Sentry DSN (:sentry_dsn)
# - Deploy keys (:key) # - Deploy keys (:key)
config.filter_parameters += [/_token$/, /password/, /secret/]
config.filter_parameters += %i( config.filter_parameters += %i(
authentication_token
certificate certificate
encrypted_key encrypted_key
hook hook
import_url import_url
incoming_email_token
rss_token
key key
otp_attempt otp_attempt
password
password_confirmation
private_token
runners_token
secret_token
sentry_dsn sentry_dsn
variables variables
) )
......
require 'prometheus/client' require 'prometheus/client'
require 'prometheus/client/support/unicorn'
Prometheus::Client.configure do |config| Prometheus::Client.configure do |config|
config.logger = Rails.logger config.logger = Rails.logger
...@@ -9,6 +10,8 @@ Prometheus::Client.configure do |config| ...@@ -9,6 +10,8 @@ Prometheus::Client.configure do |config|
if Rails.env.development? || Rails.env.test? if Rails.env.development? || Rails.env.test?
config.multiprocess_files_dir ||= Rails.root.join('tmp/prometheus_multiproc_dir') config.multiprocess_files_dir ||= Rails.root.join('tmp/prometheus_multiproc_dir')
end end
config.pid_provider = Prometheus::Client::Support::Unicorn.method(:worker_pid_provider)
end end
Sidekiq.configure_server do |config| Sidekiq.configure_server do |config|
......
...@@ -119,6 +119,10 @@ def instrument_classes(instrumentation) ...@@ -119,6 +119,10 @@ def instrument_classes(instrumentation)
# Needed for https://gitlab.com/gitlab-org/gitlab-ce/issues/30224#note_32306159 # Needed for https://gitlab.com/gitlab-org/gitlab-ce/issues/30224#note_32306159
instrumentation.instrument_instance_method(MergeRequestDiff, :load_commits) instrumentation.instrument_instance_method(MergeRequestDiff, :load_commits)
# Needed for https://gitlab.com/gitlab-org/gitlab-ce/issues/36061
instrumentation.instrument_instance_method(MergeRequest, :ensure_ref_fetched)
instrumentation.instrument_instance_method(MergeRequest, :fetch_ref)
end end
# rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/AbcSize
......
...@@ -7,7 +7,7 @@ class CleanupAppearancesSchema < ActiveRecord::Migration ...@@ -7,7 +7,7 @@ class CleanupAppearancesSchema < ActiveRecord::Migration
# Set this constant to true if this migration requires downtime. # Set this constant to true if this migration requires downtime.
DOWNTIME = false DOWNTIME = false
NOT_NULL_COLUMNS = %i[title description description_html created_at updated_at] NOT_NULL_COLUMNS = %i[title description created_at updated_at]
TIME_COLUMNS = %i[created_at updated_at] TIME_COLUMNS = %i[created_at updated_at]
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AllowAppearancesDescriptionHtmlNull < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def up
change_column_null :appearances, :description_html, true
end
def down
# This column should not have a `NOT NULL` class, so we don't want to revert
# back to re-adding it.
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: 20170820100558) do ActiveRecord::Schema.define(version: 20170824162758) 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"
...@@ -34,7 +34,7 @@ ActiveRecord::Schema.define(version: 20170820100558) do ...@@ -34,7 +34,7 @@ ActiveRecord::Schema.define(version: 20170820100558) do
t.string "logo" t.string "logo"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.text "description_html", null: false t.text "description_html"
t.integer "cached_markdown_version" t.integer "cached_markdown_version"
end end
......
...@@ -144,9 +144,8 @@ gitlab_rails['backup_upload_connection'] = { ...@@ -144,9 +144,8 @@ gitlab_rails['backup_upload_connection'] = {
'region' => 'eu-west-1', 'region' => 'eu-west-1',
'aws_access_key_id' => 'AKIAKIAKI', 'aws_access_key_id' => 'AKIAKIAKI',
'aws_secret_access_key' => 'secret123' 'aws_secret_access_key' => 'secret123'
# If using an IAM Profile, leave aws_access_key_id & aws_secret_access_key empty # If using an IAM Profile, don't configure aws_access_key_id & aws_secret_access_key
# ie. 'aws_access_key_id' => '', # 'use_iam_profile' => true
# 'use_iam_profile' => 'true'
} }
gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket' gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket'
``` ```
......
...@@ -48,10 +48,6 @@ module Gitlab ...@@ -48,10 +48,6 @@ module Gitlab
# Avoid resource intensive login checks if password is not provided # Avoid resource intensive login checks if password is not provided
return unless password.present? return unless password.present?
# Nothing to do here if internal auth is disabled and LDAP is
# not configured
return unless current_application_settings.password_authentication_enabled? || Gitlab::LDAP::Config.enabled?
Gitlab::Auth::UniqueIpsLimiter.limit_user! do Gitlab::Auth::UniqueIpsLimiter.limit_user! do
user = User.by_login(login) user = User.by_login(login)
......
...@@ -54,7 +54,7 @@ module Gitlab ...@@ -54,7 +54,7 @@ module Gitlab
end end
def serialize_commit(event, commit, query) def serialize_commit(event, commit, query)
commit = Commit.new(Gitlab::Git::Commit.new(commit.to_hash), @project) commit = Commit.from_hash(commit.to_hash, @project)
AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit) AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit)
end end
......
...@@ -3,6 +3,35 @@ module Gitlab ...@@ -3,6 +3,35 @@ module Gitlab
module Push module Push
extend self extend self
SAMPLE_DATA =
{
object_kind: "push",
event_name: "push",
before: "95790bf891e76fee5e1747ab589903a6a1f80f22",
after: "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
ref: "refs/heads/master",
checkout_sha: "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
message: "Hello World",
user_id: 4,
user_name: "John Smith",
user_email: "john@example.com",
user_avatar: "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
project_id: 15,
commits: [
{
id: "c5feabde2d8cd023215af4d2ceeb7a64839fc428",
message: "Add simple search to projects in public area",
timestamp: "2013-05-13T18:18:08+00:00",
url: "https://test.example.com/gitlab/gitlabhq/commit/c5feabde2d8cd023215af4d2ceeb7a64839fc428",
author: {
name: "Test User",
email: "test@example.com"
}
}
],
total_commits_count: 1
}.freeze
# Produce a hash of post-receive data # Produce a hash of post-receive data
# #
# data = { # data = {
...@@ -74,6 +103,10 @@ module Gitlab ...@@ -74,6 +103,10 @@ module Gitlab
build(project, user, commits.last&.id, commits.first&.id, ref, commits) build(project, user, commits.last&.id, commits.first&.id, ref, commits)
end end
def sample_data
SAMPLE_DATA
end
private private
def checkout_sha(repository, newrev, ref) def checkout_sha(repository, newrev, ref)
......
...@@ -3,6 +3,23 @@ module Gitlab ...@@ -3,6 +3,23 @@ module Gitlab
module Repository module Repository
extend self extend self
SAMPLE_DATA = {
event_name: 'repository_update',
user_id: 10,
user_name: 'john.doe',
user_email: 'test@example.com',
user_avatar: 'http://example.com/avatar/user.png',
project_id: 40,
changes: [
{
before: "8205ea8d81ce0c6b90fbe8280d118cc9fdad6130",
after: "4045ea7a3df38697b3730a20fb73c8bed8a3e69e",
ref: "refs/heads/master"
}
],
"refs": ["refs/heads/master"]
}.freeze
# Produce a hash of post-receive data # Produce a hash of post-receive data
def update(project, user, changes, refs) def update(project, user, changes, refs)
{ {
...@@ -30,6 +47,10 @@ module Gitlab ...@@ -30,6 +47,10 @@ module Gitlab
ref: ref ref: ref
} }
end end
def sample_data
SAMPLE_DATA
end
end end
end end
end end
...@@ -16,7 +16,7 @@ module Gitlab ...@@ -16,7 +16,7 @@ module Gitlab
def each def each
@blames.each do |blame| @blames.each do |blame|
yield( yield(
Gitlab::Git::Commit.new(blame.commit), Gitlab::Git::Commit.new(@repo, blame.commit),
blame.line blame.line
) )
end end
......
...@@ -51,7 +51,7 @@ module Gitlab ...@@ -51,7 +51,7 @@ module Gitlab
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/321 # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/321
def find(repo, commit_id = "HEAD") def find(repo, commit_id = "HEAD")
return commit_id if commit_id.is_a?(Gitlab::Git::Commit) return commit_id if commit_id.is_a?(Gitlab::Git::Commit)
return decorate(commit_id) if commit_id.is_a?(Rugged::Commit) return decorate(repo, commit_id) if commit_id.is_a?(Rugged::Commit)
obj = if commit_id.is_a?(String) obj = if commit_id.is_a?(String)
repo.rev_parse_target(commit_id) repo.rev_parse_target(commit_id)
...@@ -61,7 +61,7 @@ module Gitlab ...@@ -61,7 +61,7 @@ module Gitlab
return nil unless obj.is_a?(Rugged::Commit) return nil unless obj.is_a?(Rugged::Commit)
decorate(obj) decorate(repo, obj)
rescue Rugged::ReferenceError, Rugged::InvalidError, Rugged::ObjectError, Gitlab::Git::Repository::NoRepository rescue Rugged::ReferenceError, Rugged::InvalidError, Rugged::ObjectError, Gitlab::Git::Repository::NoRepository
nil nil
end end
...@@ -102,7 +102,7 @@ module Gitlab ...@@ -102,7 +102,7 @@ module Gitlab
if is_enabled if is_enabled
repo.gitaly_commit_client.between(base, head) repo.gitaly_commit_client.between(base, head)
else else
repo.rugged_commits_between(base, head).map { |c| decorate(c) } repo.rugged_commits_between(base, head).map { |c| decorate(repo, c) }
end end
end end
rescue Rugged::ReferenceError rescue Rugged::ReferenceError
...@@ -169,7 +169,7 @@ module Gitlab ...@@ -169,7 +169,7 @@ module Gitlab
offset = actual_options[:skip] offset = actual_options[:skip]
limit = actual_options[:max_count] limit = actual_options[:max_count]
walker.each(offset: offset, limit: limit) do |commit| walker.each(offset: offset, limit: limit) do |commit|
commits.push(decorate(commit)) commits.push(decorate(repo, commit))
end end
walker.reset walker.reset
...@@ -183,8 +183,8 @@ module Gitlab ...@@ -183,8 +183,8 @@ module Gitlab
Gitlab::GitalyClient::CommitService.new(repo).find_all_commits(options) Gitlab::GitalyClient::CommitService.new(repo).find_all_commits(options)
end end
def decorate(commit, ref = nil) def decorate(repository, commit, ref = nil)
Gitlab::Git::Commit.new(commit, ref) Gitlab::Git::Commit.new(repository, commit, ref)
end end
# Returns a diff object for the changes introduced by +rugged_commit+. # Returns a diff object for the changes introduced by +rugged_commit+.
...@@ -231,7 +231,7 @@ module Gitlab ...@@ -231,7 +231,7 @@ module Gitlab
end end
end end
def initialize(raw_commit, head = nil) def initialize(repository, raw_commit, head = nil)
raise "Nil as raw commit passed" unless raw_commit raise "Nil as raw commit passed" unless raw_commit
case raw_commit case raw_commit
...@@ -239,12 +239,13 @@ module Gitlab ...@@ -239,12 +239,13 @@ module Gitlab
init_from_hash(raw_commit) init_from_hash(raw_commit)
when Rugged::Commit when Rugged::Commit
init_from_rugged(raw_commit) init_from_rugged(raw_commit)
when Gitlab::GitalyClient::Commit when Gitaly::GitCommit
init_from_gitaly(raw_commit) init_from_gitaly(raw_commit)
else else
raise "Invalid raw commit type: #{raw_commit.class}" raise "Invalid raw commit type: #{raw_commit.class}"
end end
@repository = repository
@head = head @head = head
end end
...@@ -319,14 +320,7 @@ module Gitlab ...@@ -319,14 +320,7 @@ module Gitlab
end end
def parents def parents
case raw_commit parent_ids.map { |oid| self.class.find(@repository, oid) }.compact
when Rugged::Commit
raw_commit.parents.map { |c| Gitlab::Git::Commit.new(c) }
when Gitlab::GitalyClient::Commit
parent_ids.map { |oid| self.class.find(raw_commit.repository, oid) }.compact
else
raise NotImplementedError, "commit source doesn't support #parents"
end
end end
def stats def stats
......
...@@ -28,7 +28,6 @@ module Gitlab ...@@ -28,7 +28,6 @@ module Gitlab
@limits = self.class.collection_limits(options) @limits = self.class.collection_limits(options)
@enforce_limits = !!options.fetch(:limits, true) @enforce_limits = !!options.fetch(:limits, true)
@expanded = !!options.fetch(:expanded, true) @expanded = !!options.fetch(:expanded, true)
@from_gitaly = options.fetch(:from_gitaly, false)
@line_count = 0 @line_count = 0
@byte_count = 0 @byte_count = 0
...@@ -44,7 +43,7 @@ module Gitlab ...@@ -44,7 +43,7 @@ module Gitlab
return if @iterator.nil? return if @iterator.nil?
Gitlab::GitalyClient.migrate(:commit_raw_diffs) do |is_enabled| Gitlab::GitalyClient.migrate(:commit_raw_diffs) do |is_enabled|
if is_enabled && @from_gitaly if is_enabled && @iterator.is_a?(Gitlab::GitalyClient::DiffStitcher)
each_gitaly_patch(&block) each_gitaly_patch(&block)
else else
each_rugged_patch(&block) each_rugged_patch(&block)
......
...@@ -321,7 +321,7 @@ module Gitlab ...@@ -321,7 +321,7 @@ module Gitlab
options[:limit] ||= 0 options[:limit] ||= 0
options[:offset] ||= 0 options[:offset] ||= 0
raw_log(options).map { |c| Commit.decorate(c) } raw_log(options).map { |c| Commit.decorate(self, c) }
end end
def count_commits(options) def count_commits(options)
......
module Gitlab
module GitalyClient
class Commit
attr_reader :repository, :gitaly_commit
delegate :id, :subject, :body, :author, :committer, :parent_ids, to: :gitaly_commit
def initialize(repository, gitaly_commit)
@repository = repository
@gitaly_commit = gitaly_commit
end
end
end
end
...@@ -107,8 +107,7 @@ module Gitlab ...@@ -107,8 +107,7 @@ module Gitlab
gitaly_commit = GitalyClient.call(@repository.storage, :commit_service, :last_commit_for_path, request).commit gitaly_commit = GitalyClient.call(@repository.storage, :commit_service, :last_commit_for_path, request).commit
return unless gitaly_commit return unless gitaly_commit
commit = GitalyClient::Commit.new(@repository, gitaly_commit) Gitlab::Git::Commit.new(@repository, gitaly_commit)
Gitlab::Git::Commit.new(commit)
end end
def between(from, to) def between(from, to)
...@@ -170,7 +169,7 @@ module Gitlab ...@@ -170,7 +169,7 @@ module Gitlab
private private
def commit_diff_request_params(commit, options = {}) def commit_diff_request_params(commit, options = {})
parent_id = commit.parents[0]&.id || EMPTY_TREE_ID parent_id = commit.parent_ids.first || EMPTY_TREE_ID
{ {
repository: @gitaly_repo, repository: @gitaly_repo,
...@@ -183,8 +182,7 @@ module Gitlab ...@@ -183,8 +182,7 @@ module Gitlab
def consume_commits_response(response) def consume_commits_response(response)
response.flat_map do |message| response.flat_map do |message|
message.commits.map do |gitaly_commit| message.commits.map do |gitaly_commit|
commit = GitalyClient::Commit.new(@repository, gitaly_commit) Gitlab::Git::Commit.new(@repository, gitaly_commit)
Gitlab::Git::Commit.new(commit)
end end
end end
end end
......
...@@ -16,8 +16,7 @@ module Gitlab ...@@ -16,8 +16,7 @@ module Gitlab
response.flat_map do |message| response.flat_map do |message|
message.branches.map do |branch| message.branches.map do |branch|
gitaly_commit = GitalyClient::Commit.new(@repository, branch.target) target_commit = Gitlab::Git::Commit.decorate(@repository, branch.target)
target_commit = Gitlab::Git::Commit.decorate(gitaly_commit)
Gitlab::Git::Branch.new(@repository, branch.name, branch.target.id, target_commit) Gitlab::Git::Branch.new(@repository, branch.name, branch.target.id, target_commit)
end end
end end
...@@ -102,8 +101,7 @@ module Gitlab ...@@ -102,8 +101,7 @@ module Gitlab
response.flat_map do |message| response.flat_map do |message|
message.tags.map do |gitaly_tag| message.tags.map do |gitaly_tag|
if gitaly_tag.target_commit.present? if gitaly_tag.target_commit.present?
commit = GitalyClient::Commit.new(@repository, gitaly_tag.target_commit) gitaly_commit = Gitlab::Git::Commit.decorate(@repository, gitaly_tag.target_commit)
gitaly_commit = Gitlab::Git::Commit.new(commit)
end end
Gitlab::Git::Tag.new( Gitlab::Git::Tag.new(
...@@ -141,7 +139,7 @@ module Gitlab ...@@ -141,7 +139,7 @@ module Gitlab
committer_email: response.commit_committer.email.dup committer_email: response.commit_committer.email.dup
} }
Gitlab::Git::Commit.decorate(hash) Gitlab::Git::Commit.decorate(@repository, hash)
end end
end end
end end
......
...@@ -135,3 +135,7 @@ methods: ...@@ -135,3 +135,7 @@ methods:
- :diff_head_sha - :diff_head_sha
project: project:
- :description_html - :description_html
events:
- :action
push_event_payload:
- :action
\ No newline at end of file
...@@ -1111,31 +1111,17 @@ msgstr "因該階段的資料不足而無法顯示相關資訊" ...@@ -1111,31 +1111,17 @@ msgstr "因該階段的資料不足而無法顯示相關資訊"
msgid "Withdraw Access Request" msgid "Withdraw Access Request"
msgstr "取消權限申請" msgstr "取消權限申請"
msgid "" msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
"You are going to remove %{group_name}.\n" msgstr "將要刪除 %{group_name}。被刪除的群組無法復原!真的「確定」要這麼做嗎?"
"Removed groups CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
msgstr "即將要刪除 %{group_name}。\n"
"被刪除的群組完全無法救回來喔!\n"
"真的「100%確定」要這麼做嗎?"
msgid "" msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
"You are going to remove %{project_name_with_namespace}.\n" msgstr "將要刪除 %{project_name_with_namespace}。被刪除的專案無法復原!真的「確定」要這麼做嗎?"
"Removed project CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
msgstr "即將要刪除 %{project_name_with_namespace}。被刪除的專案完全無法救回來喔!真的「100%確定」要這麼做嗎?"
msgid "" msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?"
"You are going to remove the fork relationship to source project " msgstr "將要刪除本分支專案與主幹 %{forked_from_project} 的所有關聯。 真的「確定」要這麼做嗎?"
"%{forked_from_project}. Are you ABSOLUTELY sure?"
msgstr ""
"將要刪除本分支專案與主幹的所有關聯 (fork relationship) 。 %{forked_from_project} "
"真的「100%確定」要這麼做嗎?"
msgid "" msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
"You are going to transfer %{project_name_with_namespace} to another owner. " msgstr "將要把 %{project_name_with_namespace} 的所有權轉移給另一個人。真的「確定」要這麼做嗎?"
"Are you ABSOLUTELY sure?"
msgstr "將要把 %{project_name_with_namespace} 的所有權轉移給另一個人。真的「100%確定」要這麼做嗎?"
msgid "You can only add files when you are on a branch" msgid "You can only add files when you are on a branch"
msgstr "只能在分支 (branch) 上建立檔案" msgstr "只能在分支 (branch) 上建立檔案"
......
...@@ -8,34 +8,43 @@ describe ApplicationController do ...@@ -8,34 +8,43 @@ describe ApplicationController do
it 'redirects if the user is over their password expiry' do it 'redirects if the user is over their password expiry' do
user.password_expires_at = Time.new(2002) user.password_expires_at = Time.new(2002)
expect(user.ldap_user?).to be_falsey expect(user.ldap_user?).to be_falsey
allow(controller).to receive(:current_user).and_return(user) allow(controller).to receive(:current_user).and_return(user)
expect(controller).to receive(:redirect_to) expect(controller).to receive(:redirect_to)
expect(controller).to receive(:new_profile_password_path) expect(controller).to receive(:new_profile_password_path)
controller.send(:check_password_expiration) controller.send(:check_password_expiration)
end end
it 'does not redirect if the user is under their password expiry' do it 'does not redirect if the user is under their password expiry' do
user.password_expires_at = Time.now + 20010101 user.password_expires_at = Time.now + 20010101
expect(user.ldap_user?).to be_falsey expect(user.ldap_user?).to be_falsey
allow(controller).to receive(:current_user).and_return(user) allow(controller).to receive(:current_user).and_return(user)
expect(controller).not_to receive(:redirect_to) expect(controller).not_to receive(:redirect_to)
controller.send(:check_password_expiration) controller.send(:check_password_expiration)
end end
it 'does not redirect if the user is over their password expiry but they are an ldap user' do it 'does not redirect if the user is over their password expiry but they are an ldap user' do
user.password_expires_at = Time.new(2002) user.password_expires_at = Time.new(2002)
allow(user).to receive(:ldap_user?).and_return(true) allow(user).to receive(:ldap_user?).and_return(true)
allow(controller).to receive(:current_user).and_return(user) allow(controller).to receive(:current_user).and_return(user)
expect(controller).not_to receive(:redirect_to) expect(controller).not_to receive(:redirect_to)
controller.send(:check_password_expiration) controller.send(:check_password_expiration)
end end
it 'does not redirect if the user is over their password expiry but sign-in is disabled' do it 'redirects if the user is over their password expiry and sign-in is disabled' do
stub_application_setting(password_authentication_enabled: false) stub_application_setting(password_authentication_enabled: false)
user.password_expires_at = Time.new(2002) user.password_expires_at = Time.new(2002)
expect(user.ldap_user?).to be_falsey
allow(controller).to receive(:current_user).and_return(user) allow(controller).to receive(:current_user).and_return(user)
expect(controller).not_to receive(:redirect_to) expect(controller).to receive(:redirect_to)
expect(controller).to receive(:new_profile_password_path)
controller.send(:check_password_expiration) controller.send(:check_password_expiration)
end end
......
require 'spec_helper' require 'spec_helper'
describe PasswordsController do describe PasswordsController do
describe '#check_password_authentication_available' do describe '#prevent_ldap_reset' do
before do before do
@request.env["devise.mapping"] = Devise.mappings[:user] @request.env["devise.mapping"] = Devise.mappings[:user]
end end
context 'when password authentication is disabled' do context 'when password authentication is disabled' do
it 'prevents a password reset' do it 'allows password reset' do
stub_application_setting(password_authentication_enabled: false) stub_application_setting(password_authentication_enabled: false)
post :create post :create
expect(flash[:alert]).to eq 'Password authentication is unavailable.' expect(response).to have_http_status(302)
end end
end end
...@@ -22,7 +22,7 @@ describe PasswordsController do ...@@ -22,7 +22,7 @@ describe PasswordsController do
it 'prevents a password reset' do it 'prevents a password reset' do
post :create, user: { email: user.email } post :create, user: { email: user.email }
expect(flash[:alert]).to eq 'Password authentication is unavailable.' expect(flash[:alert]).to eq('Cannot reset password for LDAP user.')
end end
end end
end end
......
...@@ -101,14 +101,6 @@ describe 'Filter issues', js: true do ...@@ -101,14 +101,6 @@ describe 'Filter issues', js: true do
expect_issues_list_count(5) expect_issues_list_count(5)
expect_filtered_search_input_empty expect_filtered_search_input_empty
end end
it 'filters issues by invalid author' do
skip('to be tested, issue #26546')
end
it 'filters issues by multiple authors' do
skip('to be tested, issue #26546')
end
end end
context 'author with other filters' do context 'author with other filters' do
...@@ -158,10 +150,6 @@ describe 'Filter issues', js: true do ...@@ -158,10 +150,6 @@ describe 'Filter issues', js: true do
expect_filtered_search_input(search_term) expect_filtered_search_input(search_term)
end end
end end
it 'sorting' do
skip('to be tested, issue #26546')
end
end end
describe 'filter issues by assignee' do describe 'filter issues by assignee' do
...@@ -181,14 +169,6 @@ describe 'Filter issues', js: true do ...@@ -181,14 +169,6 @@ describe 'Filter issues', js: true do
expect_issues_list_count(8, 1) expect_issues_list_count(8, 1)
expect_filtered_search_input_empty expect_filtered_search_input_empty
end end
it 'filters issues by invalid assignee' do
skip('to be tested, issue #26546')
end
it 'filters issues by multiple assignees' do
skip('to be tested, issue #26546')
end
end end
context 'assignee with other filters' do context 'assignee with other filters' do
...@@ -238,12 +218,6 @@ describe 'Filter issues', js: true do ...@@ -238,12 +218,6 @@ describe 'Filter issues', js: true do
expect_filtered_search_input(search_term) expect_filtered_search_input(search_term)
end end
end end
context 'sorting' do
it 'sorts' do
skip('to be tested, issue #26546')
end
end
end end
describe 'filter issues by label' do describe 'filter issues by label' do
...@@ -266,10 +240,6 @@ describe 'Filter issues', js: true do ...@@ -266,10 +240,6 @@ describe 'Filter issues', js: true do
expect_filtered_search_input_empty expect_filtered_search_input_empty
end end
it 'filters issues by invalid label' do
skip('to be tested, issue #26546')
end
it 'filters issues by multiple labels' do it 'filters issues by multiple labels' do
input_filtered_search("label:~#{bug_label.title} label:~#{caps_sensitive_label.title}") input_filtered_search("label:~#{bug_label.title} label:~#{caps_sensitive_label.title}")
...@@ -471,12 +441,6 @@ describe 'Filter issues', js: true do ...@@ -471,12 +441,6 @@ describe 'Filter issues', js: true do
expect_filtered_search_input_empty expect_filtered_search_input_empty
end end
end end
context 'sorting' do
it 'sorts' do
skip('to be tested, issue #26546')
end
end
end end
describe 'filter issues by milestone' do describe 'filter issues by milestone' do
...@@ -513,14 +477,6 @@ describe 'Filter issues', js: true do ...@@ -513,14 +477,6 @@ describe 'Filter issues', js: true do
expect_filtered_search_input_empty expect_filtered_search_input_empty
end end
it 'filters issues by invalid milestones' do
skip('to be tested, issue #26546')
end
it 'filters issues by multiple milestones' do
skip('to be tested, issue #26546')
end
it 'filters issues by milestone containing special characters' do it 'filters issues by milestone containing special characters' do
special_milestone = create(:milestone, title: '!@\#{$%^&*()}', project: project) special_milestone = create(:milestone, title: '!@\#{$%^&*()}', project: project)
create(:issue, title: "Issue with special character milestone", project: project, milestone: special_milestone) create(:issue, title: "Issue with special character milestone", project: project, milestone: special_milestone)
...@@ -590,12 +546,6 @@ describe 'Filter issues', js: true do ...@@ -590,12 +546,6 @@ describe 'Filter issues', js: true do
expect_filtered_search_input(search_term) expect_filtered_search_input(search_term)
end end
end end
context 'sorting' do
it 'sorts' do
skip('to be tested, issue #26546')
end
end
end end
describe 'filter issues by text' do describe 'filter issues by text' do
......
...@@ -40,4 +40,18 @@ feature 'Issue Detail', :js do ...@@ -40,4 +40,18 @@ feature 'Issue Detail', :js do
end end
end end
end end
context 'when authored by a user who is later deleted' do
before do
issue.update_attribute(:author_id, nil)
sign_in(user)
visit project_issue_path(project, issue)
end
it 'shows the issue' do
page.within('.issuable-details') do
expect(find('h2')).to have_content(issue.title)
end
end
end
end end
...@@ -53,12 +53,12 @@ describe 'Profile > Password' do ...@@ -53,12 +53,12 @@ describe 'Profile > Password' do
context 'Regular user' do context 'Regular user' do
let(:user) { create(:user) } let(:user) { create(:user) }
it 'renders 404 when sign-in is disabled' do it 'renders 200 when sign-in is disabled' do
stub_application_setting(password_authentication_enabled: false) stub_application_setting(password_authentication_enabled: false)
visit edit_profile_password_path visit edit_profile_password_path
expect(page).to have_http_status(404) expect(page).to have_http_status(200)
end end
end end
......
...@@ -56,11 +56,10 @@ describe 'User creates files' do ...@@ -56,11 +56,10 @@ describe 'User creates files' do
find('.add-to-tree').click find('.add-to-tree').click
click_link('New file') click_link('New file')
expect(page).to have_selector('.file-editor')
end end
it 'creates and commit a new file', js: true do it 'creates and commit a new file', js: true do
expect(page).to have_selector('.file-editor')
execute_script("ace.edit('editor').setValue('*.rbca')") execute_script("ace.edit('editor').setValue('*.rbca')")
fill_in(:file_name, with: 'not_a_file.md') fill_in(:file_name, with: 'not_a_file.md')
fill_in(:commit_message, with: 'New commit message', visible: true) fill_in(:commit_message, with: 'New commit message', visible: true)
......
import { import {
calculateTop, calculateTop,
hideSubLevelItems,
showSubLevelItems, showSubLevelItems,
canShowSubItems, canShowSubItems,
canShowActiveSubItems, canShowActiveSubItems,
mouseEnterTopItems,
mouseLeaveTopItem,
getOpenMenu,
setOpenMenu,
mousePos,
getHideSubItemsInterval,
documentMouseMove,
getHeaderHeight, getHeaderHeight,
setSidebar, setSidebar,
subItemsMouseLeave,
} from '~/fly_out_nav'; } from '~/fly_out_nav';
import bp from '~/breakpoints'; import bp from '~/breakpoints';
...@@ -19,11 +26,14 @@ describe('Fly out sidebar navigation', () => { ...@@ -19,11 +26,14 @@ describe('Fly out sidebar navigation', () => {
document.body.appendChild(el); document.body.appendChild(el);
spyOn(bp, 'getBreakpointSize').and.callFake(() => breakpointSize); spyOn(bp, 'getBreakpointSize').and.callFake(() => breakpointSize);
setOpenMenu(null);
}); });
afterEach(() => { afterEach(() => {
el.remove(); document.body.innerHTML = '';
breakpointSize = 'lg'; breakpointSize = 'lg';
mousePos.length = 0;
}); });
describe('calculateTop', () => { describe('calculateTop', () => {
...@@ -50,61 +60,152 @@ describe('Fly out sidebar navigation', () => { ...@@ -50,61 +60,152 @@ describe('Fly out sidebar navigation', () => {
}); });
}); });
describe('hideSubLevelItems', () => { describe('getHideSubItemsInterval', () => {
beforeEach(() => { beforeEach(() => {
el.innerHTML = '<div class="sidebar-sub-level-items"></div>'; el.innerHTML = '<div class="sidebar-sub-level-items" style="position: fixed; top: 0; left: 100px; height: 150px;"></div>';
}); });
it('hides subitems', () => { it('returns 0 if currentOpenMenu is nil', () => {
hideSubLevelItems(el); expect(
getHideSubItemsInterval(),
).toBe(0);
});
it('returns 0 when mouse above sub-items', () => {
showSubLevelItems(el);
documentMouseMove({
clientX: el.getBoundingClientRect().left,
clientY: el.getBoundingClientRect().top,
});
documentMouseMove({
clientX: el.getBoundingClientRect().left,
clientY: el.getBoundingClientRect().top - 50,
});
expect( expect(
el.querySelector('.sidebar-sub-level-items').style.display, getHideSubItemsInterval(),
).toBe(''); ).toBe(0);
}); });
it('does not hude subitems on mobile', () => { it('returns 0 when mouse is below sub-items', () => {
breakpointSize = 'xs'; const subItems = el.querySelector('.sidebar-sub-level-items');
hideSubLevelItems(el); showSubLevelItems(el);
documentMouseMove({
clientX: el.getBoundingClientRect().left,
clientY: el.getBoundingClientRect().top,
});
documentMouseMove({
clientX: el.getBoundingClientRect().left,
clientY: (el.getBoundingClientRect().top - subItems.getBoundingClientRect().height) + 50,
});
expect( expect(
el.querySelector('.sidebar-sub-level-items').style.display, getHideSubItemsInterval(),
).not.toBe('none'); ).toBe(0);
});
it('returns 300 when mouse is moved towards sub-items', () => {
documentMouseMove({
clientX: el.getBoundingClientRect().left,
clientY: el.getBoundingClientRect().top,
});
showSubLevelItems(el);
documentMouseMove({
clientX: el.getBoundingClientRect().left + 20,
clientY: el.getBoundingClientRect().top + 10,
});
expect(
getHideSubItemsInterval(),
).toBe(300);
});
}); });
it('removes is-over class', () => { describe('mouseLeaveTopItem', () => {
beforeEach(() => {
spyOn(el.classList, 'remove'); spyOn(el.classList, 'remove');
});
hideSubLevelItems(el); it('removes is-over class if currentOpenMenu is null', () => {
mouseLeaveTopItem(el);
expect( expect(
el.classList.remove, el.classList.remove,
).toHaveBeenCalledWith('is-over'); ).toHaveBeenCalledWith('is-over');
}); });
it('removes is-above class from sub-items', () => { it('removes is-over class if currentOpenMenu is null & there are sub-items', () => {
const subItems = el.querySelector('.sidebar-sub-level-items'); el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute;"></div>';
spyOn(subItems.classList, 'remove'); mouseLeaveTopItem(el);
hideSubLevelItems(el); expect(
el.classList.remove,
).toHaveBeenCalledWith('is-over');
});
it('does not remove is-over class if currentOpenMenu is the passed in sub-items', () => {
el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute;"></div>';
setOpenMenu(el.querySelector('.sidebar-sub-level-items'));
mouseLeaveTopItem(el);
expect( expect(
subItems.classList.remove, el.classList.remove,
).toHaveBeenCalledWith('is-above'); ).not.toHaveBeenCalled();
});
}); });
it('does nothing if el has no sub-items', () => { describe('mouseEnterTopItems', () => {
el.innerHTML = ''; beforeEach(() => {
jasmine.clock().install();
spyOn(el.classList, 'remove'); el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute; top: 0; left: 100px; height: 200px;"></div>';
});
hideSubLevelItems(el); afterEach(() => {
jasmine.clock().uninstall();
});
it('shows sub-items after 0ms if no menu is open', () => {
mouseEnterTopItems(el);
expect( expect(
el.classList.remove, getHideSubItemsInterval(),
).not.toHaveBeenCalledWith(); ).toBe(0);
jasmine.clock().tick(0);
expect(
el.querySelector('.sidebar-sub-level-items').style.display,
).toBe('block');
});
it('shows sub-items after 300ms if a menu is currently open', () => {
documentMouseMove({
clientX: el.getBoundingClientRect().left,
clientY: el.getBoundingClientRect().top,
});
setOpenMenu(el.querySelector('.sidebar-sub-level-items'));
documentMouseMove({
clientX: el.getBoundingClientRect().left + 20,
clientY: el.getBoundingClientRect().top + 10,
});
mouseEnterTopItems(el);
expect(
getHideSubItemsInterval(),
).toBe(300);
jasmine.clock().tick(300);
expect(
el.querySelector('.sidebar-sub-level-items').style.display,
).toBe('block');
}); });
}); });
...@@ -133,7 +234,7 @@ describe('Fly out sidebar navigation', () => { ...@@ -133,7 +234,7 @@ describe('Fly out sidebar navigation', () => {
).not.toBe('block'); ).not.toBe('block');
}); });
it('does not shows sub-items', () => { it('shows sub-items', () => {
showSubLevelItems(el); showSubLevelItems(el);
expect( expect(
...@@ -215,4 +316,29 @@ describe('Fly out sidebar navigation', () => { ...@@ -215,4 +316,29 @@ describe('Fly out sidebar navigation', () => {
).toBeTruthy(); ).toBeTruthy();
}); });
}); });
describe('subItemsMouseLeave', () => {
beforeEach(() => {
el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute;"></div>';
setOpenMenu(el.querySelector('.sidebar-sub-level-items'));
});
it('hides subMenu if element is not hovered', () => {
subItemsMouseLeave(el);
expect(
getOpenMenu(),
).toBeNull();
});
it('does not hide subMenu if element is hovered', () => {
el.classList.add('is-over');
subItemsMouseLeave(el);
expect(
getOpenMenu(),
).not.toBeNull();
});
});
}); });
...@@ -295,6 +295,17 @@ import 'vendor/jquery.scrollTo'; ...@@ -295,6 +295,17 @@ import 'vendor/jquery.scrollTo';
this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
}); });
it('triggers scroll event when diff already loaded', function () {
spyOn(document, 'dispatchEvent');
this.class.diffsLoaded = true;
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(
document.dispatchEvent,
).toHaveBeenCalledWith(new CustomEvent('scroll'));
});
describe('with inline diff', () => { describe('with inline diff', () => {
let noteId; let noteId;
let noteLineNumId; let noteLineNumId;
......
...@@ -279,16 +279,6 @@ describe Gitlab::Auth do ...@@ -279,16 +279,6 @@ describe Gitlab::Auth do
gl_auth.find_with_user_password('ldap_user', 'password') gl_auth.find_with_user_password('ldap_user', 'password')
end end
end end
context "with sign-in disabled" do
before do
stub_application_setting(password_authentication_enabled: false)
end
it "does not find user by valid login/password" do
expect(gl_auth.find_with_user_password(username, password)).to be_nil
end
end
end end
private private
......
...@@ -210,7 +210,11 @@ describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads::Event do ...@@ -210,7 +210,11 @@ describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads::Event do
end end
end end
describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads do ##
# The background migration relies on a temporary table, hence we're migrating
# to a specific version of the database where said table is still present.
#
describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads, :migration, schema: 20170608152748 do
let(:migration) { described_class.new } let(:migration) { described_class.new }
let(:project) { create(:project_empty_repo) } let(:project) { create(:project_empty_repo) }
let(:author) { create(:user) } let(:author) { create(:user) }
...@@ -229,21 +233,6 @@ describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads do ...@@ -229,21 +233,6 @@ describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads do
) )
end end
# The background migration relies on a temporary table, hence we're migrating
# to a specific version of the database where said table is still present.
before :all do
ActiveRecord::Migration.verbose = false
ActiveRecord::Migrator
.migrate(ActiveRecord::Migrator.migrations_paths, 20170608152748)
end
after :all do
ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths)
ActiveRecord::Migration.verbose = true
end
describe '#perform' do describe '#perform' do
it 'returns if data should not be migrated' do it 'returns if data should not be migrated' do
allow(migration).to receive(:migrate?).and_return(false) allow(migration).to receive(:migrate?).and_return(false)
......
...@@ -2,7 +2,7 @@ require "spec_helper" ...@@ -2,7 +2,7 @@ require "spec_helper"
describe Gitlab::Git::Commit, seed_helper: true do describe Gitlab::Git::Commit, seed_helper: true do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
let(:commit) { Gitlab::Git::Commit.find(repository, SeedRepo::Commit::ID) } let(:commit) { described_class.find(repository, SeedRepo::Commit::ID) }
let(:rugged_commit) do let(:rugged_commit) do
repository.rugged.lookup(SeedRepo::Commit::ID) repository.rugged.lookup(SeedRepo::Commit::ID)
end end
...@@ -24,7 +24,7 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -24,7 +24,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
} }
@parents = [repo.head.target] @parents = [repo.head.target]
@gitlab_parents = @parents.map { |c| Gitlab::Git::Commit.decorate(c) } @gitlab_parents = @parents.map { |c| described_class.decorate(repository, c) }
@tree = @parents.first.tree @tree = @parents.first.tree
sha = Rugged::Commit.create( sha = Rugged::Commit.create(
...@@ -38,7 +38,7 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -38,7 +38,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
) )
@raw_commit = repo.lookup(sha) @raw_commit = repo.lookup(sha)
@commit = Gitlab::Git::Commit.new(@raw_commit) @commit = described_class.new(repository, @raw_commit)
end end
it { expect(@commit.short_id).to eq(@raw_commit.oid[0..10]) } it { expect(@commit.short_id).to eq(@raw_commit.oid[0..10]) }
...@@ -91,7 +91,7 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -91,7 +91,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
committer: committer committer: committer
) )
end end
let(:commit) { described_class.new(Gitlab::GitalyClient::Commit.new(repository, gitaly_commit)) } let(:commit) { described_class.new(repository, gitaly_commit) }
it { expect(commit.short_id).to eq(id[0..10]) } it { expect(commit.short_id).to eq(id[0..10]) }
it { expect(commit.id).to eq(id) } it { expect(commit.id).to eq(id) }
...@@ -113,45 +113,45 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -113,45 +113,45 @@ describe Gitlab::Git::Commit, seed_helper: true do
context 'Class methods' do context 'Class methods' do
describe '.find' do describe '.find' do
it "should return first head commit if without params" do it "should return first head commit if without params" do
expect(Gitlab::Git::Commit.last(repository).id).to eq( expect(described_class.last(repository).id).to eq(
repository.rugged.head.target.oid repository.rugged.head.target.oid
) )
end end
it "should return valid commit" do it "should return valid commit" do
expect(Gitlab::Git::Commit.find(repository, SeedRepo::Commit::ID)).to be_valid_commit expect(described_class.find(repository, SeedRepo::Commit::ID)).to be_valid_commit
end end
it "should return valid commit for tag" do it "should return valid commit for tag" do
expect(Gitlab::Git::Commit.find(repository, 'v1.0.0').id).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') expect(described_class.find(repository, 'v1.0.0').id).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9')
end end
it "should return nil for non-commit ids" do it "should return nil for non-commit ids" do
blob = Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb") blob = Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb")
expect(Gitlab::Git::Commit.find(repository, blob.id)).to be_nil expect(described_class.find(repository, blob.id)).to be_nil
end end
it "should return nil for parent of non-commit object" do it "should return nil for parent of non-commit object" do
blob = Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb") blob = Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb")
expect(Gitlab::Git::Commit.find(repository, "#{blob.id}^")).to be_nil expect(described_class.find(repository, "#{blob.id}^")).to be_nil
end end
it "should return nil for nonexisting ids" do it "should return nil for nonexisting ids" do
expect(Gitlab::Git::Commit.find(repository, "+123_4532530XYZ")).to be_nil expect(described_class.find(repository, "+123_4532530XYZ")).to be_nil
end end
context 'with broken repo' do context 'with broken repo' do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_BROKEN_REPO_PATH) } let(:repository) { Gitlab::Git::Repository.new('default', TEST_BROKEN_REPO_PATH) }
it 'returns nil' do it 'returns nil' do
expect(Gitlab::Git::Commit.find(repository, SeedRepo::Commit::ID)).to be_nil expect(described_class.find(repository, SeedRepo::Commit::ID)).to be_nil
end end
end end
end end
describe '.last_for_path' do describe '.last_for_path' do
context 'no path' do context 'no path' do
subject { Gitlab::Git::Commit.last_for_path(repository, 'master') } subject { described_class.last_for_path(repository, 'master') }
describe '#id' do describe '#id' do
subject { super().id } subject { super().id }
...@@ -160,7 +160,7 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -160,7 +160,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
end end
context 'path' do context 'path' do
subject { Gitlab::Git::Commit.last_for_path(repository, 'master', 'files/ruby') } subject { described_class.last_for_path(repository, 'master', 'files/ruby') }
describe '#id' do describe '#id' do
subject { super().id } subject { super().id }
...@@ -169,7 +169,7 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -169,7 +169,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
end end
context 'ref + path' do context 'ref + path' do
subject { Gitlab::Git::Commit.last_for_path(repository, SeedRepo::Commit::ID, 'encoding') } subject { described_class.last_for_path(repository, SeedRepo::Commit::ID, 'encoding') }
describe '#id' do describe '#id' do
subject { super().id } subject { super().id }
...@@ -181,7 +181,7 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -181,7 +181,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
describe '.where' do describe '.where' do
context 'path is empty string' do context 'path is empty string' do
subject do subject do
commits = Gitlab::Git::Commit.where( commits = described_class.where(
repo: repository, repo: repository,
ref: 'master', ref: 'master',
path: '', path: '',
...@@ -199,7 +199,7 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -199,7 +199,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
context 'path is nil' do context 'path is nil' do
subject do subject do
commits = Gitlab::Git::Commit.where( commits = described_class.where(
repo: repository, repo: repository,
ref: 'master', ref: 'master',
path: nil, path: nil,
...@@ -217,7 +217,7 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -217,7 +217,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
context 'ref is branch name' do context 'ref is branch name' do
subject do subject do
commits = Gitlab::Git::Commit.where( commits = described_class.where(
repo: repository, repo: repository,
ref: 'master', ref: 'master',
path: 'files', path: 'files',
...@@ -237,7 +237,7 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -237,7 +237,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
context 'ref is commit id' do context 'ref is commit id' do
subject do subject do
commits = Gitlab::Git::Commit.where( commits = described_class.where(
repo: repository, repo: repository,
ref: "874797c3a73b60d2187ed6e2fcabd289ff75171e", ref: "874797c3a73b60d2187ed6e2fcabd289ff75171e",
path: 'files', path: 'files',
...@@ -257,7 +257,7 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -257,7 +257,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
context 'ref is tag' do context 'ref is tag' do
subject do subject do
commits = Gitlab::Git::Commit.where( commits = described_class.where(
repo: repository, repo: repository,
ref: 'v1.0.0', ref: 'v1.0.0',
path: 'files', path: 'files',
...@@ -278,7 +278,7 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -278,7 +278,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
describe '.between' do describe '.between' do
subject do subject do
commits = Gitlab::Git::Commit.between(repository, SeedRepo::Commit::PARENT_ID, SeedRepo::Commit::ID) commits = described_class.between(repository, SeedRepo::Commit::PARENT_ID, SeedRepo::Commit::ID)
commits.map { |c| c.id } commits.map { |c| c.id }
end end
...@@ -294,12 +294,12 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -294,12 +294,12 @@ describe Gitlab::Git::Commit, seed_helper: true do
it 'should return a return a collection of commits' do it 'should return a return a collection of commits' do
commits = described_class.find_all(repository) commits = described_class.find_all(repository)
expect(commits).to all( be_a_kind_of(Gitlab::Git::Commit) ) expect(commits).to all( be_a_kind_of(described_class) )
end end
context 'max_count' do context 'max_count' do
subject do subject do
commits = Gitlab::Git::Commit.find_all( commits = described_class.find_all(
repository, repository,
max_count: 50 max_count: 50
) )
...@@ -322,7 +322,7 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -322,7 +322,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
context 'ref + max_count + skip' do context 'ref + max_count + skip' do
subject do subject do
commits = Gitlab::Git::Commit.find_all( commits = described_class.find_all(
repository, repository,
ref: 'master', ref: 'master',
max_count: 50, max_count: 50,
...@@ -374,7 +374,7 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -374,7 +374,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
end end
describe '#init_from_rugged' do describe '#init_from_rugged' do
let(:gitlab_commit) { Gitlab::Git::Commit.new(rugged_commit) } let(:gitlab_commit) { described_class.new(repository, rugged_commit) }
subject { gitlab_commit } subject { gitlab_commit }
describe '#id' do describe '#id' do
...@@ -384,7 +384,7 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -384,7 +384,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
end end
describe '#init_from_hash' do describe '#init_from_hash' do
let(:commit) { Gitlab::Git::Commit.new(sample_commit_hash) } let(:commit) { described_class.new(repository, sample_commit_hash) }
subject { commit } subject { commit }
describe '#id' do describe '#id' do
...@@ -451,7 +451,7 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -451,7 +451,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
end end
describe '#ref_names' do describe '#ref_names' do
let(:commit) { Gitlab::Git::Commit.find(repository, 'master') } let(:commit) { described_class.find(repository, 'master') }
subject { commit.ref_names(repository) } subject { commit.ref_names(repository) }
it 'has 1 element' do it 'has 1 element' do
...@@ -461,6 +461,17 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -461,6 +461,17 @@ describe Gitlab::Git::Commit, seed_helper: true do
it { is_expected.not_to include("feature") } it { is_expected.not_to include("feature") }
end end
describe '#rugged_commit' do
let(:raw_commit) { { id: SeedRepo::Commit::ID } }
let(:commit) { described_class.decorate(repository, raw_commit) }
subject { commit.send(:rugged_commit) }
it 'returns a rugged commit based on the id of a non-rugged raw commit' do
expect(subject.class).to be(Rugged::Commit)
expect(subject).to eq(rugged_commit)
end
end
def sample_commit_hash def sample_commit_hash
{ {
author_email: "dmitriy.zaporozhets@gmail.com", author_email: "dmitriy.zaporozhets@gmail.com",
......
...@@ -511,17 +511,22 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -511,17 +511,22 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
describe "#log" do describe "#log" do
commit_with_old_name = nil let(:commit_with_old_name) do
commit_with_new_name = nil Gitlab::Git::Commit.decorate(repository, @commit_with_old_name_id)
rename_commit = nil end
let(:commit_with_new_name) do
Gitlab::Git::Commit.decorate(repository, @commit_with_new_name_id)
end
let(:rename_commit) do
Gitlab::Git::Commit.decorate(repository, @rename_commit_id)
end
before(:context) do before(:context) do
# Add new commits so that there's a renamed file in the commit history # Add new commits so that there's a renamed file in the commit history
repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH).rugged repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH).rugged
@commit_with_old_name_id = new_commit_edit_old_file(repo)
commit_with_old_name = Gitlab::Git::Commit.decorate(new_commit_edit_old_file(repo)) @rename_commit_id = new_commit_move_file(repo)
rename_commit = Gitlab::Git::Commit.decorate(new_commit_move_file(repo)) @commit_with_new_name_id = new_commit_edit_new_file(repo)
commit_with_new_name = Gitlab::Git::Commit.decorate(new_commit_edit_new_file(repo))
end end
after(:context) do after(:context) do
......
...@@ -30,7 +30,7 @@ describe Gitlab::GitalyClient::CommitService do ...@@ -30,7 +30,7 @@ describe Gitlab::GitalyClient::CommitService do
context 'when a commit does not have a parent' do context 'when a commit does not have a parent' do
it 'sends an RPC request with empty tree ref as left commit' do it 'sends an RPC request with empty tree ref as left commit' do
initial_commit = project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863') initial_commit = project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863').raw
request = Gitaly::CommitDiffRequest.new( request = Gitaly::CommitDiffRequest.new(
repository: repository_message, repository: repository_message,
left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904', left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
......
...@@ -79,6 +79,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do ...@@ -79,6 +79,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(event).not_to be_nil expect(event).not_to be_nil
end end
it 'has the action' do
expect(event.action).not_to be_nil
end
it 'event belongs to note, belongs to merge request, belongs to a project' do it 'event belongs to note, belongs to merge request, belongs to a project' do
expect(event.note.noteable.project).not_to be_nil expect(event.note.noteable.project).not_to be_nil
end end
......
This diff is collapsed.
...@@ -28,6 +28,14 @@ The `after` hook will migrate the database **up** and reinstitutes the latest ...@@ -28,6 +28,14 @@ The `after` hook will migrate the database **up** and reinstitutes the latest
schema version, so that the process does not affect subsequent specs and schema version, so that the process does not affect subsequent specs and
ensures proper isolation. ensures proper isolation.
## Testing a class that is not an ActiveRecord::Migration
In order to test a class that is not a migration itself, you will need to
manually provide a required schema version. Please add a `schema` tag to a
context that you want to switch the database schema within.
Example: `describe SomeClass, :migration, schema: 20170608152748`.
## Available helpers ## Available helpers
Use `table` helper to create a temporary `ActiveRecord::Base` derived model Use `table` helper to create a temporary `ActiveRecord::Base` derived model
...@@ -80,8 +88,6 @@ end ...@@ -80,8 +88,6 @@ end
## Best practices ## Best practices
1. Use only one test example per migration unless there is a good reason to
use more.
1. Note that this type of tests do not run within the transaction, we use 1. Note that this type of tests do not run within the transaction, we use
a truncation database cleanup strategy. Do not depend on transaction being a truncation database cleanup strategy. Do not depend on transaction being
present. present.
...@@ -51,7 +51,6 @@ describe RemoveDotGitFromUsernames do ...@@ -51,7 +51,6 @@ describe RemoveDotGitFromUsernames do
namespace.path = path namespace.path = path
namespace.save!(validate: false) namespace.save!(validate: false)
user.username = path user.update_column(:username, path)
user.save!(validate: false)
end end
end end
...@@ -33,7 +33,6 @@ describe Commit do ...@@ -33,7 +33,6 @@ describe Commit do
describe '#to_reference' do describe '#to_reference' do
let(:project) { create(:project, :repository, path: 'sample-project') } let(:project) { create(:project, :repository, path: 'sample-project') }
let(:commit) { project.commit }
it 'returns a String reference to the object' do it 'returns a String reference to the object' do
expect(commit.to_reference).to eq commit.id expect(commit.to_reference).to eq commit.id
...@@ -47,7 +46,6 @@ describe Commit do ...@@ -47,7 +46,6 @@ describe Commit do
describe '#reference_link_text' do describe '#reference_link_text' do
let(:project) { create(:project, :repository, path: 'sample-project') } let(:project) { create(:project, :repository, path: 'sample-project') }
let(:commit) { project.commit }
it 'returns a String reference to the object' do it 'returns a String reference to the object' do
expect(commit.reference_link_text).to eq commit.short_id expect(commit.reference_link_text).to eq commit.short_id
......
...@@ -159,6 +159,7 @@ describe MergeRequest do ...@@ -159,6 +159,7 @@ describe MergeRequest do
before do before do
subject.project.has_external_issue_tracker = true subject.project.has_external_issue_tracker = true
subject.project.save! subject.project.save!
create(:jira_service, project: subject.project)
end end
it 'does not cache issues from external trackers' do it 'does not cache issues from external trackers' do
...@@ -166,6 +167,7 @@ describe MergeRequest do ...@@ -166,6 +167,7 @@ describe MergeRequest do
commit = double('commit1', safe_message: "Fixes #{issue.to_reference}") commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
allow(subject).to receive(:commits).and_return([commit]) allow(subject).to receive(:commits).and_return([commit])
expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to raise_error
expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count) expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count)
end end
......
...@@ -2013,4 +2013,65 @@ describe User do ...@@ -2013,4 +2013,65 @@ describe User do
expect(user.projects_limit_left).to eq(5) expect(user.projects_limit_left).to eq(5)
end end
end end
describe '#ensure_namespace_correct' do
context 'for a new user' do
let(:user) { build(:user) }
it 'creates the namespace' do
expect(user.namespace).to be_nil
user.save!
expect(user.namespace).not_to be_nil
end
end
context 'for an existing user' do
let(:username) { 'foo' }
let(:user) { create(:user, username: username) }
context 'when the user is updated' do
context 'when the username is changed' do
let(:new_username) { 'bar' }
it 'changes the namespace (just to compare to when username is not changed)' do
expect do
user.update_attributes!(username: new_username)
end.to change { user.namespace.updated_at }
end
it 'updates the namespace name' do
user.update_attributes!(username: new_username)
expect(user.namespace.name).to eq(new_username)
end
it 'updates the namespace path' do
user.update_attributes!(username: new_username)
expect(user.namespace.path).to eq(new_username)
end
context 'when there is a validation error (namespace name taken) while updating namespace' do
let!(:conflicting_namespace) { create(:group, name: new_username, path: 'quz') }
it 'causes the user save to fail' do
expect(user.update_attributes(username: new_username)).to be_falsey
expect(user.namespace.errors.messages[:name].first).to eq('has already been taken')
end
it 'adds the namespace errors to the user' do
user.update_attributes(username: new_username)
expect(user.errors.full_messages.first).to eq('Namespace name has already been taken')
end
end
end
context 'when the username is not changed' do
it 'does not change the namespace' do
expect do
user.update_attributes!(email: 'asdf@asdf.com')
end.not_to change { user.namespace.updated_at }
end
end
end
end
end
end end
...@@ -85,6 +85,22 @@ describe API::Triggers do ...@@ -85,6 +85,22 @@ describe API::Triggers do
expect(pipeline.variables.map { |v| { v.key => v.value } }.last).to eq(variables) expect(pipeline.variables.map { |v| { v.key => v.value } }.last).to eq(variables)
end end
end end
context 'when legacy trigger' do
before do
trigger.update(owner: nil)
end
it 'creates pipeline' do
post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'master')
expect(response).to have_http_status(201)
expect(json_response).to include('id' => pipeline.id)
pipeline.builds.reload
expect(pipeline.builds.pending.size).to eq(2)
expect(pipeline.builds.size).to eq(5)
end
end
end end
context 'when triggering a pipeline from a trigger token' do context 'when triggering a pipeline from a trigger token' do
......
...@@ -413,14 +413,12 @@ describe Ci::CreatePipelineService do ...@@ -413,14 +413,12 @@ describe Ci::CreatePipelineService do
end end
context 'when trigger belongs to a developer' do context 'when trigger belongs to a developer' do
let(:user) {} let(:user) { create(:user) }
let(:trigger) { create(:ci_trigger, owner: user) }
let(:trigger_request) { create(:ci_trigger_request, trigger: trigger) }
let(:trigger_request) do before do
create(:ci_trigger_request).tap do |request|
user = create(:user)
project.add_developer(user) project.add_developer(user)
request.trigger.update(owner: user)
end
end end
it 'does not create a pipeline' do it 'does not create a pipeline' do
...@@ -431,17 +429,15 @@ describe Ci::CreatePipelineService do ...@@ -431,17 +429,15 @@ describe Ci::CreatePipelineService do
end end
context 'when trigger belongs to a master' do context 'when trigger belongs to a master' do
let(:user) {} let(:user) { create(:user) }
let(:trigger) { create(:ci_trigger, owner: user) }
let(:trigger_request) { create(:ci_trigger_request, trigger: trigger) }
let(:trigger_request) do before do
create(:ci_trigger_request).tap do |request|
user = create(:user)
project.add_master(user) project.add_master(user)
request.trigger.update(owner: user)
end
end end
it 'does not create a pipeline' do it 'creates a pipeline' do
expect(execute_service(trigger_request: trigger_request)) expect(execute_service(trigger_request: trigger_request))
.to be_persisted .to be_persisted
expect(Ci::Pipeline.count).to eq(1) expect(Ci::Pipeline.count).to eq(1)
......
...@@ -126,7 +126,18 @@ describe NotificationService, :mailer do ...@@ -126,7 +126,18 @@ describe NotificationService, :mailer do
project.add_master(issue.author) project.add_master(issue.author)
project.add_master(assignee) project.add_master(assignee)
project.add_master(note.author) project.add_master(note.author)
create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@subscribed_participant cc this guy')
@u_custom_off = create_user_with_notification(:custom, 'custom_off')
project.add_guest(@u_custom_off)
create(
:note_on_issue,
author: @u_custom_off,
noteable: issue,
project_id: issue.project_id,
note: 'i think @subscribed_participant should see this'
)
update_custom_notification(:new_note, @u_guest_custom, resource: project) update_custom_notification(:new_note, @u_guest_custom, resource: project)
update_custom_notification(:new_note, @u_custom_global) update_custom_notification(:new_note, @u_custom_global)
end end
...@@ -136,8 +147,7 @@ describe NotificationService, :mailer do ...@@ -136,8 +147,7 @@ describe NotificationService, :mailer do
add_users_with_subscription(note.project, issue) add_users_with_subscription(note.project, issue)
reset_delivered_emails! reset_delivered_emails!
# Ensure create SentNotification by noteable = issue 6 times, not noteable = note expect(SentNotification).to receive(:record).with(issue, any_args).exactly(9).times
expect(SentNotification).to receive(:record).with(issue, any_args).exactly(8).times
notification.new_note(note) notification.new_note(note)
...@@ -149,6 +159,7 @@ describe NotificationService, :mailer do ...@@ -149,6 +159,7 @@ describe NotificationService, :mailer do
should_email(@subscriber) should_email(@subscriber)
should_email(@watcher_and_subscriber) should_email(@watcher_and_subscriber)
should_email(@subscribed_participant) should_email(@subscribed_participant)
should_email(@u_custom_off)
should_not_email(@u_guest_custom) should_not_email(@u_guest_custom)
should_not_email(@u_guest_watcher) should_not_email(@u_guest_watcher)
should_not_email(note.author) should_not_email(note.author)
......
...@@ -7,7 +7,6 @@ describe TestHooks::SystemService do ...@@ -7,7 +7,6 @@ describe TestHooks::SystemService do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:hook) { create(:system_hook) } let(:hook) { create(:system_hook) }
let(:service) { described_class.new(hook, current_user, trigger) } let(:service) { described_class.new(hook, current_user, trigger) }
let(:sample_data) { { data: 'sample' }}
let(:success_result) { { status: :success, http_status: 200, message: 'ok' } } let(:success_result) { { status: :success, http_status: 200, message: 'ok' } }
before do before do
...@@ -26,18 +25,11 @@ describe TestHooks::SystemService do ...@@ -26,18 +25,11 @@ describe TestHooks::SystemService do
context 'push_events' do context 'push_events' do
let(:trigger) { 'push_events' } let(:trigger) { 'push_events' }
it 'returns error message if not enough data' do
allow(project).to receive(:empty_repo?).and_return(true)
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: "Ensure project \"#{project.human_name}\" has commits." })
end
it 'executes hook' do it 'executes hook' do
allow(project).to receive(:empty_repo?).and_return(false) allow(project).to receive(:empty_repo?).and_return(false)
allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data) expect(Gitlab::DataBuilder::Push).to receive(:sample_data).and_call_original
expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result) expect(hook).to receive(:execute).with(Gitlab::DataBuilder::Push::SAMPLE_DATA, trigger).and_return(success_result)
expect(service.execute).to include(success_result) expect(service.execute).to include(success_result)
end end
end end
...@@ -45,18 +37,11 @@ describe TestHooks::SystemService do ...@@ -45,18 +37,11 @@ describe TestHooks::SystemService do
context 'tag_push_events' do context 'tag_push_events' do
let(:trigger) { 'tag_push_events' } let(:trigger) { 'tag_push_events' }
it 'returns error message if not enough data' do
allow(project.repository).to receive(:tags).and_return([])
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: "Ensure project \"#{project.human_name}\" has tags." })
end
it 'executes hook' do it 'executes hook' do
allow(project.repository).to receive(:tags).and_return(['tag']) allow(project.repository).to receive(:tags).and_return(['tag'])
allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data) expect(Gitlab::DataBuilder::Push).to receive(:sample_data).and_call_original
expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result) expect(hook).to receive(:execute).with(Gitlab::DataBuilder::Push::SAMPLE_DATA, trigger).and_return(success_result)
expect(service.execute).to include(success_result) expect(service.execute).to include(success_result)
end end
end end
...@@ -64,17 +49,11 @@ describe TestHooks::SystemService do ...@@ -64,17 +49,11 @@ describe TestHooks::SystemService do
context 'repository_update_events' do context 'repository_update_events' do
let(:trigger) { 'repository_update_events' } let(:trigger) { 'repository_update_events' }
it 'returns error message if not enough data' do
allow(project).to receive(:commit).and_return(nil)
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: "Ensure project \"#{project.human_name}\" has commits." })
end
it 'executes hook' do it 'executes hook' do
allow(project).to receive(:empty_repo?).and_return(false) allow(project).to receive(:empty_repo?).and_return(false)
allow(Gitlab::DataBuilder::Repository).to receive(:update).and_return(sample_data) expect(Gitlab::DataBuilder::Repository).to receive(:sample_data).and_call_original
expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result) expect(hook).to receive(:execute).with(Gitlab::DataBuilder::Repository::SAMPLE_DATA, trigger).and_return(success_result)
expect(service.execute).to include(success_result) expect(service.execute).to include(success_result)
end end
end end
......
...@@ -12,9 +12,22 @@ describe Users::UpdateService do ...@@ -12,9 +12,22 @@ describe Users::UpdateService do
end end
it 'returns an error result when record cannot be updated' do it 'returns an error result when record cannot be updated' do
result = {}
expect do expect do
update_user(user, { email: 'invalid' }) result = update_user(user, { email: 'invalid' })
end.not_to change { user.reload.email } end.not_to change { user.reload.email }
expect(result[:status]).to eq(:error)
expect(result[:message]).to eq('Email is invalid')
end
it 'includes namespace error messages' do
create(:group, name: 'taken', path: 'something_else')
result = {}
expect do
result = update_user(user, { username: 'taken' })
end.not_to change { user.reload.username }
expect(result[:status]).to eq(:error)
expect(result[:message]).to eq('Namespace name has already been taken')
end end
def update_user(user, opts) def update_user(user, opts)
......
...@@ -136,17 +136,12 @@ RSpec.configure do |config| ...@@ -136,17 +136,12 @@ RSpec.configure do |config|
Sidekiq.redis(&:flushall) Sidekiq.redis(&:flushall)
end end
config.before(:example, :migration) do config.before(:each, :migration) do
ActiveRecord::Migrator schema_migrate_down!
.migrate(migrations_paths, previous_migration.version)
reset_column_in_migration_models
end end
config.after(:example, :migration) do config.after(:context, :migration) do
ActiveRecord::Migrator.migrate(migrations_paths) schema_migrate_up!
reset_column_in_migration_models
end end
config.around(:each, :nested_groups) do |example| config.around(:each, :nested_groups) do |example|
......
...@@ -20,7 +20,7 @@ RSpec.configure do |config| ...@@ -20,7 +20,7 @@ RSpec.configure do |config|
end end
config.before(:each, :migration) do config.before(:each, :migration) do
DatabaseCleaner.strategy = :truncation DatabaseCleaner.strategy = :truncation, { cache_tables: false }
end end
config.before(:each) do config.before(:each) do
......
...@@ -35,7 +35,8 @@ module ExportFileHelper ...@@ -35,7 +35,8 @@ module ExportFileHelper
project: project, project: project,
commit_id: ci_pipeline.sha) commit_id: ci_pipeline.sha)
create(:event, :created, target: milestone, project: project, author: user) event = create(:event, :created, target: milestone, project: project, author: user, action: 5)
create(:push_event_payload, event: event)
create(:project_member, :master, user: user, project: project) create(:project_member, :master, user: user, project: project)
create(:ci_variable, project: project) create(:ci_variable, project: project)
create(:ci_trigger, project: project) create(:ci_trigger, project: project)
......
...@@ -16,6 +16,8 @@ module MigrationsHelpers ...@@ -16,6 +16,8 @@ module MigrationsHelpers
end end
def reset_column_in_migration_models def reset_column_in_migration_models
ActiveRecord::Base.clear_cache!
described_class.constants.sort.each do |name| described_class.constants.sort.each do |name|
const = described_class.const_get(name) const = described_class.const_get(name)
...@@ -31,6 +33,35 @@ module MigrationsHelpers ...@@ -31,6 +33,35 @@ module MigrationsHelpers
end end
end end
def migration_schema_version
self.class.metadata[:schema] || previous_migration.version
end
def schema_migrate_down!
disable_migrations_output do
ActiveRecord::Migrator.migrate(migrations_paths,
migration_schema_version)
end
reset_column_in_migration_models
end
def schema_migrate_up!
disable_migrations_output do
ActiveRecord::Migrator.migrate(migrations_paths)
end
reset_column_in_migration_models
end
def disable_migrations_output
ActiveRecord::Migration.verbose = false
yield
ensure
ActiveRecord::Migration.verbose = true
end
def migrate! def migrate!
ActiveRecord::Migrator.up(migrations_paths) do |migration| ActiveRecord::Migrator.up(migrations_paths) do |migration|
migration.name == described_class.name migration.name == described_class.name
......
shared_context 'gitlab email notification' do shared_context 'gitlab email notification' do
set(:project) { create(:project, :repository) }
set(:recipient) { create(:user, email: 'recipient@example.com') }
let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name } let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name }
let(:gitlab_sender) { Gitlab.config.gitlab.email_from } let(:gitlab_sender) { Gitlab.config.gitlab.email_from }
let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to } let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to }
let(:recipient) { create(:user, email: 'recipient@example.com') }
let(:project) { create(:project) }
let(:new_user_address) { 'newguy@example.com' } let(:new_user_address) { 'newguy@example.com' }
before do before 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