fix pipelines/index.html.haml merge conflict

This commit is contained in:
Regis 2017-01-02 16:24:37 -07:00
commit 0a074f2e09
473 changed files with 3660 additions and 1563 deletions

View file

@ -425,7 +425,7 @@ notify:slack:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
script:
- ./scripts/notify_slack.sh "#development" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>"
- ./scripts/notify_slack.sh "#development" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/pipelines>"
when: on_failure
only:
- master@gitlab-org/gitlab-ce

View file

@ -7,10 +7,10 @@ exclude:
linters:
AltText:
enabled: false
enabled: true
ClassAttributeWithStaticValue:
enabled: false
enabled: true
ClassesBeforeIds:
enabled: false
@ -29,14 +29,14 @@ linters:
enabled: true
FinalNewline:
enabled: false
enabled: true
present: true
HtmlAttributes:
enabled: false
enabled: true
ImplicitDiv:
enabled: false
enabled: true
LeadingCommentSpace:
enabled: false
@ -80,10 +80,10 @@ linters:
enabled: false
SpaceBeforeScript:
enabled: false
enabled: true
SpaceInsideHashAttributes:
enabled: false
enabled: true
style: space
Indentation:
@ -94,7 +94,7 @@ linters:
enabled: true
TrailingWhitespace:
enabled: false
enabled: true
UnnecessaryInterpolation:
enabled: false

View file

@ -2,6 +2,22 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 8.15.2 (2016-12-27)
- Fix finding the latest pipeline. !8301
- 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
- Fix line breaking in nodes of the pipeline graph in firefox. !8292
- Fixes confendential warning text alignment. !8293
- Hide Scroll Top button for failed build page. !8295
- Fix finding the latest pipeline. !8301
- Disable PostgreSQL statement timeouts when removing unneeded services. !8322
- Fix timeout when MR contains large files marked as binary by .gitattributes.
- Rename "autodeploy" to "auto deploy".
- Fixed GFM autocomplete error when no data exists.
- Fixed resolve discussion note button color.
## 8.15.1 (2016-12-23)
- Push payloads schedule at most 100 commits, instead of all commits.

View file

@ -217,8 +217,8 @@ We welcome merge requests with fixes and improvements to GitLab code, tests,
and/or documentation. The features we would really like a merge request for are
listed with the label [`Accepting Merge Requests` on our issue tracker for CE][accepting-mrs-ce]
and [EE][accepting-mrs-ee] but other improvements are also welcome. Please note
that if an issue is marked for the current milestone either before or while you
are working on it, a team member may take over the merge request in order to
that if an issue is marked for the current milestone either before or while you
are working on it, a team member may take over the merge request in order to
ensure the work is finished before the release date.
If you want to add a new feature that is not labeled it is best to first create
@ -300,6 +300,7 @@ you start with a very simple UI? Can you do part of the refactor? The increased
reviewability of small MRs that leads to higher code quality is more important
to us than having a minimal commit log. The smaller an MR is the more likely it
is it will be merged (quickly). After that you can send more MRs to enhance it.
The ['How to get faster PR reviews' document of Kubernetes](https://github.com/kubernetes/community/blob/master/contributors/devel/faster_reviews.md) also has some great points regarding this.
For examples of feedback on merge requests please look at already
[closed merge requests][closed-merge-requests]. If you would like quick feedback

View file

@ -332,7 +332,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'
@ -347,5 +347,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'

View file

@ -167,7 +167,7 @@ GEM
railties (>= 4.2)
dropzonejs-rails (0.7.2)
rails (> 3.1)
email_reply_parser (0.5.8)
email_reply_trimmer (0.1.6)
email_spec (1.6.0)
launchy (~> 2.1)
mail (~> 2.2)
@ -773,7 +773,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)
@ -839,7 +839,7 @@ DEPENDENCIES
diffy (~> 3.1.0)
doorkeeper (~> 4.2.0)
dropzonejs-rails (~> 0.7.1)
email_reply_parser (~> 0.5.8)
email_reply_trimmer (~> 0.1)
email_spec (~> 1.6.0)
factory_girl_rails (~> 4.7.0)
ffaker (~> 2.0.0)
@ -982,7 +982,7 @@ DEPENDENCIES
unicorn-worker-killer (~> 0.4.4)
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)

View file

@ -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.

View file

@ -89,6 +89,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')) {

View file

@ -73,7 +73,7 @@ $(() => {
});
gl.IssueBoardsSearch = new Vue({
el: '#js-boards-seach',
el: '#js-boards-search',
data: {
filters: Store.state.filters
},

View file

@ -71,3 +71,5 @@ class ListIssue {
return Vue.http.patch(url, data);
}
}
window.ListIssue = ListIssue;

View file

@ -10,3 +10,5 @@ class ListLabel {
this.priority = (obj.priority !== null) ? obj.priority : Infinity;
}
}
window.ListLabel = ListLabel;

View file

@ -148,3 +148,5 @@ class List {
});
}
}
window.List = List;

View file

@ -6,3 +6,5 @@ class ListMilestone {
this.title = obj.title;
}
}
window.ListMilestone = ListMilestone;

View file

@ -8,3 +8,5 @@ class ListUser {
this.avatar = user.avatar_url;
}
}
window.ListUser = ListUser;

View file

@ -65,4 +65,6 @@ class BoardService {
issue
});
}
};
}
window.BoardService = BoardService;

View file

@ -92,8 +92,8 @@
success: function(buildData) {
$('.js-build-output').html(buildData.trace_html);
if (removeRefreshStatuses.indexOf(buildData.status) >= 0) {
this.initScrollMonitor();
return this.$buildRefreshAnimation.remove();
this.$buildRefreshAnimation.remove();
return this.initScrollMonitor();
}
}.bind(this)
});

View file

@ -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();
});
}
},

View file

@ -92,3 +92,5 @@ class DiscussionModel {
return false;
}
}
window.DiscussionModel = DiscussionModel;

View file

@ -9,3 +9,5 @@ class NoteModel {
this.resolved_by = resolved_by;
}
}
window.NoteModel = NoteModel;

View file

@ -64,6 +64,17 @@
new UsernameValidator();
new ActiveTabMemoizer();
break;
case 'sessions:create':
if (!gon.u2f) break;
window.gl.u2fAuthenticate = new gl.U2FAuthenticate(
$("#js-authenticate-u2f"),
'#js-login-u2f-form',
gon.u2f,
document.querySelector('#js-login-2fa-device'),
document.querySelector('.js-2fa-form'),
);
window.gl.u2fAuthenticate.start();
break;
case 'projects:boards:show':
case 'projects:boards:index':
shortcut_handler = new ShortcutsNavigation();

View file

@ -20,3 +20,5 @@ class EnvironmentsService {
return this.environments.get();
}
}
window.EnvironmentsService = EnvironmentsService;

View file

@ -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");
@ -367,7 +367,7 @@
return $input.trigger('keyup');
},
isLoading(data) {
if (!data) return false;
if (!data || !data.length) return false;
if (Array.isArray(data)) data = data[0];
return data === this.defaultLoadingData[0] || data.name === this.defaultLoadingData[0];
},

View file

@ -139,15 +139,12 @@
return;
}
return $.getJSON($container.data('path')).error(function() {
$container.find('.checking').hide();
$container.find('.unavailable').show();
return new Flash('Failed to check if a new branch can be created.', 'alert');
}).success(function(data) {
if (data.can_create_branch) {
$container.find('.checking').hide();
$container.find('.available').show();
} else {
$container.find('.checking').hide();
return $container.find('.unavailable').show();
}
});

View file

@ -1,14 +1,17 @@
/* 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);
}());

View file

@ -41,15 +41,12 @@
}
beforeUpdateUsername() {
$('.loading-username').show();
$(this).find('.update-success').hide();
return $(this).find('.update-failed').hide();
$('.loading-username', this).removeClass('hidden');
}
afterUpdateUsername() {
$('.loading-username').hide();
$(this).find('.btn-save').enable();
return $(this).find('.loading-gif').hide();
$('.loading-username', this).addClass('hidden');
$('button[type=submit]', this).enable();
}
onUpdateNotifs(e, data) {

View file

@ -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) {

View file

@ -76,3 +76,5 @@ class ProtectedBranchDropdown {
this.$dropdownFooter.toggleClass('hidden', !branchName);
}
}
window.ProtectedBranchDropdown = ProtectedBranchDropdown;

View file

@ -8,21 +8,26 @@
// State Flow #1: setup -> in_progress -> authenticated -> POST to server
// State Flow #2: setup -> in_progress -> error -> setup
(function() {
const global = window.gl || (window.gl = {});
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
this.U2FAuthenticate = (function() {
function U2FAuthenticate(container, u2fParams) {
global.U2FAuthenticate = (function() {
function U2FAuthenticate(container, form, u2fParams, fallbackButton, fallbackUI) {
this.container = container;
this.renderNotSupported = bind(this.renderNotSupported, this);
this.renderAuthenticated = bind(this.renderAuthenticated, this);
this.renderError = bind(this.renderError, this);
this.renderInProgress = bind(this.renderInProgress, this);
this.renderSetup = bind(this.renderSetup, this);
this.renderTemplate = bind(this.renderTemplate, this);
this.authenticate = bind(this.authenticate, this);
this.start = bind(this.start, this);
this.appId = u2fParams.app_id;
this.challenge = u2fParams.challenge;
this.form = form;
this.fallbackButton = fallbackButton;
this.fallbackUI = fallbackUI;
if (this.fallbackButton) this.fallbackButton.addEventListener('click', this.switchToFallbackUI.bind(this));
this.signRequests = u2fParams.sign_requests.map(function(request) {
// The U2F Javascript API v1.1 requires a single challenge, with
// _no challenges per-request_. The U2F Javascript API v1.0 requires a
@ -41,7 +46,7 @@
U2FAuthenticate.prototype.start = function() {
if (U2FUtil.isU2FSupported()) {
return this.renderSetup();
return this.renderInProgress();
} else {
return this.renderNotSupported();
}
@ -77,11 +82,6 @@
return this.container.html(template(params));
};
U2FAuthenticate.prototype.renderSetup = function() {
this.renderTemplate('setup');
return this.container.find('#js-login-u2f-device').on('click', this.renderInProgress);
};
U2FAuthenticate.prototype.renderInProgress = function() {
this.renderTemplate('inProgress');
return this.authenticate();
@ -92,22 +92,29 @@
error_message: error.message(),
error_code: error.errorCode
});
return this.container.find('#js-u2f-try-again').on('click', this.renderSetup);
return this.container.find('#js-u2f-try-again').on('click', this.renderInProgress);
};
U2FAuthenticate.prototype.renderAuthenticated = function(deviceResponse) {
this.renderTemplate('authenticated');
// Prefer to do this instead of interpolating using Underscore templates
// because of JSON escaping issues.
return this.container.find("#js-device-response").val(deviceResponse);
const container = this.container[0];
container.querySelector('#js-device-response').value = deviceResponse;
container.querySelector(this.form).submit();
this.fallbackButton.classList.add('hidden');
};
U2FAuthenticate.prototype.renderNotSupported = function() {
return this.renderTemplate('notSupported');
};
U2FAuthenticate.prototype.switchToFallbackUI = function() {
this.fallbackButton.classList.add('hidden');
this.container[0].classList.add('hidden');
this.fallbackUI.classList.remove('hidden');
};
return U2FAuthenticate;
})();
}).call(this);
})();

View file

@ -26,6 +26,7 @@
.append-bottom-default { margin-bottom: $gl-padding; }
.inline { display: inline-block; }
.center { text-align: center; }
.vertical-align-middle { vertical-align: middle; }
.underlined-link { text-decoration: underline; }
.hint { font-style: italic; color: $hint-color; }
@ -57,16 +58,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 **/

View file

@ -80,29 +80,32 @@
}
}
.about-gitlab {
color: $color-light;
}
}
}
}
$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;
@ -120,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 {

View file

@ -199,6 +199,7 @@ ul.content-list {
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
align-items: center;
white-space: nowrap;
}
@ -208,6 +209,11 @@ ul.content-list {
padding-right: 8px;
}
.row-fixed-content {
flex: 0 0 auto;
margin-left: auto;
}
.row-title {
font-weight: 600;
}
@ -239,7 +245,6 @@ ul.content-list {
}
ul.controls {
padding-top: 1px;
float: right;
list-style: none;

View file

@ -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;
}
}
}
}
@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%;
}
}
}
}

View file

@ -101,6 +101,17 @@
padding: 0 8px;
border-radius: 6px;
}
.about-gitlab {
padding: 7px $gl-sidebar-padding;
font-size: $gl-font-size;
line-height: 24px;
display: block;
text-decoration: none;
font-weight: normal;
position: absolute;
bottom: 10px;
}
}
.sidebar-action-buttons {

View file

@ -91,7 +91,7 @@
// Labels
.label {
padding: 4px 5px;
font-size: 13px;
font-size: 12px;
font-style: normal;
font-weight: normal;
display: inline-block;

View file

@ -489,9 +489,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

View file

@ -31,7 +31,7 @@
.dropdown-content {
max-height: 150px;
}
}
}
.issue-board-dropdown-content {
@ -109,6 +109,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);

View file

@ -0,0 +1,13 @@
.deploy-keys-list {
width: 100%;
overflow: auto;
table {
border: 1px solid $table-border-color;
}
}
.deploy-keys-title {
padding-bottom: 2px;
line-height: 2;
}

View file

@ -1,50 +1,3 @@
// 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 {

View file

@ -98,7 +98,7 @@
}
.label {
padding: 8px 9px 9px $gl-padding;
padding: 8px 9px 9px;
font-size: 14px;
}
}

View file

@ -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 {

View file

@ -424,11 +424,19 @@
.merge-request-tabs-holder {
background-color: $white-light;
.container-limited {
max-width: $limited-layout-width;
}
&.affix {
top: 100px;
left: 0;
z-index: 10;
transition: right .15s;
@media (max-width: $screen-xs-max) {
right: 0;
}
}
&:not(.affix) .container-fluid {

View file

@ -109,7 +109,7 @@
margin: auto;
margin-top: 0;
text-align: center;
font-size: 13px;
font-size: 12px;
@media (max-width: $screen-sm-max) {
// On smaller devices the warning becomes the fourth item in the list,

View file

@ -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;
}
}

View file

@ -635,8 +635,8 @@
.grouped-pipeline-dropdown {
padding: 0;
width: 191px;
min-width: 191px;
width: 195px;
min-width: 195px;
left: auto;
right: -195px;
top: -4px;

View file

@ -22,8 +22,8 @@
background: $theme-graphite;
}
&.ui_gray {
background: $theme-gray;
&.ui_black {
background: $theme-black;
}
&.ui_green {

View file

@ -627,6 +627,12 @@ pre.light-well {
}
}
.commits-search-form {
.input-short {
min-width: 200px;
}
}
.project-last-commit {
@media (min-width: $screen-sm-min) {
margin-top: $gl-padding;

View file

@ -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

View file

@ -1,17 +1,18 @@
class Admin::GroupsController < Admin::ApplicationController
before_action :group, only: [:edit, :show, :update, :destroy, :project_update, :members_update]
before_action :group, only: [:edit, :update, :destroy, :project_update, :members_update]
def index
@groups = Group.all
@groups = Group.with_statistics
@groups = @groups.sort(@sort = params[:sort])
@groups = @groups.search(params[:name]) if params[:name].present?
@groups = @groups.page(params[:page])
end
def show
@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.page(params[:projects_page])
@projects = @group.projects.with_statistics.page(params[:projects_page])
end
def new

View file

@ -3,7 +3,7 @@ class Admin::ProjectsController < Admin::ApplicationController
before_action :group, only: [:show, :transfer]
def index
@projects = Project.all
@projects = Project.with_statistics
@projects = @projects.in_namespace(params[:namespace_id]) if params[:namespace_id].present?
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
@projects = @projects.with_push if params[:with_push].present?

View file

@ -16,9 +16,6 @@ class Admin::UsersController < Admin::ApplicationController
@joined_projects = user.projects.joined(@user)
end
def groups
end
def keys
@keys = user.keys
end

View file

@ -82,7 +82,7 @@ module CreatesCommit
return @merge_request if defined?(@merge_request)
@merge_request = MergeRequestsFinder.new(current_user, project_id: @mr_target_project.id).execute.opened.
find_by(source_branch: @mr_source_branch, target_branch: @mr_target_branch)
find_by(source_branch: @mr_source_branch, target_branch: @mr_target_branch, source_project_id: @mr_source_project)
end
def different_project?

View file

@ -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

View file

@ -42,6 +42,8 @@ class GroupsController < Groups::ApplicationController
@notification_setting = current_user.notification_settings_for(group)
end
@nested_groups = group.children
setup_projects
respond_to do |format|
@ -75,7 +77,7 @@ class GroupsController < Groups::ApplicationController
end
def projects
@projects = @group.projects.page(params[:page])
@projects = @group.projects.with_statistics.page(params[:page])
end
def update

View file

@ -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
@ -53,6 +53,6 @@ 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
end

View file

@ -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

View file

@ -38,6 +38,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] }

View file

@ -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

View file

@ -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

View file

@ -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
@ -171,48 +173,27 @@ module ProjectsHelper
nav_tabs << :merge_requests
end
if can?(current_user, :read_pipeline, project)
nav_tabs << :pipelines
end
if can?(current_user, :read_build, project)
nav_tabs << :builds
end
if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project)
nav_tabs << :container_registry
end
if can?(current_user, :read_environment, project)
nav_tabs << :environments
end
tab_ability_map = {
environments: :read_environment,
milestones: :read_milestone,
pipelines: :read_pipeline,
snippets: :read_project_snippet,
settings: :admin_project,
builds: :read_build,
labels: :read_label,
issues: :read_issue,
team: :read_project_member,
wiki: :read_wiki
}
if can?(current_user, :admin_project, project)
nav_tabs << :settings
end
if can?(current_user, :read_project_member, project)
nav_tabs << :team
end
if can?(current_user, :read_issue, project)
nav_tabs << :issues
end
if can?(current_user, :read_wiki, project)
nav_tabs << :wiki
end
if can?(current_user, :read_project_snippet, project)
nav_tabs << :snippets
end
if can?(current_user, :read_label, project)
nav_tabs << :labels
end
if can?(current_user, :read_milestone, project)
nav_tabs << :milestones
tab_ability_map.each do |tab, ability|
if can?(current_user, ability, project)
nav_tabs << tab
end
end
nav_tabs.flatten
@ -246,11 +227,6 @@ module ProjectsHelper
end
end
def repository_size(project = @project)
size_in_bytes = project.repository_size * 1.megabyte
number_to_human_size(size_in_bytes, delimiter: ',', precision: 2)
end
def default_url_to_repo(project = @project)
case default_clone_protocol
when 'ssh'
@ -398,20 +374,6 @@ module ProjectsHelper
[@project.path_with_namespace, sha, "readme"].join('-')
end
def round_commit_count(project)
count = project.commit_count
if count > 10000
'10000+'
elsif count > 5000
'5000+'
elsif count > 1000
'1000+'
else
count
end
end
def current_ref
@ref || @repository.try(:root_ref)
end

View file

@ -11,6 +11,7 @@ module SortingHelper
sort_value_due_date_soon => sort_title_due_date_soon,
sort_value_due_date_later => sort_title_due_date_later,
sort_value_largest_repo => sort_title_largest_repo,
sort_value_largest_group => sort_title_largest_group,
sort_value_recently_signin => sort_title_recently_signin,
sort_value_oldest_signin => sort_title_oldest_signin,
sort_value_downvotes => sort_title_downvotes,
@ -92,6 +93,10 @@ module SortingHelper
'Largest repository'
end
def sort_title_largest_group
'Largest group'
end
def sort_title_recently_signin
'Recent sign in'
end
@ -193,7 +198,11 @@ module SortingHelper
end
def sort_value_largest_repo
'repository_size_desc'
'storage_size_desc'
end
def sort_value_largest_group
'storage_size_desc'
end
def sort_value_recently_signin

View file

@ -0,0 +1,7 @@
module StorageHelper
def storage_counter(size_in_bytes)
precision = size_in_bytes < 1.megabyte ? 0 : 1
number_to_human_size(size_in_bytes, delimiter: ',', precision: precision, significant: false)
end
end

View file

@ -43,6 +43,8 @@ module Ci
before_destroy { project }
after_create :execute_hooks
after_save :update_project_statistics, if: :artifacts_size_changed?
after_destroy :update_project_statistics
class << self
def first_pending
@ -584,5 +586,9 @@ module Ci
Ci::MaskSecret.mask!(trace, token)
trace
end
def update_project_statistics
ProjectCacheWorker.perform_async(project_id, [], [:build_artifacts_size])
end
end
end

View file

@ -93,11 +93,8 @@ module Ci
.select("max(#{quoted_table_name}.id)")
.group(:ref, :sha)
if ref
where(id: max_id, ref: ref)
else
where(id: max_id)
end
relation = ref ? where(ref: ref) : self
relation.where(id: max_id)
end
def self.latest_status(ref = nil)
@ -105,7 +102,7 @@ module Ci
end
def self.latest_successful_for(ref)
success.latest(ref).first
success.latest(ref).order(id: :desc).first
end
def self.truncate_sha(sha)

View file

@ -92,8 +92,9 @@ module Issuable
after_save :record_metrics
def update_assignee_cache_counts
# make sure we flush the cache for both the old *and* new assignee
User.find(assignee_id_was).update_cache_counts if assignee_id_was
# make sure we flush the cache for both the old *and* new assignees(if they exist)
previous_assignee = User.find_by_id(assignee_id_was) if assignee_id_was
previous_assignee.update_cache_counts if previous_assignee
assignee.update_cache_counts if assignee
end

View file

@ -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

View file

@ -48,7 +48,13 @@ class Group < Namespace
end
def sort(method)
order_by(method)
if method == 'storage_size_desc'
# storage_size is a virtual column so we need to
# pass a string to avoid AR adding the table name
reorder('storage_size DESC, namespaces.id DESC')
else
order_by(method)
end
end
def reference_prefix
@ -155,15 +161,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
@ -189,6 +197,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

View file

@ -57,10 +57,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
@ -86,4 +82,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

View file

@ -5,4 +5,13 @@ class LfsObjectsProject < ActiveRecord::Base
validates :lfs_object_id, presence: true
validates :lfs_object_id, uniqueness: { scope: [:project_id], message: "already exists in project" }
validates :project_id, presence: true
after_create :update_project_statistics
after_destroy :update_project_statistics
private
def update_project_statistics
ProjectCacheWorker.perform_async(project_id, [], [:lfs_objects_size])
end
end

View file

@ -198,7 +198,9 @@ class MergeRequest < ActiveRecord::Base
end
def diff_size
diffs(diff_options).size
opts = diff_options || {}
raw_diffs(opts).size
end
def diff_base_commit
@ -574,11 +576,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

View file

@ -9,6 +9,7 @@ class Namespace < ActiveRecord::Base
cache_markdown_field :description, pipeline: :description
has_many :projects, dependent: :destroy
has_many :project_statistics
belongs_to :owner, class_name: "User"
belongs_to :parent, class_name: "Namespace"
@ -38,6 +39,18 @@ class Namespace < ActiveRecord::Base
scope :root, -> { where('type IS NULL') }
scope :with_statistics, -> do
joins('LEFT JOIN project_statistics ps ON ps.namespace_id = namespaces.id')
.group('namespaces.id')
.select(
'namespaces.*',
'COALESCE(SUM(ps.storage_size), 0) AS storage_size',
'COALESCE(SUM(ps.repository_size), 0) AS repository_size',
'COALESCE(SUM(ps.lfs_objects_size), 0) AS lfs_objects_size',
'COALESCE(SUM(ps.build_artifacts_size), 0) AS build_artifacts_size',
)
end
class << self
def by_path(path)
find_by('lower(path) = :value', value: path.downcase)

View file

@ -44,6 +44,7 @@ class Project < ActiveRecord::Base
after_create :ensure_dir_exist
after_create :create_project_feature, unless: :project_feature
after_save :ensure_dir_exist, if: :namespace_id_changed?
after_save :update_project_statistics, if: :namespace_id_changed?
# set last_activity_at to the same as created_at
after_create :set_last_activity_at
@ -151,6 +152,7 @@ class Project < ActiveRecord::Base
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
has_one :project_feature, dependent: :destroy
has_one :statistics, class_name: 'ProjectStatistics', dependent: :delete
has_many :commit_statuses, dependent: :destroy, foreign_key: :gl_project_id
has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id
@ -220,6 +222,7 @@ class Project < ActiveRecord::Base
scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) }
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
scope :with_statistics, -> { includes(:statistics) }
# "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled, ->(feature) {
@ -332,8 +335,10 @@ class Project < ActiveRecord::Base
end
def sort(method)
if method == 'repository_size_desc'
reorder(repository_size: :desc, id: :desc)
if method == 'storage_size_desc'
# storage_size is a joined column so we need to
# pass a string to avoid AR adding the table name
reorder('project_statistics.storage_size DESC, projects.id DESC')
else
order_by(method)
end
@ -921,7 +926,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)
@ -948,7 +953,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}"
@ -1036,14 +1041,6 @@ class Project < ActiveRecord::Base
forked? && project == forked_from_project
end
def update_repository_size
update_attribute(:repository_size, repository.size)
end
def update_commit_count
update_attribute(:commit_count, repository.commit_count)
end
def forks_count
forks.count
end
@ -1322,4 +1319,9 @@ class Project < ActiveRecord::Base
def full_path_changed?
path_changed? || namespace_id_changed?
end
def update_project_statistics
stats = statistics || build_statistics
stats.update(namespace_id: namespace_id)
end
end

View file

@ -0,0 +1,43 @@
class ProjectStatistics < ActiveRecord::Base
belongs_to :project
belongs_to :namespace
before_save :update_storage_size
STORAGE_COLUMNS = [:repository_size, :lfs_objects_size, :build_artifacts_size]
STATISTICS_COLUMNS = [:commit_count] + STORAGE_COLUMNS
def total_repository_size
repository_size + lfs_objects_size
end
def refresh!(only: nil)
STATISTICS_COLUMNS.each do |column, generator|
if only.blank? || only.include?(column)
public_send("update_#{column}")
end
end
save!
end
def update_commit_count
self.commit_count = project.repository.commit_count
end
def update_repository_size
self.repository_size = project.repository.size
end
def update_lfs_objects_size
self.lfs_objects_size = project.lfs_objects.sum(:size)
end
def update_build_artifacts_size
self.build_artifacts_size = project.builds.sum(:artifacts_size)
end
def update_storage_size
self.storage_size = STORAGE_COLUMNS.sum(&method(:read_attribute))
end
end

View file

@ -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)

View file

@ -171,9 +171,7 @@ class ProjectPolicy < BasePolicy
def disabled_features!
repository_enabled = project.feature_available?(:repository, user)
unless project.feature_available?(:issues, user)
cannot!(*named_abilities(:issue))
end
block_issues_abilities
unless project.feature_available?(:merge_requests, user) && repository_enabled
cannot!(*named_abilities(:merge_request))
@ -245,11 +243,20 @@ 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
def block_issues_abilities
unless project.feature_available?(:issues, user)
cannot! :read_issue if project.default_issues_tracker?
cannot! :create_issue
cannot! :update_issue
cannot! :admin_issue
end
end
def named_abilities(name)
[
:"read_#{name}",

View file

@ -77,7 +77,7 @@ class GitPushService < BaseService
types = []
end
ProjectCacheWorker.perform_async(@project.id, types)
ProjectCacheWorker.perform_async(@project.id, types, [:commit_count, :repository_size])
end
# Schedules processing of commit messages.

View file

@ -12,7 +12,7 @@ class GitTagPushService < BaseService
project.execute_hooks(@push_data.dup, :tag_push_hooks)
project.execute_services(@push_data.dup, :tag_push_hooks)
Ci::CreatePipelineService.new(project, current_user, @push_data).execute
ProjectCacheWorker.perform_async(project.id)
ProjectCacheWorker.perform_async(project.id, [], [:commit_count, :repository_size])
true
end

View file

@ -12,6 +12,13 @@ module Groups
return @group
end
if @group.parent && !can?(current_user, :admin_group, @group.parent)
@group.parent = nil
@group.errors.add(:parent_id, 'manage access required to create subgroup')
return @group
end
@group.name ||= @group.path.dup
@group.save
@group.add_owner(current_user)

View file

@ -36,14 +36,10 @@ class IssuableBaseService < BaseService
end
end
def filter_params(issuable_ability_name = :issue)
filter_assignee
filter_milestone
filter_labels
def filter_params(issuable)
ability_name = :"admin_#{issuable.to_ability_name}"
ability = :"admin_#{issuable_ability_name}"
unless can?(current_user, ability, project)
unless can?(current_user, ability_name, project)
params.delete(:milestone_id)
params.delete(:labels)
params.delete(:add_label_ids)
@ -52,14 +48,35 @@ class IssuableBaseService < BaseService
params.delete(:assignee_id)
params.delete(:due_date)
end
filter_assignee(issuable)
filter_milestone
filter_labels
end
def filter_assignee
if params[:assignee_id] == IssuableFinder::NONE
params[:assignee_id] = ''
def filter_assignee(issuable)
return unless params[:assignee_id].present?
assignee_id = params[:assignee_id]
if assignee_id.to_s == IssuableFinder::NONE
params[:assignee_id] = ""
else
params.delete(:assignee_id) unless assignee_can_read?(issuable, assignee_id)
end
end
def assignee_can_read?(issuable, assignee_id)
new_assignee = User.find_by_id(assignee_id)
return false unless new_assignee.present?
ability_name = :"read_#{issuable.to_ability_name}"
resource = issuable.persisted? ? issuable : project
can?(new_assignee, ability_name, resource)
end
def filter_milestone
milestone_id = params[:milestone_id]
return unless milestone_id
@ -138,7 +155,7 @@ class IssuableBaseService < BaseService
def create(issuable)
merge_slash_commands_into_params!(issuable)
filter_params
filter_params(issuable)
params.delete(:state_event)
params[:author] ||= current_user
@ -180,7 +197,7 @@ class IssuableBaseService < BaseService
change_state(issuable)
change_subscription(issuable)
change_todo(issuable)
filter_params
filter_params(issuable)
old_labels = issuable.labels.to_a
old_mentioned_users = issuable.mentioned_users.to_a

View file

@ -17,10 +17,6 @@ module Issues
private
def filter_params
super(:issue)
end
def execute_hooks(issue, action = 'open')
issue_data = hook_data(issue, action)
hooks_scope = issue.confidential? ? :confidential_issue_hooks : :issue_hooks

View file

@ -38,10 +38,6 @@ module MergeRequests
private
def filter_params
super(:merge_request)
end
def merge_requests_for(branch)
origin_merge_requests = @project.origin_merge_requests
.opened.where(source_branch: branch).to_a

View file

@ -41,7 +41,7 @@ module Notes
# We must add the error after we call #save because errors are reset
# when #save is called
if only_commands
note.errors.add(:commands_only, 'Your commands have been executed!')
note.errors.add(:commands_only, 'Commands applied')
end
note.commands_changes = command_params.keys

View file

@ -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?

View file

@ -1,7 +1,7 @@
%p
#{link_to @abuse_report.user.name, user_url(@abuse_report.user)}
(@#{@abuse_report.user.username}) was reported for abuse by
#{link_to @abuse_report.reporter.name, user_url(@abuse_report.reporter)}
#{link_to @abuse_report.user.name, user_url(@abuse_report.user)}
(@#{@abuse_report.user.username}) was reported for abuse by
#{link_to @abuse_report.reporter.name, user_url(@abuse_report.reporter)}
(@#{@abuse_report.reporter.username}).
%blockquote

View file

@ -321,7 +321,7 @@
= f.text_field :recaptcha_site_key, class: 'form-control'
.help-block
Generate site and private keys at
%a{ href: 'http://www.google.com/recaptcha', target: 'blank'} http://www.google.com/recaptcha
%a{ href: 'http://www.google.com/recaptcha', target: 'blank' } http://www.google.com/recaptcha
.form-group
= f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2'
@ -342,7 +342,7 @@
= f.text_field :akismet_api_key, class: 'form-control'
.help-block
Generate API key at
%a{ href: 'http://www.akismet.com', target: 'blank'} http://www.akismet.com
%a{ href: 'http://www.akismet.com', target: 'blank' } http://www.akismet.com
%fieldset
%legend Abuse reports

View file

@ -1,4 +1,4 @@
- submit_btn_css ||= 'btn btn-link btn-remove btn-sm'
= form_tag admin_application_path(application) do
%input{:name => "_method", :type => "hidden", :value => "delete"}/
%input{ :name => "_method", :type => "hidden", :value => "delete" }/
= submit_tag 'Destroy', onclick: "return confirm('Are you sure?')", class: submit_btn_css

View file

@ -15,7 +15,7 @@
%th
%tbody.oauth-applications
- @applications.each do |application|
%tr{:id => "application_#{application.id}"}
%tr{ :id => "application_#{application.id}" }
%td= link_to application.name, admin_application_path(application)
%td= application.redirect_uri
%td= application.access_tokens.map(&:resource_owner_id).uniq.count

View file

@ -43,4 +43,4 @@
.panel.panel-default
%iframe{src: sidekiq_path, width: '100%', height: 970, style: "border: none"}
%iframe{ src: sidekiq_path, width: '100%', height: 970, style: "border: none" }

View file

@ -10,7 +10,7 @@
%br.clearfix
-if @broadcast_messages.any?
- if @broadcast_messages.any?
%table.table
%thead
%tr

View file

@ -1,27 +1,34 @@
- 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'

View file

@ -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"

View file

@ -5,6 +5,9 @@
= link_to 'Edit', admin_group_edit_path(group), id: "edit_#{dom_id(group)}", class: 'btn'
= link_to 'Delete', [:admin, group], data: { confirm: "Are you sure you want to remove #{group.name}?" }, method: :delete, class: 'btn btn-remove'
.stats
%span.badge
= storage_counter(group.storage_size)
%span
= icon('bookmark')
= number_with_delimiter(group.projects.count)
@ -13,7 +16,7 @@
= icon('users')
= number_with_delimiter(group.users.count)
%span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)}
%span.visibility-icon.has-tooltip{ data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group) }
= visibility_level_icon(group.visibility_level, fw: false)
.avatar-container.s40

View file

@ -27,6 +27,8 @@
= sort_title_recently_updated
= link_to admin_groups_path(sort: sort_value_oldest_updated, name: project_name) do
= sort_title_oldest_updated
= link_to admin_groups_path(sort: sort_value_largest_group, name: project_name) do
= sort_title_largest_group
= link_to new_admin_group_path, class: "btn btn-new" do
New Group
%ul.content-list

View file

@ -38,6 +38,18 @@
%strong
= @group.created_at.to_s(:medium)
%li
%span.light Storage:
%strong= storage_counter(@group.storage_size)
(
= storage_counter(@group.repository_size)
repositories,
= storage_counter(@group.build_artifacts_size)
build artifacts,
= storage_counter(@group.lfs_objects_size)
LFS
)
%li
%span.light Group Git LFS status:
%strong
@ -55,8 +67,8 @@
%li
%strong
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
%span.label.label-gray
= repository_size(project)
%span.badge
= storage_counter(project.statistics.storage_size)
%span.pull-right.light
%span.monospace= project.path_with_namespace + ".git"
.panel-footer
@ -73,8 +85,8 @@
%li
%strong
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
%span.label.label-gray
= repository_size(project)
%span.badge
= storage_counter(project.statistics.storage_size)
%span.pull-right.light
%span.monospace= project.path_with_namespace + ".git"
@ -91,7 +103,7 @@
= form_tag admin_group_members_update_path(@group), id: "new_project_member", class: "bulk_import", method: :put do
%div
= users_select_tag(:user_ids, multiple: true, email_user: true, scope: :all)
%div.prepend-top-10
.prepend-top-10
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr
= button_tag 'Add users to group', class: "btn btn-create"

View file

@ -29,7 +29,7 @@
System hook will be triggered on set of events like creating project
or adding ssh key. But you can also enable extra triggers like Push events.
%div.prepend-top-default
.prepend-top-default
= f.check_box :push_events, class: 'pull-left'
.prepend-left-20
= f.label :push_events, class: 'list-label' do
@ -54,7 +54,7 @@
= f.submit "Add System Hook", class: "btn btn-create"
%hr
-if @hooks.any?
- if @hooks.any?
.panel.panel-default
.panel-heading
System hooks (#{@hooks.count})
@ -70,4 +70,3 @@
- if hook.send(trigger)
%span.label.label-gray= trigger.titleize
%span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}

View file

@ -1,4 +1,4 @@
%li{id: dom_id(label)}
%li{ id: dom_id(label) }
.label-row
= render_colored_label(label, tooltip: false)
= markdown_field(label, :description)

View file

@ -69,8 +69,8 @@
.controls
- if project.archived
%span.label.label-warning archived
%span.label.label-gray
= repository_size(project)
%span.badge
= storage_counter(project.statistics.storage_size)
= link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn"
= link_to 'Delete', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-remove"
.title

View file

@ -65,9 +65,16 @@
= @project.repository.path_to_repo
%li
%span.light Size
%strong
= repository_size(@project)
%span.light Storage:
%strong= storage_counter(@project.statistics.storage_size)
(
= storage_counter(@project.statistics.repository_size)
repository,
= storage_counter(@project.statistics.build_artifacts_size)
build artifacts,
= storage_counter(@project.statistics.lfs_objects_size)
LFS
)
%li
%span.light last commit:

View file

@ -1,4 +1,4 @@
%tr{id: dom_id(runner)}
%tr{ id: dom_id(runner) }
%td
- if runner.shared?
%span.label.label-success shared

View file

@ -15,10 +15,8 @@
%ul.nav-links
= nav_link(path: 'users#show') do
= link_to "Account", admin_user_path(@user)
= nav_link(path: 'users#groups') do
= link_to "Groups", groups_admin_user_path(@user)
= nav_link(path: 'users#projects') do
= link_to "Projects", projects_admin_user_path(@user)
= link_to "Groups and projects", projects_admin_user_path(@user)
= nav_link(path: 'users#keys') do
= link_to "SSH keys", keys_admin_user_path(@user)
= nav_link(controller: :identities) do

View file

@ -18,7 +18,7 @@
= link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: 'btn'
- unless user == current_user
.dropdown.inline
%a.dropdown-new.btn.btn-default#project-settings-button{href: '#', data: { toggle: 'dropdown' } }
%a.dropdown-new.btn.btn-default#project-settings-button{ href: '#', data: { toggle: 'dropdown' } }
= icon('cog')
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right

Some files were not shown because too many files have changed in this diff Show more