Commit 93f074d0 authored by Valery Sizov's avatar Valery Sizov

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

parents d003966d 714f70a3
......@@ -4,7 +4,11 @@ entry.
## 8.15.2 (2016-12-27)
<<<<<<< HEAD
- No changes.
=======
- Fix finding the latest pipeline. !8301
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
- Fix mr list timestamp alignment. !8271
- Fix discussion overlap text in regular screens. !8273
- Fixes mini-pipeline-graph dropdown animation and stage position in chrome, firefox and safari. !8282
......
......@@ -343,7 +343,7 @@ gem 'octokit', '~> 4.3.0'
gem 'mail_room', '~> 0.9.0'
gem 'email_reply_parser', '~> 0.5.8'
gem 'email_reply_trimmer', '~> 0.1'
gem 'html2text'
gem 'ruby-prof', '~> 0.16.2'
......@@ -358,5 +358,5 @@ gem 'paranoia', '~> 2.2'
gem 'health_check', '~> 2.2.0'
# System information
gem 'vmstat', '~> 2.2'
gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6'
......@@ -167,6 +167,7 @@ GEM
railties (>= 4.2)
dropzonejs-rails (0.7.2)
rails (> 3.1)
<<<<<<< HEAD
elasticsearch (1.0.15)
elasticsearch-api (= 1.0.15)
elasticsearch-transport (= 1.0.15)
......@@ -181,6 +182,9 @@ GEM
faraday
multi_json
email_reply_parser (0.5.8)
=======
email_reply_trimmer (0.1.6)
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
email_spec (1.6.0)
launchy (~> 2.1)
mail (~> 2.2)
......@@ -800,7 +804,7 @@ GEM
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
equalizer (~> 0.0, >= 0.0.9)
vmstat (2.2.0)
vmstat (2.3.0)
warden (1.2.6)
rack (>= 1.0)
web-console (2.3.0)
......@@ -866,9 +870,13 @@ DEPENDENCIES
diffy (~> 3.1.0)
doorkeeper (~> 4.2.0)
dropzonejs-rails (~> 0.7.1)
<<<<<<< HEAD
elasticsearch-model
elasticsearch-rails
email_reply_parser (~> 0.5.8)
=======
email_reply_trimmer (~> 0.1)
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
email_spec (~> 1.6.0)
factory_girl_rails (~> 4.7.0)
ffaker (~> 2.0.0)
......@@ -1016,7 +1024,7 @@ DEPENDENCIES
validates_hostname (~> 1.0.6)
version_sorter (~> 2.1.0)
virtus (~> 1.0.1)
vmstat (~> 2.2)
vmstat (~> 2.3.0)
web-console (~> 2.0)
webmock (~> 1.21.0)
wikicloth (= 0.8.1)
......
......@@ -69,7 +69,8 @@ to add details to the issue.
- ~UX needs help from a UX designer
- ~Frontend needs help from a Front-end engineer. Please follow the
["Implement design & UI elements" guidelines].
- ~up-for-grabs is an issue suitable for first-time contributors, of reasonable difficulty and size. Not exclusive with other labels.
- ~"Accepting Merge Requests" is a low priority, well-defined issue that we
encourage people to contribute to. Not exclusive with other labels.
- ~"feature proposal" is a proposal for a new feature for GitLab. People are encouraged to vote
in support or comment for further detail. Do not use `feature request`.
- ~bug is an issue reporting undesirable or incorrect behavior.
......
......@@ -91,6 +91,14 @@
// Set the default path for all cookies to GitLab's root directory
Cookies.defaults.path = gon.relative_url_root || '/';
// `hashchange` is not triggered when link target is already in window.location
$body.on('click', 'a[href^="#"]', function() {
var href = this.getAttribute('href');
if (href.substr(1) === gl.utils.getLocationHash()) {
setTimeout(gl.utils.handleLocationHash, 1);
}
});
// prevent default action for disabled buttons
$('.btn').click(function(e) {
if ($(this).hasClass('disabled')) {
......
......@@ -71,3 +71,5 @@ class ListIssue {
return Vue.http.patch(url, data);
}
}
window.ListIssue = ListIssue;
......@@ -10,3 +10,5 @@ class ListLabel {
this.priority = (obj.priority !== null) ? obj.priority : Infinity;
}
}
window.ListLabel = ListLabel;
......@@ -148,3 +148,5 @@ class List {
});
}
}
window.List = List;
......@@ -6,3 +6,5 @@ class ListMilestone {
this.title = obj.title;
}
}
window.ListMilestone = ListMilestone;
......@@ -8,3 +8,5 @@ class ListUser {
this.avatar = user.avatar_url;
}
}
window.ListUser = ListUser;
......@@ -78,4 +78,6 @@ class BoardService {
issue
});
}
};
}
window.BoardService = BoardService;
......@@ -59,9 +59,11 @@
},
methods: {
updateTooltip: function () {
$(this.$refs.button)
.tooltip('hide')
.tooltip('fixTitle');
this.$nextTick(() => {
$(this.$refs.button)
.tooltip('hide')
.tooltip('fixTitle');
});
},
resolve: function () {
if (!this.canResolve) return;
......@@ -90,7 +92,7 @@
new Flash('An error occurred when trying to resolve a comment. Please try again.', 'alert');
}
this.$nextTick(this.updateTooltip);
this.updateTooltip();
});
}
},
......
......@@ -92,3 +92,5 @@ class DiscussionModel {
return false;
}
}
window.DiscussionModel = DiscussionModel;
......@@ -9,3 +9,5 @@ class NoteModel {
this.resolved_by = resolved_by;
}
}
window.NoteModel = NoteModel;
......@@ -20,3 +20,5 @@ class EnvironmentsService {
return this.environments.get();
}
}
window.EnvironmentsService = EnvironmentsService;
......@@ -77,7 +77,7 @@
var _a, _y, regexp, match, atSymbolsWithBar, atSymbolsWithoutBar;
atSymbolsWithBar = Object.keys(this.app.controllers).join('|');
atSymbolsWithoutBar = Object.keys(this.app.controllers).join('');
subtext = subtext.split(' ').pop();
subtext = subtext.split(/\s+/g).pop();
flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
_a = decodeURI("%C3%80");
......
/* eslint-disable func-names, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, wrap-iife, no-else-return, consistent-return, object-shorthand, comma-dangle, no-param-reassign, padded-blocks, camelcase, prefer-arrow-callback, max-len */
/* eslint-disable func-names, no-var, object-shorthand, comma-dangle, prefer-arrow-callback */
// MarkdownPreview
//
// Handles toggling the "Write" and "Preview" tab clicks, rendering the preview,
// and showing a warning when more than `x` users are referenced.
//
(function() {
var lastTextareaPreviewed, markdownPreview, previewButtonSelector, writeButtonSelector;
(function () {
var lastTextareaPreviewed;
var markdownPreview;
var previewButtonSelector;
var writeButtonSelector;
window.MarkdownPreview = (function() {
window.MarkdownPreview = (function () {
function MarkdownPreview() {}
// Minimum number of users referenced before triggering a warning
......@@ -16,73 +19,71 @@
MarkdownPreview.prototype.ajaxCache = {};
MarkdownPreview.prototype.showPreview = function(form) {
var mdText, preview;
preview = form.find('.js-md-preview');
mdText = form.find('textarea.markdown-area').val();
MarkdownPreview.prototype.showPreview = function ($form) {
var mdText;
var preview = $form.find('.js-md-preview');
if (preview.hasClass('md-preview-loading')) {
return;
}
mdText = $form.find('textarea.markdown-area').val();
if (mdText.trim().length === 0) {
preview.text('Nothing to preview.');
return this.hideReferencedUsers(form);
this.hideReferencedUsers($form);
} else {
preview.text('Loading...');
return this.renderMarkdown(mdText, (function(_this) {
return function(response) {
preview.html(response.body);
preview.renderGFM();
return _this.renderReferencedUsers(response.references.users, form);
};
})(this));
preview.addClass('md-preview-loading').text('Loading...');
this.fetchMarkdownPreview(mdText, (function (response) {
preview.removeClass('md-preview-loading').html(response.body);
preview.renderGFM();
this.renderReferencedUsers(response.references.users, $form);
}).bind(this));
}
};
MarkdownPreview.prototype.renderMarkdown = function(text, success) {
MarkdownPreview.prototype.fetchMarkdownPreview = function (text, success) {
if (!window.preview_markdown_path) {
return;
}
if (text === this.ajaxCache.text) {
return success(this.ajaxCache.response);
success(this.ajaxCache.response);
return;
}
return $.ajax({
$.ajax({
type: 'POST',
url: window.preview_markdown_path,
data: {
text: text
},
dataType: 'json',
success: (function(_this) {
return function(response) {
_this.ajaxCache = {
text: text,
response: response
};
return success(response);
success: (function (response) {
this.ajaxCache = {
text: text,
response: response
};
})(this)
success(response);
}).bind(this)
});
};
MarkdownPreview.prototype.hideReferencedUsers = function(form) {
var referencedUsers;
referencedUsers = form.find('.referenced-users');
return referencedUsers.hide();
MarkdownPreview.prototype.hideReferencedUsers = function ($form) {
$form.find('.referenced-users').hide();
};
MarkdownPreview.prototype.renderReferencedUsers = function(users, form) {
MarkdownPreview.prototype.renderReferencedUsers = function (users, $form) {
var referencedUsers;
referencedUsers = form.find('.referenced-users');
referencedUsers = $form.find('.referenced-users');
if (referencedUsers.length) {
if (users.length >= this.referenceThreshold) {
referencedUsers.show();
return referencedUsers.find('.js-referenced-users-count').text(users.length);
referencedUsers.find('.js-referenced-users-count').text(users.length);
} else {
return referencedUsers.hide();
referencedUsers.hide();
}
}
};
return MarkdownPreview;
})();
}());
markdownPreview = new window.MarkdownPreview();
......@@ -92,19 +93,14 @@
lastTextareaPreviewed = null;
$.fn.setupMarkdownPreview = function() {
var $form, form_textarea;
$form = $(this);
form_textarea = $form.find('textarea.markdown-area');
form_textarea.on('input', function() {
return markdownPreview.hideReferencedUsers($form);
});
return form_textarea.on('blur', function() {
return markdownPreview.showPreview($form);
$.fn.setupMarkdownPreview = function () {
var $form = $(this);
$form.find('textarea.markdown-area').on('input', function () {
markdownPreview.hideReferencedUsers($form);
});
};
$(document).on('markdown-preview:show', function(e, $form) {
$(document).on('markdown-preview:show', function (e, $form) {
if (!$form) {
return;
}
......@@ -115,10 +111,10 @@
// toggle content
$form.find('.md-write-holder').hide();
$form.find('.md-preview-holder').show();
return markdownPreview.showPreview($form);
markdownPreview.showPreview($form);
});
$(document).on('markdown-preview:hide', function(e, $form) {
$(document).on('markdown-preview:hide', function (e, $form) {
if (!$form) {
return;
}
......@@ -129,34 +125,33 @@
// toggle content
$form.find('.md-write-holder').show();
$form.find('textarea.markdown-area').focus();
return $form.find('.md-preview-holder').hide();
$form.find('.md-preview-holder').hide();
});
$(document).on('markdown-preview:toggle', function(e, keyboardEvent) {
$(document).on('markdown-preview:toggle', function (e, keyboardEvent) {
var $target;
$target = $(keyboardEvent.target);
if ($target.is('textarea.markdown-area')) {
$(document).triggerHandler('markdown-preview:show', [$target.closest('form')]);
return keyboardEvent.preventDefault();
keyboardEvent.preventDefault();
} else if (lastTextareaPreviewed) {
$target = lastTextareaPreviewed;
$(document).triggerHandler('markdown-preview:hide', [$target.closest('form')]);
return keyboardEvent.preventDefault();
keyboardEvent.preventDefault();
}
});
$(document).on('click', previewButtonSelector, function(e) {
$(document).on('click', previewButtonSelector, function (e) {
var $form;
e.preventDefault();
$form = $(this).closest('form');
return $(document).triggerHandler('markdown-preview:show', [$form]);
$(document).triggerHandler('markdown-preview:show', [$form]);
});
$(document).on('click', writeButtonSelector, function(e) {
$(document).on('click', writeButtonSelector, function (e) {
var $form;
e.preventDefault();
$form = $(this).closest('form');
return $(document).triggerHandler('markdown-preview:hide', [$form]);
$(document).triggerHandler('markdown-preview:hide', [$form]);
});
}).call(this);
}());
......@@ -15,6 +15,7 @@
},
data: function(term, callback) {
var finalCallback, projectsCallback;
var orderBy = $dropdown.data('order-by');
finalCallback = function(projects) {
return callback(projects);
};
......@@ -34,7 +35,7 @@
if (this.groupId) {
return Api.groupProjects(this.groupId, term, projectsCallback);
} else {
return Api.projects(term, this.orderBy, projectsCallback);
return Api.projects(term, orderBy, projectsCallback);
}
},
url: function(project) {
......
......@@ -78,6 +78,12 @@
this.$dropdownFooter.toggleClass('hidden', !branchName);
}
}
<<<<<<< HEAD
global.gl.ProtectedBranchDropdown = ProtectedBranchDropdown;
})(window);
=======
}
window.ProtectedBranchDropdown = ProtectedBranchDropdown;
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
......@@ -57,16 +57,33 @@ pre {
border-radius: 0;
color: $well-pre-color;
}
&.wrap {
word-break: break-word;
white-space: pre-wrap;
}
}
hr {
margin: $gl-padding 0;
border-top: 1px solid darken($gray-normal, 8%);
}
.str-truncated {
@include str-truncated;
}
.block-truncated {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
> div,
.str-truncated {
display: inline;
}
}
.item-title { font-weight: 600; }
/** FLASH message **/
......
......@@ -87,25 +87,25 @@
}
}
$theme-charcoal: #3d454d;
$theme-charcoal-light: #485157;
$theme-charcoal-dark: #383f45;
$theme-charcoal-text: #b9bbbe;
$theme-charcoal-light: #b9bbbe;
$theme-charcoal: #485157;
$theme-charcoal-dark: #3d454d;
$theme-charcoal-darker: #383f45;
$theme-blue-light: #becde9;
$theme-blue: #2980b9;
$theme-blue-dark: #1970a9;
$theme-blue-darker: #096099;
$theme-graphite-lighter: #ccc;
$theme-graphite-light: #777;
$theme-graphite: #666;
$theme-graphite-dark: #555;
$theme-graphite-light: #ccc;
$theme-graphite: #777;
$theme-graphite-dark: #666;
$theme-graphite-darker: #555;
$theme-gray-light: #979797;
$theme-gray: #373737;
$theme-gray-dark: #272727;
$theme-gray-darker: #222;
$theme-black-light: #979797;
$theme-black: #373737;
$theme-black-dark: #272727;
$theme-black-darker: #222;
$theme-green-light: #adc;
$theme-green: #019875;
......@@ -123,15 +123,15 @@ body {
}
&.ui_charcoal {
@include gitlab-theme($theme-charcoal-text, $theme-charcoal-light, $theme-charcoal, $theme-charcoal-dark);
@include gitlab-theme($theme-charcoal-light, $theme-charcoal, $theme-charcoal-dark, $theme-charcoal-darker);
}
&.ui_graphite {
@include gitlab-theme($theme-graphite-lighter, $theme-graphite-light, $theme-graphite, $theme-graphite-dark);
@include gitlab-theme($theme-graphite-light, $theme-graphite, $theme-graphite-dark, $theme-graphite-darker);
}
&.ui_gray {
@include gitlab-theme($theme-gray-light, $theme-gray, $theme-gray-dark, $theme-gray-darker);
&.ui_black {
@include gitlab-theme($theme-black-light, $theme-black, $theme-black-dark, $theme-black-darker);
}
&.ui_green {
......
......@@ -205,6 +205,7 @@ ul.content-list {
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
align-items: center;
white-space: nowrap;
}
......@@ -214,6 +215,11 @@ ul.content-list {
padding-right: 8px;
}
.row-fixed-content {
flex: 0 0 auto;
margin-left: auto;
}
.row-title {
font-weight: 600;
}
......
......@@ -116,8 +116,8 @@
padding-top: 16px;
padding-bottom: 11px;
display: inline-block;
width: 50%;
line-height: 28px;
white-space: normal;
/* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-xs-max) {
......@@ -158,30 +158,24 @@
}
.nav-controls {
width: 50%;
display: inline-block;
float: right;
text-align: right;
padding: 11px 0;
margin-bottom: 0;
> .dropdown {
margin-right: $gl-padding-top;
display: inline-block;
vertical-align: top;
&:last-child {
margin-right: 0;
}
}
> .btn {
> .btn,
> .btn-container,
> .dropdown,
> input,
> form {
margin-right: $gl-padding-top;
display: inline-block;
vertical-align: top;
&:last-child {
margin-right: 0;
float: right;
}
}
......@@ -189,19 +183,21 @@
float: none;
}
> form {
display: inline-block;
}
.icon-label {
display: none;
}
input {
.btn,
.dropdown,
.dropdown-toggle,
input,
form {
height: 35px;
}
input {
display: inline-block;
position: relative;
margin-right: $gl-padding-top;
/* Medium devices (desktops, 992px and up) */
@media (min-width: $screen-md-min) { width: 200px; }
......@@ -225,6 +221,7 @@
.btn,
form,
.dropdown,
.dropdown-toggle,
.dropdown-menu-toggle,
.form-control {
margin: 0 0 10px;
......@@ -263,6 +260,10 @@
.nav-text,
.nav-controls {
width: auto;
@media (max-width: $screen-xs-max) {
width: 100%;
}
}
}
......@@ -275,6 +276,10 @@
padding: 17px 0;
}
}
pre {
width: 100%;
}
}
.layout-nav {
......@@ -427,4 +432,41 @@
border-bottom: none;
}
}
}
\ No newline at end of file
}
@media (max-width: $screen-xs-max) {
.top-area {
flex-flow: row wrap;
.nav-controls {
$controls-margin: $btn-xs-side-margin - 2px;
flex: 0 0 100%;
&.controls-flex {
display: flex;
flex-flow: row wrap;
align-items: center;
justify-content: center;
padding: 0 0 $gl-padding-top;
}
.controls-item,
.controls-item-full,
.controls-item:last-child {
flex: 1 1 35%;
display: block;
width: 100%;
margin: $controls-margin;
.btn,
.dropdown {
margin: 0;
}
}
.controls-item-full {
flex: 1 1 100%;
}
}
}
}
......@@ -91,7 +91,7 @@
// Labels
.label {
padding: 4px 5px;
font-size: 13px;
font-size: 12px;
font-style: normal;
font-weight: normal;
display: inline-block;
......
......@@ -496,9 +496,9 @@ $project-network-controls-color: #888;
*/
$runner-state-shared-bg: #32b186;
$runner-state-specific-bg: #3498db;
$runner-status-online-color: green;
$runner-status-offline-color: gray;
$runner-status-paused-color: red;
$runner-status-online-color: $green-normal;
$runner-status-offline-color: $gray-darkest;
$runner-status-paused-color: $red-normal;
/*
Stat Graph
......
......@@ -108,6 +108,12 @@
&.has-border {
border-top: 3px solid;
margin-top: -1px;
margin-right: -1px;
margin-left: -1px;
padding-top: 1px;
padding-right: 1px;
padding-left: 1px;
.board-title {
padding-top: ($gl-padding - 3px);
......
.deploy-keys-list {
width: 100%;
overflow: auto;
table {
border: 1px solid $table-border-color;
}
}
.deploy-keys-title {
padding-bottom: 2px;
line-height: 2;
}
// Limit MR description for side-by-side diff view
.limit-container-width {
.detail-page-header {
max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2));
margin-left: auto;
margin-right: auto;
}
.issuable-details {
.detail-page-description,
.mr-source-target,
.mr-state-widget,
.merge-manually {
max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2));
margin-left: auto;
margin-right: auto;
}
.merge-request-tabs-holder {
&.affix {
border-bottom: 1px solid $border-color;
.nav-links {
border: 0;
}
}
.container-fluid {
padding-left: 0;
padding-right: 0;
max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2));
margin-left: auto;
margin-right: auto;
}
}
}
.diffs {
.mr-version-controls,
.files-changed {
max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2));
margin-left: auto;
margin-right: auto;
}
}
}
.issuable-details {
section {
.issuable-discussion {
......
......@@ -98,7 +98,7 @@
}
.label {
padding: 8px 9px 9px $gl-padding;
padding: 8px 9px 9px;
font-size: 14px;
}
}
......
......@@ -105,19 +105,19 @@
li {
flex: 1;
text-align: center;
border-left: 1px solid $border-color;
&:first-of-type {
border-left: none;
border-top-left-radius: $border-radius-default;
}
&:last-of-type {
border-left: 1px solid $border-color;
border-top-right-radius: $border-radius-default;
}
&:not(.active) {
background-color: $gray-light;
border-left: 1px solid $border-color;
}
a {
......
......@@ -424,6 +424,10 @@
.merge-request-tabs-holder {
background-color: $white-light;
.container-limited {
max-width: $limited-layout-width;
}
&.affix {
top: 100px;
left: 0;
......
......@@ -557,18 +557,14 @@ ul.notes {
&.is-active {
color: $gl-text-green;
svg path {
svg {
fill: $gl-text-green;
}
}
svg {
position: relative;
color: $gray-darkest;
path {
fill: $gray-darkest;
}
fill: $gray-darkest;
}
}
......
......@@ -22,8 +22,8 @@
background: $theme-graphite;
}
&.ui_gray {
background: $theme-gray;
&.ui_black {
background: $theme-black;
}
&.ui_green {
......
......@@ -10,7 +10,7 @@ class Admin::DeployKeysController < Admin::ApplicationController
end
def create
@deploy_key = deploy_keys.new(deploy_key_params)
@deploy_key = deploy_keys.new(deploy_key_params.merge(user: current_user))
if @deploy_key.save
redirect_to admin_deploy_keys_path
......@@ -39,6 +39,6 @@ class Admin::DeployKeysController < Admin::ApplicationController
end
def deploy_key_params
params.require(:deploy_key).permit(:key, :title)
params.require(:deploy_key).permit(:key, :title, :can_push)
end
end
......@@ -9,7 +9,7 @@ class Admin::GroupsController < Admin::ApplicationController
end
def show
@group = Group.with_statistics.find_by_full_path(params[:id])
@group = Group.with_statistics.joins(:route).group('routes.path').find_by_full_path(params[:id])
@members = @group.members.order("access_level DESC").page(params[:members_page])
@requesters = AccessRequestsFinder.new(@group).execute(current_user)
@projects = @group.projects.with_statistics.page(params[:projects_page])
......
......@@ -4,6 +4,9 @@ class Dashboard::TodosController < Dashboard::ApplicationController
def index
@sort = params[:sort]
@todos = @todos.page(params[:page])
if @todos.out_of_range? && @todos.total_pages != 0
redirect_to url_for(params.merge(page: @todos.total_pages))
end
end
def destroy
......
......@@ -16,7 +16,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
end
def create
@key = DeployKey.new(deploy_key_params)
@key = DeployKey.new(deploy_key_params.merge(user: current_user))
set_index_vars
if @key.valid? && @project.deploy_keys << @key
......@@ -59,7 +59,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
end
def deploy_key_params
params.require(:deploy_key).permit(:key, :title)
params.require(:deploy_key).permit(:key, :title, :can_push)
end
def log_audit_event(key_title, options = {})
......
......@@ -25,6 +25,9 @@ class Projects::IssuesController < Projects::ApplicationController
def index
@issues = issues_collection
@issues = @issues.page(params[:page])
if @issues.out_of_range? && @issues.total_pages != 0
return redirect_to url_for(params.merge(page: @issues.total_pages))
end
if params[:label_name].present?
@labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute
......
......@@ -40,6 +40,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def index
@merge_requests = merge_requests_collection
@merge_requests = @merge_requests.page(params[:page])
if @merge_requests.out_of_range? && @merge_requests.total_pages != 0
return redirect_to url_for(params.merge(page: @merge_requests.total_pages))
end
if params[:label_name].present?
labels_params = { project_id: @project.id, title: params[:label_name] }
......
......@@ -26,6 +26,9 @@ class Projects::SnippetsController < Projects::ApplicationController
scope: params[:scope]
)
@snippets = @snippets.page(params[:page])
if @snippets.out_of_range? && @snippets.total_pages != 0
redirect_to namespace_project_snippets_path(page: @snippets.total_pages)
end
end
def new
......
......@@ -8,7 +8,7 @@ class Projects::TagsController < Projects::ApplicationController
before_action :authorize_admin_project!, only: [:destroy]
def index
params[:sort] = params[:sort].presence || 'name'
params[:sort] = params[:sort].presence || sort_value_recently_updated
@sort = params[:sort]
@tags = TagsFinder.new(@repository, params).execute
......
......@@ -61,7 +61,7 @@ module ProjectsHelper
project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" }
if current_user
project_link << button_tag(type: 'button', class: "dropdown-toggle-caret js-projects-dropdown-toggle", aria: { label: "Toggle switch project dropdown" }, data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" }) do
project_link << button_tag(type: 'button', class: 'dropdown-toggle-caret js-projects-dropdown-toggle', aria: { label: 'Toggle switch project dropdown' }, data: { target: '.js-dropdown-menu-projects', toggle: 'dropdown', order_by: 'last_activity_at' }) do
icon("chevron-down")
end
end
......@@ -90,10 +90,12 @@ module ProjectsHelper
end
def project_for_deploy_key(deploy_key)
if deploy_key.projects.include?(@project)
if deploy_key.has_access_to?(@project)
@project
else
deploy_key.projects.find { |project| can?(current_user, :read_project, project) }
deploy_key.projects.find do |project|
can?(current_user, :read_project, project)
end
end
end
......
......@@ -20,4 +20,18 @@ class DeployKey < Key
def destroyed_when_orphaned?
self.private?
end
def has_access_to?(project)
projects.include?(project)
end
def can_push_to?(project)
can_push? && has_access_to?(project)
end
private
# we don't want to notify the user for deploy keys
def notify_user
end
end
......@@ -178,15 +178,17 @@ class Group < Namespace
end
def has_owner?(user)
owners.include?(user)
members_with_parents.owners.where(user_id: user).any?
end
def has_master?(user)
members.masters.where(user_id: user).any?
members_with_parents.masters.where(user_id: user).any?
end
# Check if user is a last owner of the group.
# Parent owners are ignored for nested groups.
def last_owner?(user)
has_owner?(user) && owners.size == 1
owners.include?(user) && owners.size == 1
end
def avatar_type
......@@ -239,6 +241,14 @@ class Group < Namespace
end
def refresh_members_authorized_projects
UserProjectAccessChangedService.new(users.pluck(:id)).execute
UserProjectAccessChangedService.new(users_with_parents.pluck(:id)).execute
end
def members_with_parents
GroupMember.where(requested_at: nil, source_id: parents.map(&:id).push(id))
end
def users_with_parents
User.where(id: members_with_parents.select(:user_id))
end
end
......@@ -59,10 +59,6 @@ class Key < ActiveRecord::Base
)
end
def notify_user
run_after_commit { NotificationService.new.new_key(self) }
end
def post_create_hook
SystemHooksService.new.execute_hooks_for(self, :create)
end
......@@ -88,4 +84,8 @@ class Key < ActiveRecord::Base
self.fingerprint = Gitlab::KeyFingerprint.new(self.key).fingerprint
end
def notify_user
run_after_commit { NotificationService.new.new_key(self) }
end
end
......@@ -599,11 +599,7 @@ class MergeRequest < ActiveRecord::Base
ext = Gitlab::ReferenceExtractor.new(project, current_user)
ext.analyze(description)
issues = ext.issues
closing_issues = Gitlab::ClosingIssueExtractor.new(project, current_user).
closed_by_message(description)
issues - closing_issues
ext.issues - closes_issues
end
def target_project_path
......
......@@ -1036,7 +1036,7 @@ class Project < ActiveRecord::Base
Rails.logger.error "Project #{old_path_with_namespace} cannot be renamed because container registry tags are present"
# we currently doesn't support renaming repository if it contains tags in container registry
raise Exception.new('Project cannot be renamed, because tags are present in its container registry')
raise StandardError.new('Project cannot be renamed, because tags are present in its container registry')
end
if gitlab_shell.mv_repository(repository_storage_path, old_path_with_namespace, new_path_with_namespace)
......@@ -1063,7 +1063,7 @@ class Project < ActiveRecord::Base
# if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs
raise Exception.new('repository cannot be renamed')
raise StandardError.new('repository cannot be renamed')
end
Gitlab::AppLogger.info "Project was renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}"
......
......@@ -4,7 +4,7 @@ class GroupPolicy < BasePolicy
return unless @user
globally_viewable = @subject.public? || (@subject.internal? && !@user.external?)
member = @subject.users.include?(@user)
member = @subject.users_with_parents.include?(@user)
owner = @user.admin? || @subject.has_owner?(@user)
master = owner || @subject.has_master?(@user)
......
......@@ -261,7 +261,7 @@ class ProjectPolicy < BasePolicy
def project_group_member?(user)
project.group &&
(
project.group.members.exists?(user_id: user.id) ||
project.group.members_with_parents.exists?(user_id: user.id) ||
project.group.requesters.exists?(user_id: user.id)
)
end
......
......@@ -36,6 +36,7 @@ class IssuableBaseService < BaseService
end
end
<<<<<<< HEAD
def create_time_estimate_note(issuable)
SystemNoteService.change_time_estimate(issuable, issuable.project, current_user)
end
......@@ -47,6 +48,11 @@ class IssuableBaseService < BaseService
def filter_params(issuable)
ability_name = :"admin_#{issuable.to_ability_name}"
=======
def filter_params(issuable)
ability_name = :"admin_#{issuable.to_ability_name}"
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
unless can?(current_user, ability_name, project)
params.delete(:milestone_id)
params.delete(:labels)
......@@ -164,7 +170,10 @@ class IssuableBaseService < BaseService
def create(issuable)
merge_slash_commands_into_params!(issuable)
filter_params(issuable)
<<<<<<< HEAD
change_time_spent(issuable)
=======
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
params.delete(:state_event)
params[:author] ||= current_user
......@@ -206,7 +215,10 @@ class IssuableBaseService < BaseService
change_state(issuable)
change_subscription(issuable)
change_todo(issuable)
<<<<<<< HEAD
time_spent = change_time_spent(issuable)
=======
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
filter_params(issuable)
old_labels = issuable.labels.to_a
old_mentioned_users = issuable.mentioned_users.to_a
......
......@@ -74,7 +74,7 @@ module Users
# remove - The IDs of the authorization rows to remove.
# add - Rows to insert in the form `[user id, project id, access level]`
def update_authorizations(remove = [], add = [])
return if remove.empty? && add.empty?
return if remove.empty? && add.empty? && user.authorized_projects_populated
User.transaction do
user.remove_project_authorizations(remove) unless remove.empty?
......
- page_title "Deploy Keys"
.panel.panel-default.prepend-top-default
.panel-heading
Public deploy keys (#{@deploy_keys.count})
.controls
= link_to 'New Deploy Key', new_admin_deploy_key_path, class: "btn btn-new btn-sm"
- if @deploy_keys.any?
.table-holder
%table.table
%thead.panel-heading
%h3.page-title.deploy-keys-title
Public deploy keys (#{@deploy_keys.count})
.pull-right
= link_to 'New Deploy Key', new_admin_deploy_key_path, class: 'btn btn-new btn-sm btn-inverted'
- if @deploy_keys.any?
.table-holder.deploy-keys-list
%table.table
%thead
%tr
%th.col-sm-2 Title
%th.col-sm-4 Fingerprint
%th.col-sm-2 Write access allowed
%th.col-sm-2 Added at
%th.col-sm-2
%tbody
- @deploy_keys.each do |deploy_key|
%tr
%th Title
%th Fingerprint
%th Added at
%th
%tbody
- @deploy_keys.each do |deploy_key|
%tr
%td
%strong= deploy_key.title
%td
%code.key-fingerprint= deploy_key.fingerprint
%td
%span.cgray
added #{time_ago_with_tooltip(deploy_key.created_at)}
%td
= link_to 'Remove', admin_deploy_key_path(deploy_key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-sm btn-remove delete-key pull-right"
%td
%strong= deploy_key.title
%td
%code.key-fingerprint= deploy_key.fingerprint
%td
- if deploy_key.can_push?
Yes
- else
No
%td
%span.cgray
added #{time_ago_with_tooltip(deploy_key.created_at)}
%td
= link_to 'Remove', admin_deploy_key_path(deploy_key), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove delete-key pull-right'
......@@ -16,6 +16,14 @@
Paste a machine public key here. Read more about how to generate it
= link_to "here", help_page_path("ssh/README")
= f.text_area :key, class: "form-control thin_area", rows: 5
.form-group
.control-label
.col-sm-10
= f.label :can_push do
= f.check_box :can_push
%strong Write access allowed
%p.light.append-bottom-0
Allow this key to push to repository as well? (Default only allows pull access.)
.form-actions
= f.submit 'Create', class: "btn-create btn"
......
......@@ -36,6 +36,12 @@
%span
Snippets
<<<<<<< HEAD
= link_to help_path, title: 'About GitLab EE', class: 'about-gitlab' do
%span
About GitLab EE
=======
= link_to help_path, title: 'About GitLab CE', class: 'about-gitlab' do
%span
About GitLab CE
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
......@@ -3,7 +3,7 @@
= render "projects/commits/head"
%div{ class: container_class }
.top-area
.top-area.adjust
.nav-text
Protected branches can be managed in project settings
......@@ -12,7 +12,7 @@
= search_field_tag :search, params[:search], { placeholder: 'Filter by branch name', id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false }
.dropdown.inline
%button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'}
%button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'}
%span.light
= projects_sort_options_hash[@sort]
= icon('chevron-down')
......
......@@ -6,6 +6,9 @@
= deploy_key.title
.description
= deploy_key.fingerprint
- if deploy_key.can_push?
.write-access-allowed
Write access allowed
.deploy-key-content.prepend-left-default.deploy-key-projects
- deploy_key.projects.each do |project|
- if can?(current_user, :read_project, project)
......
......@@ -10,4 +10,13 @@
%p.light.append-bottom-0
Paste a machine public key here. Read more about how to generate it
= link_to "here", help_page_path("ssh/README")
.form-group
.checkbox
= f.label :can_push do
= f.check_box :can_push
%strong Write access allowed
.form-group
%p.light.append-bottom-0
Allow this key to push to repository as well? (Default only allows pull access.)
= f.submit "Add key", class: "btn-create btn"
......@@ -10,7 +10,7 @@
%span.pull-right
= link_to 'Change branches', mr_change_branches_path(@merge_request)
%hr
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input' } do |f|
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input js-quick-submit' } do |f|
= render 'shared/issuable/form', f: f, issuable: @merge_request
= f.hidden_field :source_project_id
= f.hidden_field :source_branch
......
......@@ -50,7 +50,12 @@
- if mr_issues_mentioned_but_not_closing.present?
#{"Issue".pluralize(mr_issues_mentioned_but_not_closing.size)}
!= markdown issues_sentence(mr_issues_mentioned_but_not_closing), pipeline: :gfm, author: @merge_request.author
<<<<<<< HEAD
#{mr_issues_mentioned_but_not_closing.size > 1 ? 'are' : 'is'} mentioned but will not closed.
=======
#{mr_issues_mentioned_but_not_closing.size > 1 ? 'are' : 'is'} mentioned but will not be closed.
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
- if @merge_request.approvals.any?
.mr-widget-footer.approved-by-users
......
- can_resolve = @merge_request.conflicts_can_be_resolved_by?(current_user)
- can_resolve_in_ui = @merge_request.conflicts_can_be_resolved_in_ui?
- can_merge = @merge_request.can_be_merged_via_command_line_by?(current_user)
%h4.has-conflicts
= icon("exclamation-triangle")
This merge request contains merge conflicts
%p
Please
- if @merge_request.conflicts_can_be_resolved_by?(current_user)
- if @merge_request.conflicts_can_be_resolved_in_ui?
= link_to "resolve these conflicts", conflicts_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
- else
%span.has-tooltip{title: "These conflicts cannot be resolved through GitLab"}
resolve these conflicts locally
- else
resolve these conflicts
To merge this request, resolve these conflicts
- if can_resolve && !can_resolve_in_ui
locally
or
- unless can_merge
ask someone with write access to this repository to
merge it locally.
- if @merge_request.can_be_merged_via_command_line_by?(current_user)
#{link_to "merge this request manually", "#modal_merge_info", class: "how_to_merge_link vlink", "data-toggle" => "modal"}.
- else
ask someone with write access to this repository to merge this request manually.
- if (can_resolve && can_resolve_in_ui) || can_merge
.btn-group
- if can_resolve && can_resolve_in_ui
= link_to "Resolve conflicts", conflicts_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "btn"
- if can_merge
= link_to "Merge locally", "#modal_merge_info", class: "btn how_to_merge_link vlink", "data-toggle" => "modal"
- commit = @repository.commit(tag.dereferenced_target)
- release = @releases.find { |release| release.tag == tag.name }
%li
%div
%li.flex-row
.row-main-content.str-truncated
= link_to namespace_project_tag_path(@project.namespace, @project, tag.name) do
%span.item-title
= icon('tag')
......@@ -10,24 +10,25 @@
&nbsp;
= strip_gpg_signature(tag.message)
.controls
= render 'projects/buttons/download', project: @project, ref: tag.name
- if commit
.block-truncated
= render 'projects/branches/commit', commit: commit, project: @project
- else
%p
Cant find HEAD commit for this tag
- if release && release.description.present?
.description.prepend-top-default
.wiki
= preserve do
= markdown_field(release, :description)
- if can?(current_user, :push_code, @project)
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn has-tooltip', title: "Edit release notes", data: { container: "body" } do
= icon("pencil")
.row-fixed-content.controls
= render 'projects/buttons/download', project: @project, ref: tag.name
- if can?(current_user, :admin_project, @project)
= link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
- if can?(current_user, :push_code, @project)
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn has-tooltip', title: "Edit release notes", data: { container: "body" } do
= icon("pencil")
- if commit
= render 'projects/branches/commit', commit: commit, project: @project
- else
%p
Cant find HEAD commit for this tag
- if release && release.description.present?
.description.prepend-top-default
.wiki
= preserve do
= markdown_field(release, :description)
- if can?(current_user, :admin_project, @project)
= link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
......@@ -2,16 +2,16 @@
- page_title "Tags"
= render "projects/commits/head"
%div{ class: container_class }
.top-area
.nav-text
%div.flex-list{ class: container_class }
.top-area.flex-row
.nav-text.row-main-content
Tags give the ability to mark specific points in history as being important
.nav-controls
.nav-controls.row-fixed-content
= form_tag(filter_tags_path, method: :get) do
= search_field_tag :search, params[:search], { placeholder: 'Filter by tag name', id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false }
.dropdown.inline
.dropdown
%button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown'} }
%span.light
= projects_sort_options_hash[@sort]
......@@ -32,7 +32,7 @@
.tags
- if @tags.any?
%ul.content-list
%ul.flex-list.content-list
= render partial: 'tag', collection: @tags
= paginate @tags, theme: 'gitlab'
......
......@@ -12,21 +12,23 @@
- else
Cant find HEAD commit for this tag
.nav-controls
.nav-controls.controls-flex
- if can?(current_user, :push_code, @project)
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Edit release notes' do
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Edit release notes' do
= icon("pencil")
= link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse files' do
= link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse files' do
= icon('files-o')
= link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse commits' do
= link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse commits' do
= icon('history')
= render 'projects/buttons/download', project: @project, ref: @tag.name
.btn-container.controls-item
= render 'projects/buttons/download', project: @project, ref: @tag.name
- if can?(current_user, :admin_project, @project)
.pull-right
.btn-container.controls-item-full
= link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
%i.fa.fa-trash-o
- if @tag.message.present?
%pre.body
%pre.wrap
= strip_gpg_signature(@tag.message)
.append-bottom-default.prepend-top-default
......
---
title: Go to a project order
merge_request: 7737
author: Jacopo Beschi @jacopo-beschi
---
title: Prevent empty pagination when list is not empty
merge_request: 8172
author:
---
title: Improve visibility of "Resolve conflicts" and "Merge locally" actions
merge_request: 8229
author:
---
title: ensure permalinks scroll to correct position on multiple clicks
merge_request: 8046
author:
---
title: fix border in login session tabs
merge_request: 8346
author:
---
title: Fixed GFM autocomplete error when no data exists
title: Change status colors of runners to better defaults
merge_request:
author:
---
title: Fix mr list timestamp alignment
merge_request: 8271
author:
---
title: Fix discussion overlap text in regular screens
merge_request: 8273
author:
---
title: Fix timeout when MR contains large files marked as binary by .gitattributes
merge_request:
author:
---
title: Fixes mini-pipeline-graph dropdown animation and stage position in chrome, firefox and safari
merge_request: 8282
author:
---
title: Fix line breaking in nodes of the pipeline graph in firefox
merge_request: 8292
author:
---
title: Fixes confendential warning text alignment
merge_request: 8293
author:
---
title: Hide Scroll Top button for failed build page
merge_request: 8295
author:
---
title: Cache project authorizations even when user has access to zero projects
merge_request: 8327
author:
---
title: Make CTRL+Enter submits a new merge request
merge_request: 8360
author: Saad Shahd
---
title: Fixes issue boards list colored top border visual glitch
merge_request: 7898
author: Pier Paolo Ramon
---
title: Disable PostgreSQL statement timeouts when removing unneeded services
merge_request: 8322
author:
---
title: Fix 500 error when visit group from admin area if group name contains dot
merge_request:
author:
---
title: Rename users with namespace ending with .git
merge_request: 8309
author:
---
title: Rename projects wth reserved names
merge_request: 8234
author:
---
title: Allow to add deploy keys with write-access
merge_request: 5807
author: Ali Ibrahim
---
title: Fix a Grape deprecation, use `#request_method` instead of `#route_method`
merge_request:
author:
---
title: Fix finding the latest pipeline
merge_request: 8301
author:
---
title: Darkened hr border color in descriptions because of update of bootstrap
merge_request: 8333
author:
---
title: Fix a minor grammar error in merge request widget
merge_request: 8337
author:
---
title: Rename "autodeploy" to "auto deploy"
title: Fixed GFM dropdown not showing on new lines
merge_request:
author:
---
title: change 'gray' color theme name to 'black' to match the actual color
merge_request: 7908
author: BM5k
---
title: Fix unclear closing issue behaviour on Merge Request show page
merge_request: 8345
author: Gabriel Gizotti
---
title: About GitLab link in sidebar that links to help page
merge_request: 8316
author:
......@@ -36,7 +36,7 @@ namespace :admin do
scope(path: 'groups/*id',
controller: :groups,
constraints: { id: Gitlab::Regex.namespace_route_regex }) do
constraints: { id: Gitlab::Regex.namespace_route_regex, format: /(html|json|atom)/ }) do
scope(as: :group) do
put :members_update
......
class AddCanPushToKeys < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
DOWNTIME = false
def up
add_column_with_default(:keys, :can_push, :boolean, default: false, allow_null: false)
end
def down
remove_column(:keys, :can_push)
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class RemoveDotGitFromUsernames < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
include Gitlab::ShellAdapter
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def up
invalid_users.each do |user|
id = user['id']
namespace_id = user['namespace_id']
path_was = user['username']
path_was_wildcard = quote_string("#{path_was}/%")
path = quote_string(rename_path(path_was))
move_namespace(namespace_id, path_was, path)
execute "UPDATE routes SET path = '#{path}' WHERE source_type = 'Namespace' AND source_id = #{namespace_id}"
execute "UPDATE namespaces SET path = '#{path}' WHERE id = #{namespace_id}"
execute "UPDATE users SET username = '#{path}' WHERE id = #{id}"
select_all("SELECT id, path FROM routes WHERE path LIKE '#{path_was_wildcard}'").each do |route|
new_path = "#{path}/#{route['path'].split('/').last}"
execute "UPDATE routes SET path = '#{new_path}' WHERE id = #{route['id']}"
end
end
end
def down
# nothing to do here
end
private
def invalid_users
select_all("SELECT u.id, u.username, n.path AS namespace_path, n.id AS namespace_id FROM users u
INNER JOIN namespaces n ON n.owner_id = u.id
WHERE n.type is NULL AND n.path LIKE '%.git'")
end
def route_exists?(path)
select_all("SELECT id, path FROM routes WHERE path = '#{quote_string(path)}'").present?
end
# Accepts invalid path like test.git and returns test_git or
# test_git1 if test_git already taken
def rename_path(path)
# To stay closer with original name and reduce risk of duplicates
# we rename suffix instead of removing it
path = path.sub(/\.git\z/, '_git')
counter = 0
base = path
while route_exists?(path)
counter += 1
path = "#{base}#{counter}"
end
path
end
def move_namespace(namespace_id, path_was, path)
repository_storage_paths = select_all("SELECT distinct(repository_storage) FROM projects WHERE namespace_id = #{namespace_id}").map do |row|
Gitlab.config.repositories.storages[row['repository_storage']]
end.compact
# Move the namespace directory in all storages paths used by member projects
repository_storage_paths.each do |repository_storage_path|
# Ensure old directory exists before moving it
gitlab_shell.add_namespace(repository_storage_path, path_was)
unless gitlab_shell.mv_namespace(repository_storage_path, path_was, path)
Rails.logger.error "Exception moving path #{repository_storage_path} from #{path_was} to #{path}"
# if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs
raise Exception.new('namespace directory cannot be moved')
end
end
Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
end
end
class RenameSlackAndMattermostNotificationServices < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
update_column_in_batches(:services, :type, 'SlackService') do |table, query|
query.where(table[:type].eq('SlackNotificationService'))
end
update_column_in_batches(:services, :type, 'MattermostService') do |table, query|
query.where(table[:type].eq('MattermostNotificationService'))
end
end
def down
update_column_in_batches(:services, :type, 'SlackNotificationService') do |table, query|
query.where(table[:type].eq('SlackService'))
end
update_column_in_batches(:services, :type, 'MattermostNotificationService') do |table, query|
query.where(table[:type].eq('MattermostService'))
end
end
end
require 'thread'
class RenameReservedProjectNames < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
include Gitlab::ShellAdapter
DOWNTIME = false
THREAD_COUNT = 8
KNOWN_PATHS = %w(.well-known
all
assets
blame
blob
commits
create
create_dir
edit
files
files
find_file
groups
hooks
issues
logs_tree
merge_requests
new
new
preview
profile
projects
public
raw
repository
robots.txt
s
snippets
teams
tree
u
unsubscribes
update
users
wikis)
def up
queues = Array.new(THREAD_COUNT) { Queue.new }
start = false
threads = Array.new(THREAD_COUNT) do |index|
Thread.new do
queue = queues[index]
# Wait until we have input to process.
until start; end
rename_projects(queue.pop) until queue.empty?
end
end
enum = queues.each
reserved_projects.each_slice(100) do |slice|
begin
queue = enum.next
rescue StopIteration
enum.rewind
retry
end
queue << slice
end
start = true
threads.each(&:join)
end
def down
# nothing to do here
end
private
def reserved_projects
Project.unscoped.
includes(:namespace).
where('EXISTS (SELECT 1 FROM namespaces WHERE projects.namespace_id = namespaces.id)').
where('projects.path' => KNOWN_PATHS)
end
def route_exists?(full_path)
quoted_path = ActiveRecord::Base.connection.quote_string(full_path)
ActiveRecord::Base.connection.
select_all("SELECT id, path FROM routes WHERE path = '#{quoted_path}'").present?
end
# Adds number to the end of the path that is not taken by other route
def rename_path(namespace_path, path_was)
counter = 0
path = "#{path_was}#{counter}"
while route_exists?("#{namespace_path}/#{path}")
counter += 1
path = "#{path_was}#{counter}"
end
path
end
def rename_projects(projects)
projects.each do |project|
id = project.id
path_was = project.path
namespace_path = project.namespace.path
path = rename_path(namespace_path, path_was)
begin
# Because project path update is quite complex operation we can't safely
# copy-paste all code from GitLab. As exception we use Rails code here
project.rename_repo if rename_project_row(project, path)
rescue Exception => e # rubocop: disable Lint/RescueException
Rails.logger.error "Exception when renaming project #{id}: #{e.message}"
end
end
end
def rename_project_row(project, path)
project.respond_to?(:update_attributes) &&
project.update_attributes(path: path) &&
project.respond_to?(:rename_repo)
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20161221140236) do
ActiveRecord::Schema.define(version: 20161227192806) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -601,6 +601,7 @@ ActiveRecord::Schema.define(version: 20161221140236) do
t.string "type"
t.string "fingerprint"
t.boolean "public", default: false, null: false
t.boolean "can_push", default: false, null: false
end
add_index "keys", ["fingerprint"], name: "index_keys_on_fingerprint", unique: true, using: :btree
......@@ -1509,4 +1510,4 @@ ActiveRecord::Schema.define(version: 20161221140236) do
add_foreign_key "subscriptions", "projects", on_delete: :cascade
add_foreign_key "trending_projects", "projects", on_delete: :cascade
add_foreign_key "u2f_registrations", "users"
end
end
\ No newline at end of file
......@@ -20,12 +20,14 @@ Example response:
"id": 1,
"title": "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
"can_push": false,
"created_at": "2013-10-02T10:12:29Z"
},
{
"id": 3,
"title": "Another Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
"can_push": true,
"created_at": "2013-10-02T11:12:29Z"
}
]
......@@ -55,12 +57,14 @@ Example response:
"id": 1,
"title": "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
"can_push": false,
"created_at": "2013-10-02T10:12:29Z"
},
{
"id": 3,
"title": "Another Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
"can_push": false,
"created_at": "2013-10-02T11:12:29Z"
}
]
......@@ -92,6 +96,7 @@ Example response:
"id": 1,
"title": "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
"can_push": false,
"created_at": "2013-10-02T10:12:29Z"
}
```
......@@ -107,14 +112,15 @@ project only if original one was is accessible by the same user.
POST /projects/:id/deploy_keys
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project |
| `title` | string | yes | New deploy key's title |
| `key` | string | yes | New deploy key |
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project |
| `title` | string | yes | New deploy key's title |
| `key` | string | yes | New deploy key |
| `can_push` | boolean | no | Can deploy key push to the project's repository |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA..."}' "https://gitlab.example.com/api/v3/projects/5/deploy_keys/"
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA...", "can_push": "true"}' "https://gitlab.example.com/api/v3/projects/5/deploy_keys/"
```
Example response:
......@@ -124,6 +130,7 @@ Example response:
"key" : "ssh-rsa AAAA...",
"id" : 12,
"title" : "My deploy key",
"can_push": true,
"created_at" : "2015-08-29T12:44:31.550Z"
}
```
......
......@@ -80,6 +80,7 @@ PUT /application/settings
| `koding_enabled` | boolean | no | Enable Koding integration. Default is `false`. |
| `koding_url` | string | yes (if `koding_enabled` is `true`) | The Koding instance URL for integration. |
| `disabled_oauth_sign_in_sources` | Array of strings | no | Disabled OAuth sign-in sources |
<<<<<<< HEAD
| `help_text` | string | no | GitLab server administrator information |
| `elasticsearch_indexing` | boolean | no | Enable Elasticsearch indexing |
| `elasticsearch_search` | boolean | no | Enable Elasticsearch search |
......@@ -87,6 +88,8 @@ PUT /application/settings
| `elasticsearch_port` | integer | no | The TCP/IP port that Elasticsearch listens to. The default value is 9200 |
| `usage_ping_enabled` | boolean | no | Every week GitLab will report license usage back to GitLab, Inc.|
| `repository_size_limit` | integer | no | Size limit per repository (MB) |
=======
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
```bash
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings?signup_enabled=false&default_project_visibility=1
......
......@@ -3,12 +3,15 @@
## Log arguments to Sidekiq jobs
If you want to see what arguments are being passed to Sidekiq jobs you can set
the SIDEKIQ_LOG_ARGUMENTS environment variable.
the `SIDEKIQ_LOG_ARGUMENTS` [environment variable]
(https://docs.gitlab.com/omnibus/settings/environment-variables.html) to `1` (true).
Example:
```
SIDEKIQ_LOG_ARGUMENTS=1 bundle exec foreman start
gitlab_rails['env'] = {"SIDEKIQ_LOG_ARGUMENTS" => "1"}
```
It is not recommend to enable this setting in production because some Sidekiq
jobs (such as sending a password reset email) take secret arguments (for
example the password reset token).
Please note: It is not recommend to enable this setting in production because some
Sidekiq jobs (such as sending a password reset email) take secret arguments (for
example the password reset token).
\ No newline at end of file
......@@ -39,4 +39,19 @@ When information is updating in place, a quick, subtle animation is needed. The
![Quick update animation](img/animation-quickupdate.gif)
> TODO: Add guidance for other kinds of animation
\ No newline at end of file
### Moving transitions
When elements move on screen, there should be a quick animation so it is clear to users what moved where. The timing of this animation differs based on the amount of movement and change. Consider animations between `200ms` and `400ms`.
#### Shifting elements on reorder
An example of a moving transition is when elements have to move out of the way when you drag an element.
View the [interactive example](http://codepen.io/awhildy/full/ALyKPE/) here.
![Reorder animation](img/animation-reorder.gif)
#### Autoscroll the page
Another example of a moving transition is when you have to autoscroll the page to keep an active element visible.
View the [interactive example](http://codepen.io/awhildy/full/PbxgVo/) here.
![Autoscroll animation](img/animation-autoscroll.gif)
\ No newline at end of file
......@@ -50,13 +50,13 @@ GitLab uses Font Awesome icons throughout our interface.
## Color
| | |
| :------: | :------- |
| ![Blue](img/color-blue.png) | Blue is used to highlight primary active elements (such as the current tab), as well as other organizational and managing commands.|
| ![Green](img/color-green.png) | Green is for actions that create new objects. |
| ![Orange](img/color-orange.png) | Orange is used for warnings |
| ![Red](img/color-red.png) | Red is reserved for delete and other destructive commands |
| ![Grey](img/color-grey.png) | Grey is used for neutral secondary elements. Depending on context, white is sometimes used instead. |
| | State | Action |
| :------: | :------- | :------- |
| ![Blue](img/color-blue.png) | Primary and active (such as the current tab) | Organizational, managing, and retry commands|
| ![Green](img/color-green.png) | Opened | Create new objects |
| ![Orange](img/color-orange.png) | Warning | Non destructive action |
| ![Red](img/color-red.png) | Closed | Delete and other destructive commands |
| ![Grey](img/color-grey.png) | Neutral | Neutral secondary commands |
> TODO: Establish a perspective for color in terms of our personality and rationalize with Marketing usage.
......
@admin
Feature: Admin Deploy Keys
Background:
Given I sign in as an admin
And there are public deploy keys in system
Scenario: Deploy Keys list
When I visit admin deploy keys page
Then I should see all public deploy keys
Scenario: Deploy Keys new
When I visit admin deploy keys page
And I click 'New Deploy Key'
And I submit new deploy key
Then I should be on admin deploy keys page
And I should see newly created deploy key without write access
Scenario: Deploy Keys new with write access
When I visit admin deploy keys page
And I click 'New Deploy Key'
And I submit new deploy key with write access
Then I should be on admin deploy keys page
And I should see newly created deploy key with write access
class Spinach::Features::AdminDeployKeys < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedAdmin
step 'there are public deploy keys in system' do
create(:deploy_key, public: true)
create(:another_deploy_key, public: true)
end
step 'I should see all public deploy keys' do
DeployKey.are_public.each do |p|
expect(page).to have_content p.title
end
end
step 'I visit admin deploy key page' do
visit admin_deploy_key_path(deploy_key)
end
step 'I visit admin deploy keys page' do
visit admin_deploy_keys_path
end
step 'I click \'New Deploy Key\'' do
click_link 'New Deploy Key'
end
step 'I submit new deploy key' do
fill_in "deploy_key_title", with: "laptop"
fill_in "deploy_key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop"
click_button "Create"
end
step 'I submit new deploy key with write access' do
fill_in "deploy_key_title", with: "server"
fill_in "deploy_key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop"
check "deploy_key_can_push"
click_button "Create"
end
step 'I should be on admin deploy keys page' do
expect(current_path).to eq admin_deploy_keys_path
end
step 'I should see newly created deploy key without write access' do
expect(page).to have_content(deploy_key.title)
expect(page).to have_content('No')
end
step 'I should see newly created deploy key with write access' do
expect(page).to have_content(deploy_key.title)
expect(page).to have_content('Yes')
end
def deploy_key
@deploy_key ||= DeployKey.are_public.first
end
end
......@@ -353,7 +353,7 @@ module API
end
class SSHKey < Grape::Entity
expose :id, :title, :key, :created_at
expose :id, :title, :key, :created_at, :can_push
end
class SSHKeyWithUser < SSHKey
......
......@@ -96,7 +96,7 @@ module API
end
def authenticate_non_get!
authenticate! unless %w[GET HEAD].include?(route.route_method)
authenticate! unless %w[GET HEAD].include?(route.request_method)
end
def authenticate_by_gitlab_shell_token!
......
......@@ -69,8 +69,6 @@ module API
optional :created_at, type: String, desc: 'The creation date of the note'
end
post ":id/#{noteables_str}/:noteable_id/notes" do
required_attributes! [:body]
opts = {
note: params[:body],
noteable_type: noteables_str.classify,
......
......@@ -87,6 +87,10 @@ module API
given sentry_enabled: ->(val) { val } do
requires :sentry_dsn, type: String, desc: 'Sentry Data Source Name'
end
<<<<<<< HEAD
=======
optional :repository_storage, type: String, desc: 'Storage paths for new projects'
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
optional :repository_checks_enabled, type: Boolean, desc: "GitLab will periodically run 'git fsck' in all project and wiki repositories to look for silent disk corruption issues."
optional :koding_enabled, type: Boolean, desc: 'Enable Koding'
given koding_enabled: ->(val) { val } do
......@@ -102,6 +106,7 @@ module API
requires :housekeeping_full_repack_period, type: Integer, desc: "Number of Git pushes after which a full 'git repack' is run."
requires :housekeeping_gc_period, type: Integer, desc: "Number of Git pushes after which 'git gc' is run."
end
<<<<<<< HEAD
# GitLab-EE specific settings
optional :help_text, type: String, desc: 'GitLab server administrator information'
optional :max_pages_size, type: Integer, desc: 'Maximum size of pages in MB'
......@@ -116,6 +121,8 @@ module API
optional :repository_storage, type: String, desc: 'The first entry in `repository_storages`. Deprecated, but retained for compatibility reasons'
optional :repository_storages, type: Array[String], desc: 'A list of names of enabled storage paths, taken from `gitlab.yml`. New projects will be created in one of these stores, chosen at random.'
optional :repository_size_limit, type: Integer, desc: 'Size limit per repository (MB)'
=======
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
at_least_one_of :default_branch_protection, :default_project_visibility, :default_snippet_visibility,
:default_group_visibility, :restricted_visibility_levels, :import_sources,
:enabled_git_access_protocol, :gravatar_enabled, :default_projects_limit,
......@@ -127,11 +134,17 @@ module API
:shared_runners_enabled, :max_artifacts_size, :container_registry_token_expire_delay,
:metrics_enabled, :sidekiq_throttling_enabled, :recaptcha_enabled,
:akismet_enabled, :admin_notification_email, :sentry_enabled,
<<<<<<< HEAD
:repository_checks_enabled, :koding_enabled, :housekeeping_enabled,
:version_check_enabled, :email_author_in_body, :html_emails_enabled,
# GitLab-EE specific settings
:help_text, :max_pages_size, :elasticsearch_indexing, :usage_ping_enabled,
:repository_storage, :repository_storages, :repository_size_limit
=======
:repository_storage, :repository_checks_enabled, :koding_enabled,
:version_check_enabled, :email_author_in_body, :html_emails_enabled,
:housekeeping_enabled
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
end
put "application/settings" do
if current_settings.update_attributes(declared_params(include_missing: false))
......
......@@ -9,8 +9,7 @@ module Gitlab
def lfs_deploy_token?(for_project)
type == :lfs_deploy_token &&
actor &&
actor.projects.include?(for_project)
actor.try(:has_access_to?, for_project)
end
def success?
......
module Gitlab
module Checks
class ChangeAccess
<<<<<<< HEAD
include PathLocksHelper
attr_reader :user_access, :project
=======
attr_reader :user_access, :project, :skip_authorization
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
def initialize(change, user_access:, project:, env: {})
def initialize(
change, user_access:, project:, env: {}, skip_authorization: false)
@oldrev, @newrev, @ref = change.values_at(:oldrev, :newrev, :ref)
@branch_name = Gitlab::Git.branch_name(@ref)
@user_access = user_access
@project = project
@env = env
@skip_authorization = skip_authorization
end
def exec
......@@ -25,6 +31,7 @@ module Gitlab
protected
def protected_branch_checks
return if skip_authorization
return unless @branch_name
return unless project.protected_branch?(@branch_name)
......@@ -50,6 +57,8 @@ module Gitlab
end
def tag_checks
return if skip_authorization
tag_ref = Gitlab::Git.tag_name(@ref)
if tag_ref && protected_tag?(tag_ref) && user_access.cannot_do_action?(:admin_project)
......@@ -58,6 +67,8 @@ module Gitlab
end
def push_checks
return if skip_authorization
if user_access.cannot_do_action?(:push_code)
"You are not allowed to push code to this project."
end
......
......@@ -13,9 +13,17 @@ module Gitlab
encoding = body.encoding
body = discourse_email_trimmer(body)
body = EmailReplyTrimmer.trim(body)
body = EmailReplyParser.parse_reply(body)
return '' unless body
# not using /\s+$/ here because that deletes empty lines
body = body.gsub(/[ \t]$/, '')
# NOTE: We currently don't support empty quotes.
# EmailReplyTrimmer allows this as a special case,
# so we detect it manually here.
return "" if body.lines.all? { |l| l.strip.empty? || l.start_with?('>') }
body.force_encoding(encoding).encode("UTF-8")
end
......@@ -57,30 +65,6 @@ module Gitlab
rescue
nil
end
REPLYING_HEADER_LABELS = %w(From Sent To Subject Reply To Cc Bcc Date)
REPLYING_HEADER_REGEX = Regexp.union(REPLYING_HEADER_LABELS.map { |label| "#{label}:" })
def discourse_email_trimmer(body)
lines = body.scrub.lines.to_a
range_end = 0
lines.each_with_index do |l, idx|
# This one might be controversial but so many reply lines have years, times and end with a colon.
# Let's try it and see how well it works.
break if (l =~ /\d{4}/ && l =~ /\d:\d\d/ && l =~ /\:$/) ||
(l =~ /On \w+ \d+,? \d+,?.*wrote:/)
# Headers on subsequent lines
break if (0..2).all? { |off| lines[idx + off] =~ REPLYING_HEADER_REGEX }
# Headers on the same line
break if REPLYING_HEADER_LABELS.count { |label| l.include?(label) } >= 3
range_end = idx
end
lines[0..range_end].join.strip
end
end
end
end
......@@ -8,7 +8,8 @@ module Gitlab
ERROR_MESSAGES = {
upload: 'You are not allowed to upload code for this project.',
download: 'You are not allowed to download code from this project.',
deploy_key: 'Deploy keys are not allowed to push code.',
deploy_key_upload:
'This deploy key does not have write access to this project.',
no_repo: 'A repository for this project does not exist yet.'
}
......@@ -33,16 +34,21 @@ module Gitlab
check_active_user!
check_project_accessibility!
check_command_existence!(cmd)
check_repository_existence!
check_geo_license!
case cmd
when *DOWNLOAD_COMMANDS
download_access_check
check_download_access!
when *PUSH_COMMANDS
<<<<<<< HEAD
push_access_check(changes)
when *GIT_ANNEX_COMMANDS
git_annex_access_check(project, changes)
=======
check_push_access!(changes)
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
end
build_status_object(true)
......@@ -50,6 +56,7 @@ module Gitlab
build_status_object(false, ex.message)
end
<<<<<<< HEAD
def download_access_check
if user
user_download_access_check
......@@ -77,15 +84,12 @@ module Gitlab
end
def guest_can_downlod_code?
=======
def guest_can_download_code?
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
Guest.can?(:download_code, project)
end
def user_download_access_check
unless user_can_download_code? || build_can_download_code?
raise UnauthorizedError, ERROR_MESSAGES[:download]
end
end
def user_can_download_code?
authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_code)
end
......@@ -94,6 +98,7 @@ module Gitlab
authentication_abilities.include?(:build_download_code) && user_access.can_do_action?(:build_download_code)
end
<<<<<<< HEAD
def user_push_access_check(changes)
unless authentication_abilities.include?(:push_code)
raise UnauthorizedError, ERROR_MESSAGES[:upload]
......@@ -142,6 +147,8 @@ module Gitlab
Checks::ChangeAccess.new(change, user_access: user_access, project: project, env: @env).exec
end
=======
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
def protocol_allowed?
Gitlab::ProtocolAccess.allowed?(protocol)
end
......@@ -155,6 +162,8 @@ module Gitlab
end
def check_active_user!
return if deploy_key?
if user && !user_access.allowed?
raise UnauthorizedError, "Your account has been blocked."
end
......@@ -172,6 +181,7 @@ module Gitlab
end
end
<<<<<<< HEAD
def check_geo_license!
if Gitlab::Geo.secondary? && !Gitlab::Geo.license_allows?
raise UnauthorizedError, 'Your current license does not have GitLab Geo add-on enabled.'
......@@ -211,14 +221,41 @@ module Gitlab
end
def deploy_key_can_read_project?
=======
def check_repository_existence!
unless project.repository.exists?
raise UnauthorizedError, ERROR_MESSAGES[:no_repo]
end
end
def check_download_access!
return if deploy_key?
passed = user_can_download_code? ||
build_can_download_code? ||
guest_can_download_code?
unless passed
raise UnauthorizedError, ERROR_MESSAGES[:download]
end
end
def check_push_access!(changes)
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
if deploy_key
return true if project.public?
deploy_key.projects.include?(project)
check_deploy_key_push_access!
elsif user
check_user_push_access!
else
false
raise UnauthorizedError, ERROR_MESSAGES[:upload]
end
return if changes.blank? # Allow access.
check_change_access!(changes)
end
<<<<<<< HEAD
def can_read_project?
if user
user_access.can_read_project?
......@@ -228,9 +265,62 @@ module Gitlab
true
else
Guest.can?(:read_project, project)
=======
def check_user_push_access!
unless authentication_abilities.include?(:push_code)
raise UnauthorizedError, ERROR_MESSAGES[:upload]
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
end
end
def check_deploy_key_push_access!
unless deploy_key.can_push_to?(project)
raise UnauthorizedError, ERROR_MESSAGES[:deploy_key_upload]
end
end
def check_change_access!(changes)
changes_list = Gitlab::ChangesList.new(changes)
# Iterate over all changes to find if user allowed all of them to be applied
changes_list.each do |change|
status = check_single_change_access(change)
unless status.allowed?
# If user does not have access to make at least one change - cancel all push
raise UnauthorizedError, status.message
end
end
end
def check_single_change_access(change)
Checks::ChangeAccess.new(
change,
user_access: user_access,
project: project,
env: @env,
skip_authorization: deploy_key?).exec
end
def matching_merge_request?(newrev, branch_name)
Checks::MatchingMergeRequest.new(newrev, branch_name, project).match?
end
def deploy_key
actor if deploy_key?
end
def deploy_key?
actor.is_a?(DeployKey)
end
def can_read_project?
if deploy_key
deploy_key.has_access_to?(project)
elsif user
user.can?(:read_project, project)
end || Guest.can?(:read_project, project)
end
protected
def user
......
module Gitlab
class GitAccessWiki < GitAccess
def guest_can_downlod_code?
def guest_can_download_code?
Guest.can?(:download_wiki_code, project)
end
......@@ -8,10 +8,15 @@ module Gitlab
authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_wiki_code)
end
<<<<<<< HEAD
def change_access_check(change)
if Gitlab::Geo.enabled? && Gitlab::Geo.secondary?
build_status_object(false, "You can't push code to a secondary GitLab Geo node.")
elsif user_access.can_do_action?(:create_wiki)
=======
def check_single_change_access(change)
if user_access.can_do_action?(:create_wiki)
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
build_status_object(true)
else
build_status_object(false, "You are not allowed to write to this project's wiki.")
......
......@@ -15,7 +15,7 @@ module Gitlab
Theme.new(1, 'Graphite', 'ui_graphite'),
Theme.new(2, 'Charcoal', 'ui_charcoal'),
Theme.new(3, 'Green', 'ui_green'),
Theme.new(4, 'Gray', 'ui_gray'),
Theme.new(4, 'Black', 'ui_black'),
Theme.new(5, 'Violet', 'ui_violet'),
Theme.new(6, 'Blue', 'ui_blue')
].freeze
......
......@@ -8,6 +8,8 @@ module Gitlab
end
def can_do_action?(action)
return false if no_user_or_blocked?
@permission_cache ||= {}
@permission_cache[action] ||= user.can?(action, project)
end
......@@ -17,7 +19,7 @@ module Gitlab
end
def allowed?
return false if user.blank? || user.blocked?
return false if no_user_or_blocked?
if user.requires_ldap_check? && user.try_obtain_ldap_lease
return false unless Gitlab::LDAP::Access.allowed?(user)
......@@ -27,7 +29,7 @@ module Gitlab
end
def can_push_to_branch?(ref)
return false unless user
return false if no_user_or_blocked?
if project.protected_branch?(ref)
return true if project.empty_repo? && project.user_can_push_to_empty_repo?(user)
......@@ -40,7 +42,7 @@ module Gitlab
end
def can_merge_to_branch?(ref)
return false unless user
return false if no_user_or_blocked?
if project.protected_branch?(ref)
access_levels = project.protected_branches.matching(ref).map(&:merge_access_levels).flatten
......@@ -51,9 +53,15 @@ module Gitlab
end
def can_read_project?
return false unless user
return false if no_user_or_blocked?
user.can?(:read_project, project)
end
private
def no_user_or_blocked?
user.nil? || user.blocked?
end
end
end
require 'spec_helper'
describe Dashboard::TodosController do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:todo_service) { TodoService.new }
describe 'GET #index' do
before do
sign_in(user)
project.team << [user, :developer]
end
context 'when using pagination' do
let(:last_page) { user.todos.page().total_pages }
let!(:issues) { create_list(:issue, 2, project: project, assignee: user) }
before do
issues.each { |issue| todo_service.new_issue(issue, user) }
allow(Kaminari.config).to receive(:default_per_page).and_return(1)
end
it 'redirects to last_page if page number is larger than number of pages' do
get :index, page: (last_page + 1).to_param
expect(response).to redirect_to(dashboard_todos_path(page: last_page))
end
it 'redirects to correspondent page' do
get :index, page: last_page
expect(assigns(:todos).current_page).to eq(last_page)
expect(response).to have_http_status(200)
end
end
end
end
......@@ -52,6 +52,36 @@ describe Projects::IssuesController do
expect(response).to have_http_status(404)
end
end
context 'with page param' do
let(:last_page) { project.issues.page().total_pages }
let!(:issue_list) { create_list(:issue, 2, project: project) }
before do
sign_in(user)
project.team << [user, :developer]
allow(Kaminari.config).to receive(:default_per_page).and_return(1)
end
it 'redirects to last_page if page number is larger than number of pages' do
get :index,
namespace_id: project.namespace.path.to_param,
project_id: project.path.to_param,
page: (last_page + 1).to_param
expect(response).to redirect_to(namespace_project_issues_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope]))
end
it 'redirects to specified page' do
get :index,
namespace_id: project.namespace.path.to_param,
project_id: project.path.to_param,
page: last_page.to_param
expect(assigns(:issues).current_page).to eq(last_page)
expect(response).to have_http_status(200)
end
end
end
describe 'GET #new' do
......
......@@ -213,11 +213,29 @@ describe Projects::MergeRequestsController do
end
describe 'GET index' do
def get_merge_requests
def get_merge_requests(page = nil)
get :index,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
state: 'opened'
state: 'opened', page: page.to_param
end
context 'when page param' do
let(:last_page) { project.merge_requests.page().total_pages }
let!(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
it 'redirects to last_page if page number is larger than number of pages' do
get_merge_requests(last_page + 1)
expect(response).to redirect_to(namespace_project_merge_requests_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope]))
end
it 'redirects to specified page' do
get_merge_requests(last_page)
expect(assigns(:merge_requests).current_page).to eq(last_page)
expect(response).to have_http_status(200)
end
end
context 'when filtering by opened state' do
......
......@@ -11,6 +11,28 @@ describe Projects::SnippetsController do
end
describe 'GET #index' do
context 'when page param' do
let(:last_page) { project.snippets.page().total_pages }
let!(:project_snippet) { create(:project_snippet, :public, project: project, author: user) }
it 'redirects to last_page if page number is larger than number of pages' do
get :index,
namespace_id: project.namespace.path,
project_id: project.path, page: (last_page + 1).to_param
expect(response).to redirect_to(namespace_project_snippets_path(page: last_page))
end
it 'redirects to specified page' do
get :index,
namespace_id: project.namespace.path,
project_id: project.path, page: last_page.to_param
expect(assigns(:snippets).current_page).to eq(last_page)
expect(response).to have_http_status(200)
end
end
context 'when the project snippet is private' do
let!(:project_snippet) { create(:project_snippet, :private, project: project, author: user) }
......
......@@ -17,6 +17,16 @@ feature 'Admin Groups', feature: true do
end
end
describe 'show a group' do
scenario 'shows the group' do
group = create(:group, :private)
visit admin_group_path(group)
expect(page).to have_content("Group: #{group.name}")
end
end
describe 'group edit' do
scenario 'shows the visibility level radio populated with the group visibility_level value' do
group = create(:group, :private)
......
......@@ -47,6 +47,18 @@ feature 'GFM autocomplete', feature: true, js: true do
expect_to_wrap(true, label_item, note, label.title)
end
it "shows dropdown after a new line" do
note = find('#note_note')
page.within '.timeline-content-form' do
note.native.send_keys('test')
note.native.send_keys(:enter)
note.native.send_keys(:enter)
note.native.send_keys('@')
end
expect(page).to have_selector('.atwho-container')
end
it "does not show dropdown when preceded with a special character" do
note = find('#note_note')
page.within '.timeline-content-form' do
......
......@@ -41,7 +41,7 @@ feature 'Merge Request closing issues message', feature: true do
let(:merge_request_description) { "Description\n\nRefers to #{issue_1.to_reference} and #{issue_2.to_reference}" }
it 'does not display closing issue message' do
expect(page).to have_content("Issues #{issue_1.to_reference} and #{issue_2.to_reference} are mentioned but will not closed.")
expect(page).to have_content("Issues #{issue_1.to_reference} and #{issue_2.to_reference} are mentioned but will not be closed.")
end
end
......@@ -49,7 +49,7 @@ feature 'Merge Request closing issues message', feature: true do
let(:merge_request_description) { "Description\n\ncloses #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" }
it 'does not display closing issue message' do
expect(page).to have_content("Accepting this merge request will close issue #{issue_1.to_reference}. Issue #{issue_2.to_reference} is mentioned but will not closed.")
expect(page).to have_content("Accepting this merge request will close issue #{issue_1.to_reference}. Issue #{issue_2.to_reference} is mentioned but will not be closed.")
end
end
end
require 'rails_helper'
describe 'Milestone draggable', feature: true, js: true do
include WaitForAjax
let(:milestone) { create(:milestone, project: project, title: 8.14) }
let(:project) { create(:empty_project, :public) }
let(:user) { create(:user) }
......@@ -74,6 +76,8 @@ describe 'Milestone draggable', feature: true, js: true do
visit namespace_project_milestone_path(project.namespace, project, milestone)
issue.drag_to(issue_target)
wait_for_ajax
end
def create_and_drag_merge_request(params = {})
......@@ -82,5 +86,7 @@ describe 'Milestone draggable', feature: true, js: true do
visit namespace_project_milestone_path(project.namespace, project, milestone)
page.find("a[href='#tab-merge-requests']").click
merge_request.drag_to(merge_request_target)
wait_for_ajax
end
end
......@@ -34,7 +34,7 @@ feature 'Master creates tag', feature: true do
expect(current_path).to eq(
namespace_project_tag_path(project.namespace, project, 'v3.0'))
expect(page).to have_content 'v3.0'
page.within 'pre.body' do
page.within 'pre.wrap' do
expect(page).to have_content "Awesome tag message\n\n- hello\n- world"
end
end
......
......@@ -4,7 +4,7 @@
GitLab Org
%a.project-item-select-holder{href: "/gitlab-org/gitlab-test"}
GitLab Test
%i.fa.chevron-down.dropdown-toggle-caret.js-projects-dropdown-toggle{ "data-toggle" => "dropdown", "data-target" => ".header-content" }
%i.fa.chevron-down.dropdown-toggle-caret.js-projects-dropdown-toggle{ "data-toggle" => "dropdown", "data-target" => ".header-content", "data-order-by" => "last_activity_at" }
.js-dropdown-menu-projects
.dropdown-menu.dropdown-select.dropdown-menu-projects
.dropdown-title
......
......@@ -26,6 +26,7 @@
var fakeAjaxResponse = function fakeAjaxResponse(req) {
var d;
expect(req.url).toBe('/api/v3/projects.json?simple=true');
expect(req.data).toEqual({ search: '', order_by: 'last_activity_at', per_page: 20 });
d = $.Deferred();
d.resolve(this.projects_data);
return d.promise();
......
......@@ -88,8 +88,6 @@ describe Gitlab::Email::ReplyParser, lib: true do
expect(test_parse_body(fixture_file("emails/inline_reply.eml"))).
to eq(
<<-BODY.strip_heredoc.chomp
On Wed, Oct 8, 2014 at 11:12 AM, techAPJ <info@unconfigured.discourse.org> wrote:
> techAPJ <https://meta.discourse.org/users/techapj>
> November 28
>
......
......@@ -55,7 +55,7 @@ describe Gitlab::GitAccess, lib: true do
end
end
describe 'download_access_check' do
describe '#check_download_access!' do
subject { access.check('git-upload-pack', '_any') }
describe 'master permissions' do
......@@ -87,7 +87,7 @@ describe Gitlab::GitAccess, lib: true do
end
end
describe 'without acccess to project' do
describe 'without access to project' do
context 'pull code' do
it { expect(subject.allowed?).to be_falsey }
end
......@@ -117,7 +117,7 @@ describe Gitlab::GitAccess, lib: true do
end
describe 'deploy key permissions' do
let(:key) { create(:deploy_key) }
let(:key) { create(:deploy_key, user: user) }
let(:actor) { key }
context 'pull code' do
......@@ -141,7 +141,7 @@ describe Gitlab::GitAccess, lib: true do
end
context 'from private project' do
let(:project) { create(:project, :internal) }
let(:project) { create(:project, :private) }
it { expect(subject).not_to be_allowed }
end
......@@ -199,7 +199,7 @@ describe Gitlab::GitAccess, lib: true do
end
end
describe 'push_access_check' do
describe '#check_push_access!' do
before { merge_into_protected_branch }
let(:unprotected_branch) { FFaker::Internet.user_name }
......@@ -247,6 +247,7 @@ describe Gitlab::GitAccess, lib: true do
permissions_matrix[role].each do |action, allowed|
context action do
<<<<<<< HEAD
subject { access.push_access_check(changes[action]) }
it do
......@@ -283,6 +284,10 @@ describe Gitlab::GitAccess, lib: true do
else
expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError)
end
=======
subject { access.send(:check_push_access!, changes[action]) }
it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey }
>>>>>>> 714f70a38df10e678bffde6e6081a97e31d8317c
end
end
end
......@@ -769,13 +774,13 @@ describe Gitlab::GitAccess, lib: true do
end
end
shared_examples 'can not push code' do
shared_examples 'pushing code' do |can|
subject { access.check('git-receive-pack', '_any') }
context 'when project is authorized' do
before { authorize }
it { expect(subject).not_to be_allowed }
it { expect(subject).public_send(can, be_allowed) }
end
context 'when unauthorized' do
......@@ -802,7 +807,7 @@ describe Gitlab::GitAccess, lib: true do
describe 'build authentication abilities' do
let(:authentication_abilities) { build_authentication_abilities }
it_behaves_like 'can not push code' do
it_behaves_like 'pushing code', :not_to do
def authorize
project.team << [user, :reporter]
end
......@@ -821,12 +826,26 @@ describe Gitlab::GitAccess, lib: true do
end
describe 'deploy key permissions' do
let(:key) { create(:deploy_key) }
let(:key) { create(:deploy_key, user: user, can_push: can_push) }
let(:actor) { key }
it_behaves_like 'can not push code' do
def authorize
key.projects << project
context 'when deploy_key can push' do
let(:can_push) { true }
it_behaves_like 'pushing code', :to do
def authorize
key.projects << project
end
end
end
context 'when deploy_key cannot push' do
let(:can_push) { false }
it_behaves_like 'pushing code', :not_to do
def authorize
key.projects << project
end
end
end
end
......
......@@ -35,7 +35,7 @@ describe Gitlab::GitAccessWiki, lib: true do
end
end
describe '#download_access_check' do
describe '#access_check_download!' do
subject { access.check('git-upload-pack', '_any') }
before do
......
......@@ -251,6 +251,7 @@ DeployKey:
- type
- fingerprint
- public
- can_push
Service:
- id
- type
......
# encoding: utf-8
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20161226122833_remove_dot_git_from_usernames.rb')
describe RemoveDotGitFromUsernames do
let(:user) { create(:user) }
describe '#up' do
let(:migration) { described_class.new }
before do
namespace = user.namespace
namespace.path = 'test.git'
namespace.save!(validate: false)
user.username = 'test.git'
user.save!(validate: false)
end
it 'renames user with .git in username' do
migration.up
expect(user.reload.username).to eq('test_git')
expect(user.namespace.reload.path).to eq('test_git')
expect(user.namespace.route.path).to eq('test_git')
end
end
end
# encoding: utf-8
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20161221153951_rename_reserved_project_names.rb')
# This migration uses multiple threads, and thus different transactions. This
# means data created in this spec may not be visible to some threads. To work
# around this we use the TRUNCATE cleaning strategy.
describe RenameReservedProjectNames, truncate: true do
let(:migration) { described_class.new }
let!(:project) { create(:empty_project) }
before do
project.path = 'projects'
project.save!(validate: false)
end
describe '#up' do
context 'when project repository exists' do
before { project.create_repository }
context 'when no exception is raised' do
it 'renames project with reserved names' do
migration.up
expect(project.reload.path).to eq('projects0')
end
end
context 'when exception is raised during rename' do
before do
allow(project).to receive(:rename_repo).and_raise(StandardError)
end
it 'captures exception from project rename' do
expect { migration.up }.not_to raise_error
end
end
end
context 'when project repository does not exist' do
it 'does not raise error' do
expect { migration.up }.not_to raise_error
end
end
end
end
require 'spec_helper'
describe DeployKey, models: true do
include EmailHelpers
describe "Associations" do
it { is_expected.to have_many(:deploy_keys_projects) }
it { is_expected.to have_many(:projects) }
end
describe 'notification' do
let(:user) { create(:user) }
it 'does not send a notification' do
perform_enqueued_jobs do
create(:deploy_key, user: user)
end
should_not_email(user)
end
end
end
......@@ -307,4 +307,15 @@ describe Group, models: true do
it { is_expected.to be_valid }
it { expect(subject.parent).to be_kind_of(Group) }
end
describe '#members_with_parents' do
let!(:group) { create(:group, :nested) }
let!(:master) { group.parent.add_user(create(:user), GroupMember::MASTER) }
let!(:developer) { group.add_user(create(:user), GroupMember::DEVELOPER) }
it 'returns parents members' do
expect(group.members_with_parents).to include(developer)
expect(group.members_with_parents).to include(master)
end
end
end
require 'spec_helper'
describe Key, models: true do
include EmailHelpers
describe "Associations" do
it { is_expected.to belong_to(:user) }
end
......@@ -96,4 +98,16 @@ describe Key, models: true do
expect(described_class.new(key: " #{valid_key} ").key).to eq(valid_key)
end
end
describe 'notification' do
let(:user) { create(:user) }
it 'sends a notification' do
perform_enqueued_jobs do
create(:key, user: user)
end
should_email(user)
end
end
end
......@@ -284,12 +284,16 @@ describe MergeRequest, models: true do
end
describe '#issues_mentioned_but_not_closing' do
it 'detects issues mentioned in description but not closed' do
mentioned_issue = create(:issue, project: subject.project)
let(:closing_issue) { create :issue, project: subject.project }
let(:mentioned_issue) { create :issue, project: subject.project }
let(:commit) { double('commit', safe_message: "Fixes #{closing_issue.to_reference}") }
it 'detects issues mentioned in description but not closed' do
subject.project.team << [subject.author, :developer]
subject.description = "Is related to #{mentioned_issue.to_reference}"
subject.description = "Is related to #{mentioned_issue.to_reference} and #{closing_issue.to_reference}"
allow(subject).to receive(:commits).and_return([commit])
allow(subject.project).to receive(:default_branch).
and_return(subject.target_branch)
......
......@@ -105,4 +105,70 @@ describe GroupPolicy, models: true do
is_expected.to include(*owner_permissions)
end
end
describe 'private nested group inherit permissions' do
let(:nested_group) { create(:group, :private, parent: group) }
subject { described_class.abilities(current_user, nested_group).to_set }
context 'with no user' do
let(:current_user) { nil }
it do
is_expected.not_to include(:read_group)
is_expected.not_to include(*master_permissions)
is_expected.not_to include(*owner_permissions)
end
end
context 'guests' do
let(:current_user) { guest }
it do
is_expected.to include(:read_group)
is_expected.not_to include(*master_permissions)
is_expected.not_to include(*owner_permissions)
end
end
context 'reporter' do
let(:current_user) { reporter }
it do
is_expected.to include(:read_group)
is_expected.not_to include(*master_permissions)
is_expected.not_to include(*owner_permissions)
end
end
context 'developer' do
let(:current_user) { developer }
it do
is_expected.to include(:read_group)
is_expected.not_to include(*master_permissions)
is_expected.not_to include(*owner_permissions)
end
end
context 'master' do
let(:current_user) { master }
it do
is_expected.to include(:read_group)
is_expected.to include(*master_permissions)
is_expected.not_to include(*owner_permissions)
end
end
context 'owner' do
let(:current_user) { owner }
it do
is_expected.to include(:read_group)
is_expected.to include(*master_permissions)
is_expected.to include(*owner_permissions)
end
end
end
end
......@@ -396,7 +396,7 @@ describe API::Helpers, api: true do
%w[HEAD GET].each do |method_name|
context "method is #{method_name}" do
before do
expect_any_instance_of(self.class).to receive(:route).and_return(double(route_method: method_name))
expect_any_instance_of(self.class).to receive(:route).and_return(double(request_method: method_name))
end
it 'does not raise an error' do
......@@ -410,7 +410,7 @@ describe API::Helpers, api: true do
%w[POST PUT PATCH DELETE].each do |method_name|
context "method is #{method_name}" do
before do
expect_any_instance_of(self.class).to receive(:route).and_return(double(route_method: method_name))
expect_any_instance_of(self.class).to receive(:route).and_return(double(request_method: method_name))
end
it 'calls authenticate!' do
......
......@@ -483,12 +483,26 @@ describe 'Git HTTP requests', lib: true do
shared_examples 'can download code only' do
it 'downloads get status 200' do
clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
allow_any_instance_of(Repository).
to receive(:exists?).and_return(true)
clone_get "#{project.path_with_namespace}.git",
user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(200)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
it 'downloads from non-existing repository and gets 403' do
allow_any_instance_of(Repository).
to receive(:exists?).and_return(false)
clone_get "#{project.path_with_namespace}.git",
user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(403)
end
it 'uploads get status 403' do
push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
......
......@@ -132,12 +132,18 @@ describe Admin::HealthCheckController, "routing" do
end
describe Admin::GroupsController, "routing" do
let(:name) { 'complex.group-namegit' }
it "to #index" do
expect(get("/admin/groups")).to route_to('admin/groups#index')
end
it "to #show" do
expect(get("/admin/groups/gitlab")).to route_to('admin/groups#show', id: 'gitlab')
expect(get("/admin/groups/gitlab/subgroup")).to route_to('admin/groups#show', id: 'gitlab/subgroup')
expect(get("/admin/groups/#{name}")).to route_to('admin/groups#show', id: name)
expect(get("/admin/groups/#{name}/subgroup")).to route_to('admin/groups#show', id: "#{name}/subgroup")
end
it "to #edit" do
expect(get("/admin/groups/#{name}/edit")).to route_to('admin/groups#edit', id: name)
end
end
......@@ -54,12 +54,37 @@ describe Users::RefreshAuthorizedProjectsService do
end
describe '#update_authorizations' do
it 'does nothing when there are no rows to add and remove' do
expect(user).not_to receive(:remove_project_authorizations)
expect(ProjectAuthorization).not_to receive(:insert_authorizations)
expect(user).not_to receive(:set_authorized_projects_column)
context 'when there are no rows to add and remove' do
it 'does not change authorizations' do
expect(user).not_to receive(:remove_project_authorizations)
expect(ProjectAuthorization).not_to receive(:insert_authorizations)
service.update_authorizations([], [])
service.update_authorizations([], [])
end
context 'when the authorized projects column is not set' do
before do
user.update!(authorized_projects_populated: nil)
end
it 'populates the authorized projects column' do
service.update_authorizations([], [])
expect(user.authorized_projects_populated).to eq true
end
end
context 'when the authorized projects column is set' do
before do
user.update!(authorized_projects_populated: true)
end
it 'does nothing' do
expect(user).not_to receive(:set_authorized_projects_column)
service.update_authorizations([], [])
end
end
end
it 'removes authorizations that should be removed' do
......@@ -84,7 +109,7 @@ describe Users::RefreshAuthorizedProjectsService do
it 'populates the authorized projects column' do
# make sure we start with a nil value no matter what the default in the
# factory may be.
user.update(authorized_projects_populated: nil)
user.update!(authorized_projects_populated: nil)
service.update_authorizations([], [[user.id, project.id, Gitlab::Access::MASTER]])
......
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