Merge branch 'master' into auto-pipelines-vue
* master: (76 commits) Update "Installation from source" guide for 8.15.0 Group links spec update Updates the font weight of button styles because of the change to system fonts Refactor SSH keys docs Improvements to setting up ssh Do not reload diff for merge request made from fork when target branch in fork is updated Add 8.12.10, 8.12.11, and 8.12.12 CHANGELOG.md items Changes after review Fix broken test Adds CHANGELOG entry Adds tests Uniformize props name format Replace commit icon svg logic Replace play icon svg logic Updated JS based on review Fixed group links dropdown to match Update docs to reflect new defaults on omnibus Merge branch 'jej-23867-use-mr-finder-instead-of-access-check' into 'security' Merge branch 'html-safe-diff-line-content' into 'security' Merge branch 'rs-filter-authentication_token' into 'security' Merge branch 'destroy-session' into 'security' ... Conflicts: app/models/ci/pipeline.rb app/models/commit_status.rb app/views/projects/ci/pipelines/_pipeline.html.haml app/views/projects/commit/_pipeline.html.haml app/views/projects/pipelines/_with_tabs.html.haml app/views/projects/pipelines/index.html.haml lib/api/helpers.rb
This commit is contained in:
commit
0f40ae5f18
|
|
@ -8,7 +8,8 @@
|
|||
"globals": {
|
||||
"_": false,
|
||||
"gl": false,
|
||||
"gon": false
|
||||
"gon": false,
|
||||
"localStorage": false
|
||||
},
|
||||
"plugins": [
|
||||
"filenames"
|
||||
|
|
|
|||
108
.gitlab-ci.yml
108
.gitlab-ci.yml
|
|
@ -30,7 +30,12 @@ stages:
|
|||
- post-test
|
||||
- pages
|
||||
|
||||
# Prepare and merge knapsack tests
|
||||
# Predefined scopes
|
||||
.dedicated-runner: &dedicated-runner
|
||||
tags:
|
||||
- gitlab-org
|
||||
- 2gb
|
||||
|
||||
.knapsack-state: &knapsack-state
|
||||
services: []
|
||||
variables:
|
||||
|
|
@ -45,47 +50,14 @@ stages:
|
|||
paths:
|
||||
- knapsack/
|
||||
|
||||
knapsack:
|
||||
<<: *knapsack-state
|
||||
stage: prepare
|
||||
script:
|
||||
- mkdir -p knapsack/
|
||||
- '[[ -f knapsack/rspec_report.json ]] || echo "{}" > knapsack/rspec_report.json'
|
||||
- '[[ -f knapsack/spinach_report.json ]] || echo "{}" > knapsack/spinach_report.json'
|
||||
|
||||
update-knapsack:
|
||||
<<: *knapsack-state
|
||||
stage: post-test
|
||||
script:
|
||||
- scripts/merge-reports knapsack/rspec_report.json knapsack/rspec_node_*.json
|
||||
- scripts/merge-reports knapsack/spinach_report.json knapsack/spinach_node_*.json
|
||||
- rm -f knapsack/*_node_*.json
|
||||
only:
|
||||
- master@gitlab-org/gitlab-ce
|
||||
- master@gitlab-org/gitlab-ee
|
||||
- master@gitlab/gitlabhq
|
||||
- master@gitlab/gitlab-ee
|
||||
|
||||
.use-db: &use-db
|
||||
services:
|
||||
- mysql:latest
|
||||
- redis:alpine
|
||||
|
||||
setup-test-env:
|
||||
<<: *use-db
|
||||
stage: prepare
|
||||
script:
|
||||
- bundle exec rake assets:precompile 2>/dev/null
|
||||
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
|
||||
artifacts:
|
||||
expire_in: 7d
|
||||
paths:
|
||||
- public/assets
|
||||
- tmp/tests
|
||||
|
||||
|
||||
.rspec-knapsack: &rspec-knapsack
|
||||
stage: test
|
||||
<<: *dedicated-runner
|
||||
<<: *use-db
|
||||
script:
|
||||
- JOB_NAME=( $CI_BUILD_NAME )
|
||||
|
|
@ -103,6 +75,7 @@ setup-test-env:
|
|||
|
||||
.spinach-knapsack: &spinach-knapsack
|
||||
stage: test
|
||||
<<: *dedicated-runner
|
||||
<<: *use-db
|
||||
script:
|
||||
- JOB_NAME=( $CI_BUILD_NAME )
|
||||
|
|
@ -118,6 +91,44 @@ setup-test-env:
|
|||
- knapsack/
|
||||
- coverage/
|
||||
|
||||
# Prepare and merge knapsack tests
|
||||
|
||||
knapsack:
|
||||
<<: *knapsack-state
|
||||
<<: *dedicated-runner
|
||||
stage: prepare
|
||||
script:
|
||||
- mkdir -p knapsack/
|
||||
- '[[ -f knapsack/rspec_report.json ]] || echo "{}" > knapsack/rspec_report.json'
|
||||
- '[[ -f knapsack/spinach_report.json ]] || echo "{}" > knapsack/spinach_report.json'
|
||||
|
||||
setup-test-env:
|
||||
<<: *use-db
|
||||
<<: *dedicated-runner
|
||||
stage: prepare
|
||||
script:
|
||||
- bundle exec rake assets:precompile 2>/dev/null
|
||||
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
|
||||
artifacts:
|
||||
expire_in: 7d
|
||||
paths:
|
||||
- public/assets
|
||||
- tmp/tests
|
||||
|
||||
update-knapsack:
|
||||
<<: *knapsack-state
|
||||
<<: *dedicated-runner
|
||||
stage: post-test
|
||||
script:
|
||||
- scripts/merge-reports knapsack/rspec_report.json knapsack/rspec_node_*.json
|
||||
- scripts/merge-reports knapsack/spinach_report.json knapsack/spinach_node_*.json
|
||||
- rm -f knapsack/*_node_*.json
|
||||
only:
|
||||
- master@gitlab-org/gitlab-ce
|
||||
- master@gitlab-org/gitlab-ee
|
||||
- master@gitlab/gitlabhq
|
||||
- master@gitlab/gitlab-ee
|
||||
|
||||
rspec 0 20: *rspec-knapsack
|
||||
rspec 1 20: *rspec-knapsack
|
||||
rspec 2 20: *rspec-knapsack
|
||||
|
|
@ -166,10 +177,12 @@ spinach 9 10: *spinach-knapsack
|
|||
|
||||
.rspec-knapsack-ruby21: &rspec-knapsack-ruby21
|
||||
<<: *rspec-knapsack
|
||||
<<: *dedicated-runner
|
||||
<<: *ruby-21
|
||||
|
||||
.spinach-knapsack-ruby21: &spinach-knapsack-ruby21
|
||||
<<: *spinach-knapsack
|
||||
<<: *dedicated-runner
|
||||
<<: *ruby-21
|
||||
|
||||
rspec 0 20 ruby21: *rspec-knapsack-ruby21
|
||||
|
|
@ -214,6 +227,7 @@ spinach 9 10 ruby21: *spinach-knapsack-ruby21
|
|||
|
||||
.exec: &exec
|
||||
<<: *ruby-static-analysis
|
||||
<<: *dedicated-runner
|
||||
stage: test
|
||||
script:
|
||||
- bundle exec $CI_BUILD_NAME
|
||||
|
|
@ -249,12 +263,14 @@ rake ee_compat_check:
|
|||
rake db:migrate:reset:
|
||||
stage: test
|
||||
<<: *use-db
|
||||
<<: *dedicated-runner
|
||||
script:
|
||||
- rake db:migrate:reset
|
||||
|
||||
rake db:seed_fu:
|
||||
stage: test
|
||||
<<: *use-db
|
||||
<<: *dedicated-runner
|
||||
variables:
|
||||
SIZE: "1"
|
||||
SETUP_DB: "false"
|
||||
|
|
@ -276,6 +292,7 @@ teaspoon:
|
|||
- node_modules/
|
||||
stage: test
|
||||
<<: *use-db
|
||||
<<: *dedicated-runner
|
||||
script:
|
||||
- npm install
|
||||
- npm link istanbul
|
||||
|
|
@ -288,20 +305,23 @@ teaspoon:
|
|||
|
||||
lint-doc:
|
||||
stage: test
|
||||
<<: *dedicated-runner
|
||||
image: "phusion/baseimage:latest"
|
||||
before_script: []
|
||||
script:
|
||||
- scripts/lint-doc.sh
|
||||
|
||||
bundler:check:
|
||||
stage: test
|
||||
<<: *ruby-static-analysis
|
||||
script:
|
||||
stage: test
|
||||
<<: *dedicated-runner
|
||||
<<: *ruby-static-analysis
|
||||
script:
|
||||
- bundle check
|
||||
|
||||
bundler:audit:
|
||||
stage: test
|
||||
<<: *ruby-static-analysis
|
||||
<<: *dedicated-runner
|
||||
only:
|
||||
- master@gitlab-org/gitlab-ce
|
||||
- master@gitlab-org/gitlab-ee
|
||||
|
|
@ -313,6 +333,7 @@ bundler:audit:
|
|||
migration paths:
|
||||
stage: test
|
||||
<<: *use-db
|
||||
<<: *dedicated-runner
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
only:
|
||||
|
|
@ -334,6 +355,7 @@ migration paths:
|
|||
coverage:
|
||||
stage: post-test
|
||||
services: []
|
||||
<<: *dedicated-runner
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
USE_BUNDLE_INSTALL: "true"
|
||||
|
|
@ -347,6 +369,7 @@ coverage:
|
|||
- coverage/assets/
|
||||
|
||||
lint:javascript:
|
||||
<<: *dedicated-runner
|
||||
cache:
|
||||
paths:
|
||||
- node_modules/
|
||||
|
|
@ -358,6 +381,7 @@ lint:javascript:
|
|||
- npm --silent run eslint
|
||||
|
||||
lint:javascript:report:
|
||||
<<: *dedicated-runner
|
||||
cache:
|
||||
paths:
|
||||
- node_modules/
|
||||
|
|
@ -379,6 +403,7 @@ lint:javascript:report:
|
|||
trigger_docs:
|
||||
stage: post-test
|
||||
image: "alpine"
|
||||
<<: *dedicated-runner
|
||||
before_script:
|
||||
- apk update && apk add curl
|
||||
variables:
|
||||
|
|
@ -394,6 +419,7 @@ trigger_docs:
|
|||
|
||||
notify:slack:
|
||||
stage: post-test
|
||||
<<: *dedicated-runner
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
USE_BUNDLE_INSTALL: "false"
|
||||
|
|
@ -409,6 +435,7 @@ notify:slack:
|
|||
pages:
|
||||
before_script: []
|
||||
stage: pages
|
||||
<<: *dedicated-runner
|
||||
dependencies:
|
||||
- coverage
|
||||
- teaspoon
|
||||
|
|
@ -423,11 +450,12 @@ pages:
|
|||
paths:
|
||||
- public
|
||||
only:
|
||||
- master
|
||||
- master@gitlab-org/gitlab-ce
|
||||
|
||||
# Insurance in case a gem needed by one of our releases gets yanked from
|
||||
# rubygems.org in the future.
|
||||
cache gems:
|
||||
<<: *dedicated-runner
|
||||
only:
|
||||
- tags
|
||||
variables:
|
||||
|
|
@ -437,3 +465,5 @@ cache gems:
|
|||
artifacts:
|
||||
paths:
|
||||
- vendor/cache
|
||||
only:
|
||||
- master@gitlab-org/gitlab-ce
|
||||
|
|
|
|||
33
CHANGELOG.md
33
CHANGELOG.md
|
|
@ -2,6 +2,19 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 8.14.4 (2016-12-08)
|
||||
|
||||
- Fix diff view permalink highlighting. !7090
|
||||
- Fix pipeline author for Slack and use pipeline id for pipeline link. !7506
|
||||
- Fix compatibility with Internet Explorer 11 for merge requests. !7525 (Steffen Rauh)
|
||||
- Reenables /user API request to return private-token if user is admin and request is made with sudo. !7615
|
||||
- Fix Cicking on tabs on pipeline page should set URL. !7709
|
||||
- Authorize users into imported GitLab project.
|
||||
- Destroy a user's session when they delete their own account.
|
||||
- Don't accidentally mark unsafe diff lines as HTML safe.
|
||||
- Replace MR access checks with use of MergeRequestsFinder.
|
||||
- Remove visible content caching.
|
||||
|
||||
## 8.14.3 (2016-12-02)
|
||||
|
||||
- Pass commit data to ProcessCommitWorker to reduce Git overhead. !7744
|
||||
|
|
@ -251,6 +264,11 @@ entry.
|
|||
- Fix "Without projects" filter. !6611 (Ben Bodenmiller)
|
||||
- Fix 404 when visit /projects page
|
||||
|
||||
## 8.13.9 (2016-12-08)
|
||||
|
||||
- Reenables /user API request to return private-token if user is admin and request is made with sudo. !7615
|
||||
- Replace MR access checks with use of MergeRequestsFinder.
|
||||
|
||||
## 8.13.8 (2016-12-02)
|
||||
|
||||
- Pass tag SHA to post-receive hook when tag is created via UI. !7700
|
||||
|
|
@ -495,6 +513,21 @@ entry.
|
|||
- Fix broken Project API docs (Takuya Noguchi)
|
||||
- Migrate invalid project members (owner -> master)
|
||||
|
||||
## 8.12.12 (2016-12-08)
|
||||
|
||||
- Replace MR access checks with use of MergeRequestsFinder
|
||||
- Reenables /user API request to return private-token if user is admin and request is made with sudo
|
||||
|
||||
## 8.12.11 (2016-12-02)
|
||||
|
||||
- No changes
|
||||
|
||||
## 8.12.10 (2016-11-28)
|
||||
|
||||
- Fix information disclosure in `Projects::BlobController#update`
|
||||
- Fix missing access checks on issue lookup using IssuableFinder
|
||||
- Replace issue access checks with use of IssuableFinder
|
||||
|
||||
## 8.12.9 (2016-11-07)
|
||||
|
||||
- Fix XSS issue in Markdown autolinker
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
1.1.0
|
||||
1.1.1
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -271,7 +271,7 @@ group :development, :test do
|
|||
gem 'fuubar', '~> 2.0.0'
|
||||
|
||||
gem 'database_cleaner', '~> 1.5.0'
|
||||
gem 'factory_girl_rails', '~> 4.6.0'
|
||||
gem 'factory_girl_rails', '~> 4.7.0'
|
||||
gem 'rspec-rails', '~> 3.5.0'
|
||||
gem 'rspec-retry', '~> 0.4.5'
|
||||
gem 'spinach-rails', '~> 0.2.1'
|
||||
|
|
|
|||
|
|
@ -177,10 +177,10 @@ GEM
|
|||
excon (0.52.0)
|
||||
execjs (2.6.0)
|
||||
expression_parser (0.9.0)
|
||||
factory_girl (4.5.0)
|
||||
factory_girl (4.7.0)
|
||||
activesupport (>= 3.0.0)
|
||||
factory_girl_rails (4.6.0)
|
||||
factory_girl (~> 4.5.0)
|
||||
factory_girl_rails (4.7.0)
|
||||
factory_girl (~> 4.7.0)
|
||||
railties (>= 3.0.0)
|
||||
faraday (0.9.2)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
|
|
@ -819,7 +819,7 @@ DEPENDENCIES
|
|||
dropzonejs-rails (~> 0.7.1)
|
||||
email_reply_parser (~> 0.5.8)
|
||||
email_spec (~> 1.6.0)
|
||||
factory_girl_rails (~> 4.6.0)
|
||||
factory_girl_rails (~> 4.7.0)
|
||||
ffaker (~> 2.0.0)
|
||||
flay (~> 2.6.1)
|
||||
fog-aws (~> 0.9)
|
||||
|
|
|
|||
|
|
@ -70,6 +70,8 @@
|
|||
// e.g.
|
||||
// Api.gitignoreText item.name, @requestFileSuccess.bind(@)
|
||||
requestFileSuccess(file, { skipFocus } = {}) {
|
||||
if (!file) return;
|
||||
|
||||
const oldValue = this.editor.getValue();
|
||||
let newValue = file.content;
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
switch (page) {
|
||||
case 'sessions:new':
|
||||
new UsernameValidator();
|
||||
new ActiveTabMemoizer();
|
||||
break;
|
||||
case 'projects:boards:show':
|
||||
case 'projects:boards:index':
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@
|
|||
projectStoppedEnvironmentsPath: environmentsData.projectStoppedEnvironmentsPath,
|
||||
newEnvironmentPath: environmentsData.newEnvironmentPath,
|
||||
helpPagePath: environmentsData.helpPagePath,
|
||||
commitIconSvg: environmentsData.commitIconSvg,
|
||||
playIconSvg: environmentsData.playIconSvg,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -227,7 +229,9 @@
|
|||
:model="model"
|
||||
:toggleRow="toggleRow.bind(model)"
|
||||
:can-create-deployment="canCreateDeploymentParsed"
|
||||
:can-read-environment="canReadEnvironmentParsed"></tr>
|
||||
:can-read-environment="canReadEnvironmentParsed"
|
||||
:play-icon-svg="playIconSvg"
|
||||
:commit-icon-svg="commitIconSvg"></tr>
|
||||
|
||||
<tr v-if="model.isOpen && model.children && model.children.length > 0"
|
||||
is="environment-item"
|
||||
|
|
@ -235,7 +239,9 @@
|
|||
:model="children"
|
||||
:toggleRow="toggleRow.bind(children)"
|
||||
:can-create-deployment="canCreateDeploymentParsed"
|
||||
:can-read-environment="canReadEnvironmentParsed">
|
||||
:can-read-environment="canReadEnvironmentParsed"
|
||||
:play-icon-svg="playIconSvg"
|
||||
:commit-icon-svg="commitIconSvg">
|
||||
</tr>
|
||||
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -12,38 +12,18 @@
|
|||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Appends the svg icon that were render in the index page.
|
||||
* In order to reuse the svg instead of copy and paste in this template
|
||||
* we need to render it outside this component using =custom_icon partial.
|
||||
*
|
||||
* TODO: Remove this when webpack is merged.
|
||||
*
|
||||
*/
|
||||
mounted() {
|
||||
const playIcon = document.querySelector('.play-icon-svg.hidden svg');
|
||||
|
||||
const dropdownContainer = this.$el.querySelector('.dropdown-play-icon-container');
|
||||
const actionContainers = this.$el.querySelectorAll('.action-play-icon-container');
|
||||
// Phantomjs does not have support to iterate a nodelist.
|
||||
const actionsArray = [].slice.call(actionContainers);
|
||||
|
||||
if (playIcon && actionsArray && dropdownContainer) {
|
||||
dropdownContainer.appendChild(playIcon.cloneNode(true));
|
||||
|
||||
actionsArray.forEach((element) => {
|
||||
element.appendChild(playIcon.cloneNode(true));
|
||||
});
|
||||
}
|
||||
playIconSvg: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
|
||||
template: `
|
||||
<div class="inline">
|
||||
<div class="dropdown">
|
||||
<a class="dropdown-new btn btn-default" data-toggle="dropdown">
|
||||
<span class="dropdown-play-icon-container"></span>
|
||||
<span class="js-dropdown-play-icon-container" v-html="playIconSvg"></span>
|
||||
<i class="fa fa-caret-down"></i>
|
||||
</a>
|
||||
|
||||
|
|
@ -53,7 +33,9 @@
|
|||
data-method="post"
|
||||
rel="nofollow"
|
||||
class="js-manual-action-link">
|
||||
<span class="action-play-icon-container"></span>
|
||||
|
||||
<span class="js-action-play-icon-container" v-html="playIconSvg"></span>
|
||||
|
||||
<span>
|
||||
{{action.name}}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@
|
|||
|
||||
window.gl.environmentsList.ExternalUrlComponent = Vue.component('external-url-component', {
|
||||
props: {
|
||||
external_url: {
|
||||
externalUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
template: `
|
||||
<a class="btn external_url" :href="external_url" target="_blank">
|
||||
<a class="btn external_url" :href="externalUrl" target="_blank">
|
||||
<i class="fa fa-external-link"></i>
|
||||
</a>
|
||||
`,
|
||||
|
|
|
|||
|
|
@ -58,6 +58,16 @@
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
|
||||
commitIconSvg: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
|
||||
playIconSvg: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
|
|
@ -451,11 +461,12 @@
|
|||
<div v-if="!isFolder && hasLastDeploymentKey" class="js-commit-component">
|
||||
<commit-component
|
||||
:tag="commitTag"
|
||||
:commit_ref="commitRef"
|
||||
:commit_url="commitUrl"
|
||||
:short_sha="commitShortSha"
|
||||
:commit-ref="commitRef"
|
||||
:commit-url="commitUrl"
|
||||
:short-sha="commitShortSha"
|
||||
:title="commitTitle"
|
||||
:author="commitAuthor">
|
||||
:author="commitAuthor"
|
||||
:commit-icon-svg="commitIconSvg">
|
||||
</commit-component>
|
||||
</div>
|
||||
<p v-if="!isFolder && !hasLastDeploymentKey" class="commit-title">
|
||||
|
|
@ -476,6 +487,7 @@
|
|||
<div v-if="hasManualActions && canCreateDeployment"
|
||||
class="inline js-manual-actions-container">
|
||||
<actions-component
|
||||
:play-icon-svg="playIconSvg"
|
||||
:actions="manualActions">
|
||||
</actions-component>
|
||||
</div>
|
||||
|
|
@ -483,22 +495,22 @@
|
|||
<div v-if="model.external_url && canReadEnvironment"
|
||||
class="inline js-external-url-container">
|
||||
<external-url-component
|
||||
:external_url="model.external_url">
|
||||
</external_url-component>
|
||||
:external-url="model.external_url">
|
||||
</external-url-component>
|
||||
</div>
|
||||
|
||||
<div v-if="isStoppable && canCreateDeployment"
|
||||
class="inline js-stop-component-container">
|
||||
<stop-component
|
||||
:stop_url="model.stop_path">
|
||||
:stop-url="model.stop_path">
|
||||
</stop-component>
|
||||
</div>
|
||||
|
||||
<div v-if="canRetry && canCreateDeployment"
|
||||
class="inline js-rollback-component-container">
|
||||
<rollback-component
|
||||
:is_last_deployment="isLastDeployment"
|
||||
:retry_url="retryUrl">
|
||||
:is-last-deployment="isLastDeployment"
|
||||
:retry-url="retryUrl">
|
||||
</rollback-component>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,19 +7,20 @@
|
|||
|
||||
window.gl.environmentsList.RollbackComponent = Vue.component('rollback-component', {
|
||||
props: {
|
||||
retry_url: {
|
||||
retryUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
is_last_deployment: {
|
||||
|
||||
isLastDeployment: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
template: `
|
||||
<a class="btn" :href="retry_url" data-method="post" rel="nofollow">
|
||||
<span v-if="is_last_deployment">
|
||||
<a class="btn" :href="retryUrl" data-method="post" rel="nofollow">
|
||||
<span v-if="isLastDeployment">
|
||||
Re-deploy
|
||||
</span>
|
||||
<span v-else>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
window.gl.environmentsList.StopComponent = Vue.component('stop-component', {
|
||||
props: {
|
||||
stop_url: {
|
||||
stopUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
template: `
|
||||
<a class="btn stop-env-link"
|
||||
:href="stop_url"
|
||||
:href="stopUrl"
|
||||
data-confirm="Are you sure you want to stop this environment?"
|
||||
data-method="post"
|
||||
rel="nofollow">
|
||||
|
|
|
|||
|
|
@ -650,6 +650,11 @@
|
|||
} else if(value) {
|
||||
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']");
|
||||
}
|
||||
|
||||
if (this.options.isSelectable && !this.options.isSelectable(selectedObject, el)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (el.hasClass(ACTIVE_CLASS)) {
|
||||
el.removeClass(ACTIVE_CLASS);
|
||||
if (field && field.length) {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
setTimeago = true;
|
||||
}
|
||||
|
||||
$timeagoEls.each(function() {
|
||||
$timeagoEls.filter(':not([data-timeago-rendered])').each(function() {
|
||||
var $el = $(this);
|
||||
$el.attr('title', gl.utils.formatDate($el.attr('datetime')));
|
||||
|
||||
|
|
@ -39,6 +39,8 @@
|
|||
template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
|
||||
});
|
||||
}
|
||||
|
||||
$el.attr('data-timeago-rendered', true);
|
||||
gl.utils.renderTimeago($el);
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,38 +1,81 @@
|
|||
/* eslint-disable */
|
||||
((w) => {
|
||||
w.gl = w.gl || {};
|
||||
/* eslint-disable class-methods-use-this */
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
|
||||
class Members {
|
||||
constructor() {
|
||||
this.addListeners();
|
||||
this.initGLDropdown();
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
$('.project_member, .group_member').off('ajax:success').on('ajax:success', this.removeRow);
|
||||
$('.js-member-update-control').off('change').on('change', this.formSubmit);
|
||||
$('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess);
|
||||
$('.js-member-update-control').off('change').on('change', this.formSubmit.bind(this));
|
||||
$('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess.bind(this));
|
||||
gl.utils.disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change');
|
||||
}
|
||||
|
||||
initGLDropdown() {
|
||||
$('.js-member-permissions-dropdown').each((i, btn) => {
|
||||
const $btn = $(btn);
|
||||
|
||||
$btn.glDropdown({
|
||||
selectable: true,
|
||||
isSelectable(selected, $el) {
|
||||
return !$el.hasClass('is-active');
|
||||
},
|
||||
fieldName: $btn.data('field-name'),
|
||||
id(selected, $el) {
|
||||
return $el.data('id');
|
||||
},
|
||||
toggleLabel(selected, $el) {
|
||||
return $el.text();
|
||||
},
|
||||
clicked: (selected, $link) => {
|
||||
this.formSubmit(null, $link);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
removeRow(e) {
|
||||
const $target = $(e.target);
|
||||
|
||||
if ($target.hasClass('btn-remove')) {
|
||||
$target.closest('.member')
|
||||
.fadeOut(function () {
|
||||
.fadeOut(function fadeOutMemberRow() {
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
formSubmit() {
|
||||
$(this).closest('form').trigger("submit.rails").end().disable();
|
||||
formSubmit(e, $el = null) {
|
||||
const $this = e ? $(e.currentTarget) : $el;
|
||||
const { $toggle, $dateInput } = this.getMemberListItems($this);
|
||||
|
||||
$this.closest('form').trigger('submit.rails');
|
||||
|
||||
$toggle.disable();
|
||||
$dateInput.disable();
|
||||
}
|
||||
|
||||
formSuccess() {
|
||||
$(this).find('.js-member-update-control').enable();
|
||||
formSuccess(e) {
|
||||
const { $toggle, $dateInput } = this.getMemberListItems($(e.currentTarget).closest('.member'));
|
||||
|
||||
$toggle.enable();
|
||||
$dateInput.enable();
|
||||
}
|
||||
|
||||
getMemberListItems($el) {
|
||||
const $memberListItem = $el.is('.member') ? $el : $(`#${$el.data('el-id')}`);
|
||||
|
||||
return {
|
||||
$memberListItem,
|
||||
$toggle: $memberListItem.find('.dropdown-menu-toggle'),
|
||||
$dateInput: $memberListItem.find('.js-access-expiration-date'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
gl.Members = Members;
|
||||
})(window);
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
/* eslint no-param-reassign: ["error", { "props": false }]*/
|
||||
/* eslint no-new: "off" */
|
||||
((global) => {
|
||||
/**
|
||||
* Memorize the last selected tab after reloading a page.
|
||||
* Does that setting the current selected tab in the localStorage
|
||||
*/
|
||||
class ActiveTabMemoizer {
|
||||
constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
|
||||
this.currentTabKey = currentTabKey;
|
||||
this.tabSelector = tabSelector;
|
||||
this.bootstrap();
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
const tabs = document.querySelectorAll(this.tabSelector);
|
||||
if (tabs.length > 0) {
|
||||
tabs[0].addEventListener('click', (e) => {
|
||||
if (e.target && e.target.nodeName === 'A') {
|
||||
const anchorName = e.target.getAttribute('href');
|
||||
this.saveData(anchorName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.showTab();
|
||||
}
|
||||
|
||||
showTab() {
|
||||
const anchorName = this.readData();
|
||||
if (anchorName) {
|
||||
const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`);
|
||||
if (tab) {
|
||||
tab.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
saveData(val) {
|
||||
localStorage.setItem(this.currentTabKey, val);
|
||||
}
|
||||
|
||||
readData() {
|
||||
return localStorage.getItem(this.currentTabKey);
|
||||
}
|
||||
}
|
||||
|
||||
global.ActiveTabMemoizer = ActiveTabMemoizer;
|
||||
})(window);
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
* name
|
||||
* ref_url
|
||||
*/
|
||||
commit_ref: {
|
||||
commitRef: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
|
|
@ -32,16 +32,16 @@
|
|||
/**
|
||||
* Used to link to the commit sha.
|
||||
*/
|
||||
commit_url: {
|
||||
commitUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to show the commit short_sha that links to the commit url.
|
||||
* Used to show the commit short sha that links to the commit url.
|
||||
*/
|
||||
short_sha: {
|
||||
shortSha: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
|
|
@ -68,6 +68,11 @@
|
|||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
|
||||
commitIconSvg: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
|
@ -80,7 +85,7 @@
|
|||
* @returns {Boolean}
|
||||
*/
|
||||
hasCommitRef() {
|
||||
return this.commit_ref && this.commit_ref.name && this.commit_ref.ref_url;
|
||||
return this.commitRef && this.commitRef.name && this.commitRef.ref_url;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -110,24 +115,6 @@
|
|||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* In order to reuse the svg instead of copy and paste in this template
|
||||
* we need to render it outside this component using =custom_icon partial.
|
||||
* Make sure it has this structure:
|
||||
* .commit-icon-svg.hidden
|
||||
* svg
|
||||
*
|
||||
* TODO: Find a better way to include SVG
|
||||
*/
|
||||
mounted() {
|
||||
const commitIconContainer = this.$el.querySelector('.commit-icon-container');
|
||||
const commitIcon = document.querySelector('.commit-icon-svg.hidden svg');
|
||||
|
||||
if (commitIconContainer && commitIcon) {
|
||||
commitIconContainer.appendChild(commitIcon.cloneNode(true));
|
||||
}
|
||||
},
|
||||
|
||||
template: `
|
||||
<div class="branch-commit">
|
||||
|
||||
|
|
@ -138,15 +125,15 @@
|
|||
|
||||
<a v-if="hasCommitRef"
|
||||
class="monospace branch-name"
|
||||
:href="commit_ref.ref_url">
|
||||
{{commit_ref.name}}
|
||||
:href="commitRef.ref_url">
|
||||
{{commitRef.name}}
|
||||
</a>
|
||||
|
||||
<div class="icon-container commit-icon commit-icon-container"></div>
|
||||
<div v-html="commitIconSvg" class="commit-icon js-commit-icon"></div>
|
||||
|
||||
<a class="commit-id monospace"
|
||||
:href="commit_url">
|
||||
{{short_sha}}
|
||||
:href="commitUrl">
|
||||
{{shortSha}}
|
||||
</a>
|
||||
|
||||
<p class="commit-title">
|
||||
|
|
@ -162,7 +149,7 @@
|
|||
</a>
|
||||
|
||||
<a class="commit-row-message"
|
||||
:href="commit_url">
|
||||
:href="commitUrl">
|
||||
{{title}}
|
||||
</a>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
@import "framework/animations.scss";
|
||||
@import "framework/avatar.scss";
|
||||
@import "framework/asciidoctor.scss";
|
||||
@import "framework/blocks.scss";
|
||||
@import "framework/buttons.scss";
|
||||
@import "framework/calendar.scss";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
.admonitionblock td.icon {
|
||||
width: 1%;
|
||||
|
||||
[class^="fa icon-"] {
|
||||
@extend .fa-2x;
|
||||
}
|
||||
|
||||
.icon-note {
|
||||
@extend .fa-thumb-tack;
|
||||
}
|
||||
|
||||
.icon-tip {
|
||||
@extend .fa-lightbulb-o;
|
||||
}
|
||||
|
||||
.icon-warning {
|
||||
@extend .fa-exclamation-triangle;
|
||||
}
|
||||
|
||||
.icon-caution {
|
||||
@extend .fa-fire;
|
||||
}
|
||||
|
||||
.icon-important {
|
||||
@extend .fa-exclamation-circle;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
.awards {
|
||||
.emoji-icon {
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,5 +136,6 @@
|
|||
|
||||
.award-control-icon {
|
||||
color: $award-emoji-new-btn-icon-color;
|
||||
margin-top: 1px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
@mixin btn-default {
|
||||
border-radius: 3px;
|
||||
font-size: $gl-font-size;
|
||||
font-weight: 500;
|
||||
font-weight: 400;
|
||||
padding: $gl-vert-padding $gl-btn-padding;
|
||||
|
||||
&:focus,
|
||||
|
|
|
|||
|
|
@ -255,6 +255,7 @@ img.emoji {
|
|||
height: 20px;
|
||||
vertical-align: top;
|
||||
width: 20px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.chart {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,11 @@
|
|||
border-radius: $border-radius-base;
|
||||
white-space: nowrap;
|
||||
|
||||
&[disabled] {
|
||||
background-color: $input-bg-disabled;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&.no-outline {
|
||||
outline: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,10 @@
|
|||
border-bottom: 2px solid $link-underline-blue;
|
||||
color: $black;
|
||||
font-weight: 600;
|
||||
|
||||
.badge {
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
|
||||
.badge {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,10 @@
|
|||
@media (min-width: $screen-sm-min) {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.dropdown-menu-toggle {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.member-access-text {
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ ul.notes {
|
|||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(rgba($gray-light, 0.1) -100px, $white-light 100%);
|
||||
background: linear-gradient(rgba($white-light, 0.1) -100px, $white-light 100%);
|
||||
}
|
||||
|
||||
&.hide-shade {
|
||||
|
|
@ -413,7 +413,6 @@ ul.notes {
|
|||
.fa {
|
||||
color: $notes-action-color;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class Admin::GroupsController < Admin::ApplicationController
|
|||
private
|
||||
|
||||
def group
|
||||
@group ||= Group.find_by(path: params[:id])
|
||||
@group ||= Group.find_by_full_path(params[:id])
|
||||
end
|
||||
|
||||
def group_params
|
||||
|
|
|
|||
|
|
@ -81,10 +81,8 @@ module CreatesCommit
|
|||
def merge_request_exists?
|
||||
return @merge_request if defined?(@merge_request)
|
||||
|
||||
@merge_request = @mr_target_project.merge_requests.opened.find_by(
|
||||
source_branch: @mr_source_branch,
|
||||
target_branch: @mr_target_branch
|
||||
)
|
||||
@merge_request = MergeRequestsFinder.new(current_user, project_id: @mr_target_project.id).execute.opened.
|
||||
find_by(source_branch: @mr_source_branch, target_branch: @mr_target_branch)
|
||||
end
|
||||
|
||||
def different_project?
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class Groups::ApplicationController < ApplicationController
|
|||
def group
|
||||
unless @group
|
||||
id = params[:group_id] || params[:id]
|
||||
@group = Group.find_by(path: id)
|
||||
@group = Group.find_by_full_path(id)
|
||||
|
||||
unless @group && can?(current_user, :read_group, @group)
|
||||
@group = nil
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
|
||||
return render_404 if @target_branch.blank?
|
||||
|
||||
create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title} has been successfully reverted.",
|
||||
create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully reverted.",
|
||||
success_path: successful_change_path, failure_path: failed_change_path)
|
||||
end
|
||||
|
||||
|
|
@ -74,26 +74,24 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
|
||||
return render_404 if @target_branch.blank?
|
||||
|
||||
create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title} has been successfully cherry-picked.",
|
||||
create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully cherry-picked.",
|
||||
success_path: successful_change_path, failure_path: failed_change_path)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def successful_change_path
|
||||
return referenced_merge_request_url if @commit.merged_merge_request
|
||||
|
||||
namespace_project_commits_url(@project.namespace, @project, @target_branch)
|
||||
referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @target_branch)
|
||||
end
|
||||
|
||||
def failed_change_path
|
||||
return referenced_merge_request_url if @commit.merged_merge_request
|
||||
|
||||
namespace_project_commit_url(@project.namespace, @project, params[:id])
|
||||
referenced_merge_request_url || namespace_project_commit_url(@project.namespace, @project, params[:id])
|
||||
end
|
||||
|
||||
def referenced_merge_request_url
|
||||
namespace_project_merge_request_url(@project.namespace, @project, @commit.merged_merge_request)
|
||||
if merge_request = @commit.merged_merge_request(current_user)
|
||||
namespace_project_merge_request_url(@project.namespace, @project, merge_request)
|
||||
end
|
||||
end
|
||||
|
||||
def commit
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class Projects::CommitsController < Projects::ApplicationController
|
|||
@note_counts = project.notes.where(commit_id: @commits.map(&:id)).
|
||||
group(:commit_id).count
|
||||
|
||||
@merge_request = @project.merge_requests.opened.
|
||||
@merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.
|
||||
find_by(source_project: @project, source_branch: @ref, target_branch: @repository.root_ref)
|
||||
|
||||
respond_to do |format|
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ class Projects::CompareController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def merge_request
|
||||
@merge_request ||= @project.merge_requests.opened.
|
||||
@merge_request ||= MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.
|
||||
find_by(source_project: @project, source_branch: @head_ref, target_branch: @start_ref)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class Projects::DiscussionsController < Projects::ApplicationController
|
|||
private
|
||||
|
||||
def merge_request
|
||||
@merge_request ||= @project.merge_requests.find_by!(iid: params[:merge_request_id])
|
||||
@merge_request ||= MergeRequestsFinder.new(current_user, project_id: @project.id).find_by!(iid: params[:merge_request_id])
|
||||
end
|
||||
|
||||
def discussion
|
||||
|
|
|
|||
|
|
@ -10,14 +10,37 @@ class Projects::ProjectMembersController < Projects::ApplicationController
|
|||
@project_members = @project.project_members
|
||||
@project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project)
|
||||
|
||||
group = @project.group
|
||||
|
||||
if group
|
||||
# We need `.where.not(user_id: nil)` here otherwise when a group has an
|
||||
# invitee, it would make the following query return 0 rows since a NULL
|
||||
# user_id would be present in the subquery
|
||||
# See http://stackoverflow.com/questions/129077/not-in-clause-and-null-values
|
||||
# FIXME: This whole logic should be moved to a finder!
|
||||
non_null_user_ids = @project_members.where.not(user_id: nil).select(:user_id)
|
||||
group_members = group.group_members.where.not(user_id: non_null_user_ids)
|
||||
group_members = group_members.non_invite unless can?(current_user, :admin_group, @group)
|
||||
end
|
||||
|
||||
if params[:search].present?
|
||||
users = @project.users.search(params[:search]).to_a
|
||||
@project_members = @project_members.where(user_id: users)
|
||||
user_ids = @project.users.search(params[:search]).select(:id)
|
||||
@project_members = @project_members.where(user_id: user_ids)
|
||||
|
||||
if group_members
|
||||
user_ids = group.users.search(params[:search]).select(:id)
|
||||
group_members = group_members.where(user_id: user_ids)
|
||||
end
|
||||
|
||||
@group_links = @project.project_group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
|
||||
end
|
||||
|
||||
@project_members = @project_members.order(access_level: :desc).page(params[:page])
|
||||
wheres = ["id IN (#{@project_members.select(:id).to_sql})"]
|
||||
wheres << "id IN (#{group_members.select(:id).to_sql})" if group_members
|
||||
|
||||
@project_members = Member.
|
||||
where(wheres.join(' OR ')).
|
||||
order(access_level: :desc).page(params[:page])
|
||||
|
||||
@requesters = AccessRequestsFinder.new(@project).execute(current_user)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class Projects::TodosController < Projects::ApplicationController
|
|||
when "issue"
|
||||
IssuesFinder.new(current_user, project_id: @project.id).find(params[:issuable_id])
|
||||
when "merge_request"
|
||||
@project.merge_requests.find(params[:issuable_id])
|
||||
MergeRequestsFinder.new(current_user, project_id: @project.id).find(params[:issuable_id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -27,7 +27,10 @@ class RegistrationsController < Devise::RegistrationsController
|
|||
DeleteUserService.new(current_user).execute(current_user)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to new_user_session_path, notice: "Account successfully removed." }
|
||||
format.html do
|
||||
session.try(:destroy)
|
||||
redirect_to new_user_session_path, notice: "Account successfully removed."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ class SessionsController < Devise::SessionsController
|
|||
resource.update_attributes(reset_password_token: nil,
|
||||
reset_password_sent_at: nil)
|
||||
end
|
||||
# hide the signed-in notification
|
||||
flash[:notice] = nil
|
||||
log_audit_event(current_user, with: authentication_method)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -77,6 +77,10 @@ class IssuableFinder
|
|||
counts
|
||||
end
|
||||
|
||||
def find_by!(*params)
|
||||
execute.find_by!(*params)
|
||||
end
|
||||
|
||||
def group
|
||||
return @group if defined?(@group)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class NotesFinder
|
|||
when "issue"
|
||||
IssuesFinder.new(current_user, project_id: project.id).find(target_id).notes.inc_author
|
||||
when "merge_request"
|
||||
project.merge_requests.find(target_id).mr_and_commit_notes.inc_author
|
||||
MergeRequestsFinder.new(current_user, project_id: project.id).find(target_id).mr_and_commit_notes.inc_author
|
||||
when "snippet", "project_snippet"
|
||||
project.snippets.find(target_id).notes
|
||||
else
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ module CommitsHelper
|
|||
def revert_commit_link(commit, continue_to_path, btn_class: nil, has_tooltip: true)
|
||||
return unless current_user
|
||||
|
||||
tooltip = "Revert this #{commit.change_type_title} in a new merge request" if has_tooltip
|
||||
tooltip = "Revert this #{commit.change_type_title(current_user)} in a new merge request" if has_tooltip
|
||||
|
||||
if can_collaborate_with_project?
|
||||
btn_class = "btn btn-warning btn-#{btn_class}" unless btn_class.nil?
|
||||
|
|
@ -154,7 +154,7 @@ module CommitsHelper
|
|||
def cherry_pick_commit_link(commit, continue_to_path, btn_class: nil, has_tooltip: true)
|
||||
return unless current_user
|
||||
|
||||
tooltip = "Cherry-pick this #{commit.change_type_title} in a new merge request"
|
||||
tooltip = "Cherry-pick this #{commit.change_type_title(current_user)} in a new merge request"
|
||||
|
||||
if can_collaborate_with_project?
|
||||
btn_class = "btn btn-default btn-#{btn_class}" unless btn_class.nil?
|
||||
|
|
|
|||
|
|
@ -55,7 +55,9 @@ module DiffHelper
|
|||
if line.blank?
|
||||
" ".html_safe
|
||||
else
|
||||
line.sub(/^[\-+ ]/, '').html_safe
|
||||
# We can't use `sub` because the HTML-safeness of `line` will not survive.
|
||||
line[0] = '' if line.start_with?('+', '-', ' ')
|
||||
line
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,12 @@ module EventsHelper
|
|||
@project.feature_available?(feature_key, current_user)
|
||||
end
|
||||
|
||||
def comments_visible?
|
||||
event_filter_visible(:repository) ||
|
||||
event_filter_visible(:merge_requests) ||
|
||||
event_filter_visible(:issues)
|
||||
end
|
||||
|
||||
def event_preposition(event)
|
||||
if event.push? || event.commented? || event.target
|
||||
"at"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ module GroupsHelper
|
|||
|
||||
def group_icon(group)
|
||||
if group.is_a?(String)
|
||||
group = Group.find_by(path: group)
|
||||
group = Group.find_by_full_path(group)
|
||||
end
|
||||
|
||||
group.try(:avatar_url) || image_path('no_group_avatar.png')
|
||||
|
|
|
|||
|
|
@ -100,34 +100,29 @@ module Ci
|
|||
where.not(duration: nil).sum(:duration)
|
||||
end
|
||||
|
||||
def stages_query
|
||||
statuses.group('stage').select(:stage)
|
||||
.order('max(stage_idx)')
|
||||
def stages_count
|
||||
statuses.select(:stage).distinct.count
|
||||
end
|
||||
|
||||
def stages_name
|
||||
statuses.order(:stage_idx).distinct.
|
||||
pluck(:stage, :stage_idx).map(&:first)
|
||||
end
|
||||
|
||||
def stages
|
||||
self.stages_query.pluck(:stage)
|
||||
end
|
||||
|
||||
def stages_with_statuses
|
||||
status_sql = statuses.latest.where('stage=sg.stage').status_sql
|
||||
|
||||
stages_with_statuses = CommitStatus.from(self.stages_query, :sg).
|
||||
stages_query = statuses.group('stage').select(:stage)
|
||||
.order('max(stage_idx)')
|
||||
|
||||
stages_with_statuses = CommitStatus.from(stages_query, :sg).
|
||||
pluck('sg.stage', status_sql)
|
||||
|
||||
stages_with_statuses.map do |stage|
|
||||
OpenStruct.new(
|
||||
name: stage.first,
|
||||
status: stage.last,
|
||||
pipeline: self
|
||||
)
|
||||
Ci::Stage.new(self, name: stage.first, status: stage.last)
|
||||
end
|
||||
end
|
||||
|
||||
def stages_with_latest_statuses
|
||||
statuses.latest.includes(project: :namespace).order(:stage_idx).group_by(&:stage)
|
||||
end
|
||||
|
||||
def artifacts
|
||||
builds.latest.with_artifacts_not_expired
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
module Ci
|
||||
# Currently this is artificial object, constructed dynamically
|
||||
# We should migrate this object to actual database record in the future
|
||||
class Stage
|
||||
include StaticModel
|
||||
|
||||
attr_reader :pipeline, :name
|
||||
|
||||
delegate :project, to: :pipeline
|
||||
|
||||
def initialize(pipeline, name:, status: nil)
|
||||
@pipeline = pipeline
|
||||
@name = name
|
||||
@status = status
|
||||
end
|
||||
|
||||
def to_param
|
||||
name
|
||||
end
|
||||
|
||||
def status
|
||||
@status ||= statuses.latest.status
|
||||
end
|
||||
|
||||
def detailed_status
|
||||
Gitlab::Ci::Status::Stage::Factory.new(self).fabricate!
|
||||
end
|
||||
|
||||
def statuses
|
||||
@statuses ||= pipeline.statuses.where(stage: name)
|
||||
end
|
||||
|
||||
def builds
|
||||
@builds ||= pipeline.builds.where(stage: name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -245,44 +245,47 @@ class Commit
|
|||
project.repository.next_branch("cherry-pick-#{short_id}", mild: true)
|
||||
end
|
||||
|
||||
def revert_description
|
||||
if merged_merge_request
|
||||
"This reverts merge request #{merged_merge_request.to_reference}"
|
||||
def revert_description(user)
|
||||
if merged_merge_request?(user)
|
||||
"This reverts merge request #{merged_merge_request(user).to_reference}"
|
||||
else
|
||||
"This reverts commit #{sha}"
|
||||
end
|
||||
end
|
||||
|
||||
def revert_message
|
||||
%Q{Revert "#{title.strip}"\n\n#{revert_description}}
|
||||
def revert_message(user)
|
||||
%Q{Revert "#{title.strip}"\n\n#{revert_description(user)}}
|
||||
end
|
||||
|
||||
def reverts_commit?(commit)
|
||||
description? && description.include?(commit.revert_description)
|
||||
def reverts_commit?(commit, user)
|
||||
description? && description.include?(commit.revert_description(user))
|
||||
end
|
||||
|
||||
def merge_commit?
|
||||
parents.size > 1
|
||||
end
|
||||
|
||||
def merged_merge_request
|
||||
return @merged_merge_request if defined?(@merged_merge_request)
|
||||
|
||||
@merged_merge_request = project.merge_requests.find_by(merge_commit_sha: id) if merge_commit?
|
||||
def merged_merge_request(current_user)
|
||||
# Memoize with per-user access check
|
||||
@merged_merge_request_hash ||= Hash.new do |hash, user|
|
||||
hash[user] = merged_merge_request_no_cache(user)
|
||||
end
|
||||
|
||||
@merged_merge_request_hash[current_user]
|
||||
end
|
||||
|
||||
def has_been_reverted?(current_user = nil, noteable = self)
|
||||
def has_been_reverted?(current_user, noteable = self)
|
||||
ext = all_references(current_user)
|
||||
|
||||
noteable.notes_with_associations.system.each do |note|
|
||||
note.all_references(current_user, extractor: ext)
|
||||
end
|
||||
|
||||
ext.commits.any? { |commit_ref| commit_ref.reverts_commit?(self) }
|
||||
ext.commits.any? { |commit_ref| commit_ref.reverts_commit?(self, current_user) }
|
||||
end
|
||||
|
||||
def change_type_title
|
||||
merged_merge_request ? 'merge request' : 'commit'
|
||||
def change_type_title(user)
|
||||
merged_merge_request?(user) ? 'merge request' : 'commit'
|
||||
end
|
||||
|
||||
# Get the URI type of the given path
|
||||
|
|
@ -350,4 +353,12 @@ class Commit
|
|||
|
||||
changes
|
||||
end
|
||||
|
||||
def merged_merge_request?(user)
|
||||
!!merged_merge_request(user)
|
||||
end
|
||||
|
||||
def merged_merge_request_no_cache(user)
|
||||
MergeRequestsFinder.new(user, project_id: project.id).find_by(merge_commit_sha: id) if merge_commit?
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -31,18 +31,13 @@ class CommitStatus < ActiveRecord::Base
|
|||
end
|
||||
|
||||
scope :exclude_ignored, -> do
|
||||
quoted_when = connection.quote_column_name('when')
|
||||
# We want to ignore failed_but_allowed jobs
|
||||
where("allow_failure = ? OR status IN (?)",
|
||||
false, all_state_names - [:failed, :canceled]).
|
||||
# We want to ignore skipped manual jobs
|
||||
where("#{quoted_when} <> ? OR status <> ?", 'manual', 'skipped').
|
||||
# We want to ignore skipped on_failure
|
||||
where("#{quoted_when} <> ? OR status <> ?", 'on_failure', 'skipped')
|
||||
false, all_state_names - [:failed, :canceled])
|
||||
end
|
||||
|
||||
scope :latest_ci_stages, -> { latest.ordered.includes(project: :namespace) }
|
||||
scope :retried_ci_stages, -> { retried.ordered.includes(project: :namespace) }
|
||||
scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) }
|
||||
scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) }
|
||||
|
||||
state_machine :status do
|
||||
event :enqueue do
|
||||
|
|
@ -117,11 +112,6 @@ class CommitStatus < ActiveRecord::Base
|
|||
name.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip
|
||||
end
|
||||
|
||||
def self.stages
|
||||
# We group by stage name, but order stages by theirs' index
|
||||
unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').select('sg.stage')
|
||||
end
|
||||
|
||||
def failed_but_allowed?
|
||||
allow_failure? && (failed? || canceled?)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ module HasStatus
|
|||
AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped]
|
||||
STARTED_STATUSES = %w[running success failed skipped]
|
||||
ACTIVE_STATUSES = %w[pending running]
|
||||
COMPLETED_STATUSES = %w[success failed canceled]
|
||||
COMPLETED_STATUSES = %w[success failed canceled skipped]
|
||||
ORDERED_STATUSES = %w[failed pending running canceled success skipped]
|
||||
|
||||
class_methods do
|
||||
|
|
@ -23,9 +23,10 @@ module HasStatus
|
|||
canceled = scope.canceled.select('count(*)').to_sql
|
||||
|
||||
"(CASE
|
||||
WHEN (#{builds})=(#{skipped}) THEN 'skipped'
|
||||
WHEN (#{builds})=(#{success}) THEN 'success'
|
||||
WHEN (#{builds})=(#{created}) THEN 'created'
|
||||
WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'skipped'
|
||||
WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success'
|
||||
WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled'
|
||||
WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending'
|
||||
WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running'
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
module Milestoneish
|
||||
def closed_items_count(user = nil)
|
||||
def closed_items_count(user)
|
||||
issues_visible_to_user(user).closed.size + merge_requests.closed_and_merged.size
|
||||
end
|
||||
|
||||
def total_items_count(user = nil)
|
||||
def total_items_count(user)
|
||||
issues_visible_to_user(user).size + merge_requests.size
|
||||
end
|
||||
|
||||
def complete?(user = nil)
|
||||
def complete?(user)
|
||||
total_items_count(user) > 0 && total_items_count(user) == closed_items_count(user)
|
||||
end
|
||||
|
||||
def percent_complete(user = nil)
|
||||
def percent_complete(user)
|
||||
((closed_items_count(user) * 100) / total_items_count(user)).abs
|
||||
rescue ZeroDivisionError
|
||||
0
|
||||
|
|
@ -29,7 +29,7 @@ module Milestoneish
|
|||
(Date.today - start_date).to_i
|
||||
end
|
||||
|
||||
def issues_visible_to_user(user = nil)
|
||||
def issues_visible_to_user(user)
|
||||
issues.visible_to_user(user)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
# Store object full path in separate table for easy lookup and uniq validation
|
||||
# Object must have path db field and respond to full_path and full_path_changed? methods.
|
||||
module Routable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_one :route, as: :source, autosave: true, dependent: :destroy
|
||||
|
||||
validates_associated :route
|
||||
|
||||
before_validation :update_route_path, if: :full_path_changed?
|
||||
end
|
||||
|
||||
class_methods do
|
||||
# Finds a single object by full path match in routes table.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# Klass.find_by_full_path('gitlab-org/gitlab-ce')
|
||||
#
|
||||
# Returns a single object, or nil.
|
||||
def find_by_full_path(path)
|
||||
# On MySQL we want to ensure the ORDER BY uses a case-sensitive match so
|
||||
# any literal matches come first, for this we have to use "BINARY".
|
||||
# Without this there's still no guarantee in what order MySQL will return
|
||||
# rows.
|
||||
binary = Gitlab::Database.mysql? ? 'BINARY' : ''
|
||||
|
||||
order_sql = "(CASE WHEN #{binary} routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)"
|
||||
|
||||
where_paths_in([path]).reorder(order_sql).take
|
||||
end
|
||||
|
||||
# Builds a relation to find multiple objects by their full paths.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# Klass.where_paths_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee})
|
||||
#
|
||||
# Returns an ActiveRecord::Relation.
|
||||
def where_paths_in(paths)
|
||||
wheres = []
|
||||
cast_lower = Gitlab::Database.postgresql?
|
||||
|
||||
paths.each do |path|
|
||||
path = connection.quote(path)
|
||||
where = "(routes.path = #{path})"
|
||||
|
||||
if cast_lower
|
||||
where = "(#{where} OR (LOWER(routes.path) = LOWER(#{path})))"
|
||||
end
|
||||
|
||||
wheres << where
|
||||
end
|
||||
|
||||
if wheres.empty?
|
||||
none
|
||||
else
|
||||
joins(:route).where(wheres.join(' OR '))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_route_path
|
||||
route || build_route(source: self)
|
||||
route.path = full_path
|
||||
end
|
||||
end
|
||||
|
|
@ -101,7 +101,9 @@ class MergeRequest < ActiveRecord::Base
|
|||
validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?]
|
||||
validate :validate_fork, unless: :closed_without_fork?
|
||||
|
||||
scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
|
||||
scope :by_source_or_target_branch, ->(branch_name) do
|
||||
where("source_branch = :branch OR target_branch = :branch", branch: branch_name)
|
||||
end
|
||||
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
|
||||
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
|
||||
scope :of_projects, ->(ids) { where(target_project_id: ids) }
|
||||
|
|
@ -805,7 +807,7 @@ class MergeRequest < ActiveRecord::Base
|
|||
@merge_commit ||= project.commit(merge_commit_sha) if merge_commit_sha
|
||||
end
|
||||
|
||||
def can_be_reverted?(current_user = nil)
|
||||
def can_be_reverted?(current_user)
|
||||
merge_commit && !merge_commit.has_been_reverted?(current_user, self)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,16 @@ class Namespace < ActiveRecord::Base
|
|||
include CacheMarkdownField
|
||||
include Sortable
|
||||
include Gitlab::ShellAdapter
|
||||
include Routable
|
||||
|
||||
cache_markdown_field :description, pipeline: :description
|
||||
|
||||
has_many :projects, dependent: :destroy
|
||||
belongs_to :owner, class_name: "User"
|
||||
|
||||
belongs_to :parent, class_name: "Namespace"
|
||||
has_many :children, class_name: "Namespace", foreign_key: :parent_id
|
||||
|
||||
validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
|
||||
validates :name,
|
||||
presence: true,
|
||||
|
|
@ -86,7 +90,7 @@ class Namespace < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def to_param
|
||||
path
|
||||
full_path
|
||||
end
|
||||
|
||||
def human_name
|
||||
|
|
@ -150,6 +154,14 @@ class Namespace < ActiveRecord::Base
|
|||
Gitlab.config.lfs.enabled
|
||||
end
|
||||
|
||||
def full_path
|
||||
if parent
|
||||
parent.full_path + '/' + path
|
||||
else
|
||||
path
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def repository_storage_paths
|
||||
|
|
@ -185,4 +197,8 @@ class Namespace < ActiveRecord::Base
|
|||
where(projects: { namespace_id: id }).
|
||||
find_each(&:refresh_members_authorized_projects)
|
||||
end
|
||||
|
||||
def full_path_changed?
|
||||
path_changed? || parent_id_changed?
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ class Project < ActiveRecord::Base
|
|||
include TokenAuthenticatable
|
||||
include ProjectFeaturesCompatibility
|
||||
include SelectForProjectAuthorization
|
||||
include Routable
|
||||
|
||||
extend Gitlab::ConfigHelper
|
||||
|
||||
|
|
@ -324,87 +325,6 @@ class Project < ActiveRecord::Base
|
|||
non_archived.where(table[:name].matches(pattern))
|
||||
end
|
||||
|
||||
# Finds a single project for the given path.
|
||||
#
|
||||
# path - The full project path (including namespace path).
|
||||
#
|
||||
# Returns a Project, or nil if no project could be found.
|
||||
def find_with_namespace(path)
|
||||
namespace_path, project_path = path.split('/', 2)
|
||||
|
||||
return unless namespace_path && project_path
|
||||
|
||||
namespace_path = connection.quote(namespace_path)
|
||||
project_path = connection.quote(project_path)
|
||||
|
||||
# On MySQL we want to ensure the ORDER BY uses a case-sensitive match so
|
||||
# any literal matches come first, for this we have to use "BINARY".
|
||||
# Without this there's still no guarantee in what order MySQL will return
|
||||
# rows.
|
||||
binary = Gitlab::Database.mysql? ? 'BINARY' : ''
|
||||
|
||||
order_sql = "(CASE WHEN #{binary} namespaces.path = #{namespace_path} " \
|
||||
"AND #{binary} projects.path = #{project_path} THEN 0 ELSE 1 END)"
|
||||
|
||||
where_paths_in([path]).reorder(order_sql).take
|
||||
end
|
||||
|
||||
# Builds a relation to find multiple projects by their full paths.
|
||||
#
|
||||
# Each path must be in the following format:
|
||||
#
|
||||
# namespace_path/project_path
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# gitlab-org/gitlab-ce
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# Project.where_paths_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee})
|
||||
#
|
||||
# This would return the projects with the full paths matching the values
|
||||
# given.
|
||||
#
|
||||
# paths - An Array of full paths (namespace path + project path) for which
|
||||
# to find the projects.
|
||||
#
|
||||
# Returns an ActiveRecord::Relation.
|
||||
def where_paths_in(paths)
|
||||
wheres = []
|
||||
cast_lower = Gitlab::Database.postgresql?
|
||||
|
||||
paths.each do |path|
|
||||
namespace_path, project_path = path.split('/', 2)
|
||||
|
||||
next unless namespace_path && project_path
|
||||
|
||||
namespace_path = connection.quote(namespace_path)
|
||||
project_path = connection.quote(project_path)
|
||||
|
||||
where = "(namespaces.path = #{namespace_path}
|
||||
AND projects.path = #{project_path})"
|
||||
|
||||
if cast_lower
|
||||
where = "(
|
||||
#{where}
|
||||
OR (
|
||||
LOWER(namespaces.path) = LOWER(#{namespace_path})
|
||||
AND LOWER(projects.path) = LOWER(#{project_path})
|
||||
)
|
||||
)"
|
||||
end
|
||||
|
||||
wheres << where
|
||||
end
|
||||
|
||||
if wheres.empty?
|
||||
none
|
||||
else
|
||||
joins(:namespace).where(wheres.join(' OR '))
|
||||
end
|
||||
end
|
||||
|
||||
def visibility_levels
|
||||
Gitlab::VisibilityLevel.options
|
||||
end
|
||||
|
|
@ -440,6 +360,10 @@ class Project < ActiveRecord::Base
|
|||
def group_ids
|
||||
joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)
|
||||
end
|
||||
|
||||
# Add alias for Routable method for compatibility with old code.
|
||||
# In future all calls `find_with_namespace` should be replaced with `find_by_full_path`
|
||||
alias_method :find_with_namespace, :find_by_full_path
|
||||
end
|
||||
|
||||
def lfs_enabled?
|
||||
|
|
@ -879,13 +803,14 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
alias_method :human_name, :name_with_namespace
|
||||
|
||||
def path_with_namespace
|
||||
if namespace
|
||||
namespace.path + '/' + path
|
||||
def full_path
|
||||
if namespace && path
|
||||
namespace.full_path + '/' + path
|
||||
else
|
||||
path
|
||||
end
|
||||
end
|
||||
alias_method :path_with_namespace, :full_path
|
||||
|
||||
def execute_hooks(data, hooks_scope = :push_hooks)
|
||||
hooks.send(hooks_scope).each do |hook|
|
||||
|
|
@ -1373,4 +1298,8 @@ class Project < ActiveRecord::Base
|
|||
def validate_board_limit(board)
|
||||
raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS
|
||||
end
|
||||
|
||||
def full_path_changed?
|
||||
path_changed? || namespace_id_changed?
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -950,7 +950,7 @@ class Repository
|
|||
update_branch_with_hooks(user, base_branch) do
|
||||
committer = user_to_committer(user)
|
||||
source_sha = Rugged::Commit.create(rugged,
|
||||
message: commit.revert_message,
|
||||
message: commit.revert_message(user),
|
||||
author: committer,
|
||||
committer: committer,
|
||||
tree: revert_tree_id,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
class Route < ActiveRecord::Base
|
||||
belongs_to :source, polymorphic: true
|
||||
|
||||
validates :source, presence: true
|
||||
|
||||
validates :path,
|
||||
length: { within: 1..255 },
|
||||
presence: true,
|
||||
uniqueness: { case_sensitive: false }
|
||||
|
||||
after_update :rename_children, if: :path_changed?
|
||||
|
||||
def rename_children
|
||||
# We update each row separately because MySQL does not have regexp_replace.
|
||||
# rubocop:disable Rails/FindEach
|
||||
Route.where('path LIKE ?', "#{path_was}%").each do |route|
|
||||
# Note that update column skips validation and callbacks.
|
||||
# We need this to avoid recursive call of rename_children method
|
||||
route.update_column(:path, route.path.sub(path_was, path))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -44,11 +44,11 @@ module Ci
|
|||
def valid_statuses_for_when(value)
|
||||
case value
|
||||
when 'on_success'
|
||||
%w[success]
|
||||
%w[success skipped]
|
||||
when 'on_failure'
|
||||
%w[failed]
|
||||
when 'always'
|
||||
%w[success failed]
|
||||
%w[success failed skipped]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ module Commits
|
|||
repository.public_send(action, current_user, @commit, into, tree_id)
|
||||
success
|
||||
else
|
||||
error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title} automatically.
|
||||
error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title(current_user)} automatically.
|
||||
It may have already been #{action.to_s.dasherize}, or a more recent commit may have updated some of its content."
|
||||
raise ChangeError, error_msg
|
||||
end
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ class DestroyGroupService
|
|||
::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
|
||||
end
|
||||
|
||||
group.children.each do |group|
|
||||
DestroyGroupService.new(group, current_user).async_execute
|
||||
end
|
||||
|
||||
group.really_destroy!
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -55,8 +55,9 @@ module MergeRequests
|
|||
# Refresh merge request diff if we push to source or target branch of merge request
|
||||
# Note: we should update merge requests from forks too
|
||||
def reload_merge_requests
|
||||
merge_requests = @project.merge_requests.opened.by_branch(@branch_name).to_a
|
||||
merge_requests += fork_merge_requests.by_branch(@branch_name).to_a
|
||||
merge_requests = @project.merge_requests.opened.
|
||||
by_source_or_target_branch(@branch_name).to_a
|
||||
merge_requests += fork_merge_requests
|
||||
merge_requests = filter_merge_requests(merge_requests)
|
||||
|
||||
merge_requests.each do |merge_request|
|
||||
|
|
@ -157,13 +158,14 @@ module MergeRequests
|
|||
def merge_requests_for_source_branch
|
||||
@source_merge_requests ||= begin
|
||||
merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a
|
||||
merge_requests += fork_merge_requests.where(source_branch: @branch_name).to_a
|
||||
merge_requests += fork_merge_requests
|
||||
filter_merge_requests(merge_requests)
|
||||
end
|
||||
end
|
||||
|
||||
def fork_merge_requests
|
||||
@fork_merge_requests ||= @project.fork_merge_requests.opened
|
||||
@fork_merge_requests ||= @project.fork_merge_requests.opened.
|
||||
where(source_branch: @branch_name).to_a
|
||||
end
|
||||
|
||||
def branch_added?
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Extra methods for uploader
|
||||
module UploaderHelper
|
||||
IMAGE_EXT = %w[png jpg jpeg gif bmp tiff]
|
||||
IMAGE_EXT = %w[png jpg jpeg gif bmp tiff svg]
|
||||
# We recommend using the .mp4 format over .mov. Videos in .mov format can
|
||||
# still be used but you really need to make sure they are served with the
|
||||
# proper MIME type video/mp4 and not video/quicktime or your videos won't play
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
- page_title "Sign in"
|
||||
|
||||
%div
|
||||
- if form_based_providers.any?
|
||||
= render 'devise/shared/tabs_ldap'
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
:plain
|
||||
var $listItem = $('#{escape_javascript(render('shared/members/member', member: @group_member))}');
|
||||
$("##{dom_id(@group_member)} .list-item-name").replaceWith($listItem.find('.list-item-name'));
|
||||
gl.utils.localTimeAgo($('.js-timeago'), $("##{dom_id(@group_member)}"));
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@
|
|||
= link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
|
||||
%span
|
||||
Merge Requests
|
||||
%span.badge.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count)
|
||||
%span.badge.count.merge_counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count)
|
||||
|
||||
- if project_nav_tab? :wiki
|
||||
= nav_link(controller: :wikis) do
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@
|
|||
%tr.success-message
|
||||
%td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;"}
|
||||
- build_count = @pipeline.statuses.latest.size
|
||||
- stage_count = @pipeline.stages.size
|
||||
- stage_count = @pipeline.stages_count
|
||||
Pipeline
|
||||
%a{href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;"}
|
||||
= "\##{@pipeline.id}"
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ Commit Author: <%= commit.author_name %>
|
|||
<% end -%>
|
||||
|
||||
<% build_count = @pipeline.statuses.latest.size -%>
|
||||
<% stage_count = @pipeline.stages.size -%>
|
||||
<% stage_count = @pipeline.stages_count -%>
|
||||
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>.
|
||||
|
||||
You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
- form = local_assigns.fetch(:form)
|
||||
|
||||
.form-group
|
||||
.checkbox.builds-feature
|
||||
= form.label :only_allow_merge_if_build_succeeds do
|
||||
= form.check_box :only_allow_merge_if_build_succeeds
|
||||
%strong Only allow merge requests to be merged if the build succeeds
|
||||
%br
|
||||
%span.descr
|
||||
Builds need to be configured to enable this feature.
|
||||
= link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
|
||||
.checkbox
|
||||
= form.label :only_allow_merge_if_all_discussions_are_resolved do
|
||||
= form.check_box :only_allow_merge_if_all_discussions_are_resolved
|
||||
%strong Only allow merge requests to be merged if all discussions are resolved
|
||||
|
|
@ -1,18 +1,8 @@
|
|||
.merge-requests-feature
|
||||
%fieldset.builds-feature
|
||||
%hr
|
||||
%h5.prepend-top-0
|
||||
Merge Requests
|
||||
.form-group
|
||||
.checkbox
|
||||
= f.label :only_allow_merge_if_build_succeeds do
|
||||
= f.check_box :only_allow_merge_if_build_succeeds
|
||||
%strong Only allow merge requests to be merged if the build succeeds
|
||||
%br
|
||||
%span.descr
|
||||
Builds need to be configured to enable this feature.
|
||||
= link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
|
||||
.checkbox
|
||||
= f.label :only_allow_merge_if_all_discussions_are_resolved do
|
||||
= f.check_box :only_allow_merge_if_all_discussions_are_resolved
|
||||
%strong Only allow merge requests to be merged if all discussions are resolved
|
||||
- form = local_assigns.fetch(:form)
|
||||
|
||||
%fieldset.features.merge-requests-feature.append-bottom-default
|
||||
%hr
|
||||
%h5.prepend-top-0
|
||||
Merge Requests
|
||||
|
||||
= render 'projects/merge_request_merge_settings', form: form
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@
|
|||
%span.label.label-primary
|
||||
= tag
|
||||
|
||||
- if @build.pipeline.stages.many?
|
||||
- if @build.pipeline.stages_count > 1
|
||||
.dropdown.build-dropdown
|
||||
.title Stage
|
||||
%button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'}
|
||||
|
|
@ -120,7 +120,7 @@
|
|||
%ul.dropdown-menu
|
||||
- @build.pipeline.stages.each do |stage|
|
||||
%li
|
||||
%a.stage-item= stage
|
||||
%a.stage-item= stage.name
|
||||
|
||||
.builds-container
|
||||
- HasStatus::ORDERED_STATUSES.each do |build_status|
|
||||
|
|
|
|||
|
|
@ -104,9 +104,9 @@
|
|||
= link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
|
||||
= icon('remove', class: 'cred')
|
||||
- elsif allow_retry
|
||||
- if build.retryable?
|
||||
= link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
|
||||
= icon('repeat')
|
||||
- elsif build.playable? && !admin
|
||||
- if build.playable? && !admin
|
||||
= link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
|
||||
= custom_icon('icon_play')
|
||||
- elsif build.retryable?
|
||||
= link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
|
||||
= icon('repeat')
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
Cant find HEAD commit for this branch
|
||||
|
||||
%td.stage-cell
|
||||
- pipeline.stages_with_statuses.each do |stage|
|
||||
- pipeline.stages.each do |stage|
|
||||
- if stage.status
|
||||
- tooltip = "#{stage.name.titleize}: #{stage.status || 'not found'}"
|
||||
.stage-container
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
.modal-content
|
||||
.modal-header
|
||||
%a.close{href: "#", "data-dismiss" => "modal"} ×
|
||||
%h3.page-title== #{label} this #{commit.change_type_title}
|
||||
%h3.page-title== #{label} this #{commit.change_type_title(current_user)}
|
||||
.modal-body
|
||||
= form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: 'form-horizontal js-#{type}-form js-requires-input' do
|
||||
.form-group.branch
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
%tr
|
||||
%th{colspan: 10}
|
||||
%strong
|
||||
%a{name: stage}
|
||||
- status = statuses.latest.status
|
||||
%span{class: "ci-status-link ci-status-icon-#{status}"}
|
||||
= ci_icon_for_status(status)
|
||||
- if stage
|
||||
|
||||
= stage.titleize
|
||||
= render statuses.latest_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, allow_retry: true
|
||||
= render statuses.retried_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, retried: true
|
||||
%tr
|
||||
%td{colspan: 10}
|
||||
|
||||
|
|
@ -24,20 +24,8 @@
|
|||
in
|
||||
= time_interval_in_words pipeline.duration
|
||||
|
||||
.row-content-block.build-content.middle-block.pipeline-graph.hidden
|
||||
.pipeline-visualization
|
||||
%ul.stage-column-list
|
||||
- stages = pipeline.stages_with_latest_statuses
|
||||
- stages.each do |stage, statuses|
|
||||
%li.stage-column
|
||||
.stage-name
|
||||
%a{name: stage}
|
||||
- if stage
|
||||
= stage.titleize
|
||||
.builds-container
|
||||
%ul
|
||||
= render "projects/commit/pipeline_stage", statuses: statuses
|
||||
|
||||
.row-content-block.build-content.middle-block.hidden
|
||||
= render "projects/pipelines/graph", pipeline: pipeline
|
||||
|
||||
- if pipeline.yaml_errors.present?
|
||||
.bs-callout.bs-callout-danger
|
||||
|
|
@ -62,5 +50,4 @@
|
|||
- if pipeline.project.build_coverage_enabled?
|
||||
%th Coverage
|
||||
%th
|
||||
- pipeline.stages.each do |stage|
|
||||
= render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.relevant.where(stage: stage)
|
||||
= render partial: "projects/stage/stage", collection: pipeline.stages, as: :stage
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
- status_groups = statuses.sort_by(&:name).group_by(&:group_name)
|
||||
- status_groups.each do |group_name, grouped_statuses|
|
||||
- if grouped_statuses.one?
|
||||
- status = grouped_statuses.first
|
||||
- is_playable = status.playable? && can?(current_user, :update_build, @project)
|
||||
%li.build{ class: ("playable" if is_playable) }
|
||||
.curve
|
||||
.build-content
|
||||
= render "projects/#{status.to_partial_path}_pipeline", subject: status
|
||||
- else
|
||||
%li.build
|
||||
.curve
|
||||
.dropdown.inline.build-content
|
||||
= render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses
|
||||
|
|
@ -112,7 +112,8 @@
|
|||
%span.descr Enable Container Registry for this project
|
||||
= link_to icon('question-circle'), help_page_path('user/project/container_registry'), target: '_blank'
|
||||
|
||||
= render 'merge_request_settings', f: f
|
||||
= render 'merge_request_settings', form: f
|
||||
|
||||
%hr
|
||||
%fieldset.features.append-bottom-default
|
||||
%h5.prepend-top-0
|
||||
|
|
|
|||
|
|
@ -17,4 +17,6 @@
|
|||
"project-stopped-environments-path" => project_environments_path(@project, scope: :stopped),
|
||||
"new-environment-path" => new_namespace_project_environment_path(@project.namespace, @project),
|
||||
"help-page-path" => help_page_path("ci/environments"),
|
||||
"css-class" => container_class}}
|
||||
"css-class" => container_class,
|
||||
"commit-icon-svg" => custom_icon("icon_commit"),
|
||||
"play-icon-svg" => custom_icon("icon_play")}}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,11 @@
|
|||
- if @forked_project && @forked_project.errors.any?
|
||||
%p
|
||||
–
|
||||
= @forked_project.errors.full_messages.first
|
||||
- error = @forked_project.errors.full_messages.first
|
||||
- if error.include?("already been taken")
|
||||
Name has already been taken
|
||||
- else
|
||||
= error
|
||||
|
||||
%p
|
||||
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork", class: "btn" do
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
:plain
|
||||
var $listItem = $('#{escape_javascript(render('shared/members/group', group_link: @group_link))}');
|
||||
$("#group_member_#{@group_link.id} .list-item-name").replaceWith($listItem.find('.list-item-name'));
|
||||
gl.utils.localTimeAgo($('.js-timeago'), $("#group_member_#{@group_link.id}"));
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
%span.label-branch= source_branch_with_namespace(@merge_request)
|
||||
%span into
|
||||
%span.label-branch
|
||||
= link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch)
|
||||
= link_to_if @merge_request.target_branch_exists?, @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch)
|
||||
- if @merge_request.open? && @merge_request.diverged_from_target_branch?
|
||||
%span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
- pipeline = local_assigns.fetch(:pipeline)
|
||||
.pipeline-visualization.pipeline-graph
|
||||
%ul.stage-column-list
|
||||
= render partial: "projects/stage/graph", collection: pipeline.stages, as: :stage
|
||||
|
|
@ -12,19 +12,8 @@
|
|||
|
||||
.tab-content
|
||||
#js-tab-pipeline.tab-pane
|
||||
.build-content.middle-block.pipeline-graph
|
||||
.pipeline-visualization
|
||||
%ul.stage-column-list
|
||||
- stages = pipeline.stages_with_latest_statuses
|
||||
- stages.each do |stage, statuses|
|
||||
%li.stage-column
|
||||
.stage-name
|
||||
%a{name: stage}
|
||||
- if stage
|
||||
= stage.titleize
|
||||
.builds-container
|
||||
%ul
|
||||
= render "projects/commit/pipeline_stage", statuses: statuses
|
||||
.build-content.middle-block
|
||||
= render "projects/pipelines/graph", pipeline: pipeline
|
||||
|
||||
#js-tab-builds.tab-pane
|
||||
- if pipeline.yaml_errors.present?
|
||||
|
|
@ -50,5 +39,4 @@
|
|||
- if pipeline.project.build_coverage_enabled?
|
||||
%th Coverage
|
||||
%th
|
||||
- pipeline.stages.each do |stage|
|
||||
= render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.relevant.where(stage: stage)
|
||||
= render partial: "projects/stage/stage", collection: pipeline.stages, as: :stage
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
:plain
|
||||
var $listItem = $('#{escape_javascript(render('shared/members/member', member: @project_member))}');
|
||||
$("##{dom_id(@project_member)} .list-item-name").replaceWith($listItem.find('.list-item-name'));
|
||||
gl.utils.localTimeAgo($('.js-timeago'), $("##{dom_id(@project_member)}"));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
- stage = local_assigns.fetch(:stage)
|
||||
- statuses = stage.statuses.latest
|
||||
- status_groups = statuses.sort_by(&:name).group_by(&:group_name)
|
||||
%li.stage-column
|
||||
.stage-name
|
||||
%a{ name: stage.name }
|
||||
= stage.name.titleize
|
||||
.builds-container
|
||||
%ul
|
||||
- status_groups.each do |group_name, grouped_statuses|
|
||||
- if grouped_statuses.one?
|
||||
- status = grouped_statuses.first
|
||||
- is_playable = status.playable? && can?(current_user, :update_build, @project)
|
||||
%li.build{ class: ("playable" if is_playable) }
|
||||
.curve
|
||||
.build-content
|
||||
= render "projects/#{status.to_partial_path}_pipeline", subject: status
|
||||
- else
|
||||
%li.build
|
||||
.curve
|
||||
.dropdown.inline.build-content
|
||||
= render "projects/stage/in_stage_group", name: group_name, subject: grouped_statuses
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
%tr
|
||||
%th{colspan: 10}
|
||||
%strong
|
||||
%a{ name: stage.name }
|
||||
%span{class: "ci-status-link ci-status-icon-#{stage.status}"}
|
||||
= ci_icon_for_status(stage.status)
|
||||
|
||||
= stage.name.titleize
|
||||
= render stage.statuses.latest_ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, allow_retry: true
|
||||
= render stage.statuses.retried_ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, retried: true
|
||||
%tr
|
||||
%td{colspan: 10}
|
||||
|
||||
|
|
@ -5,5 +5,7 @@
|
|||
- if event_filter_visible(:merge_requests)
|
||||
= event_filter_link EventFilter.merged, 'Merge events'
|
||||
- if event_filter_visible(:issues)
|
||||
= event_filter_link EventFilter.issue, 'Issue events'
|
||||
- if comments_visible?
|
||||
= event_filter_link EventFilter.comments, 'Comments'
|
||||
= event_filter_link EventFilter.team, 'Team'
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
- group_link = local_assigns[:group_link]
|
||||
- group = group_link.group
|
||||
- can_admin_member = can?(current_user, :admin_project_member, @project)
|
||||
%li.member.group_member{ id: "group_member_#{group_link.id}" }
|
||||
- dom_id = "group_member_#{group_link.id}"
|
||||
%li.member.group_member{ id: dom_id }
|
||||
%span{ class: "list-item-name" }
|
||||
= image_tag group_icon(group), class: "avatar s40", alt: ''
|
||||
%strong
|
||||
|
|
@ -14,7 +15,23 @@
|
|||
Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)}
|
||||
.controls.member-controls
|
||||
= form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do
|
||||
= select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can_admin_member
|
||||
= hidden_field_tag "group_link[group_access]", group_link.group_access
|
||||
.member-form-control.dropdown.append-right-5
|
||||
%button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button",
|
||||
disabled: !can_admin_member,
|
||||
data: { toggle: "dropdown", field_name: "group_link[group_access]" } }
|
||||
%span.dropdown-toggle-text
|
||||
= group_link.human_access
|
||||
= icon("chevron-down")
|
||||
.dropdown-menu.dropdown-select.dropdown-menu-align-right.dropdown-menu-selectable
|
||||
= dropdown_title("Change permissions")
|
||||
.dropdown-content
|
||||
%ul
|
||||
- Gitlab::Access.options.each do |role, role_id|
|
||||
%li
|
||||
= link_to role, "javascript:void(0)",
|
||||
class: ("is-active" if group_link.group_access == role_id),
|
||||
data: { id: role_id, el_id: dom_id }
|
||||
.prepend-left-5.clearable-input.member-form-control
|
||||
= text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can_admin_member
|
||||
%i.clear-icon.js-clear-input
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@
|
|||
%strong Blocked
|
||||
|
||||
- if source.instance_of?(Group) && !@group
|
||||
= link_to source, class: "member-group-link prepend-left-5" do
|
||||
= "· #{source.name}"
|
||||
·
|
||||
= link_to source.name, source, class: "member-group-link"
|
||||
|
||||
.hidden-xs.cgray
|
||||
- if member.request?
|
||||
|
|
@ -45,12 +45,28 @@
|
|||
= time_ago_with_tooltip(member.created_at)
|
||||
- if show_roles
|
||||
.controls.member-controls
|
||||
- if show_controls
|
||||
- if show_controls && (member.respond_to?(:group) && @group) || (member.respond_to?(:project) && @project)
|
||||
- if user != current_user
|
||||
= form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f|
|
||||
= f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can_admin_member
|
||||
= f.hidden_field :access_level
|
||||
.member-form-control.dropdown.append-right-5
|
||||
%button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button",
|
||||
disabled: !can_admin_member,
|
||||
data: { toggle: "dropdown", field_name: "#{f.object_name}[access_level]" } }
|
||||
%span.dropdown-toggle-text
|
||||
= member.human_access
|
||||
= icon("chevron-down")
|
||||
.dropdown-menu.dropdown-select.dropdown-menu-align-right.dropdown-menu-selectable
|
||||
= dropdown_title("Change permissions")
|
||||
.dropdown-content
|
||||
%ul
|
||||
- Gitlab::Access.options.each do |role, role_id|
|
||||
%li
|
||||
= link_to role, "javascript:void(0)",
|
||||
class: ("is-active" if member.access_level == role_id),
|
||||
data: { id: role_id, el_id: dom_id(member) }
|
||||
.prepend-left-5.clearable-input.member-form-control
|
||||
= f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can_admin_member
|
||||
= f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can_admin_member, data: { el_id: dom_id(member) }
|
||||
%i.clear-icon.js-clear-input
|
||||
- else
|
||||
%span.member-access-text= member.human_access
|
||||
|
|
|
|||
|
|
@ -25,8 +25,10 @@
|
|||
%span.milestone-stat
|
||||
%strong== #{milestone.percent_complete(current_user)}%
|
||||
complete
|
||||
%span.milestone-stat
|
||||
%span.remaining-days= milestone_remaining_days(milestone)
|
||||
- remaining_days = milestone_remaining_days(milestone)
|
||||
- if remaining_days.present?
|
||||
%span.milestone-stat
|
||||
%span.remaining-days= remaining_days
|
||||
|
||||
.milestone-progress-buttons
|
||||
%span.tab-issues-buttons
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Fix wrong tab selected when loggin fails and multiple login tabs exists
|
||||
merge_request: 7314
|
||||
author: Jacopo Beschi @jacopo-beschi
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
title: Fix diff view permalink highlighting
|
||||
merge_request: 7090
|
||||
author:
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: 'Remove unnecessary target branch link from MR page in case of deleted target branch'
|
||||
merge_request: 7916
|
||||
author: Rydkin Maxim
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
title: Fix Cicking on tabs on pipeline page should set URL
|
||||
merge_request: 7709
|
||||
author:
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: 'fix: 24982- Remove''Signed in successfully'' message After this change the
|
||||
sign-in-success flash message will not be shown'
|
||||
merge_request: 7837
|
||||
author: jnoortheen
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Remove wrong '.builds-feature' class from the MR settings fieldset
|
||||
merge_request: 7930
|
||||
author:
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: 'Fix comments activity tab visibility condition'
|
||||
merge_request: 7913
|
||||
author: Rydkin Maxim
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Resolve "Provide SVG as a prop instead of hiding and copy them in environments table"
|
||||
merge_request: 7992
|
||||
author:
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: 'API: Ability to set ''should_remove_source_branch'' on merge requests'
|
||||
merge_request:
|
||||
author: Robert Schilling
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue