summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGrzegorz Bizon <grzesiek.bizon@gmail.com>2016-09-03 09:35:21 +0200
committerGrzegorz Bizon <grzesiek.bizon@gmail.com>2016-09-03 09:35:21 +0200
commit2436631dea9264045d8694705a95d90c30b4057d (patch)
tree53c55f8c7ec7d4695d863585fe4dc8aba4527c84 /app
parentb3cd41b4014fa96780218f0f086de239731ec91a (diff)
parent2aaab34b67eb2a6593780eda33d501a715ef0c5f (diff)
downloadgitlab-ce-refactor/ci-config-add-logical-validation.tar.gz
Merge branch 'master' into refactor/ci-config-add-logical-validationrefactor/ci-config-add-logical-validation
* master: (414 commits) Remove suggested colors hover underline Fix markdown anchor icon interaction Fix expiration date picker after update Refactored code to rely less on IDs that could change Move CHANGELOG entry for !5858 from 8.11 to 8.12 Hides merge request section in edit project when disabled Fix a typo Change minimum Unicorns required to two Update memory requirements Added `.term-bold` declaration. Change the inline code to codeblocks for the new features doc guideline Fix GitLab import button Rename behaviour to behavior in bug issue template for consistency Convert datetime coffeescript spec to ES6 Align add button on repository view Update CHANGELOG with 8.11.4 entries. removed null return - renamed 'placeTop' to 'placeProfileAvatarsToTop' Refactor Ci::Build#raw_trace Move CHANGELOG entry to a proper version Change widths of content in MR pipeline tab ... Conflicts: lib/gitlab/ci/config/node/jobs.rb
Diffstat (limited to 'app')
-rw-r--r--app/assets/images/icon-link.pngbin729 -> 0 bytes
-rw-r--r--app/assets/images/icon_anchor.svg1
-rw-r--r--app/assets/javascripts/abuse_reports.js.es63
-rw-r--r--app/assets/javascripts/activities.js4
-rw-r--r--app/assets/javascripts/application.js4
-rw-r--r--app/assets/javascripts/awards_handler.js1
-rw-r--r--app/assets/javascripts/behaviors/toggler_behavior.js30
-rw-r--r--app/assets/javascripts/boards/boards_bundle.js.es67
-rw-r--r--app/assets/javascripts/boards/components/board.js.es615
-rw-r--r--app/assets/javascripts/boards/components/board_list.js.es613
-rw-r--r--app/assets/javascripts/boards/models/list.js.es629
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js.es63
-rw-r--r--app/assets/javascripts/dispatcher.js1
-rw-r--r--app/assets/javascripts/gl_dropdown.js24
-rw-r--r--app/assets/javascripts/issue.js3
-rw-r--r--app/assets/javascripts/labels_select.js2
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js8
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js12
-rw-r--r--app/assets/javascripts/logo.js54
-rw-r--r--app/assets/javascripts/merge_conflict_resolver.js.es64
-rw-r--r--app/assets/javascripts/project.js8
-rw-r--r--app/assets/javascripts/project_new.js20
-rw-r--r--app/assets/javascripts/right_sidebar.js2
-rw-r--r--app/assets/javascripts/todos.js28
-rw-r--r--app/assets/javascripts/user.js31
-rw-r--r--app/assets/javascripts/user.js.es634
-rw-r--r--app/assets/javascripts/users/calendar.js51
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/buttons.scss6
-rw-r--r--app/assets/stylesheets/framework/common.scss6
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss19
-rw-r--r--app/assets/stylesheets/framework/files.scss2
-rw-r--r--app/assets/stylesheets/framework/forms.scss1
-rw-r--r--app/assets/stylesheets/framework/header.scss54
-rw-r--r--app/assets/stylesheets/framework/logo.scss118
-rw-r--r--app/assets/stylesheets/framework/mixins.scss56
-rw-r--r--app/assets/stylesheets/framework/nav.scss30
-rw-r--r--app/assets/stylesheets/framework/selects.scss2
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap_variables.scss2
-rw-r--r--app/assets/stylesheets/framework/typography.scss27
-rw-r--r--app/assets/stylesheets/framework/variables.scss139
-rw-r--r--app/assets/stylesheets/pages/admin.scss4
-rw-r--r--app/assets/stylesheets/pages/boards.scss60
-rw-r--r--app/assets/stylesheets/pages/builds.scss15
-rw-r--r--app/assets/stylesheets/pages/commits.scss6
-rw-r--r--app/assets/stylesheets/pages/environments.scss5
-rw-r--r--app/assets/stylesheets/pages/events.scss9
-rw-r--r--app/assets/stylesheets/pages/import.scss19
-rw-r--r--app/assets/stylesheets/pages/issues.scss8
-rw-r--r--app/assets/stylesheets/pages/labels.scss1
-rw-r--r--app/assets/stylesheets/pages/merge_conflicts.scss2
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss12
-rw-r--r--app/assets/stylesheets/pages/notes.scss16
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss96
-rw-r--r--app/assets/stylesheets/pages/projects.scss35
-rw-r--r--app/assets/stylesheets/pages/search.scss4
-rw-r--r--app/assets/stylesheets/pages/status.scss9
-rw-r--r--app/assets/stylesheets/pages/todos.scss2
-rw-r--r--app/assets/stylesheets/pages/tree.scss14
-rw-r--r--app/assets/stylesheets/pages/xterm.scss3
-rw-r--r--app/controllers/admin/system_info_controller.rb8
-rw-r--r--app/controllers/application_controller.rb14
-rw-r--r--app/controllers/concerns/toggle_award_emoji.rb10
-rw-r--r--app/controllers/import/gitorious_controller.rb47
-rw-r--r--app/controllers/jwt_controller.rb2
-rw-r--r--app/controllers/namespaces_controller.rb2
-rw-r--r--app/controllers/projects/application_controller.rb2
-rw-r--r--app/controllers/projects/artifacts_controller.rb44
-rw-r--r--app/controllers/projects/avatars_controller.rb2
-rw-r--r--app/controllers/projects/boards/issues_controller.rb15
-rw-r--r--app/controllers/projects/builds_controller.rb4
-rw-r--r--app/controllers/projects/discussions_controller.rb2
-rw-r--r--app/controllers/projects/issues_controller.rb4
-rw-r--r--app/controllers/projects/labels_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb16
-rw-r--r--app/controllers/projects/milestones_controller.rb2
-rw-r--r--app/controllers/projects/snippets_controller.rb2
-rw-r--r--app/controllers/projects/tags_controller.rb8
-rw-r--r--app/controllers/projects_controller.rb18
-rw-r--r--app/finders/issuable_finder.rb2
-rw-r--r--app/finders/tags_finder.rb29
-rw-r--r--app/finders/todos_finder.rb2
-rw-r--r--app/helpers/application_helper.rb2
-rw-r--r--app/helpers/ci_status_helper.rb11
-rw-r--r--app/helpers/compare_helper.rb2
-rw-r--r--app/helpers/gitlab_routing_helper.rb16
-rw-r--r--app/helpers/issuables_helper.rb13
-rw-r--r--app/helpers/lfs_helper.rb4
-rw-r--r--app/helpers/merge_requests_helper.rb2
-rw-r--r--app/helpers/nav_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb35
-rw-r--r--app/helpers/search_helper.rb2
-rw-r--r--app/helpers/sentry_helper.rb22
-rw-r--r--app/helpers/tags_helper.rb10
-rw-r--r--app/helpers/todos_helper.rb34
-rw-r--r--app/mailers/base_mailer.rb2
-rw-r--r--app/models/ability.rb587
-rw-r--r--app/models/application_setting.rb2
-rw-r--r--app/models/ci/build.rb29
-rw-r--r--app/models/ci/pipeline.rb8
-rw-r--r--app/models/commit.rb9
-rw-r--r--app/models/commit_range.rb7
-rw-r--r--app/models/commit_status.rb4
-rw-r--r--app/models/concerns/awardable.rb14
-rw-r--r--app/models/concerns/has_status.rb (renamed from app/models/concerns/statuseable.rb)2
-rw-r--r--app/models/concerns/issuable.rb4
-rw-r--r--app/models/concerns/note_on_diff.rb4
-rw-r--r--app/models/concerns/project_features_compatibility.rb37
-rw-r--r--app/models/concerns/taskable.rb4
-rw-r--r--app/models/diff_note.rb4
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/merge_request.rb110
-rw-r--r--app/models/merge_request_diff.rb170
-rw-r--r--app/models/note.rb4
-rw-r--r--app/models/project.rb52
-rw-r--r--app/models/project_feature.rb63
-rw-r--r--app/models/repository.rb19
-rw-r--r--app/models/user.rb8
-rw-r--r--app/policies/base_policy.rb116
-rw-r--r--app/policies/ci/build_policy.rb13
-rw-r--r--app/policies/ci/runner_policy.rb13
-rw-r--r--app/policies/commit_status_policy.rb5
-rw-r--r--app/policies/deployment_policy.rb5
-rw-r--r--app/policies/environment_policy.rb5
-rw-r--r--app/policies/external_issue_policy.rb5
-rw-r--r--app/policies/global_policy.rb8
-rw-r--r--app/policies/group_member_policy.rb19
-rw-r--r--app/policies/group_policy.rb45
-rw-r--r--app/policies/issuable_policy.rb14
-rw-r--r--app/policies/issue_policy.rb28
-rw-r--r--app/policies/merge_request_policy.rb3
-rw-r--r--app/policies/namespace_policy.rb10
-rw-r--r--app/policies/note_policy.rb19
-rw-r--r--app/policies/personal_snippet_policy.rb16
-rw-r--r--app/policies/project_member_policy.rb22
-rw-r--r--app/policies/project_policy.rb224
-rw-r--r--app/policies/project_snippet_policy.rb20
-rw-r--r--app/policies/user_policy.rb11
-rw-r--r--app/services/base_service.rb6
-rw-r--r--app/services/boards/issues/list_service.rb7
-rw-r--r--app/services/boards/lists/create_service.rb9
-rw-r--r--app/services/ci/process_pipeline_service.rb16
-rw-r--r--app/services/ci/register_build_service.rb8
-rw-r--r--app/services/ci/web_hook_service.rb35
-rw-r--r--app/services/issuable_base_service.rb20
-rw-r--r--app/services/merge_requests/build_service.rb2
-rw-r--r--app/services/merge_requests/get_urls_service.rb2
-rw-r--r--app/services/merge_requests/resolve_service.rb21
-rw-r--r--app/services/merge_requests/update_service.rb4
-rw-r--r--app/services/projects/create_service.rb4
-rw-r--r--app/services/projects/fork_service.rb4
-rw-r--r--app/services/system_note_service.rb10
-rw-r--r--app/services/todo_service.rb3
-rw-r--r--app/views/admin/appearances/_form.html.haml2
-rw-r--r--app/views/admin/background_jobs/_head.html.haml46
-rw-r--r--app/views/admin/dashboard/_head.html.haml54
-rw-r--r--app/views/admin/projects/show.html.haml6
-rw-r--r--app/views/admin/system_info/show.html.haml12
-rw-r--r--app/views/ci/lints/show.html.haml3
-rw-r--r--app/views/dashboard/todos/index.html.haml38
-rw-r--r--app/views/events/_event.html.haml2
-rw-r--r--app/views/explore/groups/index.html.haml2
-rw-r--r--app/views/groups/group_members/update.js.haml2
-rw-r--r--app/views/help/ui.html.haml2
-rw-r--r--app/views/import/gitorious/status.html.haml54
-rw-r--r--app/views/layouts/nav/_group.html.haml2
-rw-r--r--app/views/layouts/nav/_group_settings.html.haml38
-rw-r--r--app/views/layouts/nav/_project_settings.html.haml2
-rw-r--r--app/views/projects/_merge_request_settings.html.haml25
-rw-r--r--app/views/projects/boards/components/_board.html.haml14
-rw-r--r--app/views/projects/branches/_branch.html.haml2
-rw-r--r--app/views/projects/builds/_sidebar.html.haml73
-rw-r--r--app/views/projects/buttons/_download.html.haml46
-rw-r--r--app/views/projects/buttons/_fork.html.haml4
-rw-r--r--app/views/projects/ci/builds/_build.html.haml2
-rw-r--r--app/views/projects/ci/builds/_build_pipeline.html.haml5
-rw-r--r--app/views/projects/ci/pipelines/_pipeline.html.haml4
-rw-r--r--app/views/projects/commit/_ci_stage.html.haml4
-rw-r--r--app/views/projects/commit/_pipelines_list.haml5
-rw-r--r--app/views/projects/commits/_commit.html.haml2
-rw-r--r--app/views/projects/commits/_head.html.haml5
-rw-r--r--app/views/projects/deployments/_actions.haml4
-rw-r--r--app/views/projects/edit.html.haml88
-rw-r--r--app/views/projects/forks/index.html.haml4
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml5
-rw-r--r--app/views/projects/graphs/_head.html.haml32
-rw-r--r--app/views/projects/issues/_head.html.haml54
-rw-r--r--app/views/projects/issues/_new_branch.html.haml17
-rw-r--r--app/views/projects/issues/_related_branches.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_diffs.html.haml1
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml4
-rw-r--r--app/views/projects/merge_requests/show/_versions.html.haml31
-rw-r--r--app/views/projects/new.html.haml7
-rw-r--r--app/views/projects/notes/_note.html.haml4
-rw-r--r--app/views/projects/pipelines/_head.html.haml36
-rw-r--r--app/views/projects/pipelines/index.html.haml5
-rw-r--r--app/views/projects/project_members/update.js.haml2
-rw-r--r--app/views/projects/repositories/_download_archive.html.haml37
-rw-r--r--app/views/projects/show.html.haml2
-rw-r--r--app/views/projects/tags/_download.html.haml14
-rw-r--r--app/views/projects/tags/_tag.html.haml3
-rw-r--r--app/views/projects/tags/index.html.haml17
-rw-r--r--app/views/projects/tags/show.html.haml3
-rw-r--r--app/views/projects/tree/_tree_content.html.haml11
-rw-r--r--app/views/projects/tree/show.html.haml3
-rw-r--r--app/views/projects/wikis/_nav.html.haml22
-rw-r--r--app/views/shared/_logo.svg16
-rw-r--r--app/views/shared/_nav_scroll.html.haml4
-rw-r--r--app/views/shared/icons/_icon_play.svg4
-rw-r--r--app/views/shared/icons/_icon_status_created.svg1
-rw-r--r--app/views/shared/issuable/_filter.html.haml21
-rw-r--r--app/views/shared/issuable/_form.html.haml4
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/views/users/show.html.haml2
215 files changed, 2476 insertions, 1908 deletions
diff --git a/app/assets/images/icon-link.png b/app/assets/images/icon-link.png
deleted file mode 100644
index 5b55e12571c..00000000000
--- a/app/assets/images/icon-link.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/icon_anchor.svg b/app/assets/images/icon_anchor.svg
new file mode 100644
index 00000000000..7e242586bad
--- /dev/null
+++ b/app/assets/images/icon_anchor.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="#333" fill-rule="evenodd" d="M9.683 6.676l-.047-.048C8.27 5.26 6.07 5.243 4.726 6.588l-2.29 2.29c-1.344 1.344-1.328 3.544.04 4.91 1.366 1.368 3.564 1.385 4.908.04l1.753-1.752c-.695.074-1.457-.078-2.176-.444L5.934 12.66c-.634.634-1.67.625-2.312-.017-.642-.643-.65-1.677-.017-2.312L6.035 7.9c.634-.634 1.67-.625 2.312.017.024.024.048.05.07.075l.003-.002c.36.36.943.366 1.3.01.355-.356.35-.938-.01-1.3l-.027-.024zM6.58 9.586l.048.05c1.367 1.366 3.565 1.384 4.91.04l2.29-2.292c1.344-1.343 1.328-3.542-.04-4.91-1.366-1.366-3.564-1.384-4.908-.04L7.127 4.187c.695-.074 1.457.078 2.176.444l1.028-1.027c.635-.634 1.67-.624 2.313.017.643.644.652 1.678.018 2.312l-2.43 2.432c-.635.634-1.67.624-2.313-.018-.024-.024-.048-.05-.07-.075l-.003.004c-.36-.362-.943-.367-1.3-.01-.355.355-.35.937.01 1.3.01.007.018.015.027.023z"/></svg> \ No newline at end of file
diff --git a/app/assets/javascripts/abuse_reports.js.es6 b/app/assets/javascripts/abuse_reports.js.es6
index 748084b0307..2fe46b9fd06 100644
--- a/app/assets/javascripts/abuse_reports.js.es6
+++ b/app/assets/javascripts/abuse_reports.js.es6
@@ -1,4 +1,3 @@
-window.gl = window.gl || {};
((global) => {
const MAX_MESSAGE_LENGTH = 500;
const MESSAGE_CELL_SELECTOR = '.abuse-reports .message';
@@ -36,4 +35,4 @@ window.gl = window.gl || {};
}
global.AbuseReports = AbuseReports;
-})(window.gl);
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js
index 1ab3c2197d8..d5e11e22be5 100644
--- a/app/assets/javascripts/activities.js
+++ b/app/assets/javascripts/activities.js
@@ -12,7 +12,7 @@
}
Activities.prototype.updateTooltips = function() {
- return gl.utils.localTimeAgo($('.js-timeago', '#activity'));
+ return gl.utils.localTimeAgo($('.js-timeago', '.content_list'));
};
Activities.prototype.reloadActivities = function() {
@@ -26,7 +26,7 @@
event_filters = $.cookie("event_filter");
filter = sender.attr("id").split("_")[0];
$.cookie("event_filter", (event_filters !== filter ? filter : ""), {
- path: '/'
+ path: gon.relative_url_root || '/'
});
if (event_filters !== filter) {
return sender.closest('li').toggleClass("active");
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index fc354dfd677..43a679501a7 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -288,7 +288,7 @@
new Aside();
if ($window.width() < 1024 && $.cookie('pin_nav') === 'true') {
$.cookie('pin_nav', 'false', {
- path: '/',
+ path: gon.relative_url_root || '/',
expires: 365 * 10
});
$('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded').removeClass('page-sidebar-pinned');
@@ -313,7 +313,7 @@
$topNav.removeClass('header-pinned-nav').toggleClass('header-collapsed header-expanded');
}
$.cookie('pin_nav', doPinNav, {
- path: '/',
+ path: gon.relative_url_root || '/',
expires: 365 * 10
});
if ($.cookie('pin_nav') === 'true' || doPinNav) {
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index aee1c29eee3..ad12cb906e1 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -320,6 +320,7 @@
frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
frequentlyUsedEmojis.push(emoji);
return $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), {
+ path: gon.relative_url_root || '/',
expires: 365
});
};
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js
index 1b7b63489ea..5467e3edc69 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.js
+++ b/app/assets/javascripts/behaviors/toggler_behavior.js
@@ -1,10 +1,26 @@
-(function() {
+(function(w) {
$(function() {
- return $("body").on("click", ".js-toggle-button", function(e) {
- $(this).find('i').toggleClass('fa fa-chevron-down').toggleClass('fa fa-chevron-up');
- $(this).closest(".js-toggle-container").find(".js-toggle-content").toggle();
- return e.preventDefault();
+ $('body').on('click', '.js-toggle-button', function(e) {
+ e.preventDefault();
+ $(this)
+ .find('.fa')
+ .toggleClass('fa-chevron-down fa-chevron-up')
+ .end()
+ .closest('.js-toggle-container')
+ .find('.js-toggle-content')
+ .toggle()
+ ;
});
- });
-}).call(this);
+ // If we're accessing a permalink, ensure it is not inside a
+ // closed js-toggle-container!
+ var hash = w.gl.utils.getLocationHash();
+ var anchor = hash && document.getElementById(hash);
+ var container = anchor && $(anchor).closest('.js-toggle-container');
+
+ if (container && container.find('.js-toggle-content').is(':hidden')) {
+ container.find('.js-toggle-button').trigger('click');
+ anchor.scrollIntoView();
+ }
+ });
+})(window);
diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6
index a612cf0f1ae..91c12570e09 100644
--- a/app/assets/javascripts/boards/boards_bundle.js.es6
+++ b/app/assets/javascripts/boards/boards_bundle.js.es6
@@ -54,4 +54,11 @@ $(() => {
});
}
});
+
+ gl.IssueBoardsSearch = new Vue({
+ el: '#js-boards-seach',
+ data: {
+ filters: Store.state.filters
+ }
+ });
});
diff --git a/app/assets/javascripts/boards/components/board.js.es6 b/app/assets/javascripts/boards/components/board.js.es6
index d7f4107cb02..7e86f001f44 100644
--- a/app/assets/javascripts/boards/components/board.js.es6
+++ b/app/assets/javascripts/boards/components/board.js.es6
@@ -21,15 +21,10 @@
},
data () {
return {
- query: '',
filters: Store.state.filters
};
},
watch: {
- query () {
- this.list.filters = this.getFilterData();
- this.list.getIssues(true);
- },
filters: {
handler () {
this.list.page = 1;
@@ -38,16 +33,6 @@
deep: true
}
},
- methods: {
- getFilterData () {
- const filters = this.filters;
- let queryData = { search: this.query };
-
- Object.keys(filters).forEach((key) => { queryData[key] = filters[key]; });
-
- return queryData;
- }
- },
ready () {
const options = gl.issueBoards.getBoardSortableDefaultOptions({
disabled: this.disabled,
diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6
index a6644e9eb8c..50fc11d7737 100644
--- a/app/assets/javascripts/boards/components/board_list.js.es6
+++ b/app/assets/javascripts/boards/components/board_list.js.es6
@@ -20,7 +20,8 @@
data () {
return {
scrollOffset: 250,
- filters: Store.state.filters
+ filters: Store.state.filters,
+ showCount: false
};
},
watch: {
@@ -30,6 +31,15 @@
this.$els.list.scrollTop = 0;
},
deep: true
+ },
+ issues () {
+ this.$nextTick(() => {
+ if (this.scrollHeight() > this.listHeight()) {
+ this.showCount = true;
+ } else {
+ this.showCount = false;
+ }
+ });
}
},
methods: {
@@ -58,6 +68,7 @@
group: 'issues',
sort: false,
disabled: this.disabled,
+ filter: '.board-list-count',
onStart: (e) => {
const card = this.$refs.issue[e.oldIndex];
diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6
index be2b8c568a8..91fd620fdb3 100644
--- a/app/assets/javascripts/boards/models/list.js.es6
+++ b/app/assets/javascripts/boards/models/list.js.es6
@@ -11,6 +11,7 @@ class List {
this.loading = true;
this.loadingMore = false;
this.issues = [];
+ this.issuesSize = 0;
if (obj.label) {
this.label = new ListLabel(obj.label);
@@ -51,17 +52,13 @@ class List {
}
nextPage () {
- if (Math.floor(this.issues.length / 20) === this.page) {
+ if (this.issuesSize > this.issues.length) {
this.page++;
return this.getIssues(false);
}
}
- canSearch () {
- return this.type === 'backlog';
- }
-
getIssues (emptyIssues = true) {
const filters = this.filters;
let data = { page: this.page };
@@ -80,12 +77,13 @@ class List {
.then((resp) => {
const data = resp.json();
this.loading = false;
+ this.issuesSize = data.size;
if (emptyIssues) {
this.issues = [];
}
- this.createIssues(data);
+ this.createIssues(data.issues);
});
}
@@ -96,14 +94,20 @@ class List {
}
addIssue (issue, listFrom) {
- this.issues.push(issue);
+ if (!this.findIssue(issue.id)) {
+ this.issues.push(issue);
- if (this.label) {
- issue.addLabel(this.label);
- }
+ if (this.label) {
+ issue.addLabel(this.label);
+ }
- if (listFrom) {
- gl.boardService.moveIssue(issue.id, listFrom.id, this.id);
+ if (listFrom) {
+ this.issuesSize++;
+ gl.boardService.moveIssue(issue.id, listFrom.id, this.id)
+ .then(() => {
+ listFrom.getIssues(false);
+ });
+ }
}
}
@@ -116,6 +120,7 @@ class List {
const matchesRemove = removeIssue.id === issue.id;
if (matchesRemove) {
+ this.issuesSize--;
issue.removeLabel(this.label);
}
diff --git a/app/assets/javascripts/boards/stores/boards_store.js.es6 b/app/assets/javascripts/boards/stores/boards_store.js.es6
index 18f26a1f911..bd07ee0c161 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js.es6
+++ b/app/assets/javascripts/boards/stores/boards_store.js.es6
@@ -15,7 +15,8 @@
author_id: gl.utils.getParameterValues('author_id')[0],
assignee_id: gl.utils.getParameterValues('assignee_id')[0],
milestone_title: gl.utils.getParameterValues('milestone_title')[0],
- label_name: gl.utils.getParameterValues('label_name[]')
+ label_name: gl.utils.getParameterValues('label_name[]'),
+ search: ''
};
},
addList (listObj) {
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index ba64d2bcf0b..38cdc7b9fba 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -199,6 +199,7 @@
break;
case 'labels':
switch (path[2]) {
+ case 'new':
case 'edit':
new Labels();
}
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 0179b320a3b..77b2082cba0 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -117,7 +117,7 @@
}
});
} else {
- return elements.show();
+ return elements.show().removeClass('option-hidden');
}
}
};
@@ -190,9 +190,9 @@
currentIndex = -1;
- NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link, .option-hidden';
+ NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link';
- SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ")";
+ SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ", .option-hidden)";
CURSOR_SELECT_SCROLL_PADDING = 5
@@ -556,7 +556,7 @@
if (isInput) {
field = $(this.el);
} else {
- field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']");
+ field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + escape(value) + "']");
}
if (el.hasClass(ACTIVE_CLASS)) {
el.removeClass(ACTIVE_CLASS);
@@ -565,10 +565,6 @@
} else {
field.remove();
}
- if (this.options.toggleLabel) {
- this.updateLabel(selectedObject, el, this);
- }
- return selectedObject;
} else if (el.hasClass(INDETERMINATE_CLASS)) {
el.addClass(ACTIVE_CLASS);
el.removeClass(INDETERMINATE_CLASS);
@@ -578,7 +574,6 @@
if (!field.length && fieldName) {
this.addInput(fieldName, value, selectedObject);
}
- return selectedObject;
} else {
if (!this.options.multiSelect || el.hasClass('dropdown-clear-active')) {
this.dropdown.find("." + ACTIVE_CLASS).removeClass(ACTIVE_CLASS);
@@ -590,9 +585,6 @@
field.remove();
}
el.addClass(ACTIVE_CLASS);
- if (this.options.toggleLabel) {
- this.updateLabel(selectedObject, el, this);
- }
if (value != null) {
if (!field.length && fieldName) {
this.addInput(fieldName, value, selectedObject);
@@ -600,8 +592,14 @@
field.val(value).trigger('change');
}
}
- return selectedObject;
}
+
+ // Update label right after input has been added
+ if (this.options.toggleLabel) {
+ this.updateLabel(selectedObject, el, this);
+ }
+
+ return selectedObject;
};
GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) {
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 6838d9d8da1..e6422602ce8 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -127,7 +127,7 @@
Issue.prototype.initCanCreateBranch = function() {
var $container;
- $container = $('div#new-branch');
+ $container = $('#new-branch');
if ($container.length === 0) {
return;
}
@@ -139,7 +139,6 @@
if (data.can_create_branch) {
$container.find('.checking').hide();
$container.find('.available').show();
- return $container.find('a').attr('disabled', false);
} else {
$container.find('.checking').hide();
return $container.find('.unavailable').show();
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 565dbeacdb3..bab23ff5ac0 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -164,7 +164,7 @@
instance.addInput(this.fieldName, label.id);
}
}
- if ($form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + (this.id(label)) + "']").length) {
+ if ($form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + escape(this.id(label)) + "']").length) {
selectedClass.push('is-active');
}
if ($dropdown.hasClass('js-multiselect') && removesAll) {
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 10afa7e4329..d4d5927d3b0 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -67,6 +67,14 @@
$.timeago.settings.strings = tmpLocale;
};
+ w.gl.utils.getDayDifference = function(a, b) {
+ var millisecondsPerDay = 1000 * 60 * 60 * 24;
+ var date1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
+ var date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
+
+ return Math.floor((date2 - date1) / millisecondsPerDay);
+ }
+
})(window);
}).call(this);
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index fffbfd19745..533310cc87c 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -43,7 +43,7 @@
}
return newUrl;
};
- return w.gl.utils.removeParamQueryString = function(url, param) {
+ w.gl.utils.removeParamQueryString = function(url, param) {
var urlVariables, variables;
url = decodeURIComponent(url);
urlVariables = url.split('&');
@@ -59,6 +59,16 @@
return results;
})()).join('&');
};
+ w.gl.utils.getLocationHash = function(url) {
+ var hashIndex;
+ if (typeof url === 'undefined') {
+ // Note: We can't use window.location.hash here because it's
+ // not consistent across browsers - Firefox will pre-decode it
+ url = window.location.href;
+ }
+ hashIndex = url.indexOf('#');
+ return hashIndex === -1 ? null : url.substring(hashIndex + 1);
+ };
})(window);
}).call(this);
diff --git a/app/assets/javascripts/logo.js b/app/assets/javascripts/logo.js
index 218f24fe908..7d8eef1b495 100644
--- a/app/assets/javascripts/logo.js
+++ b/app/assets/javascripts/logo.js
@@ -1,54 +1,12 @@
(function() {
- var clearHighlights, currentTimer, defaultClass, delay, firstPiece, pieceIndex, pieces, start, stop, work;
-
Turbolinks.enableProgressBar();
- defaultClass = 'tanuki-shape';
-
- pieces = ['path#tanuki-right-cheek', 'path#tanuki-right-eye, path#tanuki-right-ear', 'path#tanuki-nose', 'path#tanuki-left-eye, path#tanuki-left-ear', 'path#tanuki-left-cheek'];
-
- pieceIndex = 0;
-
- firstPiece = pieces[0];
-
- currentTimer = null;
-
- delay = 150;
-
- clearHighlights = function() {
- return $("." + defaultClass + ".highlight").attr('class', defaultClass);
- };
-
- start = function() {
- clearHighlights();
- pieceIndex = 0;
- if (pieces[0] !== firstPiece) {
- pieces.reverse();
- }
- if (currentTimer) {
- clearInterval(currentTimer);
- }
- return currentTimer = setInterval(work, delay);
- };
-
- stop = function() {
- clearInterval(currentTimer);
- return clearHighlights();
- };
-
- work = function() {
- clearHighlights();
- $(pieces[pieceIndex]).attr('class', defaultClass + " highlight");
- if (pieceIndex === pieces.length - 1) {
- pieceIndex = 0;
- return pieces.reverse();
- } else {
- return pieceIndex++;
- }
- };
-
- $(document).on('page:fetch', start);
+ $(document).on('page:fetch', function() {
+ $('.tanuki-logo').addClass('animate');
+ });
- $(document).on('page:change', stop);
+ $(document).on('page:change', function() {
+ $('.tanuki-logo').removeClass('animate');
+ });
}).call(this);
diff --git a/app/assets/javascripts/merge_conflict_resolver.js.es6 b/app/assets/javascripts/merge_conflict_resolver.js.es6
index 77bffbcb403..b56fd5aa658 100644
--- a/app/assets/javascripts/merge_conflict_resolver.js.es6
+++ b/app/assets/javascripts/merge_conflict_resolver.js.es6
@@ -75,10 +75,8 @@ class MergeConflictResolver {
window.location.href = data.redirect_to;
})
.error(() => {
- new Flash('Something went wrong!');
- })
- .always(() => {
this.vue.isSubmitting = false;
+ new Flash('Something went wrong!');
});
}
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
index 4e1de4dfb72..66e097c0a28 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/project.js
@@ -17,19 +17,15 @@
return $(this).parents('form').submit();
});
$('.hide-no-ssh-message').on('click', function(e) {
- var path;
- path = '/';
$.cookie('hide_no_ssh_message', 'false', {
- path: path
+ path: gon.relative_url_root || '/'
});
$(this).parents('.no-ssh-key-message').remove();
return e.preventDefault();
});
$('.hide-no-password-message').on('click', function(e) {
- var path;
- path = '/';
$.cookie('hide_no_password_message', 'false', {
- path: path
+ path: gon.relative_url_root || '/'
});
$(this).parents('.no-password-message').remove();
return e.preventDefault();
diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js
index 798f15e40a0..a787b11f2a9 100644
--- a/app/assets/javascripts/project_new.js
+++ b/app/assets/javascripts/project_new.js
@@ -4,6 +4,8 @@
this.ProjectNew = (function() {
function ProjectNew() {
this.toggleSettings = bind(this.toggleSettings, this);
+ this.$selects = $('.features select');
+
$('.project-edit-container').on('ajax:before', (function(_this) {
return function() {
$('.project-edit-container').hide();
@@ -15,18 +17,24 @@
}
ProjectNew.prototype.toggleSettings = function() {
- this._showOrHide('#project_builds_enabled', '.builds-feature');
- return this._showOrHide('#project_merge_requests_enabled', '.merge-requests-feature');
+ var self = this;
+
+ this.$selects.each(function () {
+ var $select = $(this),
+ className = $select.data('field').replace(/_/g, '-')
+ .replace('access-level', 'feature');
+ self._showOrHide($select, '.' + className);
+ });
};
ProjectNew.prototype.toggleSettingsOnclick = function() {
- return $('#project_builds_enabled, #project_merge_requests_enabled').on('click', this.toggleSettings);
+ this.$selects.on('change', this.toggleSettings);
};
ProjectNew.prototype._showOrHide = function(checkElement, container) {
- var $container;
- $container = $(container);
- if ($(checkElement).prop('checked')) {
+ var $container = $(container);
+
+ if ($(checkElement).val() !== '0') {
return $container.show();
} else {
return $container.hide();
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index dc4d5113826..e3d5f413c77 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -30,7 +30,7 @@
}
if (!triggered) {
return $.cookie("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'), {
- path: '/'
+ path: gon.relative_url_root || '/'
});
}
});
diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js
index 6e677fa8cc6..23eda7d44ca 100644
--- a/app/assets/javascripts/todos.js
+++ b/app/assets/javascripts/todos.js
@@ -13,6 +13,7 @@
this.perPage = this.el.data('perPage');
this.clearListeners();
this.initBtnListeners();
+ this.initFilters();
}
Todos.prototype.clearListeners = function() {
@@ -27,6 +28,31 @@
return $('.todo').on('click', this.goToTodoUrl);
};
+ Todos.prototype.initFilters = function() {
+ new UsersSelect();
+ this.initFilterDropdown($('.js-project-search'), 'project_id', ['text']);
+ this.initFilterDropdown($('.js-type-search'), 'type');
+ this.initFilterDropdown($('.js-action-search'), 'action_id');
+
+ $('form.filter-form').on('submit', function (event) {
+ event.preventDefault();
+ Turbolinks.visit(this.action + '&' + $(this).serialize());
+ });
+ };
+
+ Todos.prototype.initFilterDropdown = function($dropdown, fieldName, searchFields) {
+ $dropdown.glDropdown({
+ selectable: true,
+ filterable: searchFields ? true : false,
+ fieldName: fieldName,
+ search: { fields: searchFields },
+ data: $dropdown.data('data'),
+ clicked: function() {
+ return $dropdown.closest('form.filter-form').submit();
+ }
+ })
+ };
+
Todos.prototype.doneClicked = function(e) {
var $this;
e.preventDefault();
@@ -66,7 +92,7 @@
success: (function(_this) {
return function(data) {
$this.remove();
- $('.js-todos-list').remove();
+ $('.prepend-top-default').html('<div class="nothing-here-block">You\'re all done!</div>');
return _this.updateBadges(data);
};
})(this)
diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/user.js
deleted file mode 100644
index b46390ad8f4..00000000000
--- a/app/assets/javascripts/user.js
+++ /dev/null
@@ -1,31 +0,0 @@
-(function() {
- this.User = (function() {
- function User(opts) {
- this.opts = opts;
- $('.profile-groups-avatars').tooltip({
- "placement": "top"
- });
- this.initTabs();
- $('.hide-project-limit-message').on('click', function(e) {
- var path;
- path = '/';
- $.cookie('hide_project_limit_message', 'false', {
- path: path
- });
- $(this).parents('.project-limit-message').remove();
- return e.preventDefault();
- });
- }
-
- User.prototype.initTabs = function() {
- return new UserTabs({
- parentEl: '.user-profile',
- action: this.opts.action
- });
- };
-
- return User;
-
- })();
-
-}).call(this);
diff --git a/app/assets/javascripts/user.js.es6 b/app/assets/javascripts/user.js.es6
new file mode 100644
index 00000000000..6889d3a7491
--- /dev/null
+++ b/app/assets/javascripts/user.js.es6
@@ -0,0 +1,34 @@
+(global => {
+ global.User = class {
+ constructor(opts) {
+ this.opts = opts;
+ this.placeProfileAvatarsToTop();
+ this.initTabs();
+ this.hideProjectLimitMessage();
+ }
+
+ placeProfileAvatarsToTop() {
+ $('.profile-groups-avatars').tooltip({
+ placement: 'top'
+ });
+ }
+
+ initTabs() {
+ return new UserTabs({
+ parentEl: '.user-profile',
+ action: this.opts.action
+ });
+ }
+
+ hideProjectLimitMessage() {
+ $('.hide-project-limit-message').on('click', e => {
+ e.preventDefault();
+ const path = gon.relative_url_root || '/';
+ $.cookie('hide_project_limit_message', 'false', {
+ path: path
+ });
+ $(this).parents('.project-limit-message').remove();
+ });
+ }
+ }
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js
index 8b3dbf5f5ae..74ecf4f4cf9 100644
--- a/app/assets/javascripts/users/calendar.js
+++ b/app/assets/javascripts/users/calendar.js
@@ -3,7 +3,6 @@
this.Calendar = (function() {
function Calendar(timestamps, calendar_activities_path) {
- var group, i;
this.calendar_activities_path = calendar_activities_path;
this.clickDay = bind(this.clickDay, this);
this.currentSelectedDate = '';
@@ -13,26 +12,36 @@
this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
this.months = [];
this.timestampsTmp = [];
- i = 0;
- group = 0;
- _.each(timestamps, (function(_this) {
- return function(count, date) {
- var day, innerArray, newDate;
- newDate = new Date(parseInt(date) * 1000);
- day = newDate.getDay();
- if ((day === 0 && i !== 0) || i === 0) {
- _this.timestampsTmp.push([]);
- group++;
- }
- innerArray = _this.timestampsTmp[group - 1];
- innerArray.push({
- count: count,
- date: newDate,
- day: day
- });
- return i++;
- };
- })(this));
+ var group = 0;
+
+ var today = new Date()
+ today.setHours(0, 0, 0, 0, 0);
+
+ var oneYearAgo = new Date(today);
+ oneYearAgo.setFullYear(today.getFullYear() - 1);
+
+ var days = gl.utils.getDayDifference(oneYearAgo, today);
+
+ for(var i = 0; i <= days; i++) {
+ var date = new Date(oneYearAgo);
+ date.setDate(date.getDate() + i);
+
+ var day = date.getDay();
+ var count = timestamps[date.getTime() * 0.001];
+
+ if ((day === 0 && i !== 0) || i === 0) {
+ this.timestampsTmp.push([]);
+ group++;
+ }
+
+ var innerArray = this.timestampsTmp[group - 1];
+ innerArray.push({
+ count: count || 0,
+ date: date,
+ day: day
+ });
+ }
+
this.colorKey = this.initColorKey();
this.color = this.initColor();
this.renderSvg(group);
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index a306b8f3f29..d5cca1b10fb 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -24,6 +24,7 @@
@import "framework/issue_box.scss";
@import "framework/jquery.scss";
@import "framework/lists.scss";
+@import "framework/logo.scss";
@import "framework/markdown_area.scss";
@import "framework/mobile.scss";
@import "framework/modal.scss";
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 6c3786b49bb..4618687a4be 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -200,13 +200,15 @@
svg {
height: 15px;
- width: auto;
+ width: 15px;
position: relative;
top: 2px;
}
svg, .fa {
- margin-right: 3px;
+ &:not(:last-child) {
+ margin-right: 3px;
+ }
}
}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index c1e5305644b..5957dce89bc 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -53,7 +53,7 @@ pre {
&.well-pre {
border: 1px solid #eee;
- background: #f9f9f9;
+ background: $gray-light;
border-radius: 0;
color: #555;
}
@@ -225,7 +225,7 @@ li.note {
.milestone {
&.milestone-closed {
- background: #f9f9f9;
+ background: $gray-light;
}
.progress {
margin-bottom: 0;
@@ -248,7 +248,7 @@ li.note {
img.emoji {
height: 20px;
- vertical-align: middle;
+ vertical-align: top;
width: 20px;
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 7d3a063d6c2..b0ba112476b 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -17,6 +17,12 @@
.dropdown {
position: relative;
+
+ .btn-link {
+ &:hover {
+ cursor: pointer;
+ }
+ }
}
.open {
@@ -177,6 +183,13 @@
&.dropdown-menu-user-link {
line-height: 16px;
}
+
+ .icon-play {
+ fill: $table-text-gray;
+ margin-right: 6px;
+ height: 12px;
+ width: 11px;
+ }
}
.dropdown-header {
@@ -189,6 +202,12 @@
.separator + .dropdown-header {
padding-top: 2px;
}
+
+ .unclickable {
+ cursor: not-allowed;
+ padding: 5px 8px;
+ color: $dropdown-header-color;
+ }
}
.dropdown-menu-large {
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index d3e3fc50736..e3be45ba1dc 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -115,7 +115,7 @@
padding: 0;
}
td.blame-commit {
- background: #f9f9f9;
+ background: $gray-light;
min-width: 350px;
.commit-author-link {
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 43d55661541..37ff7e22ed1 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -19,7 +19,6 @@ input[type='text'].danger {
}
.form-actions {
- margin: -$gl-padding;
margin-top: 0;
margin-bottom: -$gl-padding;
padding: $gl-padding;
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 0c607071840..1036219172e 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -2,16 +2,6 @@
* Application Header
*
*/
-@mixin tanuki-logo-colors($path-color) {
- fill: $path-color;
- transition: all 0.8s;
-
- &:hover,
- &.highlight {
- fill: lighten($path-color, 25%);
- transition: all 0.1s;
- }
-}
header {
transition: padding $sidebar-transition-duration;
@@ -25,7 +15,7 @@ header {
margin: 8px 0;
text-align: center;
- #tanuki-logo, img {
+ .tanuki-logo, img {
height: 36px;
}
}
@@ -94,7 +84,7 @@ header {
.side-nav-toggle {
position: absolute;
left: -10px;
- margin: 6px 0;
+ margin: 7px 0;
font-size: 18px;
padding: 6px 10px;
border: none;
@@ -146,6 +136,8 @@ header {
}
.title {
+ position: relative;
+ padding-right: 20px;
margin: 0;
font-size: 19px;
max-width: 400px;
@@ -158,7 +150,11 @@ header {
vertical-align: top;
white-space: nowrap;
- @media (max-width: $screen-sm-max) {
+ @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
+ max-width: 300px;
+ }
+
+ @media (max-width: $screen-xs-max) {
max-width: 190px;
}
@@ -170,11 +166,15 @@ header {
}
.dropdown-toggle-caret {
- position: relative;
- top: -2px;
+ color: $gl-text-color;
+ border: transparent;
+ background: transparent;
+ position: absolute;
+ right: 3px;
width: 12px;
- line-height: 12px;
- margin-left: 5px;
+ line-height: 19px;
+ margin-top: (($header-height - 19) / 2);
+ padding: 0;
font-size: 10px;
text-align: center;
cursor: pointer;
@@ -205,26 +205,6 @@ header {
}
}
-#tanuki-logo {
-
- #tanuki-left-ear,
- #tanuki-right-ear,
- #tanuki-nose {
- @include tanuki-logo-colors($tanuki-red);
- }
-
- #tanuki-left-eye,
- #tanuki-right-eye {
- @include tanuki-logo-colors($tanuki-orange);
- }
-
- #tanuki-left-cheek,
- #tanuki-right-cheek {
- @include tanuki-logo-colors($tanuki-yellow);
- }
-
-}
-
@media (max-width: $screen-xs-max) {
header .container-fluid {
font-size: 18px;
diff --git a/app/assets/stylesheets/framework/logo.scss b/app/assets/stylesheets/framework/logo.scss
new file mode 100644
index 00000000000..3ee3fb4cee5
--- /dev/null
+++ b/app/assets/stylesheets/framework/logo.scss
@@ -0,0 +1,118 @@
+@mixin unique-keyframes {
+ $animation-name: unique-id();
+ @include webkit-prefix(animation-name, $animation-name);
+
+ @-webkit-keyframes #{$animation-name} {
+ @content;
+ }
+ @keyframes #{$animation-name} {
+ @content;
+ }
+}
+
+@mixin tanuki-logo-colors($path-color) {
+ fill: $path-color;
+ transition: all 0.8s;
+
+ &:hover {
+ fill: lighten($path-color, 25%);
+ transition: all 0.1s;
+ }
+}
+
+@mixin tanuki-second-highlight-animations($tanuki-color) {
+ @include unique-keyframes {
+ 10%, 80% {
+ fill: #{$tanuki-color}
+ }
+ 20%, 90% {
+ fill: lighten($tanuki-color, 25%);
+ }
+ }
+}
+
+@mixin tanuki-forth-highlight-animations($tanuki-color) {
+ @include unique-keyframes {
+ 30%, 60% {
+ fill: #{$tanuki-color};
+ }
+ 40%, 70% {
+ fill: lighten($tanuki-color, 25%);
+ }
+ }
+}
+
+.tanuki-logo {
+
+ .tanuki-left-ear,
+ .tanuki-right-ear,
+ .tanuki-nose {
+ @include tanuki-logo-colors($tanuki-red);
+ }
+
+ .tanuki-left-eye,
+ .tanuki-right-eye {
+ @include tanuki-logo-colors($tanuki-orange);
+ }
+
+ .tanuki-left-cheek,
+ .tanuki-right-cheek {
+ @include tanuki-logo-colors($tanuki-yellow);
+ }
+
+ &.animate {
+ .tanuki-shape {
+ @include webkit-prefix(animation-duration, 1.5s);
+ @include webkit-prefix(animation-iteration-count, infinite);
+ }
+
+ .tanuki-left-cheek {
+ @include unique-keyframes {
+ 0%, 10%, 100% {
+ fill: lighten($tanuki-yellow, 25%);
+ }
+ 90% {
+ fill: $tanuki-yellow;
+ }
+ }
+ }
+
+ .tanuki-left-eye {
+ @include tanuki-second-highlight-animations($tanuki-orange);
+ }
+
+ .tanuki-left-ear {
+ @include tanuki-second-highlight-animations($tanuki-red);
+ }
+
+ .tanuki-nose {
+ @include unique-keyframes {
+ 20%, 70% {
+ fill: $tanuki-red;
+ }
+ 30%, 80% {
+ fill: lighten($tanuki-red, 25%);
+ }
+ }
+ }
+
+ .tanuki-right-eye {
+ @include tanuki-forth-highlight-animations($tanuki-orange);
+ }
+
+ .tanuki-right-ear {
+ @include tanuki-forth-highlight-animations($tanuki-red);
+ }
+
+ .tanuki-right-cheek {
+ @include unique-keyframes {
+ 40% {
+ fill: $tanuki-yellow;
+ }
+ 60% {
+ fill: lighten($tanuki-yellow, 25%);
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index d2d60ed7196..00f92cef9a4 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -9,43 +9,11 @@
border-radius: $radius;
}
-@mixin border-radius-left($radius) {
- @include border-radius($radius 0 0 $radius)
-}
-
-@mixin border-radius-right($radius) {
- @include border-radius(0 0 $radius $radius)
-}
-
-@mixin linear-gradient($from, $to) {
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from($from), to($to));
- background-image: -webkit-linear-gradient($from, $to);
- background-image: -moz-linear-gradient($from, $to);
- background-image: -ms-linear-gradient($from, $to);
- background-image: -o-linear-gradient($from, $to);
-}
-
-@mixin transition($transition) {
- -webkit-transition: $transition;
- -moz-transition: $transition;
- -ms-transition: $transition;
- -o-transition: $transition;
- transition: $transition;
-}
-
/**
* Prefilled mixins
* Mixins with fixed values
*/
-@mixin shade {
- @include box-shadow(0 0 3px #ddd);
-}
-
-@mixin solid-shade {
- @include box-shadow(0 0 0 3px #f1f1f1);
-}
-
@mixin str-truncated($max_width: 82%) {
display: inline-block;
overflow: hidden;
@@ -76,7 +44,7 @@
}
&.active {
- background: #f9f9f9;
+ background: $gray-light;
a {
font-weight: 600;
}
@@ -94,23 +62,6 @@
}
}
-@mixin input-big {
- height: 36px;
- padding: 5px 10px;
- font-size: 16px;
- line-height: 24px;
- color: #7f8fa4;
- background-color: #fff;
- border-color: #e7e9ed;
-}
-
-@mixin btn-big {
- height: 36px;
- padding: 5px 10px;
- font-size: 16px;
- line-height: 24px;
-}
-
@mixin bulleted-list {
> ul {
list-style-type: disc;
@@ -129,3 +80,8 @@
color: rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.1);
}
+
+@mixin webkit-prefix($property, $value) {
+ #{'-webkit-' + $property}: $value;
+ #{$property}: $value;
+}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 9e924f99e9c..9efbaf54e90 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -1,4 +1,4 @@
-@mixin fade($gradient-direction, $rgba, $gradient-color) {
+@mixin fade($gradient-direction, $gradient-color) {
visibility: hidden;
opacity: 0;
z-index: 2;
@@ -8,10 +8,7 @@
height: 30px;
transition-duration: .3s;
-webkit-transform: translateZ(0);
- background: -webkit-linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
- background: -o-linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
- background: -moz-linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
- background: linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
+ background: linear-gradient(to $gradient-direction, $gradient-color 45%, rgba($gradient-color, 0.4));
&.scrolling {
visibility: visible;
@@ -71,7 +68,7 @@
.badge {
font-weight: normal;
background-color: #eee;
- color: #78a;
+ color: $btn-transparent-color;
vertical-align: baseline;
}
}
@@ -161,6 +158,7 @@
> .dropdown {
margin-right: $gl-padding-top;
display: inline-block;
+ vertical-align: top;
&:last-child {
margin-right: 0;
@@ -210,12 +208,6 @@
}
}
- .project-filter-form {
- input {
- background-color: $background-color;
- }
- }
-
@media (max-width: $screen-xs-max) {
padding-bottom: 0;
width: 100%;
@@ -335,10 +327,6 @@
}
}
- .badge {
- color: $gl-icon-color;
- }
-
&:hover {
a, i {
color: $black;
@@ -356,7 +344,7 @@
}
.fade-right {
- @include fade(left, rgba(255, 255, 255, 0.4), $background-color);
+ @include fade(left, $background-color);
right: -5px;
.fa {
@@ -365,7 +353,7 @@
}
.fade-left {
- @include fade(right, rgba(255, 255, 255, 0.4), $background-color);
+ @include fade(right, $background-color);
left: -5px;
.fa {
@@ -376,6 +364,7 @@
&.sub-nav-scroll {
.fade-right {
+ @include fade(left, $dark-background-color);
right: 0;
.fa {
@@ -384,6 +373,7 @@
}
.fade-left {
+ @include fade(right, $dark-background-color);
left: 0;
.fa {
@@ -400,7 +390,7 @@
@include scrolling-links();
.fade-right {
- @include fade(left, rgba(255, 255, 255, 0.4), $white-light);
+ @include fade(left, $white-light);
right: -5px;
.fa {
@@ -409,7 +399,7 @@
}
.fade-left {
- @include fade(right, rgba(255, 255, 255, 0.4), $white-light);
+ @include fade(right, $white-light);
left: -5px;
.fa {
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index b2e22b60440..c75dacf95d9 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -151,7 +151,7 @@
background-position: right 0 bottom 6px;
border: 1px solid $input-border;
@include border-radius($border-radius-default);
- @include transition(border-color ease-in-out .15s, box-shadow ease-in-out .15s);
+ transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
&:focus {
border-color: $input-border-focus;
diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index 371c1bf17e1..915aa631ef8 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
@@ -125,7 +125,7 @@ $panel-inner-border: $border-color;
//
//##
-$well-bg: #f9f9f9;
+$well-bg: $gray-light;
$well-border: #eee;
//== Code
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 06874a993fa..3f8433a0e7f 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -159,25 +159,18 @@
position: relative;
a.anchor {
- // Setting `display: none` would prevent the anchor being scrolled to, so
- // instead we set the height to 0 and it gets updated on hover.
- height: 0;
+ left: -16px;
+ position: absolute;
+ text-decoration: none;
+
+ &:after {
+ content: url('icon_anchor.svg');
+ visibility: hidden;
+ }
}
- &:hover > a.anchor {
- $size: 14px;
- position: absolute;
- right: 100%;
- top: 50%;
- margin-top: -11px;
- margin-right: 0;
- padding-right: 15px;
- display: inline-block;
- width: $size;
- height: $size;
- background-image: image-url("icon-link.png");
- background-size: contain;
- background-repeat: no-repeat;
+ &:hover > a.anchor:after {
+ visibility: visible;
}
}
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 5da390118c6..e4a3ca3a9ea 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -10,12 +10,78 @@ $sidebar-transition-duration: .15s;
$sidebar-breakpoint: 1024px;
/*
+ * Color schema
+ */
+$white-light: #fff;
+$white-normal: #ededed;
+$white-dark: #ececec;
+
+$gray-light: #fafafa;
+$gray-normal: #f5f5f5;
+$gray-dark: #ededed;
+$gray-darkest: #c9c9c9;
+
+$green-light: #38ae67;
+$green-normal: #2faa60;
+$green-dark: #2ca05b;
+
+$blue-light: #2ea8e5;
+$blue-normal: #2d9fd8;
+$blue-dark: #2897ce;
+
+$blue-medium-light: #3498cb;
+$blue-medium: #2f8ebf;
+$blue-medium-dark: #2d86b4;
+
+$orange-light: #fc8a51;
+$orange-normal: #e75e40;
+$orange-dark: #ce5237;
+
+$red-light: #e52c5a;
+$red-normal: #d22852;
+$red-dark: darken($red-normal, 5%);
+
+$black: #000;
+$black-transparent: rgba(0, 0, 0, 0.3);
+
+$border-white-light: #f1f2f4;
+$border-white-normal: #d6dae2;
+$border-white-dark: #c6cacf;
+
+$border-gray-light: #dcdcdc;
+$border-gray-normal: #d7d7d7;
+$border-gray-dark: #c6cacf;
+
+$border-green-light: #2faa60;
+$border-green-normal: #2ca05b;
+$border-green-dark: #279654;
+
+$border-blue-light: #2d9fd8;
+$border-blue-normal: #2897ce;
+$border-blue-dark: #258dc1;
+
+$border-orange-light: #fc6d26;
+$border-orange-normal: #ce5237;
+$border-orange-dark: #c14e35;
+
+$border-red-light: #d22852;
+$border-red-normal: #ca264f;
+$border-red-dark: darken($border-red-normal, 5%);
+
+$help-well-bg: $gray-light;
+$help-well-border: #e5e5e5;
+
+$warning-message-bg: #fbf2d9;
+$warning-message-color: #9e8e60;
+$warning-message-border: #f0e2bb;
+
+/*
* UI elements
*/
$border-color: #e5e5e5;
$focus-border-color: #3aabf0;
$table-border-color: #f0f0f0;
-$background-color: #fafafa;
+$background-color: $gray-light;
$dark-background-color: #f5f5f5;
$table-text-gray: #8f8f8f;
@@ -90,73 +156,6 @@ $btn-side-margin: 10px;
$btn-sm-side-margin: 7px;
$btn-xs-side-margin: 5px;
-/*
- * Color schema
- */
-
-$white-light: #fff;
-$white-normal: #ededed;
-$white-dark: #ececec;
-
-$gray-light: #faf9f9;
-$gray-normal: #f5f5f5;
-$gray-dark: #ededed;
-$gray-darkest: #c9c9c9;
-
-$green-light: #38ae67;
-$green-normal: #2faa60;
-$green-dark: #2ca05b;
-
-$blue-light: #2ea8e5;
-$blue-normal: #2d9fd8;
-$blue-dark: #2897ce;
-
-$blue-medium-light: #3498cb;
-$blue-medium: #2f8ebf;
-$blue-medium-dark: #2d86b4;
-
-$orange-light: #fc8a51;
-$orange-normal: #e75e40;
-$orange-dark: #ce5237;
-
-$red-light: #e52c5a;
-$red-normal: #d22852;
-$red-dark: darken($red-normal, 5%);
-
-$black: #000;
-$black-transparent: rgba(0, 0, 0, 0.3);
-
-$border-white-light: #f1f2f4;
-$border-white-normal: #d6dae2;
-$border-white-dark: #c6cacf;
-
-$border-gray-light: #dcdcdc;
-$border-gray-normal: #d7d7d7;
-$border-gray-dark: #c6cacf;
-
-$border-green-light: #2faa60;
-$border-green-normal: #2ca05b;
-$border-green-dark: #279654;
-
-$border-blue-light: #2d9fd8;
-$border-blue-normal: #2897ce;
-$border-blue-dark: #258dc1;
-
-$border-orange-light: #fc6d26;
-$border-orange-normal: #ce5237;
-$border-orange-dark: #c14e35;
-
-$border-red-light: #d22852;
-$border-red-normal: #ca264f;
-$border-red-dark: darken($border-red-normal, 5%);
-
-$help-well-bg: #fafafa;
-$help-well-border: #e5e5e5;
-
-$warning-message-bg: #fbf2d9;
-$warning-message-color: #9e8e60;
-$warning-message-border: #f0e2bb;
-
/* tanuki logo colors */
$tanuki-red: #e24329;
$tanuki-orange: #fc6d26;
@@ -186,7 +185,7 @@ $line-removed-dark: #fac5cd;
$line-number-old: #f9d7dc;
$line-number-new: #ddfbe6;
$line-number-select: #fbf2da;
-$match-line: #fafafa;
+$match-line: $gray-light;
$table-border-gray: #f0f0f0;
$line-target-blue: #eaf3fc;
$line-select-yellow: #fcf8e7;
@@ -267,7 +266,7 @@ $zen-control-hover-color: #111;
$calendar-header-color: #b8b8b8;
$calendar-hover-bg: #ecf3fe;
$calendar-border-color: rgba(#000, .1);
-$calendar-unselectable-bg: #faf9f9;
+$calendar-unselectable-bg: $gray-light;
/*
* Personal Access Tokens
diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss
index c9cdfdcd29c..8f71381f5c4 100644
--- a/app/assets/stylesheets/pages/admin.scss
+++ b/app/assets/stylesheets/pages/admin.scss
@@ -96,6 +96,10 @@
line-height: inherit;
}
}
+
+ .label-default {
+ color: $btn-transparent-color;
+ }
}
.abuse-reports {
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 9ac4d801ac4..037278bb083 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -10,7 +10,7 @@
.is-dragging {
// Important because plugin sets inline CSS
opacity: 1!important;
-
+
* {
// !important to make sure no style can override this when dragging
cursor: -webkit-grabbing!important;
@@ -142,11 +142,6 @@
}
}
-.board-header-loading-spinner {
- margin-right: 10px;
- color: $gray-darkest;
-}
-
.board-inner-container {
border-bottom: 1px solid $border-color;
padding: $gl-padding;
@@ -160,40 +155,6 @@
border-bottom: 1px solid $border-color;
}
-.board-search-container {
- position: relative;
- background-color: #fff;
-
- .form-control {
- padding-right: 30px;
- }
-}
-
-.board-search-icon,
-.board-search-clear-btn {
- position: absolute;
- right: $gl-padding + 10px;
- top: 50%;
- margin-top: -7px;
- font-size: 14px;
-}
-
-.board-search-icon {
- color: $gl-placeholder-color;
-}
-
-.board-search-clear-btn {
- padding: 0;
- line-height: 1;
- background: transparent;
- border: 0;
- outline: 0;
-
- &:hover {
- color: $gl-link-color;
- }
-}
-
.board-delete {
margin-right: 10px;
padding: 0;
@@ -304,3 +265,22 @@
margin-right: 8px;
font-weight: 500;
}
+
+.issue-boards-search {
+ width: 335px;
+
+ .form-control {
+ display: inline-block;
+ width: 210px;
+ }
+}
+
+.board-list-count {
+ padding: 10px 0;
+ color: $gl-placeholder-color;
+ font-size: 13px;
+
+ > .fa {
+ margin-right: 5px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index c1bb250b42d..23255f34710 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -36,6 +36,7 @@
&.affix {
right: 30px;
bottom: 15px;
+ z-index: 1;
@media (min-width: $screen-md-min) {
right: 26%;
@@ -107,7 +108,7 @@
}
.blocks-container {
- padding: $gl-padding;
+ padding: 0 $gl-padding;
}
.block {
@@ -122,6 +123,13 @@
}
}
+ .retry-link {
+ color: $gl-link-color;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+
.stage-item {
cursor: pointer;
@@ -131,7 +139,7 @@
}
.build-dropdown {
- padding: 0 $gl-padding;
+ padding: $gl-padding 0;
.dropdown-menu-toggle {
margin-top: 8px;
@@ -145,12 +153,11 @@
}
.builds-container {
- margin-top: $gl-padding;
background-color: $white-light;
border-top: 1px solid $border-color;
border-bottom: 1px solid $border-color;
max-height: 300px;
- overflow: scroll;
+ overflow: auto;
svg {
position: relative;
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 6a58b445afa..b369f5c0805 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -113,11 +113,13 @@
.commit-row-description {
font-size: 14px;
- border-left: 1px solid #eee;
+ border-left: 1px solid $btn-gray-hover;
padding: 10px 15px;
margin: 10px 0;
- background: #f9f9f9;
+ background: $gray-light;
display: none;
+ white-space: pre-line;
+ word-break: normal;
pre {
border: none;
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 55f9d4a0011..d01c60ee6ab 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -4,8 +4,9 @@
margin: 0;
}
- .fa-play {
- font-size: 14px;
+ .icon-play {
+ height: 13px;
+ width: 12px;
}
.dropdown-new {
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index 5c336bb1c7e..1d00da1266c 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -60,7 +60,7 @@
pre {
border: none;
- background: #f9f9f9;
+ background: $gray-light;
border-radius: 0;
color: #777;
margin: 0 20px;
@@ -92,7 +92,7 @@
border: 1px solid #eee;
padding: 5px;
@include border-radius(5px);
- background: #f9f9f9;
+ background: $gray-light;
margin-left: 10px;
top: -6px;
img {
@@ -115,11 +115,8 @@
}
&.commits-stat {
- margin-top: 3px;
display: block;
- padding: 3px;
- padding-left: 0;
-
+ padding: 0 3px 0 0;
&:hover {
background: none;
}
diff --git a/app/assets/stylesheets/pages/import.scss b/app/assets/stylesheets/pages/import.scss
index 84cc35239f9..a4f76a9495a 100644
--- a/app/assets/stylesheets/pages/import.scss
+++ b/app/assets/stylesheets/pages/import.scss
@@ -1,22 +1,3 @@
-i.icon-gitorious {
- display: inline-block;
- background-position: 0 0;
- background-size: contain;
- background-repeat: no-repeat;
-}
-
-i.icon-gitorious-small {
- background-image: image-url('gitorious-logo-blue.png');
- width: 13px;
- height: 13px;
-}
-
-i.icon-gitorious-big {
- background-image: image-url('gitorious-logo-black.png');
- width: 18px;
- height: 18px;
-}
-
.import-jobs-from-col,
.import-jobs-to-col {
width: 40%;
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index dfe1e3075da..910700b0206 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -12,6 +12,10 @@
padding-right: 8px;
margin-bottom: 10px;
min-width: 15px;
+
+ .selected_issue {
+ vertical-align: text-top;
+ }
}
.issue-labels {
@@ -68,12 +72,12 @@ form.edit-issue {
}
&.closed {
- background: #f9f9f9;
+ background: $gray-light;
border-color: #e5e5e5;
}
&.merged {
- background: #f9f9f9;
+ background: $gray-light;
border-color: #e5e5e5;
}
}
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 606459f82cd..38c7cd98e41 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -7,6 +7,7 @@
display: inline-block;
margin-right: 10px;
margin-bottom: 10px;
+ text-decoration: none;
}
&.suggest-colors-dropdown {
diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss
index 1f499897c16..5ec660799e3 100644
--- a/app/assets/stylesheets/pages/merge_conflicts.scss
+++ b/app/assets/stylesheets/pages/merge_conflicts.scss
@@ -16,7 +16,7 @@ $colors: (
white_button_origin_chosen : #268ced,
white_header_not_chosen : #f0f0f0,
- white_line_not_chosen : #f9f9f9,
+ white_line_not_chosen : $gray-light,
dark_header_head_neutral : rgba(#3f3, .2),
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index fcdaf671538..7fdd79fa8b9 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -269,7 +269,7 @@
.builds {
.table-holder {
- overflow-x: scroll;
+ overflow-x: auto;
}
}
@@ -375,6 +375,16 @@
}
}
+.mr-version-switch {
+ background: $background-color;
+ padding: $gl-btn-padding;
+ color: $gl-placeholder-color;
+
+ a.btn-link {
+ color: $gl-dark-link-color;
+ }
+}
+
.merge-request-details {
.title {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 08d1692c888..54124a3d658 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -281,19 +281,13 @@ ul.notes {
font-size: 17px;
}
- &.js-note-delete {
- i {
- &:hover {
- color: $gl-text-red;
- }
+ &:hover {
+ .danger-highlight {
+ color: $gl-text-red;
}
- }
- &.js-note-edit {
- i {
- &:hover {
- color: $gl-link-color;
- }
+ .link-highlight {
+ color: $gl-link-color;
}
}
}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 6fa097e3bf1..ee5d9de66d8 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -2,6 +2,7 @@
.stage {
max-width: 90px;
width: 90px;
+ text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -146,6 +147,7 @@
}
.stage-cell {
+ text-align: center;
svg {
height: 18px;
@@ -153,10 +155,6 @@
vertical-align: middle;
overflow: visible;
}
-
- .light {
- width: 3px;
- }
}
.duration,
@@ -215,6 +213,13 @@
border-color: $border-white-normal;
}
}
+
+ .btn {
+ .icon-play {
+ height: 13px;
+ width: 12px;
+ }
+ }
}
}
@@ -254,7 +259,6 @@
width: 100%;
overflow: auto;
white-space: nowrap;
- max-height: 500px;
transition: max-height 0.3s, padding 0.3s;
&.graph-collapsed {
@@ -265,7 +269,6 @@
.pipeline-visualization {
position: relative;
- min-width: 1220px;
ul {
padding: 0;
@@ -275,7 +278,7 @@
.stage-column {
display: inline-block;
vertical-align: top;
- margin-right: 50px;
+ margin-right: 65px;
li {
list-style: none;
@@ -321,6 +324,14 @@
a {
color: $layout-link-gray;
+ text-decoration: none;
+
+ &:hover {
+ .ci-status-text {
+ text-decoration: underline;
+ }
+ }
+
}
}
@@ -336,9 +347,9 @@
content: '';
position: absolute;
top: 50%;
- right: -54px;
+ right: -69px;
border-top: 2px solid $border-color;
- width: 54px;
+ width: 69px;
height: 1px;
}
}
@@ -358,22 +369,25 @@
&::after {
right: -20px;
border-right: 2px solid $border-color;
- border-radius: 0 0 50px;
+ border-radius: 0 0 15px;
}
// Left connecting curves
&::before {
left: -20px;
border-left: 2px solid $border-color;
- border-radius: 0 0 0 50px;
+ border-radius: 0 0 0 15px;
}
}
// Connect second build to first build with smaller curved line
&:nth-child(2) {
&::after, &::before {
- height: 45px;
- top: -26px;
+ height: 29px;
+ top: -10px;
+ }
+ .curve {
+ display: block;
}
}
}
@@ -392,6 +406,12 @@
border: none;
}
}
+ // Remove opposite curve
+ .curve {
+ &::before {
+ display: none;
+ }
+ }
}
}
@@ -403,6 +423,39 @@
border: none;
}
}
+ // Remove opposite curve
+ .curve {
+ &::after {
+ display: none;
+ }
+ }
+ }
+ }
+
+ // Curve first child connecting lines in opposite direction
+ .curve {
+ display: none;
+
+ &::before,
+ &::after {
+ content: '';
+ width: 21px;
+ height: 25px;
+ position: absolute;
+ top: -28.5px;
+ border-top: 2px solid $border-color;
+ }
+
+ &::after {
+ left: -39px;
+ border-right: 2px solid $border-color;
+ border-radius: 0 15px;
+ }
+
+ &::before {
+ right: -39px;
+ border-left: 2px solid $border-color;
+ border-radius: 15px 0 0;
}
}
}
@@ -421,11 +474,22 @@
.pipelines.tab-pane {
.content-list.pipelines {
- overflow: scroll;
+ overflow: auto;
}
.stage {
- max-width: 60px;
- width: 60px;
+ max-width: 100px;
+ width: 100px;
+ }
+
+ .pipeline-actions {
+ min-width: initial;
+ }
+}
+
+.ci-status-icon-created {
+
+ svg {
+ fill: $table-text-gray;
}
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index eaf2d3270b3..f2db373da52 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -311,6 +311,14 @@ a.deploy-project-label {
color: $gl-success;
}
+.lfs-enabled {
+ color: $gl-success;
+}
+
+.lfs-disabled {
+ color: $gl-warning;
+}
+
.breadcrumb.repo-breadcrumb {
padding: 0;
background: transparent;
@@ -600,18 +608,25 @@ pre.light-well {
}
}
-.project-show-readme .readme-holder {
- padding: $gl-padding 0;
- border-top: 0;
-
- .edit-project-readme {
- z-index: 2;
- position: relative;
+.project-show-readme {
+ .row-content-block {
+ background-color: inherit;
+ border: none;
}
- .wiki h1 {
- border-bottom: none;
- padding: 0;
+ .readme-holder {
+ padding: $gl-padding 0;
+ border-top: 0;
+
+ .edit-project-readme {
+ z-index: 2;
+ position: relative;
+ }
+
+ .wiki h1 {
+ border-bottom: none;
+ padding: 0;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index c9d436d72ba..436fb00ba2e 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -80,7 +80,7 @@
.search-icon {
@extend .fa-search;
- @include transition(color .15s);
+ transition: color 0.15s;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
@@ -125,7 +125,7 @@
}
.location-badge {
- @include transition(all .15s);
+ transition: all 0.15s;
background-color: $location-badge-active-bg;
color: $white-light;
}
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index 587f2d9f3c1..0ee7ceecae5 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -43,6 +43,15 @@
border-color: $blue-normal;
}
+ &.ci-created {
+ color: $table-text-gray;
+ border-color: $table-text-gray;
+
+ svg {
+ fill: $table-text-gray;
+ }
+ }
+
svg {
height: 13px;
width: 13px;
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 0340526a53a..68a5d1ae06c 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -99,7 +99,7 @@
pre {
border: none;
- background: #f9f9f9;
+ background: $gray-light;
border-radius: 0;
color: #777;
margin: 0 20px;
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 9da40fe2b09..cdd38442550 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -11,6 +11,20 @@
}
}
+ .add-to-tree {
+ 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;
diff --git a/app/assets/stylesheets/pages/xterm.scss b/app/assets/stylesheets/pages/xterm.scss
index 8d855ce99b0..c9846103762 100644
--- a/app/assets/stylesheets/pages/xterm.scss
+++ b/app/assets/stylesheets/pages/xterm.scss
@@ -20,6 +20,9 @@
$l-cyan: #8abeb7;
$l-white: $ci-text-color;
+ .term-bold {
+ font-weight: bold;
+ }
.term-italic {
font-style: italic;
}
diff --git a/app/controllers/admin/system_info_controller.rb b/app/controllers/admin/system_info_controller.rb
index e4c73008826..ca04a17caa1 100644
--- a/app/controllers/admin/system_info_controller.rb
+++ b/app/controllers/admin/system_info_controller.rb
@@ -29,7 +29,8 @@ class Admin::SystemInfoController < Admin::ApplicationController
]
def show
- system_info = Vmstat.snapshot
+ @cpus = Vmstat.cpu rescue nil
+ @memory = Vmstat.memory rescue nil
mounts = Sys::Filesystem.mounts
@disks = []
@@ -50,10 +51,5 @@ class Admin::SystemInfoController < Admin::ApplicationController
rescue Sys::Filesystem::Error
end
end
-
- @cpus = system_info.cpus.length
-
- @mem_used = system_info.memory.active_bytes
- @mem_total = system_info.memory.total_bytes
end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 70a2275592b..bd4ba384b29 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -24,8 +24,8 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
- helper_method :abilities, :can?, :current_application_settings
- helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled?
+ helper_method :can?, :current_application_settings
+ helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled?
rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception)
@@ -97,12 +97,8 @@ class ApplicationController < ActionController::Base
current_application_settings.after_sign_out_path.presence || new_user_session_path
end
- def abilities
- Ability.abilities
- end
-
def can?(object, action, subject)
- abilities.allowed?(object, action, subject)
+ Ability.allowed?(object, action, subject)
end
def access_denied!
@@ -250,10 +246,6 @@ class ApplicationController < ActionController::Base
Gitlab::OAuth::Provider.enabled?(:bitbucket) && Gitlab::BitbucketImport.public_key.present?
end
- def gitorious_import_enabled?
- current_application_settings.import_sources.include?('gitorious')
- end
-
def google_code_import_enabled?
current_application_settings.import_sources.include?('google_code')
end
diff --git a/app/controllers/concerns/toggle_award_emoji.rb b/app/controllers/concerns/toggle_award_emoji.rb
index 036777c80c1..172d5344b7a 100644
--- a/app/controllers/concerns/toggle_award_emoji.rb
+++ b/app/controllers/concerns/toggle_award_emoji.rb
@@ -8,10 +8,14 @@ module ToggleAwardEmoji
def toggle_award_emoji
name = params.require(:name)
- awardable.toggle_award_emoji(name, current_user)
- TodoService.new.new_award_emoji(to_todoable(awardable), current_user)
+ if awardable.user_can_award?(current_user, name)
+ awardable.toggle_award_emoji(name, current_user)
+ TodoService.new.new_award_emoji(to_todoable(awardable), current_user)
- render json: { ok: true }
+ render json: { ok: true }
+ else
+ render json: { ok: false }
+ end
end
private
diff --git a/app/controllers/import/gitorious_controller.rb b/app/controllers/import/gitorious_controller.rb
deleted file mode 100644
index a4c4ad23027..00000000000
--- a/app/controllers/import/gitorious_controller.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-class Import::GitoriousController < Import::BaseController
- before_action :verify_gitorious_import_enabled
-
- def new
- redirect_to client.authorize_url(callback_import_gitorious_url)
- end
-
- def callback
- session[:gitorious_repos] = params[:repos]
- redirect_to status_import_gitorious_path
- end
-
- def status
- @repos = client.repos
-
- @already_added_projects = current_user.created_projects.where(import_type: "gitorious")
- already_added_projects_names = @already_added_projects.pluck(:import_source)
-
- @repos.reject! { |repo| already_added_projects_names.include? repo.full_name }
- end
-
- def jobs
- jobs = current_user.created_projects.where(import_type: "gitorious").to_json(only: [:id, :import_status])
- render json: jobs
- end
-
- def create
- @repo_id = params[:repo_id]
- repo = client.repo(@repo_id)
- @target_namespace = params[:new_namespace].presence || repo.namespace
- @project_name = repo.name
-
- namespace = get_or_create_namespace || (render and return)
-
- @project = Gitlab::GitoriousImport::ProjectCreator.new(repo, namespace, current_user).execute
- end
-
- private
-
- def client
- @client ||= Gitlab::GitoriousImport::Client.new(session[:gitorious_repos])
- end
-
- def verify_gitorious_import_enabled
- render_404 unless gitorious_import_enabled?
- end
-end
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
index 014b9b43ff2..66ebdcc37a7 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -37,7 +37,7 @@ class JwtController < ApplicationController
def authenticate_project(login, password)
if login == 'gitlab-ci-token'
- Project.find_by(builds_enabled: true, runners_token: password)
+ Project.with_builds_enabled.find_by(runners_token: password)
end
end
diff --git a/app/controllers/namespaces_controller.rb b/app/controllers/namespaces_controller.rb
index 5a94dcb0dbd..83eec1bf4a2 100644
--- a/app/controllers/namespaces_controller.rb
+++ b/app/controllers/namespaces_controller.rb
@@ -14,7 +14,7 @@ class NamespacesController < ApplicationController
if user
redirect_to user_path(user)
- elsif group && can?(current_user, :read_group, namespace)
+ elsif group && can?(current_user, :read_group, group)
redirect_to group_path(group)
elsif current_user.nil?
authenticate_user!
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 91315a07deb..b2ff36f6538 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -88,6 +88,6 @@ class Projects::ApplicationController < ApplicationController
end
def builds_enabled
- return render_404 unless @project.builds_enabled?
+ return render_404 unless @project.feature_available?(:builds, current_user)
end
end
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index 7241949393b..59222637961 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -1,22 +1,25 @@
class Projects::ArtifactsController < Projects::ApplicationController
+ include ExtractsPath
+
layout 'project'
before_action :authorize_read_build!
before_action :authorize_update_build!, only: [:keep]
+ before_action :extract_ref_name_and_path
before_action :validate_artifacts!
def download
- unless artifacts_file.file_storage?
- return redirect_to artifacts_file.url
+ if artifacts_file.file_storage?
+ send_file artifacts_file.path, disposition: 'attachment'
+ else
+ redirect_to artifacts_file.url
end
-
- send_file artifacts_file.path, disposition: 'attachment'
end
def browse
directory = params[:path] ? "#{params[:path]}/" : ''
@entry = build.artifacts_metadata_entry(directory)
- return render_404 unless @entry.exists?
+ render_404 unless @entry.exists?
end
def file
@@ -34,14 +37,41 @@ class Projects::ArtifactsController < Projects::ApplicationController
redirect_to namespace_project_build_path(project.namespace, project, build)
end
+ def latest_succeeded
+ target_path = artifacts_action_path(@path, project, build)
+
+ if target_path
+ redirect_to(target_path)
+ else
+ render_404
+ end
+ end
+
private
+ def extract_ref_name_and_path
+ return unless params[:ref_name_and_path]
+
+ @ref_name, @path = extract_ref(params[:ref_name_and_path])
+ end
+
def validate_artifacts!
- render_404 unless build.artifacts?
+ render_404 unless build && build.artifacts?
end
def build
- @build ||= project.builds.find_by!(id: params[:build_id])
+ @build ||= build_from_id || build_from_ref
+ end
+
+ def build_from_id
+ project.builds.find_by(id: params[:build_id]) if params[:build_id]
+ end
+
+ def build_from_ref
+ return unless @ref_name
+
+ builds = project.latest_successful_builds_for(@ref_name)
+ builds.find_by(name: params[:job])
end
def artifacts_file
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index 5962f74c39b..ada7db3c552 100644
--- a/app/controllers/projects/avatars_controller.rb
+++ b/app/controllers/projects/avatars_controller.rb
@@ -4,7 +4,7 @@ class Projects::AvatarsController < Projects::ApplicationController
before_action :authorize_admin_project!, only: [:destroy]
def show
- @blob = @repository.blob_at_branch('master', @project.avatar_in_git)
+ @blob = @repository.blob_at_branch(@repository.root_ref, @project.avatar_in_git)
if @blob
headers['X-Content-Type-Options'] = 'nosniff'
diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb
index 1a4f6b50e8f..9404612a993 100644
--- a/app/controllers/projects/boards/issues_controller.rb
+++ b/app/controllers/projects/boards/issues_controller.rb
@@ -8,12 +8,15 @@ module Projects
issues = ::Boards::Issues::ListService.new(project, current_user, filter_params).execute
issues = issues.page(params[:page])
- render json: issues.as_json(
- only: [:iid, :title, :confidential],
- include: {
- assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
- labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] }
- })
+ render json: {
+ issues: issues.as_json(
+ only: [:iid, :title, :confidential],
+ include: {
+ assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
+ labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] }
+ }),
+ size: issues.total_count
+ }
end
def update
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index 12195c3cbb8..77934ff9962 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -78,8 +78,8 @@ class Projects::BuildsController < Projects::ApplicationController
end
def raw
- if @build.has_trace?
- send_file @build.path_to_trace, type: 'text/plain; charset=utf-8', disposition: 'inline'
+ if @build.has_trace_file?
+ send_file @build.trace_file_path, type: 'text/plain; charset=utf-8', disposition: 'inline'
else
render_404
end
diff --git a/app/controllers/projects/discussions_controller.rb b/app/controllers/projects/discussions_controller.rb
index b2e8733ccb7..d174e1145a7 100644
--- a/app/controllers/projects/discussions_controller.rb
+++ b/app/controllers/projects/discussions_controller.rb
@@ -38,6 +38,6 @@ class Projects::DiscussionsController < Projects::ApplicationController
end
def module_enabled
- render_404 unless @project.merge_requests_enabled
+ render_404 unless @project.feature_available?(:merge_requests, current_user)
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 7b0189150f8..72d2d361878 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -201,7 +201,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
def module_enabled
- return render_404 unless @project.issues_enabled && @project.default_issues_tracker?
+ return render_404 unless @project.feature_available?(:issues, current_user) && @project.default_issues_tracker?
end
def redirect_to_external_issue_tracker
@@ -212,7 +212,7 @@ class Projects::IssuesController < Projects::ApplicationController
if action_name == 'new'
redirect_to external.new_issue_path
else
- redirect_to external.issues_url
+ redirect_to external.project_path
end
end
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 0ca675623e5..28fa4a5b141 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -99,7 +99,7 @@ class Projects::LabelsController < Projects::ApplicationController
protected
def module_enabled
- unless @project.issues_enabled || @project.merge_requests_enabled
+ unless @project.feature_available?(:issues, current_user) || @project.feature_available?(:merge_requests, current_user)
return render_404
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 6a8c7166b39..4f9ca0097a1 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -83,12 +83,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def diffs
apply_diff_view_cookie!
- @merge_request_diff = @merge_request.merge_request_diff
+ @merge_request_diff =
+ if params[:diff_id]
+ @merge_request.merge_request_diffs.find(params[:diff_id])
+ else
+ @merge_request.merge_request_diff
+ end
respond_to do |format|
format.html { define_discussion_vars }
format.json do
- @diffs = @merge_request.diffs(diff_options)
+ unless @merge_request_diff.latest?
+ # Disable comments if browsing older version of the diff
+ @diff_notes_disabled = true
+ end
+
+ @diffs = @merge_request_diff.diffs(diff_options)
render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") }
end
@@ -403,7 +413,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def module_enabled
- return render_404 unless @project.merge_requests_enabled
+ return render_404 unless @project.feature_available?(:merge_requests, current_user)
end
def validates_merge_request
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index da2892bfb3f..ff63f22cb5b 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -106,7 +106,7 @@ class Projects::MilestonesController < Projects::ApplicationController
end
def module_enabled
- unless @project.issues_enabled || @project.merge_requests_enabled
+ unless @project.feature_available?(:issues, current_user) || @project.feature_available?(:merge_requests, current_user)
return render_404
end
end
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 6d0a7ee1031..17ceefec3b8 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -94,7 +94,7 @@ class Projects::SnippetsController < Projects::ApplicationController
end
def module_enabled
- return render_404 unless @project.snippets_enabled
+ return render_404 unless @project.feature_available?(:snippets, current_user)
end
def snippet_params
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 8592579abbd..6ea8ee62bc5 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -1,4 +1,6 @@
class Projects::TagsController < Projects::ApplicationController
+ include SortingHelper
+
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
@@ -6,8 +8,10 @@ class Projects::TagsController < Projects::ApplicationController
before_action :authorize_admin_project!, only: [:destroy]
def index
- @sort = params[:sort] || 'name'
- @tags = @repository.tags_sorted_by(@sort)
+ params[:sort] = params[:sort].presence || 'name'
+
+ @sort = params[:sort]
+ @tags = TagsFinder.new(@repository, params).execute
@tags = Kaminari.paginate_array(@tags).page(params[:page])
@releases = project.releases.where(tag: @tags.map(&:name))
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index fc52cd2f367..eaa38fa6c98 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -303,13 +303,23 @@ class ProjectsController < Projects::ApplicationController
end
def project_params
+ project_feature_attributes =
+ {
+ project_feature_attributes:
+ [
+ :issues_access_level, :builds_access_level,
+ :wiki_access_level, :merge_requests_access_level, :snippets_access_level
+ ]
+ }
+
params.require(:project).permit(
:name, :path, :description, :issues_tracker, :tag_list, :runners_token,
- :issues_enabled, :merge_requests_enabled, :snippets_enabled, :container_registry_enabled,
+ :container_registry_enabled,
:issues_tracker_id, :default_branch,
- :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
- :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
- :public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled
+ :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
+ :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
+ :public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled,
+ :lfs_enabled, project_feature_attributes
)
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 33daac0399e..60996b181f2 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -64,7 +64,7 @@ class IssuableFinder
if project?
@project = Project.find(params[:project_id])
- unless Ability.abilities.allowed?(current_user, :read_project, @project)
+ unless Ability.allowed?(current_user, :read_project, @project)
@project = nil
end
else
diff --git a/app/finders/tags_finder.rb b/app/finders/tags_finder.rb
new file mode 100644
index 00000000000..b474f0805dc
--- /dev/null
+++ b/app/finders/tags_finder.rb
@@ -0,0 +1,29 @@
+class TagsFinder
+ def initialize(repository, params)
+ @repository = repository
+ @params = params
+ end
+
+ def execute
+ tags = @repository.tags_sorted_by(sort)
+ filter_by_name(tags)
+ end
+
+ private
+
+ def sort
+ @params[:sort].presence
+ end
+
+ def search
+ @params[:search].presence
+ end
+
+ def filter_by_name(tags)
+ if search
+ tags.select { |tag| tag.name.include?(search) }
+ else
+ tags
+ end
+ end
+end
diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb
index 06b3e8a9502..a93a63bdb9b 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -83,7 +83,7 @@ class TodosFinder
if project?
@project = Project.find(params[:project_id])
- unless Ability.abilities.allowed?(current_user, :read_project, @project)
+ unless Ability.allowed?(current_user, :read_project, @project)
@project = nil
end
else
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index f3733b01721..5f3765cad0d 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -110,7 +110,7 @@ module ApplicationHelper
project = event.project
# Skip if project repo is empty or MR disabled
- return false unless project && !project.empty_repo? && project.merge_requests_enabled
+ return false unless project && !project.empty_repo? && project.feature_available?(:merge_requests, current_user)
# Skip if user already created appropriate MR
return false if project.merge_requests.where(source_branch: event.branch_name).opened.any?
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index bb285a17baf..639deb7c521 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -25,6 +25,11 @@ module CiStatusHelper
end
end
+ def ci_status_for_statuseable(subject)
+ status = subject.try(:status) || 'not found'
+ status.humanize
+ end
+
def ci_icon_for_status(status)
icon_name =
case status
@@ -41,7 +46,7 @@ module CiStatusHelper
when 'play'
'icon_play'
when 'created'
- 'icon_status_pending'
+ 'icon_status_created'
else
'icon_status_cancel'
end
@@ -66,10 +71,10 @@ module CiStatusHelper
Ci::Runner.shared.blank?
end
- def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '')
+ def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '', container: 'body')
klass = "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}"
title = "#{type.titleize}: #{ci_label_for_status(status)}"
- data = { toggle: 'tooltip', placement: tooltip_placement }
+ data = { toggle: 'tooltip', placement: tooltip_placement, container: container }
if path
link_to ci_icon_for_status(status), path,
diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb
index f1dc906cab4..aa54ee07bdc 100644
--- a/app/helpers/compare_helper.rb
+++ b/app/helpers/compare_helper.rb
@@ -3,7 +3,7 @@ module CompareHelper
from.present? &&
to.present? &&
from != to &&
- project.merge_requests_enabled &&
+ project.feature_available?(:merge_requests, current_user) &&
project.repository.branch_names.include?(from) &&
project.repository.branch_names.include?(to)
end
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 5386ddadc62..a322a90cc4e 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -149,4 +149,20 @@ module GitlabRoutingHelper
def resend_invite_group_member_path(group_member, *args)
resend_invite_group_group_member_path(group_member.source, group_member)
end
+
+ # Artifacts
+
+ def artifacts_action_path(path, project, build)
+ action, path_params = path.split('/', 2)
+ args = [project.namespace, project, build, path_params]
+
+ case action
+ when 'download'
+ download_namespace_project_build_artifacts_path(*args)
+ when 'browse'
+ browse_namespace_project_build_artifacts_path(*args)
+ when 'file'
+ file_namespace_project_build_artifacts_path(*args)
+ end
+ end
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index b9baeb1d6c4..5c04bba323f 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -49,6 +49,19 @@ module IssuablesHelper
end
end
+ def project_dropdown_label(project_id, default_label)
+ return default_label if project_id.nil?
+ return "Any project" if project_id == "0"
+
+ project = Project.find_by(id: project_id)
+
+ if project
+ project.name_with_namespace
+ else
+ default_label
+ end
+ end
+
def milestone_dropdown_label(milestone_title, default_label = "Milestone")
if milestone_title == Milestone::Upcoming.name
milestone_title = Milestone::Upcoming.title
diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb
index eb651e3687e..5d82abfca79 100644
--- a/app/helpers/lfs_helper.rb
+++ b/app/helpers/lfs_helper.rb
@@ -23,10 +23,14 @@ module LfsHelper
end
def lfs_download_access?
+ return false unless project.lfs_enabled?
+
project.public? || ci? || (user && user.can?(:download_code, project))
end
def lfs_upload_access?
+ return false unless project.lfs_enabled?
+
user && user.can?(:push_code, project)
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index db6e731c744..a9e175c3f5c 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -98,6 +98,6 @@ module MergeRequestsHelper
end
def merge_request_button_visibility(merge_request, closed)
- return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?)
+ return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork?
end
end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 6c1cc6ef072..2b0ff6c0d00 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -25,6 +25,8 @@ module NavHelper
current_path?('merge_requests#commits') ||
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"
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 356f27f2d5d..4c685b97c03 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -61,7 +61,9 @@ module ProjectsHelper
project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" }
if current_user
- project_link << icon("chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle", aria: { label: "Toggle switch project dropdown" }, data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" })
+ 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
+ icon("chevron-down")
+ end
end
full_title = "#{namespace_link} / #{project_link}".html_safe
@@ -187,6 +189,18 @@ module ProjectsHelper
nav_tabs.flatten
end
+ def project_lfs_status(project)
+ if project.lfs_enabled?
+ content_tag(:span, class: 'lfs-enabled') do
+ 'Enabled'
+ end
+ else
+ content_tag(:span, class: 'lfs-disabled') do
+ 'Disabled'
+ end
+ end
+ end
+
def git_user_name
if current_user
current_user.name
@@ -400,4 +414,23 @@ module ProjectsHelper
message.strip.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]")
end
+
+ def project_feature_options
+ {
+ 'Disabled' => ProjectFeature::DISABLED,
+ 'Only team members' => ProjectFeature::PRIVATE,
+ '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 c0195713f4a..4549c2e5bb6 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -44,7 +44,7 @@ module SearchHelper
def help_autocomplete
[
{ category: "Help", label: "API Help", url: help_page_path("api/README") },
- { category: "Help", label: "Markdown Help", url: help_page_path("markdown/markdown") },
+ { category: "Help", label: "Markdown Help", url: help_page_path("user/markdown") },
{ category: "Help", label: "Permissions Help", url: help_page_path("user/permissions") },
{ category: "Help", label: "Public Access Help", url: help_page_path("public_access/public_access") },
{ category: "Help", label: "Rake Tasks Help", url: help_page_path("raketasks/README") },
diff --git a/app/helpers/sentry_helper.rb b/app/helpers/sentry_helper.rb
index f8cccade15b..3d255df66a0 100644
--- a/app/helpers/sentry_helper.rb
+++ b/app/helpers/sentry_helper.rb
@@ -1,27 +1,9 @@
module SentryHelper
def sentry_enabled?
- Rails.env.production? && current_application_settings.sentry_enabled?
+ Gitlab::Sentry.enabled?
end
def sentry_context
- return unless sentry_enabled?
-
- if current_user
- Raven.user_context(
- id: current_user.id,
- email: current_user.email,
- username: current_user.username,
- )
- end
-
- Raven.tags_context(program: sentry_program_context)
- end
-
- def sentry_program_context
- if Sidekiq.server?
- 'sidekiq'
- else
- 'rails'
- end
+ Gitlab::Sentry.context(current_user)
end
end
diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb
index fb85544df2d..c0ec1634cdb 100644
--- a/app/helpers/tags_helper.rb
+++ b/app/helpers/tags_helper.rb
@@ -3,6 +3,16 @@ module TagsHelper
"/tags/#{tag}"
end
+ def filter_tags_path(options = {})
+ exist_opts = {
+ search: params[:search],
+ sort: params[:sort]
+ }
+
+ options = exist_opts.merge(options)
+ namespace_project_tags_path(@project.namespace, @project, @id, options)
+ end
+
def tag_list(project)
html = ''
project.tag_list.each do |tag|
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 0465327060e..1e86f648203 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -78,13 +78,11 @@ module TodosHelper
end
def todo_actions_options
- actions = [
- OpenStruct.new(id: '', title: 'Any Action'),
- OpenStruct.new(id: Todo::ASSIGNED, title: 'Assigned'),
- OpenStruct.new(id: Todo::MENTIONED, title: 'Mentioned')
+ [
+ { id: '', text: 'Any Action' },
+ { id: Todo::ASSIGNED, text: 'Assigned' },
+ { id: Todo::MENTIONED, text: 'Mentioned' }
]
-
- options_from_collection_for_select(actions, 'id', 'title', params[:action_id])
end
def todo_projects_options
@@ -92,22 +90,28 @@ module TodosHelper
projects = projects.includes(:namespace)
projects = projects.map do |project|
- OpenStruct.new(id: project.id, title: project.name_with_namespace)
+ { id: project.id, text: project.name_with_namespace }
end
- projects.unshift(OpenStruct.new(id: '', title: 'Any Project'))
-
- options_from_collection_for_select(projects, 'id', 'title', params[:project_id])
+ projects.unshift({ id: '', text: 'Any Project' }).to_json
end
def todo_types_options
- types = [
- OpenStruct.new(title: 'Any Type', name: ''),
- OpenStruct.new(title: 'Issue', name: 'Issue'),
- OpenStruct.new(title: 'Merge Request', name: 'MergeRequest')
+ [
+ { id: '', text: 'Any Type' },
+ { id: 'Issue', text: 'Issue' },
+ { id: 'MergeRequest', text: 'Merge Request' }
]
+ end
+
+ def todo_actions_dropdown_label(selected_action_id, default_action)
+ selected_action = todo_actions_options.find { |action| action[:id] == selected_action_id.to_i}
+ selected_action ? selected_action[:text] : default_action
+ end
- options_from_collection_for_select(types, 'name', 'title', params[:type])
+ def todo_types_dropdown_label(selected_type, default_type)
+ selected_type = todo_types_options.find { |type| type[:id] == selected_type && type[:id] != '' }
+ selected_type ? selected_type[:text] : default_type
end
private
diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb
index 8b83bbd93b7..61a574d3dc0 100644
--- a/app/mailers/base_mailer.rb
+++ b/app/mailers/base_mailer.rb
@@ -9,7 +9,7 @@ class BaseMailer < ActionMailer::Base
default reply_to: Proc.new { default_reply_to_address.format }
def can?
- Ability.abilities.allowed?(current_user, action, subject)
+ Ability.allowed?(current_user, action, subject)
end
private
diff --git a/app/models/ability.rb b/app/models/ability.rb
index a49dd703926..fa8f8bc3a5f 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -1,34 +1,5 @@
class Ability
class << self
- # rubocop: disable Metrics/CyclomaticComplexity
- def allowed(user, subject)
- return anonymous_abilities(user, subject) if user.nil?
- return [] unless user.is_a?(User)
- return [] if user.blocked?
-
- abilities_by_subject_class(user: user, subject: subject)
- end
-
- def abilities_by_subject_class(user:, subject:)
- case subject
- when CommitStatus then commit_status_abilities(user, subject)
- when Project then project_abilities(user, subject)
- when Issue then issue_abilities(user, subject)
- when Note then note_abilities(user, subject)
- when ProjectSnippet then project_snippet_abilities(user, subject)
- when PersonalSnippet then personal_snippet_abilities(user, subject)
- when MergeRequest then merge_request_abilities(user, subject)
- when Group then group_abilities(user, subject)
- when Namespace then namespace_abilities(user, subject)
- when GroupMember then group_member_abilities(user, subject)
- when ProjectMember then project_member_abilities(user, subject)
- when User then user_abilities
- when ExternalIssue, Deployment, Environment then project_abilities(user, subject.project)
- when Ci::Runner then runner_abilities(user, subject)
- else []
- end.concat(global_abilities(user))
- end
-
# Given a list of users and a project this method returns the users that can
# read the given project.
def users_that_can_read_project(users, project)
@@ -61,359 +32,7 @@ class Ability
issues.select { |issue| issue.visible_to_user?(user) }
end
- # List of possible abilities for anonymous user
- def anonymous_abilities(user, subject)
- if subject.is_a?(PersonalSnippet)
- anonymous_personal_snippet_abilities(subject)
- elsif subject.is_a?(ProjectSnippet)
- anonymous_project_snippet_abilities(subject)
- elsif subject.is_a?(CommitStatus)
- anonymous_commit_status_abilities(subject)
- elsif subject.is_a?(Project) || subject.respond_to?(:project)
- anonymous_project_abilities(subject)
- elsif subject.is_a?(Group) || subject.respond_to?(:group)
- anonymous_group_abilities(subject)
- elsif subject.is_a?(User)
- anonymous_user_abilities
- else
- []
- end
- end
-
- def anonymous_project_abilities(subject)
- project = if subject.is_a?(Project)
- subject
- else
- subject.project
- end
-
- if project && project.public?
- rules = [
- :read_project,
- :read_board,
- :read_list,
- :read_wiki,
- :read_label,
- :read_milestone,
- :read_project_snippet,
- :read_project_member,
- :read_merge_request,
- :read_note,
- :read_pipeline,
- :read_commit_status,
- :read_container_image,
- :download_code
- ]
-
- # Allow to read builds by anonymous user if guests are allowed
- rules << :read_build if project.public_builds?
-
- # Allow to read issues by anonymous user if issue is not confidential
- rules << :read_issue unless subject.is_a?(Issue) && subject.confidential?
-
- rules - project_disabled_features_rules(project)
- else
- []
- end
- end
-
- def anonymous_commit_status_abilities(subject)
- rules = anonymous_project_abilities(subject.project)
- # If subject is Ci::Build which inherits from CommitStatus filter the abilities
- rules = filter_build_abilities(rules) if subject.is_a?(Ci::Build)
- rules
- end
-
- def anonymous_group_abilities(subject)
- rules = []
-
- group = if subject.is_a?(Group)
- subject
- else
- subject.group
- end
-
- rules << :read_group if group.public?
-
- rules
- end
-
- def anonymous_personal_snippet_abilities(snippet)
- if snippet.public?
- [:read_personal_snippet]
- else
- []
- end
- end
-
- def anonymous_project_snippet_abilities(snippet)
- if snippet.public?
- [:read_project_snippet]
- else
- []
- end
- end
-
- def anonymous_user_abilities
- [:read_user] unless restricted_public_level?
- end
-
- def global_abilities(user)
- rules = []
- rules << :create_group if user.can_create_group
- rules << :read_users_list
- rules
- end
-
- def project_abilities(user, project)
- key = "/user/#{user.id}/project/#{project.id}"
-
- if RequestStore.active?
- RequestStore.store[key] ||= uncached_project_abilities(user, project)
- else
- uncached_project_abilities(user, project)
- end
- end
-
- def uncached_project_abilities(user, project)
- rules = []
- # Push abilities on the users team role
- rules.push(*project_team_rules(project.team, user))
-
- owner = user.admin? ||
- project.owner == user ||
- (project.group && project.group.has_owner?(user))
-
- if owner
- rules.push(*project_owner_rules)
- end
-
- if project.public? || (project.internal? && !user.external?)
- rules.push(*public_project_rules)
-
- # Allow to read builds for internal projects
- rules << :read_build if project.public_builds?
-
- unless owner || project.team.member?(user) || project_group_member?(project, user)
- rules << :request_access if project.request_access_enabled
- end
- end
-
- if project.archived?
- rules -= project_archived_rules
- end
-
- (rules - project_disabled_features_rules(project)).uniq
- end
-
- def project_team_rules(team, user)
- # Rules based on role in project
- if team.master?(user)
- project_master_rules
- elsif team.developer?(user)
- project_dev_rules
- elsif team.reporter?(user)
- project_report_rules
- elsif team.guest?(user)
- project_guest_rules
- else
- []
- end
- end
-
- def public_project_rules
- @public_project_rules ||= project_guest_rules + [
- :download_code,
- :fork_project,
- :read_commit_status,
- :read_pipeline,
- :read_container_image
- ]
- end
-
- def project_guest_rules
- @project_guest_rules ||= [
- :read_project,
- :read_wiki,
- :read_issue,
- :read_board,
- :read_list,
- :read_label,
- :read_milestone,
- :read_project_snippet,
- :read_project_member,
- :read_merge_request,
- :read_note,
- :create_project,
- :create_issue,
- :create_note,
- :upload_file
- ]
- end
-
- def project_report_rules
- @project_report_rules ||= project_guest_rules + [
- :download_code,
- :fork_project,
- :create_project_snippet,
- :update_issue,
- :admin_issue,
- :admin_label,
- :admin_list,
- :read_commit_status,
- :read_build,
- :read_container_image,
- :read_pipeline,
- :read_environment,
- :read_deployment
- ]
- end
-
- def project_dev_rules
- @project_dev_rules ||= project_report_rules + [
- :admin_merge_request,
- :update_merge_request,
- :create_commit_status,
- :update_commit_status,
- :create_build,
- :update_build,
- :create_pipeline,
- :update_pipeline,
- :create_merge_request,
- :create_wiki,
- :push_code,
- :resolve_note,
- :create_container_image,
- :update_container_image,
- :create_environment,
- :create_deployment
- ]
- end
-
- def project_archived_rules
- @project_archived_rules ||= [
- :create_merge_request,
- :push_code,
- :push_code_to_protected_branches,
- :update_merge_request,
- :admin_merge_request
- ]
- end
-
- def project_master_rules
- @project_master_rules ||= project_dev_rules + [
- :push_code_to_protected_branches,
- :update_project_snippet,
- :update_environment,
- :update_deployment,
- :admin_milestone,
- :admin_project_snippet,
- :admin_project_member,
- :admin_merge_request,
- :admin_note,
- :admin_wiki,
- :admin_project,
- :admin_commit_status,
- :admin_build,
- :admin_container_image,
- :admin_pipeline,
- :admin_environment,
- :admin_deployment
- ]
- end
-
- def project_owner_rules
- @project_owner_rules ||= project_master_rules + [
- :change_namespace,
- :change_visibility_level,
- :rename_project,
- :remove_project,
- :archive_project,
- :remove_fork_project,
- :destroy_merge_request,
- :destroy_issue
- ]
- end
-
- def project_disabled_features_rules(project)
- rules = []
-
- unless project.issues_enabled
- rules += named_abilities('issue')
- end
-
- unless project.merge_requests_enabled
- rules += named_abilities('merge_request')
- end
-
- unless project.issues_enabled or project.merge_requests_enabled
- rules += named_abilities('label')
- rules += named_abilities('milestone')
- end
-
- unless project.snippets_enabled
- rules += named_abilities('project_snippet')
- end
-
- unless project.wiki_enabled
- rules += named_abilities('wiki')
- end
-
- unless project.builds_enabled
- rules += named_abilities('build')
- rules += named_abilities('pipeline')
- rules += named_abilities('environment')
- rules += named_abilities('deployment')
- end
-
- unless project.container_registry_enabled
- rules += named_abilities('container_image')
- end
-
- rules
- end
-
- def group_abilities(user, group)
- rules = []
- rules << :read_group if can_read_group?(user, group)
-
- owner = user.admin? || group.has_owner?(user)
- master = owner || group.has_master?(user)
-
- # Only group masters and group owners can create new projects
- if master
- rules += [
- :create_projects,
- :admin_milestones
- ]
- end
-
- # Only group owner and administrators can admin group
- if owner
- rules += [
- :admin_group,
- :admin_namespace,
- :admin_group_member,
- :change_visibility_level
- ]
- end
-
- if group.public? || (group.internal? && !user.external?)
- rules << :request_access if group.request_access_enabled && group.users.exclude?(user)
- end
-
- rules.flatten
- end
-
- def can_read_group?(user, group)
- return true if user.admin?
- return true if group.public?
- return true if group.internal? && !user.external?
- return true if group.users.include?(user)
-
- GroupProjectsFinder.new(group).execute(user).any?
- end
-
+ # TODO: make this private and use the actual abilities stuff for this
def can_edit_note?(user, note)
return false if !note.editable? || !user.present?
return true if note.author == user || user.admin?
@@ -426,207 +45,23 @@ class Ability
end
end
- def namespace_abilities(user, namespace)
- rules = []
-
- # Only namespace owner and administrators can admin it
- if namespace.owner == user || user.admin?
- rules += [
- :create_projects,
- :admin_namespace
- ]
- end
-
- rules.flatten
- end
-
- [:issue, :merge_request].each do |name|
- define_method "#{name}_abilities" do |user, subject|
- rules = []
-
- if subject.author == user || (subject.respond_to?(:assignee) && subject.assignee == user)
- rules += [
- :"read_#{name}",
- :"update_#{name}",
- ]
- end
-
- rules += project_abilities(user, subject.project)
- rules = filter_confidential_issues_abilities(user, subject, rules) if subject.is_a?(Issue)
- rules
- end
- end
-
- def note_abilities(user, note)
- rules = []
-
- if note.author == user
- rules += [
- :read_note,
- :update_note,
- :admin_note,
- :resolve_note
- ]
- end
-
- if note.respond_to?(:project) && note.project
- rules += project_abilities(user, note.project)
- end
-
- if note.for_merge_request? && note.noteable.author == user
- rules << :resolve_note
- end
-
- rules
- end
-
- def personal_snippet_abilities(user, snippet)
- rules = []
-
- if snippet.author == user
- rules += [
- :read_personal_snippet,
- :update_personal_snippet,
- :admin_personal_snippet
- ]
- end
-
- if snippet.public? || (snippet.internal? && !user.external?)
- rules << :read_personal_snippet
- end
-
- rules
+ def allowed?(user, action, subject)
+ allowed(user, subject).include?(action)
end
- def project_snippet_abilities(user, snippet)
- rules = []
-
- if snippet.author == user || user.admin?
- rules += [
- :read_project_snippet,
- :update_project_snippet,
- :admin_project_snippet
- ]
- end
-
- if snippet.public? || (snippet.internal? && !user.external?) || (snippet.private? && snippet.project.team.member?(user))
- rules << :read_project_snippet
- end
-
- rules
- end
-
- def group_member_abilities(user, subject)
- rules = []
- target_user = subject.user
- group = subject.group
-
- unless group.last_owner?(target_user)
- can_manage = group_abilities(user, group).include?(:admin_group_member)
-
- if can_manage
- rules << :update_group_member
- rules << :destroy_group_member
- elsif user == target_user
- rules << :destroy_group_member
- end
- end
-
- rules
- end
-
- def project_member_abilities(user, subject)
- rules = []
- target_user = subject.user
- project = subject.project
-
- unless target_user == project.owner
- can_manage = project_abilities(user, project).include?(:admin_project_member)
-
- if can_manage
- rules << :update_project_member
- rules << :destroy_project_member
- elsif user == target_user
- rules << :destroy_project_member
- end
- end
-
- rules
- end
-
- def commit_status_abilities(user, subject)
- rules = project_abilities(user, subject.project)
- # If subject is Ci::Build which inherits from CommitStatus filter the abilities
- rules = filter_build_abilities(rules) if subject.is_a?(Ci::Build)
- rules
- end
-
- def filter_build_abilities(rules)
- # If we can't read build we should also not have that
- # ability when looking at this in context of commit_status
- %w(read create update admin).each do |rule|
- rules.delete(:"#{rule}_commit_status") unless rules.include?(:"#{rule}_build")
- end
- rules
- end
-
- def runner_abilities(user, runner)
- if user.is_admin?
- [:assign_runner]
- elsif runner.is_shared? || runner.locked?
- []
- elsif user.ci_authorized_runners.include?(runner)
- [:assign_runner]
- else
- []
- end
- end
+ def allowed(user, subject)
+ return uncached_allowed(user, subject) unless RequestStore.active?
- def user_abilities
- [:read_user]
- end
-
- def abilities
- @abilities ||= begin
- abilities = Six.new
- abilities << self
- abilities
- end
+ user_key = user ? user.id : 'anonymous'
+ subject_key = subject ? "#{subject.class.name}/#{subject.id}" : 'global'
+ key = "/ability/#{user_key}/#{subject_key}"
+ RequestStore[key] ||= uncached_allowed(user, subject).freeze
end
private
- def restricted_public_level?
- current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
- end
-
- def named_abilities(name)
- [
- :"read_#{name}",
- :"create_#{name}",
- :"update_#{name}",
- :"admin_#{name}"
- ]
- end
-
- def filter_confidential_issues_abilities(user, issue, rules)
- return rules if user.admin? || !issue.confidential?
-
- unless issue.author == user || issue.assignee == user || issue.project.team.member?(user, Gitlab::Access::REPORTER)
- rules.delete(:admin_issue)
- rules.delete(:read_issue)
- rules.delete(:update_issue)
- end
-
- rules
- end
-
- def project_group_member?(project, user)
- project.group &&
- (
- project.group.members.exists?(user_id: user.id) ||
- project.group.requesters.exists?(user_id: user.id)
- )
+ def uncached_allowed(user, subject)
+ BasePolicy.class_for(subject).abilities(user, subject)
end
end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index f0bcb2d7cda..246477ffe88 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -146,7 +146,7 @@ class ApplicationSetting < ActiveRecord::Base
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
domain_whitelist: Settings.gitlab['domain_whitelist'],
- import_sources: %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project],
+ import_sources: %w[github bitbucket gitlab google_code fogbugz git gitlab_project],
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'],
require_two_factor_authentication: false,
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 096b3b801af..61052437318 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -208,22 +208,31 @@ module Ci
end
end
+ def has_trace_file?
+ File.exist?(path_to_trace) || has_old_trace_file?
+ end
+
def has_trace?
raw_trace.present?
end
def raw_trace
- if File.file?(path_to_trace)
- File.read(path_to_trace)
- elsif project.ci_id && File.file?(old_path_to_trace)
- # Temporary fix for build trace data integrity
- File.read(old_path_to_trace)
+ if File.exist?(trace_file_path)
+ File.read(trace_file_path)
else
# backward compatibility
read_attribute :trace
end
end
+ ##
+ # Deprecated
+ #
+ # This is a hotfix for CI build data integrity, see #4246
+ def has_old_trace_file?
+ project.ci_id && File.exist?(old_path_to_trace)
+ end
+
def trace
trace = raw_trace
if project && trace.present? && project.runners_token.present?
@@ -262,6 +271,14 @@ module Ci
end
end
+ def trace_file_path
+ if has_old_trace_file?
+ old_path_to_trace
+ else
+ path_to_trace
+ end
+ end
+
def dir_to_trace
File.join(
Settings.gitlab_ci.builds_path,
@@ -352,7 +369,7 @@ module Ci
end
def artifacts?
- !artifacts_expired? && artifacts_file.exists?
+ !artifacts_expired? && self[:artifacts_file].present?
end
def artifacts_metadata?
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 087abe4cbb1..bd1737c7587 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -1,7 +1,7 @@
module Ci
class Pipeline < ActiveRecord::Base
extend Ci::Model
- include Statuseable
+ include HasStatus
self.table_name = 'ci_commits'
@@ -65,8 +65,8 @@ module Ci
end
# ref can't be HEAD or SHA, can only be branch/tag name
- scope :latest_successful_for, ->(ref = default_branch) do
- where(ref: ref).success.order(id: :desc).limit(1)
+ def self.latest_successful_for(ref)
+ where(ref: ref).order(id: :desc).success.first
end
def self.truncate_sha(sha)
@@ -83,7 +83,7 @@ module Ci
end
def stages_with_latest_statuses
- statuses.latest.order(:stage_idx).group_by(&:stage)
+ statuses.latest.includes(project: :namespace).order(:stage_idx).group_by(&:stage)
end
def project_id
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 817d063e4a2..e64fd1e0c1b 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -108,15 +108,6 @@ class Commit
@diff_line_count
end
- # Returns a string describing the commit for use in a link title
- #
- # Example
- #
- # "Commit: Alex Denisov - Project git clone panel"
- def link_title
- "Commit: #{author_name} - #{title}"
- end
-
# Returns the commits title.
#
# Usually, the commit title is the first line of the commit message.
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index 630ee9601e0..656a242c265 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -4,12 +4,10 @@
#
# range = CommitRange.new('f3f85602...e86e1013', project)
# range.exclude_start? # => false
-# range.reference_title # => "Commits f3f85602 through e86e1013"
# range.to_s # => "f3f85602...e86e1013"
#
# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae', project)
# range.exclude_start? # => true
-# range.reference_title # => "Commits f3f85602^ through e86e1013"
# range.to_param # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"}
# range.to_s # => "f3f85602..e86e1013"
#
@@ -109,11 +107,6 @@ class CommitRange
reference
end
- # Returns a String for use in a link's title attribute
- def reference_title
- "Commits #{sha_start} through #{sha_to}"
- end
-
# Return a Hash of parameters for passing to a URL helper
#
# See `namespace_project_compare_url`
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 84ceeac7d3e..4a628924499 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -1,5 +1,5 @@
class CommitStatus < ActiveRecord::Base
- include Statuseable
+ include HasStatus
include Importable
self.table_name = 'ci_builds'
@@ -25,6 +25,8 @@ class CommitStatus < ActiveRecord::Base
scope :retried, -> { where.not(id: latest) }
scope :ordered, -> { order(:name) }
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
+ scope :latest_ci_stages, -> { latest.ordered.includes(project: :namespace) }
+ scope :retried_ci_stages, -> { retried.ordered.includes(project: :namespace) }
state_machine :status do
event :enqueue do
diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb
index 800a16ab246..d8d4575bb4d 100644
--- a/app/models/concerns/awardable.rb
+++ b/app/models/concerns/awardable.rb
@@ -2,7 +2,7 @@ module Awardable
extend ActiveSupport::Concern
included do
- has_many :award_emoji, -> { includes(:user) }, as: :awardable, dependent: :destroy
+ has_many :award_emoji, -> { includes(:user).order(:id) }, as: :awardable, dependent: :destroy
if self < Participable
# By default we always load award_emoji user association
@@ -59,6 +59,18 @@ module Awardable
true
end
+ def awardable_votes?(name)
+ AwardEmoji::UPVOTE_NAME == name || AwardEmoji::DOWNVOTE_NAME == name
+ end
+
+ def user_can_award?(current_user, name)
+ if user_authored?(current_user)
+ !awardable_votes?(normalize_name(name))
+ else
+ true
+ end
+ end
+
def awarded_emoji?(emoji_name, current_user)
award_emoji.where(name: emoji_name, user: current_user).exists?
end
diff --git a/app/models/concerns/statuseable.rb b/app/models/concerns/has_status.rb
index 750f937b724..f7b8352405c 100644
--- a/app/models/concerns/statuseable.rb
+++ b/app/models/concerns/has_status.rb
@@ -1,4 +1,4 @@
-module Statuseable
+module HasStatus
extend ActiveSupport::Concern
AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped]
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 8e11d4f57cf..22231b2e0f0 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -196,6 +196,10 @@ module Issuable
end
end
+ def user_authored?(user)
+ user == author
+ end
+
def subscribed_without_subscriptions?(user)
participants(user).include?(user)
end
diff --git a/app/models/concerns/note_on_diff.rb b/app/models/concerns/note_on_diff.rb
index a881fb83b7f..b8dd27a7afe 100644
--- a/app/models/concerns/note_on_diff.rb
+++ b/app/models/concerns/note_on_diff.rb
@@ -28,4 +28,8 @@ module NoteOnDiff
def can_be_award_emoji?
false
end
+
+ def to_discussion
+ Discussion.new([self])
+ end
end
diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb
new file mode 100644
index 00000000000..9216122923e
--- /dev/null
+++ b/app/models/concerns/project_features_compatibility.rb
@@ -0,0 +1,37 @@
+# Makes api V3 compatible with old project features permissions methods
+#
+# After migrating issues_enabled merge_requests_enabled builds_enabled snippets_enabled and wiki_enabled
+# fields to a new table "project_features", support for the old fields is still needed in the API.
+
+module ProjectFeaturesCompatibility
+ extend ActiveSupport::Concern
+
+ def wiki_enabled=(value)
+ write_feature_attribute(:wiki_access_level, value)
+ end
+
+ def builds_enabled=(value)
+ write_feature_attribute(:builds_access_level, value)
+ end
+
+ def merge_requests_enabled=(value)
+ write_feature_attribute(:merge_requests_access_level, value)
+ end
+
+ def issues_enabled=(value)
+ write_feature_attribute(:issues_access_level, value)
+ end
+
+ def snippets_enabled=(value)
+ write_feature_attribute(:snippets_access_level, value)
+ end
+
+ private
+
+ def write_feature_attribute(field, value)
+ build_project_feature unless project_feature
+
+ access_level = value == "true" ? ProjectFeature::ENABLED : ProjectFeature::DISABLED
+ project_feature.update_attribute(field, access_level)
+ end
+end
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index df2a9e3e84b..a3ac577cf3e 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -52,11 +52,11 @@ module Taskable
end
# Return a string that describes the current state of this Taskable's task
- # list items, e.g. "20 tasks (12 completed, 8 remaining)"
+ # list items, e.g. "12 of 20 tasks completed"
def task_status
return '' if description.blank?
sum = tasks.summary
- "#{sum.item_count} tasks (#{sum.complete_count} completed, #{sum.incomplete_count} remaining)"
+ "#{sum.complete_count} of #{sum.item_count} #{'task'.pluralize(sum.item_count)} completed"
end
end
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index c8320ff87fa..4442cefc7e9 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -107,10 +107,6 @@ class DiffNote < Note
self.noteable.find_diff_discussion(self.discussion_id)
end
- def to_discussion
- Discussion.new([self])
- end
-
private
def supported?
diff --git a/app/models/event.rb b/app/models/event.rb
index fd736d12359..a0b7b0dc2b5 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -65,7 +65,7 @@ class Event < ActiveRecord::Base
elsif created_project?
true
elsif issue? || issue_note?
- Ability.abilities.allowed?(user, :read_issue, note? ? note_target : target)
+ Ability.allowed?(user, :read_issue, note? ? note_target : target)
else
((merge_request? || note?) && target.present?) || milestone?
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 5330a07ee35..b0b1313f94a 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -10,14 +10,16 @@ class MergeRequest < ActiveRecord::Base
belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
belongs_to :merge_user, class_name: "User"
- has_one :merge_request_diff, dependent: :destroy
+ has_many :merge_request_diffs, dependent: :destroy
+ has_one :merge_request_diff,
+ -> { order('merge_request_diffs.id DESC') }
has_many :events, as: :target, dependent: :destroy
serialize :merge_params, Hash
- after_create :create_merge_request_diff, unless: :importing?
- after_update :update_merge_request_diff
+ after_create :ensure_merge_request_diff, unless: :importing?
+ after_update :reload_diff_if_branch_changed
delegate :commits, :real_size, to: :merge_request_diff, prefix: nil
@@ -89,13 +91,13 @@ class MergeRequest < ActiveRecord::Base
end
end
- validates :source_project, presence: true, unless: [:allow_broken, :importing?]
+ validates :source_project, presence: true, unless: [:allow_broken, :importing?, :closed_without_fork?]
validates :source_branch, presence: true
validates :target_project, presence: true
validates :target_branch, presence: true
validates :merge_user, presence: true, if: :merge_when_build_succeeds?
- validate :validate_branches, unless: [:allow_broken, :importing?]
- validate :validate_fork
+ validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?]
+ validate :validate_fork, unless: :closed_without_fork?
scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
@@ -170,10 +172,10 @@ class MergeRequest < ActiveRecord::Base
end
def diffs(diff_options = nil)
- if self.compare
- self.compare.diffs(diff_options)
+ if compare
+ compare.diffs(diff_options)
else
- Gitlab::Diff::FileCollection::MergeRequest.new(self, diff_options: diff_options)
+ merge_request_diff.diffs(diff_options)
end
end
@@ -184,8 +186,8 @@ class MergeRequest < ActiveRecord::Base
def diff_base_commit
if persisted?
merge_request_diff.base_commit
- elsif diff_start_commit && diff_head_commit
- self.target_project.merge_base_commit(diff_start_sha, diff_head_sha)
+ else
+ branch_merge_base_commit
end
end
@@ -238,12 +240,21 @@ class MergeRequest < ActiveRecord::Base
def source_branch_head
source_branch_ref = @source_branch_sha || source_branch
- source_project.repository.commit(source_branch) if source_branch_ref
+ source_project.repository.commit(source_branch_ref) if source_branch_ref
end
def target_branch_head
target_branch_ref = @target_branch_sha || target_branch
- target_project.repository.commit(target_branch) if target_branch_ref
+ target_project.repository.commit(target_branch_ref) if target_branch_ref
+ end
+
+ def branch_merge_base_commit
+ start_sha = target_branch_sha
+ head_sha = source_branch_sha
+
+ if start_sha && head_sha
+ target_project.merge_base_commit(start_sha, head_sha)
+ end
end
def target_branch_sha
@@ -267,16 +278,16 @@ class MergeRequest < ActiveRecord::Base
# Return diff_refs instance trying to not touch the git repository
def diff_sha_refs
if merge_request_diff && merge_request_diff.diff_refs_by_sha?
- return Gitlab::Diff::DiffRefs.new(
- base_sha: merge_request_diff.base_commit_sha,
- start_sha: merge_request_diff.start_commit_sha,
- head_sha: merge_request_diff.head_commit_sha
- )
+ merge_request_diff.diff_refs
else
diff_refs
end
end
+ def branch_merge_base_sha
+ branch_merge_base_commit.try(:sha)
+ end
+
def validate_branches
if target_project == source_project && target_branch == source_branch
errors.add :branch_conflict, "You can not use same project/branch for source and target"
@@ -294,36 +305,49 @@ class MergeRequest < ActiveRecord::Base
def validate_fork
return true unless target_project && source_project
+ return true if target_project == source_project
+ return true unless forked_source_project_missing?
- if target_project == source_project
- true
- else
- # If source and target projects are different
- # we should check if source project is actually a fork of target project
- if source_project.forked_from?(target_project)
- true
- else
- errors.add :validate_fork,
- 'Source project is not a fork of target project'
- end
- end
+ errors.add :validate_fork,
+ 'Source project is not a fork of the target project'
end
- def update_merge_request_diff
+ def closed_without_fork?
+ closed? && forked_source_project_missing?
+ end
+
+ def forked_source_project_missing?
+ return false unless for_fork?
+ return true unless source_project
+
+ !source_project.forked_from?(target_project)
+ end
+
+ def ensure_merge_request_diff
+ merge_request_diff || create_merge_request_diff
+ end
+
+ def create_merge_request_diff
+ merge_request_diffs.create
+ reload_merge_request_diff
+ end
+
+ def reload_merge_request_diff
+ merge_request_diff(true)
+ end
+
+ def reload_diff_if_branch_changed
if source_branch_changed? || target_branch_changed?
reload_diff
end
end
def reload_diff
- return unless merge_request_diff && open?
+ return unless open?
old_diff_refs = self.diff_refs
-
- merge_request_diff.reload_content
-
+ create_merge_request_diff
MergeRequests::MergeRequestDiffCacheService.new.execute(self)
-
new_diff_refs = self.diff_refs
update_diff_notes_positions(
@@ -387,7 +411,7 @@ class MergeRequest < ActiveRecord::Base
def can_remove_source_branch?(current_user)
!source_project.protected_branch?(source_branch) &&
!source_project.root_ref?(source_branch) &&
- Ability.abilities.allowed?(current_user, :push_code, source_project) &&
+ Ability.allowed?(current_user, :push_code, source_project) &&
diff_head_commit == source_branch_head
end
@@ -705,7 +729,9 @@ class MergeRequest < ActiveRecord::Base
end
def pipeline
- @pipeline ||= source_project.pipeline(diff_head_sha, source_branch) if diff_head_sha && source_project
+ return unless diff_head_sha && source_project
+
+ @pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha)
end
def all_pipelines
@@ -777,8 +803,12 @@ class MergeRequest < ActiveRecord::Base
return @conflicts_can_be_resolved_in_ui = false unless has_complete_diff_refs?
begin
- @conflicts_can_be_resolved_in_ui = conflicts.files.each(&:lines)
- rescue Gitlab::Conflict::Parser::ParserError, Gitlab::Conflict::FileCollection::ConflictSideMissing
+ # Try to parse each conflict. If the MR's mergeable status hasn't been updated,
+ # ensure that we don't say there are conflicts to resolve when there are no conflict
+ # files.
+ conflicts.files.each(&:lines)
+ @conflicts_can_be_resolved_in_ui = conflicts.files.length > 0
+ rescue Rugged::OdbError, Gitlab::Conflict::Parser::ParserError, Gitlab::Conflict::FileCollection::ConflictSideMissing
@conflicts_can_be_resolved_in_ui = false
end
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 32cc6a3bfea..445179a4487 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -8,8 +8,6 @@ class MergeRequestDiff < ActiveRecord::Base
belongs_to :merge_request
- delegate :source_branch_sha, :target_branch_sha, :target_branch, :source_branch, to: :merge_request, prefix: nil
-
state_machine :state, initial: :empty do
state :collected
state :overflow
@@ -24,12 +22,47 @@ class MergeRequestDiff < ActiveRecord::Base
serialize :st_commits
serialize :st_diffs
- after_create :reload_content, unless: :importing?
- after_save :keep_around_commits, unless: :importing?
+ # All diff information is collected from repository after object is created.
+ # It allows you to override variables like head_commit_sha before getting diff.
+ after_create :save_git_content, unless: :importing?
+
+ def self.select_without_diff
+ select(column_names - ['st_diffs'])
+ end
- def reload_content
+ # Collect information about commits and diff from repository
+ # and save it to the database as serialized data
+ def save_git_content
+ ensure_commits_sha
+ save_commits
reload_commits
- reload_diffs
+ save_diffs
+ keep_around_commits
+ end
+
+ def ensure_commits_sha
+ merge_request.fetch_ref
+ self.start_commit_sha ||= merge_request.target_branch_sha
+ self.head_commit_sha ||= merge_request.source_branch_sha
+ self.base_commit_sha ||= find_base_sha
+ save
+ end
+
+ # Override head_commit_sha to keep compatibility with merge request diff
+ # created before version 8.4 that does not store head_commit_sha in separate db field.
+ def head_commit_sha
+ if persisted? && super.nil?
+ last_commit.try(:sha)
+ else
+ super
+ end
+ end
+
+ # This method will rely on repository branch sha
+ # in case start_commit_sha is nil. Its necesarry for old merge request diff
+ # created before version 8.4 to work
+ def safe_start_commit_sha
+ start_commit_sha || merge_request.target_branch_sha
end
def size
@@ -38,14 +71,11 @@ class MergeRequestDiff < ActiveRecord::Base
def raw_diffs(options = {})
if options[:ignore_whitespace_change]
- @raw_diffs_no_whitespace ||= begin
- compare = Gitlab::Git::Compare.new(
+ @diffs_no_whitespace ||=
+ Gitlab::Git::Compare.new(
repository.raw_repository,
- self.start_commit_sha || self.target_branch_sha,
- self.head_commit_sha || self.source_branch_sha,
- )
- compare.diffs(options)
- end
+ safe_start_commit_sha,
+ head_commit_sha).diffs(options)
else
@raw_diffs ||= {}
@raw_diffs[options] ||= load_diffs(st_diffs, options)
@@ -56,6 +86,11 @@ class MergeRequestDiff < ActiveRecord::Base
@commits ||= load_commits(st_commits || [])
end
+ def reload_commits
+ @commits = nil
+ commits
+ end
+
def last_commit
commits.first
end
@@ -65,55 +100,60 @@ class MergeRequestDiff < ActiveRecord::Base
end
def base_commit
- return unless self.base_commit_sha
+ return unless base_commit_sha
- project.commit(self.base_commit_sha)
+ project.commit(base_commit_sha)
end
def start_commit
- return unless self.start_commit_sha
+ return unless start_commit_sha
- project.commit(self.start_commit_sha)
+ project.commit(start_commit_sha)
end
def head_commit
- return last_commit unless self.head_commit_sha
+ return unless head_commit_sha
+
+ project.commit(head_commit_sha)
+ end
+
+ def diff_refs
+ return unless start_commit_sha || base_commit_sha
- project.commit(self.head_commit_sha)
+ Gitlab::Diff::DiffRefs.new(
+ base_sha: base_commit_sha,
+ start_sha: start_commit_sha,
+ head_sha: head_commit_sha
+ )
end
def diff_refs_by_sha?
base_commit_sha? && head_commit_sha? && start_commit_sha?
end
+ def diffs(diff_options = nil)
+ Gitlab::Diff::FileCollection::MergeRequestDiff.new(self, diff_options: diff_options)
+ end
+
+ def project
+ merge_request.target_project
+ end
+
def compare
@compare ||=
- begin
- # Update ref for merge request
- merge_request.fetch_ref
+ Gitlab::Git::Compare.new(
+ repository.raw_repository,
+ safe_start_commit_sha,
+ head_commit_sha
+ )
+ end
- Gitlab::Git::Compare.new(
- repository.raw_repository,
- self.target_branch_sha,
- self.source_branch_sha
- )
- end
+ def latest?
+ self == merge_request.merge_request_diff
end
private
- # Collect array of Git::Commit objects
- # between target and source branches
- def unmerged_commits
- commits = compare.commits
-
- if commits.present?
- commits = Commit.decorate(commits, merge_request.source_project).reverse
- end
-
- commits
- end
-
def dump_commits(commits)
commits.map(&:to_hash)
end
@@ -122,26 +162,21 @@ class MergeRequestDiff < ActiveRecord::Base
array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash), merge_request.source_project) }
end
- # Reload all commits related to current merge request from repo
+ # Load all commits related to current merge request diff from repo
# and save it as array of hashes in st_commits db field
- def reload_commits
+ def save_commits
new_attributes = {}
- commit_objects = unmerged_commits
+ commits = compare.commits
- if commit_objects.present?
- new_attributes[:st_commits] = dump_commits(commit_objects)
+ if commits.present?
+ commits = Commit.decorate(commits, merge_request.source_project).reverse
+ new_attributes[:st_commits] = dump_commits(commits)
end
update_columns_serialized(new_attributes)
end
- # Collect array of Git::Diff objects
- # between target and source branches
- def unmerged_diffs
- compare.diffs(Commit.max_diff_options)
- end
-
def dump_diffs(diffs)
if diffs.respond_to?(:map)
diffs.map(&:to_hash)
@@ -162,16 +197,16 @@ class MergeRequestDiff < ActiveRecord::Base
end
end
- # Reload diffs between branches related to current merge request from repo
+ # Load diffs between branches related to current merge request diff from repo
# and save it as array of hashes in st_diffs db field
- def reload_diffs
+ def save_diffs
new_attributes = {}
new_diffs = []
if commits.size.zero?
new_attributes[:state] = :empty
else
- diff_collection = unmerged_diffs
+ diff_collection = compare.diffs(Commit.max_diff_options)
if diff_collection.overflow?
# Set our state to 'overflow' to make the #empty? and #collected?
@@ -188,32 +223,17 @@ class MergeRequestDiff < ActiveRecord::Base
end
new_attributes[:st_diffs] = new_diffs
-
- new_attributes[:start_commit_sha] = self.target_branch_sha
- new_attributes[:head_commit_sha] = self.source_branch_sha
- new_attributes[:base_commit_sha] = branch_base_sha
-
update_columns_serialized(new_attributes)
-
- keep_around_commits
- end
-
- def project
- merge_request.target_project
end
def repository
project.repository
end
- def branch_base_commit
- return unless self.source_branch_sha && self.target_branch_sha
-
- project.merge_base_commit(self.source_branch_sha, self.target_branch_sha)
- end
+ def find_base_sha
+ return unless head_commit_sha && start_commit_sha
- def branch_base_sha
- branch_base_commit.try(:sha)
+ project.merge_base_commit(head_commit_sha, start_commit_sha).try(:sha)
end
def utf8_st_diffs
@@ -248,8 +268,8 @@ class MergeRequestDiff < ActiveRecord::Base
end
def keep_around_commits
- repository.keep_around(target_branch_sha)
- repository.keep_around(source_branch_sha)
- repository.keep_around(branch_base_sha)
+ repository.keep_around(start_commit_sha)
+ repository.keep_around(head_commit_sha)
+ repository.keep_around(base_commit_sha)
end
end
diff --git a/app/models/note.rb b/app/models/note.rb
index f2656df028b..b94e3cff2ce 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -223,6 +223,10 @@ class Note < ActiveRecord::Base
end
end
+ def user_authored?(user)
+ user == author
+ end
+
def award_emoji?
can_be_award_emoji? && contains_emoji_only?
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 8cf093be4c3..a6de2c48071 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -11,24 +11,23 @@ class Project < ActiveRecord::Base
include AfterCommitQueue
include CaseSensitivity
include TokenAuthenticatable
+ include ProjectFeaturesCompatibility
extend Gitlab::ConfigHelper
UNKNOWN_IMPORT_URL = 'http://unknown.git'
+ delegate :feature_available?, :builds_enabled?, :wiki_enabled?, :merge_requests_enabled?, to: :project_feature, allow_nil: true
+
default_value_for :archived, false
default_value_for :visibility_level, gitlab_config_features.visibility_level
- default_value_for :issues_enabled, gitlab_config_features.issues
- default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests
- default_value_for :builds_enabled, gitlab_config_features.builds
- default_value_for :wiki_enabled, gitlab_config_features.wiki
- default_value_for :snippets_enabled, gitlab_config_features.snippets
default_value_for :container_registry_enabled, gitlab_config_features.container_registry
default_value_for(:repository_storage) { current_application_settings.repository_storage }
default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
after_create :ensure_dir_exist
after_save :ensure_dir_exist, if: :namespace_id_changed?
+ after_initialize :setup_project_feature
# set last_activity_at to the same as created_at
after_create :set_last_activity_at
@@ -62,10 +61,10 @@ class Project < ActiveRecord::Base
belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id'
belongs_to :namespace
- has_one :board, dependent: :destroy
-
has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id'
+ has_one :board, dependent: :destroy
+
# Project services
has_many :services
has_one :campfire_service, dependent: :destroy
@@ -130,6 +129,7 @@ class Project < ActiveRecord::Base
has_many :notification_settings, dependent: :destroy, as: :source
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
+ has_one :project_feature, dependent: :destroy
has_many :commit_statuses, dependent: :destroy, class_name: 'CommitStatus', foreign_key: :gl_project_id
has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id
@@ -142,6 +142,7 @@ class Project < ActiveRecord::Base
has_many :deployments, dependent: :destroy
accepts_nested_attributes_for :variables, allow_destroy: true
+ accepts_nested_attributes_for :project_feature
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true
@@ -159,8 +160,6 @@ class Project < ActiveRecord::Base
length: { within: 0..255 },
format: { with: Gitlab::Regex.project_path_regex,
message: Gitlab::Regex.project_path_regex_message }
- validates :issues_enabled, :merge_requests_enabled,
- :wiki_enabled, inclusion: { in: [true, false] }
validates :namespace, presence: true
validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id
@@ -196,6 +195,9 @@ class Project < ActiveRecord::Base
scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) }
+ scope :with_builds_enabled, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id').where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0') }
+ scope :with_issues_enabled, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id').where('project_features.issues_access_level IS NULL or project_features.issues_access_level > 0') }
+
scope :active, -> { joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') }
scope :abandoned, -> { where('projects.last_activity_at < ?', 6.months.ago) }
@@ -390,6 +392,13 @@ class Project < ActiveRecord::Base
end
end
+ def lfs_enabled?
+ return false unless Gitlab.config.lfs.enabled
+ return Gitlab.config.lfs.enabled if self[:lfs_enabled].nil?
+
+ self[:lfs_enabled]
+ end
+
def repository_storage_path
Gitlab.config.repositories.storages[repository_storage]
end
@@ -436,7 +445,7 @@ class Project < ActiveRecord::Base
# ref can't be HEAD, can only be branch/tag name or SHA
def latest_successful_builds_for(ref = default_branch)
- latest_pipeline = pipelines.latest_successful_for(ref).first
+ latest_pipeline = pipelines.latest_successful_for(ref)
if latest_pipeline
latest_pipeline.builds.latest.with_artifacts
@@ -680,6 +689,10 @@ class Project < ActiveRecord::Base
update_column(:has_external_issue_tracker, services.external_issue_trackers.any?)
end
+ def has_wiki?
+ wiki_enabled? || has_external_wiki?
+ end
+
def external_wiki
if has_external_wiki.nil?
cache_has_external_wiki # Populate
@@ -1035,6 +1048,7 @@ class Project < ActiveRecord::Base
"refs/heads/#{branch}",
force: true)
repository.copy_gitattributes(branch)
+ repository.expire_avatar_cache(branch)
reload_default_branch
end
@@ -1095,16 +1109,21 @@ class Project < ActiveRecord::Base
!namespace.share_with_group_lock
end
- def pipeline(sha, ref)
+ def pipeline_for(ref, sha = nil)
+ sha ||= commit(ref).try(:sha)
+
+ return unless sha
+
pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
end
- def ensure_pipeline(sha, ref, current_user = nil)
- pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref, user: current_user)
+ def ensure_pipeline(ref, sha, current_user = nil)
+ pipeline_for(ref, sha) ||
+ pipelines.create(sha: sha, ref: ref, user: current_user)
end
def enable_ci
- self.builds_enabled = true
+ project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
end
def any_runners?(&block)
@@ -1271,6 +1290,11 @@ class Project < ActiveRecord::Base
private
+ # Prevents the creation of project_feature record for every project
+ def setup_project_feature
+ build_project_feature unless project_feature
+ end
+
def default_branch_protected?
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL ||
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
new file mode 100644
index 00000000000..9c602c582bd
--- /dev/null
+++ b/app/models/project_feature.rb
@@ -0,0 +1,63 @@
+class ProjectFeature < ActiveRecord::Base
+ # == Project features permissions
+ #
+ # Grants access level to project tools
+ #
+ # Tools can be enabled only for users, everyone or disabled
+ # Access control is made only for non private projects
+ #
+ # levels:
+ #
+ # Disabled: not enabled for anyone
+ # Private: enabled only for team members
+ # Enabled: enabled for everyone able to access the project
+ #
+
+ # Permision levels
+ DISABLED = 0
+ PRIVATE = 10
+ ENABLED = 20
+
+ FEATURES = %i(issues merge_requests wiki snippets builds)
+
+ belongs_to :project
+
+ def feature_available?(feature, user)
+ raise ArgumentError, 'invalid project feature' unless FEATURES.include?(feature)
+
+ get_permission(user, public_send("#{feature}_access_level"))
+ end
+
+ def builds_enabled?
+ return true unless builds_access_level
+
+ builds_access_level > DISABLED
+ end
+
+ def wiki_enabled?
+ return true unless wiki_access_level
+
+ wiki_access_level > DISABLED
+ end
+
+ def merge_requests_enabled?
+ return true unless merge_requests_access_level
+
+ merge_requests_access_level > DISABLED
+ end
+
+ private
+
+ def get_permission(user, level)
+ case level
+ when DISABLED
+ false
+ when PRIVATE
+ user && (project.team.member?(user) || user.admin?)
+ when ENABLED
+ true
+ else
+ true
+ end
+ end
+end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index bdc3b9d1c1c..f891e8374d2 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -120,8 +120,21 @@ class Repository
commits
end
- def find_branch(name)
- raw_repository.branches.find { |branch| branch.name == name }
+ def find_branch(name, fresh_repo: true)
+ # Since the Repository object may have in-memory index changes, invalidating the memoized Repository object may
+ # cause unintended side effects. Because finding a branch is a read-only operation, we can safely instantiate
+ # a new repo here to ensure a consistent state to avoid a libgit2 bug where concurrent access (e.g. via git gc)
+ # may cause the branch to "disappear" erroneously or have the wrong SHA.
+ #
+ # See: https://github.com/libgit2/libgit2/issues/1534 and https://gitlab.com/gitlab-org/gitlab-ce/issues/15392
+ raw_repo =
+ if fresh_repo
+ Gitlab::Git::Repository.new(path_to_repo)
+ else
+ raw_repository
+ end
+
+ raw_repo.find_branch(name)
end
def find_tag(name)
@@ -1065,7 +1078,7 @@ class Repository
@avatar ||= cache.fetch(:avatar) do
AVATAR_FILES.find do |file|
- blob_at_branch('master', file)
+ blob_at_branch(root_ref, file)
end
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index ad3cfbc03e4..6996740eebd 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -433,7 +433,7 @@ class User < ActiveRecord::Base
#
# This logic is duplicated from `Ability#project_abilities` into a SQL form.
def projects_where_can_admin_issues
- authorized_projects(Gitlab::Access::REPORTER).non_archived.where.not(issues_enabled: false)
+ authorized_projects(Gitlab::Access::REPORTER).non_archived.with_issues_enabled
end
def is_admin?
@@ -460,16 +460,12 @@ class User < ActiveRecord::Base
can?(:create_group, nil)
end
- def abilities
- Ability.abilities
- end
-
def can_select_namespace?
several_namespaces? || admin
end
def can?(action, subject)
- abilities.allowed?(self, action, subject)
+ Ability.allowed?(self, action, subject)
end
def first_name
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
new file mode 100644
index 00000000000..118c100ca11
--- /dev/null
+++ b/app/policies/base_policy.rb
@@ -0,0 +1,116 @@
+class BasePolicy
+ class RuleSet
+ attr_reader :can_set, :cannot_set
+ def initialize(can_set, cannot_set)
+ @can_set = can_set
+ @cannot_set = cannot_set
+ end
+
+ def size
+ to_set.size
+ end
+
+ def self.empty
+ new(Set.new, Set.new)
+ end
+
+ def can?(ability)
+ @can_set.include?(ability) && !@cannot_set.include?(ability)
+ end
+
+ def include?(ability)
+ can?(ability)
+ end
+
+ def to_set
+ @can_set - @cannot_set
+ end
+
+ def merge(other)
+ @can_set.merge(other.can_set)
+ @cannot_set.merge(other.cannot_set)
+ end
+
+ def can!(*abilities)
+ @can_set.merge(abilities)
+ end
+
+ def cannot!(*abilities)
+ @cannot_set.merge(abilities)
+ end
+
+ def freeze
+ @can_set.freeze
+ @cannot_set.freeze
+ super
+ end
+ end
+
+ def self.abilities(user, subject)
+ new(user, subject).abilities
+ end
+
+ def self.class_for(subject)
+ return GlobalPolicy if subject.nil?
+
+ subject.class.ancestors.each do |klass|
+ next unless klass.name
+
+ begin
+ policy_class = "#{klass.name}Policy".constantize
+
+ # NOTE: the < operator here tests whether policy_class
+ # inherits from BasePolicy
+ return policy_class if policy_class < BasePolicy
+ rescue NameError
+ nil
+ end
+ end
+
+ raise "no policy for #{subject.class.name}"
+ end
+
+ attr_reader :user, :subject
+ def initialize(user, subject)
+ @user = user
+ @subject = subject
+ end
+
+ def abilities
+ return RuleSet.empty if @user && @user.blocked?
+ return anonymous_abilities if @user.nil?
+ collect_rules { rules }
+ end
+
+ def anonymous_abilities
+ collect_rules { anonymous_rules }
+ end
+
+ def anonymous_rules
+ rules
+ end
+
+ def delegate!(new_subject)
+ @rule_set.merge(Ability.allowed(@user, new_subject))
+ end
+
+ def can?(rule)
+ @rule_set.can?(rule)
+ end
+
+ def can!(*rules)
+ @rule_set.can!(*rules)
+ end
+
+ def cannot!(*rules)
+ @rule_set.cannot!(*rules)
+ end
+
+ private
+
+ def collect_rules(&b)
+ @rule_set = RuleSet.empty
+ yield
+ @rule_set
+ end
+end
diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb
new file mode 100644
index 00000000000..2232e231cf8
--- /dev/null
+++ b/app/policies/ci/build_policy.rb
@@ -0,0 +1,13 @@
+module Ci
+ class BuildPolicy < CommitStatusPolicy
+ def rules
+ super
+
+ # If we can't read build we should also not have that
+ # ability when looking at this in context of commit_status
+ %w(read create update admin).each do |rule|
+ cannot! :"#{rule}_commit_status" unless can? :"#{rule}_build"
+ end
+ end
+ end
+end
diff --git a/app/policies/ci/runner_policy.rb b/app/policies/ci/runner_policy.rb
new file mode 100644
index 00000000000..7edd383530d
--- /dev/null
+++ b/app/policies/ci/runner_policy.rb
@@ -0,0 +1,13 @@
+module Ci
+ class RunnerPolicy < BasePolicy
+ def rules
+ return unless @user
+
+ can! :assign_runner if @user.is_admin?
+
+ return if @subject.is_shared? || @subject.locked?
+
+ can! :assign_runner if @user.ci_authorized_runners.include?(@subject)
+ end
+ end
+end
diff --git a/app/policies/commit_status_policy.rb b/app/policies/commit_status_policy.rb
new file mode 100644
index 00000000000..593df738328
--- /dev/null
+++ b/app/policies/commit_status_policy.rb
@@ -0,0 +1,5 @@
+class CommitStatusPolicy < BasePolicy
+ def rules
+ delegate! @subject.project
+ end
+end
diff --git a/app/policies/deployment_policy.rb b/app/policies/deployment_policy.rb
new file mode 100644
index 00000000000..163d070ff90
--- /dev/null
+++ b/app/policies/deployment_policy.rb
@@ -0,0 +1,5 @@
+class DeploymentPolicy < BasePolicy
+ def rules
+ delegate! @subject.project
+ end
+end
diff --git a/app/policies/environment_policy.rb b/app/policies/environment_policy.rb
new file mode 100644
index 00000000000..f4219569161
--- /dev/null
+++ b/app/policies/environment_policy.rb
@@ -0,0 +1,5 @@
+class EnvironmentPolicy < BasePolicy
+ def rules
+ delegate! @subject.project
+ end
+end
diff --git a/app/policies/external_issue_policy.rb b/app/policies/external_issue_policy.rb
new file mode 100644
index 00000000000..d9e28bd107a
--- /dev/null
+++ b/app/policies/external_issue_policy.rb
@@ -0,0 +1,5 @@
+class ExternalIssuePolicy < BasePolicy
+ def rules
+ delegate! @subject.project
+ end
+end
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
new file mode 100644
index 00000000000..3c2fbe6b56b
--- /dev/null
+++ b/app/policies/global_policy.rb
@@ -0,0 +1,8 @@
+class GlobalPolicy < BasePolicy
+ def rules
+ return unless @user
+
+ can! :create_group if @user.can_create_group
+ can! :read_users_list
+ end
+end
diff --git a/app/policies/group_member_policy.rb b/app/policies/group_member_policy.rb
new file mode 100644
index 00000000000..62335527654
--- /dev/null
+++ b/app/policies/group_member_policy.rb
@@ -0,0 +1,19 @@
+class GroupMemberPolicy < BasePolicy
+ def rules
+ return unless @user
+
+ target_user = @subject.user
+ group = @subject.group
+
+ return if group.last_owner?(target_user)
+
+ can_manage = Ability.allowed?(@user, :admin_group_member, group)
+
+ if can_manage
+ can! :update_group_member
+ can! :destroy_group_member
+ elsif @user == target_user
+ can! :destroy_group_member
+ end
+ end
+end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
new file mode 100644
index 00000000000..97ff6233968
--- /dev/null
+++ b/app/policies/group_policy.rb
@@ -0,0 +1,45 @@
+class GroupPolicy < BasePolicy
+ def rules
+ can! :read_group if @subject.public?
+ return unless @user
+
+ globally_viewable = @subject.public? || (@subject.internal? && !@user.external?)
+ member = @subject.users.include?(@user)
+ owner = @user.admin? || @subject.has_owner?(@user)
+ master = owner || @subject.has_master?(@user)
+
+ can_read = false
+ can_read ||= globally_viewable
+ can_read ||= member
+ can_read ||= @user.admin?
+ can_read ||= GroupProjectsFinder.new(@subject).execute(@user).any?
+ can! :read_group if can_read
+
+ # Only group masters and group owners can create new projects
+ if master
+ can! :create_projects
+ can! :admin_milestones
+ end
+
+ # Only group owner and administrators can admin group
+ if owner
+ can! :admin_group
+ can! :admin_namespace
+ can! :admin_group_member
+ can! :change_visibility_level
+ end
+
+ if globally_viewable && @subject.request_access_enabled && !member
+ can! :request_access
+ end
+ end
+
+ def can_read_group?
+ return true if @subject.public?
+ return true if @user.admin?
+ return true if @subject.internal? && !@user.external?
+ return true if @subject.users.include?(@user)
+
+ GroupProjectsFinder.new(@subject).execute(@user).any?
+ end
+end
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
new file mode 100644
index 00000000000..c253f9a9399
--- /dev/null
+++ b/app/policies/issuable_policy.rb
@@ -0,0 +1,14 @@
+class IssuablePolicy < BasePolicy
+ def action_name
+ @subject.class.name.underscore
+ end
+
+ def rules
+ if @user && (@subject.author == @user || @subject.assignee == @user)
+ can! :"read_#{action_name}"
+ can! :"update_#{action_name}"
+ end
+
+ delegate! @subject.project
+ end
+end
diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb
new file mode 100644
index 00000000000..bd1811a3c54
--- /dev/null
+++ b/app/policies/issue_policy.rb
@@ -0,0 +1,28 @@
+class IssuePolicy < IssuablePolicy
+ def issue
+ @subject
+ end
+
+ def rules
+ super
+
+ if @subject.confidential? && !can_read_confidential?
+ cannot! :read_issue
+ cannot! :admin_issue
+ cannot! :update_issue
+ cannot! :read_issue
+ end
+ end
+
+ private
+
+ def can_read_confidential?
+ return false unless @user
+ return true if @user.admin?
+ return true if @subject.author == @user
+ return true if @subject.assignee == @user
+ return true if @subject.project.team.member?(@user, Gitlab::Access::REPORTER)
+
+ false
+ end
+end
diff --git a/app/policies/merge_request_policy.rb b/app/policies/merge_request_policy.rb
new file mode 100644
index 00000000000..bc3afc626fb
--- /dev/null
+++ b/app/policies/merge_request_policy.rb
@@ -0,0 +1,3 @@
+class MergeRequestPolicy < IssuablePolicy
+ # pass
+end
diff --git a/app/policies/namespace_policy.rb b/app/policies/namespace_policy.rb
new file mode 100644
index 00000000000..29bb357e00a
--- /dev/null
+++ b/app/policies/namespace_policy.rb
@@ -0,0 +1,10 @@
+class NamespacePolicy < BasePolicy
+ def rules
+ return unless @user
+
+ if @subject.owner == @user || @user.admin?
+ can! :create_projects
+ can! :admin_namespace
+ end
+ end
+end
diff --git a/app/policies/note_policy.rb b/app/policies/note_policy.rb
new file mode 100644
index 00000000000..83847466ee2
--- /dev/null
+++ b/app/policies/note_policy.rb
@@ -0,0 +1,19 @@
+class NotePolicy < BasePolicy
+ def rules
+ delegate! @subject.project
+
+ return unless @user
+
+ if @subject.author == @user
+ can! :read_note
+ can! :update_note
+ can! :admin_note
+ can! :resolve_note
+ end
+
+ if @subject.for_merge_request? &&
+ @subject.noteable.author == @user
+ can! :resolve_note
+ end
+ end
+end
diff --git a/app/policies/personal_snippet_policy.rb b/app/policies/personal_snippet_policy.rb
new file mode 100644
index 00000000000..46c5aa1a5be
--- /dev/null
+++ b/app/policies/personal_snippet_policy.rb
@@ -0,0 +1,16 @@
+class PersonalSnippetPolicy < BasePolicy
+ def rules
+ can! :read_personal_snippet if @subject.public?
+ return unless @user
+
+ if @subject.author == @user
+ can! :read_personal_snippet
+ can! :update_personal_snippet
+ can! :admin_personal_snippet
+ end
+
+ if @subject.internal? && !@user.external?
+ can! :read_personal_snippet
+ end
+ end
+end
diff --git a/app/policies/project_member_policy.rb b/app/policies/project_member_policy.rb
new file mode 100644
index 00000000000..1c038dddd4b
--- /dev/null
+++ b/app/policies/project_member_policy.rb
@@ -0,0 +1,22 @@
+class ProjectMemberPolicy < BasePolicy
+ def rules
+ # anonymous users have no abilities here
+ return unless @user
+
+ target_user = @subject.user
+ project = @subject.project
+
+ return if target_user == project.owner
+
+ can_manage = Ability.allowed?(@user, :admin_project_member, project)
+
+ if can_manage
+ can! :update_project_member
+ can! :destroy_project_member
+ end
+
+ if @user == target_user
+ can! :destroy_project_member
+ end
+ end
+end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
new file mode 100644
index 00000000000..acf36d422d1
--- /dev/null
+++ b/app/policies/project_policy.rb
@@ -0,0 +1,224 @@
+class ProjectPolicy < BasePolicy
+ def rules
+ team_access!(user)
+
+ owner = user.admin? ||
+ project.owner == user ||
+ (project.group && project.group.has_owner?(user))
+
+ owner_access! if owner
+
+ if project.public? || (project.internal? && !user.external?)
+ guest_access!
+ public_access!
+
+ # Allow to read builds for internal projects
+ can! :read_build if project.public_builds?
+
+ if project.request_access_enabled &&
+ !(owner || project.team.member?(user) || project_group_member?(user))
+ can! :request_access
+ end
+ end
+
+ archived_access! if project.archived?
+
+ disabled_features!
+ end
+
+ def project
+ @subject
+ end
+
+ def guest_access!
+ can! :read_project
+ can! :read_board
+ can! :read_list
+ can! :read_wiki
+ can! :read_issue
+ can! :read_label
+ can! :read_milestone
+ can! :read_project_snippet
+ can! :read_project_member
+ can! :read_merge_request
+ can! :read_note
+ can! :create_project
+ can! :create_issue
+ can! :create_note
+ can! :upload_file
+ end
+
+ def reporter_access!
+ can! :download_code
+ can! :fork_project
+ can! :create_project_snippet
+ can! :update_issue
+ can! :admin_issue
+ can! :admin_label
+ can! :admin_list
+ can! :read_commit_status
+ can! :read_build
+ can! :read_container_image
+ can! :read_pipeline
+ can! :read_environment
+ can! :read_deployment
+ end
+
+ def developer_access!
+ can! :admin_merge_request
+ can! :update_merge_request
+ can! :create_commit_status
+ can! :update_commit_status
+ can! :create_build
+ can! :update_build
+ can! :create_pipeline
+ can! :update_pipeline
+ can! :create_merge_request
+ can! :create_wiki
+ can! :push_code
+ can! :resolve_note
+ can! :create_container_image
+ can! :update_container_image
+ can! :create_environment
+ can! :create_deployment
+ end
+
+ def master_access!
+ can! :push_code_to_protected_branches
+ can! :update_project_snippet
+ can! :update_environment
+ can! :update_deployment
+ can! :admin_milestone
+ can! :admin_project_snippet
+ can! :admin_project_member
+ can! :admin_merge_request
+ can! :admin_note
+ can! :admin_wiki
+ can! :admin_project
+ can! :admin_commit_status
+ can! :admin_build
+ can! :admin_container_image
+ can! :admin_pipeline
+ can! :admin_environment
+ can! :admin_deployment
+ end
+
+ def public_access!
+ can! :download_code
+ can! :fork_project
+ can! :read_commit_status
+ can! :read_pipeline
+ can! :read_container_image
+ end
+
+ def owner_access!
+ guest_access!
+ reporter_access!
+ developer_access!
+ master_access!
+ can! :change_namespace
+ can! :change_visibility_level
+ can! :rename_project
+ can! :remove_project
+ can! :archive_project
+ can! :remove_fork_project
+ can! :destroy_merge_request
+ can! :destroy_issue
+ end
+
+ # Push abilities on the users team role
+ def team_access!(user)
+ access = project.team.max_member_access(user.id)
+
+ guest_access! if access >= Gitlab::Access::GUEST
+ reporter_access! if access >= Gitlab::Access::REPORTER
+ developer_access! if access >= Gitlab::Access::DEVELOPER
+ master_access! if access >= Gitlab::Access::MASTER
+ end
+
+ def archived_access!
+ cannot! :create_merge_request
+ cannot! :push_code
+ cannot! :push_code_to_protected_branches
+ cannot! :update_merge_request
+ cannot! :admin_merge_request
+ end
+
+ def disabled_features!
+ unless project.feature_available?(:issues, user)
+ cannot!(*named_abilities(:issue))
+ end
+
+ unless project.feature_available?(:merge_requests, user)
+ cannot!(*named_abilities(:merge_request))
+ end
+
+ unless project.feature_available?(:issues, user) || project.feature_available?(:merge_requests, user)
+ cannot!(*named_abilities(:label))
+ cannot!(*named_abilities(:milestone))
+ end
+
+ unless project.feature_available?(:snippets, user)
+ cannot!(*named_abilities(:project_snippet))
+ end
+
+ unless project.feature_available?(:wiki, user) || project.has_external_wiki?
+ cannot!(*named_abilities(:wiki))
+ end
+
+ unless project.feature_available?(:builds, user)
+ cannot!(*named_abilities(:build))
+ cannot!(*named_abilities(:pipeline))
+ cannot!(*named_abilities(:environment))
+ cannot!(*named_abilities(:deployment))
+ end
+
+ unless project.container_registry_enabled
+ cannot!(*named_abilities(:container_image))
+ end
+ end
+
+ def anonymous_rules
+ return unless project.public?
+
+ can! :read_project
+ can! :read_board
+ can! :read_list
+ can! :read_wiki
+ can! :read_label
+ can! :read_milestone
+ can! :read_project_snippet
+ can! :read_project_member
+ can! :read_merge_request
+ can! :read_note
+ can! :read_pipeline
+ can! :read_commit_status
+ can! :read_container_image
+ can! :download_code
+
+ # NOTE: may be overridden by IssuePolicy
+ can! :read_issue
+
+ # Allow to read builds by anonymous user if guests are allowed
+ can! :read_build if project.public_builds?
+
+ disabled_features!
+ end
+
+ def project_group_member?(user)
+ project.group &&
+ (
+ project.group.members.exists?(user_id: user.id) ||
+ project.group.requesters.exists?(user_id: user.id)
+ )
+ end
+
+ def named_abilities(name)
+ [
+ :"read_#{name}",
+ :"create_#{name}",
+ :"update_#{name}",
+ :"admin_#{name}"
+ ]
+ end
+end
diff --git a/app/policies/project_snippet_policy.rb b/app/policies/project_snippet_policy.rb
new file mode 100644
index 00000000000..57acccfafd9
--- /dev/null
+++ b/app/policies/project_snippet_policy.rb
@@ -0,0 +1,20 @@
+class ProjectSnippetPolicy < BasePolicy
+ def rules
+ can! :read_project_snippet if @subject.public?
+ return unless @user
+
+ if @user && @subject.author == @user || @user.admin?
+ can! :read_project_snippet
+ can! :update_project_snippet
+ can! :admin_project_snippet
+ end
+
+ if @subject.internal? && !@user.external?
+ can! :read_project_snippet
+ end
+
+ if @subject.private? && @subject.project.team.member?(@user)
+ can! :read_project_snippet
+ end
+ end
+end
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
new file mode 100644
index 00000000000..03a2499e263
--- /dev/null
+++ b/app/policies/user_policy.rb
@@ -0,0 +1,11 @@
+class UserPolicy < BasePolicy
+ include Gitlab::CurrentSettings
+
+ def rules
+ can! :read_user if @user || !restricted_public_level?
+ end
+
+ def restricted_public_level?
+ current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
+ end
+end
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index 0d55ba5a981..0c208150fb8 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -7,12 +7,8 @@ class BaseService
@project, @current_user, @params = project, user, params.dup
end
- def abilities
- Ability.abilities
- end
-
def can?(object, action, subject)
- abilities.allowed?(object, action, subject)
+ Ability.allowed?(object, action, subject)
end
def notification_service
diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb
index 435a8c6e681..34efd09ed9f 100644
--- a/app/services/boards/issues/list_service.rb
+++ b/app/services/boards/issues/list_service.rb
@@ -36,7 +36,12 @@ module Boards
end
def set_state
- params[:state] = list.done? ? 'closed' : 'opened'
+ params[:state] =
+ case list.list_type.to_sym
+ when :backlog then 'opened'
+ when :done then 'closed'
+ else 'all'
+ end
end
def board_label_ids
diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb
index 5cb408b9d20..b1887820bd4 100644
--- a/app/services/boards/lists/create_service.rb
+++ b/app/services/boards/lists/create_service.rb
@@ -3,7 +3,10 @@ module Boards
class CreateService < Boards::BaseService
def execute
List.transaction do
- create_list_at(next_position)
+ label = project.labels.find(params[:label_id])
+ position = next_position
+
+ create_list(label, position)
end
end
@@ -14,8 +17,8 @@ module Boards
max_position.nil? ? 0 : max_position.succ
end
- def create_list_at(position)
- board.lists.create(params.merge(list_type: :label, position: position))
+ def create_list(label, position)
+ board.lists.create(label: label, list_type: :label, position: position)
end
end
end
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index 6f7610d42ba..de48a50774e 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -10,13 +10,15 @@ module Ci
create_builds!
end
- new_builds =
- stage_indexes_of_created_builds.map do |index|
- process_stage(index)
- end
+ @pipeline.with_lock do
+ new_builds =
+ stage_indexes_of_created_builds.map do |index|
+ process_stage(index)
+ end
- # Return a flag if a when builds got enqueued
- new_builds.flatten.any?
+ # Return a flag if a when builds got enqueued
+ new_builds.flatten.any?
+ end
end
private
@@ -34,7 +36,7 @@ module Ci
end
def process_build(build, current_status)
- return false unless Statuseable::COMPLETED_STATUSES.include?(current_status)
+ return false unless HasStatus::COMPLETED_STATUSES.include?(current_status)
if valid_statuses_for_when(build.when).include?(current_status)
build.enqueue
diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb
index 9a187f5d694..6973191b203 100644
--- a/app/services/ci/register_build_service.rb
+++ b/app/services/ci/register_build_service.rb
@@ -8,16 +8,18 @@ module Ci
builds =
if current_runner.shared?
builds.
- # don't run projects which have not enabled shared runners
- joins(:project).where(projects: { builds_enabled: true, shared_runners_enabled: true }).
+ # don't run projects which have not enabled shared runners and builds
+ joins(:project).where(projects: { shared_runners_enabled: true }).
+ joins('LEFT JOIN project_features ON ci_builds.gl_project_id = project_features.project_id').
# this returns builds that are ordered by number of running builds
# we prefer projects that don't use shared runners at all
joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id").
+ where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0').
order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
else
# do run projects which are only assigned to this runner (FIFO)
- builds.where(project: current_runner.projects.where(builds_enabled: true)).order('created_at ASC')
+ builds.where(project: current_runner.projects.with_builds_enabled).order('created_at ASC')
end
build = builds.find do |build|
diff --git a/app/services/ci/web_hook_service.rb b/app/services/ci/web_hook_service.rb
deleted file mode 100644
index 92e6df442b4..00000000000
--- a/app/services/ci/web_hook_service.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-module Ci
- class WebHookService
- def build_end(build)
- execute_hooks(build.project, build_data(build))
- end
-
- def execute_hooks(project, data)
- project.web_hooks.each do |web_hook|
- async_execute_hook(web_hook, data)
- end
- end
-
- def async_execute_hook(hook, data)
- Sidekiq::Client.enqueue(Ci::WebHookWorker, hook.id, data)
- end
-
- def build_data(build)
- project = build.project
- data = {}
- data.merge!({
- build_id: build.id,
- build_name: build.name,
- build_status: build.status,
- build_started_at: build.started_at,
- build_finished_at: build.finished_at,
- project_id: project.id,
- project_name: project.name,
- gitlab_url: project.gitlab_url,
- ref: build.ref,
- before_sha: build.before_sha,
- sha: build.sha,
- })
- end
- end
-end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index e06c37c323e..4c8d93999a7 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -45,6 +45,7 @@ class IssuableBaseService < BaseService
unless can?(current_user, ability, project)
params.delete(:milestone_id)
+ params.delete(:labels)
params.delete(:add_label_ids)
params.delete(:remove_label_ids)
params.delete(:label_ids)
@@ -72,6 +73,7 @@ class IssuableBaseService < BaseService
filter_labels_in_param(:add_label_ids)
filter_labels_in_param(:remove_label_ids)
filter_labels_in_param(:label_ids)
+ find_or_create_label_ids
end
def filter_labels_in_param(key)
@@ -80,6 +82,17 @@ class IssuableBaseService < BaseService
params[key] = project.labels.where(id: params[key]).pluck(:id)
end
+ def find_or_create_label_ids
+ labels = params.delete(:labels)
+ return unless labels
+
+ params[:label_ids] = labels.split(",").map do |label_name|
+ project.labels.create_with(color: Label::DEFAULT_COLOR)
+ .find_or_create_by(title: label_name.strip)
+ .id
+ end
+ end
+
def process_label_ids(attributes, existing_label_ids: nil)
label_ids = attributes.delete(:label_ids)
add_label_ids = attributes.delete(:add_label_ids)
@@ -162,7 +175,12 @@ class IssuableBaseService < BaseService
if params.present? && update_issuable(issuable, params)
issuable.reset_events_cache
- handle_common_system_notes(issuable, old_labels: old_labels)
+
+ # We do not touch as it will affect a update on updated_at field
+ ActiveRecord::Base.no_touching do
+ handle_common_system_notes(issuable, old_labels: old_labels)
+ end
+
handle_changes(issuable, old_labels: old_labels, old_mentioned_users: old_mentioned_users)
issuable.create_new_cross_references!(current_user)
execute_hooks(issuable, 'update')
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 290742f1506..e57791f6818 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -83,7 +83,7 @@ module MergeRequests
closes_issue = "Closes ##{iid}"
if merge_request.description.present?
- merge_request.description += closes_issue.prepend("\n")
+ merge_request.description += closes_issue.prepend("\n\n")
else
merge_request.description = closes_issue
end
diff --git a/app/services/merge_requests/get_urls_service.rb b/app/services/merge_requests/get_urls_service.rb
index 08c1f72d65a..1262ecbc29a 100644
--- a/app/services/merge_requests/get_urls_service.rb
+++ b/app/services/merge_requests/get_urls_service.rb
@@ -31,7 +31,7 @@ module MergeRequests
def get_branches(changes)
return [] if project.empty_repo?
- return [] unless project.merge_requests_enabled
+ return [] unless project.merge_requests_enabled?
changes_list = Gitlab::ChangesList.new(changes)
changes_list.map do |change|
diff --git a/app/services/merge_requests/resolve_service.rb b/app/services/merge_requests/resolve_service.rb
index adc71b0c2bc..19caa038c44 100644
--- a/app/services/merge_requests/resolve_service.rb
+++ b/app/services/merge_requests/resolve_service.rb
@@ -1,11 +1,14 @@
module MergeRequests
class ResolveService < MergeRequests::BaseService
- attr_accessor :conflicts, :rugged, :merge_index
+ attr_accessor :conflicts, :rugged, :merge_index, :merge_request
def execute(merge_request)
@conflicts = merge_request.conflicts
@rugged = project.repository.rugged
@merge_index = conflicts.merge_index
+ @merge_request = merge_request
+
+ fetch_their_commit!
conflicts.files.each do |file|
write_resolved_file_to_index(file, params[:sections])
@@ -27,5 +30,21 @@ module MergeRequests
merge_index.add(path: our_path, oid: rugged.write(new_file, :blob), mode: file.our_mode)
merge_index.conflict_remove(our_path)
end
+
+ # If their commit (in the target project) doesn't exist in the source project, it
+ # can't be a parent for the merge commit we're about to create. If that's the case,
+ # fetch the target branch ref into the source project so the commit exists in both.
+ #
+ def fetch_their_commit!
+ return if rugged.include?(conflicts.their_commit.oid)
+
+ random_string = SecureRandom.hex
+
+ project.repository.fetch_ref(
+ merge_request.target_project.repository.path_to_repo,
+ "refs/heads/#{merge_request.target_branch}",
+ "refs/tmp/#{random_string}/head"
+ )
+ end
end
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 30c5f24988c..398ec47f0ea 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -11,6 +11,10 @@ module MergeRequests
params.except!(:target_project_id)
params.except!(:source_branch)
+ if merge_request.closed_without_fork?
+ params.except!(:target_branch, :force_remove_source_branch)
+ end
+
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
update(merge_request)
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 55956be2844..be749ba4a1c 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -7,7 +7,6 @@ module Projects
def execute
forked_from_project_id = params.delete(:forked_from_project_id)
import_data = params.delete(:import_data)
-
@project = Project.new(params)
# Make sure that the user is allowed to use the specified visibility level
@@ -81,8 +80,7 @@ module Projects
log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"")
unless @project.gitlab_project_import?
- @project.create_wiki if @project.wiki_enabled?
-
+ @project.create_wiki if @project.feature_available?(:wiki, current_user)
@project.build_missing_services
@project.create_labels
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index de6dc38cc8e..a2de4dccece 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -8,7 +8,6 @@ module Projects
name: @project.name,
path: @project.path,
shared_runners_enabled: @project.shared_runners_enabled,
- builds_enabled: @project.builds_enabled,
namespace_id: @params[:namespace].try(:id) || current_user.namespace.id
}
@@ -17,6 +16,9 @@ module Projects
end
new_project = CreateService.new(current_user, new_params).execute
+ builds_access_level = @project.project_feature.builds_access_level
+ new_project.project_feature.update_attributes(builds_access_level: builds_access_level)
+
new_project
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 546a8f11330..0c8446e7c3d 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -269,11 +269,11 @@ module SystemNoteService
#
# Example Note text:
#
- # "mentioned in #1"
+ # "Mentioned in #1"
#
- # "mentioned in !2"
+ # "Mentioned in !2"
#
- # "mentioned in 54f7727c"
+ # "Mentioned in 54f7727c"
#
# See cross_reference_note_content.
#
@@ -308,7 +308,7 @@ module SystemNoteService
# Check if a cross-reference is disallowed
#
- # This method prevents adding a "mentioned in !1" note on every single commit
+ # This method prevents adding a "Mentioned in !1" note on every single commit
# in a merge request. Additionally, it prevents the creation of references to
# external issues (which would fail).
#
@@ -417,7 +417,7 @@ module SystemNoteService
end
def cross_reference_note_prefix
- 'mentioned in '
+ 'Mentioned in '
end
def cross_reference_note_content(gfm_reference)
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index e0ccb654590..2aab8c736d6 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -148,7 +148,8 @@ class TodoService
def mark_todos_as_done_by_ids(ids, current_user)
todos = current_user.todos.where(id: ids)
- marked_todos = todos.update_all(state: :done)
+ # Only return those that are not really on that state
+ marked_todos = todos.where.not(state: :done).update_all(state: :done)
current_user.update_todos_count_cache
marked_todos
end
diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml
index 92e2dae4842..9175b3d3f96 100644
--- a/app/views/admin/appearances/_form.html.haml
+++ b/app/views/admin/appearances/_form.html.haml
@@ -13,7 +13,7 @@
.col-sm-10
= f.text_area :description, class: "form-control", rows: 10
.hint
- Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('markdown/markdown'), target: '_blank'}.
+ Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('user/markdown'), target: '_blank'}.
.form-group
= f.label :logo, class: 'control-label'
.col-sm-10
diff --git a/app/views/admin/background_jobs/_head.html.haml b/app/views/admin/background_jobs/_head.html.haml
index 89d7a40d6b0..107fc25244a 100644
--- a/app/views/admin/background_jobs/_head.html.haml
+++ b/app/views/admin/background_jobs/_head.html.haml
@@ -1,22 +1,24 @@
-.nav-links.sub-nav
- %ul{ class: (container_class) }
- = nav_link(controller: :system_info) do
- = link_to admin_system_info_path, title: 'System Info' do
- %span
- System Info
- = nav_link(controller: :background_jobs) do
- = link_to admin_background_jobs_path, title: 'Background Jobs' do
- %span
- Background Jobs
- = nav_link(controller: :logs) do
- = link_to admin_logs_path, title: 'Logs' do
- %span
- Logs
- = nav_link(controller: :health_check) do
- = link_to admin_health_check_path, title: 'Health Check' do
- %span
- Health Check
- = nav_link(controller: :requests_profiles) do
- = link_to admin_requests_profiles_path, title: 'Requests Profiles' do
- %span
- Requests Profiles
+.scrolling-tabs-container.sub-nav-scroll
+ = render 'shared/nav_scroll'
+ .nav-links.sub-nav.scrolling-tabs
+ %ul{ class: (container_class) }
+ = nav_link(controller: :system_info) do
+ = link_to admin_system_info_path, title: 'System Info' do
+ %span
+ System Info
+ = nav_link(controller: :background_jobs) do
+ = link_to admin_background_jobs_path, title: 'Background Jobs' do
+ %span
+ Background Jobs
+ = nav_link(controller: :logs) do
+ = link_to admin_logs_path, title: 'Logs' do
+ %span
+ Logs
+ = nav_link(controller: :health_check) do
+ = link_to admin_health_check_path, title: 'Health Check' do
+ %span
+ Health Check
+ = nav_link(controller: :requests_profiles) do
+ = link_to admin_requests_profiles_path, title: 'Requests Profiles' do
+ %span
+ Requests Profiles
diff --git a/app/views/admin/dashboard/_head.html.haml b/app/views/admin/dashboard/_head.html.haml
index b74da64f82e..c91ab4cb946 100644
--- a/app/views/admin/dashboard/_head.html.haml
+++ b/app/views/admin/dashboard/_head.html.haml
@@ -1,26 +1,28 @@
-.nav-links.sub-nav
- %ul{ class: (container_class) }
- = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
- = link_to admin_root_path, title: 'Overview' do
- %span
- Overview
- = nav_link(controller: [:admin, :projects]) do
- = link_to admin_namespaces_projects_path, title: 'Projects' do
- %span
- Projects
- = nav_link(controller: :users) do
- = link_to admin_users_path, title: 'Users' do
- %span
- Users
- = nav_link(controller: :groups) do
- = link_to admin_groups_path, title: 'Groups' do
- %span
- Groups
- = nav_link path: 'builds#index' do
- = link_to admin_builds_path, title: 'Builds' do
- %span
- Builds
- = nav_link path: ['runners#index', 'runners#show'] do
- = link_to admin_runners_path, title: 'Runners' do
- %span
- Runners
+.scrolling-tabs-container.sub-nav-scroll
+ = render 'shared/nav_scroll'
+ .nav-links.sub-nav.scrolling-tabs
+ %ul{ class: (container_class) }
+ = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
+ = link_to admin_root_path, title: 'Overview' do
+ %span
+ Overview
+ = nav_link(controller: [:admin, :projects]) do
+ = link_to admin_namespaces_projects_path, title: 'Projects' do
+ %span
+ Projects
+ = nav_link(controller: :users) do
+ = link_to admin_users_path, title: 'Users' do
+ %span
+ Users
+ = nav_link(controller: :groups) do
+ = link_to admin_groups_path, title: 'Groups' do
+ %span
+ Groups
+ = nav_link path: 'builds#index' do
+ = link_to admin_builds_path, title: 'Builds' do
+ %span
+ Builds
+ = nav_link path: ['runners#index', 'runners#show'] do
+ = link_to admin_runners_path, title: 'Runners' do
+ %span
+ Runners
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index b2c607361b3..6c7c3c48604 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -73,6 +73,12 @@
%span.light last commit:
%strong
= last_commit(@project)
+
+ %li
+ %span.light Git LFS status:
+ %strong
+ = project_lfs_status(@project)
+ = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
- else
%li
%span.light repository:
diff --git a/app/views/admin/system_info/show.html.haml b/app/views/admin/system_info/show.html.haml
index 6956e5ab795..bfc6142067a 100644
--- a/app/views/admin/system_info/show.html.haml
+++ b/app/views/admin/system_info/show.html.haml
@@ -9,12 +9,20 @@
.light-well
%h4 CPU
.data
- %h1= "#{@cpus} cores"
+ - if @cpus
+ %h1= "#{@cpus.length} cores"
+ - else
+ = icon('warning', class: 'text-warning')
+ Unable to collect CPU info
.col-sm-4
.light-well
%h4 Memory
.data
- %h1= "#{number_to_human_size(@mem_used)} / #{number_to_human_size(@mem_total)}"
+ - if @memory
+ %h1= "#{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)}"
+ - else
+ = icon('warning', class: 'text-warning')
+ Unable to collect memory info
.col-sm-4
.light-well
%h4 Disks
diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml
index 0044d779c31..889086c62b1 100644
--- a/app/views/ci/lints/show.html.haml
+++ b/app/views/ci/lints/show.html.haml
@@ -1,3 +1,6 @@
+- page_title "CI Lint"
+- page_description "Validate your GitLab CI configuration file"
+
%h2 Check your .gitlab-ci.yml
%hr
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index d320d3bcc1e..9d31f31c639 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -28,21 +28,25 @@
.row-content-block.second-block
= form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do
.filter-item.inline
- = select_tag('project_id', todo_projects_options,
- class: 'select2 trigger-submit', include_blank: true,
- data: {placeholder: 'Project'})
+ - if params[:project_id].present?
+ = hidden_field_tag(:project_id, params[:project_id])
+ = dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit', title: 'Filter by project', filter: true, filterInput: 'input#project-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit',
+ placeholder: 'Search projects', data: { data: todo_projects_options } })
.filter-item.inline
- = users_select_tag(:author_id, selected: params[:author_id],
- placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true)
+ - if params[:author_id].present?
+ = hidden_field_tag(:author_id, params[:author_id])
+ = dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit',
+ placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author' } })
.filter-item.inline
- = select_tag('type', todo_types_options,
- class: 'select2 trigger-submit', include_blank: true,
- data: {placeholder: 'Type'})
+ - if params[:type].present?
+ = hidden_field_tag(:type, params[:type])
+ = dropdown_tag(todo_types_dropdown_label(params[:type], 'Type'), options: { toggle_class: 'js-type-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit',
+ data: { data: todo_types_options } })
.filter-item.inline.actions-filter
- = select_tag('action_id', todo_actions_options,
- class: 'select2 trigger-submit', include_blank: true,
- data: {placeholder: 'Action'})
-
+ - if params[:action_id].present?
+ = hidden_field_tag(:action_id, params[:action_id])
+ = dropdown_tag(todo_actions_dropdown_label(params[:action_id], 'Action'), options: { toggle_class: 'js-action-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit',
+ data: { data: todo_actions_options }})
.pull-right
.dropdown.inline.prepend-left-10
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
@@ -66,7 +70,7 @@
- if @todos.any?
.js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} }
- @todos.group_by(&:project).each do |group|
- .panel.panel-default.panel-small.js-todos-list
+ .panel.panel-default.panel-small
- project = group[0]
.panel-heading
= link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
@@ -76,11 +80,3 @@
= paginate @todos, theme: "gitlab"
- else
.nothing-here-block You're all done!
-
-:javascript
- new UsersSelect();
-
- $('form.filter-form').on('submit', function (event) {
- event.preventDefault();
- Turbolinks.visit(this.action + '&' + $(this).serialize());
- });
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 5c318cd3b8b..31fdcc5e21b 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -1,7 +1,7 @@
- if event.visible_to_user?(current_user)
.event-item{ class: event_row_class(event) }
.event-item-timestamp
- #{time_ago_with_tooltip(event.created_at)}
+ #{time_ago_with_tooltip(event.created_at, skip_js: true)}
= cache [event, current_application_settings, "v2.2"] do
= author_avatar(event, size: 40)
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index 57f6e7e0612..b8248a80a27 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -24,7 +24,7 @@
- else
= sort_title_recently_created
%b.caret
- %ul.dropdown-menu
+ %ul.dropdown-menu.dropdown-menu-align-right
%li
= link_to explore_groups_path(sort: sort_value_recently_created) do
= sort_title_recently_created
diff --git a/app/views/groups/group_members/update.js.haml b/app/views/groups/group_members/update.js.haml
index 742f9d7a433..3be7ed8432c 100644
--- a/app/views/groups/group_members/update.js.haml
+++ b/app/views/groups/group_members/update.js.haml
@@ -1,3 +1,3 @@
:plain
$("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}');
- new MemberExpirationDate();
+ new gl.MemberExpirationDate();
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index 85e188d6f8b..d16bd61b779 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -549,4 +549,4 @@
%li wiki page
%li help page
- You can check how markdown rendered at #{link_to 'Markdown help page', help_page_path("markdown/markdown")}.
+ You can check how markdown rendered at #{link_to 'Markdown help page', help_page_path("user/markdown")}.
diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml
deleted file mode 100644
index ed3afb0ce33..00000000000
--- a/app/views/import/gitorious/status.html.haml
+++ /dev/null
@@ -1,54 +0,0 @@
-- page_title "Gitorious import"
-- header_title "Projects", root_path
-%h3.page-title
- %i.icon-gitorious.icon-gitorious-big
- Import projects from Gitorious.org
-
-%p.light
- Select projects you want to import.
-%hr
-%p
- = button_tag class: "btn btn-import btn-success js-import-all" do
- Import all projects
- = icon("spinner spin", class: "loading-icon")
-
-.table-responsive
- %table.table.import-jobs
- %colgroup.import-jobs-from-col
- %colgroup.import-jobs-to-col
- %colgroup.import-jobs-status-col
- %thead
- %tr
- %th From Gitorious.org
- %th To GitLab
- %th Status
- %tbody
- - @already_added_projects.each do |project|
- %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
- %td
- = link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank"
- %td
- = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
- %td.job-status
- - if project.import_status == 'finished'
- %span
- %i.fa.fa-check
- done
- - elsif project.import_status == 'started'
- %i.fa.fa-spinner.fa-spin
- started
- - else
- = project.human_import_status_name
-
- - @repos.each do |repo|
- %tr{id: "repo_#{repo.id}"}
- %td
- = link_to repo.full_name, "https://gitorious.org/#{repo.full_name}", target: "_blank"
- %td.import-target
- = repo.full_name
- %td.import-actions.job-status
- = button_tag class: "btn btn-import js-add-to-import" do
- Import
- = icon("spinner spin", class: "loading-icon")
-
-.js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitorious_path}", import_path: "#{import_gitorious_path}" } }
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index d7d36c84b6c..27ac1760166 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,5 +1,5 @@
+= render 'layouts/nav/group_settings'
.scrolling-tabs-container{ class: nav_control_class }
- = render 'layouts/nav/group_settings'
.fade-left
= icon('angle-left')
.fade-right
diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml
index bf9a7ecb786..75275afc0f3 100644
--- a/app/views/layouts/nav/_group_settings.html.haml
+++ b/app/views/layouts/nav/_group_settings.html.haml
@@ -1,22 +1,26 @@
- if current_user
+ - can_admin_group = can?(current_user, :admin_group, @group)
- can_edit = can?(current_user, :admin_group, @group)
- member = @group.members.find_by(user_id: current_user.id)
- can_leave = member && can?(current_user, :destroy_group_member, member)
- .controls
- .dropdown.group-settings-dropdown
- %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'}
- = icon('cog')
- = icon('caret-down')
- %ul.dropdown-menu.dropdown-menu-align-right
- = nav_link(path: 'groups#projects') do
- = link_to 'Projects', projects_group_path(@group), title: 'Projects'
- %li.divider
- - if can_edit
- %li
- = link_to 'Edit Group', edit_group_path(@group)
- - if can_leave
- %li
- = link_to polymorphic_path([:leave, @group, :members]),
- data: { confirm: leave_confirmation_message(@group) }, method: :delete, title: 'Leave group' do
- Leave Group
+ - if can_admin_group || can_edit || can_leave
+ .controls
+ .dropdown.group-settings-dropdown
+ %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'}
+ = icon('cog')
+ = icon('caret-down')
+ %ul.dropdown-menu.dropdown-menu-align-right
+ - if can_admin_group
+ = nav_link(path: 'groups#projects') do
+ = link_to 'Projects', projects_group_path(@group), title: 'Projects'
+ - if can_edit || can_leave
+ %li.divider
+ - if can_edit
+ %li
+ = link_to 'Edit Group', edit_group_path(@group)
+ - if can_leave
+ %li
+ = link_to polymorphic_path([:leave, @group, :members]),
+ data: { confirm: leave_confirmation_message(@group) }, method: :delete, title: 'Leave group' do
+ Leave Group
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 52a5bdc1a1b..613b8b7d301 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -26,7 +26,7 @@
%span
Protected Branches
- - if @project.builds_enabled?
+ - if @project.feature_available?(:builds, current_user)
= nav_link(controller: :runners) do
= link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do
%span
diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml
index 19b4249374b..14eb47089b1 100644
--- a/app/views/projects/_merge_request_settings.html.haml
+++ b/app/views/projects/_merge_request_settings.html.haml
@@ -1,11 +1,14 @@
-%fieldset.builds-feature
- %h5.prepend-top-0
- Merge Requests
- .form-group
- .checkbox
- = f.label :only_allow_merge_if_build_succeeds do
- = f.check_box :only_allow_merge_if_build_succeeds
- %strong Only allow merge requests to be merged if the build succeeds
- .help-block
- Builds need to be configured to enable this feature.
- = link_to icon('question-circle'), help_page_path('workflow/merge_requests', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
+.merge-requests-feature
+ %fieldset.builds-feature
+ %hr
+ %h5.prepend-top-0
+ Merge Requests
+ .form-group
+ .checkbox
+ = f.label :only_allow_merge_if_build_succeeds do
+ = f.check_box :only_allow_merge_if_build_succeeds
+ %strong Only allow merge requests to be merged if the build succeeds
+ %br
+ %span.descr
+ Builds need to be configured to enable this feature.
+ = link_to icon('question-circle'), help_page_path('workflow/merge_requests', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml
index de53a298f84..73066150fb3 100644
--- a/app/views/projects/boards/components/_board.html.haml
+++ b/app/views/projects/boards/components/_board.html.haml
@@ -13,19 +13,13 @@
%h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" }
{{ list.title }}
%span.pull-right{ "v-if" => "list.type !== 'blank'" }
- {{ list.issues.length }}
+ {{ list.issuesSize }}
- if can?(current_user, :admin_list, @project)
%board-delete{ "inline-template" => true,
":list" => "list",
"v-if" => "!list.preset && list.id" }
%button.board-delete.has-tooltip.pull-right{ type: "button", title: "Delete list", "aria-label" => "Delete list", data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
= icon("trash")
- = icon("spinner spin", class: "board-header-loading-spinner pull-right", "v-show" => "list.loadingMore")
- .board-inner-container.board-search-container{ "v-if" => "list.canSearch()" }
- %input.form-control{ type: "text", placeholder: "Search issues", "v-model" => "query", "debounce" => "250" }
- = icon("search", class: "board-search-icon", "v-show" => "!query")
- %button.board-search-clear-btn{ type: "button", role: "button", "aria-label" => "Clear search", "@click" => "query = ''", "v-show" => "query" }
- = icon("times", class: "board-search-clear")
%board-list{ "inline-template" => true,
"v-if" => "list.type !== 'blank'",
":list" => "list",
@@ -39,5 +33,11 @@
"v-show" => "!loading",
":data-board" => "list.id" }
= render "projects/boards/components/card"
+ %li.board-list-count.text-center{ "v-if" => "showCount" }
+ = icon("spinner spin", "v-show" => "list.loadingMore" )
+ %span{ "v-if" => "list.issues.length === list.issuesSize" }
+ Showing all issues
+ %span{ "v-else" => true }
+ Showing {{ list.issues.length }} of {{ list.issuesSize }} issues
- if can?(current_user, :admin_list, @project)
= render "projects/boards/components/blank_state"
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 6192ccb710b..808e6b95746 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -27,6 +27,8 @@
= link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-default', method: :post, title: "Compare" do
Compare
+ = render 'projects/buttons/download', project: @project, ref: branch.name
+
- if can_remove_branch?(@project, branch.name)
= link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index 5b0b58e087b..5ce36a475a9 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -1,3 +1,6 @@
+- builds = @build.pipeline.builds.latest.to_a
+- statuses = ["failed", "pending", "running", "canceled", "success", "skipped"]
+
%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar
.block.build-sidebar-header.visible-xs-block.visible-sm-block.append-bottom-default
Build
@@ -11,40 +14,6 @@
%p.build-detail-row
#{@build.coverage}%
- - builds = @build.pipeline.builds.latest.to_a
- - statuses = ["failed", "pending", "running", "canceled", "success", "skipped"]
- - if builds.size > 1
- .dropdown.build-dropdown
- .build-light-text Stage
- %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'}
- %span.stage-selection More
- = icon('caret-down')
- %ul.dropdown-menu
- - builds.map(&:stage).uniq.each do |stage|
- %li
- %a.stage-item= stage
-
- .builds-container
- - statuses.each do |build_status|
- - builds.select{|build| build.status == build_status}.each do |build|
- .build-job{class: ('active' if build == @build), data: {stage: build.stage}}
- = link_to namespace_project_build_path(@project.namespace, @project, build) do
- = icon('check')
- = ci_icon_for_status(build.status)
- %span
- - if build.name
- = build.name
- - else
- = build.id
-
- - if @build.retried?
- %li.active
- %a
- Build ##{@build.id}
- &middot;
- %i.fa.fa-warning
- This build was retried.
-
.blocks-container
- if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?)
.block{ class: ("block-first" if !@build.coverage) }
@@ -76,7 +45,7 @@
.title
Build details
- if can?(current_user, :update_build, @build) && @build.retryable?
- = link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right', method: :post
+ = link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right retry-link', method: :post
- if @build.merge_request
%p.build-detail-row
%span.build-light-text Merge Request:
@@ -100,7 +69,7 @@
- elsif @build.runner
\##{@build.runner.id}
.btn-group.btn-group-justified{ role: :group }
- - if @build.has_trace?
+ - if @build.has_trace_file?
= link_to 'Raw', raw_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default'
- if @build.active?
= link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post
@@ -141,3 +110,35 @@
- @build.tag_list.each do |tag|
%span.label.label-primary
= tag
+
+ - if builds.size > 1
+ .dropdown.build-dropdown
+ .title Stage
+ %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'}
+ %span.stage-selection More
+ = icon('caret-down')
+ %ul.dropdown-menu
+ - builds.map(&:stage).uniq.each do |stage|
+ %li
+ %a.stage-item= stage
+
+ .builds-container
+ - statuses.each do |build_status|
+ - builds.select{|build| build.status == build_status}.each do |build|
+ .build-job{class: ('active' if build == @build), data: {stage: build.stage}}
+ = link_to namespace_project_build_path(@project.namespace, @project, build) do
+ = icon('check')
+ = ci_icon_for_status(build.status)
+ %span
+ - if build.name
+ = build.name
+ - else
+ = build.id
+
+ - if @build.retried?
+ %li.active
+ %a
+ Build ##{@build.id}
+ &middot;
+ %i.fa.fa-warning
+ This build was retried.
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index 58f43ecb5d5..5f5e071eb40 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -1,4 +1,42 @@
-- unless @project.empty_repo?
- - if can? current_user, :download_code, @project
- = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn has-tooltip', data: {container: "body"}, rel: 'nofollow', title: "Download ZIP" do
- = icon('download')
+- if !project.empty_repo? && can?(current_user, :download_code, project)
+ %span.btn-group{class: 'hidden-xs hidden-sm btn-grouped'}
+ .dropdown.inline
+ %button.btn{ 'data-toggle' => 'dropdown' }
+ = icon('download')
+ %span.caret
+ %span.sr-only
+ Select Archive Format
+ %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
+ %li.dropdown-header Source code
+ %li
+ = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do
+ %i.fa.fa-download
+ %span Download zip
+ %li
+ = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
+ %i.fa.fa-download
+ %span Download tar.gz
+ %li
+ = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
+ %i.fa.fa-download
+ %span Download tar.bz2
+ %li
+ = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow' do
+ %i.fa.fa-download
+ %span Download tar
+
+ - pipeline = project.pipelines.latest_successful_for(ref)
+ - if pipeline
+ - artifacts = pipeline.builds.latest.with_artifacts
+ - if artifacts.any?
+ %li.dropdown-header Artifacts
+ - unless pipeline.latest?
+ - latest_pipeline = project.pipeline_for(ref)
+ %li
+ .unclickable= ci_status_for_statuseable(latest_pipeline)
+ %li.dropdown-header Previous Artifacts
+ - artifacts.each do |job|
+ %li
+ = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do
+ %i.fa.fa-download
+ %span Download '#{job.name}'
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index d78888e9fe4..22db33498f1 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -3,11 +3,11 @@
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has-tooltip' do
= custom_icon('icon_fork')
- Fork
+ %span Fork
- else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has-tooltip' do
= custom_icon('icon_fork')
- Fork
+ %span Fork
%div.count-with-arrow
%span.arrow
= link_to namespace_project_forks_path(@project.namespace, @project), class: "count" do
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 1fdf32466f2..73de8abe55b 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -89,4 +89,4 @@
= icon('repeat')
- elsif build.playable?
= link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
- = icon('play')
+ = custom_icon('icon_play')
diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml
index 04cbd0c3591..36fb0300aeb 100644
--- a/app/views/projects/ci/builds/_build_pipeline.html.haml
+++ b/app/views/projects/ci/builds/_build_pipeline.html.haml
@@ -1,14 +1,15 @@
- is_playable = subject.playable? && can?(current_user, :update_build, @project)
%li.build{class: ("playable" if is_playable)}
+ .curve
.build-content
- if is_playable
= link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do
= render_status_with_link('build', 'play')
- = subject.name
+ %span.ci-status-text= subject.name
- elsif can?(current_user, :read_build, @project)
= link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do
= render_status_with_link('build', subject.status)
- = subject.name
+ %span.ci-status-text= subject.name
- else
= render_status_with_link('build', subject.status)
= ci_icon_for_status(subject.status)
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index b119f6edf14..bb9493f5158 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -66,13 +66,13 @@
- if actions.any?
.btn-group
%a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
- = icon("play")
+ = custom_icon('icon_play')
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
- actions.each do |build|
%li
= link_to play_namespace_project_build_path(pipeline.project.namespace, pipeline.project, build), method: :post, rel: 'nofollow' do
- = icon("play")
+ = custom_icon('icon_play')
%span= build.name.humanize
- if artifacts.present?
.btn-group
diff --git a/app/views/projects/commit/_ci_stage.html.haml b/app/views/projects/commit/_ci_stage.html.haml
index 9d925cacc0d..6bb900e3fc1 100644
--- a/app/views/projects/commit/_ci_stage.html.haml
+++ b/app/views/projects/commit/_ci_stage.html.haml
@@ -8,8 +8,8 @@
- if stage
&nbsp;
= stage.titleize
- = render statuses.latest.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, allow_retry: true
- = render statuses.retried.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, retried: true
+ = render statuses.latest_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, allow_retry: true
+ = render statuses.retried_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, retried: true
%tr
%td{colspan: 10}
&nbsp;
diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml
index 29f4ef8f49e..f41a11a056d 100644
--- a/app/views/projects/commit/_pipelines_list.haml
+++ b/app/views/projects/commit/_pipelines_list.haml
@@ -10,7 +10,10 @@
%th Commit
- pipelines.stages.each do |stage|
%th.stage
- %span.has-tooltip{ title: "#{stage.titleize}" }
+ - if stage.titleize.length > 12
+ %span.has-tooltip{ title: "#{stage.titleize}" }
+ = stage.titleize
+ - else
= stage.titleize
%th
%th
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index fd888f41b1e..389477d0927 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -7,7 +7,7 @@
- cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count]
- cache_key.push(commit.status) if commit.status
-= cache(cache_key) do
+= cache(cache_key, expires_in: 1.day) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
= author_avatar(commit, size: 36)
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index 61152649907..4d1ee1c5318 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -1,8 +1,5 @@
.scrolling-tabs-container.sub-nav-scroll
- .fade-left
- = icon('angle-left')
- .fade-right
- = icon('angle-right')
+ = render 'shared/nav_scroll'
.nav-links.sub-nav.scrolling-tabs
%ul{ class: (container_class) }
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml
index f7bf3b834ef..16d134eb6b6 100644
--- a/app/views/projects/deployments/_actions.haml
+++ b/app/views/projects/deployments/_actions.haml
@@ -5,13 +5,13 @@
.inline
.dropdown
%a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
- = icon("play")
+ = custom_icon('icon_play')
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
- actions.each do |action|
%li
= link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
- = icon("play")
+ = custom_icon('icon_play')
%span= action.name.humanize
- if local_assigns.fetch(:allow_rollback, false)
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index b282aa52b25..f6d751a343e 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -44,42 +44,56 @@
%hr
%fieldset.features.append-bottom-0
%h5.prepend-top-0
- Features
- .form-group
- .checkbox
- = f.label :issues_enabled do
- = f.check_box :issues_enabled
- %strong Issues
- %br
- %span.descr Lightweight issue tracking system for this project
- .form-group
- .checkbox
- = f.label :merge_requests_enabled do
- = f.check_box :merge_requests_enabled
- %strong Merge Requests
- %br
- %span.descr Submit changes to be merged upstream
- .form-group
- .checkbox
- = f.label :builds_enabled do
- = f.check_box :builds_enabled
- %strong Builds
- %br
- %span.descr Test and deploy your changes before merge
- .form-group
- .checkbox
- = f.label :wiki_enabled do
- = f.check_box :wiki_enabled
- %strong Wiki
- %br
- %span.descr Pages for project documentation
- .form-group
- .checkbox
- = f.label :snippets_enabled do
- = f.check_box :snippets_enabled
- %strong Snippets
- %br
- %span.descr Share code pastes with others out of git repository
+ Feature Visibility
+
+ = f.fields_for :project_feature do |feature_fields|
+ .form_group.prepend-top-20
+ .row
+ .col-md-9
+ = feature_fields.label :issues_access_level, "Issues", class: 'label-light'
+ %span.help-block Lightweight issue tracking system for this project
+ .col-md-3
+ = project_feature_access_select(:issues_access_level)
+
+ .row
+ .col-md-9
+ = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light'
+ %span.help-block Submit changes to be merged upstream
+ .col-md-3
+ = project_feature_access_select(:merge_requests_access_level)
+
+ .row
+ .col-md-9
+ = feature_fields.label :builds_access_level, "Builds", class: 'label-light'
+ %span.help-block Submit Test and deploy your changes before merge
+ .col-md-3
+ = project_feature_access_select(:builds_access_level)
+
+ .row
+ .col-md-9
+ = feature_fields.label :wiki_access_level, "Wiki", class: 'label-light'
+ %span.help-block Pages for project documentation
+ .col-md-3
+ = project_feature_access_select(:wiki_access_level)
+
+ .row
+ .col-md-9
+ = feature_fields.label :snippets_access_level, "Snippets", class: 'label-light'
+ %span.help-block Share code pastes with others out of Git repository
+ .col-md-3
+ = project_feature_access_select(:snippets_access_level)
+
+ - if Gitlab.config.lfs.enabled && current_user.admin?
+ .form-group
+ .checkbox
+ = f.label :lfs_enabled do
+ = f.check_box :lfs_enabled, checked: @project.lfs_enabled?
+ %strong LFS
+ %br
+ %span.descr
+ Git Large File Storage
+ = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
+
- if Gitlab.config.registry.enabled
.form-group
.checkbox
@@ -88,7 +102,7 @@
%strong Container Registry
%br
%span.descr Enable Container Registry for this repository
- %hr
+
= render 'merge_request_settings', f: f
%hr
%fieldset.features.append-bottom-default
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index a1d79bdabda..bacc5708e4b 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -32,11 +32,11 @@
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do
= custom_icon('icon_fork')
- Fork
+ %span Fork
- else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do
= custom_icon('icon_fork')
- Fork
+ %span Fork
= render 'projects', projects: @forks
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
index 584c0fa18ae..576d0bec51b 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
@@ -1,9 +1,10 @@
%li.build
+ .curve
.build-content
- if subject.target_url
- link_to subject.target_url do
= render_status_with_link('commit status', subject.status)
- = subject.name
+ %span.ci-status-text= subject.name
- else
= render_status_with_link('commit status', subject.status)
- = subject.name
+ %span.ci-status-text= subject.name
diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml
index 45e51389c00..082e2cb4d8c 100644
--- a/app/views/projects/graphs/_head.html.haml
+++ b/app/views/projects/graphs/_head.html.haml
@@ -1,16 +1,18 @@
-.nav-links.sub-nav
- %ul{ class: (container_class) }
+.scrolling-tabs-container.sub-nav-scroll
+ = render 'shared/nav_scroll'
+ .nav-links.sub-nav.scrolling-tabs
+ %ul{ class: (container_class) }
- - content_for :page_specific_javascripts do
- = page_specific_javascript_tag('lib/chart.js')
- = page_specific_javascript_tag('graphs/graphs_bundle.js')
- = nav_link(action: :show) do
- = link_to 'Contributors', namespace_project_graph_path
- = nav_link(action: :commits) do
- = link_to 'Commits', commits_namespace_project_graph_path
- = nav_link(action: :languages) do
- = link_to 'Languages', languages_namespace_project_graph_path
- - if @project.builds_enabled?
- = nav_link(action: :ci) do
- = link_to ci_namespace_project_graph_path do
- Continuous Integration
+ - content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('lib/chart.js')
+ = page_specific_javascript_tag('graphs/graphs_bundle.js')
+ = nav_link(action: :show) do
+ = link_to 'Contributors', namespace_project_graph_path
+ = nav_link(action: :commits) do
+ = link_to 'Commits', commits_namespace_project_graph_path
+ = nav_link(action: :languages) do
+ = link_to 'Languages', languages_namespace_project_graph_path
+ - if @project.feature_available?(:builds, current_user)
+ = nav_link(action: :ci) do
+ = link_to ci_namespace_project_graph_path do
+ Continuous Integration
diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml
index b6cb559afcb..f88b33018d0 100644
--- a/app/views/projects/issues/_head.html.haml
+++ b/app/views/projects/issues/_head.html.haml
@@ -1,30 +1,32 @@
-.nav-links.sub-nav
- %ul{ class: (container_class) }
- - if project_nav_tab?(:issues) && !current_controller?(:merge_requests)
- = nav_link(controller: :issues) do
- = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do
- %span
- Issues
+.scrolling-tabs-container.sub-nav-scroll
+ = render 'shared/nav_scroll'
+ .nav-links.sub-nav.scrolling-tabs
+ %ul{ class: (container_class) }
+ - if project_nav_tab?(:issues) && !current_controller?(:merge_requests)
+ = nav_link(controller: :issues) do
+ = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do
+ %span
+ Issues
- = nav_link(controller: :boards) do
- = link_to namespace_project_board_path(@project.namespace, @project), title: 'Board' do
- %span
- Board
+ = nav_link(controller: :boards) do
+ = link_to namespace_project_board_path(@project.namespace, @project), title: 'Board' do
+ %span
+ Board
- - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests)
- = nav_link(controller: :merge_requests) do
- = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do
- %span
- Merge Requests
+ - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests)
+ = nav_link(controller: :merge_requests) do
+ = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do
+ %span
+ Merge Requests
- - if project_nav_tab? :labels
- = nav_link(controller: :labels) do
- = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
- %span
- Labels
+ - if project_nav_tab? :labels
+ = nav_link(controller: :labels) do
+ = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
+ %span
+ Labels
- - if project_nav_tab? :milestones
- = nav_link(controller: :milestones) do
- = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
- %span
- Milestones
+ - if project_nav_tab? :milestones
+ = nav_link(controller: :milestones) do
+ = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
+ %span
+ Milestones \ No newline at end of file
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index 24749699c6d..33556a1a2b3 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -1,13 +1,12 @@
- if can?(current_user, :push_code, @project)
.pull-right
#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
= link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid),
- method: :post, class: 'btn btn-new btn-inverted has-tooltip', title: @issue.to_branch_name, disabled: 'disabled' do
- .checking
- = icon('spinner spin')
- Checking branches
- .available.hide
- New branch
- .unavailable.hide
- = icon('exclamation-triangle')
- New branch unavailable
+ method: :post, class: 'btn btn-new btn-inverted btn-grouped has-tooltip available hide', title: @issue.to_branch_name do
+ New branch
+ = link_to '#', class: 'unavailable btn btn-grouped hide', disabled: 'disabled' do
+ = icon('exclamation-triangle')
+ New branch unavailable
diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml
index 6ea9f612d13..a8eeab3e55e 100644
--- a/app/views/projects/issues/_related_branches.html.haml
+++ b/app/views/projects/issues/_related_branches.html.haml
@@ -5,7 +5,7 @@
- @related_branches.each do |branch|
%li
- target = @project.repository.find_branch(branch).target
- - pipeline = @project.pipeline(target.sha, branch) if target
+ - pipeline = @project.pipeline_for(branch, target.sha) if target
- if pipeline
%span.related-branch-ci-status
= render_pipeline_status(pipeline)
diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml
index 013b05628fa..99c71e1454a 100644
--- a/app/views/projects/merge_requests/show/_diffs.html.haml
+++ b/app/views/projects/merge_requests/show/_diffs.html.haml
@@ -1,4 +1,5 @@
- if @merge_request_diff.collected?
+ = render 'projects/merge_requests/show/versions'
= render "projects/diffs/diffs", diffs: @diffs
- elsif @merge_request_diff.empty?
.nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 098ce19da21..e35291dff7d 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,3 +1,7 @@
+- if @merge_request.closed_without_fork?
+ .alert.alert-danger
+ %p The source project of this merge request has been removed.
+
.clearfix.detail-page-header
.issuable-header
.issuable-status-box.status-box{ class: status_box_class(@merge_request) }
diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml
new file mode 100644
index 00000000000..2da70ce7137
--- /dev/null
+++ b/app/views/projects/merge_requests/show/_versions.html.haml
@@ -0,0 +1,31 @@
+- merge_request_diffs = @merge_request.merge_request_diffs.select_without_diff
+
+- if merge_request_diffs.size > 1
+ .mr-version-switch
+ Version:
+ %span.dropdown.inline
+ %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} }
+ %strong.monospace<
+ - if @merge_request_diff.latest?
+ #{"latest"}
+ - else
+ #{@merge_request_diff.head_commit.short_id}
+ %span.caret
+ %ul.dropdown-menu.dropdown-menu-selectable
+ - 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
+ %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
+ = 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}
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index ea4898f2107..fda0592dd41 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -55,16 +55,11 @@
= render 'bitbucket_import_modal'
%div
- if gitlab_import_enabled?
- = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless bitbucket_import_configured?}" do
+ = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do
= icon('gitlab', text: 'GitLab.com')
- unless gitlab_import_configured?
= render 'gitlab_import_modal'
%div
- - if gitorious_import_enabled?
- = link_to new_import_gitorious_path, class: 'btn import_gitorious' do
- %i.icon-gitorious.icon-gitorious-small
- Gitorious.org
- %div
- if google_code_import_enabled?
= link_to new_import_google_code_path, class: 'btn import_google_code' do
= icon('google', text: 'Google Code')
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index d2ac1ce2b9a..7c82177f9ea 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -52,11 +52,11 @@
- if note.emoji_awardable?
= link_to '#', title: 'Award Emoji', class: 'note-action-button note-emoji-button js-add-award js-note-emoji', data: { position: 'right' } do
= icon('spinner spin')
- = icon('smile-o')
+ = icon('smile-o', class: 'link-highlight')
- if note_editable
= link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
- = icon('pencil')
+ = icon('pencil', class: 'link-highlight')
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button hidden-xs js-note-delete danger' do
= icon('trash-o')
.note-body{class: note_editable ? 'js-task-list-container' : ''}
diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml
index d65faf86d4e..f611ddc8f5f 100644
--- a/app/views/projects/pipelines/_head.html.haml
+++ b/app/views/projects/pipelines/_head.html.haml
@@ -1,19 +1,21 @@
-.nav-links.sub-nav
- %ul{ class: (container_class) }
- - if project_nav_tab? :pipelines
- = nav_link(controller: :pipelines) do
- = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
- %span
- Pipelines
+.scrolling-tabs-container.sub-nav-scroll
+ = render 'shared/nav_scroll'
+ .nav-links.sub-nav.scrolling-tabs
+ %ul{ class: (container_class) }
+ - if project_nav_tab? :pipelines
+ = nav_link(controller: :pipelines) do
+ = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
+ %span
+ Pipelines
- - if project_nav_tab? :builds
- = nav_link(controller: %w(builds)) do
- = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
- %span
- Builds
+ - if project_nav_tab? :builds
+ = nav_link(controller: %w(builds)) do
+ = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
+ %span
+ Builds
- - if project_nav_tab? :environments
- = nav_link(controller: %w(environments)) do
- = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
- %span
- Environments
+ - if project_nav_tab? :environments
+ = nav_link(controller: %w(environments)) do
+ = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
+ %span
+ Environments
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index 5f466bdbac2..4d957e0d890 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -49,7 +49,10 @@
%th Commit
- stages.each do |stage|
%th.stage
- %span.has-tooltip{ title: "#{stage.titleize}" }
+ - if stage.titleize.length > 12
+ %span.has-tooltip{ title: "#{stage.titleize}" }
+ = stage.titleize
+ - else
= stage.titleize
%th
%th
diff --git a/app/views/projects/project_members/update.js.haml b/app/views/projects/project_members/update.js.haml
index 833954bc039..37e55dc72a3 100644
--- a/app/views/projects/project_members/update.js.haml
+++ b/app/views/projects/project_members/update.js.haml
@@ -1,3 +1,3 @@
:plain
$("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @project_member))}');
- new MemberExpirationDate();
+ new gl.MemberExpirationDate();
diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml
deleted file mode 100644
index 24658319060..00000000000
--- a/app/views/projects/repositories/_download_archive.html.haml
+++ /dev/null
@@ -1,37 +0,0 @@
-- ref = ref || nil
-- btn_class = btn_class || ''
-- split_button = split_button || false
-- if split_button == true
- %span.btn-group{class: btn_class}
- = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn col-xs-10', rel: 'nofollow' do
- %i.fa.fa-download
- %span Download zip
- %a.col-xs-2.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' }
- %span.caret
- %span.sr-only
- Select Archive Format
- %ul.col-xs-10.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
- %li
- = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), rel: 'nofollow' do
- %i.fa.fa-download
- %span Download zip
- %li
- = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
- %i.fa.fa-download
- %span Download tar.gz
- %li
- = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
- %i.fa.fa-download
- %span Download tar.bz2
- %li
- = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar'), rel: 'nofollow' do
- %i.fa.fa-download
- %span Download tar
-- else
- %span.btn-group{class: btn_class}
- = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
- %i.fa.fa-download
- %span zip
- = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), class: 'btn', rel: 'nofollow' do
- %i.fa.fa-download
- %span tar.gz
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 340e159c874..9adce776c1c 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -72,7 +72,7 @@
= render "projects/buttons/koding"
.btn-group.project-repo-btn-group
- = render "projects/buttons/download"
+ = render 'projects/buttons/download', project: @project, ref: @ref
= render 'projects/buttons/dropdown'
= render 'shared/notifications/button', notification_setting: @notification_setting
diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml
deleted file mode 100644
index 8a11dbfa9f4..00000000000
--- a/app/views/projects/tags/_download.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-%span.btn-group
- = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), class: 'btn btn-default', rel: 'nofollow' do
- %span Source code
- %a.btn.btn-default.dropdown-toggle{ 'data-toggle' => 'dropdown' }
- %span.caret
- %span.sr-only
- Select Archive Format
- %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
- %li
- = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do
- %span Download zip
- %li
- = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
- %span Download tar.gz
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index 2c11c0e5b21..a156d98bab8 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -11,8 +11,7 @@
= strip_gpg_signature(tag.message)
.controls
- - if can?(current_user, :download_code, @project)
- = render 'projects/tags/download', ref: tag.name, project: @project
+ = render 'projects/buttons/download', project: @project, ref: tag.name
- if can?(current_user, :push_code, @project)
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn has-tooltip', title: "Edit release notes", data: { container: "body" } do
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 368231e73fe..6adbe9351dc 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -8,21 +8,24 @@
Tags give the ability to mark specific points in history as being important
.nav-controls
- - if can? current_user, :push_code, @project
- = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
- New tag
+ = form_tag(filter_tags_path, method: :get) do
+ = search_field_tag :search, params[:search], { placeholder: 'Filter by tag name', id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false }
.dropdown.inline
%button.dropdown-toggle.btn{ type: 'button', data: { toggle: 'dropdown'} }
- %span.light= @sort.humanize
+ %span.light
+ = @sort.humanize
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
%li
- = link_to namespace_project_tags_path(sort: nil) do
+ = link_to filter_tags_path(sort: nil) do
Name
- = link_to namespace_project_tags_path(sort: sort_value_recently_updated) do
+ = link_to filter_tags_path(sort: sort_value_recently_updated) do
= sort_title_recently_updated
- = link_to namespace_project_tags_path(sort: sort_value_oldest_updated) do
+ = link_to filter_tags_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated
+ - if can?(current_user, :push_code, @project)
+ = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
+ New tag
.tags
- if @tags.any?
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 395d7af6cbb..4dd7439b2d0 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -12,8 +12,7 @@
= icon('files-o')
= link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse commits' do
= icon('history')
- - if can? current_user, :download_code, @project
- = render 'projects/tags/download', ref: @tag.name, project: @project
+ = render 'projects/buttons/download', project: @project, ref: @tag.name
- if can?(current_user, :admin_project, @project)
.pull-right
= link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index 558e6146ae9..ca5d2d7722a 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -5,16 +5,17 @@
%tr
%th Name
%th Last Update
- %th.hidden-xs
- .pull-left Last Commit
- .last-commit.hidden-sm.pull-left
- &nbsp;
+ %th.hidden-xs.last-commit
+ Last Commit
+ .last-commit-content.hidden-sm
%i.fa.fa-angle-right
&nbsp;
%small.light
= link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
&ndash;
- = truncate(@commit.title, length: 50)
+ = 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'
- if @path.present?
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index bf5360b4dee..37d341212af 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -10,8 +10,7 @@
%div{ class: container_class }
.tree-controls
= render 'projects/find_file_link'
- - if can? current_user, :download_code, @project
- = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'hidden-xs hidden-sm btn-grouped', split_button: true
+ = render 'projects/buttons/download', project: @project, ref: @ref
#tree-holder.tree-holder.clearfix
.nav-block
diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml
index f8ea479e0b1..551a20c1044 100644
--- a/app/views/projects/wikis/_nav.html.haml
+++ b/app/views/projects/wikis/_nav.html.haml
@@ -1,13 +1,15 @@
-.nav-links.sub-nav
- %ul{ class: (container_class) }
- = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
- = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
+.scrolling-tabs-container.sub-nav-scroll
+ = render 'shared/nav_scroll'
+ .nav-links.sub-nav.scrolling-tabs
+ %ul{ class: (container_class) }
+ = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
+ = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
- = nav_link(path: 'wikis#pages') do
- = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project)
+ = nav_link(path: 'wikis#pages') do
+ = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project)
- = nav_link(path: 'wikis#git_access') do
- = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
- Git Access
+ = nav_link(path: 'wikis#git_access') do
+ = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
+ Git Access
- = render 'projects/wikis/new'
+ = render 'projects/wikis/new'
diff --git a/app/views/shared/_logo.svg b/app/views/shared/_logo.svg
index b07f1c5603e..9b67422da2c 100644
--- a/app/views/shared/_logo.svg
+++ b/app/views/shared/_logo.svg
@@ -1,9 +1,9 @@
-<svg width="36" height="36" id="tanuki-logo">
- <path id="tanuki-right-ear" class="tanuki-shape" fill="#e24329" d="M2 14l9.38 9v-9l-4-12.28c-.205-.632-1.176-.632-1.38 0z"/>
- <path id="tanuki-left-ear" class="tanuki-shape" fill="#e24329" d="M34 14l-9.38 9v-9l4-12.28c.205-.632 1.176-.632 1.38 0z"/>
- <path id="tanuki-nose" class="tanuki-shape" fill="#e24329" d="M18,34.38 3,14 33,14 Z"/>
- <path id="tanuki-right-eye" class="tanuki-shape" fill="#fc6d26" d="M18,34.38 11.38,14 2,14 6,25Z"/>
- <path id="tanuki-left-eye" class="tanuki-shape" fill="#fc6d26" d="M18,34.38 24.62,14 34,14 30,25Z"/>
- <path id="tanuki-right-cheek" class="tanuki-shape" fill="#fca326" d="M2 14L.1 20.16c-.18.565 0 1.2.5 1.56l17.42 12.66z"/>
- <path id="tanuki-left-cheek" class="tanuki-shape" fill="#fca326" d="M34 14l1.9 6.16c.18.565 0 1.2-.5 1.56L18 34.38z"/>
+<svg width="36" height="36" class="tanuki-logo">
+ <path class="tanuki-shape tanuki-left-ear" fill="#e24329" d="M2 14l9.38 9v-9l-4-12.28c-.205-.632-1.176-.632-1.38 0z"/>
+ <path class="tanuki-shape tanuki-right-ear" fill="#e24329" d="M34 14l-9.38 9v-9l4-12.28c.205-.632 1.176-.632 1.38 0z"/>
+ <path class="tanuki-shape tanuki-nose" fill="#e24329" d="M18,34.38 3,14 33,14 Z"/>
+ <path class="tanuki-shape tanuki-left-eye" fill="#fc6d26" d="M18,34.38 11.38,14 2,14 6,25Z"/>
+ <path class="tanuki-shape tanuki-right-eye" fill="#fc6d26" d="M18,34.38 24.62,14 34,14 30,25Z"/>
+ <path class="tanuki-shape tanuki-left-cheek" fill="#fca326" d="M2 14L.1 20.16c-.18.565 0 1.2.5 1.56l17.42 12.66z"/>
+ <path class="tanuki-shape tanuki-right-cheek" fill="#fca326" d="M34 14l1.9 6.16c.18.565 0 1.2-.5 1.56L18 34.38z"/>
</svg>
diff --git a/app/views/shared/_nav_scroll.html.haml b/app/views/shared/_nav_scroll.html.haml
new file mode 100644
index 00000000000..4e3b1b3a571
--- /dev/null
+++ b/app/views/shared/_nav_scroll.html.haml
@@ -0,0 +1,4 @@
+.fade-left
+ = icon('angle-left')
+.fade-right
+ = icon('angle-right') \ No newline at end of file
diff --git a/app/views/shared/icons/_icon_play.svg b/app/views/shared/icons/_icon_play.svg
index 80a6d41dbf6..e965afa9a56 100644
--- a/app/views/shared/icons/_icon_play.svg
+++ b/app/views/shared/icons/_icon_play.svg
@@ -1 +1,3 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 11"><path fill-rule="evenodd" d="m9.283 6.47l-7.564 4.254c-.949.534-1.719.266-1.719-.576v-9.292c0-.852.756-1.117 1.719-.576l7.564 4.254c.949.534.963 1.392 0 1.934"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 11" class="icon-play">
+ <path fill-rule="evenodd" d="m9.283 6.47l-7.564 4.254c-.949.534-1.719.266-1.719-.576v-9.292c0-.852.756-1.117 1.719-.576l7.564 4.254c.949.534.963 1.392 0 1.934"/>
+ </svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_icon_status_created.svg b/app/views/shared/icons/_icon_status_created.svg
new file mode 100644
index 00000000000..1f5c3b51b03
--- /dev/null
+++ b/app/views/shared/icons/_icon_status_created.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" enable-background="new 0 0 14 14"><path d="M12.5,7 C12.5,4 10,1.5 7,1.5 C4,1.5 1.5,4 1.5,7 C1.5,10 4,12.5 7,12.5 C10,12.5 12.5,10 12.5,7 L12.5,7 Z M0,7 C0,3.1 3.1,0 7,0 C10.9,0 14,3.1 14,7 C14,10.9 10.9,14 7,14 C3.1,14 0,10.9 0,7 L0,7 Z" /><circle cx="7" cy="7" r="3.25"/></svg>
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 4f8ea7e7cef..0f4f744a71f 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -27,15 +27,18 @@
= render "shared/issuable/label_dropdown"
.pull-right
- - if controller.controller_name == 'boards' && can?(current_user, :admin_list, @project)
- .dropdown
- %button.btn.btn-create.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, project_id: @project.try(:id) } }
- Create new list
- .dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable
- = render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Create a new list" }
- - if can?(current_user, :admin_label, @project)
- = render partial: "shared/issuable/label_page_create"
- = dropdown_loading
+ - if controller.controller_name == 'boards'
+ #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)
+ .dropdown.pull-right
+ %button.btn.btn-create.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, project_id: @project.try(:id) } }
+ Create new list
+ .dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable
+ = render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Create a new list" }
+ - if can?(current_user, :admin_label, @project)
+ = render partial: "shared/issuable/label_page_create"
+ = dropdown_loading
- else
= render 'shared/sort_dropdown'
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 22594b46443..3856a4917b4 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -134,7 +134,7 @@
title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' }
= icon('question-circle')
-- if issuable.is_a?(MergeRequest)
+- if issuable.is_a?(MergeRequest) && !issuable.closed_without_fork?
%hr
- if @merge_request.new_record?
.form-group
@@ -175,7 +175,7 @@
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel'
- else
.pull-right
- - if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project)
+ - if can?(current_user, :"destroy_#{issuable.to_ability_name}", @project)
= link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" },
method: :delete, class: 'btn btn-danger btn-grouped'
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel'
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index d34d28f6736..24a1a616919 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -12,7 +12,7 @@
- if params[:label_name].present?
- if params[:label_name].respond_to?('any?')
- params[:label_name].each do |label|
- = hidden_field_tag "label_name[]", label, id: nil
+ = hidden_field_tag "label_name[]", u(label), id: nil
.dropdown
%button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data}
%span.dropdown-toggle-text
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index c1b50e65af5..b13daaf43c9 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -118,7 +118,7 @@
= icon('spinner spin', class: 'block-loading')
- if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right'
- .value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels_array.any?) }
+ .value.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels_array.any?) }
- if issuable.labels_array.any?
- issuable.labels_array.each do |label|
= link_to_label(label, type: issuable.to_ability_name)
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index c7f39868e71..9a052abe40a 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -123,6 +123,6 @@
:javascript
var userProfile;
- userProfile = new User({
+ userProfile = new gl.User({
action: "#{controller.action_name}"
});