Commit 1c14ced6 authored by Sean McGivern's avatar Sean McGivern

Merge branch 'ce_upstream' into 'master'

CE upstream

Closes gitaly#544, gitlab-qa#68, and gitaly#595

See merge request gitlab-org/gitlab-ee!3051
parents 16a9e1d8 0c8f4deb
......@@ -411,6 +411,7 @@ docs lint:
before_script: []
script:
- scripts/lint-doc.sh
- scripts/lint-changelog-yaml
- mv doc/ /nanoc/content/
- cd /nanoc
# Build HTML from Markdown
......
......@@ -50,7 +50,7 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._
Thank you for your interest in contributing to GitLab. This guide details how
to contribute to GitLab in a way that is efficient for everyone.
Looking for something to work on? Look for the label [Accepting Merge Requests](#i-want-to-contribute).
Looking for something to work on? Look for issues with the label [Accepting Merge Requests](#i-want-to-contribute).
GitLab comes into two flavors, GitLab Community Edition (CE) our free and open
source edition, and GitLab Enterprise Edition (EE) which is our commercial
......@@ -102,7 +102,7 @@ the remaining issues on the GitHub issue tracker.
## I want to contribute!
If you want to contribute to GitLab, but are not sure where to start,
look for [issues with the label `Accepting Merge Requests` and weight < 5][accepting-mrs-weight].
look for [issues with the label `Accepting Merge Requests` and small weight][accepting-mrs-weight].
These issues will be of reasonable size and challenge, for anyone to start
contributing to GitLab.
......@@ -210,8 +210,7 @@ We add the ~"Accepting Merge Requests" label to:
- Low priority ~bug issues (i.e. we do not add it to the bugs that we want to
solve in the ~"Next Patch Release")
- Small ~"feature proposal" that do not need ~UX / ~"Product work", or for which
the ~UX / ~"Product work" is already done
- Small ~"feature proposal"
- Small ~"technical debt" issues
After adding the ~"Accepting Merge Requests" label, we try to estimate the
......@@ -224,6 +223,17 @@ know how difficult the issue is. Additionally:
- We encourage people that have never contributed to any open source project to
look for ["Accepting Merge Requests" issues with a weight of 1][firt-timers]
If you've decided that you would like to work on an issue, please @-mention
the [appropriate product manager](https://about.gitlab.com/handbook/product/#who-to-talk-to-for-what)
as soon as possible. The product manager will then pull in appropriate GitLab team
members to further discuss scope, design, and technical considerations. This will
ensure that that your contribution is aligned with the GitLab product and minimize
any rework and delay in getting it merged into master.
GitLab team members who apply the ~"Accepting Merge Requests" label to an issue
should update the issue description with a responsible product manager, inviting
any potential community contributor to @-mention per above.
[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests&scope=all&sort=weight_asc&state=opened
[firt-timers]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=Accepting+Merge+Requests&scope=all&sort=upvotes_desc&state=opened&weight=1
......
{"iconCount":134,"icons":["abuse","account","admin","angle-double-left","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-right","assignee","bold","book","branch","calendar","cancel","chevron-down","chevron-left","chevron-right","chevron-up","clock","code","comment-dots","comment-next","comment","comments","commit","credit-card","disk","doc_code","doc_image","doc_text","download","duplicate","earth","eye-slash","eye","file-additions","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","merge-request-close-m","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","star-o","star","stop","talic","task-done","template","thump-down","thump-up","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]}
\ No newline at end of file
{"iconCount":135,"spriteSize":58718,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-right","assignee","bold","book","branch","calendar","cancel","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","comment-dots","comment-next","comment","comments","commit","credit-card","disk","doc_code","doc_image","doc_text","download","duplicate","earth","eye-slash","eye","file-additions","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","star-o","star","stop","talic","task-done","template","thump-down","thump-up","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
<script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
export default {
props: {
......@@ -8,6 +10,8 @@
},
components: {
userAvatarImage,
limitWarning,
totalTime,
},
};
</script>
......
<script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
export default {
props: {
......@@ -8,6 +10,8 @@
},
components: {
userAvatarImage,
limitWarning,
totalTime,
},
};
</script>
......
<script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import iconCommit from '../svg/icon_commit.svg';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import iconCommit from '../svg/icon_commit.svg';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
export default {
export default {
props: {
items: Array,
stage: Object,
},
components: {
userAvatarImage,
totalTime,
limitWarning,
},
computed: {
iconCommit() {
return iconCommit;
},
},
};
};
</script>
<template>
<div>
......
<script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
export default {
props: {
......@@ -8,6 +10,8 @@
},
components: {
userAvatarImage,
totalTime,
limitWarning,
},
};
</script>
......
<script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import iconBranch from '../svg/icon_branch.svg';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
export default {
props: {
......@@ -9,6 +11,8 @@
},
components: {
userAvatarImage,
totalTime,
limitWarning,
},
computed: {
iconBranch() {
......
<script>
import iconBuildStatus from '../svg/icon_build_status.svg';
import iconBranch from '../svg/icon_branch.svg';
import iconBuildStatus from '../svg/icon_build_status.svg';
import iconBranch from '../svg/icon_branch.svg';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
export default {
export default {
props: {
items: Array,
stage: Object,
},
components: {
totalTime,
limitWarning,
},
computed: {
iconBuildStatus() {
return iconBuildStatus;
......@@ -15,7 +21,7 @@ export default {
return iconBranch;
},
},
};
};
</script>
<template>
<div>
......
......@@ -3,14 +3,12 @@
import Vue from 'vue';
import Cookies from 'js-cookie';
import Translate from '../vue_shared/translate';
import limitWarningComponent from './components/limit_warning_component.vue';
import stageCodeComponent from './components/stage_code_component.vue';
import stagePlanComponent from './components/stage_plan_component.vue';
import stageComponent from './components/stage_component.vue';
import stageReviewComponent from './components/stage_review_component.vue';
import stageStagingComponent from './components/stage_staging_component.vue';
import stageTestComponent from './components/stage_test_component.vue';
import totalTime from './components/total_time_component.vue';
import CycleAnalyticsService from './cycle_analytics_service';
import CycleAnalyticsStore from './cycle_analytics_store';
......@@ -133,8 +131,4 @@ $(() => {
},
},
});
// Register global components
Vue.component('limit-warning', limitWarningComponent);
Vue.component('total-time', totalTime);
});
......@@ -15,7 +15,6 @@
/* global NotificationsDropdown */
/* global GroupAvatar */
/* global LineHighlighter */
/* global ProjectFork */
/* global BuildArtifacts */
/* global GroupsSelect */
/* global Search */
......@@ -524,7 +523,9 @@ import initGroupAnalytics from './init_group_analytics';
shortcut_handler = true;
break;
case 'projects:forks:new':
new ProjectFork();
import(/* webpackChunkName: 'project_fork' */ './project_fork')
.then(fork => fork.default())
.catch(() => {});
break;
case 'projects:artifacts:browse':
new ShortcutsNavigation();
......
......@@ -67,10 +67,13 @@ const PARTICIPANTS_ROW_COUNT = 7;
originalText = $(this).data("original-text");
if (currentText === originalText) {
$(this).text(lessText);
if (gl.lazyLoader) gl.lazyLoader.loadCheck();
} else {
$(this).text(originalText);
}
return $(".js-participants-hidden").toggle();
$(".js-participants-hidden").toggle();
};
return IssuableContext;
......
......@@ -71,6 +71,7 @@ export const handleLocationHash = () => {
// This is required to handle non-unicode characters in hash
hash = decodeURIComponent(hash);
const target = document.getElementById(hash) || document.getElementById(`user-content-${hash}`);
const fixedTabs = document.querySelector('.js-tabs-affix');
const fixedDiffStats = document.querySelector('.js-diff-files-changed.is-stuck');
const fixedNav = document.querySelector('.navbar-gitlab');
......@@ -78,15 +79,10 @@ export const handleLocationHash = () => {
let adjustment = 0;
if (fixedNav) adjustment -= fixedNav.offsetHeight;
// scroll to user-generated markdown anchor if we cannot find a match
if (document.getElementById(hash) === null) {
const target = document.getElementById(`user-content-${hash}`);
if (target && target.scrollIntoView) {
target.scrollIntoView(true);
window.scrollBy(0, adjustment);
}
} else {
// only adjust for fixedTabs when not targeting user-generated content
if (fixedTabs) {
adjustment -= fixedTabs.offsetHeight;
}
......@@ -96,7 +92,6 @@ export const handleLocationHash = () => {
}
window.scrollBy(0, adjustment);
}
};
// Check if element scrolled into viewport from above or below
......
......@@ -55,7 +55,7 @@ window.dateFormat = dateFormat;
if (!timeagoInstance) {
const localeRemaining = function(number, index) {
return [
[s__('Timeago|less than a minute ago'), s__('Timeago|a while')],
[s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
[s__('Timeago|less than a minute ago'), s__('Timeago|%s seconds remaining')],
[s__('Timeago|about a minute ago'), s__('Timeago|1 minute remaining')],
[s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
......@@ -73,7 +73,7 @@ window.dateFormat = dateFormat;
};
locale = function(number, index) {
return [
[s__('Timeago|less than a minute ago'), s__('Timeago|a while')],
[s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
[s__('Timeago|less than a minute ago'), s__('Timeago|in %s seconds')],
[s__('Timeago|about a minute ago'), s__('Timeago|in 1 minute')],
[s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')],
......
......@@ -124,7 +124,6 @@ import './preview_markdown';
import './project';
import './project_avatar';
import './project_find_file';
import './project_fork';
import './project_import';
import './project_label_subscription';
import './project_new';
......
......@@ -62,7 +62,7 @@
},
deleteHandler() {
// eslint-disable-next-line no-alert
if (confirm('Are you sure you want to delete this list?')) {
if (confirm('Are you sure you want to delete this comment?')) {
this.isDeleting = true;
this.deleteNote(this.note)
......
......@@ -28,8 +28,7 @@
popoverOptions() {
return {
html: true,
delay: { hide: 600 },
trigger: 'hover',
trigger: 'focus',
placement: 'top',
title: '<div class="autodevops-title">This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b></div>',
content: `<a class="autodevops-link" href="${this.autoDevopsHelpPath}" target="_blank" rel="noopener noreferrer nofollow">Learn more about Auto DevOps</a>`,
......@@ -82,6 +81,7 @@
</span>
<a
v-if="pipeline.flags.auto_devops"
tabindex="0"
class="js-pipeline-url-autodevops label label-info autodevops-badge"
v-popover="popoverOptions"
role="button">
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, max-len */
(function() {
this.ProjectFork = (function() {
function ProjectFork() {
$('.fork-thumbnail a').on('click', function() {
export default () => {
$('.fork-thumbnail a').on('click', function forkThumbnailClicked() {
if ($(this).hasClass('disabled')) return false;
$('.fork-namespaces').hide();
return $('.save-project-loader').show();
});
}
return ProjectFork;
})();
}).call(window);
};
......@@ -19,7 +19,7 @@ export default class ProjectsService {
getSearchedProjects(searchQuery) {
return this.projectsPath.get({
simple: false,
simple: true,
per_page: 20,
membership: !!gon.current_user_id,
order_by: 'last_activity_at',
......
......@@ -95,7 +95,7 @@ export default RepoFile;
</div>
</td>
<td class="hidden-xs">
<td class="hidden-xs text-right">
<span
class="commit-update"
:title="tooltipTitle(file.lastCommitUpdate)">
......
......@@ -75,7 +75,7 @@ export default {
<tr>
<th class="name">Name</th>
<th class="hidden-sm hidden-xs last-commit">Last Commit</th>
<th class="hidden-xs last-update">Last Update</th>
<th class="hidden-xs last-update text-right">Last Update</th>
</tr>
</thead>
<tbody>
......
......@@ -58,13 +58,13 @@ const RepoHelper = {
return langs.find(lang => lang.extensions && lang.extensions.indexOf(`.${ext}`) > -1);
},
setDirectoryOpen(tree) {
setDirectoryOpen(tree, title) {
const file = tree;
if (!file) return undefined;
file.opened = true;
file.icon = 'fa-folder-open';
RepoHelper.updateHistoryEntry(file.url, file.name);
RepoHelper.updateHistoryEntry(file.url, title);
return file;
},
......@@ -135,6 +135,8 @@ const RepoHelper = {
return Service.getContent()
.then((response) => {
const data = response.data;
if (response.headers && response.headers['page-title']) data.pageTitle = response.headers['page-title'];
Store.isTree = RepoHelper.isTree(data);
if (!Store.isTree) {
if (!file) file = data;
......@@ -168,7 +170,7 @@ const RepoHelper = {
} else {
// it's a tree
if (!file) Store.isRoot = RepoHelper.isRoot(Service.url);
file = RepoHelper.setDirectoryOpen(file);
file = RepoHelper.setDirectoryOpen(file, data.pageTitle || data.name);
const newDirectory = RepoHelper.dataToListOfFiles(data);
Store.addFilesToDirectory(file, Store.files, newDirectory);
Store.prevURL = Service.blobURLtoParentTree(Service.url);
......@@ -255,7 +257,7 @@ const RepoHelper = {
history.pushState({ key: RepoHelper.key }, '', url);
if (title) {
document.title = `${title} · GitLab`;
document.title = title;
}
},
......
......@@ -83,7 +83,7 @@ const RepoStore = {
}).catch(Helper.loadingError);
}
if (!file.loading) Helper.updateHistoryEntry(file.url, file.name);
if (!file.loading) Helper.updateHistoryEntry(file.url, file.pageTitle || file.name);
RepoStore.binary = file.binary;
},
......
......@@ -29,7 +29,11 @@ import Cookies from 'js-cookie';
$('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
$('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
$document.on('click', '.js-sidebar-toggle', function(e, triggered) {
$document.on('click', '.js-sidebar-toggle', this.sidebarToggleClicked);
return $(document).off('click', '.js-issuable-todo').on('click', '.js-issuable-todo', this.toggleTodo);
};
Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
var $allGutterToggleIcons, $this, $thisIcon;
e.preventDefault();
$this = $(this);
......@@ -47,10 +51,8 @@ import Cookies from 'js-cookie';
if (gl.lazyLoader) gl.lazyLoader.loadCheck();
}
if (!triggered) {
return Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'));
Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'));
}
});
return $(document).off('click', '.js-issuable-todo').on('click', '.js-issuable-todo', this.toggleTodo);
};
Sidebar.prototype.toggleTodo = function(e) {
......
......@@ -287,6 +287,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
onClearInputClick(e) {
e.preventDefault();
this.wrap.toggleClass('has-value', !!e.target.value);
return this.searchInput.val('').focus();
}
......
......@@ -126,7 +126,7 @@
.search-input-wrap {
.search-icon,
.clear-icon {
color: rgba($color-200, .8);
fill: rgba($color-200, .8);
}
}
......@@ -141,7 +141,7 @@
.search-input-wrap {
.search-icon {
color: rgba($color-200, .8);
fill: rgba($color-200, .8);
}
}
}
......@@ -252,7 +252,7 @@ body {
.search-input-wrap {
.search-icon {
color: $theme-gray-200;
fill: $theme-gray-200;
}
.search-input {
......
......@@ -109,8 +109,7 @@ header {
.user-counter {
svg {
height: 16px;
width: 23px;
margin-right: 3px;
}
}
......@@ -133,16 +132,16 @@ header {
}
&.navbar-gitlab-new {
.fa-times {
.close-icon {
display: none;
}
.menu-expanded {
.fa-ellipsis-v {
.more-icon {
display: none;
}
.fa-times {
.close-icon {
display: block;
}
}
......
......@@ -27,7 +27,9 @@
}
svg {
&.s8 { @include svg-size(8px); }
&.s16 { @include svg-size(16px); }
&.s18 { @include svg-size(18px); }
&.s24 { @include svg-size(24px); }
&.s32 { @include svg-size(32px); }
&.s48 { @include svg-size(48px); }
......
......@@ -120,17 +120,24 @@ header.navbar-gitlab-new {
.container-fluid {
.navbar-toggle {
min-width: 45px;
padding: 4px $gl-padding;
padding: 0 $gl-padding;
margin-right: -7px;
font-size: 14px;
text-align: center;
color: currentColor;
svg {
fill: currentColor;
}
&:hover,
&:focus,
&.active {
color: currentColor;
background-color: transparent;
svg {
fill: currentColor;
}
}
}
......@@ -279,10 +286,6 @@ header.navbar-gitlab-new {
}
}
.admin-icon i {
font-size: 18px;
}
.caret-down {
height: 11px;
width: 11px;
......
......@@ -56,8 +56,8 @@ $new-sidebar-collapsed-width: 50px;
color: $hover-color;
.settings-avatar {
i {
color: $hover-color;
svg {
fill: $hover-color;
}
}
}
......@@ -76,12 +76,9 @@ $new-sidebar-collapsed-width: 50px;
.settings-avatar {
background-color: $white-light;
i {
font-size: 20px;
width: 100%;
color: $gl-text-color-secondary;
text-align: center;
align-self: center;
svg {
fill: $gl-text-color-secondary;
margin: auto;
}
}
......@@ -177,16 +174,16 @@ $new-sidebar-collapsed-width: 50px;
.nav-icon-container {
display: flex;
margin-right: 8px;
svg {
height: 16px;
width: 16px;
}
}
.fly-out-top-item {
display: none;
}
svg {
height: 16px;
width: 16px;
}
}
.nav-sidebar-inner-scroll {
......@@ -354,18 +351,22 @@ $new-sidebar-collapsed-width: 50px;
display: flex;
align-items: center;
i {
font-size: 20px;
svg {
fill: $gl-text-color-secondary;
margin-right: 8px;
}
.fa-angle-double-right {
.icon-angle-double-right {
display: none;
}
&:hover {
background-color: $border-color;
color: $gl-text-color;
svg {
fill: $gl-text-color;
}
}
}
......@@ -407,15 +408,16 @@ $new-sidebar-collapsed-width: 50px;
.toggle-sidebar-button {
width: $new-sidebar-collapsed-width - 2px;
padding: 16px 18px;
padding: 16px;
.collapse-text,
.fa-angle-double-left {
.icon-angle-double-left {
display: none;
}
.fa-angle-double-right {
.icon-angle-double-right {
display: block;
margin: 0;
}
}
}
......
......@@ -524,7 +524,7 @@ a.deploy-project-label {
text-align: center;
width: 169px;
&:hover,
&:hover:not(.disabled),
&.forked {
background-color: $row-hover;
border-color: $row-hover-border;
......@@ -551,6 +551,15 @@ a.deploy-project-label {
padding-top: $gl-padding;
color: $gl-text-color;
&.disabled {
opacity: .3;
cursor: not-allowed;
&:hover {
text-decoration: none;
}
}
.caption {
min-height: 30px;
padding: $gl-padding 0;
......
......@@ -81,17 +81,10 @@ input[type="checkbox"]:hover {
.clear-icon {
position: absolute;
right: 5px;
top: 0;
&::before {
font-family: FontAwesome;
font-weight: $gl-font-weight-normal;
font-style: normal;
}
top: 4px;
}
.search-icon {
@extend .fa-search;
transition: color $default-transition-duration;
-webkit-user-select: none;
-moz-user-select: none;
......@@ -99,7 +92,6 @@ input[type="checkbox"]:hover {
}
.clear-icon {
@extend .fa-times;
display: none;
}
......
......@@ -25,6 +25,8 @@ class ApplicationController < ActionController::Base
around_action :set_locale
after_action :set_page_title_header, if: -> { request.format == :json }
protect_from_forgery with: :exception
helper_method :can?, :current_application_settings
......@@ -347,4 +349,9 @@ class ApplicationController < ActionController::Base
sign_in user, store: false
end
end
def set_page_title_header
# Per https://tools.ietf.org/html/rfc5987, headers need to be ISO-8859-1, not UTF-8
response.headers['Page-Title'] = page_title('GitLab').encode('ISO-8859-1')
end
end
......@@ -59,6 +59,7 @@ module AuthenticatesWithTwoFactor
sign_in(user)
else
user.increment_failed_attempts!
Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=OTP")
flash.now[:alert] = 'Invalid two-factor code.'
prompt_for_two_factor(user)
end
......@@ -75,6 +76,7 @@ module AuthenticatesWithTwoFactor
sign_in(user)
else
user.increment_failed_attempts!
Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=U2F")
flash.now[:alert] = 'Authentication via U2F device failed.'
prompt_for_two_factor(user)
end
......
......@@ -16,6 +16,7 @@ class ConfirmationsController < Devise::ConfirmationsController
if signed_in?(resource_name)
after_sign_in(resource)
else
Gitlab::AppLogger.info("Email Confirmed: username=#{resource.username} email=#{resource.email} ip=#{request.remote_ip}")
flash[:notice] += " Please sign in."
new_session_path(resource_name)
end
......
......@@ -41,6 +41,8 @@ class Projects::BlobController < Projects::ApplicationController
end
format.json do
page_title @blob.path, @ref, @project.name_with_namespace
show_json
end
end
......
......@@ -35,6 +35,8 @@ class Projects::TreeController < Projects::ApplicationController
end
format.json do
page_title @path.presence || _("Files"), @ref, @project.name_with_namespace
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38261
Gitlab::GitalyClient.allow_n_plus_1_calls do
render json: TreeSerializer.new(project: @project, repository: @repository, ref: @ref).represent(@tree)
......
......@@ -43,10 +43,12 @@ class RegistrationsController < Devise::RegistrationsController
end
def after_sign_up_path_for(user)
Gitlab::AppLogger.info("User Created: username=#{user.username} email=#{user.email} ip=#{request.remote_ip} confirmed:#{user.confirmed?}")
user.confirmed? ? dashboard_projects_path : users_almost_there_path
end
def after_inactive_sign_up_path_for(_resource)
def after_inactive_sign_up_path_for(resource)
Gitlab::AppLogger.info("User Created: username=#{resource.username} email=#{resource.email} ip=#{request.remote_ip} confirmed:false")
users_almost_there_path
end
......
......@@ -15,6 +15,8 @@ class SessionsController < Devise::SessionsController
before_action :auto_sign_in_with_provider, only: [:new]
before_action :load_recaptcha
after_action :log_failed_login, only: [:new], if: :failed_login?
def new
set_minimum_password_length
@ldap_servers = Gitlab::LDAP::Config.available_servers
......@@ -31,12 +33,13 @@ class SessionsController < Devise::SessionsController
end
# hide the signed-in notification
flash[:notice] = nil
log_audit_event(current_user, with: authentication_method)
log_audit_event(current_user, resource, with: authentication_method)
log_user_activity(current_user)
end
end
def destroy
Gitlab::AppLogger.info("User Logout: username=#{current_user.username} ip=#{request.remote_ip}")
super
# hide the signed_out notice
flash[:notice] = nil
......@@ -44,6 +47,14 @@ class SessionsController < Devise::SessionsController
private
def log_failed_login
Gitlab::AppLogger.info("Failed Login: username=#{user_params[:login]} ip=#{request.remote_ip}")
end
def failed_login?
(options = env["warden.options"]) && options[:action] == "unauthenticated"
end
def login_counter
@login_counter ||= Gitlab::Metrics.counter(:user_session_logins_total, 'User sign in count')
end
......@@ -147,7 +158,8 @@ class SessionsController < Devise::SessionsController
user.invalidate_otp_backup_code!(user_params[:otp_attempt])
end
def log_audit_event(user, options = {})
def log_audit_event(user, resource, options = {})
Gitlab::AppLogger.info("Successful Login: username=#{resource.username} ip=#{request.remote_ip} method=#{options[:with]} admin=#{resource.admin?}")
AuditEventService.new(user, user, options)
.for_authentication.security_event
end
......
......@@ -21,7 +21,7 @@ module BreadcrumbsHelper
def breadcrumb_list_item(link)
content_tag "li" do
link + icon("angle-right", class: "breadcrumbs-list-angle")
link + sprite_icon("angle-right", size: 8, css_class: "breadcrumbs-list-angle")
end
end
......
......@@ -24,9 +24,9 @@ module IconsHelper
end
def sprite_icon(icon_name, size: nil, css_class: nil)
css_classes = size ? "s#{size}" : nil
css_classes = size ? "s#{size}" : ""
css_classes << " #{css_class}" unless css_class.blank?
content_tag(:svg, content_tag(:use, "", { "xlink:href" => "#{image_path('icons.svg')}##{icon_name}" } ), class: css_classes)
content_tag(:svg, content_tag(:use, "", { "xlink:href" => "#{image_path('icons.svg')}##{icon_name}" } ), class: css_classes.empty? ? nil : css_classes)
end
def audit_icon(names, options = {})
......
......@@ -9,7 +9,7 @@ module PageLayoutHelper
end
# Segments are seperated by middot
@page_title.join(" \u00b7 ")
@page_title.join(" · ")
end
# Define or get a description for the current page
......
......@@ -23,11 +23,14 @@ module ProjectsHelper
classes = %W[avatar avatar-inline s#{opts[:size]}]
classes << opts[:avatar_class] if opts[:avatar_class]
image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: classes, alt: '')
avatar = avatar_icon(author, opts[:size])
src = opts[:lazy_load] ? nil : avatar
image_tag(src, width: opts[:size], class: classes, alt: '', "data-src" => avatar)
end
def link_to_member(project, author, opts = {}, &block)
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name", tooltip: false }
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name", tooltip: false, lazy_load: false }
opts = default_opts.merge(opts)
return "(deleted)" unless author
......
......@@ -73,7 +73,7 @@ class GpgKey < ActiveRecord::Base
end
def verified_and_belongs_to_email?(email)
emails_with_verified_status.fetch(email, false)
emails_with_verified_status.fetch(email.downcase, false)
end
def update_invalid_gpg_signatures
......
......@@ -75,6 +75,7 @@ class Project < ActiveRecord::Base
attr_accessor :old_path_with_namespace
attr_accessor :template_name
attr_writer :pipeline_status
attr_accessor :skip_disk_validation
alias_attribute :title, :name
......@@ -231,7 +232,7 @@ class Project < ActiveRecord::Base
validates :import_url, importable_url: true, if: [:external_import?, :import_url_changed?]
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
validate :can_create_repository?, on: [:create, :update], if: ->(project) { !project.persisted? || project.renamed? }
validate :check_repository_path_availability, on: [:create, :update], if: ->(project) { !project.persisted? || project.renamed? }
validate :avatar_type,
if: ->(project) { project.avatar.present? && project.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
......@@ -1018,7 +1019,8 @@ class Project < ActiveRecord::Base
end
# Check if repository already exists on disk
def can_create_repository?
def check_repository_path_availability
return true if skip_disk_validation
return false unless repository_storage_path
expires_full_path_cache # we need to clear cache to validate renames correctly
......
......@@ -41,7 +41,10 @@ class Repository
CACHED_METHODS = %i(size commit_count rendered_readme contribution_guide
changelog license_blob license_key gitignore koding_yml
gitlab_ci_yml branch_names tag_names branch_count
tag_count avatar exists? empty? root_ref).freeze
tag_count avatar exists? empty? root_ref has_visible_content?).freeze
# Methods that use cache_method but only memoize the value
MEMOIZED_CACHED_METHODS = %i(license empty_repo?).freeze
# Certain method caches should be refreshed when certain types of files are
# changed. This Hash maps file types (as returned by Gitlab::FileDetector) to
......@@ -98,12 +101,6 @@ class Repository
)
end
# we need to have this method here because it is not cached in ::Git and
# the method is called multiple times for every request
def has_visible_content?
branch_count > 0
end
def inspect
"#<#{self.class.name}:#{@disk_path}>"
end
......@@ -282,7 +279,7 @@ class Repository
end
def expire_branches_cache
expire_method_caches(%i(branch_names branch_count))
expire_method_caches(%i(branch_names branch_count has_visible_content?))
@local_branches = nil
@branch_exists_memo = nil
end
......@@ -353,7 +350,7 @@ class Repository
def expire_emptiness_caches
return unless empty?
expire_method_caches(%i(empty?))
expire_method_caches(%i(empty? has_visible_content?))
end
def lookup_cache
......@@ -530,9 +527,10 @@ class Repository
delegate :tag_names, to: :raw_repository
cache_method :tag_names, fallback: []
delegate :branch_count, :tag_count, to: :raw_repository
delegate :branch_count, :tag_count, :has_visible_content?, to: :raw_repository
cache_method :branch_count, fallback: 0
cache_method :tag_count, fallback: 0
cache_method :has_visible_content?, fallback: false
def avatar
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38327
......
......@@ -711,8 +711,12 @@ class User < ActiveRecord::Base
end
def ldap_user?
if identities.loaded?
identities.find { |identity| identity.provider.start_with?('ldap') && !identity.extern_uid.nil? }
else
identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
end
end
def ldap_identity
@ldap_identity ||= identities.find_by(["provider LIKE ?", "ldap%"])
......@@ -1086,6 +1090,12 @@ class User < ActiveRecord::Base
user_synced_attributes_metadata&.read_only?(attribute)
end
# override, from Devise
def lock_access!
Gitlab::AppLogger.info("Account Locked: username=#{username}")
super
end
protected
# override, from Devise::Validatable
......
......@@ -11,6 +11,8 @@ class GlobalPolicy < BasePolicy
with_options scope: :user, score: 0
condition(:access_locked) { @user.access_locked? }
condition(:can_create_fork, scope: :user) { @user.manageable_namespaces.any? { |namespace| @user.can?(:create_projects, namespace) } }
rule { anonymous }.policy do
prevent :log_in
prevent :access_api
......@@ -40,6 +42,10 @@ class GlobalPolicy < BasePolicy
enable :create_group
end
rule { can_create_fork }.policy do
enable :create_fork
end
rule { access_locked }.policy do
prevent :log_in
end
......
class NamespacePolicy < BasePolicy
rule { anonymous }.prevent_all
condition(:personal_project, scope: :subject) { @subject.kind == 'user' }
condition(:can_create_personal_project, scope: :user) { @user.can_create_project? }
condition(:owner) { @subject.owner == @user }
rule { owner | admin }.policy do
enable :create_projects
enable :admin_namespace
end
rule { personal_project & ~can_create_personal_project }.prevent :create_projects
end
......@@ -21,8 +21,8 @@
%a
Loading...
= dropdown_loading
%i.search-icon
%i.clear-icon.js-clear-input
= sprite_icon('search', size: 16, css_class: 'search-icon')
= sprite_icon('close', size: 16, css_class: 'clear-icon js-clear-input')
= hidden_field_tag :group_id, @group.try(:id), class: 'js-search-group-options', data: group_data_attrs
......
......@@ -24,29 +24,29 @@
= render 'layouts/search' unless current_controller?(:search)
%li.visible-sm-inline-block.visible-xs-inline-block
= link_to search_path, title: 'Search', aria: { label: "Search" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search')
= sprite_icon('search', size: 16)
- if current_user
= nav_link(path: 'dashboard#issues', html_options: { class: "user-counter" }) do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= custom_icon('issues')
= sprite_icon('issues', size: 16)
- issues_count = assigned_issuables_count(:issues)
%span.badge.issues-count{ class: ('hidden' if issues_count.zero?) }
= number_with_delimiter(issues_count)
= nav_link(path: 'dashboard#merge_requests', html_options: { class: "user-counter" }) do
= link_to assigned_mrs_dashboard_path, title: 'Merge requests', class: 'dashboard-shortcuts-merge_requests', aria: { label: "Merge requests" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= custom_icon('mr_bold')
= sprite_icon('git-merge', size: 16)
- merge_requests_count = assigned_issuables_count(:merge_requests)
%span.badge.merge-requests-count{ class: ('hidden' if merge_requests_count.zero?) }
= number_with_delimiter(merge_requests_count)
= nav_link(controller: 'dashboard/todos', html_options: { class: "user-counter" }) do
= link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, class: 'shortcuts-todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= custom_icon('todo_done')
= sprite_icon('todo-done', size: 16)
%span.badge.todos-count{ class: ('hidden' if todos_pending_count.zero?) }
= todos_count_format(todos_pending_count)
%li.header-user.dropdown
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
= image_tag avatar_icon(current_user, 23), width: 23, height: 23, class: "header-user-avatar"
= custom_icon('caret_down')
= sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu-nav.dropdown-menu-align-right
%ul
%li.current-user
......@@ -75,7 +75,7 @@
%button.navbar-toggle.hidden-sm.hidden-md.hidden-lg{ type: 'button' }
%span.sr-only Toggle navigation
= icon('ellipsis-v', class: 'js-navbar-toggle-right')
= icon('times', class: 'js-navbar-toggle-left')
= sprite_icon('more', size: 16, css_class: 'more-icon js-navbar-toggle-right')
= sprite_icon('close', size: 16, css_class: 'close-icon js-navbar-toggle-left')
= render 'shared/outdated_browser'
%li.header-new.dropdown
= link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip", title: "New...", ref: 'tooltip', aria: { label: "New..." }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body' } do
= custom_icon('plus_square')
= custom_icon('caret_down')
= sprite_icon('plus-square', size: 16)
= sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu-nav.dropdown-menu-align-right
%ul
- if @group&.persisted?
......
......@@ -2,7 +2,7 @@
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects" }) do
%a{ href: "#", data: { toggle: "dropdown" } }
Projects
= custom_icon('caret_down')
= sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu.projects-dropdown-menu
= render "layouts/nav/projects_dropdown/show"
......@@ -25,7 +25,7 @@
%li.header-more.dropdown.hidden-lg
%a{ href: "#", data: { toggle: "dropdown" } }
More
= custom_icon('caret_down')
= sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu
%ul
= nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { class: "visible-xs" }) do
......@@ -54,7 +54,7 @@
- if current_user.admin?
= nav_link(controller: 'admin/dashboard') do
= link_to admin_root_path, class: 'admin-icon', title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('wrench fw')
= sprite_icon('admin', size: 18)
- if Gitlab::Sherlock.enabled?
%li
= link_to sherlock_transactions_path, class: 'admin-icon', title: 'Sherlock Transactions',
......
......@@ -4,7 +4,7 @@
%li.dropdown
%button.text-expander.has-tooltip.js-breadcrumbs-collapsed-expander{ type: "button", data: { toggle: "dropdown", container: "body" }, "aria-label": button_tooltip, title: button_tooltip }
= icon("ellipsis-h")
= icon("angle-right", class: "breadcrumbs-list-angle")
= sprite_icon("angle-right", css_class: "breadcrumbs-list-angle")
.dropdown-menu
%ul
- @breadcrumb_dropdown_links[dropdown_location].each_with_index do |link, index|
......
......@@ -3,7 +3,7 @@
.context-header
= link_to admin_root_path, title: 'Admin Overview' do
.avatar-container.s40.settings-avatar
= icon('wrench')
= sprite_icon('admin', size: 24)
.sidebar-context-title Admin Area
%ul.sidebar-top-level-items
= nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts conversational_development_index), html_options: {class: 'home'}) do
......@@ -12,7 +12,6 @@
= sprite_icon('overview')
%span.nav-item-name
Overview
%ul.sidebar-sub-level-items
= nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts conversational_development_index), html_options: { class: "fly-out-top-item" } ) do
= link_to admin_root_path do
......
......@@ -3,7 +3,7 @@
.context-header
= link_to profile_path, title: 'Profile Settings' do
.avatar-container.s40.settings-avatar
= icon('user')
= sprite_icon('user', size: 24)
.sidebar-context-title User Settings
%ul.sidebar-top-level-items
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
......
......@@ -4,12 +4,11 @@
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: _('Go to your fork'), class: 'btn has-tooltip' do
= custom_icon('icon_fork')
%span= s_('GoToYourFork|Fork')
- elsif !current_user.can_create_project?
= link_to new_project_fork_path(@project), title: _('You have reached your project limit'), class: 'btn has-tooltip disabled' do
= custom_icon('icon_fork')
%span= s_('CreateNewFork|Fork')
- else
= link_to new_project_fork_path(@project), class: 'btn' do
- can_create_fork = current_user.can?(:create_fork)
= link_to new_project_fork_path(@project),
class: "btn btn-default #{'has-tooltip disabled' unless can_create_fork}",
title: (_('You have reached your project limit') unless can_create_fork) do
= custom_icon('icon_fork')
%span= s_('CreateNewFork|Fork')
.count-with-arrow
......
......@@ -13,7 +13,7 @@
- if @namespaces.present?
%label.label-light
%span
Click to fork the project to a user or group
Click to fork the project
- @namespaces.in_groups_of(6, false) do |group|
.row
- group.each do |namespace|
......@@ -29,8 +29,12 @@
.caption
= namespace.human_name
- else
.fork-thumbnail
= link_to project_forks_path(@project, namespace_key: namespace.id), method: "POST" do
- can_create_project = current_user.can?(:create_projects, namespace)
.fork-thumbnail{ class: ("disabled" unless can_create_project) }
= link_to project_forks_path(@project, namespace_key: namespace.id),
method: "POST",
class: ("disabled has-tooltip" unless can_create_project),
title: (_('You have reached your project limit') unless can_create_project) do
- if /no_((\w*)_)*avatar/.match(avatar)
.no-avatar
= icon 'question'
......
......@@ -12,7 +12,7 @@
= webpack_bundle_tag 'repo'
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
- if show_auto_devops_callout?(@project)
- if show_auto_devops_callout?(@project) && !show_new_repo?
= render 'shared/auto_devops_callout'
= render 'projects/last_push'
= render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id)
- commit_message = @page.persisted? ? "Update #{@page.title}" : "Create #{@page.title}"
- commit_message = @page.persisted? ? s_("WikiPageEdit|Update %{page_title}") : s_("WikiPageCreate|Create %{page_title}")
- commit_message = commit_message % { page_title: @page.title }
= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form common-note-form prepend-top-default js-quick-submit' } do |f|
= form_errors(@page)
......@@ -12,13 +13,13 @@
.form-group
.col-sm-12= f.label :format, class: 'control-label-full-width'
.col-sm-12
= f.select :format, options_for_select(ProjectWiki::MARKUPS, {selected: @page.format}), {}, class: "form-control"
= f.select :format, options_for_select(ProjectWiki::MARKUPS, {selected: @page.format}), {}, class: 'form-control'
.form-group
.col-sm-12= f.label :content, class: 'control-label-full-width'
.col-sm-12
= render layout: 'projects/md_preview', locals: { url: project_wiki_preview_markdown_path(@project, @page.slug) } do
= render 'projects/zen', f: f, attr: :content, classes: 'note-textarea', placeholder: 'Write your content or drag files here...'
= render 'projects/zen', f: f, attr: :content, classes: 'note-textarea', placeholder: s_("WikiPage|Write your content or drag files here...")
= render 'shared/notes/hints'
.clearfix
......@@ -26,12 +27,11 @@
.help-block
= succeed '.' do
To link to a (new) page, simply type
%code [Link Title](page-slug)
= (s_("WikiMarkdownTip|To link to a (new) page, simply type %{link_example}") % { link_example: '<code>[Link Title](page-slug)</code>' }).html_safe
= succeed '.' do
More examples are in the
= link_to 'documentation', help_page_path("user/markdown", anchor: "wiki-specific-markdown")
- markdown_link = link_to s_("WikiMarkdownDocs|documentation"), help_page_path('user/markdown', anchor: 'wiki-specific-markdown')
= (s_("WikiMarkdownDocs|More examples are in the %{docs_link}") % { docs_link: markdown_link }).html_safe
.form-group
.col-sm-12= f.label :commit_message, class: 'control-label-full-width'
......@@ -39,10 +39,10 @@
.form-actions
- if @page && @page.persisted?
= f.submit 'Save changes', class: "btn-save btn"
= f.submit _("Save changes"), class: 'btn-save btn'
.pull-right
= link_to "Cancel", project_wiki_path(@project, @page), class: "btn btn-cancel btn-grouped"
= link_to _("Cancel"), project_wiki_path(@project, @page), class: 'btn btn-cancel btn-grouped'
- else
= f.submit 'Create page', class: "btn-create btn"
= f.submit s_("Wiki|Create page"), class: 'btn-create btn'
.pull-right
= link_to "Cancel", project_wiki_path(@project, :home), class: "btn btn-cancel"
= link_to _("Cancel"), project_wiki_path(@project, :home), class: 'btn btn-cancel'
- if (@page && @page.persisted?)
- if can?(current_user, :create_wiki, @project)
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
New page
= s_("Wiki|New page")
= link_to project_wiki_history_path(@project, @page), class: "btn" do
Page history
= s_("Wiki|Page history")
- if can?(current_user, :create_wiki, @project) && @page.latest?
= link_to project_wiki_edit_path(@project, @page), class: "btn js-wiki-edit" do
Edit
= _("Edit")
......@@ -3,16 +3,15 @@
.modal-content
.modal-header
%a.close{ href: "#", "data-dismiss" => "modal" } ×
%h3.page-title New Wiki Page
%h3.page-title= s_("WikiNewPageTitle|New Wiki Page")
.modal-body
%form.new-wiki-page
.form-group
= label_tag :new_wiki_path do
%span Page slug
= text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => project_wikis_path(@project), autofocus: true
%span= s_("WikiPage|Page slug")
= text_field_tag :new_wiki_path, nil, placeholder: s_("WikiNewPagePlaceholder|how-to-setup"), class: 'form-control', required: true, :'data-wikis-path' => project_wikis_path(@project), autofocus: true
%span.new-wiki-page-slug-tip
= icon('lightbulb-o')
Tip: You can specify the full path for the new file.
We will automatically create any missing directories.
= s_("WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories.")
.form-actions
= button_tag 'Create page', class: 'build-new-wiki btn btn-create'
= button_tag s_("Wiki|Create page"), class: "build-new-wiki btn btn-create"
......@@ -2,4 +2,4 @@
= link_to wiki_page.title, project_wiki_path(@project, wiki_page)
%small (#{wiki_page.format})
.pull-right
%small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)}
%small= (s_("Last edited %{date}") % { date: time_ago_with_tooltip(wiki_page.commit.authored_date) }).html_safe
......@@ -8,7 +8,7 @@
= link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do
= succeed '&nbsp;' do
= icon('cloud-download')
Clone repository
= _("Clone repository")
.blocks-container
.block.block-first
......@@ -17,6 +17,6 @@
.block
= link_to project_wikis_pages_path(@project), class: 'btn btn-block' do
More Pages
= s_("Wiki|More Pages")
= render 'projects/wikis/new'
- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout
- page_title "Edit", @page.title.capitalize, "Wiki"
- page_title _("Edit"), @page.title.capitalize, _("Wiki")
- if @conflict
.alert.alert-danger
Someone edited the page the same time you did. Please check out
= link_to "the page", project_wiki_path(@project, @page), target: "_blank"
and make sure your changes will not unintentionally remove theirs.
- page_link = link_to s_("WikiPageConflictMessage|the page"), project_wiki_path(@project, @page), target: "_blank"
= (s_("WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs.") % { page_link: page_link }).html_safe
.wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
......@@ -20,20 +19,20 @@
%span.light
&middot;
- if @page.persisted?
Edit Page
= s_("Wiki|Edit Page")
- else
Create Page
= s_("Wiki|Create Page")
.nav-controls
- if can?(current_user, :create_wiki, @project)
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
New page
= s_("Wiki|New page")
- if @page.persisted?
= link_to project_wiki_history_path(@project, @page), class: "btn" do
Page history
= s_("Wiki|Page history")
- if can?(current_user, :admin_wiki, @project)
= link_to project_wiki_path(@project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do
Delete
= link_to project_wiki_path(@project, @page), data: { confirm: s_("WikiPageConfirmDelete|Are you sure you want to delete this page?")}, method: :delete, class: "btn btn-danger" do
= _("Delete")
= render 'form'
......
- page_title "Wiki"
- page_title _("Wiki")
%h3.page-title Empty page
%h3.page-title= _("Wiki|Empty page")
%hr
.error_message
You are not allowed to create wiki pages
= s_("WikiEmptyPageError|You are not allowed to create wiki pages")
- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout
- page_title "Git Access", "Wiki"
- page_title s_("WikiClone|Git Access"), _("Wiki")
.wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.visible-xs.visible-sm.pull-right.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
.git-access-header
Clone repository
= _("Clone repository")
%strong= @project_wiki.full_path
= render "shared/clone_panel", project: @project_wiki
.wiki-git-access
%h3 Install Gollum
%h3= s_("WikiClone|Install Gollum")
%pre.dark
:preserve
gem install gollum
%p
It is recommended to install
%code github-markdown
so that GFM features render locally:
= (s_("WikiClone|It is recommended to install %{markdown} so that GFM features render locally:") % { markdown: "<code>github-markdown</code>" }).html_safe
%pre.dark
:preserve
gem install github-markdown
%h3 Clone your wiki
%h3= s_("WikiClone|Clone your wiki")
%pre.dark
:preserve
git clone #{ content_tag(:span, h(default_url_to_repo(@project_wiki)), class: 'clone')}
cd #{h @project_wiki.path}
%h3 Start Gollum and edit locally
%h3= s_("WikiClone|Start Gollum and edit locally")
%pre.dark
:preserve
gollum
......
- page_title "History", @page.title.capitalize, "Wiki"
- page_title _("History"), @page.title.capitalize, _("Wiki")
.wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
......@@ -9,17 +9,17 @@
= link_to @page.title.capitalize, project_wiki_path(@project, @page)
%span.light
&middot;
History
= _("History")
.table-holder
%table.table
%thead
%tr
%th Page version
%th Author
%th Commit Message
%th Last updated
%th Format
%th= s_("Wiki|Page version")
%th= _("Author")
%th= _("Commit Message")
%th= _("Last updated")
%th= _("Format")
%tbody
- @page.versions.each_with_index do |version, index|
- commit = version
......
- @no_container = true
- add_to_breadcrumbs "Wiki", get_project_wiki_path(@project)
- breadcrumb_title "Pages"
- page_title "Pages", "Wiki"
- breadcrumb_title s_("Wiki|Pages")
- page_title s_("Wiki|Pages"), _("Wiki")
%div{ class: container_class }
.wiki-page-header
.nav-text
%h2.wiki-page-title
Wiki Pages
= s_("Wiki|Wiki Pages")
.nav-controls
= link_to project_wikis_git_access_path(@project), class: 'btn' do
= icon('cloud-download')
Clone repository
= _("Clone repository")
%ul.wiki-pages-list.content-list
= render @wiki_entries, context: 'pages'
......
- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout
- breadcrumb_title @page.title.capitalize
- wiki_breadcrumb_dropdown_links(@page.slug)
- page_title @page.title.capitalize, "Wiki"
- add_to_breadcrumbs "Wiki", get_project_wiki_path(@project)
- page_title @page.title.capitalize, _("Wiki")
- add_to_breadcrumbs _("Wiki"), get_project_wiki_path(@project)
.wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
......@@ -11,9 +11,7 @@
.nav-text
%h2.wiki-page-title= @page.title.capitalize
%span.wiki-last-edit-by
Last edited by
%strong
#{@page.commit.author.name}
= (_("Last edited by %{name}") % { name: "<strong>#{@page.commit.author.name}</strong>" }).html_safe
#{time_ago_with_tooltip(@page.commit.authored_date)}
.nav-controls
......@@ -21,8 +19,10 @@
- if @page.historical?
.warning_message
This is an old version of this page.
You can view the #{link_to "most recent version", project_wiki_path(@project, @page)} or browse the #{link_to "history", project_wiki_history_path(@project, @page)}.
= s_("WikiHistoricalPage|This is an old version of this page.")
- most_recent_link = link_to s_("WikiHistoricalPage|most recent version"), project_wiki_path(@project, @page)
- history_link = link_to s_("WikiHistoricalPage|history"), project_wiki_history_path(@project, @page)
= (s_("WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}.") % { most_recent_link: most_recent_link, history_link: history_link }).html_safe
.wiki-holder.prepend-top-default.append-bottom-default
.wiki
......
%a.toggle-sidebar-button.js-toggle-sidebar{ role: "button", type: "button", title: "Toggle sidebar" }
= icon('angle-double-left')
= icon('angle-double-right')
= sprite_icon('angle-double-left', css_class: 'icon-angle-double-left')
= sprite_icon('angle-double-right', css_class: 'icon-angle-double-right')
%span.collapse-text Collapse sidebar
= button_tag class: 'close-nav-button', type: 'button' do
= icon ('times')
= sprite_icon('close', size: 16)
%span.collapse-text Close sidebar
......@@ -11,7 +11,7 @@
.hide-collapsed.participants-list
- participants.each do |participant|
.participants-author.js-participants-author
= link_to_member(@project, participant, name: false, size: 24)
= link_to_member(@project, participant, name: false, size: 24, lazy_load: true)
- if participants_extra > 0
.hide-collapsed.participants-more
%a.js-participants-more{ href: "#", data: { original_text: "+ #{participants_size - 7} more", less_text: "- show less" } }
......
---
title: Link SAML users to LDAP by email.
merge_request: 14216
author:
type: changed
---
title: Load sidebar participants avatars only when visible
merge_request: 14270
author:
type: other
---
title: Reschedule merge request diff background migrations to catch failures from
9.5 run
merge_request:
author:
type: fixed
---
title: Use `simple=true` for projects API in Projects dropdown for better search performance
merge_request:
author:
type: other
---
title: Ensure no exception is raised when Raven tries to get the current user in API
context
merge_request: 14580
author:
type: fixed
---
title: Improves UX of autodevops popover to match gpg one
merge_request:
author:
type: fixed
---
title: Fix comment deletion confirmation dialog typo
merge_request:
author:
type: fixed
---
title: Whitelist authorized_keys.lock in the gitlab:check rake task
merge_request: 14624
author:
type: fixed
---
title: Compare email addresses case insensitively when verifying GPG signatures
merge_request: 14376
author: Tim Bishop
type: fixed
---
title: Fixed fork button being disabled for users who can fork to a group
merge_request:
author:
type: fixed
---
title: Fixed notes not being scrolled to in merge requests
merge_request:
author:
type: fixed
---
title: Fix case sensitive email confirmation on signup
merge_request: 14606
author: robdel12
type: fixed
---
title: Add username as GL_USERNAME in hooks
merge_request:
author:
---
title: Fix gitlab-rake gitlab:import:repos task failing
merge_request:
author:
type: fixed
---
title: Fix pushes to an empty repository not invalidating has_visible_content? cache
merge_request:
author:
type: fixed
---
title: Ensure all refs are restored on a restore from backup
merge_request:
author:
type: fixed
---
title: Make Redcarpet Markdown renderer thread-safe
merge_request:
author:
type: fixed
......@@ -36,7 +36,7 @@ Devise.setup do |config|
# Configure which authentication keys should be case-insensitive.
# These keys will be downcased upon creating or modifying a user and when used
# to authenticate or find a user. Default is :email.
config.case_insensitive_keys = [:email]
config.case_insensitive_keys = [:email, :email_confirmation]
# Configure which authentication keys should have whitespace stripped.
# These keys will have whitespace before and after removed upon creating or
......
class ScheduleMergeRequestDiffMigrationsTakeTwo < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
BATCH_SIZE = 500
MIGRATION = 'DeserializeMergeRequestDiffsAndCommits'
DELAY_INTERVAL = 10.minutes
disable_ddl_transaction!
class MergeRequestDiff < ActiveRecord::Base
self.table_name = 'merge_request_diffs'
include ::EachBatch
default_scope { where('st_commits IS NOT NULL OR st_diffs IS NOT NULL') }
end
# By this point, we assume ScheduleMergeRequestDiffMigrations - the first
# version of this - has already run. On GitLab.com, we have ~220k un-migrated
# rows, but these rows will, in general, take a long time.
#
# With a gap of 10 minutes per batch, and 500 rows per batch, these migrations
# are scheduled over 220_000 / 500 / 6 ~= 74 hours, which is a little over
# three days.
def up
queue_background_migration_jobs_by_range_at_intervals(MergeRequestDiff, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
end
def down
end
end
......@@ -12,6 +12,7 @@ status of the migration.
Gitaly makes heavy use of [feature flags](feature_flags.md).
Each Rugged-to-Gitaly migration goes through a [series of phases](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/MIGRATION_PROCESS.md):
* **Opt-In**: by default the Rugged implementation is used.
* Production instances can choose to enable the Gitaly endpoint by enabling the feature flag.
* For testing purposes, you may wish to enable all feature flags by default. This can be done by exporting the following
......@@ -19,7 +20,7 @@ Each Rugged-to-Gitaly migration goes through a [series of phases](https://gitlab
* On developer instances (ie, when `Rails.env.development?` is true), the Gitaly endpoint
is enabled by default, but can be _disabled_ using feature flags.
* **Opt-Out**: by default, the Gitaly endpoint is used, but the feature can be explicitly disabled using the feature flag.
* **Madatory**: The migration is complete and cannot be disabled. The old codepath is removed.
* **Mandatory**: The migration is complete and cannot be disabled. The old codepath is removed.
### Enabling and Disabling Feature
......@@ -49,6 +50,35 @@ If your test-suite is failing with Gitaly issues, as a first step, try running:
rm -rf tmp/tests/gitaly
```
## `TooManyInvocationsError` errors
During development and testing, you may experience `Gitlab::GitalyClient::TooManyInvocationsError` failures.
The `GitalyClient` will attempt to block against potential n+1 issues by raising this error
when Gitaly is called more than 30 times in a single Rails request or Sidekiq execution.
As a temporary measure, export `GITALY_DISABLE_REQUEST_LIMITS=1` to suppress the error. This will disable the n+1 detection
in your development environment.
Please raise an issue in the GitLab CE or EE repositories to report the issue. Include the labels ~Gitaly
~performance ~"technical debt". Please ensure that the issue contains the full stack trace and error message of the
`TooManyInvocationsError`. Also include any known failing tests if possible.
Isolate the source of the n+1 problem. This will normally be a loop that results in Gitaly being called for each
element in an array. If you are unable to isolate the problem, please contact a member
of the [Gitaly Team](https://gitlab.com/groups/gl-gitaly/group_members) for assistance.
Once the source has been found, wrap it in an `allow_n_plus_1_calls` block, as follows:
```ruby
# n+1: link to n+1 issue
Gitlab::GitalyClient.allow_n_plus_1_calls do
# original code
commits.each { |commit| ... }
end
```
Once the code is wrapped in this block, this code-path will be excluded from n+1 detection.
---
[Return to Development documentation](README.md)
# GitLab Helm Chart
> **Note**:
* This chart will be replaced by the [gitlab-omnibus](gitlab_omnibus.md) chart, once it supports [additional configuration options](https://gitlab.com/charts/charts.gitlab.io/issues/68).
* This chart will be replaced by the [gitlab-omnibus](gitlab_omnibus.md) chart, once it supports [additional configuration options](https://gitlab.com/charts/charts.gitlab.io/issues/68). For more information on available charts, please see our [overview](index.md#chart-overview).
* These charts have been tested on Google Container Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/charts.gitlab.io/issues).
For more information on available GitLab Helm Charts, please see our [overview](index.md#chart-overview).
## Introduction
The `gitlab` Helm chart deploys just GitLab into your Kubernetes cluster, and offers extensive configuration options. This chart requires advanced knowledge of Kubernetes to successfully use. For most deployments we **strongly recommended** the [gitlab-omnibus](gitlab_omnibus.md) chart, which will replace this chart once it supports [additional configuration options](https://gitlab.com/charts/charts.gitlab.io/issues/68). Due to the difficulty in supporting upgrades to the `omnibus-gitlab` chart, migrating will require exporting data out of this instance and importing it into the new deployment.
This chart includes the following:
......@@ -15,8 +20,6 @@ This chart includes the following:
- Optional PostgreSQL deployment using the [PostgreSQL Chart](https://github.com/kubernetes/charts/tree/master/stable/postgresql) (defaults to enabled)
- Optional Ingress (defaults to disabled)
For more information on available GitLab Helm Charts, please see our [overview](index.md#chart-overview).
## Prerequisites
- _At least_ 3 GB of RAM available on your cluster. 41GB of storage and 2 CPU are also required.
......
......@@ -9,12 +9,12 @@ should be deployed, upgraded, and configured.
## Chart Overview
* **[GitLab-Omnibus](#gitlab-omnibus-chart-recommended)**: The best way to run GitLab on Kubernetes today. It is suited for small to medium deployments, and is in beta while support for backups and other features are added.
* **[Upcoming Cloud Native Charts](#upcoming-cloud-native-helm-charts)**: The next generation of charts, currently in development. Will support large deployments, with horizontal scaling of individual GitLab components.
* **[GitLab-Omnibus](gitlab_omnibus.md)**: The best way to run GitLab on Kubernetes today. It is suited for small to medium deployments, and is in beta while support for backups and other features are added.
* **[Cloud Native GitLab Chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md)**: The next generation GitLab chart, currently in development. Will support large deployments with horizontal scaling of individual GitLab components.
* Other Charts
* [GitLab Runner Chart](#gitlab-runner-chart): For deploying just the GitLab Runner.
* [Advanced GitLab Installation](#advanced-gitlab-installation): Provides additional deployment options, but provides less functionality out-of-the-box. It's beta, no longer actively developed, and will be deprecated by [gitlab-omnibus](#gitlab-omnibus-chart-recommended) once it supports these options.
* [Community Contributed Charts](#community-contributed-charts): Community contributed charts, deprecated by the official GitLab charts.
* [GitLab Runner Chart](gitlab_runner_chart.md): For deploying just the GitLab Runner.
* [Advanced GitLab Installation](gitlab_chart.md): Provides additional deployment options, but provides less functionality out-of-the-box. It's beta, no longer actively developed, and will be deprecated by [gitlab-omnibus](#gitlab-omnibus-chart-recommended) once it supports these options.
* [Community Contributed Charts](#community-contributed-charts): Community contributed charts, deprecated by the official GitLab chart.
## GitLab-Omnibus Chart (Recommended)
> **Note**: This chart is in beta while [additional features](https://gitlab.com/charts/charts.gitlab.io/issues/68) are being added.
......@@ -25,9 +25,9 @@ Once the [cloud native charts](#upcoming-cloud-native-helm-charts) are ready for
Learn more about the [gitlab-omnibus chart.](gitlab_omnibus.md)
## Upcoming Cloud Native Charts
## Cloud Native GitLab Chart
GitLab is working towards building a [cloud native deployment method](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). A key part of this effort is to isolate each service into its [own Docker container and Helm chart](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2420), rather than utilizing the all-in-one container image of the [current charts](#official-gitlab-helm-charts-recommended).
GitLab is working towards building a [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). A key part of this effort is to isolate each service into its [own Docker container and Helm chart](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2420), rather than utilizing the all-in-one container image of the [current charts](#official-gitlab-helm-charts-recommended).
By offering individual containers and charts, we will be able to provide a number of benefits:
* Easier horizontal scaling of each service,
......@@ -37,6 +37,8 @@ By offering individual containers and charts, we will be able to provide a numbe
This is a large project and will be worked on over the span of multiple releases. For the most up-to-date status and release information, please see our [tracking issue](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2420). We do not expect these to be production ready before the second half of 2018.
Learn more about the [cloud native GitLab chart.](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md)
## Other Charts
### GitLab Runner Chart
......@@ -55,7 +57,7 @@ Learn more about the [gitlab chart.](gitlab_chart.md)
### Community Contributed Charts
The community has also [contributed GitLab charts](https://github.com/kubernetes/charts/tree/master/stable/gitlab-ce) to the [Helm Stable Repository](https://github.com/kubernetes/charts#repository-structure). These charts should be considered [deprecated](https://github.com/kubernetes/charts/issues/1138) in favor of the [official Charts](#official-gitlab-helm-charts-recommended).
The community has also contributed GitLab [CE](https://github.com/kubernetes/charts/tree/master/stable/gitlab-ce) and [EE](https://github.com/kubernetes/charts/tree/master/stable/gitlab-ee) charts to the [Helm Stable Repository](https://github.com/kubernetes/charts#repository-structure). These charts should be considered [deprecated](https://github.com/kubernetes/charts/issues/1138) in favor of the [official Charts](gitlab_omnibus.md).
[chart]: https://github.com/kubernetes/charts
[helm]: https://github.com/kubernetes/helm/blob/master/README.md
......@@ -18,7 +18,7 @@ traffic until the system is ready or restart the container as needed.
To access monitoring resources, the client IP needs to be included in a whitelist.
[Read how to add IPs to a whitelist for the monitoring endpoints.][admin].
[Read how to add IPs to a whitelist for the monitoring endpoints][admin].
## Using the endpoint
......
......@@ -2,19 +2,13 @@ module EE
module SessionsController
extend ActiveSupport::Concern
prepended do
after_action :log_failed_login, only: :new, if: :failed_login?
end
private
def log_failed_login
::AuditEventService.new(request.filtered_parameters['user']['login'], nil, ip_address: request.remote_ip)
.for_failed_login.unauth_security_event
end
def failed_login?
env['warden.options'] && env['warden.options'][:action] == 'unauthenticated'
super
end
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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