Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorLin Jen-Shin <godfat@godfat.org>2016-12-09 20:27:56 +0300
committerLin Jen-Shin <godfat@godfat.org>2016-12-09 20:27:56 +0300
commite0df1b5f9944cff1ee7234c9fa292a7a06456c08 (patch)
treebd3e760fb41f1974bd6aac0ccefbc6f97cdce04f /app
parent3e01385bca92dc8c0df3aa4032cc58d708dc0ff5 (diff)
parentad4c2a08104cba0557d824fac6a70eedd45921b7 (diff)
Merge remote-tracking branch 'upstream/master' into fix-git-hooks-when-creating-file
* upstream/master: (60 commits) Refactor SSH keys docs Improvements to setting up ssh Do not reload diff for merge request made from fork when target branch in fork is updated Add 8.12.10, 8.12.11, and 8.12.12 CHANGELOG.md items Changes after review Fix broken test Adds CHANGELOG entry Adds tests Uniformize props name format Replace commit icon svg logic Replace play icon svg logic Update docs to reflect new defaults on omnibus Merge branch 'jej-23867-use-mr-finder-instead-of-access-check' into 'security' Merge branch 'html-safe-diff-line-content' into 'security' Merge branch 'rs-filter-authentication_token' into 'security' Merge branch 'destroy-session' into 'security' remove unnecessary issues event filter on comments tab Updating reference to database password Add CHANGELOG entry Displays milestone remaining days only when it's present ...
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/blob/template_selector.js.es62
-rw-r--r--app/assets/javascripts/dispatcher.js.es61
-rw-r--r--app/assets/javascripts/environments/components/environment.js.es610
-rw-r--r--app/assets/javascripts/environments/components/environment_actions.js.es634
-rw-r--r--app/assets/javascripts/environments/components/environment_external_url.js.es64
-rw-r--r--app/assets/javascripts/environments/components/environment_item.js.es630
-rw-r--r--app/assets/javascripts/environments/components/environment_rollback.js.es69
-rw-r--r--app/assets/javascripts/environments/components/environment_stop.js.es64
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js4
-rw-r--r--app/assets/javascripts/signin_tabs_memoizer.js.es649
-rw-r--r--app/assets/javascripts/vue_common_component/commit.js.es645
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/asciidoctor.scss27
-rw-r--r--app/assets/stylesheets/framework/awards.scss5
-rw-r--r--app/assets/stylesheets/framework/common.scss1
-rw-r--r--app/assets/stylesheets/framework/nav.scss4
-rw-r--r--app/assets/stylesheets/pages/notes.scss3
-rw-r--r--app/controllers/admin/groups_controller.rb2
-rw-r--r--app/controllers/concerns/creates_commit.rb6
-rw-r--r--app/controllers/groups/application_controller.rb2
-rw-r--r--app/controllers/projects/commit_controller.rb16
-rw-r--r--app/controllers/projects/commits_controller.rb2
-rw-r--r--app/controllers/projects/compare_controller.rb2
-rw-r--r--app/controllers/projects/discussions_controller.rb2
-rw-r--r--app/controllers/projects/project_members_controller.rb30
-rw-r--r--app/controllers/projects/todos_controller.rb2
-rw-r--r--app/controllers/registrations_controller.rb5
-rw-r--r--app/controllers/sessions_controller.rb2
-rw-r--r--app/finders/issuable_finder.rb4
-rw-r--r--app/finders/notes_finder.rb2
-rw-r--r--app/helpers/commits_helper.rb4
-rw-r--r--app/helpers/diff_helper.rb4
-rw-r--r--app/helpers/events_helper.rb6
-rw-r--r--app/helpers/groups_helper.rb2
-rw-r--r--app/models/ci/pipeline.rb34
-rw-r--r--app/models/ci/stage.rb37
-rw-r--r--app/models/commit.rb41
-rw-r--r--app/models/commit_status.rb25
-rw-r--r--app/models/concerns/has_status.rb5
-rw-r--r--app/models/concerns/milestoneish.rb10
-rw-r--r--app/models/concerns/routable.rb70
-rw-r--r--app/models/merge_request.rb6
-rw-r--r--app/models/namespace.rb18
-rw-r--r--app/models/project.rb97
-rw-r--r--app/models/repository.rb2
-rw-r--r--app/models/route.rb22
-rw-r--r--app/services/ci/process_pipeline_service.rb4
-rw-r--r--app/services/commits/change_service.rb2
-rw-r--r--app/services/destroy_group_service.rb4
-rw-r--r--app/services/merge_requests/refresh_service.rb10
-rw-r--r--app/uploaders/uploader_helper.rb2
-rw-r--r--app/views/devise/sessions/new.html.haml1
-rw-r--r--app/views/layouts/nav/_project.html.haml2
-rw-r--r--app/views/notify/pipeline_success_email.html.haml2
-rw-r--r--app/views/notify/pipeline_success_email.text.erb2
-rw-r--r--app/views/projects/_merge_request_merge_settings.html.haml15
-rw-r--r--app/views/projects/_merge_request_settings.html.haml26
-rw-r--r--app/views/projects/builds/_sidebar.html.haml4
-rw-r--r--app/views/projects/ci/builds/_build.html.haml8
-rw-r--r--app/views/projects/ci/pipelines/_pipeline.html.haml12
-rw-r--r--app/views/projects/commit/_change.html.haml2
-rw-r--r--app/views/projects/commit/_ci_stage.html.haml15
-rw-r--r--app/views/projects/commit/_pipeline.html.haml19
-rw-r--r--app/views/projects/commit/_pipeline_stage.html.haml14
-rw-r--r--app/views/projects/commit/_pipelines_list.haml2
-rw-r--r--app/views/projects/edit.html.haml3
-rw-r--r--app/views/projects/environments/index.html.haml4
-rw-r--r--app/views/projects/forks/error.html.haml6
-rw-r--r--app/views/projects/merge_requests/_show.html.haml2
-rw-r--r--app/views/projects/pipelines/_graph.html.haml4
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml18
-rw-r--r--app/views/projects/pipelines/index.html.haml3
-rw-r--r--app/views/projects/stage/_graph.html.haml22
-rw-r--r--app/views/projects/stage/_in_stage_group.html.haml (renamed from app/views/projects/commit/_pipeline_status_group.html.haml)0
-rw-r--r--app/views/projects/stage/_stage.html.haml13
-rw-r--r--app/views/shared/_event_filter.html.haml2
-rw-r--r--app/views/shared/members/_member.html.haml6
-rw-r--r--app/views/shared/milestones/_summary.html.haml6
78 files changed, 565 insertions, 363 deletions
diff --git a/app/assets/javascripts/blob/template_selector.js.es6 b/app/assets/javascripts/blob/template_selector.js.es6
index 5434a19bcec..0ff5c0fab05 100644
--- a/app/assets/javascripts/blob/template_selector.js.es6
+++ b/app/assets/javascripts/blob/template_selector.js.es6
@@ -70,6 +70,8 @@
// e.g.
// Api.gitignoreText item.name, @requestFileSuccess.bind(@)
requestFileSuccess(file, { skipFocus } = {}) {
+ if (!file) return;
+
const oldValue = this.editor.getValue();
let newValue = file.content;
diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6
index 3a7c5ff3681..413117c2226 100644
--- a/app/assets/javascripts/dispatcher.js.es6
+++ b/app/assets/javascripts/dispatcher.js.es6
@@ -24,6 +24,7 @@
switch (page) {
case 'sessions:new':
new UsernameValidator();
+ new ActiveTabMemoizer();
break;
case 'projects:boards:show':
case 'projects:boards:index':
diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6
index 84faabf938a..1db29dd47fb 100644
--- a/app/assets/javascripts/environments/components/environment.js.es6
+++ b/app/assets/javascripts/environments/components/environment.js.es6
@@ -74,6 +74,8 @@
projectStoppedEnvironmentsPath: environmentsData.projectStoppedEnvironmentsPath,
newEnvironmentPath: environmentsData.newEnvironmentPath,
helpPagePath: environmentsData.helpPagePath,
+ commitIconSvg: environmentsData.commitIconSvg,
+ playIconSvg: environmentsData.playIconSvg,
};
},
@@ -227,7 +229,9 @@
:model="model"
:toggleRow="toggleRow.bind(model)"
:can-create-deployment="canCreateDeploymentParsed"
- :can-read-environment="canReadEnvironmentParsed"></tr>
+ :can-read-environment="canReadEnvironmentParsed"
+ :play-icon-svg="playIconSvg"
+ :commit-icon-svg="commitIconSvg"></tr>
<tr v-if="model.isOpen && model.children && model.children.length > 0"
is="environment-item"
@@ -235,7 +239,9 @@
:model="children"
:toggleRow="toggleRow.bind(children)"
:can-create-deployment="canCreateDeploymentParsed"
- :can-read-environment="canReadEnvironmentParsed">
+ :can-read-environment="canReadEnvironmentParsed"
+ :play-icon-svg="playIconSvg"
+ :commit-icon-svg="commitIconSvg">
</tr>
</template>
diff --git a/app/assets/javascripts/environments/components/environment_actions.js.es6 b/app/assets/javascripts/environments/components/environment_actions.js.es6
index d149a446e0b..7c743705d51 100644
--- a/app/assets/javascripts/environments/components/environment_actions.js.es6
+++ b/app/assets/javascripts/environments/components/environment_actions.js.es6
@@ -12,38 +12,18 @@
required: false,
default: () => [],
},
- },
-
- /**
- * Appends the svg icon that were render in the index page.
- * In order to reuse the svg instead of copy and paste in this template
- * we need to render it outside this component using =custom_icon partial.
- *
- * TODO: Remove this when webpack is merged.
- *
- */
- mounted() {
- const playIcon = document.querySelector('.play-icon-svg.hidden svg');
-
- const dropdownContainer = this.$el.querySelector('.dropdown-play-icon-container');
- const actionContainers = this.$el.querySelectorAll('.action-play-icon-container');
- // Phantomjs does not have support to iterate a nodelist.
- const actionsArray = [].slice.call(actionContainers);
-
- if (playIcon && actionsArray && dropdownContainer) {
- dropdownContainer.appendChild(playIcon.cloneNode(true));
- actionsArray.forEach((element) => {
- element.appendChild(playIcon.cloneNode(true));
- });
- }
+ playIconSvg: {
+ type: String,
+ required: false,
+ },
},
template: `
<div class="inline">
<div class="dropdown">
<a class="dropdown-new btn btn-default" data-toggle="dropdown">
- <span class="dropdown-play-icon-container"></span>
+ <span class="js-dropdown-play-icon-container" v-html="playIconSvg"></span>
<i class="fa fa-caret-down"></i>
</a>
@@ -53,7 +33,9 @@
data-method="post"
rel="nofollow"
class="js-manual-action-link">
- <span class="action-play-icon-container"></span>
+
+ <span class="js-action-play-icon-container" v-html="playIconSvg"></span>
+
<span>
{{action.name}}
</span>
diff --git a/app/assets/javascripts/environments/components/environment_external_url.js.es6 b/app/assets/javascripts/environments/components/environment_external_url.js.es6
index 79cd5ded5bd..aed65b33c04 100644
--- a/app/assets/javascripts/environments/components/environment_external_url.js.es6
+++ b/app/assets/javascripts/environments/components/environment_external_url.js.es6
@@ -7,14 +7,14 @@
window.gl.environmentsList.ExternalUrlComponent = Vue.component('external-url-component', {
props: {
- external_url: {
+ externalUrl: {
type: String,
default: '',
},
},
template: `
- <a class="btn external_url" :href="external_url" target="_blank">
+ <a class="btn external_url" :href="externalUrl" target="_blank">
<i class="fa fa-external-link"></i>
</a>
`,
diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6
index 6ed14261fc3..2e046a60146 100644
--- a/app/assets/javascripts/environments/components/environment_item.js.es6
+++ b/app/assets/javascripts/environments/components/environment_item.js.es6
@@ -58,6 +58,16 @@
required: false,
default: false,
},
+
+ commitIconSvg: {
+ type: String,
+ required: false,
+ },
+
+ playIconSvg: {
+ type: String,
+ required: false,
+ },
},
data() {
@@ -451,11 +461,12 @@
<div v-if="!isFolder && hasLastDeploymentKey" class="js-commit-component">
<commit-component
:tag="commitTag"
- :commit_ref="commitRef"
- :commit_url="commitUrl"
- :short_sha="commitShortSha"
+ :commit-ref="commitRef"
+ :commit-url="commitUrl"
+ :short-sha="commitShortSha"
:title="commitTitle"
- :author="commitAuthor">
+ :author="commitAuthor"
+ :commit-icon-svg="commitIconSvg">
</commit-component>
</div>
<p v-if="!isFolder && !hasLastDeploymentKey" class="commit-title">
@@ -476,6 +487,7 @@
<div v-if="hasManualActions && canCreateDeployment"
class="inline js-manual-actions-container">
<actions-component
+ :play-icon-svg="playIconSvg"
:actions="manualActions">
</actions-component>
</div>
@@ -483,22 +495,22 @@
<div v-if="model.external_url && canReadEnvironment"
class="inline js-external-url-container">
<external-url-component
- :external_url="model.external_url">
- </external_url-component>
+ :external-url="model.external_url">
+ </external-url-component>
</div>
<div v-if="isStoppable && canCreateDeployment"
class="inline js-stop-component-container">
<stop-component
- :stop_url="model.stop_path">
+ :stop-url="model.stop_path">
</stop-component>
</div>
<div v-if="canRetry && canCreateDeployment"
class="inline js-rollback-component-container">
<rollback-component
- :is_last_deployment="isLastDeployment"
- :retry_url="retryUrl">
+ :is-last-deployment="isLastDeployment"
+ :retry-url="retryUrl">
</rollback-component>
</div>
</div>
diff --git a/app/assets/javascripts/environments/components/environment_rollback.js.es6 b/app/assets/javascripts/environments/components/environment_rollback.js.es6
index 55e5c826e07..6d4e8fad604 100644
--- a/app/assets/javascripts/environments/components/environment_rollback.js.es6
+++ b/app/assets/javascripts/environments/components/environment_rollback.js.es6
@@ -7,19 +7,20 @@
window.gl.environmentsList.RollbackComponent = Vue.component('rollback-component', {
props: {
- retry_url: {
+ retryUrl: {
type: String,
default: '',
},
- is_last_deployment: {
+
+ isLastDeployment: {
type: Boolean,
default: true,
},
},
template: `
- <a class="btn" :href="retry_url" data-method="post" rel="nofollow">
- <span v-if="is_last_deployment">
+ <a class="btn" :href="retryUrl" data-method="post" rel="nofollow">
+ <span v-if="isLastDeployment">
Re-deploy
</span>
<span v-else>
diff --git a/app/assets/javascripts/environments/components/environment_stop.js.es6 b/app/assets/javascripts/environments/components/environment_stop.js.es6
index e6d66a0148c..7292f924e5c 100644
--- a/app/assets/javascripts/environments/components/environment_stop.js.es6
+++ b/app/assets/javascripts/environments/components/environment_stop.js.es6
@@ -7,7 +7,7 @@
window.gl.environmentsList.StopComponent = Vue.component('stop-component', {
props: {
- stop_url: {
+ stopUrl: {
type: String,
default: '',
},
@@ -15,7 +15,7 @@
template: `
<a class="btn stop-env-link"
- :href="stop_url"
+ :href="stopUrl"
data-confirm="Are you sure you want to stop this environment?"
data-method="post"
rel="nofollow">
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 963d2851e5f..e8e502694d6 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -29,7 +29,7 @@
setTimeago = true;
}
- $timeagoEls.each(function() {
+ $timeagoEls.filter(':not([data-timeago-rendered])').each(function() {
var $el = $(this);
$el.attr('title', gl.utils.formatDate($el.attr('datetime')));
@@ -39,6 +39,8 @@
template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
});
}
+
+ $el.attr('data-timeago-rendered', true);
gl.utils.renderTimeago($el);
});
};
diff --git a/app/assets/javascripts/signin_tabs_memoizer.js.es6 b/app/assets/javascripts/signin_tabs_memoizer.js.es6
new file mode 100644
index 00000000000..d811d1cd53a
--- /dev/null
+++ b/app/assets/javascripts/signin_tabs_memoizer.js.es6
@@ -0,0 +1,49 @@
+/* eslint no-param-reassign: ["error", { "props": false }]*/
+/* eslint no-new: "off" */
+((global) => {
+ /**
+ * Memorize the last selected tab after reloading a page.
+ * Does that setting the current selected tab in the localStorage
+ */
+ class ActiveTabMemoizer {
+ constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
+ this.currentTabKey = currentTabKey;
+ this.tabSelector = tabSelector;
+ this.bootstrap();
+ }
+
+ bootstrap() {
+ const tabs = document.querySelectorAll(this.tabSelector);
+ if (tabs.length > 0) {
+ tabs[0].addEventListener('click', (e) => {
+ if (e.target && e.target.nodeName === 'A') {
+ const anchorName = e.target.getAttribute('href');
+ this.saveData(anchorName);
+ }
+ });
+ }
+
+ this.showTab();
+ }
+
+ showTab() {
+ const anchorName = this.readData();
+ if (anchorName) {
+ const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`);
+ if (tab) {
+ tab.click();
+ }
+ }
+ }
+
+ saveData(val) {
+ localStorage.setItem(this.currentTabKey, val);
+ }
+
+ readData() {
+ return localStorage.getItem(this.currentTabKey);
+ }
+ }
+
+ global.ActiveTabMemoizer = ActiveTabMemoizer;
+})(window);
diff --git a/app/assets/javascripts/vue_common_component/commit.js.es6 b/app/assets/javascripts/vue_common_component/commit.js.es6
index 2ef2959cbf4..62a22e39a3b 100644
--- a/app/assets/javascripts/vue_common_component/commit.js.es6
+++ b/app/assets/javascripts/vue_common_component/commit.js.es6
@@ -23,7 +23,7 @@
* name
* ref_url
*/
- commit_ref: {
+ commitRef: {
type: Object,
required: false,
default: () => ({}),
@@ -32,16 +32,16 @@
/**
* Used to link to the commit sha.
*/
- commit_url: {
+ commitUrl: {
type: String,
required: false,
default: '',
},
/**
- * Used to show the commit short_sha that links to the commit url.
+ * Used to show the commit short sha that links to the commit url.
*/
- short_sha: {
+ shortSha: {
type: String,
required: false,
default: '',
@@ -68,6 +68,11 @@
required: false,
default: () => ({}),
},
+
+ commitIconSvg: {
+ type: String,
+ required: false,
+ },
},
computed: {
@@ -80,7 +85,7 @@
* @returns {Boolean}
*/
hasCommitRef() {
- return this.commit_ref && this.commit_ref.name && this.commit_ref.ref_url;
+ return this.commitRef && this.commitRef.name && this.commitRef.ref_url;
},
/**
@@ -110,24 +115,6 @@
},
},
- /**
- * In order to reuse the svg instead of copy and paste in this template
- * we need to render it outside this component using =custom_icon partial.
- * Make sure it has this structure:
- * .commit-icon-svg.hidden
- * svg
- *
- * TODO: Find a better way to include SVG
- */
- mounted() {
- const commitIconContainer = this.$el.querySelector('.commit-icon-container');
- const commitIcon = document.querySelector('.commit-icon-svg.hidden svg');
-
- if (commitIconContainer && commitIcon) {
- commitIconContainer.appendChild(commitIcon.cloneNode(true));
- }
- },
-
template: `
<div class="branch-commit">
@@ -138,15 +125,15 @@
<a v-if="hasCommitRef"
class="monospace branch-name"
- :href="commit_ref.ref_url">
- {{commit_ref.name}}
+ :href="commitRef.ref_url">
+ {{commitRef.name}}
</a>
- <div class="icon-container commit-icon commit-icon-container"></div>
+ <div v-html="commitIconSvg" class="commit-icon js-commit-icon"></div>
<a class="commit-id monospace"
- :href="commit_url">
- {{short_sha}}
+ :href="commitUrl">
+ {{shortSha}}
</a>
<p class="commit-title">
@@ -162,7 +149,7 @@
</a>
<a class="commit-row-message"
- :href="commit_url">
+ :href="commitUrl">
{{title}}
</a>
</span>
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 4d4835568ed..c82a9a2b9e3 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -6,6 +6,7 @@
@import "framework/animations.scss";
@import "framework/avatar.scss";
+@import "framework/asciidoctor.scss";
@import "framework/blocks.scss";
@import "framework/buttons.scss";
@import "framework/calendar.scss";
diff --git a/app/assets/stylesheets/framework/asciidoctor.scss b/app/assets/stylesheets/framework/asciidoctor.scss
new file mode 100644
index 00000000000..62493c32833
--- /dev/null
+++ b/app/assets/stylesheets/framework/asciidoctor.scss
@@ -0,0 +1,27 @@
+.admonitionblock td.icon {
+ width: 1%;
+
+ [class^="fa icon-"] {
+ @extend .fa-2x;
+ }
+
+ .icon-note {
+ @extend .fa-thumb-tack;
+ }
+
+ .icon-tip {
+ @extend .fa-lightbulb-o;
+ }
+
+ .icon-warning {
+ @extend .fa-exclamation-triangle;
+ }
+
+ .icon-caution {
+ @extend .fa-fire;
+ }
+
+ .icon-important {
+ @extend .fa-exclamation-circle;
+ }
+}
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index c13cb4a02b2..dece5c3202b 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -1,7 +1,7 @@
.awards {
.emoji-icon {
- width: 19px;
- height: 19px;
+ width: 20px;
+ height: 20px;
}
}
@@ -136,5 +136,6 @@
.award-control-icon {
color: $award-emoji-new-btn-icon-color;
+ margin-top: 1px;
}
}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 600bf17259b..251e43d2edd 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -255,6 +255,7 @@ img.emoji {
height: 20px;
vertical-align: top;
width: 20px;
+ margin-top: 1px;
}
.chart {
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 69da520f21f..ea77348633d 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -71,6 +71,10 @@
border-bottom: 2px solid $link-underline-blue;
color: $black;
font-weight: 600;
+
+ .badge {
+ color: $black;
+ }
}
.badge {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 16b099c09eb..10eb3d4203e 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -124,7 +124,7 @@ ul.notes {
position: absolute;
left: 0;
bottom: 0;
- background: linear-gradient(rgba($gray-light, 0.1) -100px, $white-light 100%);
+ background: linear-gradient(rgba($white-light, 0.1) -100px, $white-light 100%);
}
&.hide-shade {
@@ -413,7 +413,6 @@ ul.notes {
.fa {
color: $notes-action-color;
position: relative;
- top: 1px;
font-size: 17px;
}
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index aa7570cd896..1e3d194e9f9 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -56,7 +56,7 @@ class Admin::GroupsController < Admin::ApplicationController
private
def group
- @group ||= Group.find_by(path: params[:id])
+ @group ||= Group.find_by_full_path(params[:id])
end
def group_params
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index 5d11f286e9a..d2fcb2efd6a 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -82,10 +82,8 @@ module CreatesCommit
def merge_request_exists?
return @merge_request if defined?(@merge_request)
- @merge_request = @mr_target_project.merge_requests.opened.find_by(
- source_branch: @mr_source_branch,
- target_branch: @mr_target_branch
- )
+ @merge_request = MergeRequestsFinder.new(current_user, project_id: @mr_target_project.id).execute.opened.
+ find_by(source_branch: @mr_source_branch, target_branch: @mr_target_branch)
end
def different_project?
diff --git a/app/controllers/groups/application_controller.rb b/app/controllers/groups/application_controller.rb
index 949b4a6c25a..c411c21bb80 100644
--- a/app/controllers/groups/application_controller.rb
+++ b/app/controllers/groups/application_controller.rb
@@ -9,7 +9,7 @@ class Groups::ApplicationController < ApplicationController
def group
unless @group
id = params[:group_id] || params[:id]
- @group = Group.find_by(path: id)
+ @group = Group.find_by_full_path(id)
unless @group && can?(current_user, :read_group, @group)
@group = nil
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index cdfc1ba7b92..8197d9e4c99 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -65,7 +65,7 @@ class Projects::CommitController < Projects::ApplicationController
return render_404 if @target_branch.blank?
- create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title} has been successfully reverted.",
+ create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully reverted.",
success_path: successful_change_path, failure_path: failed_change_path)
end
@@ -74,26 +74,24 @@ class Projects::CommitController < Projects::ApplicationController
return render_404 if @target_branch.blank?
- create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title} has been successfully cherry-picked.",
+ create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully cherry-picked.",
success_path: successful_change_path, failure_path: failed_change_path)
end
private
def successful_change_path
- return referenced_merge_request_url if @commit.merged_merge_request
-
- namespace_project_commits_url(@project.namespace, @project, @target_branch)
+ referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @target_branch)
end
def failed_change_path
- return referenced_merge_request_url if @commit.merged_merge_request
-
- namespace_project_commit_url(@project.namespace, @project, params[:id])
+ referenced_merge_request_url || namespace_project_commit_url(@project.namespace, @project, params[:id])
end
def referenced_merge_request_url
- namespace_project_merge_request_url(@project.namespace, @project, @commit.merged_merge_request)
+ if merge_request = @commit.merged_merge_request(current_user)
+ namespace_project_merge_request_url(@project.namespace, @project, merge_request)
+ end
end
def commit
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index aba87b6144b..ad92f05a42d 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -21,7 +21,7 @@ class Projects::CommitsController < Projects::ApplicationController
@note_counts = project.notes.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
- @merge_request = @project.merge_requests.opened.
+ @merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.
find_by(source_project: @project, source_branch: @ref, target_branch: @repository.root_ref)
respond_to do |format|
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index e2b178314c0..325987199fa 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -54,7 +54,7 @@ class Projects::CompareController < Projects::ApplicationController
end
def merge_request
- @merge_request ||= @project.merge_requests.opened.
+ @merge_request ||= MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.
find_by(source_project: @project, source_branch: @head_ref, target_branch: @start_ref)
end
end
diff --git a/app/controllers/projects/discussions_controller.rb b/app/controllers/projects/discussions_controller.rb
index 148e39630e3..1349b015a63 100644
--- a/app/controllers/projects/discussions_controller.rb
+++ b/app/controllers/projects/discussions_controller.rb
@@ -24,7 +24,7 @@ class Projects::DiscussionsController < Projects::ApplicationController
private
def merge_request
- @merge_request ||= @project.merge_requests.find_by!(iid: params[:merge_request_id])
+ @merge_request ||= MergeRequestsFinder.new(current_user, project_id: @project.id).find_by!(iid: params[:merge_request_id])
end
def discussion
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index 699a56ae2f8..3fb8bba3cd0 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -10,14 +10,38 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@project_members = @project.project_members
@project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project)
+ group = @project.group
+
+ if group
+ # We need `.where.not(user_id: nil)` here otherwise when a group has an
+ # invitee, it would make the following query return 0 rows since a NULL
+ # user_id would be present in the subquery
+ # See http://stackoverflow.com/questions/129077/not-in-clause-and-null-values
+ # FIXME: This whole logic should be moved to a finder!
+ non_null_user_ids = @project_members.where.not(user_id: nil).select(:user_id)
+ group_members = group.group_members.where.not(user_id: non_null_user_ids)
+ group_members = group_members.non_invite unless can?(current_user, :admin_group, @group)
+ end
+
if params[:search].present?
- users = @project.users.search(params[:search]).to_a
- @project_members = @project_members.where(user_id: users)
+ user_ids = @project.users.search(params[:search]).select(:id)
+ @project_members = @project_members.where(user_id: user_ids)
+
+ if group_members
+ user_ids = group.users.search(params[:search]).select(:id)
+ group_members = group_members.where(user_id: user_ids)
+ end
@group_links = @project.project_group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
end
- @project_members = @project_members.order(access_level: :desc).page(params[:page])
+ member_ids = @project_members.pluck(:id)
+
+ if group_members
+ member_ids += group_members.pluck(:id)
+ end
+
+ @project_members = Member.where(id: member_ids).order(access_level: :desc).page(params[:page])
@requesters = AccessRequestsFinder.new(@project).execute(current_user)
diff --git a/app/controllers/projects/todos_controller.rb b/app/controllers/projects/todos_controller.rb
index 52517381c65..a41fcb85c40 100644
--- a/app/controllers/projects/todos_controller.rb
+++ b/app/controllers/projects/todos_controller.rb
@@ -18,7 +18,7 @@ class Projects::TodosController < Projects::ApplicationController
when "issue"
IssuesFinder.new(current_user, project_id: @project.id).find(params[:issuable_id])
when "merge_request"
- @project.merge_requests.find(params[:issuable_id])
+ MergeRequestsFinder.new(current_user, project_id: @project.id).find(params[:issuable_id])
end
end
end
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 3327f4f2b87..c45196cc3e9 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -27,7 +27,10 @@ class RegistrationsController < Devise::RegistrationsController
DeleteUserService.new(current_user).execute(current_user)
respond_to do |format|
- format.html { redirect_to new_user_session_path, notice: "Account successfully removed." }
+ format.html do
+ session.try(:destroy)
+ redirect_to new_user_session_path, notice: "Account successfully removed."
+ end
end
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 5d7ecfeacf4..38e7c6f4a48 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -31,6 +31,8 @@ class SessionsController < Devise::SessionsController
resource.update_attributes(reset_password_token: nil,
reset_password_sent_at: nil)
end
+ # hide the signed-in notification
+ flash[:notice] = nil
log_audit_event(current_user, with: authentication_method)
end
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index c9bee01b9ad..b4c14d05eaf 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -77,6 +77,10 @@ class IssuableFinder
counts
end
+ def find_by!(*params)
+ execute.find_by!(*params)
+ end
+
def group
return @group if defined?(@group)
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index a653a6d59c6..2484339e3a4 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -14,7 +14,7 @@ class NotesFinder
when "issue"
IssuesFinder.new(current_user, project_id: project.id).find(target_id).notes.inc_author
when "merge_request"
- project.merge_requests.find(target_id).mr_and_commit_notes.inc_author
+ MergeRequestsFinder.new(current_user, project_id: project.id).find(target_id).mr_and_commit_notes.inc_author
when "snippet", "project_snippet"
project.snippets.find(target_id).notes
else
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index ed402b698fb..66a720a9426 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -130,7 +130,7 @@ module CommitsHelper
def revert_commit_link(commit, continue_to_path, btn_class: nil, has_tooltip: true)
return unless current_user
- tooltip = "Revert this #{commit.change_type_title} in a new merge request" if has_tooltip
+ tooltip = "Revert this #{commit.change_type_title(current_user)} in a new merge request" if has_tooltip
if can_collaborate_with_project?
btn_class = "btn btn-warning btn-#{btn_class}" unless btn_class.nil?
@@ -154,7 +154,7 @@ module CommitsHelper
def cherry_pick_commit_link(commit, continue_to_path, btn_class: nil, has_tooltip: true)
return unless current_user
- tooltip = "Cherry-pick this #{commit.change_type_title} in a new merge request"
+ tooltip = "Cherry-pick this #{commit.change_type_title(current_user)} in a new merge request"
if can_collaborate_with_project?
btn_class = "btn btn-default btn-#{btn_class}" unless btn_class.nil?
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index f489f9aa0d6..c35d6611ab0 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -55,7 +55,9 @@ module DiffHelper
if line.blank?
"&nbsp;".html_safe
else
- line.sub(/^[\-+ ]/, '').html_safe
+ # We can't use `sub` because the HTML-safeness of `line` will not survive.
+ line[0] = '' if line.start_with?('+', '-', ' ')
+ line
end
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index f1a0b929d82..362046c0270 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -45,6 +45,12 @@ module EventsHelper
@project.feature_available?(feature_key, current_user)
end
+ def comments_visible?
+ event_filter_visible(:repository) ||
+ event_filter_visible(:merge_requests) ||
+ event_filter_visible(:issues)
+ end
+
def event_preposition(event)
if event.push? || event.commented? || event.target
"at"
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 19ab059aea6..f6d4ea4659a 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -5,7 +5,7 @@ module GroupsHelper
def group_icon(group)
if group.is_a?(String)
- group = Group.find_by(path: group)
+ group = Group.find_by_full_path(group)
end
group.try(:avatar_url) || image_path('no_group_avatar.png')
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index caf6908505e..fda8228a1e9 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -21,8 +21,6 @@ module Ci
after_create :keep_around_commits, unless: :importing?
- delegate :stages, to: :statuses
-
state_machine :status, initial: :created do
event :enqueue do
transition created: :pending
@@ -98,17 +96,35 @@ module Ci
sha[0...8]
end
- def self.stages
- # We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries
- CommitStatus.where(pipeline: pluck(:id)).stages
- end
-
def self.total_duration
where.not(duration: nil).sum(:duration)
end
- def stages_with_latest_statuses
- statuses.latest.includes(project: :namespace).order(:stage_idx).group_by(&:stage)
+ def stages_count
+ statuses.select(:stage).distinct.count
+ end
+
+ def stages_name
+ statuses.order(:stage_idx).distinct.
+ pluck(:stage, :stage_idx).map(&:first)
+ end
+
+ def stages
+ status_sql = statuses.latest.where('stage=sg.stage').status_sql
+
+ stages_query = statuses.group('stage').select(:stage)
+ .order('max(stage_idx)')
+
+ stages_with_statuses = CommitStatus.from(stages_query, :sg).
+ pluck('sg.stage', status_sql)
+
+ stages_with_statuses.map do |stage|
+ Ci::Stage.new(self, name: stage.first, status: stage.last)
+ end
+ end
+
+ def artifacts
+ builds.latest.with_artifacts_not_expired
end
def project_id
diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb
new file mode 100644
index 00000000000..d2a37c0a827
--- /dev/null
+++ b/app/models/ci/stage.rb
@@ -0,0 +1,37 @@
+module Ci
+ # Currently this is artificial object, constructed dynamically
+ # We should migrate this object to actual database record in the future
+ class Stage
+ include StaticModel
+
+ attr_reader :pipeline, :name
+
+ delegate :project, to: :pipeline
+
+ def initialize(pipeline, name:, status: nil)
+ @pipeline = pipeline
+ @name = name
+ @status = status
+ end
+
+ def to_param
+ name
+ end
+
+ def status
+ @status ||= statuses.latest.status
+ end
+
+ def detailed_status
+ Gitlab::Ci::Status::Stage::Factory.new(self).fabricate!
+ end
+
+ def statuses
+ @statuses ||= pipeline.statuses.where(stage: name)
+ end
+
+ def builds
+ @builds ||= pipeline.builds.where(stage: name)
+ end
+ end
+end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 248140f421b..1831cc7e175 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -245,44 +245,47 @@ class Commit
project.repository.next_branch("cherry-pick-#{short_id}", mild: true)
end
- def revert_description
- if merged_merge_request
- "This reverts merge request #{merged_merge_request.to_reference}"
+ def revert_description(user)
+ if merged_merge_request?(user)
+ "This reverts merge request #{merged_merge_request(user).to_reference}"
else
"This reverts commit #{sha}"
end
end
- def revert_message
- %Q{Revert "#{title.strip}"\n\n#{revert_description}}
+ def revert_message(user)
+ %Q{Revert "#{title.strip}"\n\n#{revert_description(user)}}
end
- def reverts_commit?(commit)
- description? && description.include?(commit.revert_description)
+ def reverts_commit?(commit, user)
+ description? && description.include?(commit.revert_description(user))
end
def merge_commit?
parents.size > 1
end
- def merged_merge_request
- return @merged_merge_request if defined?(@merged_merge_request)
-
- @merged_merge_request = project.merge_requests.find_by(merge_commit_sha: id) if merge_commit?
+ def merged_merge_request(current_user)
+ # Memoize with per-user access check
+ @merged_merge_request_hash ||= Hash.new do |hash, user|
+ hash[user] = merged_merge_request_no_cache(user)
+ end
+
+ @merged_merge_request_hash[current_user]
end
- def has_been_reverted?(current_user = nil, noteable = self)
+ def has_been_reverted?(current_user, noteable = self)
ext = all_references(current_user)
noteable.notes_with_associations.system.each do |note|
note.all_references(current_user, extractor: ext)
end
- ext.commits.any? { |commit_ref| commit_ref.reverts_commit?(self) }
+ ext.commits.any? { |commit_ref| commit_ref.reverts_commit?(self, current_user) }
end
- def change_type_title
- merged_merge_request ? 'merge request' : 'commit'
+ def change_type_title(user)
+ merged_merge_request?(user) ? 'merge request' : 'commit'
end
# Get the URI type of the given path
@@ -350,4 +353,12 @@ class Commit
changes
end
+
+ def merged_merge_request?(user)
+ !!merged_merge_request(user)
+ end
+
+ def merged_merge_request_no_cache(user)
+ MergeRequestsFinder.new(user, project_id: project.id).find_by(merge_commit_sha: id) if merge_commit?
+ end
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index c345bf293c9..cf90475f4d4 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -31,18 +31,13 @@ class CommitStatus < ActiveRecord::Base
end
scope :exclude_ignored, -> do
- quoted_when = connection.quote_column_name('when')
# We want to ignore failed_but_allowed jobs
where("allow_failure = ? OR status IN (?)",
- false, all_state_names - [:failed, :canceled]).
- # We want to ignore skipped manual jobs
- where("#{quoted_when} <> ? OR status <> ?", 'manual', 'skipped').
- # We want to ignore skipped on_failure
- where("#{quoted_when} <> ? OR status <> ?", 'on_failure', 'skipped')
+ false, all_state_names - [:failed, :canceled])
end
- scope :latest_ci_stages, -> { latest.ordered.includes(project: :namespace) }
- scope :retried_ci_stages, -> { retried.ordered.includes(project: :namespace) }
+ scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) }
+ scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) }
state_machine :status do
event :enqueue do
@@ -117,20 +112,6 @@ class CommitStatus < ActiveRecord::Base
name.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip
end
- def self.stages
- # We group by stage name, but order stages by theirs' index
- unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage')
- end
-
- def self.stages_status
- # We execute subquery for each stage to calculate a stage status
- statuses = unscoped.from(all, :sg).group('stage').pluck('sg.stage', all.where('stage=sg.stage').status_sql)
- statuses.inject({}) do |h, k|
- h[k.first] = k.last
- h
- end
- end
-
def failed_but_allowed?
allow_failure? && (failed? || canceled?)
end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 2f5aa91a964..90432fc4050 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -4,7 +4,7 @@ module HasStatus
AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped]
STARTED_STATUSES = %w[running success failed skipped]
ACTIVE_STATUSES = %w[pending running]
- COMPLETED_STATUSES = %w[success failed canceled]
+ COMPLETED_STATUSES = %w[success failed canceled skipped]
ORDERED_STATUSES = %w[failed pending running canceled success skipped]
class_methods do
@@ -23,9 +23,10 @@ module HasStatus
canceled = scope.canceled.select('count(*)').to_sql
"(CASE
+ WHEN (#{builds})=(#{skipped}) THEN 'skipped'
WHEN (#{builds})=(#{success}) THEN 'success'
WHEN (#{builds})=(#{created}) THEN 'created'
- WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'skipped'
+ WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success'
WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled'
WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending'
WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running'
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index e65fc9eaa09..875e9834487 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -1,17 +1,17 @@
module Milestoneish
- def closed_items_count(user = nil)
+ def closed_items_count(user)
issues_visible_to_user(user).closed.size + merge_requests.closed_and_merged.size
end
- def total_items_count(user = nil)
+ def total_items_count(user)
issues_visible_to_user(user).size + merge_requests.size
end
- def complete?(user = nil)
+ def complete?(user)
total_items_count(user) > 0 && total_items_count(user) == closed_items_count(user)
end
- def percent_complete(user = nil)
+ def percent_complete(user)
((closed_items_count(user) * 100) / total_items_count(user)).abs
rescue ZeroDivisionError
0
@@ -29,7 +29,7 @@ module Milestoneish
(Date.today - start_date).to_i
end
- def issues_visible_to_user(user = nil)
+ def issues_visible_to_user(user)
issues.visible_to_user(user)
end
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
new file mode 100644
index 00000000000..d36bb9da296
--- /dev/null
+++ b/app/models/concerns/routable.rb
@@ -0,0 +1,70 @@
+# Store object full path in separate table for easy lookup and uniq validation
+# Object must have path db field and respond to full_path and full_path_changed? methods.
+module Routable
+ extend ActiveSupport::Concern
+
+ included do
+ has_one :route, as: :source, autosave: true, dependent: :destroy
+
+ validates_associated :route
+
+ before_validation :update_route_path, if: :full_path_changed?
+ end
+
+ class_methods do
+ # Finds a single object by full path match in routes table.
+ #
+ # Usage:
+ #
+ # Klass.find_by_full_path('gitlab-org/gitlab-ce')
+ #
+ # Returns a single object, or nil.
+ def find_by_full_path(path)
+ # On MySQL we want to ensure the ORDER BY uses a case-sensitive match so
+ # any literal matches come first, for this we have to use "BINARY".
+ # Without this there's still no guarantee in what order MySQL will return
+ # rows.
+ binary = Gitlab::Database.mysql? ? 'BINARY' : ''
+
+ order_sql = "(CASE WHEN #{binary} routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)"
+
+ where_paths_in([path]).reorder(order_sql).take
+ end
+
+ # Builds a relation to find multiple objects by their full paths.
+ #
+ # Usage:
+ #
+ # Klass.where_paths_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee})
+ #
+ # Returns an ActiveRecord::Relation.
+ def where_paths_in(paths)
+ wheres = []
+ cast_lower = Gitlab::Database.postgresql?
+
+ paths.each do |path|
+ path = connection.quote(path)
+ where = "(routes.path = #{path})"
+
+ if cast_lower
+ where = "(#{where} OR (LOWER(routes.path) = LOWER(#{path})))"
+ end
+
+ wheres << where
+ end
+
+ if wheres.empty?
+ none
+ else
+ joins(:route).where(wheres.join(' OR '))
+ end
+ end
+ end
+
+ private
+
+ def update_route_path
+ route || build_route(source: self)
+ route.path = full_path
+ end
+end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 33b578e12c1..ea3cf1cdaac 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -101,7 +101,9 @@ class MergeRequest < ActiveRecord::Base
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 :by_source_or_target_branch, ->(branch_name) do
+ where("source_branch = :branch OR target_branch = :branch", branch: branch_name)
+ end
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
scope :of_projects, ->(ids) { where(target_project_id: ids) }
@@ -805,7 +807,7 @@ class MergeRequest < ActiveRecord::Base
@merge_commit ||= project.commit(merge_commit_sha) if merge_commit_sha
end
- def can_be_reverted?(current_user = nil)
+ def can_be_reverted?(current_user)
merge_commit && !merge_commit.has_been_reverted?(current_user, self)
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 7a545f752b6..37374044551 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -4,12 +4,16 @@ class Namespace < ActiveRecord::Base
include CacheMarkdownField
include Sortable
include Gitlab::ShellAdapter
+ include Routable
cache_markdown_field :description, pipeline: :description
has_many :projects, dependent: :destroy
belongs_to :owner, class_name: "User"
+ belongs_to :parent, class_name: "Namespace"
+ has_many :children, class_name: "Namespace", foreign_key: :parent_id
+
validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
validates :name,
presence: true,
@@ -86,7 +90,7 @@ class Namespace < ActiveRecord::Base
end
def to_param
- path
+ full_path
end
def human_name
@@ -150,6 +154,14 @@ class Namespace < ActiveRecord::Base
Gitlab.config.lfs.enabled
end
+ def full_path
+ if parent
+ parent.full_path + '/' + path
+ else
+ path
+ end
+ end
+
private
def repository_storage_paths
@@ -185,4 +197,8 @@ class Namespace < ActiveRecord::Base
where(projects: { namespace_id: id }).
find_each(&:refresh_members_authorized_projects)
end
+
+ def full_path_changed?
+ path_changed? || parent_id_changed?
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 590885c0177..77d740081c6 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -14,6 +14,7 @@ class Project < ActiveRecord::Base
include TokenAuthenticatable
include ProjectFeaturesCompatibility
include SelectForProjectAuthorization
+ include Routable
extend Gitlab::ConfigHelper
@@ -324,87 +325,6 @@ class Project < ActiveRecord::Base
non_archived.where(table[:name].matches(pattern))
end
- # Finds a single project for the given path.
- #
- # path - The full project path (including namespace path).
- #
- # Returns a Project, or nil if no project could be found.
- def find_with_namespace(path)
- namespace_path, project_path = path.split('/', 2)
-
- return unless namespace_path && project_path
-
- namespace_path = connection.quote(namespace_path)
- project_path = connection.quote(project_path)
-
- # On MySQL we want to ensure the ORDER BY uses a case-sensitive match so
- # any literal matches come first, for this we have to use "BINARY".
- # Without this there's still no guarantee in what order MySQL will return
- # rows.
- binary = Gitlab::Database.mysql? ? 'BINARY' : ''
-
- order_sql = "(CASE WHEN #{binary} namespaces.path = #{namespace_path} " \
- "AND #{binary} projects.path = #{project_path} THEN 0 ELSE 1 END)"
-
- where_paths_in([path]).reorder(order_sql).take
- end
-
- # Builds a relation to find multiple projects by their full paths.
- #
- # Each path must be in the following format:
- #
- # namespace_path/project_path
- #
- # For example:
- #
- # gitlab-org/gitlab-ce
- #
- # Usage:
- #
- # Project.where_paths_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee})
- #
- # This would return the projects with the full paths matching the values
- # given.
- #
- # paths - An Array of full paths (namespace path + project path) for which
- # to find the projects.
- #
- # Returns an ActiveRecord::Relation.
- def where_paths_in(paths)
- wheres = []
- cast_lower = Gitlab::Database.postgresql?
-
- paths.each do |path|
- namespace_path, project_path = path.split('/', 2)
-
- next unless namespace_path && project_path
-
- namespace_path = connection.quote(namespace_path)
- project_path = connection.quote(project_path)
-
- where = "(namespaces.path = #{namespace_path}
- AND projects.path = #{project_path})"
-
- if cast_lower
- where = "(
- #{where}
- OR (
- LOWER(namespaces.path) = LOWER(#{namespace_path})
- AND LOWER(projects.path) = LOWER(#{project_path})
- )
- )"
- end
-
- wheres << where
- end
-
- if wheres.empty?
- none
- else
- joins(:namespace).where(wheres.join(' OR '))
- end
- end
-
def visibility_levels
Gitlab::VisibilityLevel.options
end
@@ -440,6 +360,10 @@ class Project < ActiveRecord::Base
def group_ids
joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)
end
+
+ # Add alias for Routable method for compatibility with old code.
+ # In future all calls `find_with_namespace` should be replaced with `find_by_full_path`
+ alias_method :find_with_namespace, :find_by_full_path
end
def lfs_enabled?
@@ -879,13 +803,14 @@ class Project < ActiveRecord::Base
end
alias_method :human_name, :name_with_namespace
- def path_with_namespace
- if namespace
- namespace.path + '/' + path
+ def full_path
+ if namespace && path
+ namespace.full_path + '/' + path
else
path
end
end
+ alias_method :path_with_namespace, :full_path
def execute_hooks(data, hooks_scope = :push_hooks)
hooks.send(hooks_scope).each do |hook|
@@ -1373,4 +1298,8 @@ class Project < ActiveRecord::Base
def validate_board_limit(board)
raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS
end
+
+ def full_path_changed?
+ path_changed? || namespace_id_changed?
+ end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 4034a49ae63..44f66b89600 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -940,7 +940,7 @@ class Repository
committer = user_to_committer(user)
Rugged::Commit.create(rugged,
- message: commit.revert_message,
+ message: commit.revert_message(user),
author: committer,
committer: committer,
tree: revert_tree_id,
diff --git a/app/models/route.rb b/app/models/route.rb
new file mode 100644
index 00000000000..d40214b9da6
--- /dev/null
+++ b/app/models/route.rb
@@ -0,0 +1,22 @@
+class Route < ActiveRecord::Base
+ belongs_to :source, polymorphic: true
+
+ validates :source, presence: true
+
+ validates :path,
+ length: { within: 1..255 },
+ presence: true,
+ uniqueness: { case_sensitive: false }
+
+ after_update :rename_children, if: :path_changed?
+
+ def rename_children
+ # We update each row separately because MySQL does not have regexp_replace.
+ # rubocop:disable Rails/FindEach
+ Route.where('path LIKE ?', "#{path_was}%").each do |route|
+ # Note that update column skips validation and callbacks.
+ # We need this to avoid recursive call of rename_children method
+ route.update_column(:path, route.path.sub(path_was, path))
+ end
+ end
+end
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index 2e028c44d8b..79eb97b7b55 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -44,11 +44,11 @@ module Ci
def valid_statuses_for_when(value)
case value
when 'on_success'
- %w[success]
+ %w[success skipped]
when 'on_failure'
%w[failed]
when 'always'
- %w[success failed]
+ %w[success failed skipped]
else
[]
end
diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb
index d49fcd42a08..99d459908e7 100644
--- a/app/services/commits/change_service.rb
+++ b/app/services/commits/change_service.rb
@@ -41,7 +41,7 @@ module Commits
success
else
- error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title} automatically.
+ error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title(current_user)} automatically.
It may have already been #{action.to_s.dasherize}, or a more recent commit may have updated some of its content."
raise ChangeError, error_msg
end
diff --git a/app/services/destroy_group_service.rb b/app/services/destroy_group_service.rb
index a880952e274..2316c57bf1e 100644
--- a/app/services/destroy_group_service.rb
+++ b/app/services/destroy_group_service.rb
@@ -20,6 +20,10 @@ class DestroyGroupService
::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
end
+ group.children.each do |group|
+ DestroyGroupService.new(group, current_user).async_execute
+ end
+
group.really_destroy!
end
end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index e4056306bc4..0a9563ed7e7 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -55,8 +55,9 @@ module MergeRequests
# Refresh merge request diff if we push to source or target branch of merge request
# Note: we should update merge requests from forks too
def reload_merge_requests
- merge_requests = @project.merge_requests.opened.by_branch(@branch_name).to_a
- merge_requests += fork_merge_requests.by_branch(@branch_name).to_a
+ merge_requests = @project.merge_requests.opened.
+ by_source_or_target_branch(@branch_name).to_a
+ merge_requests += fork_merge_requests
merge_requests = filter_merge_requests(merge_requests)
merge_requests.each do |merge_request|
@@ -157,13 +158,14 @@ module MergeRequests
def merge_requests_for_source_branch
@source_merge_requests ||= begin
merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a
- merge_requests += fork_merge_requests.where(source_branch: @branch_name).to_a
+ merge_requests += fork_merge_requests
filter_merge_requests(merge_requests)
end
end
def fork_merge_requests
- @fork_merge_requests ||= @project.fork_merge_requests.opened
+ @fork_merge_requests ||= @project.fork_merge_requests.opened.
+ where(source_branch: @branch_name).to_a
end
def branch_added?
diff --git a/app/uploaders/uploader_helper.rb b/app/uploaders/uploader_helper.rb
index b10ad71d052..fbaea2744a3 100644
--- a/app/uploaders/uploader_helper.rb
+++ b/app/uploaders/uploader_helper.rb
@@ -1,6 +1,6 @@
# Extra methods for uploader
module UploaderHelper
- IMAGE_EXT = %w[png jpg jpeg gif bmp tiff]
+ IMAGE_EXT = %w[png jpg jpeg gif bmp tiff svg]
# We recommend using the .mp4 format over .mov. Videos in .mov format can
# still be used but you really need to make sure they are served with the
# proper MIME type video/mp4 and not video/quicktime or your videos won't play
diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml
index fa8e7979461..af87129e49e 100644
--- a/app/views/devise/sessions/new.html.haml
+++ b/app/views/devise/sessions/new.html.haml
@@ -1,4 +1,5 @@
- page_title "Sign in"
+
%div
- if form_based_providers.any?
= render 'devise/shared/tabs_ldap'
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 701bcd3ab71..7bd11f5727a 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -77,7 +77,7 @@
= link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
%span
Merge Requests
- %span.badge.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count)
+ %span.badge.count.merge_counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count)
- if project_nav_tab? :wiki
= nav_link(controller: :wikis) do
diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml
index 697c8d19257..56c1949ab2b 100644
--- a/app/views/notify/pipeline_success_email.html.haml
+++ b/app/views/notify/pipeline_success_email.html.haml
@@ -133,7 +133,7 @@
%tr.success-message
%td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;"}
- build_count = @pipeline.statuses.latest.size
- - stage_count = @pipeline.stages.size
+ - stage_count = @pipeline.stages_count
Pipeline
%a{href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;"}
= "\##{@pipeline.id}"
diff --git a/app/views/notify/pipeline_success_email.text.erb b/app/views/notify/pipeline_success_email.text.erb
index ae22d474f2c..40e5e306426 100644
--- a/app/views/notify/pipeline_success_email.text.erb
+++ b/app/views/notify/pipeline_success_email.text.erb
@@ -16,7 +16,7 @@ Commit Author: <%= commit.author_name %>
<% end -%>
<% build_count = @pipeline.statuses.latest.size -%>
-<% stage_count = @pipeline.stages.size -%>
+<% stage_count = @pipeline.stages_count -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>.
You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>.
diff --git a/app/views/projects/_merge_request_merge_settings.html.haml b/app/views/projects/_merge_request_merge_settings.html.haml
new file mode 100644
index 00000000000..afe2fd7fd7b
--- /dev/null
+++ b/app/views/projects/_merge_request_merge_settings.html.haml
@@ -0,0 +1,15 @@
+- form = local_assigns.fetch(:form)
+
+.form-group
+ .checkbox.builds-feature
+ = form.label :only_allow_merge_if_build_succeeds do
+ = form.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('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
+ .checkbox
+ = form.label :only_allow_merge_if_all_discussions_are_resolved do
+ = form.check_box :only_allow_merge_if_all_discussions_are_resolved
+ %strong Only allow merge requests to be merged if all discussions are resolved
diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml
index 6e143c4b570..818010bc7d3 100644
--- a/app/views/projects/_merge_request_settings.html.haml
+++ b/app/views/projects/_merge_request_settings.html.haml
@@ -1,18 +1,8 @@
-.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('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
- .checkbox
- = f.label :only_allow_merge_if_all_discussions_are_resolved do
- = f.check_box :only_allow_merge_if_all_discussions_are_resolved
- %strong Only allow merge requests to be merged if all discussions are resolved
+- form = local_assigns.fetch(:form)
+
+%fieldset.features.merge-requests-feature.append-bottom-default
+ %hr
+ %h5.prepend-top-0
+ Merge Requests
+
+ = render 'projects/merge_request_merge_settings', form: form
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index d5004f6a066..ce8b66b1945 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -111,7 +111,7 @@
%span.label.label-primary
= tag
- - if @build.pipeline.stages.many?
+ - if @build.pipeline.stages_count > 1
.dropdown.build-dropdown
.title Stage
%button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'}
@@ -120,7 +120,7 @@
%ul.dropdown-menu
- @build.pipeline.stages.each do |stage|
%li
- %a.stage-item= stage
+ %a.stage-item= stage.name
.builds-container
- HasStatus::ORDERED_STATUSES.each do |build_status|
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index e75547c815f..18b3b04154f 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -104,9 +104,9 @@
= link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
= icon('remove', class: 'cred')
- elsif allow_retry
- - if build.retryable?
- = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
- = icon('repeat')
- - elsif build.playable? && !admin
+ - if build.playable? && !admin
= 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
= custom_icon('icon_play')
+ - elsif build.retryable?
+ = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
+ = icon('repeat')
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index 0f08f4e8592..b58dceb58c9 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -43,15 +43,13 @@
- else
Cant find HEAD commit for this branch
- - stages_status = pipeline.statuses.latest.stages_status
%td.stage-cell
- - stages.each do |stage|
- - status = stages_status[stage]
- - tooltip = "#{stage.titleize}: #{status || 'not found'}"
- - if status
+ - pipeline.stages.each do |stage|
+ - if stage.status
+ - tooltip = "#{stage.name.titleize}: #{stage.status || 'not found'}"
.stage-container
- = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do
- = ci_icon_for_status(status)
+ = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage.name), class: "has-tooltip ci-status-icon-#{stage.status}", title: tooltip do
+ = ci_icon_for_status(stage.status)
%td
- if pipeline.duration
diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml
index e4cd55b9f7a..f6e3d5e76f5 100644
--- a/app/views/projects/commit/_change.html.haml
+++ b/app/views/projects/commit/_change.html.haml
@@ -11,7 +11,7 @@
.modal-content
.modal-header
%a.close{href: "#", "data-dismiss" => "modal"} ×
- %h3.page-title== #{label} this #{commit.change_type_title}
+ %h3.page-title== #{label} this #{commit.change_type_title(current_user)}
.modal-body
= form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: 'form-horizontal js-#{type}-form js-requires-input' do
.form-group.branch
diff --git a/app/views/projects/commit/_ci_stage.html.haml b/app/views/projects/commit/_ci_stage.html.haml
deleted file mode 100644
index 3a3d750439f..00000000000
--- a/app/views/projects/commit/_ci_stage.html.haml
+++ /dev/null
@@ -1,15 +0,0 @@
-%tr
- %th{colspan: 10}
- %strong
- %a{name: stage}
- - status = statuses.latest.status
- %span{class: "ci-status-link ci-status-icon-#{status}"}
- = ci_icon_for_status(status)
- - if stage
- &nbsp;
- = stage.titleize
- = render statuses.latest_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, allow_retry: true
- = render statuses.retried_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, retried: true
-%tr
- %td{colspan: 10}
- &nbsp;
diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml
index 1174158eb65..c7b5c1124b3 100644
--- a/app/views/projects/commit/_pipeline.html.haml
+++ b/app/views/projects/commit/_pipeline.html.haml
@@ -24,20 +24,8 @@
in
= time_interval_in_words pipeline.duration
- .row-content-block.build-content.middle-block.pipeline-graph.hidden
- .pipeline-visualization
- %ul.stage-column-list
- - stages = pipeline.stages_with_latest_statuses
- - stages.each do |stage, statuses|
- %li.stage-column
- .stage-name
- %a{name: stage}
- - if stage
- = stage.titleize
- .builds-container
- %ul
- = render "projects/commit/pipeline_stage", statuses: statuses
-
+ .row-content-block.build-content.middle-block.hidden
+ = render "projects/pipelines/graph", pipeline: pipeline
- if pipeline.yaml_errors.present?
.bs-callout.bs-callout-danger
@@ -62,5 +50,4 @@
- if pipeline.project.build_coverage_enabled?
%th Coverage
%th
- - pipeline.statuses.relevant.stages.each do |stage|
- = render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.relevant.where(stage: stage)
+ = render partial: "projects/stage/stage", collection: pipeline.stages, as: :stage
diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml
deleted file mode 100644
index f9a9c8707f5..00000000000
--- a/app/views/projects/commit/_pipeline_stage.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-- status_groups = statuses.sort_by(&:name).group_by(&:group_name)
-- status_groups.each do |group_name, grouped_statuses|
- - if grouped_statuses.one?
- - status = grouped_statuses.first
- - is_playable = status.playable? && can?(current_user, :update_build, @project)
- %li.build{ class: ("playable" if is_playable) }
- .curve
- .build-content
- = render "projects/#{status.to_partial_path}_pipeline", subject: status
- - else
- %li.build
- .curve
- .dropdown.inline.build-content
- = render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses
diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml
index 2dc91a9b762..7f42fde0fea 100644
--- a/app/views/projects/commit/_pipelines_list.haml
+++ b/app/views/projects/commit/_pipelines_list.haml
@@ -12,4 +12,4 @@
%th Stages
%th
%th
- = render pipelines, commit_sha: true, stage: true, allow_retry: true, stages: pipelines.stages, show_commit: false
+ = render pipelines, commit_sha: true, stage: true, allow_retry: true, show_commit: false
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 3a5af2723c6..01cd8fa0938 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -112,7 +112,8 @@
%span.descr Enable Container Registry for this project
= link_to icon('question-circle'), help_page_path('user/project/container_registry'), target: '_blank'
- = render 'merge_request_settings', f: f
+ = render 'merge_request_settings', form: f
+
%hr
%fieldset.features.append-bottom-default
%h5.prepend-top-0
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index a9235d6af35..a65a630f2d0 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -17,4 +17,6 @@
"project-stopped-environments-path" => project_environments_path(@project, scope: :stopped),
"new-environment-path" => new_namespace_project_environment_path(@project.namespace, @project),
"help-page-path" => help_page_path("ci/environments"),
- "css-class" => container_class}}
+ "css-class" => container_class,
+ "commit-icon-svg" => custom_icon("icon_commit"),
+ "play-icon-svg" => custom_icon("icon_play")}}
diff --git a/app/views/projects/forks/error.html.haml b/app/views/projects/forks/error.html.haml
index 3d0ab5b85d6..98d81308407 100644
--- a/app/views/projects/forks/error.html.haml
+++ b/app/views/projects/forks/error.html.haml
@@ -13,7 +13,11 @@
- if @forked_project && @forked_project.errors.any?
%p
&ndash;
- = @forked_project.errors.full_messages.first
+ - error = @forked_project.errors.full_messages.first
+ - if error.include?("already been taken")
+ Name has already been taken
+ - else
+ = error
%p
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork", class: "btn" do
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 0e2975bd551..896f10104fa 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -31,7 +31,7 @@
%span.label-branch= source_branch_with_namespace(@merge_request)
%span into
%span.label-branch
- = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch)
+ = link_to_if @merge_request.target_branch_exists?, @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch)
- if @merge_request.open? && @merge_request.diverged_from_target_branch?
%span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
diff --git a/app/views/projects/pipelines/_graph.html.haml b/app/views/projects/pipelines/_graph.html.haml
new file mode 100644
index 00000000000..0202833c0bf
--- /dev/null
+++ b/app/views/projects/pipelines/_graph.html.haml
@@ -0,0 +1,4 @@
+- pipeline = local_assigns.fetch(:pipeline)
+.pipeline-visualization.pipeline-graph
+ %ul.stage-column-list
+ = render partial: "projects/stage/graph", collection: pipeline.stages, as: :stage
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index 3464e155a1b..739e5930822 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -12,19 +12,8 @@
.tab-content
#js-tab-pipeline.tab-pane
- .build-content.middle-block.pipeline-graph
- .pipeline-visualization
- %ul.stage-column-list
- - stages = pipeline.stages_with_latest_statuses
- - stages.each do |stage, statuses|
- %li.stage-column
- .stage-name
- %a{name: stage}
- - if stage
- = stage.titleize
- .builds-container
- %ul
- = render "projects/commit/pipeline_stage", statuses: statuses
+ .build-content.middle-block
+ = render "projects/pipelines/graph", pipeline: pipeline
#js-tab-builds.tab-pane
- if pipeline.yaml_errors.present?
@@ -50,5 +39,4 @@
- if pipeline.project.build_coverage_enabled?
%th Coverage
%th
- - pipeline.statuses.relevant.stages.each do |stage|
- = render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.relevant.where(stage: stage)
+ = render partial: "projects/stage/stage", collection: pipeline.stages, as: :stage
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index 4bc49072f35..e1e787dbde4 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -37,7 +37,6 @@
%span CI Lint
%div.content-list.pipelines
- - stages = @pipelines.stages
- if @pipelines.blank?
%div
.nothing-here-block No pipelines to show
@@ -51,6 +50,6 @@
%th Stages
%th
%th.hidden-xs
- = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages
+ = render @pipelines, commit_sha: true, stage: true, allow_retry: true
= paginate @pipelines, theme: 'gitlab'
diff --git a/app/views/projects/stage/_graph.html.haml b/app/views/projects/stage/_graph.html.haml
new file mode 100644
index 00000000000..1d8fa10db0c
--- /dev/null
+++ b/app/views/projects/stage/_graph.html.haml
@@ -0,0 +1,22 @@
+- stage = local_assigns.fetch(:stage)
+- statuses = stage.statuses.latest
+- status_groups = statuses.sort_by(&:name).group_by(&:group_name)
+%li.stage-column
+ .stage-name
+ %a{ name: stage.name }
+ = stage.name.titleize
+ .builds-container
+ %ul
+ - status_groups.each do |group_name, grouped_statuses|
+ - if grouped_statuses.one?
+ - status = grouped_statuses.first
+ - is_playable = status.playable? && can?(current_user, :update_build, @project)
+ %li.build{ class: ("playable" if is_playable) }
+ .curve
+ .build-content
+ = render "projects/#{status.to_partial_path}_pipeline", subject: status
+ - else
+ %li.build
+ .curve
+ .dropdown.inline.build-content
+ = render "projects/stage/in_stage_group", name: group_name, subject: grouped_statuses
diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/stage/_in_stage_group.html.haml
index 2b26ad9d6fa..2b26ad9d6fa 100644
--- a/app/views/projects/commit/_pipeline_status_group.html.haml
+++ b/app/views/projects/stage/_in_stage_group.html.haml
diff --git a/app/views/projects/stage/_stage.html.haml b/app/views/projects/stage/_stage.html.haml
new file mode 100644
index 00000000000..1684e02fbad
--- /dev/null
+++ b/app/views/projects/stage/_stage.html.haml
@@ -0,0 +1,13 @@
+%tr
+ %th{colspan: 10}
+ %strong
+ %a{ name: stage.name }
+ %span{class: "ci-status-link ci-status-icon-#{stage.status}"}
+ = ci_icon_for_status(stage.status)
+ &nbsp;
+ = stage.name.titleize
+= render stage.statuses.latest_ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, allow_retry: true
+= render stage.statuses.retried_ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, retried: true
+%tr
+ %td{colspan: 10}
+ &nbsp;
diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml
index c367ae336db..e50ab5fea09 100644
--- a/app/views/shared/_event_filter.html.haml
+++ b/app/views/shared/_event_filter.html.haml
@@ -5,5 +5,7 @@
- if event_filter_visible(:merge_requests)
= event_filter_link EventFilter.merged, 'Merge events'
- if event_filter_visible(:issues)
+ = event_filter_link EventFilter.issue, 'Issue events'
+ - if comments_visible?
= event_filter_link EventFilter.comments, 'Comments'
= event_filter_link EventFilter.team, 'Team'
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index 432047a1c4e..e67f7d5a352 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -20,8 +20,8 @@
%strong Blocked
- if source.instance_of?(Group) && !@group
- = link_to source, class: "member-group-link prepend-left-5" do
- = "· #{source.name}"
+ &middot;
+ = link_to source.name, source, class: "member-group-link"
.hidden-xs.cgray
- if member.request?
@@ -45,7 +45,7 @@
= time_ago_with_tooltip(member.created_at)
- if show_roles
.controls.member-controls
- - if show_controls
+ - if show_controls && (member.respond_to?(:group) && @group) || (member.respond_to?(:project) && @project)
- if user != current_user
= form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f|
= f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can_admin_member
diff --git a/app/views/shared/milestones/_summary.html.haml b/app/views/shared/milestones/_summary.html.haml
index 0a237136959..d27fba805a3 100644
--- a/app/views/shared/milestones/_summary.html.haml
+++ b/app/views/shared/milestones/_summary.html.haml
@@ -25,8 +25,10 @@
%span.milestone-stat
%strong== #{milestone.percent_complete(current_user)}%
complete
- %span.milestone-stat
- %span.remaining-days= milestone_remaining_days(milestone)
+ - remaining_days = milestone_remaining_days(milestone)
+ - if remaining_days.present?
+ %span.milestone-stat
+ %span.remaining-days= remaining_days
.milestone-progress-buttons
%span.tab-issues-buttons