Commit b337a086 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'master' into backstage/gb/migrate-pipeline-stages-index

* master: (106 commits)
parents 3c5cac76 3fcb9c11
...@@ -72,3 +72,4 @@ eslint-report.html ...@@ -72,3 +72,4 @@ eslint-report.html
/locale/**/*.time_stamp /locale/**/*.time_stamp
/.rspec /.rspec
/plugins/* /plugins/*
/.gitlab_pages_secret
...@@ -110,7 +110,7 @@ stages: ...@@ -110,7 +110,7 @@ stages:
# Jobs that only need to pull cache # Jobs that only need to pull cache
.dedicated-no-docs-pull-cache-job: &dedicated-no-docs-pull-cache-job .dedicated-no-docs-pull-cache-job: &dedicated-no-docs-pull-cache-job
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs-and-qa <<: *except-docs
<<: *pull-cache <<: *pull-cache
dependencies: dependencies:
- setup-test-env - setup-test-env
...@@ -122,6 +122,10 @@ stages: ...@@ -122,6 +122,10 @@ stages:
variables: variables:
SETUP_DB: "false" SETUP_DB: "false"
.dedicated-no-docs-and-no-qa-pull-cache-job: &dedicated-no-docs-and-no-qa-pull-cache-job
<<: *dedicated-no-docs-pull-cache-job
<<: *except-docs-and-qa
.rake-exec: &rake-exec .rake-exec: &rake-exec
<<: *dedicated-no-docs-no-db-pull-cache-job <<: *dedicated-no-docs-no-db-pull-cache-job
script: script:
...@@ -222,7 +226,7 @@ stages: ...@@ -222,7 +226,7 @@ stages:
- master@gitlab/gitlab-ee - master@gitlab/gitlab-ee
.gitlab-setup: &gitlab-setup .gitlab-setup: &gitlab-setup
<<: *dedicated-no-docs-pull-cache-job <<: *dedicated-no-docs-and-no-qa-pull-cache-job
<<: *use-pg <<: *use-pg
variables: variables:
CREATE_DB_USER: "true" CREATE_DB_USER: "true"
...@@ -262,12 +266,12 @@ stages: ...@@ -262,12 +266,12 @@ stages:
# DB migration, rollback, and seed jobs # DB migration, rollback, and seed jobs
.db-migrate-reset: &db-migrate-reset .db-migrate-reset: &db-migrate-reset
<<: *dedicated-no-docs-pull-cache-job <<: *dedicated-no-docs-and-no-qa-pull-cache-job
script: script:
- bundle exec rake db:migrate:reset - bundle exec rake db:migrate:reset
.migration-paths: &migration-paths .migration-paths: &migration-paths
<<: *dedicated-no-docs-pull-cache-job <<: *dedicated-no-docs-and-no-qa-pull-cache-job
variables: variables:
CREATE_DB_USER: "true" CREATE_DB_USER: "true"
script: script:
...@@ -289,7 +293,6 @@ stages: ...@@ -289,7 +293,6 @@ stages:
# Trigger a package build in omnibus-gitlab repository # Trigger a package build in omnibus-gitlab repository
# #
package-and-qa: package-and-qa:
<<: *dedicated-runner
image: ruby:2.4-alpine image: ruby:2.4-alpine
before_script: [] before_script: []
stage: build stage: build
...@@ -648,7 +651,7 @@ migration:path-mysql: ...@@ -648,7 +651,7 @@ migration:path-mysql:
<<: *use-mysql <<: *use-mysql
.db-rollback: &db-rollback .db-rollback: &db-rollback
<<: *dedicated-no-docs-pull-cache-job <<: *dedicated-no-docs-and-no-qa-pull-cache-job
script: script:
- bundle exec rake db:migrate VERSION=20170523121229 - bundle exec rake db:migrate VERSION=20170523121229
- bundle exec rake db:migrate - bundle exec rake db:migrate
...@@ -671,7 +674,7 @@ gitlab:setup-mysql: ...@@ -671,7 +674,7 @@ gitlab:setup-mysql:
# Frontend-related jobs # Frontend-related jobs
gitlab:assets:compile: gitlab:assets:compile:
<<: *dedicated-no-docs-no-db-pull-cache-job <<: *dedicated-no-docs-and-no-qa-pull-cache-job
dependencies: [] dependencies: []
variables: variables:
NODE_ENV: "production" NODE_ENV: "production"
...@@ -692,7 +695,7 @@ gitlab:assets:compile: ...@@ -692,7 +695,7 @@ gitlab:assets:compile:
- webpack-report/ - webpack-report/
karma: karma:
<<: *dedicated-no-docs-pull-cache-job <<: *dedicated-no-docs-and-no-qa-pull-cache-job
<<: *use-pg <<: *use-pg
dependencies: dependencies:
- compile-assets - compile-assets
...@@ -816,7 +819,7 @@ coverage: ...@@ -816,7 +819,7 @@ coverage:
- coverage/assets/ - coverage/assets/
lint:javascript:report: lint:javascript:report:
<<: *dedicated-no-docs-no-db-pull-cache-job <<: *dedicated-no-docs-and-no-qa-pull-cache-job
stage: post-test stage: post-test
dependencies: dependencies:
- compile-assets - compile-assets
......
...@@ -2,6 +2,14 @@ ...@@ -2,6 +2,14 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 10.7.2 (2018-04-25)
### Security (2 changes)
- Serve archive requests with the correct file in all cases.
- Sanitizes user name to avoid XSS attacks.
## 10.7.1 (2018-04-23) ## 10.7.1 (2018-04-23)
### Fixed (11 changes) ### Fixed (11 changes)
...@@ -237,6 +245,13 @@ entry. ...@@ -237,6 +245,13 @@ entry.
- Upgrade Gitaly to upgrade its charlock_holmes. - Upgrade Gitaly to upgrade its charlock_holmes.
## 10.6.5 (2018-04-24)
### Security (1 change)
- Sanitizes user name to avoid XSS attacks.
## 10.6.4 (2018-04-09) ## 10.6.4 (2018-04-09)
### Fixed (8 changes, 1 of them is from the community) ### Fixed (8 changes, 1 of them is from the community)
...@@ -478,6 +493,13 @@ entry. ...@@ -478,6 +493,13 @@ entry.
- Use host URL to build JIRA remote link icon. - Use host URL to build JIRA remote link icon.
## 10.5.8 (2018-04-24)
### Security (1 change)
- Sanitizes user name to avoid XSS attacks.
## 10.5.7 (2018-04-03) ## 10.5.7 (2018-04-03)
### Security (2 changes) ### Security (2 changes)
......
...@@ -25,12 +25,12 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._ ...@@ -25,12 +25,12 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._
- [Workflow labels](#workflow-labels) - [Workflow labels](#workflow-labels)
- [Type labels (~"feature proposal", ~bug, ~customer, etc.)](#type-labels-feature-proposal-bug-customer-etc) - [Type labels (~"feature proposal", ~bug, ~customer, etc.)](#type-labels-feature-proposal-bug-customer-etc)
- [Subject labels (~wiki, ~"container registry", ~ldap, ~api, etc.)](#subject-labels-wiki-container-registry-ldap-api-etc) - [Subject labels (~wiki, ~"container registry", ~ldap, ~api, etc.)](#subject-labels-wiki-container-registry-ldap-api-etc)
- [Team labels (~"CI/CD", ~Discussion, ~Edge, ~Platform, etc.)](#team-labels-cicd-discussion-edge-platform-etc) - [Team labels (~"CI/CD", ~Discussion, ~Quality, ~Platform, etc.)](#team-labels-cicd-discussion-quality-platform-etc)
- [Milestone labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#milestone-labels-deliverable-stretch-next-patch-release) - [Milestone labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#milestone-labels-deliverable-stretch-next-patch-release)
- [Priority labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#bug-priority-labels-p1-p2-p3-etc) - [Priority labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#bug-priority-labels-p1-p2-p3-etc)
- [Severity labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#bug-severity-labels-s1-s2-s3-etc) - [Severity labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#bug-severity-labels-s1-s2-s3-etc)
- [Label for community contributors (~"Accepting Merge Requests")](#label-for-community-contributors-accepting-merge-requests) - [Label for community contributors (~"Accepting Merge Requests")](#label-for-community-contributors-accepting-merge-requests)
- [Implement design & UI elements](#implement-design-ui-elements) - [Implement design & UI elements](#implement-design--ui-elements)
- [Issue tracker](#issue-tracker) - [Issue tracker](#issue-tracker)
- [Issue triaging](#issue-triaging) - [Issue triaging](#issue-triaging)
- [Feature proposals](#feature-proposals) - [Feature proposals](#feature-proposals)
...@@ -114,7 +114,7 @@ is a great place to start. Issues with a lower weight (1 or 2) are deemed ...@@ -114,7 +114,7 @@ is a great place to start. Issues with a lower weight (1 or 2) are deemed
suitable for beginners. These issues will be of reasonable size and challenge, suitable for beginners. These issues will be of reasonable size and challenge,
for anyone to start contributing to GitLab. If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to for anyone to start contributing to GitLab. If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to
learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel
please consider we favor please consider we favor
[asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution! [asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution!
## Workflow labels ## Workflow labels
...@@ -127,7 +127,7 @@ Most issues will have labels for at least one of the following: ...@@ -127,7 +127,7 @@ Most issues will have labels for at least one of the following:
- Type: ~"feature proposal", ~bug, ~customer, etc. - Type: ~"feature proposal", ~bug, ~customer, etc.
- Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc. - Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
- Team: ~"CI/CD", ~Discussion, ~Edge, ~Platform, etc. - Team: ~"CI/CD", ~Discussion, ~Quality, ~Platform, etc.
- Milestone: ~Deliverable, ~Stretch, ~"Next Patch Release" - Milestone: ~Deliverable, ~Stretch, ~"Next Patch Release"
- Priority: ~P1, ~P2, ~P3, ~P4 - Priority: ~P1, ~P2, ~P3, ~P4
- Severity: ~S1, ~S2, ~S3, ~S4 - Severity: ~S1, ~S2, ~S3, ~S4
...@@ -171,13 +171,13 @@ Examples of subject labels are ~wiki, ~"container registry", ~ldap, ~api, ...@@ -171,13 +171,13 @@ Examples of subject labels are ~wiki, ~"container registry", ~ldap, ~api,
Subject labels are always all-lowercase. Subject labels are always all-lowercase.
### Team labels (~"CI/CD", ~Discussion, ~Edge, ~Platform, etc.) ### Team labels (~"CI/CD", ~Discussion, ~Quality, ~Platform, etc.)
Team labels specify what team is responsible for this issue. Team labels specify what team is responsible for this issue.
Assigning a team label makes sure issues get the attention of the appropriate Assigning a team label makes sure issues get the attention of the appropriate
people. people.
The current team labels are ~Build, ~"CI/CD", ~Discussion, ~Documentation, ~Edge, The current team labels are ~Build, ~"CI/CD", ~Discussion, ~Documentation, ~Quality,
~Geo, ~Gitaly, ~Monitoring, ~Platform, ~Release, ~"Security Products" and ~"UX". ~Geo, ~Gitaly, ~Monitoring, ~Platform, ~Release, ~"Security Products" and ~"UX".
The descriptions on the [labels page][labels-page] explain what falls under the The descriptions on the [labels page][labels-page] explain what falls under the
...@@ -199,12 +199,12 @@ release. There are three levels of Milestone labels: ...@@ -199,12 +199,12 @@ release. There are three levels of Milestone labels:
- ~Stretch: Issues that are a stretch goal for delivering in the current - ~Stretch: Issues that are a stretch goal for delivering in the current
milestone. If these issues are not done in the current release, they will milestone. If these issues are not done in the current release, they will
strongly be considered for the next release. strongly be considered for the next release.
- ~"Next Patch Release": Issues to put in the next patch release. Work on these - ~"Next Patch Release": Issues to put in the next patch release. Work on these
first, and add the "Pick Into X" label to the merge request, along with the first, and add the "Pick Into X" label to the merge request, along with the
appropriate milestone. appropriate milestone.
Each issue scheduled for the current milestone should be labeled ~Deliverable Each issue scheduled for the current milestone should be labeled ~Deliverable
or ~"Stretch". Any open issue for a previous milestone should be labeled or ~"Stretch". Any open issue for a previous milestone should be labeled
~"Next Patch Release", or otherwise rescheduled to a different milestone. ~"Next Patch Release", or otherwise rescheduled to a different milestone.
### Bug Priority labels (~P1, ~P2, ~P3 & etc.) ### Bug Priority labels (~P1, ~P2, ~P3 & etc.)
...@@ -221,16 +221,16 @@ This label documents the planned timeline & urgency which is used to measure aga ...@@ -221,16 +221,16 @@ This label documents the planned timeline & urgency which is used to measure aga
#### Specific Priority guidance #### Specific Priority guidance
| Label | Availability / Performance | | Label | Availability / Performance |
|-------|--------------------------------------------------------------| |-------|--------------------------------------------------------------|
| ~P1 | | | ~P1 | |
| ~P2 | The issue is (almost) guaranteed to occur in the near future | | ~P2 | The issue is (almost) guaranteed to occur in the near future |
| ~P3 | The issue is likely to occur in the near future | | ~P3 | The issue is likely to occur in the near future |
| ~P4 | The issue _may_ occur but it's not likely | | ~P4 | The issue _may_ occur but it's not likely |
### Bug Severity labels (~S1, ~S2, ~S3 & etc.) ### Bug Severity labels (~S1, ~S2, ~S3 & etc.)
Severity labels help us clearly communicate the impact of a ~bug on users. Severity labels help us clearly communicate the impact of a ~bug on users.
| Label | Meaning | Impact of the defect | Example | | Label | Meaning | Impact of the defect | Example |
|-------|-------------------|-------------------------------------------------------|---------| |-------|-------------------|-------------------------------------------------------|---------|
...@@ -241,9 +241,9 @@ Severity labels help us clearly communicate the impact of a ~bug on users. ...@@ -241,9 +241,9 @@ Severity labels help us clearly communicate the impact of a ~bug on users.
#### Specific Severity guidance #### Specific Severity guidance
| Label | Security Impact | | Label | Security Impact |
|-------|-------------------------------------------------------------------| |-------|-------------------------------------------------------------------|
| ~S1 | >50% customers impacted (possible company extinction level event) | | ~S1 | >50% customers impacted (possible company extinction level event) |
| ~S2 | Multiple customers impacted (but not apocalyptic) | | ~S2 | Multiple customers impacted (but not apocalyptic) |
| ~S3 | A single customer impacted | | ~S3 | A single customer impacted |
| ~S4 | No customer impact, or expected impact within 30 days | | ~S4 | No customer impact, or expected impact within 30 days |
...@@ -727,4 +727,3 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor ...@@ -727,4 +727,3 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[^1]: Please note that specs other than JavaScript specs are considered backend [^1]: Please note that specs other than JavaScript specs are considered backend
code. code.
\ No newline at end of file
...@@ -51,7 +51,6 @@ gem 'omniauth-shibboleth', '~> 1.2.0' ...@@ -51,7 +51,6 @@ gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.4' gem 'omniauth-twitter', '~> 1.4'
gem 'omniauth_crowd', '~> 2.2.0' gem 'omniauth_crowd', '~> 2.2.0'
gem 'omniauth-authentiq', '~> 0.3.1' gem 'omniauth-authentiq', '~> 0.3.1'
gem 'omniauth-jwt', '~> 0.0.2'
gem 'rack-oauth2', '~> 1.2.1' gem 'rack-oauth2', '~> 1.2.1'
gem 'jwt', '~> 1.5.6' gem 'jwt', '~> 1.5.6'
...@@ -283,7 +282,6 @@ gem 'batch-loader', '~> 1.2.1' ...@@ -283,7 +282,6 @@ gem 'batch-loader', '~> 1.2.1'
gem 'peek', '~> 1.0.1' gem 'peek', '~> 1.0.1'
gem 'peek-gc', '~> 0.0.2' gem 'peek-gc', '~> 0.0.2'
gem 'peek-mysql2', '~> 1.1.0', group: :mysql gem 'peek-mysql2', '~> 1.1.0', group: :mysql
gem 'peek-performance_bar', '~> 1.3.0'
gem 'peek-pg', '~> 1.3.0', group: :postgres gem 'peek-pg', '~> 1.3.0', group: :postgres
gem 'peek-rblineprof', '~> 0.2.0' gem 'peek-rblineprof', '~> 0.2.0'
gem 'peek-redis', '~> 1.2.0' gem 'peek-redis', '~> 1.2.0'
...@@ -416,7 +414,7 @@ end ...@@ -416,7 +414,7 @@ end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.97.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.97.0', require: 'gitaly'
gem 'grpc', '~> 1.10.0' gem 'grpc', '~> 1.11.0'
# Locked until https://github.com/google/protobuf/issues/4210 is closed # Locked until https://github.com/google/protobuf/issues/4210 is closed
gem 'google-protobuf', '= 3.5.1' gem 'google-protobuf', '= 3.5.1'
......
...@@ -374,7 +374,7 @@ GEM ...@@ -374,7 +374,7 @@ GEM
rake rake
grape_logging (1.7.0) grape_logging (1.7.0)
grape grape
grpc (1.10.0) grpc (1.11.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
googleapis-common-protos-types (~> 1.0.0) googleapis-common-protos-types (~> 1.0.0)
googleauth (>= 0.5.1, < 0.7) googleauth (>= 0.5.1, < 0.7)
...@@ -555,9 +555,6 @@ GEM ...@@ -555,9 +555,6 @@ GEM
jwt (>= 1.5) jwt (>= 1.5)
omniauth (>= 1.1.1) omniauth (>= 1.1.1)
omniauth-oauth2 (>= 1.5) omniauth-oauth2 (>= 1.5)
omniauth-jwt (0.0.2)
jwt
omniauth (~> 1.1)
omniauth-kerberos (0.3.0) omniauth-kerberos (0.3.0)
omniauth-multipassword omniauth-multipassword
timfel-krb5-auth (~> 0.8) timfel-krb5-auth (~> 0.8)
...@@ -603,8 +600,6 @@ GEM ...@@ -603,8 +600,6 @@ GEM
atomic (>= 1.0.0) atomic (>= 1.0.0)
mysql2 mysql2
peek peek
peek-performance_bar (1.3.1)
peek (>= 0.1.0)
peek-pg (1.3.0) peek-pg (1.3.0)
concurrent-ruby concurrent-ruby
concurrent-ruby-ext concurrent-ruby-ext
...@@ -1078,7 +1073,7 @@ DEPENDENCIES ...@@ -1078,7 +1073,7 @@ DEPENDENCIES
grape-entity (~> 0.6.0) grape-entity (~> 0.6.0)
grape-route-helpers (~> 2.1.0) grape-route-helpers (~> 2.1.0)
grape_logging (~> 1.7) grape_logging (~> 1.7)
grpc (~> 1.10.0) grpc (~> 1.11.0)
haml_lint (~> 0.26.0) haml_lint (~> 0.26.0)
hamlit (~> 2.6.1) hamlit (~> 2.6.1)
hashie-forbidden_attributes hashie-forbidden_attributes
...@@ -1119,7 +1114,6 @@ DEPENDENCIES ...@@ -1119,7 +1114,6 @@ DEPENDENCIES
omniauth-github (~> 1.1.1) omniauth-github (~> 1.1.1)
omniauth-gitlab (~> 1.0.2) omniauth-gitlab (~> 1.0.2)
omniauth-google-oauth2 (~> 0.5.3) omniauth-google-oauth2 (~> 0.5.3)
omniauth-jwt (~> 0.0.2)
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2) omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.10) omniauth-saml (~> 1.10)
...@@ -1130,7 +1124,6 @@ DEPENDENCIES ...@@ -1130,7 +1124,6 @@ DEPENDENCIES
peek (~> 1.0.1) peek (~> 1.0.1)
peek-gc (~> 0.0.2) peek-gc (~> 0.0.2)
peek-mysql2 (~> 1.1.0) peek-mysql2 (~> 1.1.0)
peek-performance_bar (~> 1.3.0)
peek-pg (~> 1.3.0) peek-pg (~> 1.3.0)
peek-rblineprof (~> 0.2.0) peek-rblineprof (~> 0.2.0)
peek-redis (~> 1.2.0) peek-redis (~> 1.2.0)
......
...@@ -26,11 +26,18 @@ export default { ...@@ -26,11 +26,18 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
forceModifiedIcon: {
type: Boolean,
required: false,
default: false,
},
}, },
computed: { computed: {
changedIcon() { changedIcon() {
const suffix = this.file.staged && !this.showStagedIcon ? '-solid' : ''; const suffix = this.file.staged && !this.showStagedIcon ? '-solid' : '';
return this.file.tempFile ? `file-addition${suffix}` : `file-modified${suffix}`; return this.file.tempFile && !this.forceModifiedIcon
? `file-addition${suffix}`
: `file-modified${suffix}`;
}, },
stagedIcon() { stagedIcon() {
return `${this.changedIcon}-solid`; return `${this.changedIcon}-solid`;
......
<script> <script>
import { mapActions } from 'vuex'; import { mapActions } from 'vuex';
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import newModal from './modal.vue'; import newModal from './modal.vue';
import upload from './upload.vue'; import upload from './upload.vue';
export default { export default {
components: { components: {
icon, icon,
newModal, newModal,
upload, upload,
},
props: {
branch: {
type: String,
required: true,
}, },
props: { path: {
branch: { type: String,
type: String, required: true,
required: true,
},
path: {
type: String,
required: true,
},
}, },
data() { },
return { data() {
openModal: false, return {
modalType: '', openModal: false,
dropdownOpen: false, modalType: '',
}; dropdownOpen: false,
};
},
watch: {
dropdownOpen() {
this.$nextTick(() => {
this.$refs.dropdownMenu.scrollIntoView();
});
}, },
methods: { },
...mapActions([ methods: {
'createTempEntry', ...mapActions(['createTempEntry']),
]), createNewItem(type) {
createNewItem(type) { this.modalType = type;
this.modalType = type; this.openModal = true;
this.openModal = true; this.dropdownOpen = false;
this.dropdownOpen = false;
},
hideModal() {
this.openModal = false;
},
openDropdown() {
this.dropdownOpen = !this.dropdownOpen;
},
}, },
}; hideModal() {
this.openModal = false;
},
openDropdown() {
this.dropdownOpen = !this.dropdownOpen;
},
},
};
</script> </script>
<template> <template>
...@@ -71,7 +76,10 @@ ...@@ -71,7 +76,10 @@
css-classes="pull-left" css-classes="pull-left"
/> />
</button> </button>
<ul class="dropdown-menu dropdown-menu-right"> <ul
class="dropdown-menu dropdown-menu-right"
ref="dropdownMenu"
>
<li> <li>
<a <a
href="#" href="#"
......
...@@ -40,13 +40,6 @@ export default { ...@@ -40,13 +40,6 @@ export default {
return __('Create file'); return __('Create file');
}, },
formLabelName() {
if (this.type === 'tree') {
return __('Directory name');
}
return __('File name');
},
}, },
mounted() { mounted() {
this.$refs.fieldName.focus(); this.$refs.fieldName.focus();
...@@ -82,8 +75,8 @@ export default { ...@@ -82,8 +75,8 @@ export default {
@submit.prevent="createEntryInStore" @submit.prevent="createEntryInStore"
> >
<fieldset class="form-group append-bottom-0"> <fieldset class="form-group append-bottom-0">
<label class="label-light col-sm-3"> <label class="label-light col-sm-3 ide-new-modal-label">
{{ formLabelName }} {{ __('Name') }}
</label> </label>
<div class="col-sm-9"> <div class="col-sm-9">
<input <input
......
...@@ -97,7 +97,7 @@ export default { ...@@ -97,7 +97,7 @@ export default {
:file="file" :file="file"
/> />
</span> </span>
<span class="pull-right"> <span class="pull-right ide-file-icon-holder">
<mr-file-icon <mr-file-icon
v-if="file.mrChange" v-if="file.mrChange"
/> />
...@@ -106,7 +106,8 @@ export default { ...@@ -106,7 +106,8 @@ export default {
:file="file" :file="file"
:show-tooltip="true" :show-tooltip="true"
:show-staged-icon="true" :show-staged-icon="true"
class="prepend-top-5 pull-right" :force-modified-icon="true"
class="pull-right"
/> />
</span> </span>
<new-dropdown <new-dropdown
......
...@@ -84,6 +84,7 @@ export default { ...@@ -84,6 +84,7 @@ export default {
<changed-file-icon <changed-file-icon
v-else v-else
:file="tab" :file="tab"
:force-modified-icon="true"
/> />
</button> </button>
......
...@@ -33,10 +33,7 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => { ...@@ -33,10 +33,7 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
} }
}; };
export const toggleRightPanelCollapsed = ( export const toggleRightPanelCollapsed = ({ dispatch, state }, e = undefined) => {
{ dispatch, state },
e = undefined,
) => {
if (e) { if (e) {
$(e.currentTarget) $(e.currentTarget)
.tooltip('hide') .tooltip('hide')
...@@ -77,7 +74,7 @@ export const createTempEntry = ( ...@@ -77,7 +74,7 @@ export const createTempEntry = (
} }
worker.addEventListener('message', ({ data }) => { worker.addEventListener('message', ({ data }) => {
const { file } = data; const { file, parentPath } = data;
worker.terminate(); worker.terminate();
...@@ -93,6 +90,10 @@ export const createTempEntry = ( ...@@ -93,6 +90,10 @@ export const createTempEntry = (
dispatch('setFileActive', file.path); dispatch('setFileActive', file.path);
} }
if (parentPath && !state.entries[parentPath].opened) {
commit(types.TOGGLE_TREE_OPEN, parentPath);
}
resolve(file); resolve(file);
}); });
...@@ -137,6 +138,14 @@ export const updateDelayViewerUpdated = ({ commit }, delay) => { ...@@ -137,6 +138,14 @@ export const updateDelayViewerUpdated = ({ commit }, delay) => {
commit(types.UPDATE_DELAY_VIEWER_CHANGE, delay); commit(types.UPDATE_DELAY_VIEWER_CHANGE, delay);
}; };
export const updateTempFlagForEntry = ({ commit, dispatch, state }, { file, tempFile }) => {
commit(types.UPDATE_TEMP_FLAG, { path: file.path, tempFile });
if (file.parentPath) {
dispatch('updateTempFlagForEntry', { file: state.entries[file.parentPath], tempFile });
}
};
export const toggleFileFinder = ({ commit }, fileFindVisible) => export const toggleFileFinder = ({ commit }, fileFindVisible) =>
commit(types.TOGGLE_FILE_FINDER, fileFindVisible); commit(types.TOGGLE_FILE_FINDER, fileFindVisible);
......
...@@ -63,7 +63,7 @@ export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive ...@@ -63,7 +63,7 @@ export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive
const file = state.entries[path]; const file = state.entries[path];
commit(types.TOGGLE_LOADING, { entry: file }); commit(types.TOGGLE_LOADING, { entry: file });
return service return service
.getFileData(file.url) .getFileData(`${gon.relative_url_root ? gon.relative_url_root : ''}${file.url}`)
.then(res => { .then(res => {
const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']); const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']);
setPageTitle(pageTitle); setPageTitle(pageTitle);
......
...@@ -110,6 +110,17 @@ export const updateFilesAfterCommit = ( ...@@ -110,6 +110,17 @@ export const updateFilesAfterCommit = (
{ root: true }, { root: true },
); );
commit(
rootTypes.TOGGLE_FILE_CHANGED,
{
file,
changed: false,
},
{ root: true },
);
dispatch('updateTempFlagForEntry', { file, tempFile: false }, { root: true });
eventHub.$emit(`editor.update.model.content.${file.key}`, { eventHub.$emit(`editor.update.model.content.${file.key}`, {
content: file.content, content: file.content,
changed: !!changedFile, changed: !!changedFile,
......
...@@ -59,4 +59,5 @@ export const UPDATE_FILE_AFTER_COMMIT = 'UPDATE_FILE_AFTER_COMMIT'; ...@@ -59,4 +59,5 @@ export const UPDATE_FILE_AFTER_COMMIT = 'UPDATE_FILE_AFTER_COMMIT';
export const ADD_PENDING_TAB = 'ADD_PENDING_TAB'; export const ADD_PENDING_TAB = 'ADD_PENDING_TAB';
export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB'; export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB';
export const UPDATE_TEMP_FLAG = 'UPDATE_TEMP_FLAG';
export const TOGGLE_FILE_FINDER = 'TOGGLE_FILE_FINDER'; export const TOGGLE_FILE_FINDER = 'TOGGLE_FILE_FINDER';
...@@ -4,6 +4,7 @@ import mergeRequestMutation from './mutations/merge_request'; ...@@ -4,6 +4,7 @@ import mergeRequestMutation from './mutations/merge_request';
import fileMutations from './mutations/file'; import fileMutations from './mutations/file';
import treeMutations from './mutations/tree'; import treeMutations from './mutations/tree';
import branchMutations from './mutations/branch'; import branchMutations from './mutations/branch';
import { sortTree } from './utils';
export default { export default {
[types.SET_INITIAL_DATA](state, data) { [types.SET_INITIAL_DATA](state, data) {
...@@ -73,7 +74,7 @@ export default { ...@@ -73,7 +74,7 @@ export default {
f => foundEntry.tree.find(e => e.path === f.path) === undefined, f => foundEntry.tree.find(e => e.path === f.path) === undefined,
); );
Object.assign(foundEntry, { Object.assign(foundEntry, {
tree: foundEntry.tree.concat(tree), tree: sortTree(foundEntry.tree.concat(tree)),
}); });
} }
...@@ -86,10 +87,16 @@ export default { ...@@ -86,10 +87,16 @@ export default {
if (!foundEntry) { if (!foundEntry) {
Object.assign(state.trees[`${projectId}/${branchId}`], { Object.assign(state.trees[`${projectId}/${branchId}`], {
tree: state.trees[`${projectId}/${branchId}`].tree.concat(data.treeList), tree: sortTree(state.trees[`${projectId}/${branchId}`].tree.concat(data.treeList)),
}); });
} }
}, },
[types.UPDATE_TEMP_FLAG](state, { path, tempFile }) {
Object.assign(state.entries[path], {
tempFile,
changed: tempFile,
});
},
[types.UPDATE_VIEWER](state, viewer) { [types.UPDATE_VIEWER](state, viewer) {
Object.assign(state, { Object.assign(state, {
viewer, viewer,
......
...@@ -33,6 +33,7 @@ export const dataStructure = () => ({ ...@@ -33,6 +33,7 @@ export const dataStructure = () => ({
raw: '', raw: '',
content: '', content: '',
parentTreeUrl: '', parentTreeUrl: '',
parentPath: '',
renderError: false, renderError: false,
base64: false, base64: false,
editorRow: 1, editorRow: 1,
...@@ -65,6 +66,7 @@ export const decorateData = entity => { ...@@ -65,6 +66,7 @@ export const decorateData = entity => {
previewMode, previewMode,
file_lock, file_lock,
html, html,
parentPath = '',
} = entity; } = entity;
return { return {
...@@ -81,6 +83,7 @@ export const decorateData = entity => { ...@@ -81,6 +83,7 @@ export const decorateData = entity => {
opened, opened,
active, active,
parentTreeUrl, parentTreeUrl,
parentPath,
changed, changed,
renderError, renderError,
content, content,
...@@ -121,8 +124,8 @@ const sortTreesByTypeAndName = (a, b) => { ...@@ -121,8 +124,8 @@ const sortTreesByTypeAndName = (a, b) => {
} else if (a.type === 'blob' && b.type === 'tree') { } else if (a.type === 'blob' && b.type === 'tree') {
return 1; return 1;
} }
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1; if (a.name < b.name) return -1;
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1; if (a.name > b.name) return 1;
return 0; return 0;
}; };
......
...@@ -6,6 +6,7 @@ self.addEventListener('message', e => { ...@@ -6,6 +6,7 @@ self.addEventListener('message', e => {
const treeList = []; const treeList = [];
let file; let file;
let parentPath;
const entries = data.reduce((acc, path) => { const entries = data.reduce((acc, path) => {
const pathSplit = path.split('/'); const pathSplit = path.split('/');
const blobName = pathSplit.pop().trim(); const blobName = pathSplit.pop().trim();
...@@ -17,6 +18,8 @@ self.addEventListener('message', e => { ...@@ -17,6 +18,8 @@ self.addEventListener('message', e => {
const foundEntry = acc[folderPath]; const foundEntry = acc[folderPath];
if (!foundEntry) { if (!foundEntry) {
parentPath = parentFolder ? parentFolder.path : null;
const tree = decorateData({ const tree = decorateData({
projectId, projectId,
branchId, branchId,
...@@ -29,6 +32,7 @@ self.addEventListener('message', e => { ...@@ -29,6 +32,7 @@ self.addEventListener('message', e => {
tempFile, tempFile,
changed: tempFile, changed: tempFile,
opened: tempFile, opened: tempFile,
parentPath,
}); });
Object.assign(acc, { Object.assign(acc, {
...@@ -52,6 +56,8 @@ self.addEventListener('message', e => { ...@@ -52,6 +56,8 @@ self.addEventListener('message', e => {
if (blobName !== '') { if (blobName !== '') {
const fileFolder = acc[pathSplit.join('/')]; const fileFolder = acc[pathSplit.join('/')];
parentPath = fileFolder ? fileFolder.path : null;
file = decorateData({ file = decorateData({
projectId, projectId,
branchId, branchId,
...@@ -66,6 +72,7 @@ self.addEventListener('message', e => { ...@@ -66,6 +72,7 @@ self.addEventListener('message', e => {
content, content,
base64, base64,
previewMode: viewerInformationForPath(blobName), previewMode: viewerInformationForPath(blobName),
parentPath,
}); });
Object.assign(acc, { Object.assign(acc, {
...@@ -86,5 +93,6 @@ self.addEventListener('message', e => { ...@@ -86,5 +93,6 @@ self.addEventListener('message', e => {
entries, entries,
treeList: sortTree(treeList), treeList: sortTree(treeList),
file, file,
parentPath,
}); });
}); });
...@@ -12,7 +12,8 @@ import ModalStore from './boards/stores/modal_store'; ...@@ -12,7 +12,8 @@ import ModalStore from './boards/stores/modal_store';
export default class MilestoneSelect { export default class MilestoneSelect {
constructor(currentProject, els, options = {}) { constructor(currentProject, els, options = {}) {
if (currentProject !== null) { if (currentProject !== null) {
this.currentProject = typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject; this.currentProject =
typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject;
} }
this.init(els, options); this.init(els, options);
...@@ -26,7 +27,10 @@ export default class MilestoneSelect { ...@@ -26,7 +27,10 @@ export default class MilestoneSelect {
} }
$els.each((i, dropdown) => { $els.each((i, dropdown) => {
let milestoneLinkNoneTemplate, milestoneLinkTemplate, selectedMilestone, selectedMilestoneDefault; let milestoneLinkNoneTemplate,
milestoneLinkTemplate,
selectedMilestone,
selectedMilestoneDefault;
const $dropdown = $(dropdown); const $dropdown = $(dropdown);
const projectId = $dropdown.data('projectId'); const projectId = $dropdown.data('projectId');
const milestonesUrl = $dropdown.data('milestones'); const milestonesUrl = $dropdown.data('milestones');
...@@ -46,45 +50,47 @@ export default class MilestoneSelect { ...@@ -46,45 +50,47 @@ export default class MilestoneSelect {
const $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon'); const $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon');
const $value = $block.find('.value'); const $value = $block.find('.value');
const $loading = $block.find('.block-loading').fadeOut(); const $loading = $block.find('.block-loading').fadeOut();
selectedMilestoneDefault = (showAny ? '' : null); selectedMilestoneDefault = showAny ? '' : null;
selectedMilestoneDefault = (showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault); selectedMilestoneDefault = showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault;
selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault; selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault;
if (issueUpdateURL) { if (issueUpdateURL) {
milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>'); milestoneLinkTemplate = _.template(
'<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>',
);
milestoneLinkNoneTemplate = '<span class="no-value">None</span>'; milestoneLinkNoneTemplate = '<span class="no-value">None</span>';
} }
return $dropdown.glDropdown({ return $dropdown.glDropdown({
showMenuAbove: showMenuAbove, showMenuAbove: showMenuAbove,
data: (term, callback) => axios.get(milestonesUrl) data: (term, callback) =>
.then(({ data }) => { axios.get(milestonesUrl).then(({ data }) => {
const extraOptions = []; const extraOptions = [];
if (showAny) { if (showAny) {
extraOptions.push({ extraOptions.push({
id: 0, id: null,
name: '', name: null,
title: 'Any Milestone' title: 'Any Milestone',
}); });
} }
if (showNo) { if (showNo) {
extraOptions.push({ extraOptions.push({
id: -1, id: -1,
name: 'No Milestone', name: 'No Milestone',
title: 'No Milestone' title: 'No Milestone',
}); });
} }
if (showUpcoming) { if (showUpcoming) {
extraOptions.push({ extraOptions.push({
id: -2, id: -2,
name: '#upcoming', name: '#upcoming',
title: 'Upcoming' title: 'Upcoming',
}); });
} }
if (showStarted) { if (showStarted) {
extraOptions.push({ extraOptions.push({
id: -3, id: -3,
name: '#started', name: '#started',
title: 'Started' title: 'Started',
}); });
} }
if (extraOptions.length) { if (extraOptions.length) {
...@@ -106,7 +112,7 @@ export default class MilestoneSelect { ...@@ -106,7 +112,7 @@ export default class MilestoneSelect {
`, `,
filterable: true, filterable: true,
search: { search: {
fields: ['title'] fields: ['title'],
}, },
selectable: true, selectable: true,
toggleLabel: (selected, el, e) => { toggleLabel: (selected, el, e) => {
...@@ -119,7 +125,7 @@ export default class MilestoneSelect { ...@@ -119,7 +125,7 @@ export default class MilestoneSelect {
defaultLabel: defaultLabel, defaultLabel: defaultLabel,
fieldName: $dropdown.data('fieldName'), fieldName: $dropdown.data('fieldName'),
text: milestone => _.escape(milestone.title), text: milestone => _.escape(milestone.title),
id: (milestone) => { id: milestone => {
if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) { if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) {
return milestone.name; return milestone.name;
} else { } else {
...@@ -131,7 +137,7 @@ export default class MilestoneSelect { ...@@ -131,7 +137,7 @@ export default class MilestoneSelect {
// display:block overrides the hide-collapse rule // display:block overrides the hide-collapse rule
return $value.css('display', ''); return $value.css('display', '');
}, },
opened: (e) => { opened: e => {
const $el = $(e.currentTarget); const $el = $(e.currentTarget);
if ($dropdown.hasClass('js-issue-board-sidebar') || options.handleClick) { if ($dropdown.hasClass('js-issue-board-sidebar') || options.handleClick) {
selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault; selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault;
...@@ -140,7 +146,7 @@ export default class MilestoneSelect { ...@@ -140,7 +146,7 @@ export default class MilestoneSelect {
$(`[data-milestone-id="${_.escape(selectedMilestone)}"] > a`, $el).addClass('is-active'); $(`[data-milestone-id="${_.escape(selectedMilestone)}"] > a`, $el).addClass('is-active');
}, },
vue: $dropdown.hasClass('js-issue-board-sidebar'), vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: (clickEvent) => { clicked: clickEvent => {
const { $el, e } = clickEvent; const { $el, e } = clickEvent;
let selected = clickEvent.selectedObj; let selected = clickEvent.selectedObj;
...@@ -155,11 +161,14 @@ export default class MilestoneSelect { ...@@ -155,11 +161,14 @@ export default class MilestoneSelect {
const page = $('body').attr('data-page'); const page = $('body').attr('data-page');
const isIssueIndex = page === 'projects:issues:index'; const isIssueIndex = page === 'projects:issues:index';
const isMRIndex = (page === page && page === 'projects:merge_requests:index'); const isMRIndex = page === page && page === 'projects:merge_requests:index';
const isSelecting = (selected.name !== selectedMilestone); const isSelecting = selected.name !== selectedMilestone;
selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault; selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault;
if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) { if (
$dropdown.hasClass('js-filter-bulk-update') ||
$dropdown.hasClass('js-issuable-form-dropdown')
) {
e.preventDefault(); e.preventDefault();
return; return;
} }
...@@ -177,10 +186,13 @@ export default class MilestoneSelect { ...@@ -177,10 +186,13 @@ export default class MilestoneSelect {
return $dropdown.closest('form').submit(); return $dropdown.closest('form').submit();
} else if ($dropdown.hasClass('js-issue-board-sidebar')) { } else if ($dropdown.hasClass('js-issue-board-sidebar')) {
if (selected.id !== -1 && isSelecting) { if (selected.id !== -1 && isSelecting) {
gl.issueBoards.boardStoreIssueSet('milestone', new ListMilestone({ gl.issueBoards.boardStoreIssueSet(
id: selected.id, 'milestone',
title: selected.name new ListMilestone({
})); id: selected.id,
title: selected.name,
}),
);
} else { } else {
gl.issueBoards.boardStoreIssueDelete('milestone'); gl.issueBoards.boardStoreIssueDelete('milestone');
} }
...@@ -188,7 +200,8 @@ export default class MilestoneSelect { ...@@ -188,7 +200,8 @@ export default class MilestoneSelect {
$dropdown.trigger('loading.gl.dropdown'); $dropdown.trigger('loading.gl.dropdown');
$loading.removeClass('hidden').fadeIn(); $loading.removeClass('hidden').fadeIn();
gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) gl.issueBoards.BoardsStore.detail.issue
.update($dropdown.attr('data-issue-update'))
.then(() => { .then(() => {
$dropdown.trigger('loaded.gl.dropdown'); $dropdown.trigger('loaded.gl.dropdown');
$loading.fadeOut(); $loading.fadeOut();
...@@ -203,7 +216,8 @@ export default class MilestoneSelect { ...@@ -203,7 +216,8 @@ export default class MilestoneSelect {
data[abilityName].milestone_id = selected != null ? selected : null; data[abilityName].milestone_id = selected != null ? selected : null;
$loading.removeClass('hidden').fadeIn(); $loading.removeClass('hidden').fadeIn();
$dropdown.trigger('loading.gl.dropdown'); $dropdown.trigger('loading.gl.dropdown');
return axios.put(issueUpdateURL, data) return axios
.put(issueUpdateURL, data)
.then(({ data }) => { .then(({ data }) => {
$dropdown.trigger('loaded.gl.dropdown'); $dropdown.trigger('loaded.gl.dropdown');
$loading.fadeOut(); $loading.fadeOut();
...@@ -215,7 +229,10 @@ export default class MilestoneSelect { ...@@ -215,7 +229,10 @@ export default class MilestoneSelect {
data.milestone.name = data.milestone.title; data.milestone.name = data.milestone.title;
$value.html(milestoneLinkTemplate(data.milestone)); $value.html(milestoneLinkTemplate(data.milestone));
return $sidebarCollapsedValue return $sidebarCollapsedValue
.attr('data-original-title', `${data.milestone.name}<br />${data.milestone.remaining}`) .attr(
'data-original-title',
`${data.milestone.name}<br />${data.milestone.remaining}`,
)
.find('span') .find('span')
.text(data.milestone.title); .text(data.milestone.title);
} else { } else {
...@@ -230,7 +247,7 @@ export default class MilestoneSelect { ...@@ -230,7 +247,7 @@ export default class MilestoneSelect {
$loading.fadeOut(); $loading.fadeOut();
}); });
} }
} },
}); });
}); });
} }
......
...@@ -86,7 +86,7 @@ export default { ...@@ -86,7 +86,7 @@ export default {
v-html="resolveSvg" v-html="resolveSvg"
></span> ></span>
</span> </span>
<span class=".line-resolve-text"> <span class="line-resolve-text">
{{ resolvedDiscussionCount }}/{{ discussionCount }} {{ countText }} resolved {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ countText }} resolved
</span> </span>
</div> </div>
......
...@@ -52,16 +52,15 @@ ...@@ -52,16 +52,15 @@
text() { text() {
const keepContributionsText = s__(`AdminArea| const keepContributionsText = s__(`AdminArea|
You are about to permanently delete the user %{username}. You are about to permanently delete the user %{username}.
This will delete all of the issues, merge requests, and groups linked to them. Issues, merge requests, and groups linked to them will be transferred to a system-wide "Ghost-user".
To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead.
Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`); Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`);
const deleteContributionsText = s__(`AdminArea| const deleteContributionsText = s__(`AdminArea|
You are about to permanently delete the user %{username}. You are about to permanently delete the user %{username}.
Issues, merge requests, and groups linked to them will be transferred to a system-wide "Ghost-user". This will delete all of the issues, merge requests, and groups linked to them.
To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead.
Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`); Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`);
return sprintf(this.deleteContributions ? deleteContributionsText : keepContributionsText, return sprintf(this.deleteContributions ? deleteContributionsText : keepContributionsText,
{ {
username: `<strong>${_.escape(this.username)}</strong>`, username: `<strong>${_.escape(this.username)}</strong>`,
......
...@@ -188,11 +188,11 @@ export default class ActivityCalendar { ...@@ -188,11 +188,11 @@ export default class ActivityCalendar {
}, },
{ {
text: 'W', text: 'W',
y: 29 + this.dayYPos(2), y: 29 + this.dayYPos(3),
}, },
{ {
text: 'F', text: 'F',
y: 29 + this.dayYPos(3), y: 29 + this.dayYPos(5),
}, },
]; ];
this.svg this.svg
......
...@@ -5,7 +5,6 @@ import PerformanceBarService from '../services/performance_bar_service'; ...@@ -5,7 +5,6 @@ import PerformanceBarService from '../services/performance_bar_service';
import detailedMetric from './detailed_metric.vue'; import detailedMetric from './detailed_metric.vue';
import requestSelector from './request_selector.vue'; import requestSelector from './request_selector.vue';
import simpleMetric from './simple_metric.vue'; import simpleMetric from './simple_metric.vue';
import upstreamPerformanceBar from './upstream_performance_bar.vue';
import Flash from '../../flash'; import Flash from '../../flash';
...@@ -14,7 +13,6 @@ export default { ...@@ -14,7 +13,6 @@ export default {
detailedMetric, detailedMetric,
requestSelector, requestSelector,
simpleMetric, simpleMetric,
upstreamPerformanceBar,
}, },
props: { props: {
store: { store: {
...@@ -128,9 +126,6 @@ export default { ...@@ -128,9 +126,6 @@ export default {
{{ currentRequest.details.host.hostname }} {{ currentRequest.details.host.hostname }}
</span> </span>
</div> </div>
<upstream-performance-bar
v-if="initialRequest && currentRequest.details"
/>
<detailed-metric <detailed-metric
v-for="metric in $options.detailedMetrics" v-for="metric in $options.detailedMetrics"
:key="metric.metric" :key="metric.metric"
......
<script>
export default {
mounted() {
const upstreamPerformanceBar = document
.getElementById('peek-view-performance-bar')
.cloneNode(true);
upstreamPerformanceBar.classList.remove('hidden');
this.$refs.wrapper.appendChild(upstreamPerformanceBar);
},
};
</script>
<template>
<div
id="peek-view-performance-bar-vue"
class="view"
ref="wrapper"
></div>
</template>
import 'vendor/peek.performance_bar';
import Vue from 'vue'; import Vue from 'vue';
import performanceBarApp from './components/performance_bar_app.vue'; import performanceBarApp from './components/performance_bar_app.vue';
import PerformanceBarStore from './stores/performance_bar_store'; import PerformanceBarStore from './stores/performance_bar_store';
......
import $ from 'jquery'; import $ from 'jquery';
import _ from 'underscore';
function isValidProjectId(id) { function isValidProjectId(id) {
return id > 0; return id > 0;
...@@ -43,7 +44,7 @@ class SidebarMoveIssue { ...@@ -43,7 +44,7 @@ class SidebarMoveIssue {
renderRow: project => ` renderRow: project => `
<li> <li>
<a href="#" class="js-move-issue-dropdown-item"> <a href="#" class="js-move-issue-dropdown-item">
${project.name_with_namespace} ${_.escape(project.name_with_namespace)}
</a> </a>
</li> </li>
`, `,
......
<script> <script>
import { n__ } from '~/locale'; import { n__ } from '~/locale';
import statusIcon from '../mr_widget_status_icon.vue'; import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
export default { export default {
name: 'MRWidgetFailedToMerge', name: 'MRWidgetFailedToMerge',
components: { components: {
statusIcon, statusIcon,
}, },
props: { props: {
mr: { mr: {
type: Object, type: Object,
required: true, required: true,
default: () => ({}), default: () => ({}),
},
}, },
},
data() { data() {
return { return {
timer: 10, timer: 10,
isRefreshing: false, isRefreshing: false,
}; intervalId: null,
}, };
},
computed: { computed: {
timerText() { timerText() {
return n__( return n__(
'Refreshing in a second to show the updated status...', 'Refreshing in a second to show the updated status...',
'Refreshing in %d seconds to show the updated status...', 'Refreshing in %d seconds to show the updated status...',
this.timer, this.timer,
); );
},
}, },
},
mounted() { mounted() {
setInterval(() => { this.intervalId = setInterval(this.updateTimer, 1000);
this.updateTimer(); },
}, 1000);
},
created() { created() {
eventHub.$emit('DisablePolling'); eventHub.$emit('DisablePolling');
}, },
methods: { beforeDestroy() {
refresh() { if (this.intervalId) {
this.isRefreshing = true; clearInterval(this.intervalId);
eventHub.$emit('MRWidgetUpdateRequested'); }
eventHub.$emit('EnablePolling'); },
},
updateTimer() {
this.timer = this.timer - 1;
if (this.timer === 0) { methods: {
this.refresh(); refresh() {
} this.isRefreshing = true;
}, eventHub.$emit('MRWidgetUpdateRequested');
eventHub.$emit('EnablePolling');
}, },
updateTimer() {
this.timer = this.timer - 1;
}; if (this.timer === 0) {
this.refresh();
}
},
},
};
</script> </script>
<template> <template>
<div class="mr-widget-body media"> <div class="mr-widget-body media">
......
<script> <script>
import getIconForFile from './file_icon/file_icon_map'; import getIconForFile from './file_icon/file_icon_map';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import icon from '../../vue_shared/components/icon.vue'; import icon from '../../vue_shared/components/icon.vue';
/* This is a re-usable vue component for rendering a svg sprite /* This is a re-usable vue component for rendering a svg sprite
icon icon
Sample configuration: Sample configuration:
...@@ -15,60 +15,60 @@ ...@@ -15,60 +15,60 @@
/> />
*/ */
export default { export default {
components: { components: {
loadingIcon, loadingIcon,
icon, icon,
},
props: {
fileName: {
type: String,
required: true,
}, },
props: {
fileName: {
type: String,
required: true,
},
folder: { folder: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false, default: false,
}, },
opened: { opened: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false, default: false,
}, },
loading: { loading: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false, default: false,
}, },
size: { size: {
type: Number, type: Number,
required: false, required: false,
default: 16, default: 16,
}, },
cssClasses: { cssClasses: {
type: String, type: String,
required: false, required: false,
default: '', default: '',
}, },
},
computed: {
spriteHref() {
const iconName = getIconForFile(this.fileName) || 'file';
return `${gon.sprite_file_icons}#${iconName}`;
},
folderIconName() {
return this.opened ? 'folder-open' : 'folder';
}, },
computed: { iconSizeClass() {
spriteHref() { return this.size ? `s${this.size}` : '';
const iconName = getIconForFile(this.fileName) || 'file';
return `${gon.sprite_file_icons}#${iconName}`;
},
folderIconName() {
return this.opened ? 'folder-open' : 'folder';
},
iconSizeClass() {
return this.size ? `s${this.size}` : '';
},
}, },
}; },
};
</script> </script>
<template> <template>
<span> <span>
...@@ -82,6 +82,7 @@ ...@@ -82,6 +82,7 @@
v-if="!loading && folder" v-if="!loading && folder"
:name="folderIconName" :name="folderIconName"
:size="size" :size="size"
css-classes="folder-icon"
/> />
<loading-icon <loading-icon
v-if="loading" v-if="loading"
......
class ListLabel { export default class ListLabel {
constructor(obj) { constructor(obj) {
this.id = obj.id; this.id = obj.id;
this.title = obj.title; this.title = obj.title;
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
.fork-svg { .fork-svg {
margin-right: 4px; margin-right: 4px;
vertical-align: bottom;
} }
} }
......
...@@ -70,7 +70,7 @@ ...@@ -70,7 +70,7 @@
} }
.branch-info .commit-icon { .branch-info .commit-icon {
margin-right: 3px; margin-right: 8px;
svg { svg {
top: 3px; top: 3px;
......
...@@ -772,7 +772,3 @@ ul.notes { ...@@ -772,7 +772,3 @@ ul.notes {
height: auto; height: auto;
} }
} }
.line-resolve-text {
vertical-align: middle;
}
...@@ -55,6 +55,7 @@ ...@@ -55,6 +55,7 @@
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
max-width: inherit; max-width: inherit;
line-height: 22px;
svg { svg {
vertical-align: middle; vertical-align: middle;
...@@ -67,6 +68,11 @@ ...@@ -67,6 +68,11 @@
} }
} }
.ide-file-icon-holder {
display: flex;
align-items: center;
}
.ide-file-changed-icon { .ide-file-changed-icon {
margin-left: auto; margin-left: auto;
...@@ -77,7 +83,6 @@ ...@@ -77,7 +83,6 @@
.ide-new-btn { .ide-new-btn {
display: none; display: none;
margin-bottom: -4px;
margin-right: -8px; margin-right: -8px;
} }
...@@ -90,10 +95,8 @@ ...@@ -90,10 +95,8 @@
} }
} }
&.folder { .folder-icon {
svg { fill: $gl-text-color-secondary;
fill: $gl-text-color-secondary;
}
} }
} }
...@@ -111,6 +114,7 @@ ...@@ -111,6 +114,7 @@
.file-col-commit-message { .file-col-commit-message {
display: flex; display: flex;
overflow: visible; overflow: visible;
align-items: center;
padding: 6px 12px; padding: 6px 12px;
} }
...@@ -438,7 +442,7 @@ ...@@ -438,7 +442,7 @@
.projects-sidebar { .projects-sidebar {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; flex: 1;
.context-header { .context-header {
width: auto; width: auto;
...@@ -967,3 +971,7 @@ ...@@ -967,3 +971,7 @@
background: transparent; background: transparent;
resize: none; resize: none;
} }
.ide-new-modal-label {
line-height: 34px;
}
@import 'framework/variables'; @import 'framework/variables';
@import 'peek/views/performance_bar';
@import 'peek/views/rblineprof'; @import 'peek/views/rblineprof';
#js-peek { #js-peek {
......
...@@ -8,8 +8,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -8,8 +8,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
omniauth_flow(Gitlab::Auth::OAuth) omniauth_flow(Gitlab::Auth::OAuth)
end end
Gitlab.config.omniauth.providers.each do |provider| AuthHelper.providers_for_base_controller.each do |provider|
alias_method provider['name'], :handle_omniauth alias_method provider, :handle_omniauth
end end
# Extend the standard implementation to also increment # Extend the standard implementation to also increment
......
...@@ -39,7 +39,7 @@ class GroupsFinder < UnionFinder ...@@ -39,7 +39,7 @@ class GroupsFinder < UnionFinder
def all_groups def all_groups
return [owned_groups] if params[:owned] return [owned_groups] if params[:owned]
return [Group.all] if current_user&.full_private_access? return [Group.all] if current_user&.full_private_access? && all_available?
groups = [] groups = []
groups << Gitlab::GroupHierarchy.new(groups_for_ancestors, groups_for_descendants).all_groups if current_user groups << Gitlab::GroupHierarchy.new(groups_for_ancestors, groups_for_descendants).all_groups if current_user
...@@ -67,6 +67,10 @@ class GroupsFinder < UnionFinder ...@@ -67,6 +67,10 @@ class GroupsFinder < UnionFinder
end end
def include_public_groups? def include_public_groups?
current_user.nil? || params.fetch(:all_available, true) current_user.nil? || all_available?
end
def all_available?
params.fetch(:all_available, true)
end end
end end
...@@ -14,6 +14,7 @@ class PipelinesFinder ...@@ -14,6 +14,7 @@ class PipelinesFinder
items = by_scope(items) items = by_scope(items)
items = by_status(items) items = by_status(items)
items = by_ref(items) items = by_ref(items)
items = by_sha(items)
items = by_name(items) items = by_name(items)
items = by_username(items) items = by_username(items)
items = by_yaml_errors(items) items = by_yaml_errors(items)
...@@ -69,6 +70,14 @@ class PipelinesFinder ...@@ -69,6 +70,14 @@ class PipelinesFinder
end end
end end
def by_sha(items)
if params[:sha].present?
items.where(sha: params[:sha])
else
items
end
end
def by_name(items) def by_name(items)
if params[:name].present? if params[:name].present?
items.joins(:user).where(users: { name: params[:name] }) items.joins(:user).where(users: { name: params[:name] })
......
module AuthHelper module AuthHelper
PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2 authentiq).freeze PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2 authentiq).freeze
FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze LDAP_PROVIDER = /\Aldap/
def ldap_enabled? def ldap_enabled?
Gitlab::Auth::LDAP::Config.enabled? Gitlab::Auth::LDAP::Config.enabled?
...@@ -23,7 +23,7 @@ module AuthHelper ...@@ -23,7 +23,7 @@ module AuthHelper
end end
def form_based_provider?(name) def form_based_provider?(name)
FORM_BASED_PROVIDERS.any? { |pattern| pattern === name.to_s } [LDAP_PROVIDER, 'crowd'].any? { |pattern| pattern === name.to_s }
end end
def form_based_providers def form_based_providers
...@@ -38,6 +38,10 @@ module AuthHelper ...@@ -38,6 +38,10 @@ module AuthHelper
auth_providers.reject { |provider| form_based_provider?(provider) } auth_providers.reject { |provider| form_based_provider?(provider) }
end end
def providers_for_base_controller
auth_providers.reject { |provider| LDAP_PROVIDER === provider }
end
def enabled_button_based_providers def enabled_button_based_providers
disabled_providers = Gitlab::CurrentSettings.disabled_oauth_sign_in_sources || [] disabled_providers = Gitlab::CurrentSettings.disabled_oauth_sign_in_sources || []
......
...@@ -17,7 +17,7 @@ module BlobHelper ...@@ -17,7 +17,7 @@ module BlobHelper
end end
def ide_edit_path(project = @project, ref = @ref, path = @path, options = {}) def ide_edit_path(project = @project, ref = @ref, path = @path, options = {})
"#{ide_path}/project#{edit_blob_path(project, ref, path, options)}" "#{ide_path}/project#{url_for([project, "edit", "blob", id: [ref, path], script_name: "/"])}"
end end
def edit_blob_button(project = @project, ref = @ref, path = @path, options = {}) def edit_blob_button(project = @project, ref = @ref, path = @path, options = {})
......
...@@ -63,7 +63,7 @@ module CommitsHelper ...@@ -63,7 +63,7 @@ module CommitsHelper
# Returns a link formatted as a commit branch link # Returns a link formatted as a commit branch link
def commit_branch_link(url, text) def commit_branch_link(url, text)
link_to(url, class: 'label label-gray ref-name branch-link') do link_to(url, class: 'label label-gray ref-name branch-link') do
sprite_icon('fork', size: 16, css_class: 'fork-svg') + "#{text}" sprite_icon('fork', size: 12, css_class: 'fork-svg') + "#{text}"
end end
end end
......
...@@ -41,7 +41,7 @@ module DropdownsHelper ...@@ -41,7 +41,7 @@ module DropdownsHelper
def dropdown_toggle(toggle_text, data_attr, options = {}) def dropdown_toggle(toggle_text, data_attr, options = {})
default_label = data_attr[:default_label] default_label = data_attr[:default_label]
content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), type: "button", data: data_attr) do content_tag(:button, disabled: options[:disabled], class: "dropdown-menu-toggle #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), type: "button", data: data_attr) do
output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}") output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}")
output << icon('chevron-down') output << icon('chevron-down')
output.html_safe output.html_safe
......
...@@ -400,7 +400,8 @@ module ProjectsHelper ...@@ -400,7 +400,8 @@ module ProjectsHelper
exports_path = File.join(Settings.shared['path'], 'tmp/project_exports') exports_path = File.join(Settings.shared['path'], 'tmp/project_exports')
filtered_message = message.strip.gsub(exports_path, "[REPO EXPORT PATH]") filtered_message = message.strip.gsub(exports_path, "[REPO EXPORT PATH]")
filtered_message.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]") disk_path = Gitlab.config.repositories.storages[project.repository_storage].legacy_disk_path
filtered_message.gsub(disk_path.chomp('/'), "[REPOS PATH]")
end end
def project_child_container_class(view_path) def project_child_container_class(view_path)
......
module SystemNoteHelper module SystemNoteHelper
ICON_NAMES_BY_ACTION = { ICON_NAMES_BY_ACTION = {
'commit' => 'commit', 'commit' => 'commit',
'description' => 'pencil', 'description' => 'pencil-square',
'merge' => 'git-merge', 'merge' => 'git-merge',
'merged' => 'git-merge', 'merged' => 'git-merge',
'opened' => 'issue-open', 'opened' => 'issue-open',
'closed' => 'issue-close', 'closed' => 'issue-close',
'time_tracking' => 'timer', 'time_tracking' => 'timer',
'assignee' => 'user', 'assignee' => 'user',
'title' => 'pencil', 'title' => 'pencil-square',
'task' => 'task-done', 'task' => 'task-done',
'label' => 'label', 'label' => 'label',
'cross_reference' => 'comment-dots', 'cross_reference' => 'comment-dots',
...@@ -18,7 +18,7 @@ module SystemNoteHelper ...@@ -18,7 +18,7 @@ module SystemNoteHelper
'milestone' => 'clock', 'milestone' => 'clock',
'discussion' => 'comment', 'discussion' => 'comment',
'moved' => 'arrow-right', 'moved' => 'arrow-right',
'outdated' => 'pencil', 'outdated' => 'pencil-square',
'duplicate' => 'issue-duplicate', 'duplicate' => 'issue-duplicate',
'locked' => 'lock', 'locked' => 'lock',
'unlocked' => 'lock-open' 'unlocked' => 'lock-open'
......
...@@ -45,25 +45,25 @@ module Storage ...@@ -45,25 +45,25 @@ module Storage
# Hooks # Hooks
# Save the storage paths before the projects are destroyed to use them on after destroy # Save the storages before the projects are destroyed to use them on after destroy
def prepare_for_destroy def prepare_for_destroy
old_repository_storage_paths old_repository_storages
end end
private private
def move_repositories def move_repositories
# Move the namespace directory in all storage paths used by member projects # Move the namespace directory in all storages used by member projects
repository_storage_paths.each do |repository_storage_path| repository_storages.each do |repository_storage|
# Ensure old directory exists before moving it # Ensure old directory exists before moving it
gitlab_shell.add_namespace(repository_storage_path, full_path_was) gitlab_shell.add_namespace(repository_storage, full_path_was)
# Ensure new directory exists before moving it (if there's a parent) # Ensure new directory exists before moving it (if there's a parent)
gitlab_shell.add_namespace(repository_storage_path, parent.full_path) if parent gitlab_shell.add_namespace(repository_storage, parent.full_path) if parent
unless gitlab_shell.mv_namespace(repository_storage_path, full_path_was, full_path) unless gitlab_shell.mv_namespace(repository_storage, full_path_was, full_path)
Rails.logger.error "Exception moving path #{repository_storage_path} from #{full_path_was} to #{full_path}" Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_was} to #{full_path}"
# if we cannot move namespace directory we should rollback # if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs # db changes in order to prevent out of sync between db and fs
...@@ -72,33 +72,33 @@ module Storage ...@@ -72,33 +72,33 @@ module Storage
end end
end end
def old_repository_storage_paths def old_repository_storages
@old_repository_storage_paths ||= repository_storage_paths @old_repository_storage_paths ||= repository_storages
end end
def repository_storage_paths def repository_storages
# We need to get the storage paths for all the projects, even the ones that are # We need to get the storage paths for all the projects, even the ones that are
# pending delete. Unscoping also get rids of the default order, which causes # pending delete. Unscoping also get rids of the default order, which causes
# problems with SELECT DISTINCT. # problems with SELECT DISTINCT.
Project.unscoped do Project.unscoped do
all_projects.select('distinct(repository_storage)').to_a.map(&:repository_storage_path) all_projects.select('distinct(repository_storage)').to_a.map(&:repository_storage)
end end
end end
def rm_dir def rm_dir
# Remove the namespace directory in all storages paths used by member projects # Remove the namespace directory in all storages paths used by member projects
old_repository_storage_paths.each do |repository_storage_path| old_repository_storages.each do |repository_storage|
# Move namespace directory into trash. # Move namespace directory into trash.
# We will remove it later async # We will remove it later async
new_path = "#{full_path}+#{id}+deleted" new_path = "#{full_path}+#{id}+deleted"
if gitlab_shell.mv_namespace(repository_storage_path, full_path, new_path) if gitlab_shell.mv_namespace(repository_storage, full_path, new_path)
Gitlab::AppLogger.info %Q(Namespace directory "#{full_path}" moved to "#{new_path}") Gitlab::AppLogger.info %Q(Namespace directory "#{full_path}" moved to "#{new_path}")
# Remove namespace directroy async with delay so # Remove namespace directroy async with delay so
# GitLab has time to remove all projects first # GitLab has time to remove all projects first
run_after_commit do run_after_commit do
GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage_path, new_path) GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage, new_path)
end end
end end
end end
......
...@@ -37,20 +37,20 @@ class GroupMember < Member ...@@ -37,20 +37,20 @@ class GroupMember < Member
private private
def send_invite def send_invite
notification_service.invite_group_member(self, @raw_invite_token) run_after_commit_or_now { notification_service.invite_group_member(self, @raw_invite_token) }
super super
end end
def post_create_hook def post_create_hook
notification_service.new_group_member(self) run_after_commit_or_now { notification_service.new_group_member(self) }
super super
end end
def post_update_hook def post_update_hook
if access_level_changed? if access_level_changed?
notification_service.update_group_member(self) run_after_commit { notification_service.update_group_member(self) }
end end
super super
......
...@@ -92,7 +92,7 @@ class ProjectMember < Member ...@@ -92,7 +92,7 @@ class ProjectMember < Member
private private
def send_invite def send_invite
notification_service.invite_project_member(self, @raw_invite_token) run_after_commit_or_now { notification_service.invite_project_member(self, @raw_invite_token) }
super super
end end
...@@ -100,7 +100,7 @@ class ProjectMember < Member ...@@ -100,7 +100,7 @@ class ProjectMember < Member
def post_create_hook def post_create_hook
unless owner? unless owner?
event_service.join_project(self.project, self.user) event_service.join_project(self.project, self.user)
notification_service.new_project_member(self) run_after_commit_or_now { notification_service.new_project_member(self) }
end end
super super
...@@ -108,7 +108,7 @@ class ProjectMember < Member ...@@ -108,7 +108,7 @@ class ProjectMember < Member
def post_update_hook def post_update_hook
if access_level_changed? if access_level_changed?
notification_service.update_project_member(self) run_after_commit { notification_service.update_project_member(self) }
end end
super super
......
...@@ -518,10 +518,6 @@ class Project < ActiveRecord::Base ...@@ -518,10 +518,6 @@ class Project < ActiveRecord::Base
repository.empty? repository.empty?
end end
def repository_storage_path
Gitlab.config.repositories.storages[repository_storage]&.legacy_disk_path
end
def team def team
@team ||= ProjectTeam.new(self) @team ||= ProjectTeam.new(self)
end end
...@@ -1105,7 +1101,7 @@ class Project < ActiveRecord::Base ...@@ -1105,7 +1101,7 @@ class Project < ActiveRecord::Base
# Check if repository already exists on disk # Check if repository already exists on disk
def check_repository_path_availability def check_repository_path_availability
return true if skip_disk_validation return true if skip_disk_validation
return false unless repository_storage_path return false unless repository_storage
expires_full_path_cache # we need to clear cache to validate renames correctly expires_full_path_cache # we need to clear cache to validate renames correctly
...@@ -1910,14 +1906,14 @@ class Project < ActiveRecord::Base ...@@ -1910,14 +1906,14 @@ class Project < ActiveRecord::Base
def check_repository_absence! def check_repository_absence!
return if skip_disk_validation return if skip_disk_validation
if repository_storage_path.blank? || repository_with_same_path_already_exists? if repository_storage.blank? || repository_with_same_path_already_exists?
errors.add(:base, 'There is already a repository with that name on disk') errors.add(:base, 'There is already a repository with that name on disk')
throw :abort throw :abort
end end
end end
def repository_with_same_path_already_exists? def repository_with_same_path_already_exists?
gitlab_shell.exists?(repository_storage_path, "#{disk_path}.git") gitlab_shell.exists?(repository_storage, "#{disk_path}.git")
end end
# set last_activity_at to the same as created_at # set last_activity_at to the same as created_at
......
...@@ -21,7 +21,7 @@ class ProjectWiki ...@@ -21,7 +21,7 @@ class ProjectWiki
end end
delegate :empty?, to: :pages delegate :empty?, to: :pages
delegate :repository_storage_path, :hashed_storage?, to: :project delegate :repository_storage, :hashed_storage?, to: :project
def path def path
@project.path + '.wiki' @project.path + '.wiki'
......
...@@ -84,9 +84,14 @@ class Repository ...@@ -84,9 +84,14 @@ class Repository
# Return absolute path to repository # Return absolute path to repository
def path_to_repo def path_to_repo
@path_to_repo ||= File.expand_path( @path_to_repo ||=
File.join(repository_storage_path, disk_path + '.git') begin
) storage = Gitlab.config.repositories.storages[@project.repository_storage]
File.expand_path(
File.join(storage.legacy_disk_path, disk_path + '.git')
)
end
end end
def inspect def inspect
...@@ -915,10 +920,6 @@ class Repository ...@@ -915,10 +920,6 @@ class Repository
raw_repository.fetch_ref(source_repository.raw_repository, source_ref: source_ref, target_ref: target_ref) raw_repository.fetch_ref(source_repository.raw_repository, source_ref: source_ref, target_ref: target_ref)
end end
def repository_storage_path
@project.repository_storage_path
end
def rebase(user, merge_request) def rebase(user, merge_request)
raw.rebase(user, merge_request.id, branch: merge_request.source_branch, raw.rebase(user, merge_request.id, branch: merge_request.source_branch,
branch_sha: merge_request.source_branch_sha, branch_sha: merge_request.source_branch_sha,
......
module Storage module Storage
class HashedProject class HashedProject
attr_accessor :project attr_accessor :project
delegate :gitlab_shell, :repository_storage_path, to: :project delegate :gitlab_shell, :repository_storage, to: :project
ROOT_PATH_PREFIX = '@hashed'.freeze ROOT_PATH_PREFIX = '@hashed'.freeze
...@@ -24,7 +24,7 @@ module Storage ...@@ -24,7 +24,7 @@ module Storage
end end
def ensure_storage_path_exists def ensure_storage_path_exists
gitlab_shell.add_namespace(repository_storage_path, base_dir) gitlab_shell.add_namespace(repository_storage, base_dir)
end end
def rename_repo def rename_repo
......
module Storage module Storage
class LegacyProject class LegacyProject
attr_accessor :project attr_accessor :project
delegate :namespace, :gitlab_shell, :repository_storage_path, to: :project delegate :namespace, :gitlab_shell, :repository_storage, to: :project
def initialize(project) def initialize(project)
@project = project @project = project
...@@ -24,18 +24,18 @@ module Storage ...@@ -24,18 +24,18 @@ module Storage
def ensure_storage_path_exists def ensure_storage_path_exists
return unless namespace return unless namespace
gitlab_shell.add_namespace(repository_storage_path, base_dir) gitlab_shell.add_namespace(repository_storage, base_dir)
end end
def rename_repo def rename_repo
new_full_path = project.build_full_path new_full_path = project.build_full_path
if gitlab_shell.mv_repository(repository_storage_path, project.full_path_was, new_full_path) if gitlab_shell.mv_repository(repository_storage, project.full_path_was, new_full_path)
# If repository moved successfully we need to send update instructions to users. # If repository moved successfully we need to send update instructions to users.
# However we cannot allow rollback since we moved repository # However we cannot allow rollback since we moved repository
# So we basically we mute exceptions in next actions # So we basically we mute exceptions in next actions
begin begin
gitlab_shell.mv_repository(repository_storage_path, "#{project.full_path_was}.wiki", "#{new_full_path}.wiki") gitlab_shell.mv_repository(repository_storage, "#{project.full_path_was}.wiki", "#{new_full_path}.wiki")
return true return true
rescue => e rescue => e
Rails.logger.error "Exception renaming #{project.full_path_was} -> #{new_full_path}: #{e}" Rails.logger.error "Exception renaming #{project.full_path_was} -> #{new_full_path}: #{e}"
......
...@@ -26,7 +26,7 @@ module Issues ...@@ -26,7 +26,7 @@ module Issues
issue.update(closed_by: current_user) issue.update(closed_by: current_user)
event_service.close_issue(issue, current_user) event_service.close_issue(issue, current_user)
create_note(issue, commit) if system_note create_note(issue, commit) if system_note
notification_service.close_issue(issue, current_user) if notifications notification_service.async.close_issue(issue, current_user) if notifications
todo_service.close_issue(issue, current_user) todo_service.close_issue(issue, current_user)
execute_hooks(issue, 'close') execute_hooks(issue, 'close')
invalidate_cache_counts(issue, users: issue.assignees) invalidate_cache_counts(issue, users: issue.assignees)
......
...@@ -139,7 +139,7 @@ module Issues ...@@ -139,7 +139,7 @@ module Issues
end end
def notify_participants def notify_participants
notification_service.issue_moved(@old_issue, @new_issue, @current_user) notification_service.async.issue_moved(@old_issue, @new_issue, @current_user)
end end
end end
end end
...@@ -6,7 +6,7 @@ module Issues ...@@ -6,7 +6,7 @@ module Issues
if issue.reopen if issue.reopen
event_service.reopen_issue(issue, current_user) event_service.reopen_issue(issue, current_user)
create_note(issue, 'reopened') create_note(issue, 'reopened')
notification_service.reopen_issue(issue, current_user) notification_service.async.reopen_issue(issue, current_user)
execute_hooks(issue, 'reopen') execute_hooks(issue, 'reopen')
invalidate_cache_counts(issue, users: issue.assignees) invalidate_cache_counts(issue, users: issue.assignees)
issue.update_project_counter_caches issue.update_project_counter_caches
......
...@@ -30,7 +30,7 @@ module Issues ...@@ -30,7 +30,7 @@ module Issues
if issue.assignees != old_assignees if issue.assignees != old_assignees
create_assignee_note(issue, old_assignees) create_assignee_note(issue, old_assignees)
notification_service.reassigned_issue(issue, current_user, old_assignees) notification_service.async.reassigned_issue(issue, current_user, old_assignees)
todo_service.reassigned_issue(issue, current_user, old_assignees) todo_service.reassigned_issue(issue, current_user, old_assignees)
end end
...@@ -41,13 +41,13 @@ module Issues ...@@ -41,13 +41,13 @@ module Issues
added_labels = issue.labels - old_labels added_labels = issue.labels - old_labels
if added_labels.present? if added_labels.present?
notification_service.relabeled_issue(issue, added_labels, current_user) notification_service.async.relabeled_issue(issue, added_labels, current_user)
end end
added_mentions = issue.mentioned_users - old_mentioned_users added_mentions = issue.mentioned_users - old_mentioned_users
if added_mentions.present? if added_mentions.present?
notification_service.new_mentions_in_issue(issue, added_mentions, current_user) notification_service.async.new_mentions_in_issue(issue, added_mentions, current_user)
end end
end end
......
...@@ -10,7 +10,7 @@ module MergeRequests ...@@ -10,7 +10,7 @@ module MergeRequests
if merge_request.close if merge_request.close
create_event(merge_request) create_event(merge_request)
create_note(merge_request) create_note(merge_request)
notification_service.close_mr(merge_request, current_user) notification_service.async.close_mr(merge_request, current_user)
todo_service.close_merge_request(merge_request, current_user) todo_service.close_merge_request(merge_request, current_user)
execute_hooks(merge_request, 'close') execute_hooks(merge_request, 'close')
invalidate_cache_counts(merge_request, users: merge_request.assignees) invalidate_cache_counts(merge_request, users: merge_request.assignees)
......
...@@ -6,7 +6,7 @@ module MergeRequests ...@@ -6,7 +6,7 @@ module MergeRequests
if merge_request.reopen if merge_request.reopen
create_event(merge_request) create_event(merge_request)
create_note(merge_request, 'reopened') create_note(merge_request, 'reopened')
notification_service.reopen_mr(merge_request, current_user) notification_service.async.reopen_mr(merge_request, current_user)
execute_hooks(merge_request, 'reopen') execute_hooks(merge_request, 'reopen')
merge_request.reload_diff(current_user) merge_request.reload_diff(current_user)
merge_request.mark_as_unchecked merge_request.mark_as_unchecked
......
...@@ -4,7 +4,7 @@ module MergeRequests ...@@ -4,7 +4,7 @@ module MergeRequests
return unless merge_request.discussions_resolved? return unless merge_request.discussions_resolved?
SystemNoteService.resolve_all_discussions(merge_request, project, current_user) SystemNoteService.resolve_all_discussions(merge_request, project, current_user)
notification_service.resolve_all_discussions(merge_request, current_user) notification_service.async.resolve_all_discussions(merge_request, current_user)
end end
end end
end end
...@@ -21,6 +21,7 @@ module MergeRequests ...@@ -21,6 +21,7 @@ module MergeRequests
update(merge_request) update(merge_request)
end end
# rubocop:disable Metrics/AbcSize
def handle_changes(merge_request, options) def handle_changes(merge_request, options)
old_associations = options.fetch(:old_associations, {}) old_associations = options.fetch(:old_associations, {})
old_labels = old_associations.fetch(:labels, []) old_labels = old_associations.fetch(:labels, [])
...@@ -42,8 +43,11 @@ module MergeRequests ...@@ -42,8 +43,11 @@ module MergeRequests
end end
if merge_request.previous_changes.include?('assignee_id') if merge_request.previous_changes.include?('assignee_id')
old_assignee_id = merge_request.previous_changes['assignee_id'].first
old_assignee = User.find(old_assignee_id) if old_assignee_id
create_assignee_note(merge_request) create_assignee_note(merge_request)
notification_service.reassigned_merge_request(merge_request, current_user) notification_service.async.reassigned_merge_request(merge_request, current_user, old_assignee)
todo_service.reassigned_merge_request(merge_request, current_user) todo_service.reassigned_merge_request(merge_request, current_user)
end end
...@@ -54,7 +58,7 @@ module MergeRequests ...@@ -54,7 +58,7 @@ module MergeRequests
added_labels = merge_request.labels - old_labels added_labels = merge_request.labels - old_labels
if added_labels.present? if added_labels.present?
notification_service.relabeled_merge_request( notification_service.async.relabeled_merge_request(
merge_request, merge_request,
added_labels, added_labels,
current_user current_user
...@@ -63,13 +67,14 @@ module MergeRequests ...@@ -63,13 +67,14 @@ module MergeRequests
added_mentions = merge_request.mentioned_users - old_mentioned_users added_mentions = merge_request.mentioned_users - old_mentioned_users
if added_mentions.present? if added_mentions.present?
notification_service.new_mentions_in_merge_request( notification_service.async.new_mentions_in_merge_request(
merge_request, merge_request,
added_mentions, added_mentions,
current_user current_user
) )
end end
end end
# rubocop:enable Metrics/AbcSize
def merge_from_quick_action(merge_request) def merge_from_quick_action(merge_request)
last_diff_sha = params.delete(:merge) last_diff_sha = params.delete(:merge)
......
...@@ -7,7 +7,32 @@ ...@@ -7,7 +7,32 @@
# Ex. # Ex.
# NotificationService.new.new_issue(issue, current_user) # NotificationService.new.new_issue(issue, current_user)
# #
# When calculating the recipients of a notification is expensive (for instance,
# in the new issue case), `#async` will make that calculation happen in Sidekiq
# instead:
#
# NotificationService.new.async.new_issue(issue, current_user)
#
class NotificationService class NotificationService
class Async
attr_reader :parent
delegate :respond_to_missing, to: :parent
def initialize(parent)
@parent = parent
end
def method_missing(meth, *args)
return super unless parent.respond_to?(meth)
MailScheduler::NotificationServiceWorker.perform_async(meth.to_s, *args)
end
end
def async
@async ||= Async.new(self)
end
# Always notify user about ssh key added # Always notify user about ssh key added
# only if ssh key is not deploy key # only if ssh key is not deploy key
# #
...@@ -142,8 +167,23 @@ class NotificationService ...@@ -142,8 +167,23 @@ class NotificationService
# * merge_request assignee if their notification level is not Disabled # * merge_request assignee if their notification level is not Disabled
# * users with custom level checked with "reassign merge request" # * users with custom level checked with "reassign merge request"
# #
def reassigned_merge_request(merge_request, current_user) def reassigned_merge_request(merge_request, current_user, previous_assignee)
reassign_resource_email(merge_request, current_user, :reassigned_merge_request_email) recipients = NotificationRecipientService.build_recipients(
merge_request,
current_user,
action: "reassign",
previous_assignee: previous_assignee
)
recipients.each do |recipient|
mailer.reassigned_merge_request_email(
recipient.user.id,
merge_request.id,
previous_assignee&.id,
current_user.id,
recipient.reason
).deliver_later
end
end end
# When we add labels to a merge request we should send an email to: # When we add labels to a merge request we should send an email to:
...@@ -421,29 +461,6 @@ class NotificationService ...@@ -421,29 +461,6 @@ class NotificationService
end end
end end
def reassign_resource_email(target, current_user, method)
previous_assignee_id = previous_record(target, 'assignee_id')
previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
recipients = NotificationRecipientService.build_recipients(
target,
current_user,
action: "reassign",
previous_assignee: previous_assignee
)
recipients.each do |recipient|
mailer.send(
method,
recipient.user.id,
target.id,
previous_assignee_id,
current_user.id,
recipient.reason
).deliver_later
end
end
def relabeled_resource_email(target, labels, current_user, method) def relabeled_resource_email(target, labels, current_user, method)
recipients = labels.flat_map { |l| l.subscribers(target.project) }.uniq recipients = labels.flat_map { |l| l.subscribers(target.project) }.uniq
recipients = notifiable_users( recipients = notifiable_users(
...@@ -471,14 +488,6 @@ class NotificationService ...@@ -471,14 +488,6 @@ class NotificationService
Notify Notify
end end
def previous_record(object, attribute)
return unless object && attribute
if object.previous_changes.include?(attribute)
object.previous_changes[attribute].first
end
end
private private
def recipients_for_pages_domain(domain) def recipients_for_pages_domain(domain)
......
...@@ -91,7 +91,7 @@ module Projects ...@@ -91,7 +91,7 @@ module Projects
project.run_after_commit do project.run_after_commit do
# self is now project # self is now project
GitlabShellWorker.perform_in(5.minutes, :remove_repository, self.repository_storage_path, new_path) GitlabShellWorker.perform_in(5.minutes, :remove_repository, self.repository_storage, new_path)
end end
else else
false false
...@@ -100,9 +100,9 @@ module Projects ...@@ -100,9 +100,9 @@ module Projects
def mv_repository(from_path, to_path) def mv_repository(from_path, to_path)
# There is a possibility project does not have repository or wiki # There is a possibility project does not have repository or wiki
return true unless gitlab_shell.exists?(project.repository_storage_path, from_path + '.git') return true unless gitlab_shell.exists?(project.repository_storage, from_path + '.git')
gitlab_shell.mv_repository(project.repository_storage_path, from_path, to_path) gitlab_shell.mv_repository(project.repository_storage, from_path, to_path)
end end
def attempt_rollback(project, message) def attempt_rollback(project, message)
......
...@@ -47,8 +47,8 @@ module Projects ...@@ -47,8 +47,8 @@ module Projects
private private
def move_repository(from_name, to_name) def move_repository(from_name, to_name)
from_exists = gitlab_shell.exists?(project.repository_storage_path, "#{from_name}.git") from_exists = gitlab_shell.exists?(project.repository_storage, "#{from_name}.git")
to_exists = gitlab_shell.exists?(project.repository_storage_path, "#{to_name}.git") to_exists = gitlab_shell.exists?(project.repository_storage, "#{to_name}.git")
# If we don't find the repository on either original or target we should log that as it could be an issue if the # If we don't find the repository on either original or target we should log that as it could be an issue if the
# project was not originally empty. # project was not originally empty.
...@@ -60,7 +60,7 @@ module Projects ...@@ -60,7 +60,7 @@ module Projects
return true return true
end end
gitlab_shell.mv_repository(project.repository_storage_path, from_name, to_name) gitlab_shell.mv_repository(project.repository_storage, from_name, to_name)
end end
def rollback_folder_move def rollback_folder_move
......
...@@ -127,7 +127,7 @@ module Projects ...@@ -127,7 +127,7 @@ module Projects
end end
def move_repo_folder(from_name, to_name) def move_repo_folder(from_name, to_name)
gitlab_shell.mv_repository(project.repository_storage_path, from_name, to_name) gitlab_shell.mv_repository(project.repository_storage, from_name, to_name)
end end
def execute_system_hooks def execute_system_hooks
......
module Projects module Projects
class UpdatePagesService < BaseService class UpdatePagesService < BaseService
InvaildStateError = Class.new(StandardError) InvalidStateError = Class.new(StandardError)
FailedToExtractError = Class.new(StandardError) FailedToExtractError = Class.new(StandardError)
BLOCK_SIZE = 32.kilobytes BLOCK_SIZE = 32.kilobytes
...@@ -21,8 +21,8 @@ module Projects ...@@ -21,8 +21,8 @@ module Projects
@status.enqueue! @status.enqueue!
@status.run! @status.run!
raise InvaildStateError, 'missing pages artifacts' unless build.artifacts? raise InvalidStateError, 'missing pages artifacts' unless build.artifacts?
raise InvaildStateError, 'pages are outdated' unless latest? raise InvalidStateError, 'pages are outdated' unless latest?
# Create temporary directory in which we will extract the artifacts # Create temporary directory in which we will extract the artifacts
FileUtils.mkdir_p(tmp_path) FileUtils.mkdir_p(tmp_path)
...@@ -31,16 +31,16 @@ module Projects ...@@ -31,16 +31,16 @@ module Projects
# Check if we did extract public directory # Check if we did extract public directory
archive_public_path = File.join(archive_path, 'public') archive_public_path = File.join(archive_path, 'public')
raise InvaildStateError, 'pages miss the public folder' unless Dir.exist?(archive_public_path) raise InvalidStateError, 'pages miss the public folder' unless Dir.exist?(archive_public_path)
raise InvaildStateError, 'pages are outdated' unless latest? raise InvalidStateError, 'pages are outdated' unless latest?
deploy_page!(archive_public_path) deploy_page!(archive_public_path)
success success
end end
rescue InvaildStateError => e rescue InvalidStateError => e
error(e.message) error(e.message)
rescue => e rescue => e
error(e.message, false) error(e.message)
raise e raise e
end end
...@@ -48,17 +48,15 @@ module Projects ...@@ -48,17 +48,15 @@ module Projects
def success def success
@status.success @status.success
delete_artifact!
super super
end end
def error(message, allow_delete_artifact = true) def error(message)
register_failure register_failure
log_error("Projects::UpdatePagesService: #{message}") log_error("Projects::UpdatePagesService: #{message}")
@status.allow_failure = !latest? @status.allow_failure = !latest?
@status.description = message @status.description = message
@status.drop(:script_failure) @status.drop(:script_failure)
delete_artifact! if allow_delete_artifact
super super
end end
...@@ -77,18 +75,18 @@ module Projects ...@@ -77,18 +75,18 @@ module Projects
if artifacts.ends_with?('.zip') if artifacts.ends_with?('.zip')
extract_zip_archive!(temp_path) extract_zip_archive!(temp_path)
else else
raise InvaildStateError, 'unsupported artifacts format' raise InvalidStateError, 'unsupported artifacts format'
end end
end end
def extract_zip_archive!(temp_path) def extract_zip_archive!(temp_path)
raise InvaildStateError, 'missing artifacts metadata' unless build.artifacts_metadata? raise InvalidStateError, 'missing artifacts metadata' unless build.artifacts_metadata?
# Calculate page size after extract # Calculate page size after extract
public_entry = build.artifacts_metadata_entry(SITE_PATH, recursive: true) public_entry = build.artifacts_metadata_entry(SITE_PATH, recursive: true)
if public_entry.total_size > max_size if public_entry.total_size > max_size
raise InvaildStateError, "artifacts for pages are too large: #{public_entry.total_size}" raise InvalidStateError, "artifacts for pages are too large: #{public_entry.total_size}"
end end
# Requires UnZip at least 6.00 Info-ZIP. # Requires UnZip at least 6.00 Info-ZIP.
...@@ -162,11 +160,6 @@ module Projects ...@@ -162,11 +160,6 @@ module Projects
build.artifacts_file.path build.artifacts_file.path
end end
def delete_artifact!
build.reload # Reload stable object to prevent erase artifacts with old state
build.erase_artifacts! unless build.has_expiring_artifacts?
end
def latest_sha def latest_sha
project.commit(build.ref).try(:sha).to_s project.commit(build.ref).try(:sha).to_s
ensure ensure
......
...@@ -20,11 +20,12 @@ class RepositoryArchiveCleanUpService ...@@ -20,11 +20,12 @@ class RepositoryArchiveCleanUpService
private private
def clean_up_old_archives def clean_up_old_archives
run(%W(find #{path} -not -path #{path} -type f \( -name \*.tar -o -name \*.bz2 -o -name \*.tar.gz -o -name \*.zip \) -maxdepth 2 -mmin +#{mmin} -delete)) run(%W(find #{path} -mindepth 1 -maxdepth 3 -type f \( -name \*.tar -o -name \*.bz2 -o -name \*.tar.gz -o -name \*.zip \) -mmin +#{mmin} -delete))
end end
def clean_up_empty_directories def clean_up_empty_directories
run(%W(find #{path} -not -path #{path} -type d -empty -name \*.git -maxdepth 1 -delete)) run(%W(find #{path} -mindepth 2 -maxdepth 2 -type d -empty -delete))
run(%W(find #{path} -mindepth 1 -maxdepth 1 -type d -empty -delete))
end end
def run(cmd) def run(cmd)
......
...@@ -159,7 +159,7 @@ module SystemNoteService ...@@ -159,7 +159,7 @@ module SystemNoteService
body = if noteable.time_estimate == 0 body = if noteable.time_estimate == 0
"removed time estimate" "removed time estimate"
else else
"changed time estimate to #{parsed_time}," "changed time estimate to #{parsed_time}"
end end
create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking')) create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking'))
......
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
delete_user_url: admin_user_path(user), delete_user_url: admin_user_path(user),
block_user_url: block_admin_user_path(user), block_user_url: block_admin_user_path(user),
username: user.name, username: user.name,
delete_contributions: 'false' }, type: 'button' } delete_contributions: false }, type: 'button' }
= s_('AdminUsers|Delete user') = s_('AdminUsers|Delete user')
%li %li
...@@ -52,5 +52,5 @@ ...@@ -52,5 +52,5 @@
delete_user_url: admin_user_path(user, hard_delete: true), delete_user_url: admin_user_path(user, hard_delete: true),
block_user_url: block_admin_user_path(user), block_user_url: block_admin_user_path(user),
username: user.name, username: user.name,
delete_contributions: 'true' }, type: 'button' } delete_contributions: true }, type: 'button' }
= s_('AdminUsers|Delete user and contributions') = s_('AdminUsers|Delete user and contributions')
...@@ -183,7 +183,7 @@ ...@@ -183,7 +183,7 @@
delete_user_url: admin_user_path(@user), delete_user_url: admin_user_path(@user),
block_user_url: block_admin_user_path(@user), block_user_url: block_admin_user_path(@user),
username: @user.name, username: @user.name,
delete_contributions: 'false' }, type: 'button' } delete_contributions: false }, type: 'button' }
= s_('AdminUsers|Delete user') = s_('AdminUsers|Delete user')
- else - else
- if @user.solo_owned_groups.present? - if @user.solo_owned_groups.present?
...@@ -215,7 +215,7 @@ ...@@ -215,7 +215,7 @@
delete_user_url: admin_user_path(@user, hard_delete: true), delete_user_url: admin_user_path(@user, hard_delete: true),
block_user_url: block_admin_user_path(@user), block_user_url: block_admin_user_path(@user),
username: @user.name, username: @user.name,
delete_contributions: 'true' }, type: 'button' } delete_contributions: true }, type: 'button' }
= s_('AdminUsers|Delete user and contributions') = s_('AdminUsers|Delete user and contributions')
- else - else
%p %p
......
...@@ -17,14 +17,14 @@ ...@@ -17,14 +17,14 @@
.ci-variable-row-body .ci-variable-row-body
%input.js-ci-variable-input-id{ type: "hidden", name: id_input_name, value: id } %input.js-ci-variable-input-id{ type: "hidden", name: id_input_name, value: id }
%input.js-ci-variable-input-destroy{ type: "hidden", name: destroy_input_name } %input.js-ci-variable-input-destroy{ type: "hidden", name: destroy_input_name }
%input.js-ci-variable-input-key.ci-variable-body-item.form-control{ type: "text", %input.js-ci-variable-input-key.ci-variable-body-item.qa-ci-variable-input-key.form-control{ type: "text",
name: key_input_name, name: key_input_name,
value: key, value: key,
placeholder: s_('CiVariables|Input variable key') } placeholder: s_('CiVariables|Input variable key') }
.ci-variable-body-item .ci-variable-body-item
.form-control.js-secret-value-placeholder{ class: ('hide' unless id) } .form-control.js-secret-value-placeholder.qa-ci-variable-input-value{ class: ('hide' unless id) }
= '*' * 20 = '*' * 20
%textarea.js-ci-variable-input-value.js-secret-value.form-control{ class: ('hide' if id), %textarea.js-ci-variable-input-value.js-secret-value.qa-ci-variable-input-value.form-control{ class: ('hide' if id),
rows: 1, rows: 1,
name: value_input_name, name: value_input_name,
placeholder: s_('CiVariables|Input variable value') } placeholder: s_('CiVariables|Input variable value') }
......
...@@ -5,8 +5,3 @@ ...@@ -5,8 +5,3 @@
peek_url: peek_routes.results_url, peek_url: peek_routes.results_url,
profile_url: url_for(params.merge(lineprofiler: 'true')) }, profile_url: url_for(params.merge(lineprofiler: 'true')) },
class: Peek.env } class: Peek.env }
#peek-view-performance-bar.hidden
= render_server_response_time
%span#serverstats
%ul.performance-bar
...@@ -8,18 +8,17 @@ ...@@ -8,18 +8,17 @@
%li{ class: "branch-item js-branch-#{branch.name}" } %li{ class: "branch-item js-branch-#{branch.name}" }
.branch-info .branch-info
.branch-title .branch-title
= link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name' do = sprite_icon('fork', size: 12)
= sprite_icon('fork', size: 12) = link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name prepend-left-8' do
= branch.name = branch.name
&nbsp;
- if branch.name == @repository.root_ref - if branch.name == @repository.root_ref
%span.label.label-primary default %span.label.label-primary.prepend-left-5 default
- elsif merged - elsif merged
%span.label.label-info.has-tooltip{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } } %span.label.label-info.has-tooltip.prepend-left-5{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } }
= s_('Branches|merged') = s_('Branches|merged')
- if protected_branch?(@project, branch) - if protected_branch?(@project, branch)
%span.label.label-success %span.label.label-success.prepend-left-5
= s_('Branches|protected') = s_('Branches|protected')
.block-truncated .block-truncated
......
- can_admin_project = can?(current_user, :admin_project, @project) - can_admin_project = can?(current_user, :admin_project, @project)
= render layout: 'projects/protected_branches/shared/branches_list', locals: { can_admin_project: can_admin_project } do = render layout: 'projects/protected_branches/shared/branches_list', locals: { can_admin_project: can_admin_project } do
= render partial: 'projects/protected_branches/protected_branch', collection: @protected_branches, locals: { can_admin_project: can_admin_project} = render partial: 'projects/protected_branches/protected_branch', collection: @protected_branches
- content_for :merge_access_levels do - content_for :merge_access_levels do
.merge_access_levels-container .merge_access_levels-container
= dropdown_tag('Select', = dropdown_tag('Select',
options: { toggle_class: 'js-allowed-to-merge wide', options: { toggle_class: 'js-allowed-to-merge qa-allowed-to-merge-select wide',
dropdown_class: 'dropdown-menu-selectable capitalize-header', dropdown_class: 'dropdown-menu-selectable qa-allowed-to-merge-dropdown capitalize-header',
data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }}) data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }})
- content_for :push_access_levels do - content_for :push_access_levels do
.push_access_levels-container .push_access_levels-container
......
%td %td
= hidden_field_tag "allowed_to_merge_#{protected_branch.id}", protected_branch.merge_access_levels.first.access_level = hidden_field_tag "allowed_to_merge_#{protected_branch.id}", protected_branch.merge_access_levels.first.access_level
= dropdown_tag( (protected_branch.merge_access_levels.first.humanize || 'Select') , = dropdown_tag( (protected_branch.merge_access_levels.first.humanize || 'Select') ,
options: { toggle_class: 'js-allowed-to-merge', dropdown_class: 'dropdown-menu-selectable js-allowed-to-merge-container capitalize-header', options: { toggle_class: 'js-allowed-to-merge qa-allowed-to-merge', dropdown_class: 'dropdown-menu-selectable js-allowed-to-merge-container capitalize-header',
data: { field_name: "allowed_to_merge_#{protected_branch.id}", access_level_id: protected_branch.merge_access_levels.first.id }}) data: { field_name: "allowed_to_merge_#{protected_branch.id}", access_level_id: protected_branch.merge_access_levels.first.id }})
%td %td
= hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_levels.first.access_level = hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_levels.first.access_level
......
...@@ -21,4 +21,4 @@ ...@@ -21,4 +21,4 @@
- if can_admin_project - if can_admin_project
%td %td
= link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning' = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], disabled: local_assigns[:disabled], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning"
...@@ -36,5 +36,6 @@ ...@@ -36,5 +36,6 @@
= form.text_field :domain, class: 'form-control', placeholder: 'domain.com' = form.text_field :domain, class: 'form-control', placeholder: 'domain.com'
.help-block .help-block
= s_('CICD|You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages.') = s_('CICD|You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages.')
= link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'auto-devops-base-domain'), target: '_blank'
= f.submit 'Save changes', class: "btn btn-success prepend-top-15" = f.submit 'Save changes', class: "btn btn-success prepend-top-15"
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
- github_importer:github_import_stage_import_repository - github_importer:github_import_stage_import_repository
- mail_scheduler:mail_scheduler_issue_due - mail_scheduler:mail_scheduler_issue_due
- mail_scheduler:mail_scheduler_notification_service
- object_storage_upload - object_storage_upload
- object_storage:object_storage_background_move - object_storage:object_storage_background_move
......
...@@ -4,4 +4,8 @@ module MailSchedulerQueue ...@@ -4,4 +4,8 @@ module MailSchedulerQueue
included do included do
queue_namespace :mail_scheduler queue_namespace :mail_scheduler
end end
def notification_service
@notification_service ||= NotificationService.new
end
end end
...@@ -4,8 +4,6 @@ module MailScheduler ...@@ -4,8 +4,6 @@ module MailScheduler
include MailSchedulerQueue include MailSchedulerQueue
def perform(project_id) def perform(project_id)
notification_service = NotificationService.new
Issue.opened.due_tomorrow.in_projects(project_id).preload(:project).find_each do |issue| Issue.opened.due_tomorrow.in_projects(project_id).preload(:project).find_each do |issue|
notification_service.issue_due(issue) notification_service.issue_due(issue)
end end
......
require 'active_job/arguments'
module MailScheduler
class NotificationServiceWorker
include ApplicationWorker
include MailSchedulerQueue
def perform(meth, *args)
deserialized_args = ActiveJob::Arguments.deserialize(args)
notification_service.public_send(meth, *deserialized_args) # rubocop:disable GitlabSecurity/PublicSend
rescue ActiveJob::DeserializationError
end
def self.perform_async(*args)
super(*ActiveJob::Arguments.serialize(args))
end
end
end
...@@ -13,7 +13,9 @@ class RepositoryForkWorker ...@@ -13,7 +13,9 @@ class RepositoryForkWorker
# See https://gitlab.com/gitlab-org/gitaly/issues/1110 # See https://gitlab.com/gitlab-org/gitaly/issues/1110
if args.empty? if args.empty?
source_project = target_project.forked_from_project source_project = target_project.forked_from_project
return target_project.mark_import_as_failed('Source project cannot be found.') unless source_project unless source_project
return target_project.mark_import_as_failed('Source project cannot be found.')
end
fork_repository(target_project, source_project.repository_storage, source_project.disk_path) fork_repository(target_project, source_project.repository_storage, source_project.disk_path)
else else
......
---
title: Correct text and functionality for delete user / delete user and contributions
modal.
merge_request: 18463
author: Marc Schwede
type: fixed
---
title: Don't automatically remove artifacts for pages jobs after pages:deploy has
run
merge_request: 18628
author:
type: fixed
---
title: Ensure member notifications are sent after the member actual creation/update in the DB
merge_request: 18538
author:
type: fixed
---
title: Ports omniauth-jwt gem onto GitLab OmniAuth Strategies suite
merge_request: 18580
author:
type: fixed
---
title: Fix redirection error for applications using OpenID
merge_request: 18599
author:
type: fixed
---
title: For group dashboard, we no longer show groups which the visitor is not a member of (this applies to admins and auditors)
merge_request: 17884
author: Roger Rüttimann
type: changed
---
title: Fixed inconsistent protected branch pill baseline
merge_request:
author:
type: fixed
---
title: Add sha filter to pipelines list API
merge_request: 18125
author:
type: changed
---
title: Compute notification recipients in background jobs
merge_request:
author:
type: performance
---
title: Improve performance of a service responsible for creating a pipeline
merge_request: 18582
author:
type: performance
---
title: Fix size and position for fork icon
merge_request: 18449
author: George Tsiolis
type: changed
---
title: Revert discussion counter height
merge_request: 18656
author: George Tsiolis
type: changed
---
title: Serve archive requests with the correct file in all cases
merge_request:
author:
type: security
---
title: Sanitizes user name to avoid XSS attacks
merge_request:
author:
type: security
---
title: Update timeline icon for description edit
merge_request: 18633
author: George Tsiolis
type: changed
---
title: Reset milestone filter when clicking "Any Milestone" in dashboard
merge_request: 18531
author:
type: fixed
---
title: Repository#exists? is always executed through Gitaly
merge_request:
author:
type: performance
...@@ -184,18 +184,18 @@ production: &base ...@@ -184,18 +184,18 @@ production: &base
# base_dir: uploads/-/system # base_dir: uploads/-/system
object_store: object_store:
enabled: false enabled: false
# remote_directory: uploads # Bucket name remote_directory: uploads # Bucket name
# direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false) # direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false)
# background_upload: false # Temporary option to limit automatic upload (Default: true) # background_upload: false # Temporary option to limit automatic upload (Default: true)
# proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage # proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage
connection: connection:
provider: AWS provider: AWS
aws_access_key_id: AWS_ACCESS_KEY_ID aws_access_key_id: AWS_ACCESS_KEY_ID
aws_secret_access_key: AWS_SECRET_ACCESS_KEY aws_secret_access_key: AWS_SECRET_ACCESS_KEY
region: us-east-1 region: us-east-1
# host: 'localhost' # default: s3.amazonaws.com # host: 'localhost' # default: s3.amazonaws.com
# endpoint: 'http://127.0.0.1:9000' # default: nil # endpoint: 'http://127.0.0.1:9000' # default: nil
# path_style: true # Use 'host/bucket_name/object' instead of 'bucket_name.host/object' # path_style: true # Use 'host/bucket_name/object' instead of 'bucket_name.host/object'
## GitLab Pages ## GitLab Pages
pages: pages:
...@@ -212,6 +212,8 @@ production: &base ...@@ -212,6 +212,8 @@ production: &base
artifacts_server: true artifacts_server: true
# external_http: ["1.1.1.1:80", "[2001::1]:80"] # If defined, enables custom domain support in GitLab Pages # external_http: ["1.1.1.1:80", "[2001::1]:80"] # If defined, enables custom domain support in GitLab Pages
# external_https: ["1.1.1.1:443", "[2001::1]:443"] # If defined, enables custom domain and certificate support in GitLab Pages # external_https: ["1.1.1.1:443", "[2001::1]:443"] # If defined, enables custom domain and certificate support in GitLab Pages
admin:
address: unix:/home/git/gitlab/tmp/sockets/private/pages-admin.socket # TCP connections are supported too (e.g. tcp://host:port)
## Mattermost ## Mattermost
## For enabling Add to Mattermost button ## For enabling Add to Mattermost button
...@@ -532,7 +534,7 @@ production: &base ...@@ -532,7 +534,7 @@ production: &base
# required_claims: ["name", "email"], # required_claims: ["name", "email"],
# info_map: { name: "name", email: "email" }, # info_map: { name: "name", email: "email" },
# auth_url: 'https://example.com/', # auth_url: 'https://example.com/',
# valid_within: nil, # valid_within: null,
# } # }
# } # }
# - { name: 'saml', # - { name: 'saml',
...@@ -823,7 +825,7 @@ test: ...@@ -823,7 +825,7 @@ test:
required_claims: ["name", "email"], required_claims: ["name", "email"],
info_map: { name: "name", email: "email" }, info_map: { name: "name", email: "email" },
auth_url: 'https://example.com/', auth_url: 'https://example.com/',
valid_within: nil, valid_within: null,
} }
} }
- { name: 'auth0', - { name: 'auth0',
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment