summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/application.js43
-rw-r--r--app/assets/javascripts/awards_handler.js10
-rw-r--r--app/assets/javascripts/boards/vue_resource_interceptor.js.es69
-rw-r--r--app/assets/javascripts/build.js4
-rw-r--r--app/assets/javascripts/build_variables.js.es66
-rw-r--r--app/assets/javascripts/dispatcher.js5
-rw-r--r--app/assets/javascripts/issuable.js.es6 (renamed from app/assets/javascripts/issuable.js)35
-rw-r--r--app/assets/javascripts/issues-bulk-assignment.js4
-rw-r--r--app/assets/javascripts/notes.js7
-rw-r--r--app/assets/javascripts/sidebar.js41
-rw-r--r--app/assets/javascripts/sidebar.js.es693
-rw-r--r--app/assets/javascripts/snippets_list.js.es611
-rw-r--r--app/assets/javascripts/users/calendar.js16
-rw-r--r--app/assets/stylesheets/framework/animations.scss67
-rw-r--r--app/assets/stylesheets/framework/blocks.scss4
-rw-r--r--app/assets/stylesheets/framework/files.scss22
-rw-r--r--app/assets/stylesheets/framework/filters.scss4
-rw-r--r--app/assets/stylesheets/framework/header.scss4
-rw-r--r--app/assets/stylesheets/framework/mixins.scss10
-rw-r--r--app/assets/stylesheets/framework/nav.scss2
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss18
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/pages/awards.scss7
-rw-r--r--app/assets/stylesheets/pages/builds.scss16
-rw-r--r--app/assets/stylesheets/pages/commits.scss6
-rw-r--r--app/assets/stylesheets/pages/issuable.scss15
-rw-r--r--app/assets/stylesheets/pages/issues.scss11
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss2
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss2
-rw-r--r--app/assets/stylesheets/pages/tree.scss31
-rw-r--r--app/controllers/ci/lints_controller.rb11
-rw-r--r--app/controllers/concerns/issuable_actions.rb33
-rw-r--r--app/controllers/projects/issues_controller.rb26
-rw-r--r--app/controllers/projects/merge_requests_controller.rb31
-rw-r--r--app/controllers/projects/pipelines_controller.rb9
-rw-r--r--app/finders/pipelines_finder.rb32
-rw-r--r--app/helpers/avatars_helper.rb3
-rw-r--r--app/helpers/git_helper.rb4
-rw-r--r--app/helpers/merge_requests_helper.rb10
-rw-r--r--app/helpers/nav_helper.rb19
-rw-r--r--app/helpers/projects_helper.rb24
-rw-r--r--app/helpers/search_helper.rb4
-rw-r--r--app/models/ci/runner.rb2
-rw-r--r--app/models/diff_note.rb18
-rw-r--r--app/models/discussion.rb39
-rw-r--r--app/models/merge_request_diff.rb4
-rw-r--r--app/models/repository.rb24
-rw-r--r--app/services/issuable/bulk_update_service.rb26
-rw-r--r--app/services/issues/bulk_update_service.rb25
-rw-r--r--app/views/help/_shortcuts.html.haml547
-rw-r--r--app/views/layouts/_page.html.haml2
-rw-r--r--app/views/projects/blame/show.html.haml3
-rw-r--r--app/views/projects/builds/_sidebar.html.haml7
-rw-r--r--app/views/projects/issues/_issue.html.haml4
-rw-r--r--app/views/projects/issues/_issues.html.haml2
-rw-r--r--app/views/projects/issues/_new_branch.html.haml2
-rw-r--r--app/views/projects/issues/index.html.haml2
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml4
-rw-r--r--app/views/projects/merge_requests/_merge_requests.html.haml2
-rw-r--r--app/views/projects/merge_requests/_show.html.haml2
-rw-r--r--app/views/projects/merge_requests/index.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_how_to_merge.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_versions.html.haml69
-rw-r--r--app/views/projects/refs/logs_tree.js.haml4
-rw-r--r--app/views/projects/tree/_blob_item.html.haml6
-rw-r--r--app/views/projects/tree/_tree_content.html.haml14
-rw-r--r--app/views/projects/tree/_tree_item.html.haml6
-rw-r--r--app/views/projects/triggers/index.html.haml130
-rw-r--r--app/views/shared/issuable/_filter.html.haml17
-rw-r--r--app/views/snippets/_snippets.html.haml16
-rw-r--r--app/workers/prune_old_events_worker.rb17
71 files changed, 955 insertions, 755 deletions
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 43a679501a7..fea6f41d5e9 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -174,9 +174,7 @@
$body.tooltip({
selector: '.has-tooltip, [data-toggle="tooltip"]',
placement: function(_, el) {
- var $el;
- $el = $(el);
- return $el.data('placement') || 'bottom';
+ return $(el).data('placement') || 'bottom';
}
});
$('.trigger-submit').on('change', function() {
@@ -286,42 +284,9 @@
gl.awardsHandler = new AwardsHandler();
checkInitialSidebarSize();
new Aside();
- if ($window.width() < 1024 && $.cookie('pin_nav') === 'true') {
- $.cookie('pin_nav', 'false', {
- path: gon.relative_url_root || '/',
- expires: 365 * 10
- });
- $('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded').removeClass('page-sidebar-pinned');
- $('.navbar-fixed-top').removeClass('header-pinned-nav');
- }
- $document.off('click', '.js-nav-pin').on('click', '.js-nav-pin', function(e) {
- var $page, $pinBtn, $tooltip, $topNav, doPinNav, tooltipText;
- e.preventDefault();
- $pinBtn = $(e.currentTarget);
- $page = $('.page-with-sidebar');
- $topNav = $('.navbar-fixed-top');
- $tooltip = $("#" + ($pinBtn.attr('aria-describedby')));
- doPinNav = !$page.is('.page-sidebar-pinned');
- tooltipText = 'Pin navigation';
- $(this).toggleClass('is-active');
- if (doPinNav) {
- $page.addClass('page-sidebar-pinned');
- $topNav.addClass('header-pinned-nav');
- } else {
- $tooltip.remove();
- $page.removeClass('page-sidebar-pinned').toggleClass('page-sidebar-collapsed page-sidebar-expanded');
- $topNav.removeClass('header-pinned-nav').toggleClass('header-collapsed header-expanded');
- }
- $.cookie('pin_nav', doPinNav, {
- path: gon.relative_url_root || '/',
- expires: 365 * 10
- });
- if ($.cookie('pin_nav') === 'true' || doPinNav) {
- tooltipText = 'Unpin navigation';
- }
- $tooltip.find('.tooltip-inner').text(tooltipText);
- return $pinBtn.attr('title', tooltipText).tooltip('fixTitle');
- });
+
+ // bind sidebar events
+ new gl.Sidebar();
// Custom time ago
gl.utils.shortTimeAgo($('.js-short-timeago'));
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index ad12cb906e1..5ea18ea8b7a 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -255,12 +255,12 @@
};
AwardsHandler.prototype.animateEmoji = function($emoji) {
- var className;
- className = 'pulse animated';
+ var className = 'pulse animated once short';
$emoji.addClass(className);
- return setTimeout((function() {
- return $emoji.removeClass(className);
- }), 321);
+
+ $emoji.on('webkitAnimationEnd animationEnd', function() {
+ $(this).removeClass(className);
+ });
};
AwardsHandler.prototype.createEmoji = function(votesBlock, emoji) {
diff --git a/app/assets/javascripts/boards/vue_resource_interceptor.js.es6 b/app/assets/javascripts/boards/vue_resource_interceptor.js.es6
index f9f9f7999d4..b5ff3a81ed5 100644
--- a/app/assets/javascripts/boards/vue_resource_interceptor.js.es6
+++ b/app/assets/javascripts/boards/vue_resource_interceptor.js.es6
@@ -1,10 +1,7 @@
-Vue.http.interceptors.push((request, next) => {
+Vue.http.interceptors.push((request, next) => {
Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
- Vue.nextTick(() => {
- setTimeout(() => {
- Vue.activeResources--;
- }, 500);
+ next(function (response) {
+ Vue.activeResources--;
});
- next();
});
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index 0d7d29bb0d0..4d066f13646 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -54,12 +54,14 @@
}
Build.prototype.getInitialBuildTrace = function() {
+ var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped']
+
return $.ajax({
url: this.build_url,
dataType: 'json',
success: function(build_data) {
$('.js-build-output').html(build_data.trace_html);
- if (build_data.status === 'success' || build_data.status === 'failed') {
+ if (removeRefreshStatuses.indexOf(build_data.status) >= 0) {
return $('.js-build-refresh').remove();
}
}
diff --git a/app/assets/javascripts/build_variables.js.es6 b/app/assets/javascripts/build_variables.js.es6
new file mode 100644
index 00000000000..8d3e29794a1
--- /dev/null
+++ b/app/assets/javascripts/build_variables.js.es6
@@ -0,0 +1,6 @@
+$(function(){
+ $('.reveal-variables').off('click').on('click',function(){
+ $('.js-build').toggle().niceScroll();
+ $(this).hide();
+ });
+});
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 38cdc7b9fba..179d3bc38a5 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -23,6 +23,7 @@
case 'projects:boards:show':
shortcut_handler = new ShortcutsNavigation();
break;
+ case 'projects:merge_requests:index':
case 'projects:issues:index':
Issuable.init();
new IssuableBulkActions();
@@ -93,10 +94,6 @@
break;
case "projects:merge_requests:conflicts":
window.mcui = new MergeConflictResolver()
- case 'projects:merge_requests:index':
- shortcut_handler = new ShortcutsNavigation();
- Issuable.init();
- break;
case 'dashboard:activity':
new Activities();
break;
diff --git a/app/assets/javascripts/issuable.js b/app/assets/javascripts/issuable.js.es6
index d0305c6c6a1..82c14ef0157 100644
--- a/app/assets/javascripts/issuable.js
+++ b/app/assets/javascripts/issuable.js.es6
@@ -8,6 +8,7 @@
Issuable.initTemplates();
Issuable.initSearch();
Issuable.initChecks();
+ Issuable.initResetFilters();
return Issuable.initLabelFilterRemove();
},
initTemplates: function() {
@@ -55,6 +56,17 @@
return Turbolinks.visit(issuesUrl);
};
})(this),
+ initResetFilters: function() {
+ $('.reset-filters').on('click', function(e) {
+ e.preventDefault();
+ const target = e.target;
+ const $form = $(target).parents('.js-filter-form');
+ const baseIssuesUrl = target.href;
+
+ $form.attr('action', baseIssuesUrl);
+ Turbolinks.visit(baseIssuesUrl);
+ });
+ },
initChecks: function() {
this.issuableBulkActions = $('.bulk-update').data('bulkActions');
$('.check_all_issues').off('click').on('click', function() {
@@ -64,19 +76,22 @@
return $('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(this));
},
checkChanged: function() {
- var checked_issues, ids;
- checked_issues = $('.selected_issue:checked');
- if (checked_issues.length > 0) {
- ids = $.map(checked_issues, function(value) {
+ const $checkedIssues = $('.selected_issue:checked');
+ const $updateIssuesIds = $('#update_issuable_ids');
+ const $issuesOtherFilters = $('.issues-other-filters');
+ const $issuesBulkUpdate = $('.issues_bulk_update');
+
+ if ($checkedIssues.length > 0) {
+ let ids = $.map($checkedIssues, function(value) {
return $(value).data('id');
});
- $('#update_issues_ids').val(ids);
- $('.issues-other-filters').hide();
- $('.issues_bulk_update').show();
+ $updateIssuesIds.val(ids);
+ $issuesOtherFilters.hide();
+ $issuesBulkUpdate.show();
} else {
- $('#update_issues_ids').val([]);
- $('.issues_bulk_update').hide();
- $('.issues-other-filters').show();
+ $updateIssuesIds.val([]);
+ $issuesBulkUpdate.hide();
+ $issuesOtherFilters.show();
this.issuableBulkActions.willUpdateLabels = false;
}
return true;
diff --git a/app/assets/javascripts/issues-bulk-assignment.js b/app/assets/javascripts/issues-bulk-assignment.js
index 98d3358ba92..8ca90490426 100644
--- a/app/assets/javascripts/issues-bulk-assignment.js
+++ b/app/assets/javascripts/issues-bulk-assignment.js
@@ -5,7 +5,7 @@
if (opts == null) {
opts = {};
}
- this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issues-list .issue');
+ this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issuable-list > li');
this.form.data('bulkActions', this);
this.willUpdateLabels = false;
this.bindEvents();
@@ -106,7 +106,7 @@
state_event: this.form.find('input[name="update[state_event]"]').val(),
assignee_id: this.form.find('input[name="update[assignee_id]"]').val(),
milestone_id: this.form.find('input[name="update[milestone_id]"]').val(),
- issues_ids: this.form.find('input[name="update[issues_ids]"]').val(),
+ issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(),
subscription_event: this.form.find('input[name="update[subscription_event]"]').val(),
add_label_ids: [],
remove_label_ids: []
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index d0d5cad813a..ebe9ff2b8c4 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -331,7 +331,12 @@
form.find(".js-md-write-button").click();
form.find(".js-note-text").val("").trigger("input");
form.find(".js-note-text").data("autosave").reset();
- return this.updateTargetButtons(e);
+
+ var event = document.createEvent('Event');
+ event.initEvent('autosize:update', true, false);
+ form.find('.js-autosize')[0].dispatchEvent(event);
+
+ this.updateTargetButtons(e);
};
Notes.prototype.reenableTargetFormSubmitButton = function() {
diff --git a/app/assets/javascripts/sidebar.js b/app/assets/javascripts/sidebar.js
deleted file mode 100644
index bd0c1194b36..00000000000
--- a/app/assets/javascripts/sidebar.js
+++ /dev/null
@@ -1,41 +0,0 @@
-(function() {
- var collapsed, expanded, toggleSidebar;
-
- collapsed = 'page-sidebar-collapsed';
-
- expanded = 'page-sidebar-expanded';
-
- toggleSidebar = function() {
- $('.page-with-sidebar').toggleClass(collapsed + " " + expanded);
- $('.navbar-fixed-top').toggleClass("header-collapsed header-expanded");
- if ($.cookie('pin_nav') === 'true') {
- $('.navbar-fixed-top').toggleClass('header-pinned-nav');
- $('.page-with-sidebar').toggleClass('page-sidebar-pinned');
- }
- return setTimeout((function() {
- var niceScrollBars;
- niceScrollBars = $('.nav-sidebar').niceScroll();
- return niceScrollBars.updateScrollBar();
- }), 300);
- };
-
- $(document).off('click', 'body').on('click', 'body', function(e) {
- var $nav, $target, $toggle, pageExpanded;
- if ($.cookie('pin_nav') !== 'true') {
- $target = $(e.target);
- $nav = $target.closest('.sidebar-wrapper');
- pageExpanded = $('.page-with-sidebar').hasClass('page-sidebar-expanded');
- $toggle = $target.closest('.toggle-nav-collapse, .side-nav-toggle');
- if ($nav.length === 0 && pageExpanded && $toggle.length === 0) {
- $('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded');
- return $('.navbar-fixed-top').toggleClass('header-collapsed header-expanded');
- }
- }
- });
-
- $(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', function(e) {
- e.preventDefault();
- return toggleSidebar();
- });
-
-}).call(this);
diff --git a/app/assets/javascripts/sidebar.js.es6 b/app/assets/javascripts/sidebar.js.es6
new file mode 100644
index 00000000000..755fac8107b
--- /dev/null
+++ b/app/assets/javascripts/sidebar.js.es6
@@ -0,0 +1,93 @@
+((global) => {
+ let singleton;
+
+ const pinnedStateCookie = 'pin_nav';
+ const sidebarBreakpoint = 1024;
+
+ const pageSelector = '.page-with-sidebar';
+ const navbarSelector = '.navbar-fixed-top';
+ const sidebarWrapperSelector = '.sidebar-wrapper';
+ const sidebarContentSelector = '.nav-sidebar';
+
+ const pinnedToggleSelector = '.js-nav-pin';
+ const sidebarToggleSelector = '.toggle-nav-collapse, .side-nav-toggle';
+
+ const pinnedPageClass = 'page-sidebar-pinned';
+ const expandedPageClass = 'page-sidebar-expanded';
+
+ const pinnedNavbarClass = 'header-sidebar-pinned';
+ const expandedNavbarClass = 'header-sidebar-expanded';
+
+ class Sidebar {
+ constructor() {
+ if (!singleton) {
+ singleton = this;
+ singleton.init();
+ }
+ return singleton;
+ }
+
+ init() {
+ this.isPinned = $.cookie(pinnedStateCookie) === 'true';
+ this.isExpanded = (
+ window.innerWidth >= sidebarBreakpoint &&
+ $(pageSelector).hasClass(expandedPageClass)
+ );
+ $(document)
+ .on('click', sidebarToggleSelector, () => this.toggleSidebar())
+ .on('click', pinnedToggleSelector, () => this.togglePinnedState())
+ .on('click', 'html, body', (e) => this.handleClickEvent(e))
+ .on('page:change', () => this.renderState());
+ this.renderState();
+ }
+
+ handleClickEvent(e) {
+ if (this.isExpanded && (!this.isPinned || window.innerWidth < sidebarBreakpoint)) {
+ const $target = $(e.target);
+ const targetIsToggle = $target.closest(sidebarToggleSelector).length > 0;
+ const targetIsSidebar = $target.closest(sidebarWrapperSelector).length > 0;
+ if (!targetIsToggle && (!targetIsSidebar || $target.closest('a'))) {
+ this.toggleSidebar();
+ }
+ }
+ }
+
+ toggleSidebar() {
+ this.isExpanded = !this.isExpanded;
+ this.renderState();
+ }
+
+ togglePinnedState() {
+ this.isPinned = !this.isPinned;
+ if (!this.isPinned) {
+ this.isExpanded = false;
+ }
+ $.cookie(pinnedStateCookie, this.isPinned ? 'true' : 'false', {
+ path: gon.relative_url_root || '/',
+ expires: 3650
+ });
+ this.renderState();
+ }
+
+ renderState() {
+ $(pageSelector)
+ .toggleClass(pinnedPageClass, this.isPinned && this.isExpanded)
+ .toggleClass(expandedPageClass, this.isExpanded);
+ $(navbarSelector)
+ .toggleClass(pinnedNavbarClass, this.isPinned && this.isExpanded)
+ .toggleClass(expandedNavbarClass, this.isExpanded);
+
+ const $pinnedToggle = $(pinnedToggleSelector);
+ const tooltipText = this.isPinned ? 'Unpin navigation' : 'Pin navigation';
+ const tooltipState = $pinnedToggle.attr('aria-describedby') && this.isExpanded ? 'show' : 'hide';
+ $pinnedToggle.attr('title', tooltipText).tooltip('fixTitle').tooltip(tooltipState);
+
+ if (this.isExpanded) {
+ setTimeout(() => $(sidebarContentSelector).niceScroll().updateScrollBar(), 200);
+ }
+ }
+ }
+
+ global.Sidebar = Sidebar;
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/snippets_list.js.es6 b/app/assets/javascripts/snippets_list.js.es6
new file mode 100644
index 00000000000..6f0996c0d2a
--- /dev/null
+++ b/app/assets/javascripts/snippets_list.js.es6
@@ -0,0 +1,11 @@
+(global => {
+ global.gl = global.gl || {};
+
+ gl.SnippetsList = function() {
+ var $holder = $('.snippets-list-holder');
+
+ $holder.find('.pagination').on('ajax:success', (e, data) => {
+ $holder.replaceWith(data.html);
+ });
+ }
+})(window);
diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js
index 74ecf4f4cf9..90cf551b32e 100644
--- a/app/assets/javascripts/users/calendar.js
+++ b/app/assets/javascripts/users/calendar.js
@@ -52,8 +52,22 @@
this.initTooltips();
}
+ // Add extra padding for the last month label if it is also the last column
+ Calendar.prototype.getExtraWidthPadding = function(group) {
+ var extraWidthPadding = 0;
+ var lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth();
+ var secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth();
+
+ if (lastColMonth != secondLastColMonth) {
+ extraWidthPadding = 3;
+ }
+
+ return extraWidthPadding;
+ }
+
Calendar.prototype.renderSvg = function(group) {
- return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', (group + 1) * this.daySizeWithSpace).attr('height', 167).attr('class', 'contrib-calendar');
+ var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group);
+ return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', width).attr('height', 167).attr('class', 'contrib-calendar');
};
Calendar.prototype.renderDays = function() {
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 1fec61bdba1..1e9a45c19b8 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -8,65 +8,44 @@
// Copyright (c) 2016 Daniel Eden
.animated {
- -webkit-animation-duration: 1s;
- animation-duration: 1s;
- -webkit-animation-fill-mode: both;
- animation-fill-mode: both;
-}
-
-.animated.infinite {
- -webkit-animation-iteration-count: infinite;
- animation-iteration-count: infinite;
-}
+ @include webkit-prefix(animation-duration, 1s);
+ @include webkit-prefix(animation-fill-mode, both);
-.animated.hinge {
- -webkit-animation-duration: 2s;
- animation-duration: 2s;
-}
+ &.infinite {
+ @include webkit-prefix(animation-iteration-count, infinite);
+ }
-.animated.flipOutX,
-.animated.flipOutY,
-.animated.bounceIn,
-.animated.bounceOut {
- -webkit-animation-duration: .75s;
- animation-duration: .75s;
-}
+ &.once {
+ @include webkit-prefix(animation-iteration-count, 1);
+ }
-@-webkit-keyframes pulse {
- from {
- -webkit-transform: scale3d(1, 1, 1);
- transform: scale3d(1, 1, 1);
+ &.hinge {
+ @include webkit-prefix(animation-duration, 2s);
}
- 50% {
- -webkit-transform: scale3d(1.05, 1.05, 1.05);
- transform: scale3d(1.05, 1.05, 1.05);
+ &.flipOutX,
+ &.flipOutY,
+ &.bounceIn,
+ &.bounceOut {
+ @include webkit-prefix(animation-duration, .75s);
}
- to {
- -webkit-transform: scale3d(1, 1, 1);
- transform: scale3d(1, 1, 1);
+ &.short {
+ @include webkit-prefix(animation-duration, 321ms);
+ @include webkit-prefix(animation-fill-mode, none);
}
}
-@keyframes pulse {
- from {
- -webkit-transform: scale3d(1, 1, 1);
- transform: scale3d(1, 1, 1);
+@include keyframes(pulse) {
+ from, to {
+ @include webkit-prefix(transform, scale3d(1, 1, 1));
}
50% {
- -webkit-transform: scale3d(1.05, 1.05, 1.05);
- transform: scale3d(1.05, 1.05, 1.05);
- }
-
- to {
- -webkit-transform: scale3d(1, 1, 1);
- transform: scale3d(1, 1, 1);
+ @include webkit-prefix(transform, scale3d(1.05, 1.05, 1.05));
}
}
.pulse {
- -webkit-animation-name: pulse;
- animation-name: pulse;
+ @include webkit-prefix(animation-name, pulse);
}
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 7ce203d2ec7..f5223207f3a 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -249,6 +249,10 @@
> .controls {
float: right;
}
+
+ .new-branch {
+ margin-top: 3px;
+ }
}
.content-block-small {
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index e3be45ba1dc..76a3c083697 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -63,7 +63,7 @@
&.image_file {
background: #eee;
text-align: center;
-
+
img {
padding: 20px;
max-width: 80%;
@@ -94,7 +94,6 @@
&.blame {
table {
border: none;
- box-shadow: none;
margin: 0;
}
tr {
@@ -108,19 +107,10 @@
border-right: none;
}
}
- img.avatar {
- border: 0 none;
- float: none;
- margin: 0;
- padding: 0;
- }
td.blame-commit {
+ padding: 0 10px;
+ min-width: 400px;
background: $gray-light;
- min-width: 350px;
-
- .commit-author-link {
- color: #888;
- }
}
td.line-numbers {
float: none;
@@ -133,12 +123,6 @@
}
td.lines {
padding: 0;
- code {
- font-family: $monospace_font;
- }
- pre {
- margin: 0;
- }
}
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 9209347f9bc..19827943385 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -1,6 +1,10 @@
.filter-item {
margin-right: 6px;
vertical-align: top;
+
+ &.reset-filters {
+ padding: 7px;
+ }
}
@media (min-width: $screen-sm-min) {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 1036219172e..d4a030f7f7a 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -77,10 +77,6 @@ header {
}
}
- &.header-collapsed {
- padding: 0 16px;
- }
-
.side-nav-toggle {
position: absolute;
left: -10px;
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 00f92cef9a4..1ec08cdef23 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -85,3 +85,13 @@
#{'-webkit-' + $property}: $value;
#{$property}: $value;
}
+
+@mixin keyframes($animation-name) {
+ @-webkit-keyframes #{$animation-name} {
+ @content;
+ }
+
+ @keyframes #{$animation-name} {
+ @content;
+ }
+}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 9efbaf54e90..553768b2e68 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -138,7 +138,7 @@
}
li a {
- padding: 16px 10px 11px;
+ padding: 16px 15px 11px;
}
/* Small devices (phones, tablets, 768px and lower) */
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 015fe3debf9..3b7de4b57bb 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -1,6 +1,5 @@
.page-with-sidebar {
- padding-top: $header-height;
- padding-bottom: 25px;
+ padding: $header-height 0 25px;
transition: padding $sidebar-transition-duration;
&.page-sidebar-pinned {
@@ -15,6 +14,7 @@
bottom: 0;
left: 0;
height: 100%;
+ width: 0;
overflow: hidden;
transition: width $sidebar-transition-duration;
@include box-shadow(2px 0 16px 0 $black-transparent);
@@ -128,10 +128,8 @@
.fa {
transition: transform .15s;
- }
- &.is-active {
- .fa {
+ .page-sidebar-pinned & {
transform: rotate(90deg);
}
}
@@ -152,14 +150,6 @@
}
}
-.page-sidebar-collapsed {
- padding-left: 0;
-
- .sidebar-wrapper {
- width: 0;
- }
-}
-
.page-sidebar-expanded {
.sidebar-wrapper {
width: $sidebar_width;
@@ -175,7 +165,7 @@
}
}
-header.header-pinned-nav {
+header.header-sidebar-pinned {
@media (min-width: $sidebar-breakpoint) {
padding-left: ($sidebar_width + $gl-padding);
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index e4a3ca3a9ea..9f563a4de35 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -101,6 +101,7 @@ $gl-icon-color: $gl-placeholder-color;
$gl-grayish-blue: #7f8fa4;
$gl-gray: $gl-text-color;
$gl-gray-dark: #313236;
+$gl-gray-light: $gl-placeholder-color;
$gl-header-color: $gl-title-color;
/*
diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss
index 5faedfedd66..9282e0ae03b 100644
--- a/app/assets/stylesheets/pages/awards.scss
+++ b/app/assets/stylesheets/pages/awards.scss
@@ -93,11 +93,8 @@
}
.award-control {
- margin-right: 5px;
- margin-bottom: 5px;
- padding-left: 5px;
- padding-right: 5px;
- line-height: 20px;
+ margin: 3px 5px 3px 0;
+ padding: 6px 5px;
outline: 0;
&:hover,
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 23255f34710..c879074c7fe 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -48,12 +48,6 @@
margin-bottom: 10px;
}
}
-
- .page-sidebar-collapsed {
- .scroll-controls {
- left: 70px;
- }
- }
}
.build-header {
@@ -115,6 +109,16 @@
width: 100%;
}
+ .js-build-variable {
+ color: $code-color;
+ }
+
+ .js-build-value {
+ padding: 2px 4px;
+ color: $black;
+ background-color: $white-light;
+ }
+
.build-sidebar-header {
padding: 0 $gl-padding $gl-padding;
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index b369f5c0805..dc57a837155 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -18,8 +18,7 @@
}
.commit-row-title {
- line-height: 1;
- margin-bottom: 7px;
+ line-height: 1.35;
.notes_count {
float: right;
@@ -43,6 +42,7 @@
border: 1px solid $border-gray-dark;
border-radius: $border-radius-default;
margin-left: 5px;
+ line-height: 1;
&:hover {
background-color: darken($gray-light, 10%);
@@ -136,7 +136,7 @@
.commit-row-info {
color: $gl-gray;
- line-height: 1;
+ line-height: 1.35;
a {
color: $gl-gray;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 46c4a11aa2e..63845e6b9ba 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -404,3 +404,18 @@
margin-bottom: $gl-padding;
}
}
+
+.issuable-list {
+ li {
+ .issue-check {
+ float: left;
+ padding-right: $gl-padding;
+ margin-bottom: 10px;
+ min-width: 15px;
+
+ .selected_issue {
+ vertical-align: text-top;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 910700b0206..7a26b7ad497 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -7,17 +7,6 @@
margin-bottom: 2px;
}
- .issue-check {
- float: left;
- padding-right: 8px;
- margin-bottom: 10px;
- min-width: 15px;
-
- .selected_issue {
- vertical-align: text-top;
- }
- }
-
.issue-labels {
display: inline-block;
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 7fdd79fa8b9..2a44b95de64 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -375,7 +375,7 @@
}
}
-.mr-version-switch {
+.mr-version-controls {
background: $background-color;
padding: $gl-btn-padding;
color: $gl-placeholder-color;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index ee5d9de66d8..2d66ab25da6 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -490,6 +490,6 @@
.ci-status-icon-created {
svg {
- fill: $table-text-gray;
+ fill: $gray-darkest;
}
}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index cdd38442550..1778c069706 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -15,16 +15,6 @@
vertical-align: top;
}
- .last-commit {
- max-width: 506px;
-
- .last-commit-content {
- @include str-truncated;
- width: calc(100% - 140px);
- margin-left: 3px;
- }
- }
-
.tree-table {
margin-bottom: 0;
@@ -36,6 +26,15 @@
line-height: 21px;
}
+ .last-commit {
+ @include str-truncated(60%);
+ }
+
+ .commit-history-link-spacer {
+ margin: 0 10px;
+ color: $table-border-color;
+ }
+
&:hover {
td {
background-color: $row-hover;
@@ -91,11 +90,17 @@
}
}
- .tree_commit {
- color: $gl-gray;
+ .tree-time-ago {
+ min-width: 135px;
+ color: $gl-gray-light;
+ }
+
+ .tree-commit {
+ max-width: 320px;
+ color: $gl-gray-light;
.tree-commit-link {
- color: $gl-gray;
+ color: $gl-gray-light;
&:hover {
text-decoration: underline;
diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb
index a7af3cb8345..e06d12cfce1 100644
--- a/app/controllers/ci/lints_controller.rb
+++ b/app/controllers/ci/lints_controller.rb
@@ -7,19 +7,14 @@ module Ci
def create
@content = params[:content]
+ @error = Ci::GitlabCiYamlProcessor.validation_message(@content)
+ @status = @error.blank?
- if @content.blank?
- @status = false
- @error = "Please provide content of .gitlab-ci.yml"
- else
+ if @error.blank?
@config_processor = Ci::GitlabCiYamlProcessor.new(@content)
@stages = @config_processor.stages
@builds = @config_processor.builds
- @status = true
end
- rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
- @error = e.message
- @status = false
rescue
@error = 'Undefined error'
@status = false
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index f40b62446e5..77b4efffd7f 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -3,6 +3,7 @@ module IssuableActions
included do
before_action :authorize_destroy_issuable!, only: :destroy
+ before_action :authorize_admin_issuable!, only: :bulk_update
end
def destroy
@@ -13,11 +14,41 @@ module IssuableActions
redirect_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class])
end
+ def bulk_update
+ result = Issuable::BulkUpdateService.new(project, current_user, bulk_update_params).execute(resource_name)
+ quantity = result[:count]
+
+ render json: { notice: "#{quantity} #{resource_name.pluralize(quantity)} updated" }
+ end
+
private
def authorize_destroy_issuable!
- unless current_user.can?(:"destroy_#{issuable.to_ability_name}", issuable)
+ unless can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable)
return access_denied!
end
end
+
+ def authorize_admin_issuable!
+ unless can?(current_user, :"admin_#{resource_name}", @project)
+ return access_denied!
+ end
+ end
+
+ def bulk_update_params
+ params.require(:update).permit(
+ :issuable_ids,
+ :assignee_id,
+ :milestone_id,
+ :state_event,
+ :subscription_event,
+ label_ids: [],
+ add_label_ids: [],
+ remove_label_ids: []
+ )
+ end
+
+ def resource_name
+ @resource_name ||= controller_name.singularize
+ end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 72d2d361878..de02e28e384 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -20,9 +20,6 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow modify issue
before_action :authorize_update_issue!, only: [:edit, :update]
- # Allow issues bulk update
- before_action :authorize_admin_issues!, only: [:bulk_update]
-
respond_to :html
def index
@@ -168,16 +165,6 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
- def bulk_update
- result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute
-
- respond_to do |format|
- format.json do
- render json: { notice: "#{result[:count]} issues updated" }
- end
- end
- end
-
protected
def issue
@@ -237,17 +224,4 @@ class Projects::IssuesController < Projects::ApplicationController
:milestone_id, :due_date, :state_event, :task_num, :lock_version, label_ids: []
)
end
-
- def bulk_update_params
- params.require(:update).permit(
- :issues_ids,
- :assignee_id,
- :milestone_id,
- :state_event,
- :subscription_event,
- label_ids: [],
- add_label_ids: [],
- remove_label_ids: []
- )
- end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 4f9ca0097a1..8895cb955bd 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -90,16 +90,27 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.merge_request_diff
end
+ @merge_request_diffs = @merge_request.merge_request_diffs.select_without_diff
+ @comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id }
+
+ if params[:start_sha].present?
+ @start_sha = params[:start_sha]
+ @start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha }
+
+ unless @start_version
+ render_404
+ end
+ end
+
respond_to do |format|
format.html { define_discussion_vars }
format.json do
- unless @merge_request_diff.latest?
- # Disable comments if browsing older version of the diff
- @diff_notes_disabled = true
+ if @start_sha
+ compared_diff_version
+ else
+ original_diff_version
end
- @diffs = @merge_request_diff.diffs(diff_options)
-
render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") }
end
end
@@ -529,4 +540,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController
params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
@merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
end
+
+ def compared_diff_version
+ @diff_notes_disabled = true
+ @diffs = @merge_request_diff.compare_with(@start_sha).diffs(diff_options)
+ end
+
+ def original_diff_version
+ @diff_notes_disabled = !@merge_request_diff.latest?
+ @diffs = @merge_request_diff.diffs(diff_options)
+ end
end
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index b0c72cfe4b4..371cc3787fb 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -7,11 +7,10 @@ class Projects::PipelinesController < Projects::ApplicationController
def index
@scope = params[:scope]
- all_pipelines = project.pipelines
- @pipelines_count = all_pipelines.count
- @running_or_pending_count = all_pipelines.running_or_pending.count
- @pipelines = PipelinesFinder.new(project).execute(all_pipelines, @scope)
- @pipelines = @pipelines.order(id: :desc).page(params[:page]).per(30)
+ @pipelines = PipelinesFinder.new(project).execute(scope: @scope).page(params[:page]).per(30)
+
+ @running_or_pending_count = PipelinesFinder.new(project).execute(scope: 'running').count
+ @pipelines_count = PipelinesFinder.new(project).execute.count
end
def new
diff --git a/app/finders/pipelines_finder.rb b/app/finders/pipelines_finder.rb
index 641fbf838f1..32aea75486d 100644
--- a/app/finders/pipelines_finder.rb
+++ b/app/finders/pipelines_finder.rb
@@ -1,30 +1,34 @@
class PipelinesFinder
- attr_reader :project
+ attr_reader :project, :pipelines
def initialize(project)
@project = project
+ @pipelines = project.pipelines
end
- def execute(pipelines, scope)
- case scope
- when 'running'
- pipelines.running_or_pending
- when 'branches'
- from_ids(pipelines, ids_for_ref(pipelines, branches))
- when 'tags'
- from_ids(pipelines, ids_for_ref(pipelines, tags))
- else
- pipelines
- end
+ def execute(scope: nil)
+ scoped_pipelines =
+ case scope
+ when 'running'
+ pipelines.running_or_pending
+ when 'branches'
+ from_ids(ids_for_ref(branches))
+ when 'tags'
+ from_ids(ids_for_ref(tags))
+ else
+ pipelines
+ end
+
+ scoped_pipelines.order(id: :desc)
end
private
- def ids_for_ref(pipelines, refs)
+ def ids_for_ref(refs)
pipelines.where(ref: refs).group(:ref).select('max(id)')
end
- def from_ids(pipelines, ids)
+ def from_ids(ids)
pipelines.unscoped.where(id: ids)
end
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
index aa8acbe7567..df41473543b 100644
--- a/app/helpers/avatars_helper.rb
+++ b/app/helpers/avatars_helper.rb
@@ -14,7 +14,8 @@ module AvatarsHelper
avatar_icon(options[:user] || options[:user_email], avatar_size),
class: "avatar has-tooltip hidden-xs s#{avatar_size}",
alt: "#{user_name}'s avatar",
- title: user_name
+ title: user_name,
+ data: { container: 'body' }
)
if options[:user]
diff --git a/app/helpers/git_helper.rb b/app/helpers/git_helper.rb
index 09684955233..8ab394384f3 100644
--- a/app/helpers/git_helper.rb
+++ b/app/helpers/git_helper.rb
@@ -2,4 +2,8 @@ module GitHelper
def strip_gpg_signature(text)
text.gsub(/-----BEGIN PGP SIGNATURE-----(.*)-----END PGP SIGNATURE-----/m, "")
end
+
+ def short_sha(text)
+ Commit.truncate_sha(text)
+ end
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index a9e175c3f5c..8abe7865fed 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -100,4 +100,14 @@ module MergeRequestsHelper
def merge_request_button_visibility(merge_request, closed)
return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork?
end
+
+ def merge_request_version_path(project, merge_request, merge_request_diff, start_sha = nil)
+ diffs_namespace_project_merge_request_path(
+ project.namespace, project, merge_request,
+ diff_id: merge_request_diff.id, start_sha: start_sha)
+ end
+
+ def version_index(merge_request_diff)
+ @merge_request_diffs.size - @merge_request_diffs.index(merge_request_diff)
+ end
end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 2b0ff6c0d00..df87fac132d 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -1,21 +1,7 @@
module NavHelper
- def nav_menu_collapsed?
- cookies[:collapsed_nav] == 'true'
- end
-
- def nav_sidebar_class
- if nav_menu_collapsed?
- "sidebar-collapsed"
- else
- "sidebar-expanded"
- end
- end
-
def page_sidebar_class
if pinned_nav?
"page-sidebar-expanded page-sidebar-pinned"
- else
- "page-sidebar-collapsed"
end
end
@@ -26,7 +12,6 @@ module NavHelper
current_path?('merge_requests#builds') ||
current_path?('merge_requests#conflicts') ||
current_path?('merge_requests#pipelines') ||
-
current_path?('issues#show')
if cookies[:collapsed_gutter] == 'true'
"page-gutter right-sidebar-collapsed"
@@ -43,9 +28,7 @@ module NavHelper
class_name << " with-horizontal-nav" if defined?(nav) && nav
if pinned_nav?
- class_name << " header-expanded header-pinned-nav"
- else
- class_name << " header-collapsed"
+ class_name << " header-sidebar-expanded header-sidebar-pinned"
end
class_name
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 4c685b97c03..16a8e52a4ca 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -129,6 +129,19 @@ module ProjectsHelper
current_user.recent_push(project_ids)
end
+ def project_feature_access_select(field)
+ # Don't show option "everyone with access" if project is private
+ options = project_feature_options
+
+ if @project.private?
+ options.delete('Everyone with access')
+ highest_available_option = options.values.max if @project.project_feature.send(field) == ProjectFeature::ENABLED
+ end
+
+ options = options_for_select(options, selected: highest_available_option || @project.project_feature.public_send(field))
+ content_tag(:select, options, name: "project[project_feature_attributes][#{field.to_s}]", id: "project_project_feature_attributes_#{field.to_s}", class: "pull-right form-control", data: { field: field }).html_safe
+ end
+
private
def get_project_nav_tabs(project, current_user)
@@ -422,15 +435,4 @@ module ProjectsHelper
'Everyone with access' => ProjectFeature::ENABLED
}
end
-
- def project_feature_access_select(field)
- # Don't show option "everyone with access" if project is private
- options = project_feature_options
- level = @project.project_feature.public_send(field)
-
- options.delete('Everyone with access') if @project.private? && level != ProjectFeature::ENABLED
-
- options = options_for_select(options, selected: @project.project_feature.public_send(field) || ProjectFeature::ENABLED)
- content_tag(:select, options, name: "project[project_feature_attributes][#{field.to_s}]", id: "project_project_feature_attributes_#{field.to_s}", class: "pull-right form-control", data: { field: field }).html_safe
- end
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 4549c2e5bb6..e523c46e879 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -7,8 +7,10 @@ module SearchHelper
projects_autocomplete(term)
].flatten
+ search_pattern = Regexp.new(Regexp.escape(term), "i")
+
generic_results = project_autocomplete + default_autocomplete + help_autocomplete
- generic_results.select! { |result| result[:label] =~ Regexp.new(term, "i") }
+ generic_results.select! { |result| result[:label] =~ search_pattern }
[
resources_results,
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 49f05f881a2..ed5d4b13b7e 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -2,7 +2,7 @@ module Ci
class Runner < ActiveRecord::Base
extend Ci::Model
- LAST_CONTACT_TIME = 5.minutes.ago
+ LAST_CONTACT_TIME = 2.hours.ago
AVAILABLE_SCOPES = %w[specific shared active paused online]
FORM_EDITABLE = %i[description tag_list active run_untagged locked]
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 4442cefc7e9..559b3075905 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -13,6 +13,11 @@ class DiffNote < Note
validate :positions_complete
validate :verify_supported
+ # Keep this scope in sync with the logic in `#resolvable?`
+ scope :resolvable, -> { user.where(noteable_type: 'MergeRequest') }
+ scope :resolved, -> { resolvable.where.not(resolved_at: nil) }
+ scope :unresolved, -> { resolvable.where(resolved_at: nil) }
+
after_initialize :ensure_original_discussion_id
before_validation :set_original_position, :update_position, on: :create
before_validation :set_line_code, :set_original_discussion_id
@@ -25,6 +30,16 @@ class DiffNote < Note
def build_discussion_id(noteable_type, noteable_id, position)
[super(noteable_type, noteable_id), *position.key].join("-")
end
+
+ # This method must be kept in sync with `#resolve!`
+ def resolve!(current_user)
+ unresolved.update_all(resolved_at: Time.now, resolved_by_id: current_user.id)
+ end
+
+ # This method must be kept in sync with `#unresolve!`
+ def unresolve!
+ resolved.update_all(resolved_at: nil, resolved_by_id: nil)
+ end
end
def new_diff_note?
@@ -73,6 +88,7 @@ class DiffNote < Note
self.position.diff_refs == diff_refs
end
+ # If you update this method remember to also update the scope `resolvable`
def resolvable?
!system? && for_merge_request?
end
@@ -83,6 +99,7 @@ class DiffNote < Note
self.resolved_at.present?
end
+ # If you update this method remember to also update `.resolve!`
def resolve!(current_user)
return unless resolvable?
return if resolved?
@@ -92,6 +109,7 @@ class DiffNote < Note
save!
end
+ # If you update this method remember to also update `.unresolve!`
def unresolve!
return unless resolvable?
return unless resolved?
diff --git a/app/models/discussion.rb b/app/models/discussion.rb
index 9676bc03470..de06c13481a 100644
--- a/app/models/discussion.rb
+++ b/app/models/discussion.rb
@@ -1,7 +1,7 @@
class Discussion
NUMBER_OF_TRUNCATED_DIFF_LINES = 16
- attr_reader :first_note, :last_note, :notes
+ attr_reader :notes
delegate :created_at,
:project,
@@ -36,8 +36,6 @@ class Discussion
end
def initialize(notes)
- @first_note = notes.first
- @last_note = notes.last
@notes = notes
end
@@ -70,17 +68,25 @@ class Discussion
end
def resolvable?
- return @resolvable if defined?(@resolvable)
+ return @resolvable if @resolvable.present?
@resolvable = diff_discussion? && notes.any?(&:resolvable?)
end
def resolved?
- return @resolved if defined?(@resolved)
+ return @resolved if @resolved.present?
@resolved = resolvable? && notes.none?(&:to_be_resolved?)
end
+ def first_note
+ @first_note ||= @notes.first
+ end
+
+ def last_note
+ @last_note ||= @notes.last
+ end
+
def resolved_notes
notes.select(&:resolved?)
end
@@ -100,17 +106,13 @@ class Discussion
def resolve!(current_user)
return unless resolvable?
- notes.each do |note|
- note.resolve!(current_user) if note.resolvable?
- end
+ update { |notes| notes.resolve!(current_user) }
end
def unresolve!
return unless resolvable?
- notes.each do |note|
- note.unresolve! if note.resolvable?
- end
+ update { |notes| notes.unresolve! }
end
def for_target?(target)
@@ -118,7 +120,7 @@ class Discussion
end
def active?
- return @active if defined?(@active)
+ return @active if @active.present?
@active = first_note.active?
end
@@ -174,4 +176,17 @@ class Discussion
prev_lines
end
+
+ private
+
+ def update
+ notes_relation = DiffNote.where(id: notes.map(&:id)).fresh
+ yield(notes_relation)
+
+ # Set the notes array to the updated notes
+ @notes = notes_relation.to_a
+
+ # Reset the memoized values
+ @last_resolved_note = @resolvable = @resolved = @first_note = @last_note = nil
+ end
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 445179a4487..18c583add88 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -152,6 +152,10 @@ class MergeRequestDiff < ActiveRecord::Base
self == merge_request.merge_request_diff
end
+ def compare_with(sha)
+ CompareService.new.execute(project, head_commit_sha, project, sha)
+ end
+
private
def dump_commits(commits)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 414b82516bc..7b7090b8a73 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -757,7 +757,7 @@ class Repository
end
def commit_dir(user, path, message, branch)
- commit_with_hooks(user, branch) do |ref|
+ update_branch_with_hooks(user, branch) do |ref|
committer = user_to_committer(user)
options = {}
options[:committer] = committer
@@ -774,7 +774,7 @@ class Repository
end
def commit_file(user, path, content, message, branch, update)
- commit_with_hooks(user, branch) do |ref|
+ update_branch_with_hooks(user, branch) do |ref|
committer = user_to_committer(user)
options = {}
options[:committer] = committer
@@ -796,7 +796,7 @@ class Repository
end
def update_file(user, path, content, branch:, previous_path:, message:)
- commit_with_hooks(user, branch) do |ref|
+ update_branch_with_hooks(user, branch) do |ref|
committer = user_to_committer(user)
options = {}
options[:committer] = committer
@@ -823,7 +823,7 @@ class Repository
end
def remove_file(user, path, message, branch)
- commit_with_hooks(user, branch) do |ref|
+ update_branch_with_hooks(user, branch) do |ref|
committer = user_to_committer(user)
options = {}
options[:committer] = committer
@@ -871,7 +871,7 @@ class Repository
merge_index = rugged.merge_commits(our_commit, their_commit)
return false if merge_index.conflicts?
- commit_with_hooks(user, merge_request.target_branch) do
+ update_branch_with_hooks(user, merge_request.target_branch) do
actual_options = options.merge(
parents: [our_commit, their_commit],
tree: merge_index.write_tree(rugged),
@@ -889,7 +889,7 @@ class Repository
return false unless revert_tree_id
- commit_with_hooks(user, base_branch) do
+ update_branch_with_hooks(user, base_branch) do
committer = user_to_committer(user)
source_sha = Rugged::Commit.create(rugged,
message: commit.revert_message,
@@ -906,7 +906,7 @@ class Repository
return false unless cherry_pick_tree_id
- commit_with_hooks(user, base_branch) do
+ update_branch_with_hooks(user, base_branch) do
committer = user_to_committer(user)
source_sha = Rugged::Commit.create(rugged,
message: commit.message,
@@ -922,7 +922,7 @@ class Repository
end
def resolve_conflicts(user, branch, params)
- commit_with_hooks(user, branch) do
+ update_branch_with_hooks(user, branch) do
committer = user_to_committer(user)
Rugged::Commit.create(rugged, params.merge(author: committer, committer: committer))
@@ -1026,7 +1026,7 @@ class Repository
Gitlab::Popen.popen(args, path_to_repo)
end
- def commit_with_hooks(current_user, branch)
+ def update_branch_with_hooks(current_user, branch)
update_autocrlf_option
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
@@ -1040,7 +1040,11 @@ class Repository
raise CommitError.new('Failed to create commit')
end
- oldrev = rugged.lookup(newrev).parent_ids.first || Gitlab::Git::BLANK_SHA
+ if rugged.lookup(newrev).parent_ids.empty? || target_branch.nil?
+ oldrev = Gitlab::Git::BLANK_SHA
+ else
+ oldrev = rugged.merge_base(newrev, target_branch.target.sha)
+ end
GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
update_ref!(ref, newrev, oldrev)
diff --git a/app/services/issuable/bulk_update_service.rb b/app/services/issuable/bulk_update_service.rb
new file mode 100644
index 00000000000..60891cbb255
--- /dev/null
+++ b/app/services/issuable/bulk_update_service.rb
@@ -0,0 +1,26 @@
+module Issuable
+ class BulkUpdateService < IssuableBaseService
+ def execute(type)
+ model_class = type.classify.constantize
+ update_class = type.classify.pluralize.constantize::UpdateService
+
+ ids = params.delete(:issuable_ids).split(",")
+ items = model_class.where(id: ids)
+
+ %i(state_event milestone_id assignee_id add_label_ids remove_label_ids subscription_event).each do |key|
+ params.delete(key) unless params[key].present?
+ end
+
+ items.each do |issuable|
+ next unless can?(current_user, :"update_#{type}", issuable)
+
+ update_class.new(issuable.project, current_user, params).execute(issuable)
+ end
+
+ {
+ count: items.count,
+ success: !items.count.zero?
+ }
+ end
+ end
+end
diff --git a/app/services/issues/bulk_update_service.rb b/app/services/issues/bulk_update_service.rb
deleted file mode 100644
index 7e19a73f71a..00000000000
--- a/app/services/issues/bulk_update_service.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-module Issues
- class BulkUpdateService < BaseService
- def execute
- issues_ids = params.delete(:issues_ids).split(",")
- issue_params = params
-
- %i(state_event milestone_id assignee_id add_label_ids remove_label_ids subscription_event).each do |key|
- issue_params.delete(key) unless issue_params[key].present?
- end
-
- issues = Issue.where(id: issues_ids)
-
- issues.each do |issue|
- next unless can?(current_user, :update_issue, issue)
-
- Issues::UpdateService.new(issue.project, current_user, issue_params).execute(issue)
- end
-
- {
- count: issues.count,
- success: !issues.count.zero?
- }
- end
- end
-end
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index ce4536ebdc6..16c16cec137 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -7,277 +7,278 @@
Keyboard Shortcuts
%small
= link_to '(Show all)', '#', class: 'js-more-help-button'
- .modal-body.shortcuts-cheatsheet
- .col-lg-4
- %table.shortcut-mappings
- %tbody
- %tr
- %th
- %th Global Shortcuts
- %tr
- %td.shortcut
- .key s
- %td Focus Search
- %tr
- %td.shortcut
- .key f
- %td Focus Filter
- %tr
- %td.shortcut
- .key ?
- %td Show/hide this dialog
- %tr
- %td.shortcut
- - if browser.platform.mac?
- .key &#8984; shift p
- - else
- .key ctrl shift p
- %td Toggle Markdown preview
- %tr
- %td.shortcut
- .key
- %i.fa.fa-arrow-up
- %td Edit last comment (when focused on an empty textarea)
- %tbody
- %tr
- %th
- %th Project Files browsing
- %tr
- %td.shortcut
- .key
- %i.fa.fa-arrow-up
- %td Move selection up
- %tr
- %td.shortcut
- .key
- %i.fa.fa-arrow-down
- %td Move selection down
- %tr
- %td.shortcut
- .key enter
- %td Open Selection
- %tbody
- %tr
- %th
- %th Finding Project File
- %tr
- %td.shortcut
- .key
- %i.fa.fa-arrow-up
- %td Move selection up
- %tr
- %td.shortcut
- .key
- %i.fa.fa-arrow-down
- %td Move selection down
- %tr
- %td.shortcut
- .key enter
- %td Open Selection
- %tr
- %td.shortcut
- .key esc
- %td Go back
+ .modal-body
+ .row
+ .col-lg-4
+ %table.shortcut-mappings
+ %tbody
+ %tr
+ %th
+ %th Global Shortcuts
+ %tr
+ %td.shortcut
+ .key s
+ %td Focus Search
+ %tr
+ %td.shortcut
+ .key f
+ %td Focus Filter
+ %tr
+ %td.shortcut
+ .key ?
+ %td Show/hide this dialog
+ %tr
+ %td.shortcut
+ - if browser.platform.mac?
+ .key &#8984; shift p
+ - else
+ .key ctrl shift p
+ %td Toggle Markdown preview
+ %tr
+ %td.shortcut
+ .key
+ %i.fa.fa-arrow-up
+ %td Edit last comment (when focused on an empty textarea)
+ %tbody
+ %tr
+ %th
+ %th Project Files browsing
+ %tr
+ %td.shortcut
+ .key
+ %i.fa.fa-arrow-up
+ %td Move selection up
+ %tr
+ %td.shortcut
+ .key
+ %i.fa.fa-arrow-down
+ %td Move selection down
+ %tr
+ %td.shortcut
+ .key enter
+ %td Open Selection
+ %tbody
+ %tr
+ %th
+ %th Finding Project File
+ %tr
+ %td.shortcut
+ .key
+ %i.fa.fa-arrow-up
+ %td Move selection up
+ %tr
+ %td.shortcut
+ .key
+ %i.fa.fa-arrow-down
+ %td Move selection down
+ %tr
+ %td.shortcut
+ .key enter
+ %td Open Selection
+ %tr
+ %td.shortcut
+ .key esc
+ %td Go back
- .col-lg-4
- %table.shortcut-mappings
- %tbody{ class: 'hidden-shortcut project', style: 'display:none' }
- %tr
- %th
- %th Global Dashboard
- %tr
- %td.shortcut
- .key g
- .key a
- %td
- Go to the activity feed
- %tr
- %td.shortcut
- .key g
- .key p
- %td
- Go to projects
- %tr
- %td.shortcut
- .key g
- .key i
- %td
- Go to issues
- %tr
- %td.shortcut
- .key g
- .key m
- %td
- Go to merge requests
- %tbody
- %tr
- %th
- %th Project
- %tr
- %td.shortcut
- .key g
- .key p
- %td
- Go to the project's home page
- %tr
- %td.shortcut
- .key g
- .key e
- %td
- Go to the project's activity feed
- %tr
- %td.shortcut
- .key g
- .key f
- %td
- Go to files
- %tr
- %td.shortcut
- .key g
- .key c
- %td
- Go to commits
- %tr
- %td.shortcut
- .key g
- .key b
- %td
- Go to builds
- %tr
- %td.shortcut
- .key g
- .key n
- %td
- Go to network graph
- %tr
- %td.shortcut
- .key g
- .key g
- %td
- Go to graphs
- %tr
- %td.shortcut
- .key g
- .key i
- %td
- Go to issues
- %tr
- %td.shortcut
- .key g
- .key m
- %td
- Go to merge requests
- %tr
- %td.shortcut
- .key g
- .key s
- %td
- Go to snippets
- %tr
- %td.shortcut
- .key t
- %td Go to finding file
- %tr
- %td.shortcut
- .key i
- %td New issue
- .col-lg-4
- %table.shortcut-mappings
- %tbody{ class: 'hidden-shortcut network', style: 'display:none' }
- %tr
- %th
- %th Network Graph
- %tr
- %td.shortcut
- .key
- %i.fa.fa-arrow-left
- \/
- .key h
- %td Scroll left
- %tr
- %td.shortcut
- .key
- %i.fa.fa-arrow-right
- \/
- .key l
- %td Scroll right
- %tr
- %td.shortcut
- .key
- %i.fa.fa-arrow-up
- \/
- .key k
- %td Scroll up
- %tr
- %td.shortcut
- .key
- %i.fa.fa-arrow-down
- \/
- .key j
- %td Scroll down
- %tr
- %td.shortcut
- .key
- shift
- %i.fa.fa-arrow-up
- \/
- .key
- shift k
- %td Scroll to top
- %tr
- %td.shortcut
- .key
- shift
- %i.fa.fa-arrow-down
- \/
- .key
- shift j
- %td Scroll to bottom
- %tbody{ class: 'hidden-shortcut issues', style: 'display:none' }
- %tr
- %th
- %th Issues
- %tr
- %td.shortcut
- .key a
- %td Change assignee
- %tr
- %td.shortcut
- .key m
- %td Change milestone
- %tr
- %td.shortcut
- .key r
- %td Reply (quoting selected text)
- %tr
- %td.shortcut
- .key e
- %td Edit issue
- %tr
- %td.shortcut
- .key l
- %td Change Label
- %tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' }
- %tr
- %th
- %th Merge Requests
- %tr
- %td.shortcut
- .key a
- %td Change assignee
- %tr
- %td.shortcut
- .key m
- %td Change milestone
- %tr
- %td.shortcut
- .key r
- %td Reply (quoting selected text)
- %tr
- %td.shortcut
- .key e
- %td Edit merge request
- %tr
- %td.shortcut
- .key l
- %td Change Label
+ .col-lg-4
+ %table.shortcut-mappings
+ %tbody{ class: 'hidden-shortcut project', style: 'display:none' }
+ %tr
+ %th
+ %th Global Dashboard
+ %tr
+ %td.shortcut
+ .key g
+ .key a
+ %td
+ Go to the activity feed
+ %tr
+ %td.shortcut
+ .key g
+ .key p
+ %td
+ Go to projects
+ %tr
+ %td.shortcut
+ .key g
+ .key i
+ %td
+ Go to issues
+ %tr
+ %td.shortcut
+ .key g
+ .key m
+ %td
+ Go to merge requests
+ %tbody
+ %tr
+ %th
+ %th Project
+ %tr
+ %td.shortcut
+ .key g
+ .key p
+ %td
+ Go to the project's home page
+ %tr
+ %td.shortcut
+ .key g
+ .key e
+ %td
+ Go to the project's activity feed
+ %tr
+ %td.shortcut
+ .key g
+ .key f
+ %td
+ Go to files
+ %tr
+ %td.shortcut
+ .key g
+ .key c
+ %td
+ Go to commits
+ %tr
+ %td.shortcut
+ .key g
+ .key b
+ %td
+ Go to builds
+ %tr
+ %td.shortcut
+ .key g
+ .key n
+ %td
+ Go to network graph
+ %tr
+ %td.shortcut
+ .key g
+ .key g
+ %td
+ Go to graphs
+ %tr
+ %td.shortcut
+ .key g
+ .key i
+ %td
+ Go to issues
+ %tr
+ %td.shortcut
+ .key g
+ .key m
+ %td
+ Go to merge requests
+ %tr
+ %td.shortcut
+ .key g
+ .key s
+ %td
+ Go to snippets
+ %tr
+ %td.shortcut
+ .key t
+ %td Go to finding file
+ %tr
+ %td.shortcut
+ .key i
+ %td New issue
+ .col-lg-4
+ %table.shortcut-mappings
+ %tbody{ class: 'hidden-shortcut network', style: 'display:none' }
+ %tr
+ %th
+ %th Network Graph
+ %tr
+ %td.shortcut
+ .key
+ %i.fa.fa-arrow-left
+ \/
+ .key h
+ %td Scroll left
+ %tr
+ %td.shortcut
+ .key
+ %i.fa.fa-arrow-right
+ \/
+ .key l
+ %td Scroll right
+ %tr
+ %td.shortcut
+ .key
+ %i.fa.fa-arrow-up
+ \/
+ .key k
+ %td Scroll up
+ %tr
+ %td.shortcut
+ .key
+ %i.fa.fa-arrow-down
+ \/
+ .key j
+ %td Scroll down
+ %tr
+ %td.shortcut
+ .key
+ shift
+ %i.fa.fa-arrow-up
+ \/
+ .key
+ shift k
+ %td Scroll to top
+ %tr
+ %td.shortcut
+ .key
+ shift
+ %i.fa.fa-arrow-down
+ \/
+ .key
+ shift j
+ %td Scroll to bottom
+ %tbody{ class: 'hidden-shortcut issues', style: 'display:none' }
+ %tr
+ %th
+ %th Issues
+ %tr
+ %td.shortcut
+ .key a
+ %td Change assignee
+ %tr
+ %td.shortcut
+ .key m
+ %td Change milestone
+ %tr
+ %td.shortcut
+ .key r
+ %td Reply (quoting selected text)
+ %tr
+ %td.shortcut
+ .key e
+ %td Edit issue
+ %tr
+ %td.shortcut
+ .key l
+ %td Change Label
+ %tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' }
+ %tr
+ %th
+ %th Merge Requests
+ %tr
+ %td.shortcut
+ .key a
+ %td Change assignee
+ %tr
+ %td.shortcut
+ .key m
+ %td Change milestone
+ %tr
+ %td.shortcut
+ .key r
+ %td Reply (quoting selected text)
+ %tr
+ %td.shortcut
+ .key e
+ %td Edit merge request
+ %tr
+ %td.shortcut
+ .key l
+ %td Change Label
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index bf50633af24..4f7839a881f 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,5 +1,5 @@
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
- .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
+ .sidebar-wrapper.nicescroll
.sidebar-action-buttons
= link_to '#', class: 'nav-header-btn toggle-nav-collapse', title: "Open/Close" do
%span.sr-only Toggle navigation
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 377665b096f..5a98e258b22 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -11,7 +11,7 @@
%small= number_to_human_size @blob.size
.file-actions
= render "projects/blob/actions"
- .file-content.blame.code.js-syntax-highlight
+ .table-responsive.file-content.blame.code.js-syntax-highlight
%table
- current_line = 1
- @blame_groups.each do |blame_group|
@@ -19,6 +19,7 @@
%td.blame-commit
.commit
- commit = blame_group[:commit]
+ = author_avatar(commit, size: 36)
.commit-row-title
%strong
= link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark"
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index 5ce36a475a9..56306b05934 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -90,12 +90,13 @@
- if @build.trigger_request.variables
%p
- %span.build-light-text Variables:
+ %button.btn.group.btn-group-justified.reveal-variables Reveal Variables
- @build.trigger_request.variables.each do |key, value|
- %code
- #{key}=#{value}
+ .hide.js-build
+ .js-build-variable= key
+ .js-build-value= value
.block
.title
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 79b14819865..851d4c06990 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -1,7 +1,7 @@
%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue), data: { labels: issue.label_ids, id: issue.id } }
- - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project)
+ - if @bulk_edit
.issue-check
- = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
+ = check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
.issue-title.title
%span.issue-title-text
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index f34f3c05737..a2c31c0b4c5 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -1,4 +1,4 @@
-%ul.content-list.issues-list
+%ul.content-list.issues-list.issuable-list
= render @issues
- if @issues.blank?
%li
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index 33556a1a2b3..c56b6cc11f5 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -1,6 +1,6 @@
- if can?(current_user, :push_code, @project)
.pull-right
- #new-branch{'data-path' => can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue)}
+ #new-branch.new-branch{'data-path' => can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue)}
= link_to '#', class: 'checking btn btn-grouped', disabled: 'disabled' do
= icon('spinner spin')
Checking branches
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 1a87045aa60..8da9f2100e9 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -1,4 +1,6 @@
- @no_container = true
+- @bulk_edit = can?(current_user, :admin_issue, @project)
+
- page_title "Issues"
- new_issue_email = @project.new_issue_address(current_user)
= render "projects/issues/head"
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 5029b365f93..31f8d0aeb5b 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -1,4 +1,8 @@
%li{ class: mr_css_classes(merge_request) }
+ - if @bulk_edit
+ .issue-check
+ = check_box_tag dom_id(merge_request, "selected"), nil, false, 'data-id' => merge_request.id, class: "selected_issue"
+
.merge-request-title.title
%span.merge-request-title-text
= link_to merge_request.title, merge_request_path(merge_request)
diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml
index 446887774a4..fe82f751f53 100644
--- a/app/views/projects/merge_requests/_merge_requests.html.haml
+++ b/app/views/projects/merge_requests/_merge_requests.html.haml
@@ -1,4 +1,4 @@
-%ul.content-list.mr-list
+%ul.content-list.mr-list.issuable-list
= render @merge_requests
- if @merge_requests.blank?
%li
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 9d8b4cc56be..4b4d418e8ec 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -83,7 +83,7 @@
.tab-content#diff-notes-app
#notes.notes.tab-pane.voting_notes
- .content-block.content-block-small.oneline-block
+ .content-block.content-block-small
= render 'award_emoji/awards_block', awardable: @merge_request, inline: true
.row
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index ace275c689b..144b3a9c8c8 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,4 +1,6 @@
- @no_container = true
+- @bulk_edit = can?(current_user, :admin_merge_request, @project)
+
- page_title "Merge Requests"
= render "projects/issues/head"
= render 'projects/last_push'
diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
index 571e4dd1b1a..f1d5441f9dd 100644
--- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
@@ -12,7 +12,7 @@
%pre.dark#merge-info-1
- if @merge_request.for_fork?
:preserve
- git fetch #{h @merge_request.source_project.http_url_to_repo} #{h @merge_request.source_branch}
+ git fetch #{h default_url_to_repo(@merge_request.source_project)} #{h @merge_request.source_branch}
git checkout -b #{h @merge_request.source_project_path}-#{h @merge_request.source_branch} FETCH_HEAD
- else
:preserve
diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml
index 2da70ce7137..00287f2d245 100644
--- a/app/views/projects/merge_requests/show/_versions.html.haml
+++ b/app/views/projects/merge_requests/show/_versions.html.haml
@@ -1,31 +1,60 @@
-- merge_request_diffs = @merge_request.merge_request_diffs.select_without_diff
-
-- if merge_request_diffs.size > 1
- .mr-version-switch
- Version:
- %span.dropdown.inline
+- if @merge_request_diffs.size > 1
+ .mr-version-controls
+ Changes between
+ %span.dropdown.inline.mr-version-dropdown
%a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} }
- %strong.monospace<
+ %strong
- if @merge_request_diff.latest?
- #{"latest"}
+ latest version
- else
- #{@merge_request_diff.head_commit.short_id}
+ version #{version_index(@merge_request_diff)}
%span.caret
%ul.dropdown-menu.dropdown-menu-selectable
- - merge_request_diffs.each do |merge_request_diff|
+ - @merge_request_diffs.each do |merge_request_diff|
%li
- = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, diff_id: merge_request_diff.id), class: ('is-active' if merge_request_diff == @merge_request_diff) do
- %strong.monospace
- #{merge_request_diff.head_commit.short_id}
- %br
+ = link_to merge_request_version_path(@project, @merge_request, merge_request_diff), class: ('is-active' if merge_request_diff == @merge_request_diff) do
+ %strong
+ - if merge_request_diff.latest?
+ latest version
+ - else
+ version #{version_index(merge_request_diff)}
+ .monospace #{short_sha(merge_request_diff.head_commit_sha)}
%small
#{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)},
= time_ago_with_tooltip(merge_request_diff.created_at)
- - unless @merge_request_diff.latest?
- %span.prepend-left-default
+ - if @merge_request_diff.base_commit_sha
+ and
+ %span.dropdown.inline.mr-version-compare-dropdown
+ %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} }
+ %strong
+ - if @start_sha
+ version #{version_index(@start_version)}
+ - else
+ #{@merge_request.target_branch}
+ %span.caret
+ %ul.dropdown-menu.dropdown-menu-selectable
+ - @comparable_diffs.each do |merge_request_diff|
+ %li
+ = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff == @start_version) do
+ %strong
+ - if merge_request_diff.latest?
+ latest version
+ - else
+ version #{version_index(merge_request_diff)}
+ .monospace #{short_sha(merge_request_diff.head_commit_sha)}
+ %small
+ = time_ago_with_tooltip(merge_request_diff.created_at)
+ %li
+ = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do
+ %strong
+ #{@merge_request.target_branch} (base)
+ .monospace #{short_sha(@merge_request_diff.base_commit_sha)}
+
+ - unless @merge_request_diff.latest? && !@start_sha
+ .prepend-top-10
= icon('info-circle')
- This version is not the latest one. Comments are disabled
- .pull-right
- %span.monospace
- #{@merge_request_diff.base_commit.short_id}..#{@merge_request_diff.head_commit.short_id}
+ - if @start_sha
+ Comments are disabled because you're comparing two versions of this merge request.
+ - else
+ Comments are disabled because you're viewing an old version of this merge request.
diff --git a/app/views/projects/refs/logs_tree.js.haml b/app/views/projects/refs/logs_tree.js.haml
index 8ee2aef0e61..1141168f037 100644
--- a/app/views/projects/refs/logs_tree.js.haml
+++ b/app/views/projects/refs/logs_tree.js.haml
@@ -5,8 +5,8 @@
:plain
var row = $("table.table_#{@hex_path} tr.file_#{hexdigest(file_name)}");
- row.find("td.tree_time_ago").html('#{escape_javascript time_ago_with_tooltip(commit.committed_date)}');
- row.find("td.tree_commit").html('#{escape_javascript render("projects/tree/tree_commit_column", commit: commit)}');
+ row.find("td.tree-time-ago").html('#{escape_javascript time_ago_with_tooltip(commit.committed_date)}');
+ row.find("td.tree-commit").html('#{escape_javascript render("projects/tree/tree_commit_column", commit: commit)}');
- if @more_log_url
:plain
diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml
index a3a4dba3fa4..ee417b58cbf 100644
--- a/app/views/projects/tree/_blob_item.html.haml
+++ b/app/views/projects/tree/_blob_item.html.haml
@@ -4,6 +4,6 @@
- file_name = blob_item.name
= link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@id || @commit.id, blob_item.name)), title: file_name do
%span.str-truncated= file_name
- %td.tree_time_ago.cgray
- = render 'projects/tree/spinner'
- %td.hidden-xs.tree_commit
+ %td.hidden-xs.tree-commit
+ %td.tree-time-ago.cgray.text-right
+ = render 'projects/tree/spinner' \ No newline at end of file
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index ca5d2d7722a..0f7d629ab98 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -4,20 +4,20 @@
%thead
%tr
%th Name
- %th Last Update
- %th.hidden-xs.last-commit
- Last Commit
- .last-commit-content.hidden-sm
+ %th.hidden-xs
+ .pull-left Last Commit
+ .last-commit.hidden-sm.pull-left
+ &nbsp;
%i.fa.fa-angle-right
&nbsp;
%small.light
= link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
&ndash;
= time_ago_with_tooltip(@commit.committed_date)
- &ndash;
= @commit.full_title
- = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'pull-right'
-
+ %small.commit-history-link-spacer &#124;
+ = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'commit-history-link'
+ %th.text-right Last Update
- if @path.present?
%tr.tree-item
%td.tree-item-file-name
diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml
index 9577696fc0d..1ccef6d52ab 100644
--- a/app/views/projects/tree/_tree_item.html.haml
+++ b/app/views/projects/tree/_tree_item.html.haml
@@ -4,6 +4,6 @@
- path = flatten_tree(tree_item)
= link_to namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path)), title: path do
%span.str-truncated= path
- %td.tree_time_ago.cgray
- = render 'projects/tree/spinner'
- %td.hidden-xs.tree_commit
+ %td.hidden-xs.tree-commit
+ %td.tree-time-ago.text-right
+ = render 'projects/tree/spinner' \ No newline at end of file
diff --git a/app/views/projects/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml
index 7f3de47d7df..f6e0b0a7c8a 100644
--- a/app/views/projects/triggers/index.html.haml
+++ b/app/views/projects/triggers/index.html.haml
@@ -4,65 +4,89 @@
.col-lg-3
%h4.prepend-top-0
= page_title
- %p
- Triggers can force a specific branch or tag to rebuild with an API call.
+ %p.prepend-top-20
+ Triggers can force a specific branch or tag to get rebuilt with an API call.
+ %p.append-bottom-0
+ = succeed '.' do
+ Learn more in the
+ = link_to 'triggers documentation', help_page_path('ci/triggers/README'), target: '_blank'
.col-lg-9
- %h5.prepend-top-0
- Your triggers
- - if @triggers.any?
- .table-responsive
- %table.table
- %thead
- %th Token
- %th Last used
- %th
- = render partial: 'trigger', collection: @triggers, as: :trigger
- - else
- %p.settings-message.text-center.append-bottom-default
- No triggers have been created yet. Add one using the button below.
+ .panel.panel-default
+ .panel-heading
+ %h4.panel-title
+ Manage your project's triggers
+ .panel-body
+ - if @triggers.any?
+ .table-responsive
+ %table.table
+ %thead
+ %th
+ %strong Token
+ %th
+ %strong Last used
+ %th
+ = render partial: 'trigger', collection: @triggers, as: :trigger
+ - else
+ %p.settings-message.text-center.append-bottom-default
+ No triggers have been created yet. Add one using the button below.
- = form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create') do |f|
- = f.submit "Add Trigger", class: 'btn btn-success'
+ = form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create') do |f|
+ = f.submit "Add trigger", class: 'btn btn-success'
- %h5.prepend-top-default
- Use CURL
+ .panel-footer
- %p.light
- Copy the token above, set your branch or tag name, and that reference will be rebuilt.
+ %p
+ In the following examples, you can see the exact API call you need to
+ make in order to rebuild a specific
+ %code ref
+ (branch or tag) with a trigger token.
+ %p
+ All you need to do is replace the
+ %code TOKEN
+ and
+ %code REF_NAME
+ with the trigger token and the branch or tag name respectively.
- %pre
- :plain
- curl -X POST \
- -F token=TOKEN \
- -F ref=REF_NAME \
- #{builds_trigger_url(@project.id)}
- %h5.prepend-top-default
- Use .gitlab-ci.yml
+ %h5.prepend-top-default
+ Use cURL
- %p.light
- In the
- %code .gitlab-ci.yml
- of the dependent project, include the following snippet.
- The project will rebuild at the end of the build.
+ %p.light
+ Copy one of the tokens above, set your branch or tag name, and that
+ reference will be rebuilt.
- %pre
- :plain
- trigger:
- type: deploy
- script:
- - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}"
- %h5.prepend-top-default
- Pass build variables
+ %pre
+ :plain
+ curl -X POST \
+ -F token=TOKEN \
+ -F ref=REF_NAME \
+ #{builds_trigger_url(@project.id)}
+ %h5.prepend-top-default
+ Use .gitlab-ci.yml
- %p.light
- Add
- %code variables[VARIABLE]=VALUE
- to an API request. Variable values can be used to distinguish between triggered builds and normal builds.
+ %p.light
+ In the
+ %code .gitlab-ci.yml
+ of another project, include the following snippet.
+ The project will be rebuilt at the end of the build.
- %pre.append-bottom-0
- :plain
- curl -X POST \
- -F token=TOKEN \
- -F "ref=REF_NAME" \
- -F "variables[RUN_NIGHTLY_BUILD]=true" \
- #{builds_trigger_url(@project.id)}
+ %pre
+ :plain
+ trigger_build:
+ stage: deploy
+ script:
+ - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}"
+ %h5.prepend-top-default
+ Pass build variables
+
+ %p.light
+ Add
+ %code variables[VARIABLE]=VALUE
+ to an API request. Variable values can be used to distinguish between triggered builds and normal builds.
+
+ %pre.append-bottom-0
+ :plain
+ curl -X POST \
+ -F token=TOKEN \
+ -F "ref=REF_NAME" \
+ -F "variables[RUN_NIGHTLY_BUILD]=true" \
+ #{builds_trigger_url(@project.id)}
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 0f4f744a71f..93c4d5c3d30 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -1,9 +1,11 @@
+- boards_page = controller.controller_name == 'boards'
+
.issues-filters
.issues-details-filters.row-content-block.second-block
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :issue_search]), method: :get, class: 'filter-form js-filter-form' do
- if params[:issue_search].present?
= hidden_field_tag :issue_search, params[:issue_search]
- - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project)
+ - if @bulk_edit
.check-all-holder
= check_box_tag "check_all_issues", nil, false,
class: "check_all_issues left"
@@ -26,8 +28,11 @@
.filter-item.inline.labels-filter
= render "shared/issuable/label_dropdown"
+ .filter-item.inline.reset-filters
+ %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :issue_search])} Reset filters
+
.pull-right
- - if controller.controller_name == 'boards'
+ - if boards_page
#js-boards-seach.issue-boards-search
%input.pull-left.form-control{ type: "search", placeholder: "Filter by name...", "v-model" => "filters.search", "debounce" => "250" }
- if can?(current_user, :admin_list, @project)
@@ -42,9 +47,9 @@
- else
= render 'shared/sort_dropdown'
- - if controller.controller_name == 'issues'
+ - if @bulk_edit
.issues_bulk_update.hide
- = form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post, class: 'bulk-update' do
+ = form_tag [:bulk_update, @project.namespace.becomes(Namespace), @project, type], method: :post, class: 'bulk-update' do
.filter-item.inline
= dropdown_tag("Status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]" } } ) do
%ul
@@ -67,10 +72,10 @@
%li
%a{href: "#", data: {id: "unsubscribe"}} Unsubscribe
- = hidden_field_tag 'update[issues_ids]', []
+ = hidden_field_tag 'update[issuable_ids]', []
= hidden_field_tag :state_event, params[:state_event]
.filter-item.inline
- = button_tag "Update issues", class: "btn update_selected_issues btn-save"
+ = button_tag "Update #{type.to_s.humanize(capitalize: false)}", class: "btn update_selected_issues btn-save"
- if !@labels.nil?
.row-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) }
diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml
index 80a3e731e1d..7be4a471579 100644
--- a/app/views/snippets/_snippets.html.haml
+++ b/app/views/snippets/_snippets.html.haml
@@ -1,7 +1,11 @@
-%ul.content-list
- = render partial: 'shared/snippets/snippet', collection: @snippets
- - if @snippets.empty?
- %li
- .nothing-here-block Nothing here.
+.snippets-list-holder
+ %ul.content-list
+ = render partial: 'shared/snippets/snippet', collection: @snippets
+ - if @snippets.empty?
+ %li
+ .nothing-here-block Nothing here.
-= paginate @snippets, theme: 'gitlab'
+ = paginate @snippets, theme: 'gitlab', remote: true
+
+:javascript
+ gl.SnippetsList();
diff --git a/app/workers/prune_old_events_worker.rb b/app/workers/prune_old_events_worker.rb
new file mode 100644
index 00000000000..5883cafe1d1
--- /dev/null
+++ b/app/workers/prune_old_events_worker.rb
@@ -0,0 +1,17 @@
+class PruneOldEventsWorker
+ include Sidekiq::Worker
+
+ def perform
+ # Contribution calendar shows maximum 12 months of events.
+ # Double nested query is used because MySQL doesn't allow DELETE subqueries
+ # on the same table.
+ Event.unscoped.where(
+ '(id IN (SELECT id FROM (?) ids_to_remove))',
+ Event.unscoped.where(
+ 'created_at < ?',
+ (12.months + 1.day).ago).
+ select(:id).
+ limit(10_000)).
+ delete_all
+ end
+end