Merge branch 'master' into sm-cherry-pick-list-commits-in-message
This commit is contained in:
commit
cd80a9075f
461 changed files with 6728 additions and 2671 deletions
209
.gitlab-ci.yml
209
.gitlab-ci.yml
|
@ -27,6 +27,7 @@ variables:
|
||||||
GET_SOURCES_ATTEMPTS: "3"
|
GET_SOURCES_ATTEMPTS: "3"
|
||||||
KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/rspec_report-master.json
|
KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/rspec_report-master.json
|
||||||
KNAPSACK_SPINACH_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/spinach_report-master.json
|
KNAPSACK_SPINACH_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/spinach_report-master.json
|
||||||
|
FLAKY_RSPEC_SUITE_REPORT_PATH: rspec_flaky/${CI_PROJECT_NAME}/report-master.json
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- bundle --version
|
- bundle --version
|
||||||
|
@ -45,16 +46,17 @@ stages:
|
||||||
tags:
|
tags:
|
||||||
- gitlab-org
|
- gitlab-org
|
||||||
|
|
||||||
.knapsack-state: &knapsack-state
|
.tests-metadata-state: &tests-metadata-state
|
||||||
services: []
|
services: []
|
||||||
variables:
|
variables:
|
||||||
SETUP_DB: "false"
|
SETUP_DB: "false"
|
||||||
USE_BUNDLE_INSTALL: "false"
|
USE_BUNDLE_INSTALL: "false"
|
||||||
KNAPSACK_S3_BUCKET: "gitlab-ce-cache"
|
TESTS_METADATA_S3_BUCKET: "gitlab-ce-cache"
|
||||||
artifacts:
|
artifacts:
|
||||||
expire_in: 31d
|
expire_in: 31d
|
||||||
paths:
|
paths:
|
||||||
- knapsack/
|
- knapsack/
|
||||||
|
- rspec_flaky/
|
||||||
|
|
||||||
.use-pg: &use-pg
|
.use-pg: &use-pg
|
||||||
services:
|
services:
|
||||||
|
@ -86,7 +88,7 @@ stages:
|
||||||
except:
|
except:
|
||||||
- /(^docs[\/-].*|.*-docs$)/
|
- /(^docs[\/-].*|.*-docs$)/
|
||||||
|
|
||||||
.rspec-knapsack: &rspec-knapsack
|
.rspec-metadata: &rspec-metadata
|
||||||
<<: *dedicated-runner
|
<<: *dedicated-runner
|
||||||
<<: *pull-cache
|
<<: *pull-cache
|
||||||
stage: test
|
stage: test
|
||||||
|
@ -96,8 +98,13 @@ stages:
|
||||||
- export CI_NODE_TOTAL=${JOB_NAME[-1]}
|
- export CI_NODE_TOTAL=${JOB_NAME[-1]}
|
||||||
- export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
|
- export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
|
||||||
- export KNAPSACK_GENERATE_REPORT=true
|
- export KNAPSACK_GENERATE_REPORT=true
|
||||||
|
- export ALL_FLAKY_RSPEC_REPORT_PATH=rspec_flaky/${CI_PROJECT_NAME}/all_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
|
||||||
|
- export NEW_FLAKY_RSPEC_REPORT_PATH=rspec_flaky/${CI_PROJECT_NAME}/new_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
|
||||||
|
- export FLAKY_RSPEC_GENERATE_REPORT=true
|
||||||
- export CACHE_CLASSES=true
|
- export CACHE_CLASSES=true
|
||||||
- cp ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH}
|
- cp ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH}
|
||||||
|
- cp ${FLAKY_RSPEC_SUITE_REPORT_PATH} ${ALL_FLAKY_RSPEC_REPORT_PATH}
|
||||||
|
- '[[ -f $NEW_FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${NEW_FLAKY_RSPEC_REPORT_PATH}'
|
||||||
- scripts/gitaly-test-spawn
|
- scripts/gitaly-test-spawn
|
||||||
- knapsack rspec "--color --format documentation"
|
- knapsack rspec "--color --format documentation"
|
||||||
artifacts:
|
artifacts:
|
||||||
|
@ -106,20 +113,21 @@ stages:
|
||||||
paths:
|
paths:
|
||||||
- coverage/
|
- coverage/
|
||||||
- knapsack/
|
- knapsack/
|
||||||
|
- rspec_flaky/
|
||||||
- tmp/capybara/
|
- tmp/capybara/
|
||||||
|
|
||||||
.rspec-knapsack-pg: &rspec-knapsack-pg
|
.rspec-metadata-pg: &rspec-metadata-pg
|
||||||
<<: *rspec-knapsack
|
<<: *rspec-metadata
|
||||||
<<: *use-pg
|
<<: *use-pg
|
||||||
<<: *except-docs
|
<<: *except-docs
|
||||||
|
|
||||||
.rspec-knapsack-mysql: &rspec-knapsack-mysql
|
.rspec-metadata-mysql: &rspec-metadata-mysql
|
||||||
<<: *rspec-knapsack
|
<<: *rspec-metadata
|
||||||
<<: *use-mysql
|
<<: *use-mysql
|
||||||
<<: *only-if-want-mysql
|
<<: *only-if-want-mysql
|
||||||
<<: *except-docs
|
<<: *except-docs
|
||||||
|
|
||||||
.spinach-knapsack: &spinach-knapsack
|
.spinach-metadata: &spinach-metadata
|
||||||
<<: *dedicated-runner
|
<<: *dedicated-runner
|
||||||
<<: *pull-cache
|
<<: *pull-cache
|
||||||
stage: test
|
stage: test
|
||||||
|
@ -140,13 +148,13 @@ stages:
|
||||||
- knapsack/
|
- knapsack/
|
||||||
- tmp/capybara/
|
- tmp/capybara/
|
||||||
|
|
||||||
.spinach-knapsack-pg: &spinach-knapsack-pg
|
.spinach-metadata-pg: &spinach-metadata-pg
|
||||||
<<: *spinach-knapsack
|
<<: *spinach-metadata
|
||||||
<<: *use-pg
|
<<: *use-pg
|
||||||
<<: *except-docs
|
<<: *except-docs
|
||||||
|
|
||||||
.spinach-knapsack-mysql: &spinach-knapsack-mysql
|
.spinach-metadata-mysql: &spinach-metadata-mysql
|
||||||
<<: *spinach-knapsack
|
<<: *spinach-metadata
|
||||||
<<: *use-mysql
|
<<: *use-mysql
|
||||||
<<: *only-if-want-mysql
|
<<: *only-if-want-mysql
|
||||||
<<: *except-docs
|
<<: *except-docs
|
||||||
|
@ -176,40 +184,71 @@ build-package:
|
||||||
- //@gitlab-org/gitlab-ce
|
- //@gitlab-org/gitlab-ce
|
||||||
- //@gitlab-org/gitlab-ee
|
- //@gitlab-org/gitlab-ee
|
||||||
|
|
||||||
# Prepare and merge knapsack tests
|
# Retrieve knapsack and rspec_flaky reports
|
||||||
knapsack:
|
retrieve-tests-metadata:
|
||||||
<<: *knapsack-state
|
<<: *tests-metadata-state
|
||||||
<<: *dedicated-runner
|
<<: *dedicated-runner
|
||||||
<<: *except-docs
|
<<: *except-docs
|
||||||
stage: prepare
|
stage: prepare
|
||||||
cache:
|
cache:
|
||||||
key: knapsack
|
key: tests_metadata
|
||||||
paths:
|
|
||||||
- knapsack/
|
|
||||||
policy: pull
|
policy: pull
|
||||||
script:
|
script:
|
||||||
- mkdir -p knapsack/${CI_PROJECT_NAME}/
|
- mkdir -p knapsack/${CI_PROJECT_NAME}/
|
||||||
- wget -O $KNAPSACK_RSPEC_SUITE_REPORT_PATH http://${KNAPSACK_S3_BUCKET}.s3.amazonaws.com/$KNAPSACK_RSPEC_SUITE_REPORT_PATH || rm $KNAPSACK_RSPEC_SUITE_REPORT_PATH
|
- wget -O $KNAPSACK_RSPEC_SUITE_REPORT_PATH http://${TESTS_METADATA_S3_BUCKET}.s3.amazonaws.com/$KNAPSACK_RSPEC_SUITE_REPORT_PATH || rm $KNAPSACK_RSPEC_SUITE_REPORT_PATH
|
||||||
- wget -O $KNAPSACK_SPINACH_SUITE_REPORT_PATH http://${KNAPSACK_S3_BUCKET}.s3.amazonaws.com/$KNAPSACK_SPINACH_SUITE_REPORT_PATH || rm $KNAPSACK_SPINACH_SUITE_REPORT_PATH
|
- wget -O $KNAPSACK_SPINACH_SUITE_REPORT_PATH http://${TESTS_METADATA_S3_BUCKET}.s3.amazonaws.com/$KNAPSACK_SPINACH_SUITE_REPORT_PATH || rm $KNAPSACK_SPINACH_SUITE_REPORT_PATH
|
||||||
- '[[ -f $KNAPSACK_RSPEC_SUITE_REPORT_PATH ]] || echo "{}" > ${KNAPSACK_RSPEC_SUITE_REPORT_PATH}'
|
- '[[ -f $KNAPSACK_RSPEC_SUITE_REPORT_PATH ]] || echo "{}" > ${KNAPSACK_RSPEC_SUITE_REPORT_PATH}'
|
||||||
- '[[ -f $KNAPSACK_SPINACH_SUITE_REPORT_PATH ]] || echo "{}" > ${KNAPSACK_SPINACH_SUITE_REPORT_PATH}'
|
- '[[ -f $KNAPSACK_SPINACH_SUITE_REPORT_PATH ]] || echo "{}" > ${KNAPSACK_SPINACH_SUITE_REPORT_PATH}'
|
||||||
|
- mkdir -p rspec_flaky/${CI_PROJECT_NAME}/
|
||||||
|
- wget -O $FLAKY_RSPEC_SUITE_REPORT_PATH http://${TESTS_METADATA_S3_BUCKET}.s3.amazonaws.com/$FLAKY_RSPEC_SUITE_REPORT_PATH || rm $FLAKY_RSPEC_SUITE_REPORT_PATH
|
||||||
|
- '[[ -f $FLAKY_RSPEC_SUITE_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_SUITE_REPORT_PATH}'
|
||||||
|
|
||||||
update-knapsack:
|
update-tests-metadata:
|
||||||
<<: *knapsack-state
|
<<: *tests-metadata-state
|
||||||
<<: *dedicated-runner
|
<<: *dedicated-runner
|
||||||
<<: *only-canonical-masters
|
<<: *only-canonical-masters
|
||||||
stage: post-test
|
stage: post-test
|
||||||
cache:
|
cache:
|
||||||
key: knapsack
|
key: tests_metadata
|
||||||
paths:
|
paths:
|
||||||
- knapsack/
|
- knapsack/
|
||||||
|
- rspec_flaky/
|
||||||
policy: push
|
policy: push
|
||||||
script:
|
script:
|
||||||
- retry gem install fog-aws mime-types
|
- retry gem install fog-aws mime-types
|
||||||
- scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json
|
- scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json
|
||||||
- scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach-pg_node_*.json
|
- scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach-pg_node_*.json
|
||||||
- '[[ -z ${KNAPSACK_S3_BUCKET} ]] || scripts/sync-reports put $KNAPSACK_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH $KNAPSACK_SPINACH_SUITE_REPORT_PATH'
|
- scripts/merge-reports ${FLAKY_RSPEC_SUITE_REPORT_PATH} rspec_flaky/${CI_PROJECT_NAME}/all_node_*.json
|
||||||
|
- '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH $KNAPSACK_SPINACH_SUITE_REPORT_PATH'
|
||||||
|
- '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $FLAKY_RSPEC_SUITE_REPORT_PATH'
|
||||||
- rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
|
- rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
|
||||||
|
- rm -f rspec_flaky/${CI_PROJECT_NAME}/all_node_*.json
|
||||||
|
|
||||||
|
flaky-examples-check:
|
||||||
|
<<: *dedicated-runner
|
||||||
|
<<: *except-docs
|
||||||
|
image: ruby:2.3-alpine
|
||||||
|
services: []
|
||||||
|
before_script: []
|
||||||
|
cache: {}
|
||||||
|
variables:
|
||||||
|
SETUP_DB: "false"
|
||||||
|
USE_BUNDLE_INSTALL: "false"
|
||||||
|
NEW_FLAKY_SPECS_REPORT: rspec_flaky/${CI_PROJECT_NAME}/new_rspec_flaky_examples.json
|
||||||
|
stage: post-test
|
||||||
|
allow_failure: yes
|
||||||
|
only:
|
||||||
|
- branches
|
||||||
|
except:
|
||||||
|
- master
|
||||||
|
artifacts:
|
||||||
|
expire_in: 30d
|
||||||
|
paths:
|
||||||
|
- rspec_flaky/
|
||||||
|
script:
|
||||||
|
- '[[ -f $NEW_FLAKY_SPECS_REPORT ]] || echo "{}" > ${NEW_FLAKY_SPECS_REPORT}'
|
||||||
|
- scripts/merge-reports $NEW_FLAKY_SPECS_REPORT rspec_flaky/${CI_PROJECT_NAME}/new_node_*.json
|
||||||
|
- scripts/detect-new-flaky-examples $NEW_FLAKY_SPECS_REPORT
|
||||||
|
|
||||||
setup-test-env:
|
setup-test-env:
|
||||||
<<: *use-pg
|
<<: *use-pg
|
||||||
|
@ -232,69 +271,69 @@ setup-test-env:
|
||||||
- public/assets
|
- public/assets
|
||||||
- tmp/tests
|
- tmp/tests
|
||||||
|
|
||||||
rspec-pg 0 25: *rspec-knapsack-pg
|
rspec-pg 0 25: *rspec-metadata-pg
|
||||||
rspec-pg 1 25: *rspec-knapsack-pg
|
rspec-pg 1 25: *rspec-metadata-pg
|
||||||
rspec-pg 2 25: *rspec-knapsack-pg
|
rspec-pg 2 25: *rspec-metadata-pg
|
||||||
rspec-pg 3 25: *rspec-knapsack-pg
|
rspec-pg 3 25: *rspec-metadata-pg
|
||||||
rspec-pg 4 25: *rspec-knapsack-pg
|
rspec-pg 4 25: *rspec-metadata-pg
|
||||||
rspec-pg 5 25: *rspec-knapsack-pg
|
rspec-pg 5 25: *rspec-metadata-pg
|
||||||
rspec-pg 6 25: *rspec-knapsack-pg
|
rspec-pg 6 25: *rspec-metadata-pg
|
||||||
rspec-pg 7 25: *rspec-knapsack-pg
|
rspec-pg 7 25: *rspec-metadata-pg
|
||||||
rspec-pg 8 25: *rspec-knapsack-pg
|
rspec-pg 8 25: *rspec-metadata-pg
|
||||||
rspec-pg 9 25: *rspec-knapsack-pg
|
rspec-pg 9 25: *rspec-metadata-pg
|
||||||
rspec-pg 10 25: *rspec-knapsack-pg
|
rspec-pg 10 25: *rspec-metadata-pg
|
||||||
rspec-pg 11 25: *rspec-knapsack-pg
|
rspec-pg 11 25: *rspec-metadata-pg
|
||||||
rspec-pg 12 25: *rspec-knapsack-pg
|
rspec-pg 12 25: *rspec-metadata-pg
|
||||||
rspec-pg 13 25: *rspec-knapsack-pg
|
rspec-pg 13 25: *rspec-metadata-pg
|
||||||
rspec-pg 14 25: *rspec-knapsack-pg
|
rspec-pg 14 25: *rspec-metadata-pg
|
||||||
rspec-pg 15 25: *rspec-knapsack-pg
|
rspec-pg 15 25: *rspec-metadata-pg
|
||||||
rspec-pg 16 25: *rspec-knapsack-pg
|
rspec-pg 16 25: *rspec-metadata-pg
|
||||||
rspec-pg 17 25: *rspec-knapsack-pg
|
rspec-pg 17 25: *rspec-metadata-pg
|
||||||
rspec-pg 18 25: *rspec-knapsack-pg
|
rspec-pg 18 25: *rspec-metadata-pg
|
||||||
rspec-pg 19 25: *rspec-knapsack-pg
|
rspec-pg 19 25: *rspec-metadata-pg
|
||||||
rspec-pg 20 25: *rspec-knapsack-pg
|
rspec-pg 20 25: *rspec-metadata-pg
|
||||||
rspec-pg 21 25: *rspec-knapsack-pg
|
rspec-pg 21 25: *rspec-metadata-pg
|
||||||
rspec-pg 22 25: *rspec-knapsack-pg
|
rspec-pg 22 25: *rspec-metadata-pg
|
||||||
rspec-pg 23 25: *rspec-knapsack-pg
|
rspec-pg 23 25: *rspec-metadata-pg
|
||||||
rspec-pg 24 25: *rspec-knapsack-pg
|
rspec-pg 24 25: *rspec-metadata-pg
|
||||||
|
|
||||||
rspec-mysql 0 25: *rspec-knapsack-mysql
|
rspec-mysql 0 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 1 25: *rspec-knapsack-mysql
|
rspec-mysql 1 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 2 25: *rspec-knapsack-mysql
|
rspec-mysql 2 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 3 25: *rspec-knapsack-mysql
|
rspec-mysql 3 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 4 25: *rspec-knapsack-mysql
|
rspec-mysql 4 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 5 25: *rspec-knapsack-mysql
|
rspec-mysql 5 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 6 25: *rspec-knapsack-mysql
|
rspec-mysql 6 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 7 25: *rspec-knapsack-mysql
|
rspec-mysql 7 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 8 25: *rspec-knapsack-mysql
|
rspec-mysql 8 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 9 25: *rspec-knapsack-mysql
|
rspec-mysql 9 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 10 25: *rspec-knapsack-mysql
|
rspec-mysql 10 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 11 25: *rspec-knapsack-mysql
|
rspec-mysql 11 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 12 25: *rspec-knapsack-mysql
|
rspec-mysql 12 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 13 25: *rspec-knapsack-mysql
|
rspec-mysql 13 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 14 25: *rspec-knapsack-mysql
|
rspec-mysql 14 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 15 25: *rspec-knapsack-mysql
|
rspec-mysql 15 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 16 25: *rspec-knapsack-mysql
|
rspec-mysql 16 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 17 25: *rspec-knapsack-mysql
|
rspec-mysql 17 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 18 25: *rspec-knapsack-mysql
|
rspec-mysql 18 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 19 25: *rspec-knapsack-mysql
|
rspec-mysql 19 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 20 25: *rspec-knapsack-mysql
|
rspec-mysql 20 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 21 25: *rspec-knapsack-mysql
|
rspec-mysql 21 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 22 25: *rspec-knapsack-mysql
|
rspec-mysql 22 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 23 25: *rspec-knapsack-mysql
|
rspec-mysql 23 25: *rspec-metadata-mysql
|
||||||
rspec-mysql 24 25: *rspec-knapsack-mysql
|
rspec-mysql 24 25: *rspec-metadata-mysql
|
||||||
|
|
||||||
spinach-pg 0 5: *spinach-knapsack-pg
|
spinach-pg 0 5: *spinach-metadata-pg
|
||||||
spinach-pg 1 5: *spinach-knapsack-pg
|
spinach-pg 1 5: *spinach-metadata-pg
|
||||||
spinach-pg 2 5: *spinach-knapsack-pg
|
spinach-pg 2 5: *spinach-metadata-pg
|
||||||
spinach-pg 3 5: *spinach-knapsack-pg
|
spinach-pg 3 5: *spinach-metadata-pg
|
||||||
spinach-pg 4 5: *spinach-knapsack-pg
|
spinach-pg 4 5: *spinach-metadata-pg
|
||||||
|
|
||||||
spinach-mysql 0 5: *spinach-knapsack-mysql
|
spinach-mysql 0 5: *spinach-metadata-mysql
|
||||||
spinach-mysql 1 5: *spinach-knapsack-mysql
|
spinach-mysql 1 5: *spinach-metadata-mysql
|
||||||
spinach-mysql 2 5: *spinach-knapsack-mysql
|
spinach-mysql 2 5: *spinach-metadata-mysql
|
||||||
spinach-mysql 3 5: *spinach-knapsack-mysql
|
spinach-mysql 3 5: *spinach-metadata-mysql
|
||||||
spinach-mysql 4 5: *spinach-knapsack-mysql
|
spinach-mysql 4 5: *spinach-metadata-mysql
|
||||||
|
|
||||||
# Static analysis jobs
|
# Static analysis jobs
|
||||||
.ruby-static-analysis: &ruby-static-analysis
|
.ruby-static-analysis: &ruby-static-analysis
|
||||||
|
@ -354,7 +393,7 @@ ee_compat_check:
|
||||||
except:
|
except:
|
||||||
- master
|
- master
|
||||||
- tags
|
- tags
|
||||||
- /^[\d-]+-stable(-ee)?$/
|
- /^[\d-]+-stable(-ee)?/
|
||||||
allow_failure: yes
|
allow_failure: yes
|
||||||
cache:
|
cache:
|
||||||
key: "ee_compat_check_repo"
|
key: "ee_compat_check_repo"
|
||||||
|
|
10
.rubocop.yml
10
.rubocop.yml
|
@ -1045,7 +1045,7 @@ RSpec/BeforeAfterAll:
|
||||||
RSpec/DescribeClass:
|
RSpec/DescribeClass:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
# Use `described_class` for tested class / module.
|
# Checks that the second argument to `describe` specifies a method.
|
||||||
RSpec/DescribeMethod:
|
RSpec/DescribeMethod:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
@ -1053,8 +1053,7 @@ RSpec/DescribeMethod:
|
||||||
RSpec/DescribeSymbol:
|
RSpec/DescribeSymbol:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
|
||||||
# Checks that the second argument to top level describe is the tested method
|
# Checks that tests use `described_class`.
|
||||||
# name.
|
|
||||||
RSpec/DescribedClass:
|
RSpec/DescribedClass:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
|
||||||
|
@ -1099,6 +1098,11 @@ RSpec/FilePath:
|
||||||
RSpec/Focus:
|
RSpec/Focus:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
|
||||||
|
# Checks the arguments passed to `before`, `around`, and `after`.
|
||||||
|
RSpec/HookArgument:
|
||||||
|
Enabled: true
|
||||||
|
EnforcedStyle: implicit
|
||||||
|
|
||||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||||
# SupportedStyles: is_expected, should
|
# SupportedStyles: is_expected, should
|
||||||
RSpec/ImplicitExpect:
|
RSpec/ImplicitExpect:
|
||||||
|
|
|
@ -70,12 +70,6 @@ RSpec/EmptyLineAfterFinalLet:
|
||||||
RSpec/EmptyLineAfterSubject:
|
RSpec/EmptyLineAfterSubject:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
# Offense count: 78
|
|
||||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
|
||||||
# SupportedStyles: implicit, each, example
|
|
||||||
RSpec/HookArgument:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Offense count: 9
|
# Offense count: 9
|
||||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||||
# SupportedStyles: it_behaves_like, it_should_behave_like
|
# SupportedStyles: it_behaves_like, it_should_behave_like
|
||||||
|
|
51
CHANGELOG.md
51
CHANGELOG.md
|
@ -2,6 +2,32 @@
|
||||||
documentation](doc/development/changelog.md) for instructions on adding your own
|
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||||
entry.
|
entry.
|
||||||
|
|
||||||
|
## 9.4.5 (2017-08-14)
|
||||||
|
|
||||||
|
- Fix deletion of deploy keys linked to other projects. !13162
|
||||||
|
- Allow any logged in users to read_users_list even if it's restricted. !13201
|
||||||
|
- Make Delete Merged Branches handle wildcard protected branches correctly. !13251
|
||||||
|
- Fix an order of operations for CI connection error message in merge request widget. !13252
|
||||||
|
- Fix pipeline_schedules pages when active schedule has an abnormal state. !13286
|
||||||
|
- Add missing validation error for username change with container registry tags. !13356
|
||||||
|
- Fix destroy of case-insensitive conflicting redirects. !13357
|
||||||
|
- Project pending delete no longer return 500 error in admins projects view. !13389
|
||||||
|
- Fix search box losing focus when typing.
|
||||||
|
- Use jQuery to control scroll behavior in job log for cross browser consistency.
|
||||||
|
- Use project_ref_path to create the link to a branch to fix links that 404.
|
||||||
|
- improve file upload/replace experience.
|
||||||
|
- fix jump to next discussion button.
|
||||||
|
- Fixes new issue button for failed job returning 404.
|
||||||
|
- Fix links to group milestones from issue and merge request sidebar.
|
||||||
|
- Fixed sign-in restrictions buttons not toggling active state.
|
||||||
|
- Fix Mattermost integration.
|
||||||
|
- Change project FK migration to skip existing FKs.
|
||||||
|
|
||||||
|
## 9.4.4 (2017-08-09)
|
||||||
|
|
||||||
|
- Remove hidden symlinks from project import files.
|
||||||
|
- Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
|
||||||
|
|
||||||
## 9.4.3 (2017-07-31)
|
## 9.4.3 (2017-07-31)
|
||||||
|
|
||||||
- Fix Prometheus client PID reuse bug. !13130
|
- Fix Prometheus client PID reuse bug. !13130
|
||||||
|
@ -226,6 +252,11 @@ entry.
|
||||||
- Log rescued exceptions to Sentry.
|
- Log rescued exceptions to Sentry.
|
||||||
- Remove remaining N+1 queries in merge requests API with emojis and labels.
|
- Remove remaining N+1 queries in merge requests API with emojis and labels.
|
||||||
|
|
||||||
|
## 9.3.10 (2017-08-09)
|
||||||
|
|
||||||
|
- Remove hidden symlinks from project import files.
|
||||||
|
- Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
|
||||||
|
|
||||||
## 9.3.9 (2017-07-20)
|
## 9.3.9 (2017-07-20)
|
||||||
|
|
||||||
- Fix an infinite loop when handling user-supplied regular expressions.
|
- Fix an infinite loop when handling user-supplied regular expressions.
|
||||||
|
@ -498,6 +529,11 @@ entry.
|
||||||
- Remove foreigh key on ci_trigger_schedules only if it exists.
|
- Remove foreigh key on ci_trigger_schedules only if it exists.
|
||||||
- Allow translation of Pipeline Schedules.
|
- Allow translation of Pipeline Schedules.
|
||||||
|
|
||||||
|
## 9.2.10 (2017-08-09)
|
||||||
|
|
||||||
|
- Remove hidden symlinks from project import files.
|
||||||
|
- Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
|
||||||
|
|
||||||
## 9.2.9 (2017-07-20)
|
## 9.2.9 (2017-07-20)
|
||||||
|
|
||||||
- Fix an infinite loop when handling user-supplied regular expressions.
|
- Fix an infinite loop when handling user-supplied regular expressions.
|
||||||
|
@ -753,6 +789,11 @@ entry.
|
||||||
- Fix preemptive scroll bar on user activity calendar.
|
- Fix preemptive scroll bar on user activity calendar.
|
||||||
- Pipeline chat notifications convert seconds to minutes and hours.
|
- Pipeline chat notifications convert seconds to minutes and hours.
|
||||||
|
|
||||||
|
## 9.1.10 (2017-08-09)
|
||||||
|
|
||||||
|
- Remove hidden symlinks from project import files.
|
||||||
|
- Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
|
||||||
|
|
||||||
## 9.1.9 (2017-07-20)
|
## 9.1.9 (2017-07-20)
|
||||||
|
|
||||||
- Fix an infinite loop when handling user-supplied regular expressions.
|
- Fix an infinite loop when handling user-supplied regular expressions.
|
||||||
|
@ -1076,6 +1117,11 @@ entry.
|
||||||
- Only send chat notifications for the default branch.
|
- Only send chat notifications for the default branch.
|
||||||
- Don't fill in the default kubernetes namespace.
|
- Don't fill in the default kubernetes namespace.
|
||||||
|
|
||||||
|
## 9.0.13 (2017-08-09)
|
||||||
|
|
||||||
|
- Remove hidden symlinks from project import files.
|
||||||
|
- Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
|
||||||
|
|
||||||
## 9.0.12 (2017-07-20)
|
## 9.0.12 (2017-07-20)
|
||||||
|
|
||||||
- Fix an infinite loop when handling user-supplied regular expressions.
|
- Fix an infinite loop when handling user-supplied regular expressions.
|
||||||
|
@ -1456,6 +1502,11 @@ entry.
|
||||||
- Change development tanuki favicon colors to match logo color order.
|
- Change development tanuki favicon colors to match logo color order.
|
||||||
- API issues - support filtering by iids.
|
- API issues - support filtering by iids.
|
||||||
|
|
||||||
|
## 8.17.8 (2017-08-09)
|
||||||
|
|
||||||
|
- Remove hidden symlinks from project import files.
|
||||||
|
- Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
|
||||||
|
|
||||||
## 8.17.7 (2017-07-19)
|
## 8.17.7 (2017-07-19)
|
||||||
|
|
||||||
- Renders 404 if given project is not readable by the user on Todos dashboard.
|
- Renders 404 if given project is not readable by the user on Todos dashboard.
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
0.29.0
|
0.30.0
|
||||||
|
|
7
Gemfile
7
Gemfile
|
@ -64,7 +64,7 @@ gem 'gpgme'
|
||||||
# LDAP Auth
|
# LDAP Auth
|
||||||
# GitLab fork with several improvements to original library. For full list of changes
|
# GitLab fork with several improvements to original library. For full list of changes
|
||||||
# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
|
# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
|
||||||
gem 'gitlab_omniauth-ldap', '~> 2.0.3', require: 'omniauth-ldap'
|
gem 'gitlab_omniauth-ldap', '~> 2.0.4', require: 'omniauth-ldap'
|
||||||
gem 'net-ldap'
|
gem 'net-ldap'
|
||||||
|
|
||||||
# Git Wiki
|
# Git Wiki
|
||||||
|
@ -84,7 +84,7 @@ gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
|
||||||
gem 'hashie-forbidden_attributes'
|
gem 'hashie-forbidden_attributes'
|
||||||
|
|
||||||
# Pagination
|
# Pagination
|
||||||
gem 'kaminari', '~> 0.17.0'
|
gem 'kaminari', '~> 1.0'
|
||||||
|
|
||||||
# HAML
|
# HAML
|
||||||
gem 'hamlit', '~> 2.6.1'
|
gem 'hamlit', '~> 2.6.1'
|
||||||
|
@ -324,6 +324,7 @@ group :development, :test do
|
||||||
gem 'spinach-rerun-reporter', '~> 0.0.2'
|
gem 'spinach-rerun-reporter', '~> 0.0.2'
|
||||||
gem 'rspec_profiling', '~> 0.0.5'
|
gem 'rspec_profiling', '~> 0.0.5'
|
||||||
gem 'rspec-set', '~> 0.1.3'
|
gem 'rspec-set', '~> 0.1.3'
|
||||||
|
gem 'rspec-parameterized'
|
||||||
|
|
||||||
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
|
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
|
||||||
gem 'minitest', '~> 5.7.0'
|
gem 'minitest', '~> 5.7.0'
|
||||||
|
@ -402,7 +403,7 @@ group :ed25519 do
|
||||||
end
|
end
|
||||||
|
|
||||||
# Gitaly GRPC client
|
# Gitaly GRPC client
|
||||||
gem 'gitaly', '~> 0.26.0'
|
gem 'gitaly', '~> 0.27.0'
|
||||||
|
|
||||||
gem 'toml-rb', '~> 0.3.15', require: false
|
gem 'toml-rb', '~> 0.3.15', require: false
|
||||||
|
|
||||||
|
|
58
Gemfile.lock
58
Gemfile.lock
|
@ -2,6 +2,7 @@ GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
RedCloth (4.3.2)
|
RedCloth (4.3.2)
|
||||||
|
abstract_type (0.0.7)
|
||||||
ace-rails-ap (4.1.2)
|
ace-rails-ap (4.1.2)
|
||||||
actionmailer (4.2.8)
|
actionmailer (4.2.8)
|
||||||
actionpack (= 4.2.8)
|
actionpack (= 4.2.8)
|
||||||
|
@ -41,6 +42,9 @@ GEM
|
||||||
tzinfo (~> 1.1)
|
tzinfo (~> 1.1)
|
||||||
acts-as-taggable-on (4.0.0)
|
acts-as-taggable-on (4.0.0)
|
||||||
activerecord (>= 4.0)
|
activerecord (>= 4.0)
|
||||||
|
adamantium (0.2.0)
|
||||||
|
ice_nine (~> 0.11.0)
|
||||||
|
memoizable (~> 0.4.0)
|
||||||
addressable (2.3.8)
|
addressable (2.3.8)
|
||||||
after_commit_queue (1.3.0)
|
after_commit_queue (1.3.0)
|
||||||
activerecord (>= 3.0)
|
activerecord (>= 3.0)
|
||||||
|
@ -124,6 +128,9 @@ GEM
|
||||||
coercible (1.0.0)
|
coercible (1.0.0)
|
||||||
descendants_tracker (~> 0.0.1)
|
descendants_tracker (~> 0.0.1)
|
||||||
colorize (0.7.7)
|
colorize (0.7.7)
|
||||||
|
concord (0.1.5)
|
||||||
|
adamantium (~> 0.2.0)
|
||||||
|
equalizer (~> 0.0.9)
|
||||||
concurrent-ruby (1.0.5)
|
concurrent-ruby (1.0.5)
|
||||||
concurrent-ruby-ext (1.0.5)
|
concurrent-ruby-ext (1.0.5)
|
||||||
concurrent-ruby (= 1.0.5)
|
concurrent-ruby (= 1.0.5)
|
||||||
|
@ -270,7 +277,7 @@ GEM
|
||||||
po_to_json (>= 1.0.0)
|
po_to_json (>= 1.0.0)
|
||||||
rails (>= 3.2.0)
|
rails (>= 3.2.0)
|
||||||
gherkin-ruby (0.3.2)
|
gherkin-ruby (0.3.2)
|
||||||
gitaly (0.26.0)
|
gitaly (0.27.0)
|
||||||
google-protobuf (~> 3.1)
|
google-protobuf (~> 3.1)
|
||||||
grpc (~> 1.0)
|
grpc (~> 1.0)
|
||||||
github-linguist (4.7.6)
|
github-linguist (4.7.6)
|
||||||
|
@ -289,7 +296,7 @@ GEM
|
||||||
mime-types (>= 1.16, < 3)
|
mime-types (>= 1.16, < 3)
|
||||||
posix-spawn (~> 0.3)
|
posix-spawn (~> 0.3)
|
||||||
gitlab-markup (1.5.1)
|
gitlab-markup (1.5.1)
|
||||||
gitlab_omniauth-ldap (2.0.3)
|
gitlab_omniauth-ldap (2.0.4)
|
||||||
net-ldap (~> 0.16)
|
net-ldap (~> 0.16)
|
||||||
omniauth (~> 1.3)
|
omniauth (~> 1.3)
|
||||||
pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
|
pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
|
||||||
|
@ -419,9 +426,18 @@ GEM
|
||||||
json-schema (2.6.2)
|
json-schema (2.6.2)
|
||||||
addressable (~> 2.3.8)
|
addressable (~> 2.3.8)
|
||||||
jwt (1.5.6)
|
jwt (1.5.6)
|
||||||
kaminari (0.17.0)
|
kaminari (1.0.1)
|
||||||
actionpack (>= 3.0.0)
|
activesupport (>= 4.1.0)
|
||||||
activesupport (>= 3.0.0)
|
kaminari-actionview (= 1.0.1)
|
||||||
|
kaminari-activerecord (= 1.0.1)
|
||||||
|
kaminari-core (= 1.0.1)
|
||||||
|
kaminari-actionview (1.0.1)
|
||||||
|
actionview
|
||||||
|
kaminari-core (= 1.0.1)
|
||||||
|
kaminari-activerecord (1.0.1)
|
||||||
|
activerecord
|
||||||
|
kaminari-core (= 1.0.1)
|
||||||
|
kaminari-core (1.0.1)
|
||||||
kgio (2.10.0)
|
kgio (2.10.0)
|
||||||
knapsack (1.11.0)
|
knapsack (1.11.0)
|
||||||
rake
|
rake
|
||||||
|
@ -461,6 +477,8 @@ GEM
|
||||||
mime-types (>= 1.16, < 4)
|
mime-types (>= 1.16, < 4)
|
||||||
mail_room (0.9.1)
|
mail_room (0.9.1)
|
||||||
memoist (0.15.0)
|
memoist (0.15.0)
|
||||||
|
memoizable (0.4.2)
|
||||||
|
thread_safe (~> 0.3, >= 0.3.1)
|
||||||
method_source (0.8.2)
|
method_source (0.8.2)
|
||||||
mime-types (2.99.3)
|
mime-types (2.99.3)
|
||||||
mimemagic (0.3.0)
|
mimemagic (0.3.0)
|
||||||
|
@ -601,6 +619,11 @@ GEM
|
||||||
premailer-rails (1.9.7)
|
premailer-rails (1.9.7)
|
||||||
actionmailer (>= 3, < 6)
|
actionmailer (>= 3, < 6)
|
||||||
premailer (~> 1.7, >= 1.7.9)
|
premailer (~> 1.7, >= 1.7.9)
|
||||||
|
proc_to_ast (0.1.0)
|
||||||
|
coderay
|
||||||
|
parser
|
||||||
|
unparser
|
||||||
|
procto (0.0.3)
|
||||||
prometheus-client-mmap (0.7.0.beta11)
|
prometheus-client-mmap (0.7.0.beta11)
|
||||||
mmap2 (~> 2.2, >= 2.2.7)
|
mmap2 (~> 2.2, >= 2.2.7)
|
||||||
pry (0.10.4)
|
pry (0.10.4)
|
||||||
|
@ -709,6 +732,10 @@ GEM
|
||||||
chunky_png
|
chunky_png
|
||||||
rqrcode-rails3 (0.1.7)
|
rqrcode-rails3 (0.1.7)
|
||||||
rqrcode (>= 0.4.2)
|
rqrcode (>= 0.4.2)
|
||||||
|
rspec (3.6.0)
|
||||||
|
rspec-core (~> 3.6.0)
|
||||||
|
rspec-expectations (~> 3.6.0)
|
||||||
|
rspec-mocks (~> 3.6.0)
|
||||||
rspec-core (3.6.0)
|
rspec-core (3.6.0)
|
||||||
rspec-support (~> 3.6.0)
|
rspec-support (~> 3.6.0)
|
||||||
rspec-expectations (3.6.0)
|
rspec-expectations (3.6.0)
|
||||||
|
@ -717,6 +744,12 @@ GEM
|
||||||
rspec-mocks (3.6.0)
|
rspec-mocks (3.6.0)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.6.0)
|
rspec-support (~> 3.6.0)
|
||||||
|
rspec-parameterized (0.4.0)
|
||||||
|
binding_of_caller
|
||||||
|
parser
|
||||||
|
proc_to_ast
|
||||||
|
rspec (>= 2.13, < 4)
|
||||||
|
unparser
|
||||||
rspec-rails (3.6.0)
|
rspec-rails (3.6.0)
|
||||||
actionpack (>= 3.0)
|
actionpack (>= 3.0)
|
||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
|
@ -883,6 +916,14 @@ GEM
|
||||||
get_process_mem (~> 0)
|
get_process_mem (~> 0)
|
||||||
unicorn (>= 4, < 6)
|
unicorn (>= 4, < 6)
|
||||||
uniform_notifier (1.10.0)
|
uniform_notifier (1.10.0)
|
||||||
|
unparser (0.2.6)
|
||||||
|
abstract_type (~> 0.0.7)
|
||||||
|
adamantium (~> 0.2.0)
|
||||||
|
concord (~> 0.1.5)
|
||||||
|
diff-lcs (~> 1.3)
|
||||||
|
equalizer (~> 0.0.9)
|
||||||
|
parser (>= 2.3.1.2, < 2.5)
|
||||||
|
procto (~> 0.0.2)
|
||||||
url_safe_base64 (0.2.2)
|
url_safe_base64 (0.2.2)
|
||||||
validates_hostname (1.0.6)
|
validates_hostname (1.0.6)
|
||||||
activerecord (>= 3.0)
|
activerecord (>= 3.0)
|
||||||
|
@ -984,11 +1025,11 @@ DEPENDENCIES
|
||||||
gettext (~> 3.2.2)
|
gettext (~> 3.2.2)
|
||||||
gettext_i18n_rails (~> 1.8.0)
|
gettext_i18n_rails (~> 1.8.0)
|
||||||
gettext_i18n_rails_js (~> 1.2.0)
|
gettext_i18n_rails_js (~> 1.2.0)
|
||||||
gitaly (~> 0.26.0)
|
gitaly (~> 0.27.0)
|
||||||
github-linguist (~> 4.7.0)
|
github-linguist (~> 4.7.0)
|
||||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||||
gitlab-markup (~> 1.5.1)
|
gitlab-markup (~> 1.5.1)
|
||||||
gitlab_omniauth-ldap (~> 2.0.3)
|
gitlab_omniauth-ldap (~> 2.0.4)
|
||||||
gollum-lib (~> 4.2)
|
gollum-lib (~> 4.2)
|
||||||
gollum-rugged_adapter (~> 0.4.4)
|
gollum-rugged_adapter (~> 0.4.4)
|
||||||
gon (~> 6.1.0)
|
gon (~> 6.1.0)
|
||||||
|
@ -1011,7 +1052,7 @@ DEPENDENCIES
|
||||||
jquery-rails (~> 4.1.0)
|
jquery-rails (~> 4.1.0)
|
||||||
json-schema (~> 2.6.2)
|
json-schema (~> 2.6.2)
|
||||||
jwt (~> 1.5.6)
|
jwt (~> 1.5.6)
|
||||||
kaminari (~> 0.17.0)
|
kaminari (~> 1.0)
|
||||||
knapsack (~> 1.11.0)
|
knapsack (~> 1.11.0)
|
||||||
kubeclient (~> 2.2.0)
|
kubeclient (~> 2.2.0)
|
||||||
letter_opener_web (~> 1.3.0)
|
letter_opener_web (~> 1.3.0)
|
||||||
|
@ -1085,6 +1126,7 @@ DEPENDENCIES
|
||||||
responders (~> 2.0)
|
responders (~> 2.0)
|
||||||
rouge (~> 2.0)
|
rouge (~> 2.0)
|
||||||
rqrcode-rails3 (~> 0.1.7)
|
rqrcode-rails3 (~> 0.1.7)
|
||||||
|
rspec-parameterized
|
||||||
rspec-rails (~> 3.6.0)
|
rspec-rails (~> 3.6.0)
|
||||||
rspec-retry (~> 0.4.5)
|
rspec-retry (~> 0.4.5)
|
||||||
rspec-set (~> 0.1.3)
|
rspec-set (~> 0.1.3)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/* global ListIssue */
|
/* global ListIssue */
|
||||||
/* global bp */
|
|
||||||
|
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import bp from '../../../breakpoints';
|
||||||
|
|
||||||
const ModalStore = gl.issueBoards.ModalStore;
|
const ModalStore = gl.issueBoards.ModalStore;
|
||||||
|
|
||||||
|
|
|
@ -1,66 +1,19 @@
|
||||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, quotes, no-shadow, prefer-arrow-callback, prefer-template, consistent-return, no-return-assign, new-parens, no-param-reassign, max-len */
|
export const breakpoints = {
|
||||||
|
lg: 1200,
|
||||||
|
md: 992,
|
||||||
|
sm: 768,
|
||||||
|
xs: 0,
|
||||||
|
};
|
||||||
|
|
||||||
var Breakpoints = (function() {
|
const BreakpointInstance = {
|
||||||
var BreakpointInstance, instance;
|
windowWidth: () => window.innerWidth,
|
||||||
|
getBreakpointSize() {
|
||||||
|
const windowWidth = this.windowWidth();
|
||||||
|
|
||||||
function Breakpoints() {}
|
const breakpoint = Object.keys(breakpoints).find(key => windowWidth > breakpoints[key]);
|
||||||
|
|
||||||
instance = null;
|
return breakpoint;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
BreakpointInstance = (function() {
|
export default BreakpointInstance;
|
||||||
var BREAKPOINTS;
|
|
||||||
|
|
||||||
BREAKPOINTS = ["xs", "sm", "md", "lg"];
|
|
||||||
|
|
||||||
function BreakpointInstance() {
|
|
||||||
this.setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
BreakpointInstance.prototype.setup = function() {
|
|
||||||
var allDeviceSelector, els;
|
|
||||||
allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
|
|
||||||
return ".device-" + breakpoint;
|
|
||||||
});
|
|
||||||
if ($(allDeviceSelector.join(",")).length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Create all the elements
|
|
||||||
els = $.map(BREAKPOINTS, function(breakpoint) {
|
|
||||||
return "<div class='device-" + breakpoint + " visible-" + breakpoint + "'></div>";
|
|
||||||
});
|
|
||||||
return $("body").append(els.join(''));
|
|
||||||
};
|
|
||||||
|
|
||||||
BreakpointInstance.prototype.visibleDevice = function() {
|
|
||||||
var allDeviceSelector;
|
|
||||||
allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
|
|
||||||
return ".device-" + breakpoint;
|
|
||||||
});
|
|
||||||
return $(allDeviceSelector.join(",")).filter(":visible");
|
|
||||||
};
|
|
||||||
|
|
||||||
BreakpointInstance.prototype.getBreakpointSize = function() {
|
|
||||||
var $visibleDevice;
|
|
||||||
$visibleDevice = this.visibleDevice;
|
|
||||||
// TODO: Consider refactoring in light of turbolinks removal.
|
|
||||||
// the page refreshed via turbolinks
|
|
||||||
if (!$visibleDevice().length) {
|
|
||||||
this.setup();
|
|
||||||
}
|
|
||||||
$visibleDevice = this.visibleDevice();
|
|
||||||
return $visibleDevice.attr("class").split("visible-")[1];
|
|
||||||
};
|
|
||||||
|
|
||||||
return BreakpointInstance;
|
|
||||||
})();
|
|
||||||
|
|
||||||
Breakpoints.get = function() {
|
|
||||||
return instance != null ? instance : instance = new BreakpointInstance;
|
|
||||||
};
|
|
||||||
|
|
||||||
return Breakpoints;
|
|
||||||
})();
|
|
||||||
|
|
||||||
$(() => { window.bp = Breakpoints.get(); });
|
|
||||||
|
|
||||||
window.Breakpoints = Breakpoints;
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
/* eslint-disable func-names, wrap-iife, no-use-before-define,
|
/* eslint-disable func-names, wrap-iife, no-use-before-define,
|
||||||
consistent-return, prefer-rest-params */
|
consistent-return, prefer-rest-params */
|
||||||
/* global Breakpoints */
|
|
||||||
|
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
|
import bp from './breakpoints';
|
||||||
import { bytesToKiB } from './lib/utils/number_utils';
|
import { bytesToKiB } from './lib/utils/number_utils';
|
||||||
|
|
||||||
window.Build = (function () {
|
window.Build = (function () {
|
||||||
|
@ -34,8 +33,6 @@ window.Build = (function () {
|
||||||
this.$scrollBottomBtn = $('.js-scroll-down');
|
this.$scrollBottomBtn = $('.js-scroll-down');
|
||||||
|
|
||||||
clearTimeout(Build.timeout);
|
clearTimeout(Build.timeout);
|
||||||
// Init breakpoint checker
|
|
||||||
this.bp = Breakpoints.get();
|
|
||||||
|
|
||||||
this.initSidebar();
|
this.initSidebar();
|
||||||
this.populateJobs(this.buildStage);
|
this.populateJobs(this.buildStage);
|
||||||
|
@ -230,7 +227,7 @@ window.Build = (function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
Build.prototype.shouldHideSidebarForViewport = function () {
|
Build.prototype.shouldHideSidebarForViewport = function () {
|
||||||
const bootstrapBreakpoint = this.bp.getBreakpointSize();
|
const bootstrapBreakpoint = bp.getBreakpointSize();
|
||||||
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
|
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -347,6 +347,9 @@ import initChangesDropdown from './init_changes_dropdown';
|
||||||
if ($('#tree-slider').length) new TreeView();
|
if ($('#tree-slider').length) new TreeView();
|
||||||
if ($('.blob-viewer').length) new BlobViewer();
|
if ($('.blob-viewer').length) new BlobViewer();
|
||||||
if ($('.project-show-activity').length) new gl.Activities();
|
if ($('.project-show-activity').length) new gl.Activities();
|
||||||
|
$('#tree-slider').waitForImages(function() {
|
||||||
|
gl.utils.ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case 'projects:edit':
|
case 'projects:edit':
|
||||||
setupProjectEdit();
|
setupProjectEdit();
|
||||||
|
|
|
@ -1,6 +1,19 @@
|
||||||
/* global bp */
|
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
import './breakpoints';
|
import bp from './breakpoints';
|
||||||
|
|
||||||
|
const HIDE_INTERVAL_TIMEOUT = 300;
|
||||||
|
const IS_OVER_CLASS = 'is-over';
|
||||||
|
const IS_ABOVE_CLASS = 'is-above';
|
||||||
|
const IS_SHOWING_FLY_OUT_CLASS = 'is-showing-fly-out';
|
||||||
|
let currentOpenMenu = null;
|
||||||
|
let menuCornerLocs;
|
||||||
|
let timeoutId;
|
||||||
|
|
||||||
|
export const mousePos = [];
|
||||||
|
|
||||||
|
export const setOpenMenu = (menu = null) => { currentOpenMenu = menu; };
|
||||||
|
|
||||||
|
export const slope = (a, b) => (b.y - a.y) / (b.x - a.x);
|
||||||
|
|
||||||
export const canShowActiveSubItems = (el) => {
|
export const canShowActiveSubItems = (el) => {
|
||||||
const isHiddenByMedia = bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md';
|
const isHiddenByMedia = bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md';
|
||||||
|
@ -11,8 +24,28 @@ export const canShowActiveSubItems = (el) => {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const canShowSubItems = () => bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md' || bp.getBreakpointSize() === 'lg';
|
export const canShowSubItems = () => bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md' || bp.getBreakpointSize() === 'lg';
|
||||||
|
|
||||||
|
export const getHideSubItemsInterval = () => {
|
||||||
|
if (!currentOpenMenu) return 0;
|
||||||
|
|
||||||
|
const currentMousePos = mousePos[mousePos.length - 1];
|
||||||
|
const prevMousePos = mousePos[0];
|
||||||
|
const currentMousePosY = currentMousePos.y;
|
||||||
|
const [menuTop, menuBottom] = menuCornerLocs;
|
||||||
|
|
||||||
|
if (currentMousePosY < menuTop.y ||
|
||||||
|
currentMousePosY > menuBottom.y) return 0;
|
||||||
|
|
||||||
|
if (slope(prevMousePos, menuBottom) < slope(currentMousePos, menuBottom) &&
|
||||||
|
slope(prevMousePos, menuTop) > slope(currentMousePos, menuTop)) {
|
||||||
|
return HIDE_INTERVAL_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
export const calculateTop = (boundingRect, outerHeight) => {
|
export const calculateTop = (boundingRect, outerHeight) => {
|
||||||
const windowHeight = window.innerHeight;
|
const windowHeight = window.innerHeight;
|
||||||
const bottomOverflow = windowHeight - (boundingRect.top + outerHeight);
|
const bottomOverflow = windowHeight - (boundingRect.top + outerHeight);
|
||||||
|
@ -21,45 +54,118 @@ export const calculateTop = (boundingRect, outerHeight) => {
|
||||||
boundingRect.top;
|
boundingRect.top;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const showSubLevelItems = (el) => {
|
export const hideMenu = (el) => {
|
||||||
const subItems = el.querySelector('.sidebar-sub-level-items');
|
if (!el) return;
|
||||||
|
|
||||||
if (!subItems || !canShowSubItems() || !canShowActiveSubItems(el)) return;
|
const parentEl = el.parentNode;
|
||||||
|
|
||||||
subItems.style.display = 'block';
|
el.style.display = ''; // eslint-disable-line no-param-reassign
|
||||||
el.classList.add('is-showing-fly-out');
|
el.style.transform = ''; // eslint-disable-line no-param-reassign
|
||||||
el.classList.add('is-over');
|
el.classList.remove(IS_ABOVE_CLASS);
|
||||||
|
parentEl.classList.remove(IS_OVER_CLASS);
|
||||||
|
parentEl.classList.remove(IS_SHOWING_FLY_OUT_CLASS);
|
||||||
|
|
||||||
|
setOpenMenu();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const moveSubItemsToPosition = (el, subItems) => {
|
||||||
const boundingRect = el.getBoundingClientRect();
|
const boundingRect = el.getBoundingClientRect();
|
||||||
const top = calculateTop(boundingRect, subItems.offsetHeight);
|
const top = calculateTop(boundingRect, subItems.offsetHeight);
|
||||||
const isAbove = top < boundingRect.top;
|
const isAbove = top < boundingRect.top;
|
||||||
|
|
||||||
subItems.classList.add('fly-out-list');
|
subItems.classList.add('fly-out-list');
|
||||||
subItems.style.transform = `translate3d(0, ${Math.floor(top)}px, 0)`;
|
subItems.style.transform = `translate3d(0, ${Math.floor(top)}px, 0)`; // eslint-disable-line no-param-reassign
|
||||||
|
|
||||||
|
const subItemsRect = subItems.getBoundingClientRect();
|
||||||
|
|
||||||
|
menuCornerLocs = [
|
||||||
|
{
|
||||||
|
x: subItemsRect.left, // left position of the sub items
|
||||||
|
y: subItemsRect.top, // top position of the sub items
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: subItemsRect.left, // left position of the sub items
|
||||||
|
y: subItemsRect.top + subItemsRect.height, // bottom position of the sub items
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
if (isAbove) {
|
if (isAbove) {
|
||||||
subItems.classList.add('is-above');
|
subItems.classList.add(IS_ABOVE_CLASS);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const hideSubLevelItems = (el) => {
|
export const showSubLevelItems = (el) => {
|
||||||
const subItems = el.querySelector('.sidebar-sub-level-items');
|
const subItems = el.querySelector('.sidebar-sub-level-items');
|
||||||
|
|
||||||
if (!subItems || !canShowSubItems() || !canShowActiveSubItems(el)) return;
|
if (!canShowSubItems() || !canShowActiveSubItems(el)) return;
|
||||||
|
|
||||||
el.classList.remove('is-showing-fly-out');
|
el.classList.add(IS_OVER_CLASS);
|
||||||
el.classList.remove('is-over');
|
|
||||||
subItems.style.display = '';
|
if (!subItems) return;
|
||||||
subItems.style.transform = '';
|
|
||||||
subItems.classList.remove('is-above');
|
subItems.style.display = 'block';
|
||||||
|
el.classList.add(IS_SHOWING_FLY_OUT_CLASS);
|
||||||
|
|
||||||
|
setOpenMenu(subItems);
|
||||||
|
moveSubItemsToPosition(el, subItems);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mouseEnterTopItems = (el) => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
if (currentOpenMenu) hideMenu(currentOpenMenu);
|
||||||
|
|
||||||
|
showSubLevelItems(el);
|
||||||
|
}, getHideSubItemsInterval());
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mouseLeaveTopItem = (el) => {
|
||||||
|
const subItems = el.querySelector('.sidebar-sub-level-items');
|
||||||
|
|
||||||
|
if (!canShowSubItems() || !canShowActiveSubItems(el) ||
|
||||||
|
(subItems && subItems === currentOpenMenu)) return;
|
||||||
|
|
||||||
|
el.classList.remove(IS_OVER_CLASS);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const documentMouseMove = (e) => {
|
||||||
|
mousePos.push({
|
||||||
|
x: e.clientX,
|
||||||
|
y: e.clientY,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mousePos.length > 6) mousePos.shift();
|
||||||
};
|
};
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const items = [...document.querySelectorAll('.sidebar-top-level-items > li')]
|
const sidebar = document.querySelector('.sidebar-top-level-items');
|
||||||
.filter(el => el.querySelector('.sidebar-sub-level-items'));
|
|
||||||
|
if (!sidebar) return;
|
||||||
|
|
||||||
|
const items = [...sidebar.querySelectorAll('.sidebar-top-level-items > li')];
|
||||||
|
|
||||||
|
sidebar.addEventListener('mouseleave', () => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
if (currentOpenMenu) hideMenu(currentOpenMenu);
|
||||||
|
}, getHideSubItemsInterval());
|
||||||
|
});
|
||||||
|
|
||||||
items.forEach((el) => {
|
items.forEach((el) => {
|
||||||
el.addEventListener('mouseenter', e => showSubLevelItems(e.currentTarget));
|
const subItems = el.querySelector('.sidebar-sub-level-items');
|
||||||
el.addEventListener('mouseleave', e => hideSubLevelItems(e.currentTarget));
|
|
||||||
|
if (subItems) {
|
||||||
|
subItems.addEventListener('mouseleave', () => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
hideMenu(currentOpenMenu);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
el.addEventListener('mouseenter', e => mouseEnterTopItems(e.currentTarget));
|
||||||
|
el.addEventListener('mouseleave', e => mouseLeaveTopItem(e.currentTarget));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', documentMouseMove);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
export default class GpgBadges {
|
export default class GpgBadges {
|
||||||
static fetch() {
|
static fetch() {
|
||||||
|
const badges = $('.js-loading-gpg-badge');
|
||||||
const form = $('.commits-search-form');
|
const form = $('.commits-search-form');
|
||||||
|
|
||||||
|
badges.html('<i class="fa fa-spinner fa-spin"></i>');
|
||||||
|
|
||||||
$.get({
|
$.get({
|
||||||
url: form.data('signatures-path'),
|
url: form.data('signatures-path'),
|
||||||
data: form.serialize(),
|
data: form.serialize(),
|
||||||
}).done((response) => {
|
}).done((response) => {
|
||||||
const badges = $('.js-loading-gpg-badge');
|
|
||||||
response.signatures.forEach((signature) => {
|
response.signatures.forEach((signature) => {
|
||||||
badges.filter(`[data-commit-sha="${signature.commit_sha}"]`).replaceWith(signature.html);
|
badges.filter(`[data-commit-sha="${signature.commit_sha}"]`).replaceWith(signature.html);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, comma-dangle, quotes, prefer-arrow-callback, consistent-return, one-var, no-var, one-var-declaration-per-line, no-underscore-dangle, max-len */
|
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, comma-dangle, quotes, prefer-arrow-callback, consistent-return, one-var, no-var, one-var-declaration-per-line, no-underscore-dangle, max-len */
|
||||||
/* global bp */
|
|
||||||
|
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
|
import bp from './breakpoints';
|
||||||
import UsersSelect from './users_select';
|
import UsersSelect from './users_select';
|
||||||
|
|
||||||
const PARTICIPANTS_ROW_COUNT = 7;
|
const PARTICIPANTS_ROW_COUNT = 7;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
/* eslint-disable func-names, space-before-function-paren, no-var, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len, no-multi-spaces, import/newline-after-import, import/first */
|
/* eslint-disable func-names, space-before-function-paren, no-var, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len, no-multi-spaces, import/newline-after-import, import/first */
|
||||||
/* global bp */
|
|
||||||
/* global Flash */
|
/* global Flash */
|
||||||
/* global ConfirmDangerModal */
|
/* global ConfirmDangerModal */
|
||||||
/* global Aside */
|
/* global Aside */
|
||||||
|
@ -66,7 +65,7 @@ import './api';
|
||||||
import './aside';
|
import './aside';
|
||||||
import './autosave';
|
import './autosave';
|
||||||
import loadAwardsHandler from './awards_handler';
|
import loadAwardsHandler from './awards_handler';
|
||||||
import './breakpoints';
|
import bp from './breakpoints';
|
||||||
import './broadcast_message';
|
import './broadcast_message';
|
||||||
import './build';
|
import './build';
|
||||||
import './build_artifacts';
|
import './build_artifacts';
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
/* eslint-disable no-new, class-methods-use-this */
|
/* eslint-disable no-new, class-methods-use-this */
|
||||||
/* global Breakpoints */
|
|
||||||
/* global Flash */
|
/* global Flash */
|
||||||
/* global notes */
|
/* global notes */
|
||||||
|
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
import './breakpoints';
|
|
||||||
import './flash';
|
import './flash';
|
||||||
import BlobForkSuggestion from './blob/blob_fork_suggestion';
|
import BlobForkSuggestion from './blob/blob_fork_suggestion';
|
||||||
import initChangesDropdown from './init_changes_dropdown';
|
import initChangesDropdown from './init_changes_dropdown';
|
||||||
|
import bp from './breakpoints';
|
||||||
|
|
||||||
/* eslint-disable max-len */
|
/* eslint-disable max-len */
|
||||||
// MergeRequestTabs
|
// MergeRequestTabs
|
||||||
|
@ -134,7 +133,7 @@ import initChangesDropdown from './init_changes_dropdown';
|
||||||
this.destroyPipelinesView();
|
this.destroyPipelinesView();
|
||||||
} else if (this.isDiffAction(action)) {
|
} else if (this.isDiffAction(action)) {
|
||||||
this.loadDiff($target.attr('href'));
|
this.loadDiff($target.attr('href'));
|
||||||
if (Breakpoints.get().getBreakpointSize() !== 'lg') {
|
if (bp.getBreakpointSize() !== 'lg') {
|
||||||
this.shrinkView();
|
this.shrinkView();
|
||||||
}
|
}
|
||||||
if (this.diffViewType() === 'parallel') {
|
if (this.diffViewType() === 'parallel') {
|
||||||
|
@ -145,7 +144,7 @@ import initChangesDropdown from './init_changes_dropdown';
|
||||||
this.resetViewContainer();
|
this.resetViewContainer();
|
||||||
this.mountPipelinesView();
|
this.mountPipelinesView();
|
||||||
} else {
|
} else {
|
||||||
if (Breakpoints.get().getBreakpointSize() !== 'xs') {
|
if (bp.getBreakpointSize() !== 'xs') {
|
||||||
this.expandView();
|
this.expandView();
|
||||||
}
|
}
|
||||||
this.resetViewContainer();
|
this.resetViewContainer();
|
||||||
|
@ -392,7 +391,7 @@ import initChangesDropdown from './init_changes_dropdown';
|
||||||
|
|
||||||
// Screen space on small screens is usually very sparse
|
// Screen space on small screens is usually very sparse
|
||||||
// So we dont affix the tabs on these
|
// So we dont affix the tabs on these
|
||||||
if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return;
|
if (bp.getBreakpointSize() === 'xs' || !$tabs.length) return;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
If the browser does not support position sticky, it returns the position as static.
|
If the browser does not support position sticky, it returns the position as static.
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script>
|
<script>
|
||||||
/* global Breakpoints */
|
|
||||||
import d3 from 'd3';
|
import d3 from 'd3';
|
||||||
import monitoringLegends from './monitoring_legends.vue';
|
import monitoringLegends from './monitoring_legends.vue';
|
||||||
import monitoringFlag from './monitoring_flag.vue';
|
import monitoringFlag from './monitoring_flag.vue';
|
||||||
|
@ -8,6 +7,7 @@
|
||||||
import eventHub from '../event_hub';
|
import eventHub from '../event_hub';
|
||||||
import measurements from '../utils/measurements';
|
import measurements from '../utils/measurements';
|
||||||
import { formatRelevantDigits } from '../../lib/utils/number_utils';
|
import { formatRelevantDigits } from '../../lib/utils/number_utils';
|
||||||
|
import bp from '../../breakpoints';
|
||||||
|
|
||||||
const bisectDate = d3.bisector(d => d.time).left;
|
const bisectDate = d3.bisector(d => d.time).left;
|
||||||
|
|
||||||
|
@ -42,7 +42,6 @@
|
||||||
yScale: {},
|
yScale: {},
|
||||||
margin: {},
|
margin: {},
|
||||||
data: [],
|
data: [],
|
||||||
breakpointHandler: Breakpoints.get(),
|
|
||||||
unitOfDisplay: '',
|
unitOfDisplay: '',
|
||||||
areaColorRgb: '#8fbce8',
|
areaColorRgb: '#8fbce8',
|
||||||
lineColorRgb: '#1f78d1',
|
lineColorRgb: '#1f78d1',
|
||||||
|
@ -96,7 +95,7 @@
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
draw() {
|
draw() {
|
||||||
const breakpointSize = this.breakpointHandler.getBreakpointSize();
|
const breakpointSize = bp.getBreakpointSize();
|
||||||
const query = this.columnData.queries[0];
|
const query = this.columnData.queries[0];
|
||||||
this.margin = measurements.large.margin;
|
this.margin = measurements.large.margin;
|
||||||
if (breakpointSize === 'xs' || breakpointSize === 'sm') {
|
if (breakpointSize === 'xs' || breakpointSize === 'sm') {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
/* global bp */
|
import bp from './breakpoints';
|
||||||
import './breakpoints';
|
|
||||||
|
|
||||||
export default class NewNavSidebar {
|
export default class NewNavSidebar {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
import Vue from 'vue';
|
|
||||||
import Cookies from 'js-cookie';
|
|
||||||
import Translate from '../../vue_shared/translate';
|
|
||||||
import illustrationSvg from '../icons/intro_illustration.svg';
|
|
||||||
|
|
||||||
Vue.use(Translate);
|
|
||||||
|
|
||||||
const cookieKey = 'pipeline_schedules_callout_dismissed';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'PipelineSchedulesCallout',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
docsUrl: document.getElementById('pipeline-schedules-callout').dataset.docsUrl,
|
|
||||||
illustrationSvg,
|
|
||||||
calloutDismissed: Cookies.get(cookieKey) === 'true',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
dismissCallout() {
|
|
||||||
this.calloutDismissed = true;
|
|
||||||
Cookies.set(cookieKey, this.calloutDismissed, { expires: 365 });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
template: `
|
|
||||||
<div v-if="!calloutDismissed" class="pipeline-schedules-user-callout user-callout">
|
|
||||||
<div class="bordered-box landing content-block">
|
|
||||||
<button
|
|
||||||
id="dismiss-callout-btn"
|
|
||||||
class="btn btn-default close"
|
|
||||||
@click="dismissCallout">
|
|
||||||
<i class="fa fa-times"></i>
|
|
||||||
</button>
|
|
||||||
<div class="svg-container" v-html="illustrationSvg"></div>
|
|
||||||
<div class="user-callout-copy">
|
|
||||||
<h4>{{ __('Scheduling Pipelines') }}</h4>
|
|
||||||
<p>
|
|
||||||
{{ __('The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.') }}
|
|
||||||
</p>
|
|
||||||
<p> {{ __('Learn more in the') }}
|
|
||||||
<a
|
|
||||||
:href="docsUrl"
|
|
||||||
target="_blank"
|
|
||||||
rel="nofollow">{{ s__('Learn more in the|pipeline schedules documentation') }}</a>. <!-- oneline to prevent extra space before period -->
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
};
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
<script>
|
||||||
|
import Vue from 'vue';
|
||||||
|
import Cookies from 'js-cookie';
|
||||||
|
import Translate from '../../vue_shared/translate';
|
||||||
|
import illustrationSvg from '../icons/intro_illustration.svg';
|
||||||
|
|
||||||
|
Vue.use(Translate);
|
||||||
|
|
||||||
|
const cookieKey = 'pipeline_schedules_callout_dismissed';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'PipelineSchedulesCallout',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
docsUrl: document.getElementById('pipeline-schedules-callout').dataset.docsUrl,
|
||||||
|
calloutDismissed: Cookies.get(cookieKey) === 'true',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
dismissCallout() {
|
||||||
|
this.calloutDismissed = true;
|
||||||
|
Cookies.set(cookieKey, this.calloutDismissed, { expires: 365 });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.illustrationSvg = illustrationSvg;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="!calloutDismissed"
|
||||||
|
class="pipeline-schedules-user-callout user-callout">
|
||||||
|
<div class="bordered-box landing content-block">
|
||||||
|
<button
|
||||||
|
id="dismiss-callout-btn"
|
||||||
|
class="btn btn-default close"
|
||||||
|
@click="dismissCallout">
|
||||||
|
<i
|
||||||
|
aria-hidden="true"
|
||||||
|
class="fa fa-times">
|
||||||
|
</i>
|
||||||
|
</button>
|
||||||
|
<div class="svg-container" v-html="illustrationSvg"></div>
|
||||||
|
<div class="user-callout-copy">
|
||||||
|
<h4>{{ __('Scheduling Pipelines') }}</h4>
|
||||||
|
<p>
|
||||||
|
{{ __('The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.') }}
|
||||||
|
</p>
|
||||||
|
<p> {{ __('Learn more in the') }}
|
||||||
|
<a
|
||||||
|
:href="docsUrl"
|
||||||
|
target="_blank"
|
||||||
|
rel="nofollow">{{ s__('Learn more in the|pipeline schedules documentation') }}</a>. <!-- oneline to prevent extra space before period -->
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -1,5 +1,5 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import PipelineSchedulesCallout from './components/pipeline_schedules_callout';
|
import PipelineSchedulesCallout from './components/pipeline_schedules_callout.vue';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => new Vue({
|
document.addEventListener('DOMContentLoaded', () => new Vue({
|
||||||
el: '#pipeline-schedules-callout',
|
el: '#pipeline-schedules-callout',
|
||||||
|
|
|
@ -48,6 +48,27 @@
|
||||||
return `${this.job.name} - ${this.job.status.label}`;
|
return `${this.job.name} - ${this.job.status.label}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* When the user right clicks or cmd/ctrl + click in the job name
|
||||||
|
* the dropdown should not be closed and the link should open in another tab,
|
||||||
|
* so we stop propagation of the click event inside the dropdown.
|
||||||
|
*
|
||||||
|
* Since this component is rendered multiple times per page we need to guarantee we only
|
||||||
|
* target the click event of this component.
|
||||||
|
*/
|
||||||
|
stopDropdownClickPropagation() {
|
||||||
|
$(this.$el.querySelectorAll('.js-grouped-pipeline-dropdown a.mini-pipeline-graph-dropdown-item'))
|
||||||
|
.on('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.stopDropdownClickPropagation();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -36,7 +36,7 @@ const bindEvents = () => {
|
||||||
|
|
||||||
$('.how_to_import_link').on('click', (e) => {
|
$('.how_to_import_link').on('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$('.how_to_import_link').next('.modal').show();
|
$(e.currentTarget).next('.modal').show();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.modal-header .close').on('click', () => {
|
$('.modal-header .close').on('click', () => {
|
||||||
|
|
|
@ -29,12 +29,10 @@ export default {
|
||||||
editMode() {
|
editMode() {
|
||||||
if (this.editMode) {
|
if (this.editMode) {
|
||||||
$('.project-refs-form').addClass('disabled');
|
$('.project-refs-form').addClass('disabled');
|
||||||
$('.fa-long-arrow-right').show();
|
$('.js-tree-ref-target-holder').show();
|
||||||
$('.project-refs-target-form').show();
|
|
||||||
} else {
|
} else {
|
||||||
$('.project-refs-form').removeClass('disabled');
|
$('.project-refs-form').removeClass('disabled');
|
||||||
$('.fa-long-arrow-right').hide();
|
$('.js-tree-ref-target-holder').hide();
|
||||||
$('.project-refs-target-form').hide();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,7 @@ import Store from '../stores/repo_store';
|
||||||
export default {
|
export default {
|
||||||
data: () => Store,
|
data: () => Store,
|
||||||
mounted() {
|
mounted() {
|
||||||
$(this.$el).find('.file-content').syntaxHighlight();
|
this.highlightFile();
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
html() {
|
html() {
|
||||||
|
@ -12,10 +12,16 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
highlightFile() {
|
||||||
|
$(this.$el).find('.file-content').syntaxHighlight();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
html() {
|
html() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
$(this.$el).find('.file-content').syntaxHighlight();
|
this.highlightFile();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -24,9 +30,23 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="!activeFile.render_error" v-html="activeFile.html"></div>
|
<div
|
||||||
<div v-if="activeFile.render_error" class="vertical-center render-error">
|
v-if="!activeFile.render_error"
|
||||||
<p class="text-center">The source could not be displayed because it is too large. You can <a :href="activeFile.raw_path">download</a> it instead.</p>
|
v-html="activeFile.html">
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="activeFile.tooLarge"
|
||||||
|
class="vertical-center render-error">
|
||||||
|
<p class="text-center">
|
||||||
|
The source could not be displayed because it is too large. You can <a :href="activeFile.raw_path">download</a> it instead.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="vertical-center render-error">
|
||||||
|
<p class="text-center">
|
||||||
|
The source could not be displayed because a rendering error occured. You can <a :href="activeFile.raw_path">download</a> it instead.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -33,32 +33,30 @@ const RepoSidebar = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
linkClicked(clickedFile) {
|
fileClicked(clickedFile) {
|
||||||
let url = '';
|
|
||||||
let file = clickedFile;
|
let file = clickedFile;
|
||||||
if (typeof file === 'object') {
|
|
||||||
file.loading = true;
|
file.loading = true;
|
||||||
if (file.type === 'tree' && file.opened) {
|
if (file.type === 'tree' && file.opened) {
|
||||||
file = Store.removeChildFilesOfTree(file);
|
file = Store.removeChildFilesOfTree(file);
|
||||||
file.loading = false;
|
file.loading = false;
|
||||||
} else {
|
} else {
|
||||||
url = file.url;
|
Service.url = file.url;
|
||||||
Service.url = url;
|
Helper.getContent(file)
|
||||||
// I need to refactor this to do the `then` here.
|
.then(() => {
|
||||||
// Not a callback. For now this is good enough.
|
|
||||||
// it works.
|
|
||||||
Helper.getContent(file, () => {
|
|
||||||
file.loading = false;
|
file.loading = false;
|
||||||
Helper.scrollTabsRight();
|
Helper.scrollTabsRight();
|
||||||
});
|
})
|
||||||
}
|
.catch(Helper.loadingError);
|
||||||
} else if (typeof file === 'string') {
|
|
||||||
// go back
|
|
||||||
url = file;
|
|
||||||
Service.url = url;
|
|
||||||
Helper.getContent(null, () => Helper.scrollTabsRight());
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
goToPreviousDirectoryClicked(prevURL) {
|
||||||
|
Service.url = prevURL;
|
||||||
|
Helper.getContent(null)
|
||||||
|
.then(() => Helper.scrollTabsRight())
|
||||||
|
.catch(Helper.loadingError);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -82,7 +80,7 @@ export default RepoSidebar;
|
||||||
<repo-previous-directory
|
<repo-previous-directory
|
||||||
v-if="isRoot"
|
v-if="isRoot"
|
||||||
:prev-url="prevURL"
|
:prev-url="prevURL"
|
||||||
@linkclicked="linkClicked(prevURL)"/>
|
@linkclicked="goToPreviousDirectoryClicked(prevURL)"/>
|
||||||
<repo-loading-file
|
<repo-loading-file
|
||||||
v-for="n in 5"
|
v-for="n in 5"
|
||||||
:key="n"
|
:key="n"
|
||||||
|
@ -94,7 +92,7 @@ export default RepoSidebar;
|
||||||
:key="file.id"
|
:key="file.id"
|
||||||
:file="file"
|
:file="file"
|
||||||
:is-mini="isMini"
|
:is-mini="isMini"
|
||||||
@linkclicked="linkClicked(file)"
|
@linkclicked="fileClicked(file)"
|
||||||
:is-tree="isTree"
|
:is-tree="isTree"
|
||||||
:has-files="!!files.length"
|
:has-files="!!files.length"
|
||||||
:active-file="activeFile"/>
|
:active-file="activeFile"/>
|
||||||
|
|
|
@ -10,6 +10,12 @@ const RepoTab = {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
closeLabel() {
|
||||||
|
if (this.tab.changed) {
|
||||||
|
return `${this.tab.name} changed`;
|
||||||
|
}
|
||||||
|
return `Close ${this.tab.name}`;
|
||||||
|
},
|
||||||
changedClass() {
|
changedClass() {
|
||||||
const tabChangedObj = {
|
const tabChangedObj = {
|
||||||
'fa-times': !this.tab.changed,
|
'fa-times': !this.tab.changed,
|
||||||
|
@ -34,12 +40,24 @@ export default RepoTab;
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" class="close" @click.prevent="xClicked(tab)" v-if="!tab.loading">
|
<a
|
||||||
<i class="fa" :class="changedClass"></i>
|
href="#0"
|
||||||
|
class="close"
|
||||||
|
@click.prevent="xClicked(tab)"
|
||||||
|
:aria-label="closeLabel">
|
||||||
|
<i
|
||||||
|
class="fa"
|
||||||
|
:class="changedClass"
|
||||||
|
aria-hidden="true">
|
||||||
|
</i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="#" class="repo-tab" v-if="!tab.loading" :title="tab.url" @click.prevent="tabClicked(tab)">{{tab.name}}</a>
|
<a
|
||||||
|
href="#"
|
||||||
<i v-if="tab.loading" class="fa fa-spinner fa-spin"></i>
|
class="repo-tab"
|
||||||
|
:title="tab.url"
|
||||||
|
@click.prevent="tabClicked(tab)">
|
||||||
|
{{tab.name}}
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script>
|
<script>
|
||||||
import Vue from 'vue';
|
|
||||||
import Store from '../stores/repo_store';
|
import Store from '../stores/repo_store';
|
||||||
import RepoTab from './repo_tab.vue';
|
import RepoTab from './repo_tab.vue';
|
||||||
import RepoMixin from '../mixins/repo_mixin';
|
import RepoMixin from '../mixins/repo_mixin';
|
||||||
|
@ -14,29 +13,19 @@ const RepoTabs = {
|
||||||
data: () => Store,
|
data: () => Store,
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
isOverflow() {
|
|
||||||
return this.$el.scrollWidth > this.$el.offsetWidth;
|
|
||||||
},
|
|
||||||
|
|
||||||
xClicked(file) {
|
xClicked(file) {
|
||||||
Store.removeFromOpenedFiles(file);
|
Store.removeFromOpenedFiles(file);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
|
||||||
openedFiles() {
|
|
||||||
Vue.nextTick(() => {
|
|
||||||
this.tabsOverflow = this.isOverflow();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RepoTabs;
|
export default RepoTabs;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ul id="tabs" v-if="isMini" v-cloak :class="{'overflown': tabsOverflow}">
|
<ul
|
||||||
|
v-if="isMini"
|
||||||
|
id="tabs">
|
||||||
<repo-tab v-for="tab in openedFiles" :key="tab.id" :tab="tab" :class="{'active' : tab.active}" @xclicked="xClicked"/>
|
<repo-tab v-for="tab in openedFiles" :key="tab.id" :tab="tab" :class="{'active' : tab.active}" @xclicked="xClicked"/>
|
||||||
<li class="tabs-divider" />
|
<li class="tabs-divider" />
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -10,7 +10,10 @@ function repoEditorLoader() {
|
||||||
Store.monaco = monaco;
|
Store.monaco = monaco;
|
||||||
Store.monacoLoading = false;
|
Store.monacoLoading = false;
|
||||||
resolve(RepoEditor);
|
resolve(RepoEditor);
|
||||||
}, reject);
|
}, () => {
|
||||||
|
Store.monacoLoading = false;
|
||||||
|
reject();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,12 +33,16 @@ const RepoHelper = {
|
||||||
? window.performance
|
? window.performance
|
||||||
: Date,
|
: Date,
|
||||||
|
|
||||||
|
getFileExtension(fileName) {
|
||||||
|
return fileName.split('.').pop();
|
||||||
|
},
|
||||||
|
|
||||||
getBranch() {
|
getBranch() {
|
||||||
return $('button.dropdown-menu-toggle').attr('data-ref');
|
return $('button.dropdown-menu-toggle').attr('data-ref');
|
||||||
},
|
},
|
||||||
|
|
||||||
getLanguageIDForFile(file, langs) {
|
getLanguageIDForFile(file, langs) {
|
||||||
const ext = file.name.split('.').pop();
|
const ext = RepoHelper.getFileExtension(file.name);
|
||||||
const foundLang = RepoHelper.findLanguage(ext, langs);
|
const foundLang = RepoHelper.findLanguage(ext, langs);
|
||||||
|
|
||||||
return foundLang ? foundLang.id : 'plaintext';
|
return foundLang ? foundLang.id : 'plaintext';
|
||||||
|
@ -135,21 +139,19 @@ const RepoHelper = {
|
||||||
return isRoot;
|
return isRoot;
|
||||||
},
|
},
|
||||||
|
|
||||||
getContent(treeOrFile, cb) {
|
getContent(treeOrFile) {
|
||||||
let file = treeOrFile;
|
let file = treeOrFile;
|
||||||
// const loadingData = RepoHelper.setLoading(true);
|
// const loadingData = RepoHelper.setLoading(true);
|
||||||
return Service.getContent()
|
return Service.getContent()
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
// RepoHelper.setLoading(false, loadingData);
|
// RepoHelper.setLoading(false, loadingData);
|
||||||
if (cb) cb();
|
|
||||||
Store.isTree = RepoHelper.isTree(data);
|
Store.isTree = RepoHelper.isTree(data);
|
||||||
if (!Store.isTree) {
|
if (!Store.isTree) {
|
||||||
if (!file) file = data;
|
if (!file) file = data;
|
||||||
Store.binary = data.binary;
|
Store.binary = data.binary;
|
||||||
|
|
||||||
if (data.binary) {
|
if (data.binary) {
|
||||||
Store.binaryMimeType = data.mime_type;
|
|
||||||
// file might be undefined
|
// file might be undefined
|
||||||
RepoHelper.setBinaryDataAsBase64(data);
|
RepoHelper.setBinaryDataAsBase64(data);
|
||||||
Store.setViewToPreview();
|
Store.setViewToPreview();
|
||||||
|
@ -188,9 +190,8 @@ const RepoHelper = {
|
||||||
setFile(data, file) {
|
setFile(data, file) {
|
||||||
const newFile = data;
|
const newFile = data;
|
||||||
|
|
||||||
newFile.url = file.url || location.pathname;
|
|
||||||
newFile.url = file.url;
|
newFile.url = file.url;
|
||||||
if (newFile.render_error === 'too_large') {
|
if (newFile.render_error === 'too_large' || newFile.render_error === 'collapsed') {
|
||||||
newFile.tooLarge = true;
|
newFile.tooLarge = true;
|
||||||
}
|
}
|
||||||
newFile.newContent = '';
|
newFile.newContent = '';
|
||||||
|
@ -199,10 +200,6 @@ const RepoHelper = {
|
||||||
Store.setActiveFiles(newFile);
|
Store.setActiveFiles(newFile);
|
||||||
},
|
},
|
||||||
|
|
||||||
toFA(icon) {
|
|
||||||
return `fa-${icon}`;
|
|
||||||
},
|
|
||||||
|
|
||||||
serializeBlob(blob) {
|
serializeBlob(blob) {
|
||||||
const simpleBlob = RepoHelper.serializeRepoEntity('blob', blob);
|
const simpleBlob = RepoHelper.serializeRepoEntity('blob', blob);
|
||||||
simpleBlob.lastCommitMessage = blob.last_commit.message;
|
simpleBlob.lastCommitMessage = blob.last_commit.message;
|
||||||
|
@ -226,7 +223,7 @@ const RepoHelper = {
|
||||||
type,
|
type,
|
||||||
name,
|
name,
|
||||||
url,
|
url,
|
||||||
icon: RepoHelper.toFA(icon),
|
icon: `fa-${icon}`,
|
||||||
level: 0,
|
level: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
};
|
};
|
||||||
|
@ -244,7 +241,7 @@ const RepoHelper = {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const tabs = document.getElementById('tabs');
|
const tabs = document.getElementById('tabs');
|
||||||
if (!tabs) return;
|
if (!tabs) return;
|
||||||
tabs.scrollLeft = 12000;
|
tabs.scrollLeft = tabs.scrollWidth;
|
||||||
}, 200);
|
}, 200);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@ import RepoEditButton from './components/repo_edit_button.vue';
|
||||||
import Translate from '../vue_shared/translate';
|
import Translate from '../vue_shared/translate';
|
||||||
|
|
||||||
function initDropdowns() {
|
function initDropdowns() {
|
||||||
$('.project-refs-target-form').hide();
|
$('.js-tree-ref-target-holder').hide();
|
||||||
$('.fa-long-arrow-right').hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addEventsForNonVueEls() {
|
function addEventsForNonVueEls() {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Store from '../stores/repo_store';
|
import Store from '../stores/repo_store';
|
||||||
import Api from '../../api';
|
import Api from '../../api';
|
||||||
|
import Helper from '../helpers/repo_helper';
|
||||||
|
|
||||||
const RepoService = {
|
const RepoService = {
|
||||||
url: '',
|
url: '',
|
||||||
|
@ -22,6 +23,7 @@ const RepoService = {
|
||||||
|
|
||||||
getRaw(url) {
|
getRaw(url) {
|
||||||
return axios.get(url, {
|
return axios.get(url, {
|
||||||
|
// Stop Axios from parsing a JSON file into a JS object
|
||||||
transformResponse: [res => res],
|
transformResponse: [res => res],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -36,7 +38,7 @@ const RepoService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
urlIsRichBlob(url = this.url) {
|
urlIsRichBlob(url = this.url) {
|
||||||
const extension = url.split('.').pop();
|
const extension = Helper.getFileExtension(url);
|
||||||
|
|
||||||
return this.richExtensionRegExp.test(extension);
|
return this.richExtensionRegExp.test(extension);
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,13 +3,10 @@ import Helper from '../helpers/repo_helper';
|
||||||
import Service from '../services/repo_service';
|
import Service from '../services/repo_service';
|
||||||
|
|
||||||
const RepoStore = {
|
const RepoStore = {
|
||||||
ideEl: {},
|
|
||||||
monaco: {},
|
monaco: {},
|
||||||
monacoLoading: false,
|
monacoLoading: false,
|
||||||
monacoInstance: {},
|
monacoInstance: {},
|
||||||
service: '',
|
service: '',
|
||||||
editor: '',
|
|
||||||
sidebar: '',
|
|
||||||
editMode: false,
|
editMode: false,
|
||||||
isTree: false,
|
isTree: false,
|
||||||
isRoot: false,
|
isRoot: false,
|
||||||
|
@ -17,19 +14,10 @@ const RepoStore = {
|
||||||
projectId: '',
|
projectId: '',
|
||||||
projectName: '',
|
projectName: '',
|
||||||
projectUrl: '',
|
projectUrl: '',
|
||||||
trees: [],
|
|
||||||
blobs: [],
|
|
||||||
submodules: [],
|
|
||||||
blobRaw: '',
|
blobRaw: '',
|
||||||
blobRendered: '',
|
|
||||||
currentBlobView: 'repo-preview',
|
currentBlobView: 'repo-preview',
|
||||||
openedFiles: [],
|
openedFiles: [],
|
||||||
tabSize: 100,
|
|
||||||
defaultTabSize: 100,
|
|
||||||
minTabSize: 30,
|
|
||||||
tabsOverflow: 41,
|
|
||||||
submitCommitsLoading: false,
|
submitCommitsLoading: false,
|
||||||
binaryLoaded: false,
|
|
||||||
dialog: {
|
dialog: {
|
||||||
open: false,
|
open: false,
|
||||||
title: '',
|
title: '',
|
||||||
|
@ -45,9 +33,6 @@ const RepoStore = {
|
||||||
currentBranch: '',
|
currentBranch: '',
|
||||||
targetBranch: 'new-branch',
|
targetBranch: 'new-branch',
|
||||||
commitMessage: '',
|
commitMessage: '',
|
||||||
binaryMimeType: '',
|
|
||||||
// scroll bar space for windows
|
|
||||||
scrollWidth: 0,
|
|
||||||
binaryTypes: {
|
binaryTypes: {
|
||||||
png: false,
|
png: false,
|
||||||
md: false,
|
md: false,
|
||||||
|
@ -58,7 +43,6 @@ const RepoStore = {
|
||||||
tree: false,
|
tree: false,
|
||||||
blob: false,
|
blob: false,
|
||||||
},
|
},
|
||||||
readOnly: true,
|
|
||||||
|
|
||||||
resetBinaryTypes() {
|
resetBinaryTypes() {
|
||||||
Object.keys(RepoStore.binaryTypes).forEach((key) => {
|
Object.keys(RepoStore.binaryTypes).forEach((key) => {
|
||||||
|
@ -96,7 +80,6 @@ const RepoStore = {
|
||||||
|
|
||||||
if (file.binary) {
|
if (file.binary) {
|
||||||
RepoStore.blobRaw = file.base64;
|
RepoStore.blobRaw = file.base64;
|
||||||
RepoStore.binaryMimeType = file.mime_type;
|
|
||||||
} else if (file.newContent || file.plain) {
|
} else if (file.newContent || file.plain) {
|
||||||
RepoStore.blobRaw = file.newContent || file.plain;
|
RepoStore.blobRaw = file.newContent || file.plain;
|
||||||
} else {
|
} else {
|
||||||
|
@ -238,4 +221,5 @@ const RepoStore = {
|
||||||
return RepoStore.currentBlobView === 'repo-preview';
|
return RepoStore.currentBlobView === 'repo-preview';
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RepoStore;
|
export default RepoStore;
|
||||||
|
|
|
@ -71,7 +71,7 @@ export default {
|
||||||
/>
|
/>
|
||||||
<div v-if="!isConfidential" class="no-value confidential-value">
|
<div v-if="!isConfidential" class="no-value confidential-value">
|
||||||
<i class="fa fa-eye is-not-confidential"></i>
|
<i class="fa fa-eye is-not-confidential"></i>
|
||||||
None
|
This issue is not confidential
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="value confidential-value hide-collapsed">
|
<div v-else class="value confidential-value hide-collapsed">
|
||||||
<i aria-hidden="true" data-hidden="true" class="fa fa-eye-slash is-confidential"></i>
|
<i aria-hidden="true" data-hidden="true" class="fa fa-eye-slash is-confidential"></i>
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
/* global Breakpoints */
|
import bp from './breakpoints';
|
||||||
|
|
||||||
import './breakpoints';
|
|
||||||
|
|
||||||
export default class Wikis {
|
export default class Wikis {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.bp = Breakpoints.get();
|
|
||||||
this.sidebarEl = document.querySelector('.js-wiki-sidebar');
|
this.sidebarEl = document.querySelector('.js-wiki-sidebar');
|
||||||
this.sidebarExpanded = false;
|
this.sidebarExpanded = false;
|
||||||
|
|
||||||
|
@ -41,15 +38,15 @@ export default class Wikis {
|
||||||
this.renderSidebar();
|
this.renderSidebar();
|
||||||
}
|
}
|
||||||
|
|
||||||
sidebarCanCollapse() {
|
static sidebarCanCollapse() {
|
||||||
const bootstrapBreakpoint = this.bp.getBreakpointSize();
|
const bootstrapBreakpoint = bp.getBreakpointSize();
|
||||||
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
|
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSidebar() {
|
renderSidebar() {
|
||||||
if (!this.sidebarEl) return;
|
if (!this.sidebarEl) return;
|
||||||
const { classList } = this.sidebarEl;
|
const { classList } = this.sidebarEl;
|
||||||
if (this.sidebarExpanded || !this.sidebarCanCollapse()) {
|
if (this.sidebarExpanded || !Wikis.sidebarCanCollapse()) {
|
||||||
if (!classList.contains('right-sidebar-expanded')) {
|
if (!classList.contains('right-sidebar-expanded')) {
|
||||||
classList.remove('right-sidebar-collapsed');
|
classList.remove('right-sidebar-collapsed');
|
||||||
classList.add('right-sidebar-expanded');
|
classList.add('right-sidebar-expanded');
|
||||||
|
|
|
@ -725,9 +725,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: change global style and remove mixin
|
// TODO: change global style and remove mixin
|
||||||
@mixin new-style-dropdown {
|
@mixin new-style-dropdown($selector: '') {
|
||||||
.dropdown-menu,
|
#{$selector}.dropdown-menu,
|
||||||
.dropdown-menu-nav {
|
#{$selector}.dropdown-menu-nav {
|
||||||
.divider {
|
.divider {
|
||||||
margin: 6px 0;
|
margin: 6px 0;
|
||||||
}
|
}
|
||||||
|
@ -773,7 +773,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu-align-right {
|
#{$selector}.dropdown-menu-align-right {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -339,6 +339,8 @@ a > code {
|
||||||
@extend .ref-name;
|
@extend .ref-name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include new-style-dropdown('.git-revision-dropdown');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply Markdown typography
|
* Apply Markdown typography
|
||||||
*
|
*
|
||||||
|
|
|
@ -403,6 +403,7 @@ header.navbar-gitlab-new {
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumbs-extra {
|
.breadcrumbs-extra {
|
||||||
|
display: flex;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,7 @@ $new-sidebar-collapsed-width: 50px;
|
||||||
|
|
||||||
&.sidebar-icons-only {
|
&.sidebar-icons-only {
|
||||||
width: $new-sidebar-collapsed-width;
|
width: $new-sidebar-collapsed-width;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
.badge,
|
.badge,
|
||||||
.project-title {
|
.project-title {
|
||||||
|
@ -249,32 +250,13 @@ $new-sidebar-collapsed-width: 50px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -30px;
|
top: -30px;
|
||||||
bottom: -30px;
|
bottom: -30px;
|
||||||
left: 0;
|
left: -10px;
|
||||||
right: -30px;
|
right: -30px;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 44px;
|
|
||||||
left: -30px;
|
|
||||||
right: 35px;
|
|
||||||
bottom: 0;
|
|
||||||
height: 100%;
|
|
||||||
max-height: 150px;
|
|
||||||
z-index: -1;
|
|
||||||
transform: skew(33deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-above {
|
&.is-above {
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
|
|
||||||
&::after {
|
|
||||||
top: auto;
|
|
||||||
bottom: 44px;
|
|
||||||
transform: skew(-30deg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .active {
|
> .active {
|
||||||
|
@ -321,8 +303,7 @@ $new-sidebar-collapsed-width: 50px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.active):hover > a,
|
&.active > a:hover,
|
||||||
> a:hover,
|
|
||||||
&.is-over > a {
|
&.is-over > a {
|
||||||
background-color: $white-light;
|
background-color: $white-light;
|
||||||
}
|
}
|
||||||
|
|
|
@ -286,6 +286,10 @@
|
||||||
|
|
||||||
|
|
||||||
.gpg-status-box {
|
.gpg-status-box {
|
||||||
|
&:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
&.valid {
|
&.valid {
|
||||||
@include green-status-color;
|
@include green-status-color;
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,6 +108,7 @@
|
||||||
background-color: $orange-50;
|
background-color: $orange-50;
|
||||||
border-radius: $border-radius-default $border-radius-default 0 0;
|
border-radius: $border-radius-default $border-radius-default 0 0;
|
||||||
border: 1px solid $border-gray-normal;
|
border: 1px solid $border-gray-normal;
|
||||||
|
border-bottom: none;
|
||||||
padding: 3px 12px;
|
padding: 3px 12px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -132,22 +133,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.not-confidential {
|
.confidential-issue-warning + .md-area {
|
||||||
padding: 0;
|
border-top-left-radius: 0;
|
||||||
border-top: none;
|
border-top-right-radius: 0;
|
||||||
}
|
|
||||||
|
|
||||||
.right-sidebar-expanded {
|
|
||||||
.md-area {
|
|
||||||
border-radius: 0;
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-sidebar-collapsed {
|
|
||||||
.confidential-issue-warning {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.discussion-form {
|
.discussion-form {
|
||||||
|
|
|
@ -453,7 +453,10 @@ ul.notes {
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-actions {
|
.note-actions {
|
||||||
|
align-self: flex-start;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
// For PhantomJS that does not support flex
|
// For PhantomJS that does not support flex
|
||||||
float: right;
|
float: right;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
|
@ -463,18 +466,12 @@ ul.notes {
|
||||||
float: none;
|
float: none;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-action-button {
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.more-actions-toggle {
|
|
||||||
margin-left: 2px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.more-actions {
|
.more-actions {
|
||||||
display: inline-block;
|
float: right; // phantomjs fallback
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
|
||||||
.tooltip {
|
.tooltip {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -482,16 +479,10 @@ ul.notes {
|
||||||
}
|
}
|
||||||
|
|
||||||
.more-actions-toggle {
|
.more-actions-toggle {
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
&:hover .icon,
|
&:hover .icon,
|
||||||
&:focus .icon {
|
&:focus .icon {
|
||||||
color: $blue-600;
|
color: $blue-600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
|
||||||
padding: 0 6px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.more-actions-dropdown {
|
.more-actions-dropdown {
|
||||||
|
@ -519,28 +510,42 @@ ul.notes {
|
||||||
@include notes-media('max', $screen-md-max) {
|
@include notes-media('max', $screen-md-max) {
|
||||||
float: none;
|
float: none;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.note-action-button {
|
.note-actions-item {
|
||||||
margin-left: 0;
|
margin-left: 15px;
|
||||||
}
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&.more-actions {
|
||||||
|
// compensate for narrow icon
|
||||||
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-action-button {
|
.note-action-button {
|
||||||
display: inline;
|
line-height: 1;
|
||||||
line-height: 20px;
|
padding: 0;
|
||||||
|
min-width: 16px;
|
||||||
|
color: $gray-darkest;
|
||||||
|
|
||||||
.fa {
|
.fa {
|
||||||
color: $gray-darkest;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 17px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
height: 16px;
|
height: 16px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
fill: $gray-darkest;
|
top: 0;
|
||||||
vertical-align: text-top;
|
vertical-align: text-top;
|
||||||
|
|
||||||
|
path {
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.award-control-icon-positive,
|
.award-control-icon-positive,
|
||||||
|
@ -613,10 +618,7 @@ ul.notes {
|
||||||
|
|
||||||
.note-role {
|
.note-role {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -2px;
|
padding: 0 7px;
|
||||||
display: inline-block;
|
|
||||||
padding-left: 7px;
|
|
||||||
padding-right: 7px;
|
|
||||||
color: $notes-role-color;
|
color: $notes-role-color;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
|
|
@ -824,6 +824,7 @@ button.mini-pipeline-graph-dropdown-toggle {
|
||||||
* Top arrow in the dropdown in the mini pipeline graph
|
* Top arrow in the dropdown in the mini pipeline graph
|
||||||
*/
|
*/
|
||||||
.mini-pipeline-graph-dropdown-menu {
|
.mini-pipeline-graph-dropdown-menu {
|
||||||
|
z-index: 200;
|
||||||
|
|
||||||
&::before,
|
&::before,
|
||||||
&::after {
|
&::after {
|
||||||
|
|
|
@ -29,6 +29,10 @@
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tree-ref-target-holder {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
.repo-breadcrumb {
|
.repo-breadcrumb {
|
||||||
li:last-of-type {
|
li:last-of-type {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -45,7 +45,7 @@ class Admin::AppearancesController < Admin::ApplicationController
|
||||||
|
|
||||||
# Use callbacks to share common setup or constraints between actions.
|
# Use callbacks to share common setup or constraints between actions.
|
||||||
def set_appearance
|
def set_appearance
|
||||||
@appearance = Appearance.last || Appearance.new
|
@appearance = Appearance.current || Appearance.new
|
||||||
end
|
end
|
||||||
|
|
||||||
# Only allow a trusted parameter "white list" through.
|
# Only allow a trusted parameter "white list" through.
|
||||||
|
|
|
@ -69,7 +69,7 @@ module AuthenticatesWithTwoFactor
|
||||||
if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenge])
|
if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenge])
|
||||||
# Remove any lingering user data from login
|
# Remove any lingering user data from login
|
||||||
session.delete(:otp_user_id)
|
session.delete(:otp_user_id)
|
||||||
session.delete(:challenges)
|
session.delete(:challenge)
|
||||||
|
|
||||||
remember_me(user) if user_params[:remember_me] == '1'
|
remember_me(user) if user_params[:remember_me] == '1'
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
|
|
|
@ -6,6 +6,13 @@ module CycleAnalyticsParams
|
||||||
end
|
end
|
||||||
|
|
||||||
def start_date(params)
|
def start_date(params)
|
||||||
params[:start_date] == '30' ? 30.days.ago : 90.days.ago
|
case params[:start_date]
|
||||||
|
when '7'
|
||||||
|
7.days.ago
|
||||||
|
when '30'
|
||||||
|
30.days.ago
|
||||||
|
else
|
||||||
|
90.days.ago
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -52,8 +52,10 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_events
|
def load_events
|
||||||
@events = Event.in_projects(load_projects(params.merge(non_public: true)))
|
projects = load_projects(params.merge(non_public: true))
|
||||||
@events = event_filter.apply_filter(@events).with_associations
|
|
||||||
@events = @events.limit(20).offset(params[:offset] || 0)
|
@events = EventCollection
|
||||||
|
.new(projects, offset: params[:offset].to_i, filter: event_filter)
|
||||||
|
.to_a
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,9 +29,9 @@ class DashboardController < Dashboard::ApplicationController
|
||||||
current_user.authorized_projects
|
current_user.authorized_projects
|
||||||
end
|
end
|
||||||
|
|
||||||
@events = Event.in_projects(projects)
|
@events = EventCollection
|
||||||
@events = @event_filter.apply_filter(@events).with_associations
|
.new(projects, offset: params[:offset].to_i, filter: @event_filter)
|
||||||
@events = @events.limit(20).offset(params[:offset] || 0)
|
.to_a
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_show_full_reference
|
def set_show_full_reference
|
||||||
|
|
|
@ -6,7 +6,7 @@ class Explore::ProjectsController < Explore::ApplicationController
|
||||||
def index
|
def index
|
||||||
params[:sort] ||= 'latest_activity_desc'
|
params[:sort] ||= 'latest_activity_desc'
|
||||||
@sort = params[:sort]
|
@sort = params[:sort]
|
||||||
@projects = load_projects.page(params[:page])
|
@projects = load_projects
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html
|
format.html
|
||||||
|
@ -21,7 +21,7 @@ class Explore::ProjectsController < Explore::ApplicationController
|
||||||
def trending
|
def trending
|
||||||
params[:trending] = true
|
params[:trending] = true
|
||||||
@sort = params[:sort]
|
@sort = params[:sort]
|
||||||
@projects = load_projects.page(params[:page])
|
@projects = load_projects
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html
|
format.html
|
||||||
|
@ -34,7 +34,7 @@ class Explore::ProjectsController < Explore::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def starred
|
def starred
|
||||||
@projects = load_projects.reorder('star_count DESC').page(params[:page])
|
@projects = load_projects.reorder('star_count DESC')
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html
|
format.html
|
||||||
|
@ -50,6 +50,9 @@ class Explore::ProjectsController < Explore::ApplicationController
|
||||||
|
|
||||||
def load_projects
|
def load_projects
|
||||||
ProjectsFinder.new(current_user: current_user, params: params)
|
ProjectsFinder.new(current_user: current_user, params: params)
|
||||||
.execute.includes(:route, namespace: :route)
|
.execute
|
||||||
|
.includes(:route, namespace: :route)
|
||||||
|
.page(params[:page])
|
||||||
|
.without_count
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -160,9 +160,9 @@ class GroupsController < Groups::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_events
|
def load_events
|
||||||
@events = Event.in_projects(@projects)
|
@events = EventCollection
|
||||||
@events = event_filter.apply_filter(@events).with_associations
|
.new(@projects, offset: params[:offset].to_i, filter: event_filter)
|
||||||
@events = @events.limit(20).offset(params[:offset] || 0)
|
.to_a
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_actions
|
def user_actions
|
||||||
|
|
|
@ -212,7 +212,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_merge_request
|
def create_merge_request
|
||||||
result = MergeRequests::CreateFromIssueService.new(project, current_user, issue_iid: issue.iid).execute
|
result = ::MergeRequests::CreateFromIssueService.new(project, current_user, issue_iid: issue.iid).execute
|
||||||
|
|
||||||
if result[:status] == :success
|
if result[:status] == :success
|
||||||
render json: MergeRequestCreateSerializer.new.represent(result[:merge_request])
|
render json: MergeRequestCreateSerializer.new.represent(result[:merge_request])
|
||||||
|
|
|
@ -7,6 +7,7 @@ class ProjectsController < Projects::ApplicationController
|
||||||
before_action :repository, except: [:index, :new, :create]
|
before_action :repository, except: [:index, :new, :create]
|
||||||
before_action :assign_ref_vars, only: [:show], if: :repo_exists?
|
before_action :assign_ref_vars, only: [:show], if: :repo_exists?
|
||||||
before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?]
|
before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?]
|
||||||
|
before_action :project_export_enabled, only: [:export, :download_export, :remove_export, :generate_new_export]
|
||||||
|
|
||||||
# Authorize
|
# Authorize
|
||||||
before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export]
|
before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export]
|
||||||
|
@ -301,10 +302,11 @@ class ProjectsController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_events
|
def load_events
|
||||||
@events = @project.events.recent
|
projects = Project.where(id: @project.id)
|
||||||
@events = event_filter.apply_filter(@events).with_associations
|
|
||||||
limit = (params[:limit] || 20).to_i
|
@events = EventCollection
|
||||||
@events = @events.limit(limit).offset(params[:offset] || 0)
|
.new(projects, offset: params[:offset].to_i, filter: event_filter)
|
||||||
|
.to_a
|
||||||
end
|
end
|
||||||
|
|
||||||
def project_params
|
def project_params
|
||||||
|
@ -389,4 +391,8 @@ class ProjectsController < Projects::ApplicationController
|
||||||
|
|
||||||
url_for(params)
|
url_for(params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def project_export_enabled
|
||||||
|
render_404 unless current_application_settings.project_export_enabled?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# :nocov:
|
||||||
if Rails.env.test?
|
if Rails.env.test?
|
||||||
class UnicornTestController < ActionController::Base
|
class UnicornTestController < ActionController::Base
|
||||||
def pid
|
def pid
|
||||||
|
@ -10,3 +11,4 @@ if Rails.env.test?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
# :nocov:
|
||||||
|
|
|
@ -18,7 +18,7 @@ class Admin::ProjectsFinder
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute
|
def execute
|
||||||
items = Project.with_statistics
|
items = Project.without_deleted.with_statistics
|
||||||
items = items.in_namespace(namespace_id) if namespace_id.present?
|
items = items.in_namespace(namespace_id) if namespace_id.present?
|
||||||
items = items.where(visibility_level: visibility_level) if visibility_level.present?
|
items = items.where(visibility_level: visibility_level) if visibility_level.present?
|
||||||
items = items.with_push if with_push.present?
|
items = items.with_push if with_push.present?
|
||||||
|
|
|
@ -20,7 +20,7 @@ module AppearancesHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def brand_item
|
def brand_item
|
||||||
@appearance ||= Appearance.first
|
@appearance ||= Appearance.current
|
||||||
end
|
end
|
||||||
|
|
||||||
def brand_header_logo
|
def brand_header_logo
|
||||||
|
|
|
@ -146,6 +146,7 @@ module ApplicationSettingsHelper
|
||||||
:plantuml_enabled,
|
:plantuml_enabled,
|
||||||
:plantuml_url,
|
:plantuml_url,
|
||||||
:polling_interval_multiplier,
|
:polling_interval_multiplier,
|
||||||
|
:project_export_enabled,
|
||||||
:prometheus_metrics_enabled,
|
:prometheus_metrics_enabled,
|
||||||
:recaptcha_enabled,
|
:recaptcha_enabled,
|
||||||
:recaptcha_private_key,
|
:recaptcha_private_key,
|
||||||
|
|
|
@ -59,7 +59,7 @@ module GroupsHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_group_message(group)
|
def remove_group_message(group)
|
||||||
_("You are going to remove %{group_name}.\nRemoved groups CANNOT be restored!\nAre you ABSOLUTELY sure?") %
|
_("You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") %
|
||||||
{ group_name: group.name }
|
{ group_name: group.name }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
21
app/helpers/pagination_helper.rb
Normal file
21
app/helpers/pagination_helper.rb
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
module PaginationHelper
|
||||||
|
def paginate_collection(collection, remote: nil)
|
||||||
|
if collection.is_a?(Kaminari::PaginatableWithoutCount)
|
||||||
|
paginate_without_count(collection)
|
||||||
|
elsif collection.respond_to?(:total_pages)
|
||||||
|
paginate_with_count(collection, remote: remote)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginate_without_count(collection)
|
||||||
|
render(
|
||||||
|
'kaminari/gitlab/without_count',
|
||||||
|
previous_path: path_to_prev_page(collection),
|
||||||
|
next_path: path_to_next_page(collection)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginate_with_count(collection, remote: nil)
|
||||||
|
paginate(collection, remote: remote, theme: 'gitlab')
|
||||||
|
end
|
||||||
|
end
|
|
@ -80,7 +80,7 @@ module ProjectsHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_project_message(project)
|
def remove_project_message(project)
|
||||||
_("You are going to remove %{project_name_with_namespace}.\nRemoved project CANNOT be restored!\nAre you ABSOLUTELY sure?") %
|
_("You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?") %
|
||||||
{ project_name_with_namespace: project.name_with_namespace }
|
{ project_name_with_namespace: project.name_with_namespace }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -234,6 +234,8 @@ module ProjectsHelper
|
||||||
# If no limit is applied we'll just issue a COUNT since the result set could
|
# If no limit is applied we'll just issue a COUNT since the result set could
|
||||||
# be too large to load into memory.
|
# be too large to load into memory.
|
||||||
def any_projects?(projects)
|
def any_projects?(projects)
|
||||||
|
return projects.any? if projects.is_a?(Array)
|
||||||
|
|
||||||
if projects.limit_value
|
if projects.limit_value
|
||||||
projects.to_a.any?
|
projects.to_a.any?
|
||||||
else
|
else
|
||||||
|
|
|
@ -11,11 +11,11 @@ module Emails
|
||||||
@member_source_type = member_source_type
|
@member_source_type = member_source_type
|
||||||
@member_id = member_id
|
@member_id = member_id
|
||||||
|
|
||||||
admins = member_source.members.owners_and_masters.includes(:user).pluck(:notification_email)
|
admins = member_source.members.owners_and_masters.pluck(:notification_email)
|
||||||
# A project in a group can have no explicit owners/masters, in that case
|
# A project in a group can have no explicit owners/masters, in that case
|
||||||
# we fallbacks to the group's owners/masters.
|
# we fallbacks to the group's owners/masters.
|
||||||
if admins.empty? && member_source.respond_to?(:group) && member_source.group
|
if admins.empty? && member_source.respond_to?(:group) && member_source.group
|
||||||
admins = member_source.group.members.owners_and_masters.includes(:user).pluck(:notification_email)
|
admins = member_source.group.members.owners_and_masters.pluck(:notification_email)
|
||||||
end
|
end
|
||||||
|
|
||||||
mail(to: admins,
|
mail(to: admins,
|
||||||
|
|
|
@ -8,7 +8,27 @@ class Appearance < ActiveRecord::Base
|
||||||
validates :logo, file_size: { maximum: 1.megabyte }
|
validates :logo, file_size: { maximum: 1.megabyte }
|
||||||
validates :header_logo, file_size: { maximum: 1.megabyte }
|
validates :header_logo, file_size: { maximum: 1.megabyte }
|
||||||
|
|
||||||
|
validate :single_appearance_row, on: :create
|
||||||
|
|
||||||
mount_uploader :logo, AttachmentUploader
|
mount_uploader :logo, AttachmentUploader
|
||||||
mount_uploader :header_logo, AttachmentUploader
|
mount_uploader :header_logo, AttachmentUploader
|
||||||
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||||
|
|
||||||
|
CACHE_KEY = 'current_appearance'.freeze
|
||||||
|
|
||||||
|
after_commit :flush_redis_cache
|
||||||
|
|
||||||
|
def self.current
|
||||||
|
Rails.cache.fetch(CACHE_KEY) { first }
|
||||||
|
end
|
||||||
|
|
||||||
|
def flush_redis_cache
|
||||||
|
Rails.cache.delete(CACHE_KEY)
|
||||||
|
end
|
||||||
|
|
||||||
|
def single_appearance_row
|
||||||
|
if self.class.any?
|
||||||
|
errors.add(:single_appearance_row, 'Only 1 appearances row can exist')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -241,6 +241,7 @@ class ApplicationSetting < ActiveRecord::Base
|
||||||
performance_bar_allowed_group_id: nil,
|
performance_bar_allowed_group_id: nil,
|
||||||
plantuml_enabled: false,
|
plantuml_enabled: false,
|
||||||
plantuml_url: nil,
|
plantuml_url: nil,
|
||||||
|
project_export_enabled: true,
|
||||||
recaptcha_enabled: false,
|
recaptcha_enabled: false,
|
||||||
repository_checks_enabled: true,
|
repository_checks_enabled: true,
|
||||||
repository_storages: ['default'],
|
repository_storages: ['default'],
|
||||||
|
|
|
@ -14,9 +14,15 @@ class BroadcastMessage < ActiveRecord::Base
|
||||||
default_value_for :color, '#E75E40'
|
default_value_for :color, '#E75E40'
|
||||||
default_value_for :font, '#FFFFFF'
|
default_value_for :font, '#FFFFFF'
|
||||||
|
|
||||||
|
CACHE_KEY = 'broadcast_message_current'.freeze
|
||||||
|
|
||||||
|
after_commit :flush_redis_cache
|
||||||
|
|
||||||
def self.current
|
def self.current
|
||||||
Rails.cache.fetch("broadcast_message_current", expires_in: 1.minute) do
|
Rails.cache.fetch(CACHE_KEY) do
|
||||||
where('ends_at > :now AND starts_at <= :now', now: Time.zone.now).order([:created_at, :id]).to_a
|
where('ends_at > :now AND starts_at <= :now', now: Time.zone.now)
|
||||||
|
.reorder(id: :asc)
|
||||||
|
.to_a
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -31,4 +37,8 @@ class BroadcastMessage < ActiveRecord::Base
|
||||||
def ended?
|
def ended?
|
||||||
ends_at < Time.zone.now
|
ends_at < Time.zone.now
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def flush_redis_cache
|
||||||
|
Rails.cache.delete(CACHE_KEY)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -48,6 +48,7 @@ class Event < ActiveRecord::Base
|
||||||
belongs_to :author, class_name: "User"
|
belongs_to :author, class_name: "User"
|
||||||
belongs_to :project
|
belongs_to :project
|
||||||
belongs_to :target, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
|
belongs_to :target, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
|
||||||
|
has_one :push_event_payload, foreign_key: :event_id
|
||||||
|
|
||||||
# For Hash only
|
# For Hash only
|
||||||
serialize :data # rubocop:disable Cop/ActiveRecordSerialize
|
serialize :data # rubocop:disable Cop/ActiveRecordSerialize
|
||||||
|
@ -55,19 +56,51 @@ class Event < ActiveRecord::Base
|
||||||
# Callbacks
|
# Callbacks
|
||||||
after_create :reset_project_activity
|
after_create :reset_project_activity
|
||||||
after_create :set_last_repository_updated_at, if: :push?
|
after_create :set_last_repository_updated_at, if: :push?
|
||||||
|
after_create :replicate_event_for_push_events_migration
|
||||||
|
|
||||||
# Scopes
|
# Scopes
|
||||||
scope :recent, -> { reorder(id: :desc) }
|
scope :recent, -> { reorder(id: :desc) }
|
||||||
scope :code_push, -> { where(action: PUSHED) }
|
scope :code_push, -> { where(action: PUSHED) }
|
||||||
|
|
||||||
scope :in_projects, ->(projects) do
|
scope :in_projects, -> (projects) do
|
||||||
where(project_id: projects.pluck(:id)).recent
|
sub_query = projects
|
||||||
|
.except(:order)
|
||||||
|
.select(1)
|
||||||
|
.where('projects.id = events.project_id')
|
||||||
|
|
||||||
|
where('EXISTS (?)', sub_query).recent
|
||||||
|
end
|
||||||
|
|
||||||
|
scope :with_associations, -> do
|
||||||
|
# We're using preload for "push_event_payload" as otherwise the association
|
||||||
|
# is not always available (depending on the query being built).
|
||||||
|
includes(:author, :project, project: :namespace)
|
||||||
|
.preload(:target, :push_event_payload)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope :with_associations, -> { includes(:author, :project, project: :namespace).preload(:target) }
|
|
||||||
scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
|
scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
|
||||||
|
|
||||||
|
self.inheritance_column = 'action'
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
|
def find_sti_class(action)
|
||||||
|
if action.to_i == PUSHED
|
||||||
|
PushEvent
|
||||||
|
else
|
||||||
|
Event
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def subclass_from_attributes(attrs)
|
||||||
|
# Without this Rails will keep calling this method on the returned class,
|
||||||
|
# resulting in an infinite loop.
|
||||||
|
return unless self == Event
|
||||||
|
|
||||||
|
action = attrs.with_indifferent_access[inheritance_column].to_i
|
||||||
|
|
||||||
|
PushEvent if action == PUSHED
|
||||||
|
end
|
||||||
|
|
||||||
# Update Gitlab::ContributionsCalendar#activity_dates if this changes
|
# Update Gitlab::ContributionsCalendar#activity_dates if this changes
|
||||||
def contributions
|
def contributions
|
||||||
where("action = ? OR (target_type IN (?) AND action IN (?)) OR (target_type = ? AND action = ?)",
|
where("action = ? OR (target_type IN (?) AND action IN (?)) OR (target_type = ? AND action = ?)",
|
||||||
|
@ -290,6 +323,16 @@ class Event < ActiveRecord::Base
|
||||||
@commits ||= (data[:commits] || []).reverse
|
@commits ||= (data[:commits] || []).reverse
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def commit_title
|
||||||
|
commit = commits.last
|
||||||
|
|
||||||
|
commit[:message] if commit
|
||||||
|
end
|
||||||
|
|
||||||
|
def commit_id
|
||||||
|
commit_to || commit_from
|
||||||
|
end
|
||||||
|
|
||||||
def commits_count
|
def commits_count
|
||||||
data[:total_commits_count] || commits.count || 0
|
data[:total_commits_count] || commits.count || 0
|
||||||
end
|
end
|
||||||
|
@ -385,6 +428,16 @@ class Event < ActiveRecord::Base
|
||||||
user ? author_id == user.id : false
|
user ? author_id == user.id : false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# We're manually replicating data into the new table since database triggers
|
||||||
|
# are not dumped to db/schema.rb. This could mean that a new installation
|
||||||
|
# would not have the triggers in place, thus losing events data in GitLab
|
||||||
|
# 10.0.
|
||||||
|
def replicate_event_for_push_events_migration
|
||||||
|
new_attributes = attributes.with_indifferent_access.except(:title, :data)
|
||||||
|
|
||||||
|
EventForMigration.create!(new_attributes)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def recent_update?
|
def recent_update?
|
||||||
|
|
98
app/models/event_collection.rb
Normal file
98
app/models/event_collection.rb
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
# A collection of events to display in an event list.
|
||||||
|
#
|
||||||
|
# An EventCollection is meant to be used for displaying events to a user (e.g.
|
||||||
|
# in a controller), it's not suitable for building queries that are used for
|
||||||
|
# building other queries.
|
||||||
|
class EventCollection
|
||||||
|
# To prevent users from putting too much pressure on the database by cycling
|
||||||
|
# through thousands of events we put a limit on the number of pages.
|
||||||
|
MAX_PAGE = 10
|
||||||
|
|
||||||
|
# projects - An ActiveRecord::Relation object that returns the projects for
|
||||||
|
# which to retrieve events.
|
||||||
|
# filter - An EventFilter instance to use for filtering events.
|
||||||
|
def initialize(projects, limit: 20, offset: 0, filter: nil)
|
||||||
|
@projects = projects
|
||||||
|
@limit = limit
|
||||||
|
@offset = offset
|
||||||
|
@filter = filter
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns an Array containing the events.
|
||||||
|
def to_a
|
||||||
|
return [] if current_page > MAX_PAGE
|
||||||
|
|
||||||
|
relation = if Gitlab::Database.join_lateral_supported?
|
||||||
|
relation_with_join_lateral
|
||||||
|
else
|
||||||
|
relation_without_join_lateral
|
||||||
|
end
|
||||||
|
|
||||||
|
relation.with_associations.to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Returns the events relation to use when JOIN LATERAL is not supported.
|
||||||
|
#
|
||||||
|
# This relation simply gets all the events for all authorized projects, then
|
||||||
|
# limits that set.
|
||||||
|
def relation_without_join_lateral
|
||||||
|
events = filtered_events.in_projects(projects)
|
||||||
|
|
||||||
|
paginate_events(events)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the events relation to use when JOIN LATERAL is supported.
|
||||||
|
#
|
||||||
|
# This relation is built using JOIN LATERAL, producing faster queries than a
|
||||||
|
# regular LIMIT + OFFSET approach.
|
||||||
|
def relation_with_join_lateral
|
||||||
|
projects_for_lateral = projects.select(:id).to_sql
|
||||||
|
|
||||||
|
lateral = filtered_events
|
||||||
|
.limit(limit_for_join_lateral)
|
||||||
|
.where('events.project_id = projects_for_lateral.id')
|
||||||
|
.to_sql
|
||||||
|
|
||||||
|
# The outer query does not need to re-apply the filters since the JOIN
|
||||||
|
# LATERAL body already takes care of this.
|
||||||
|
outer = base_relation
|
||||||
|
.from("(#{projects_for_lateral}) projects_for_lateral")
|
||||||
|
.joins("JOIN LATERAL (#{lateral}) AS #{Event.table_name} ON true")
|
||||||
|
|
||||||
|
paginate_events(outer)
|
||||||
|
end
|
||||||
|
|
||||||
|
def filtered_events
|
||||||
|
@filter ? @filter.apply_filter(base_relation) : base_relation
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginate_events(events)
|
||||||
|
events.limit(@limit).offset(@offset)
|
||||||
|
end
|
||||||
|
|
||||||
|
def base_relation
|
||||||
|
# We want to have absolute control over the event queries being built, thus
|
||||||
|
# we're explicitly opting out of any default scopes that may be set.
|
||||||
|
Event.unscoped.recent
|
||||||
|
end
|
||||||
|
|
||||||
|
def limit_for_join_lateral
|
||||||
|
# Applying the OFFSET on the inside of a JOIN LATERAL leads to incorrect
|
||||||
|
# results. To work around this we need to increase the inner limit for every
|
||||||
|
# page.
|
||||||
|
#
|
||||||
|
# This means that on page 1 we use LIMIT 20, and an outer OFFSET of 0. On
|
||||||
|
# page 2 we use LIMIT 40 and an outer OFFSET of 20.
|
||||||
|
@limit + @offset
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_page
|
||||||
|
(@offset / @limit) + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def projects
|
||||||
|
@projects.except(:order)
|
||||||
|
end
|
||||||
|
end
|
5
app/models/event_for_migration.rb
Normal file
5
app/models/event_for_migration.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# This model is used to replicate events between the old "events" table and the
|
||||||
|
# new "events_for_migration" table that will replace "events" in GitLab 10.0.
|
||||||
|
class EventForMigration < ActiveRecord::Base
|
||||||
|
self.table_name = 'events_for_migration'
|
||||||
|
end
|
|
@ -212,21 +212,39 @@ class Group < Namespace
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_ids_for_project_authorizations
|
def user_ids_for_project_authorizations
|
||||||
users_with_parents.pluck(:id)
|
members_with_parents.pluck(:user_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def members_with_parents
|
def members_with_parents
|
||||||
GroupMember.active.where(source_id: ancestors.pluck(:id).push(id)).where.not(user_id: nil)
|
# Avoids an unnecessary SELECT when the group has no parents
|
||||||
|
source_ids =
|
||||||
|
if parent_id
|
||||||
|
self_and_ancestors.reorder(nil).select(:id)
|
||||||
|
else
|
||||||
|
id
|
||||||
|
end
|
||||||
|
|
||||||
|
GroupMember
|
||||||
|
.active_without_invites
|
||||||
|
.where(source_id: source_ids)
|
||||||
|
end
|
||||||
|
|
||||||
|
def members_with_descendants
|
||||||
|
GroupMember
|
||||||
|
.active_without_invites
|
||||||
|
.where(source_id: self_and_descendants.reorder(nil).select(:id))
|
||||||
end
|
end
|
||||||
|
|
||||||
def users_with_parents
|
def users_with_parents
|
||||||
User.where(id: members_with_parents.select(:user_id))
|
User
|
||||||
|
.where(id: members_with_parents.select(:user_id))
|
||||||
|
.reorder(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
def users_with_descendants
|
def users_with_descendants
|
||||||
members_with_descendants = GroupMember.non_request.where(source_id: descendants.pluck(:id).push(id))
|
User
|
||||||
|
.where(id: members_with_descendants.select(:user_id))
|
||||||
User.where(id: members_with_descendants.select(:user_id))
|
.reorder(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
def max_member_access_for_user(user)
|
def max_member_access_for_user(user)
|
||||||
|
|
|
@ -41,9 +41,20 @@ class Member < ActiveRecord::Base
|
||||||
is_external_invite = arel_table[:user_id].eq(nil).and(arel_table[:invite_token].not_eq(nil))
|
is_external_invite = arel_table[:user_id].eq(nil).and(arel_table[:invite_token].not_eq(nil))
|
||||||
user_is_active = User.arel_table[:state].eq(:active)
|
user_is_active = User.arel_table[:state].eq(:active)
|
||||||
|
|
||||||
includes(:user).references(:users)
|
user_ok = Arel::Nodes::Grouping.new(is_external_invite).or(user_is_active)
|
||||||
.where(is_external_invite.or(user_is_active))
|
|
||||||
|
left_join_users
|
||||||
|
.where(user_ok)
|
||||||
.where(requested_at: nil)
|
.where(requested_at: nil)
|
||||||
|
.reorder(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Like active, but without invites. For when a User is required.
|
||||||
|
scope :active_without_invites, -> do
|
||||||
|
left_join_users
|
||||||
|
.where(users: { state: 'active' })
|
||||||
|
.where(requested_at: nil)
|
||||||
|
.reorder(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope :invite, -> { where.not(invite_token: nil) }
|
scope :invite, -> { where.not(invite_token: nil) }
|
||||||
|
@ -276,6 +287,13 @@ class Member < ActiveRecord::Base
|
||||||
@notification_setting ||= user.notification_settings_for(source)
|
@notification_setting ||= user.notification_settings_for(source)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def notifiable?(type, opts = {})
|
||||||
|
# always notify when there isn't a user yet
|
||||||
|
return true if user.blank?
|
||||||
|
|
||||||
|
NotificationRecipientService.notifiable?(user, type, notifiable_options.merge(opts))
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def send_invite
|
def send_invite
|
||||||
|
@ -332,4 +350,8 @@ class Member < ActiveRecord::Base
|
||||||
def notification_service
|
def notification_service
|
||||||
NotificationService.new
|
NotificationService.new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def notifiable_options
|
||||||
|
{}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,6 +30,10 @@ class GroupMember < Member
|
||||||
'Group'
|
'Group'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def notifiable_options
|
||||||
|
{ group: group }
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def send_invite
|
def send_invite
|
||||||
|
|
|
@ -87,6 +87,10 @@ class ProjectMember < Member
|
||||||
project.owner == user
|
project.owner == user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def notifiable_options
|
||||||
|
{ project: project }
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def delete_member_todos
|
def delete_member_todos
|
||||||
|
|
|
@ -443,7 +443,8 @@ class MergeRequest < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def reload_diff_if_branch_changed
|
def reload_diff_if_branch_changed
|
||||||
if source_branch_changed? || target_branch_changed?
|
if (source_branch_changed? || target_branch_changed?) &&
|
||||||
|
(source_branch_head && target_branch_head)
|
||||||
reload_diff
|
reload_diff
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -792,11 +793,7 @@ class MergeRequest < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_ref
|
def fetch_ref
|
||||||
target_project.repository.fetch_ref(
|
write_ref
|
||||||
source_project.repository.path_to_repo,
|
|
||||||
"refs/heads/#{source_branch}",
|
|
||||||
ref_path
|
|
||||||
)
|
|
||||||
update_column(:ref_fetched, true)
|
update_column(:ref_fetched, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -939,4 +936,17 @@ class MergeRequest < ActiveRecord::Base
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def write_ref
|
||||||
|
target_project.repository.with_repo_branch_commit(
|
||||||
|
source_project.repository, source_branch) do |commit|
|
||||||
|
if commit
|
||||||
|
target_project.repository.write_ref(ref_path, commit.sha)
|
||||||
|
else
|
||||||
|
raise Rugged::ReferenceError, 'source repository is empty'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -156,6 +156,14 @@ class Namespace < ActiveRecord::Base
|
||||||
.base_and_ancestors
|
.base_and_ancestors
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self_and_ancestors
|
||||||
|
return self.class.where(id: id) unless parent_id
|
||||||
|
|
||||||
|
Gitlab::GroupHierarchy
|
||||||
|
.new(self.class.where(id: id))
|
||||||
|
.base_and_ancestors
|
||||||
|
end
|
||||||
|
|
||||||
# Returns all the descendants of the current namespace.
|
# Returns all the descendants of the current namespace.
|
||||||
def descendants
|
def descendants
|
||||||
Gitlab::GroupHierarchy
|
Gitlab::GroupHierarchy
|
||||||
|
@ -163,6 +171,12 @@ class Namespace < ActiveRecord::Base
|
||||||
.base_and_descendants
|
.base_and_descendants
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self_and_descendants
|
||||||
|
Gitlab::GroupHierarchy
|
||||||
|
.new(self.class.where(id: id))
|
||||||
|
.base_and_descendants
|
||||||
|
end
|
||||||
|
|
||||||
def user_ids_for_project_authorizations
|
def user_ids_for_project_authorizations
|
||||||
[owner_id]
|
[owner_id]
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,14 +5,22 @@ class NotificationRecipient
|
||||||
custom_action: nil,
|
custom_action: nil,
|
||||||
target: nil,
|
target: nil,
|
||||||
acting_user: nil,
|
acting_user: nil,
|
||||||
project: nil
|
project: nil,
|
||||||
|
group: nil,
|
||||||
|
skip_read_ability: false
|
||||||
)
|
)
|
||||||
|
unless NotificationSetting.levels.key?(type) || type == :subscription
|
||||||
|
raise ArgumentError, "invalid type: #{type.inspect}"
|
||||||
|
end
|
||||||
|
|
||||||
@custom_action = custom_action
|
@custom_action = custom_action
|
||||||
@acting_user = acting_user
|
@acting_user = acting_user
|
||||||
@target = target
|
@target = target
|
||||||
@project = project || @target&.project
|
@project = project || default_project
|
||||||
|
@group = group || @project&.group
|
||||||
@user = user
|
@user = user
|
||||||
@type = type
|
@type = type
|
||||||
|
@skip_read_ability = skip_read_ability
|
||||||
end
|
end
|
||||||
|
|
||||||
def notification_setting
|
def notification_setting
|
||||||
|
@ -77,6 +85,8 @@ class NotificationRecipient
|
||||||
def has_access?
|
def has_access?
|
||||||
DeclarativePolicy.subject_scope do
|
DeclarativePolicy.subject_scope do
|
||||||
return false unless user.can?(:receive_notifications)
|
return false unless user.can?(:receive_notifications)
|
||||||
|
return true if @skip_read_ability
|
||||||
|
|
||||||
return false if @project && !user.can?(:read_project, @project)
|
return false if @project && !user.can?(:read_project, @project)
|
||||||
|
|
||||||
return true unless read_ability
|
return true unless read_ability
|
||||||
|
@ -96,6 +106,7 @@ class NotificationRecipient
|
||||||
private
|
private
|
||||||
|
|
||||||
def read_ability
|
def read_ability
|
||||||
|
return nil if @skip_read_ability
|
||||||
return @read_ability if instance_variable_defined?(:@read_ability)
|
return @read_ability if instance_variable_defined?(:@read_ability)
|
||||||
|
|
||||||
@read_ability =
|
@read_ability =
|
||||||
|
@ -111,12 +122,18 @@ class NotificationRecipient
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def default_project
|
||||||
|
return nil if @target.nil?
|
||||||
|
return @target if @target.is_a?(Project)
|
||||||
|
return @target.project if @target.respond_to?(:project)
|
||||||
|
end
|
||||||
|
|
||||||
def find_notification_setting
|
def find_notification_setting
|
||||||
project_setting = @project && user.notification_settings_for(@project)
|
project_setting = @project && user.notification_settings_for(@project)
|
||||||
|
|
||||||
return project_setting unless project_setting.nil? || project_setting.global?
|
return project_setting unless project_setting.nil? || project_setting.global?
|
||||||
|
|
||||||
group_setting = @project&.group && user.notification_settings_for(@project.group)
|
group_setting = @group && user.notification_settings_for(@group)
|
||||||
|
|
||||||
return group_setting unless group_setting.nil? || group_setting.global?
|
return group_setting unless group_setting.nil? || group_setting.global?
|
||||||
|
|
||||||
|
|
|
@ -196,7 +196,6 @@ class Project < ActiveRecord::Base
|
||||||
accepts_nested_attributes_for :import_data
|
accepts_nested_attributes_for :import_data
|
||||||
|
|
||||||
delegate :name, to: :owner, allow_nil: true, prefix: true
|
delegate :name, to: :owner, allow_nil: true, prefix: true
|
||||||
delegate :count, to: :forks, prefix: true
|
|
||||||
delegate :members, to: :team, prefix: true
|
delegate :members, to: :team, prefix: true
|
||||||
delegate :add_user, :add_users, to: :team
|
delegate :add_user, :add_users, to: :team
|
||||||
delegate :add_guest, :add_reporter, :add_developer, :add_master, to: :team
|
delegate :add_guest, :add_reporter, :add_developer, :add_master, to: :team
|
||||||
|
@ -1048,9 +1047,7 @@ class Project < ActiveRecord::Base
|
||||||
def change_head(branch)
|
def change_head(branch)
|
||||||
if repository.branch_exists?(branch)
|
if repository.branch_exists?(branch)
|
||||||
repository.before_change_head
|
repository.before_change_head
|
||||||
repository.rugged.references.create('HEAD',
|
repository.write_ref('HEAD', "refs/heads/#{branch}")
|
||||||
"refs/heads/#{branch}",
|
|
||||||
force: true)
|
|
||||||
repository.copy_gitattributes(branch)
|
repository.copy_gitattributes(branch)
|
||||||
repository.after_change_head
|
repository.after_change_head
|
||||||
reload_default_branch
|
reload_default_branch
|
||||||
|
@ -1398,6 +1395,10 @@ class Project < ActiveRecord::Base
|
||||||
# @deprecated cannot remove yet because it has an index with its name in elasticsearch
|
# @deprecated cannot remove yet because it has an index with its name in elasticsearch
|
||||||
alias_method :path_with_namespace, :full_path
|
alias_method :path_with_namespace, :full_path
|
||||||
|
|
||||||
|
def forks_count
|
||||||
|
Projects::ForksCountService.new(self).count
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def cross_namespace_reference?(from)
|
def cross_namespace_reference?(from)
|
||||||
|
|
126
app/models/push_event.rb
Normal file
126
app/models/push_event.rb
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
class PushEvent < Event
|
||||||
|
# This validation exists so we can't accidentally use PushEvent with a
|
||||||
|
# different "action" value.
|
||||||
|
validate :validate_push_action
|
||||||
|
|
||||||
|
# Authors are required as they're used to display who pushed data.
|
||||||
|
#
|
||||||
|
# We're just validating the presence of the ID here as foreign key constraints
|
||||||
|
# should ensure the ID points to a valid user.
|
||||||
|
validates :author_id, presence: true
|
||||||
|
|
||||||
|
# The project is required to build links to commits, commit ranges, etc.
|
||||||
|
#
|
||||||
|
# We're just validating the presence of the ID here as foreign key constraints
|
||||||
|
# should ensure the ID points to a valid project.
|
||||||
|
validates :project_id, presence: true
|
||||||
|
|
||||||
|
# The "data" field must not be set for push events since it's not used and a
|
||||||
|
# waste of space.
|
||||||
|
validates :data, absence: true
|
||||||
|
|
||||||
|
# These fields are also not used for push events, thus storing them would be a
|
||||||
|
# waste.
|
||||||
|
validates :target_id, absence: true
|
||||||
|
validates :target_type, absence: true
|
||||||
|
|
||||||
|
def self.sti_name
|
||||||
|
PUSHED
|
||||||
|
end
|
||||||
|
|
||||||
|
def push?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def push_with_commits?
|
||||||
|
!!(commit_from && commit_to)
|
||||||
|
end
|
||||||
|
|
||||||
|
def tag?
|
||||||
|
return super unless push_event_payload
|
||||||
|
|
||||||
|
push_event_payload.tag?
|
||||||
|
end
|
||||||
|
|
||||||
|
def branch?
|
||||||
|
return super unless push_event_payload
|
||||||
|
|
||||||
|
push_event_payload.branch?
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_push?
|
||||||
|
return super unless push_event_payload
|
||||||
|
|
||||||
|
push_event_payload.ref.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def new_ref?
|
||||||
|
return super unless push_event_payload
|
||||||
|
|
||||||
|
push_event_payload.created?
|
||||||
|
end
|
||||||
|
|
||||||
|
def rm_ref?
|
||||||
|
return super unless push_event_payload
|
||||||
|
|
||||||
|
push_event_payload.removed?
|
||||||
|
end
|
||||||
|
|
||||||
|
def commit_from
|
||||||
|
return super unless push_event_payload
|
||||||
|
|
||||||
|
push_event_payload.commit_from
|
||||||
|
end
|
||||||
|
|
||||||
|
def commit_to
|
||||||
|
return super unless push_event_payload
|
||||||
|
|
||||||
|
push_event_payload.commit_to
|
||||||
|
end
|
||||||
|
|
||||||
|
def ref_name
|
||||||
|
return super unless push_event_payload
|
||||||
|
|
||||||
|
push_event_payload.ref
|
||||||
|
end
|
||||||
|
|
||||||
|
def ref_type
|
||||||
|
return super unless push_event_payload
|
||||||
|
|
||||||
|
push_event_payload.ref_type
|
||||||
|
end
|
||||||
|
|
||||||
|
def branch_name
|
||||||
|
return super unless push_event_payload
|
||||||
|
|
||||||
|
ref_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def tag_name
|
||||||
|
return super unless push_event_payload
|
||||||
|
|
||||||
|
ref_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def commit_title
|
||||||
|
return super unless push_event_payload
|
||||||
|
|
||||||
|
push_event_payload.commit_title
|
||||||
|
end
|
||||||
|
|
||||||
|
def commit_id
|
||||||
|
commit_to || commit_from
|
||||||
|
end
|
||||||
|
|
||||||
|
def commits_count
|
||||||
|
return super unless push_event_payload
|
||||||
|
|
||||||
|
push_event_payload.commit_count
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_push_action
|
||||||
|
return if action == PUSHED
|
||||||
|
|
||||||
|
errors.add(:action, "the action #{action.inspect} is not valid")
|
||||||
|
end
|
||||||
|
end
|
22
app/models/push_event_payload.rb
Normal file
22
app/models/push_event_payload.rb
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
class PushEventPayload < ActiveRecord::Base
|
||||||
|
include ShaAttribute
|
||||||
|
|
||||||
|
belongs_to :event, inverse_of: :push_event_payload
|
||||||
|
|
||||||
|
validates :event_id, :commit_count, :action, :ref_type, presence: true
|
||||||
|
validates :commit_title, length: { maximum: 70 }
|
||||||
|
|
||||||
|
sha_attribute :commit_from
|
||||||
|
sha_attribute :commit_to
|
||||||
|
|
||||||
|
enum action: {
|
||||||
|
created: 0,
|
||||||
|
removed: 1,
|
||||||
|
pushed: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ref_type: {
|
||||||
|
branch: 0,
|
||||||
|
tag: 1
|
||||||
|
}
|
||||||
|
end
|
|
@ -224,7 +224,7 @@ class Repository
|
||||||
|
|
||||||
# This will still fail if the file is corrupted (e.g. 0 bytes)
|
# This will still fail if the file is corrupted (e.g. 0 bytes)
|
||||||
begin
|
begin
|
||||||
rugged.references.create(keep_around_ref_name(sha), sha, force: true)
|
write_ref(keep_around_ref_name(sha), sha)
|
||||||
rescue Rugged::ReferenceError => ex
|
rescue Rugged::ReferenceError => ex
|
||||||
Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
|
Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
|
||||||
rescue Rugged::OSError => ex
|
rescue Rugged::OSError => ex
|
||||||
|
@ -237,6 +237,10 @@ class Repository
|
||||||
ref_exists?(keep_around_ref_name(sha))
|
ref_exists?(keep_around_ref_name(sha))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def write_ref(ref_path, sha)
|
||||||
|
rugged.references.create(ref_path, sha, force: true)
|
||||||
|
end
|
||||||
|
|
||||||
def diverging_commit_counts(branch)
|
def diverging_commit_counts(branch)
|
||||||
root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
|
root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
|
||||||
cache.fetch(:"diverging_commit_counts_#{branch.name}") do
|
cache.fetch(:"diverging_commit_counts_#{branch.name}") do
|
||||||
|
@ -987,12 +991,10 @@ class Repository
|
||||||
if start_repository == self
|
if start_repository == self
|
||||||
start_branch_name
|
start_branch_name
|
||||||
else
|
else
|
||||||
tmp_ref = "refs/tmp/#{SecureRandom.hex}/head"
|
tmp_ref = fetch_ref(
|
||||||
|
|
||||||
fetch_ref(
|
|
||||||
start_repository.path_to_repo,
|
start_repository.path_to_repo,
|
||||||
"#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}",
|
"#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}",
|
||||||
tmp_ref
|
"refs/tmp/#{SecureRandom.hex}/head"
|
||||||
)
|
)
|
||||||
|
|
||||||
start_repository.commit(start_branch_name).sha
|
start_repository.commit(start_branch_name).sha
|
||||||
|
@ -1023,7 +1025,12 @@ class Repository
|
||||||
|
|
||||||
def fetch_ref(source_path, source_ref, target_ref)
|
def fetch_ref(source_path, source_ref, target_ref)
|
||||||
args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
|
args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
|
||||||
run_git(args)
|
message, status = run_git(args)
|
||||||
|
|
||||||
|
# Make sure ref was created, and raise Rugged::ReferenceError when not
|
||||||
|
raise Rugged::ReferenceError, message if status != 0
|
||||||
|
|
||||||
|
target_ref
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_ref(ref, ref_path)
|
def create_ref(ref, ref_path)
|
||||||
|
|
|
@ -726,9 +726,9 @@ class User < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def sanitize_attrs
|
def sanitize_attrs
|
||||||
%w[username skype linkedin twitter].each do |attr|
|
%i[skype linkedin twitter].each do |attr|
|
||||||
value = public_send(attr) # rubocop:disable GitlabSecurity/PublicSend
|
value = self[attr]
|
||||||
public_send("#{attr}=", Sanitize.clean(value)) if value.present? # rubocop:disable GitlabSecurity/PublicSend
|
self[attr] = Sanitize.clean(value) if value.present?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1069,6 +1069,7 @@ class User < ActiveRecord::Base
|
||||||
|
|
||||||
# Added according to https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/README.md#activejob-integration
|
# Added according to https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/README.md#activejob-integration
|
||||||
def send_devise_notification(notification, *args)
|
def send_devise_notification(notification, *args)
|
||||||
|
return true unless can?(:receive_notifications)
|
||||||
devise_mailer.send(notification, self, *args).deliver_later
|
devise_mailer.send(notification, self, *args).deliver_later
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -85,13 +85,13 @@ module Ci
|
||||||
end
|
end
|
||||||
|
|
||||||
def register_failure
|
def register_failure
|
||||||
failed_attempt_counter.increase
|
failed_attempt_counter.increment
|
||||||
attempt_counter.increase
|
attempt_counter.increment
|
||||||
end
|
end
|
||||||
|
|
||||||
def register_success(job)
|
def register_success(job)
|
||||||
job_queue_duration_seconds.observe({ shared_runner: @runner.shared? }, Time.now - job.created_at)
|
job_queue_duration_seconds.observe({ shared_runner: @runner.shared? }, Time.now - job.created_at)
|
||||||
attempt_counter.increase
|
attempt_counter.increment
|
||||||
end
|
end
|
||||||
|
|
||||||
def failed_attempt_counter
|
def failed_attempt_counter
|
||||||
|
|
|
@ -71,7 +71,14 @@ class EventCreateService
|
||||||
end
|
end
|
||||||
|
|
||||||
def push(project, current_user, push_data)
|
def push(project, current_user, push_data)
|
||||||
create_event(project, current_user, Event::PUSHED, data: push_data)
|
# We're using an explicit transaction here so that any errors that may occur
|
||||||
|
# when creating push payload data will result in the event creation being
|
||||||
|
# rolled back as well.
|
||||||
|
Event.transaction do
|
||||||
|
event = create_event(project, current_user, Event::PUSHED)
|
||||||
|
|
||||||
|
PushEventPayloadService.new(event, push_data).execute
|
||||||
|
end
|
||||||
|
|
||||||
Users::ActivityService.new(current_user, 'push').execute
|
Users::ActivityService.new(current_user, 'push').execute
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,9 +10,11 @@ class NotificationService
|
||||||
# only if ssh key is not deploy key
|
# only if ssh key is not deploy key
|
||||||
#
|
#
|
||||||
# This is security email so it will be sent
|
# This is security email so it will be sent
|
||||||
# even if user disabled notifications
|
# even if user disabled notifications. However,
|
||||||
|
# it won't be sent to internal users like the
|
||||||
|
# ghost user or the EE support bot.
|
||||||
def new_key(key)
|
def new_key(key)
|
||||||
if key.user
|
if key.user&.can?(:receive_notifications)
|
||||||
mailer.new_ssh_key_email(key.id).deliver_later
|
mailer.new_ssh_key_email(key.id).deliver_later
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -22,14 +24,14 @@ class NotificationService
|
||||||
# This is a security email so it will be sent even if the user user disabled
|
# This is a security email so it will be sent even if the user user disabled
|
||||||
# notifications
|
# notifications
|
||||||
def new_gpg_key(gpg_key)
|
def new_gpg_key(gpg_key)
|
||||||
if gpg_key.user
|
if gpg_key.user&.can?(:receive_notifications)
|
||||||
mailer.new_gpg_key_email(gpg_key.id).deliver_later
|
mailer.new_gpg_key_email(gpg_key.id).deliver_later
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Always notify user about email added to profile
|
# Always notify user about email added to profile
|
||||||
def new_email(email)
|
def new_email(email)
|
||||||
if email.user
|
if email.user&.can?(:receive_notifications)
|
||||||
mailer.new_email_email(email.id).deliver_later
|
mailer.new_email_email(email.id).deliver_later
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -185,6 +187,8 @@ class NotificationService
|
||||||
|
|
||||||
# Notify new user with email after creation
|
# Notify new user with email after creation
|
||||||
def new_user(user, token = nil)
|
def new_user(user, token = nil)
|
||||||
|
return true unless notifiable?(user, :mention)
|
||||||
|
|
||||||
# Don't email omniauth created users
|
# Don't email omniauth created users
|
||||||
mailer.new_user_email(user.id, token).deliver_later unless user.identities.any?
|
mailer.new_user_email(user.id, token).deliver_later unless user.identities.any?
|
||||||
end
|
end
|
||||||
|
@ -206,19 +210,27 @@ class NotificationService
|
||||||
|
|
||||||
# Members
|
# Members
|
||||||
def new_access_request(member)
|
def new_access_request(member)
|
||||||
|
return true unless member.notifiable?(:subscription)
|
||||||
|
|
||||||
mailer.member_access_requested_email(member.real_source_type, member.id).deliver_later
|
mailer.member_access_requested_email(member.real_source_type, member.id).deliver_later
|
||||||
end
|
end
|
||||||
|
|
||||||
def decline_access_request(member)
|
def decline_access_request(member)
|
||||||
|
return true unless member.notifiable?(:subscription)
|
||||||
|
|
||||||
mailer.member_access_denied_email(member.real_source_type, member.source_id, member.user_id).deliver_later
|
mailer.member_access_denied_email(member.real_source_type, member.source_id, member.user_id).deliver_later
|
||||||
end
|
end
|
||||||
|
|
||||||
# Project invite
|
# Project invite
|
||||||
def invite_project_member(project_member, token)
|
def invite_project_member(project_member, token)
|
||||||
|
return true unless project_member.notifiable?(:subscription)
|
||||||
|
|
||||||
mailer.member_invited_email(project_member.real_source_type, project_member.id, token).deliver_later
|
mailer.member_invited_email(project_member.real_source_type, project_member.id, token).deliver_later
|
||||||
end
|
end
|
||||||
|
|
||||||
def accept_project_invite(project_member)
|
def accept_project_invite(project_member)
|
||||||
|
return true unless project_member.notifiable?(:subscription)
|
||||||
|
|
||||||
mailer.member_invite_accepted_email(project_member.real_source_type, project_member.id).deliver_later
|
mailer.member_invite_accepted_email(project_member.real_source_type, project_member.id).deliver_later
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -232,10 +244,14 @@ class NotificationService
|
||||||
end
|
end
|
||||||
|
|
||||||
def new_project_member(project_member)
|
def new_project_member(project_member)
|
||||||
|
return true unless project_member.notifiable?(:mention, skip_read_ability: true)
|
||||||
|
|
||||||
mailer.member_access_granted_email(project_member.real_source_type, project_member.id).deliver_later
|
mailer.member_access_granted_email(project_member.real_source_type, project_member.id).deliver_later
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_project_member(project_member)
|
def update_project_member(project_member)
|
||||||
|
return true unless project_member.notifiable?(:mention)
|
||||||
|
|
||||||
mailer.member_access_granted_email(project_member.real_source_type, project_member.id).deliver_later
|
mailer.member_access_granted_email(project_member.real_source_type, project_member.id).deliver_later
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -249,6 +265,9 @@ class NotificationService
|
||||||
end
|
end
|
||||||
|
|
||||||
def decline_group_invite(group_member)
|
def decline_group_invite(group_member)
|
||||||
|
# always send this one, since it's a response to the user's own
|
||||||
|
# action
|
||||||
|
|
||||||
mailer.member_invite_declined_email(
|
mailer.member_invite_declined_email(
|
||||||
group_member.real_source_type,
|
group_member.real_source_type,
|
||||||
group_member.group.id,
|
group_member.group.id,
|
||||||
|
@ -258,15 +277,19 @@ class NotificationService
|
||||||
end
|
end
|
||||||
|
|
||||||
def new_group_member(group_member)
|
def new_group_member(group_member)
|
||||||
|
return true unless group_member.notifiable?(:mention)
|
||||||
|
|
||||||
mailer.member_access_granted_email(group_member.real_source_type, group_member.id).deliver_later
|
mailer.member_access_granted_email(group_member.real_source_type, group_member.id).deliver_later
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_group_member(group_member)
|
def update_group_member(group_member)
|
||||||
|
return true unless group_member.notifiable?(:mention)
|
||||||
|
|
||||||
mailer.member_access_granted_email(group_member.real_source_type, group_member.id).deliver_later
|
mailer.member_access_granted_email(group_member.real_source_type, group_member.id).deliver_later
|
||||||
end
|
end
|
||||||
|
|
||||||
def project_was_moved(project, old_path_with_namespace)
|
def project_was_moved(project, old_path_with_namespace)
|
||||||
recipients = NotificationRecipientService.notifiable_users(project.team.members, :mention, project: project)
|
recipients = notifiable_users(project.team.members, :mention, project: project)
|
||||||
|
|
||||||
recipients.each do |recipient|
|
recipients.each do |recipient|
|
||||||
mailer.project_was_moved_email(
|
mailer.project_was_moved_email(
|
||||||
|
@ -288,10 +311,14 @@ class NotificationService
|
||||||
end
|
end
|
||||||
|
|
||||||
def project_exported(project, current_user)
|
def project_exported(project, current_user)
|
||||||
|
return true unless notifiable?(current_user, :mention, project: project)
|
||||||
|
|
||||||
mailer.project_was_exported_email(current_user, project).deliver_later
|
mailer.project_was_exported_email(current_user, project).deliver_later
|
||||||
end
|
end
|
||||||
|
|
||||||
def project_not_exported(project, current_user, errors)
|
def project_not_exported(project, current_user, errors)
|
||||||
|
return true unless notifiable?(current_user, :mention, project: project)
|
||||||
|
|
||||||
mailer.project_was_not_exported_email(current_user, project, errors).deliver_later
|
mailer.project_was_not_exported_email(current_user, project, errors).deliver_later
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -300,7 +327,7 @@ class NotificationService
|
||||||
|
|
||||||
return unless mailer.respond_to?(email_template)
|
return unless mailer.respond_to?(email_template)
|
||||||
|
|
||||||
recipients ||= NotificationRecipientService.notifiable_users(
|
recipients ||= notifiable_users(
|
||||||
[pipeline.user], :watch,
|
[pipeline.user], :watch,
|
||||||
custom_action: :"#{pipeline.status}_pipeline",
|
custom_action: :"#{pipeline.status}_pipeline",
|
||||||
target: pipeline
|
target: pipeline
|
||||||
|
@ -369,7 +396,7 @@ class NotificationService
|
||||||
|
|
||||||
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) }
|
recipients = labels.flat_map { |l| l.subscribers(target.project) }
|
||||||
recipients = NotificationRecipientService.notifiable_users(
|
recipients = notifiable_users(
|
||||||
recipients, :subscription,
|
recipients, :subscription,
|
||||||
target: target,
|
target: target,
|
||||||
acting_user: current_user
|
acting_user: current_user
|
||||||
|
@ -401,4 +428,14 @@ class NotificationService
|
||||||
object.previous_changes[attribute].first
|
object.previous_changes[attribute].first
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def notifiable?(*args)
|
||||||
|
NotificationRecipientService.notifiable?(*args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def notifiable_users(*args)
|
||||||
|
NotificationRecipientService.notifiable_users(*args)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -128,6 +128,8 @@ module Projects
|
||||||
project.repository.before_delete
|
project.repository.before_delete
|
||||||
|
|
||||||
Repository.new(wiki_path, project, disk_path: repo_path).before_delete
|
Repository.new(wiki_path, project, disk_path: repo_path).before_delete
|
||||||
|
|
||||||
|
Projects::ForksCountService.new(project).delete_cache
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,11 +21,17 @@ module Projects
|
||||||
builds_access_level = @project.project_feature.builds_access_level
|
builds_access_level = @project.project_feature.builds_access_level
|
||||||
new_project.project_feature.update_attributes(builds_access_level: builds_access_level)
|
new_project.project_feature.update_attributes(builds_access_level: builds_access_level)
|
||||||
|
|
||||||
|
refresh_forks_count
|
||||||
|
|
||||||
new_project
|
new_project
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def refresh_forks_count
|
||||||
|
Projects::ForksCountService.new(@project).refresh_cache
|
||||||
|
end
|
||||||
|
|
||||||
def allowed_visibility_level
|
def allowed_visibility_level
|
||||||
project_level = @project.visibility_level
|
project_level = @project.visibility_level
|
||||||
|
|
||||||
|
|
30
app/services/projects/forks_count_service.rb
Normal file
30
app/services/projects/forks_count_service.rb
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
module Projects
|
||||||
|
# Service class for getting and caching the number of forks of a project.
|
||||||
|
class ForksCountService
|
||||||
|
def initialize(project)
|
||||||
|
@project = project
|
||||||
|
end
|
||||||
|
|
||||||
|
def count
|
||||||
|
Rails.cache.fetch(cache_key) { uncached_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def refresh_cache
|
||||||
|
Rails.cache.write(cache_key, uncached_count)
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_cache
|
||||||
|
Rails.cache.delete(cache_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def uncached_count
|
||||||
|
@project.forks.count
|
||||||
|
end
|
||||||
|
|
||||||
|
def cache_key
|
||||||
|
['projects', @project.id, 'forks_count']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -13,7 +13,13 @@ module Projects
|
||||||
::MergeRequests::CloseService.new(@project, @current_user).execute(mr)
|
::MergeRequests::CloseService.new(@project, @current_user).execute(mr)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
refresh_forks_count(@project.forked_from_project)
|
||||||
|
|
||||||
@project.forked_project_link.destroy
|
@project.forked_project_link.destroy
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def refresh_forks_count(project)
|
||||||
|
Projects::ForksCountService.new(project).refresh_cache
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -172,11 +172,11 @@ module Projects
|
||||||
end
|
end
|
||||||
|
|
||||||
def register_attempt
|
def register_attempt
|
||||||
pages_deployments_total_counter.increase
|
pages_deployments_total_counter.increment
|
||||||
end
|
end
|
||||||
|
|
||||||
def register_failure
|
def register_failure
|
||||||
pages_deployments_failed_total_counter.increase
|
pages_deployments_failed_total_counter.increment
|
||||||
end
|
end
|
||||||
|
|
||||||
def pages_deployments_total_counter
|
def pages_deployments_total_counter
|
||||||
|
|
120
app/services/push_event_payload_service.rb
Normal file
120
app/services/push_event_payload_service.rb
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
# Service class for creating push event payloads as stored in the
|
||||||
|
# "push_event_payloads" table.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
#
|
||||||
|
# data = Gitlab::DataBuilder::Push.build(...)
|
||||||
|
# event = Event.create(...)
|
||||||
|
#
|
||||||
|
# PushEventPayloadService.new(event, data).execute
|
||||||
|
class PushEventPayloadService
|
||||||
|
# event - The event this push payload belongs to.
|
||||||
|
# push_data - A Hash produced by `Gitlab::DataBuilder::Push.build` to use for
|
||||||
|
# building the push payload.
|
||||||
|
def initialize(event, push_data)
|
||||||
|
@event = event
|
||||||
|
@push_data = push_data
|
||||||
|
end
|
||||||
|
|
||||||
|
# Creates and returns a new PushEventPayload row.
|
||||||
|
#
|
||||||
|
# This method will raise upon encountering validation errors.
|
||||||
|
#
|
||||||
|
# Returns an instance of PushEventPayload.
|
||||||
|
def execute
|
||||||
|
@event.build_push_event_payload(
|
||||||
|
commit_count: commit_count,
|
||||||
|
action: action,
|
||||||
|
ref_type: ref_type,
|
||||||
|
commit_from: commit_from_id,
|
||||||
|
commit_to: commit_to_id,
|
||||||
|
ref: trimmed_ref,
|
||||||
|
commit_title: commit_title,
|
||||||
|
event_id: @event.id
|
||||||
|
)
|
||||||
|
|
||||||
|
@event.push_event_payload.save!
|
||||||
|
@event.push_event_payload
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the commit title to use.
|
||||||
|
#
|
||||||
|
# The commit title is limited to the first line and a maximum of 70
|
||||||
|
# characters.
|
||||||
|
def commit_title
|
||||||
|
commit = @push_data.fetch(:commits).last
|
||||||
|
|
||||||
|
return nil unless commit && commit[:message]
|
||||||
|
|
||||||
|
raw_msg = commit[:message]
|
||||||
|
|
||||||
|
# Find where the first line ends, without turning the entire message into an
|
||||||
|
# Array of lines (this is a waste of memory for large commit messages).
|
||||||
|
index = raw_msg.index("\n")
|
||||||
|
message = index ? raw_msg[0..index] : raw_msg
|
||||||
|
|
||||||
|
message.strip.truncate(70)
|
||||||
|
end
|
||||||
|
|
||||||
|
def commit_from_id
|
||||||
|
if create?
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
revision_before
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def commit_to_id
|
||||||
|
if remove?
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
revision_after
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def commit_count
|
||||||
|
@push_data.fetch(:total_commits_count)
|
||||||
|
end
|
||||||
|
|
||||||
|
def ref
|
||||||
|
@push_data.fetch(:ref)
|
||||||
|
end
|
||||||
|
|
||||||
|
def revision_before
|
||||||
|
@push_data.fetch(:before)
|
||||||
|
end
|
||||||
|
|
||||||
|
def revision_after
|
||||||
|
@push_data.fetch(:after)
|
||||||
|
end
|
||||||
|
|
||||||
|
def trimmed_ref
|
||||||
|
Gitlab::Git.ref_name(ref)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create?
|
||||||
|
Gitlab::Git.blank_ref?(revision_before)
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove?
|
||||||
|
Gitlab::Git.blank_ref?(revision_after)
|
||||||
|
end
|
||||||
|
|
||||||
|
def action
|
||||||
|
if create?
|
||||||
|
:created
|
||||||
|
elsif remove?
|
||||||
|
:removed
|
||||||
|
else
|
||||||
|
:pushed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def ref_type
|
||||||
|
if Gitlab::Git.tag_ref?(ref)
|
||||||
|
:tag
|
||||||
|
else
|
||||||
|
:branch
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,7 +4,7 @@ class PersonalFileUploader < FileUploader
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.base_dir
|
def self.base_dir
|
||||||
File.join(root_dir, 'system')
|
File.join(root_dir, '-', 'system')
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -48,6 +48,12 @@
|
||||||
= select(:application_setting, :enabled_git_access_protocol, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control')
|
= select(:application_setting, :enabled_git_access_protocol, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control')
|
||||||
%span.help-block#clone-protocol-help
|
%span.help-block#clone-protocol-help
|
||||||
Allow only the selected protocols to be used for Git access.
|
Allow only the selected protocols to be used for Git access.
|
||||||
|
.form-group
|
||||||
|
.col-sm-offset-2.col-sm-10
|
||||||
|
.checkbox
|
||||||
|
= f.label :project_export_enabled do
|
||||||
|
= f.check_box :project_export_enabled
|
||||||
|
Project export enabled
|
||||||
|
|
||||||
%fieldset
|
%fieldset
|
||||||
%legend Account and Limit Settings
|
%legend Account and Limit Settings
|
||||||
|
|
|
@ -28,6 +28,6 @@
|
||||||
%h3.blank-state-title
|
%h3.blank-state-title
|
||||||
Create a group
|
Create a group
|
||||||
%p.blank-state-text
|
%p.blank-state-text
|
||||||
Groups are a great way to organise projects and people.
|
Groups are a great way to organize projects and people.
|
||||||
= link_to new_group_path, class: "btn btn-new" do
|
= link_to new_group_path, class: "btn btn-new" do
|
||||||
New group
|
New group
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
%li.commit
|
%li.commit
|
||||||
.commit-row-title
|
.commit-row-title
|
||||||
= link_to truncate_sha(commit[:id]), project_commit_path(project, commit[:id]), class: "commit-sha", alt: '', title: truncate_sha(commit[:id])
|
= link_to truncate_sha(event.commit_id), project_commit_path(project, event.commit_id), class: "commit-sha", alt: '', title: truncate_sha(event.commit_id)
|
||||||
·
|
·
|
||||||
= markdown event_commit_title(commit[:message]), project: project, pipeline: :single_line, author: event.author
|
= markdown event_commit_title(event.commit_title), project: project, pipeline: :single_line, author: event.author
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
%div{ xmlns: "http://www.w3.org/1999/xhtml" }
|
%div{ xmlns: "http://www.w3.org/1999/xhtml" }
|
||||||
- event.commits.first(15).each do |commit|
|
%p
|
||||||
%p
|
%strong= event.author_name
|
||||||
%strong= commit[:author][:name]
|
= link_to "(#{truncate_sha(event.commit_id)})", project_commit_path(event.project, event.commit_id)
|
||||||
= link_to "(##{truncate_sha(commit[:id])})", project_commit_path(event.project, id: commit[:id])
|
%i
|
||||||
%i
|
at
|
||||||
at
|
= event.created_at.to_s(:short)
|
||||||
= commit[:timestamp].to_time.to_s(:short)
|
%blockquote= markdown(escape_once(event.commit_title), pipeline: :atom, project: event.project, author: event.author)
|
||||||
%blockquote= markdown(escape_once(commit[:message]), pipeline: :atom, project: event.project, author: event.author)
|
- if event.commits_count > 1
|
||||||
- if event.commits_count > 15
|
|
||||||
%p
|
%p
|
||||||
%i
|
%i
|
||||||
\... and
|
\... and
|
||||||
= pluralize(event.commits_count - 15, "more commit")
|
= pluralize(event.commits_count - 1, "more commit")
|
||||||
|
|
|
@ -14,9 +14,7 @@
|
||||||
- if event.push_with_commits?
|
- if event.push_with_commits?
|
||||||
.event-body
|
.event-body
|
||||||
%ul.well-list.event_commits
|
%ul.well-list.event_commits
|
||||||
- few_commits = event.commits[0...2]
|
= render "events/commit", project: project, event: event
|
||||||
- few_commits.each do |commit|
|
|
||||||
= render "events/commit", commit: commit, project: project, event: event
|
|
||||||
|
|
||||||
- create_mr = event.new_ref? && create_mr_button?(project.default_branch, event.ref_name, project) && event.authored_by?(current_user)
|
- create_mr = event.new_ref? && create_mr_button?(project.default_branch, event.ref_name, project) && event.authored_by?(current_user)
|
||||||
- if event.commits_count > 1
|
- if event.commits_count > 1
|
||||||
|
@ -44,9 +42,6 @@
|
||||||
= link_to create_mr_path(project.default_branch, event.ref_name, project) do
|
= link_to create_mr_path(project.default_branch, event.ref_name, project) do
|
||||||
Create Merge Request
|
Create Merge Request
|
||||||
- elsif event.rm_ref?
|
- elsif event.rm_ref?
|
||||||
- repository = project.repository
|
.event-body
|
||||||
- last_commit = repository.commit(event.commit_from)
|
%ul.well-list.event_commits
|
||||||
- if last_commit
|
= render "events/commit", project: project, event: event
|
||||||
.event-body
|
|
||||||
%ul.well-list.event_commits
|
|
||||||
= render "events/commit", commit: last_commit, project: project, event: event
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
Customize how FogBugz email addresses and usernames are imported into GitLab.
|
Customize how FogBugz email addresses and usernames are imported into GitLab.
|
||||||
In the next step, you'll be able to select the projects you want to import.
|
In the next step, you'll be able to select the projects you want to import.
|
||||||
%p
|
%p
|
||||||
The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames wil be imported into GitLab. You can change this by populating the table below.
|
The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames will be imported into GitLab. You can change this by populating the table below.
|
||||||
%ul
|
%ul
|
||||||
%li
|
%li
|
||||||
%strong Default: Map a FogBugz account ID to a full name
|
%strong Default: Map a FogBugz account ID to a full name
|
||||||
|
|
8
app/views/kaminari/gitlab/_without_count.html.haml
Normal file
8
app/views/kaminari/gitlab/_without_count.html.haml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
.gl-pagination
|
||||||
|
%ul.pagination.clearfix
|
||||||
|
- if previous_path
|
||||||
|
%li.prev
|
||||||
|
= link_to(t('views.pagination.previous'), previous_path, rel: 'prev')
|
||||||
|
- if next_path
|
||||||
|
%li.next
|
||||||
|
= link_to(t('views.pagination.next'), next_path, rel: 'next')
|
41
app/views/projects/_export.html.haml
Normal file
41
app/views/projects/_export.html.haml
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
- return unless current_application_settings.project_export_enabled?
|
||||||
|
|
||||||
|
- project = local_assigns.fetch(:project)
|
||||||
|
- expanded = Rails.env.test?
|
||||||
|
|
||||||
|
%section.settings
|
||||||
|
.settings-header
|
||||||
|
%h4
|
||||||
|
Export project
|
||||||
|
%button.btn.js-settings-toggle
|
||||||
|
= expanded ? 'Collapse' : 'Expand'
|
||||||
|
%p
|
||||||
|
Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the "New Project" page.
|
||||||
|
.settings-content.no-animate{ class: ('expanded' if expanded) }
|
||||||
|
.bs-callout.bs-callout-info
|
||||||
|
%p.append-bottom-0
|
||||||
|
%p
|
||||||
|
The following items will be exported:
|
||||||
|
%ul
|
||||||
|
%li Project and wiki repositories
|
||||||
|
%li Project uploads
|
||||||
|
%li Project configuration including web hooks and services
|
||||||
|
%li Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities
|
||||||
|
%p
|
||||||
|
The following items will NOT be exported:
|
||||||
|
%ul
|
||||||
|
%li Job traces and artifacts
|
||||||
|
%li LFS objects
|
||||||
|
%li Container registry images
|
||||||
|
%li CI variables
|
||||||
|
%li Any encrypted tokens
|
||||||
|
%p
|
||||||
|
Once the exported file is ready, you will receive a notification email with a download link.
|
||||||
|
- if project.export_project_path
|
||||||
|
= link_to 'Download export', download_export_project_path(project),
|
||||||
|
rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
|
||||||
|
= link_to 'Generate new export', generate_new_export_project_path(project),
|
||||||
|
method: :post, class: "btn btn-default"
|
||||||
|
- else
|
||||||
|
= link_to 'Export project', export_project_path(project),
|
||||||
|
method: :post, class: "btn btn-default"
|
|
@ -1,11 +1,9 @@
|
||||||
- referenced_users = local_assigns.fetch(:referenced_users, nil)
|
- referenced_users = local_assigns.fetch(:referenced_users, nil)
|
||||||
|
|
||||||
- if defined?(@issue) && @issue.confidential?
|
- if defined?(@issue) && @issue.confidential?
|
||||||
%li.confidential-issue-warning
|
.confidential-issue-warning
|
||||||
= confidential_icon(@issue)
|
= confidential_icon(@issue)
|
||||||
%span This is a confidential issue. Your comment will not be visible to the public.
|
%span This is a confidential issue. Your comment will not be visible to the public.
|
||||||
- else
|
|
||||||
%li.confidential-issue-warning.not-confidential
|
|
||||||
|
|
||||||
.md-area
|
.md-area
|
||||||
.md-header
|
.md-header
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
- if commit.has_signature?
|
- if commit.has_signature?
|
||||||
%button{ class: commit_signature_badge_classes('js-loading-gpg-badge'), data: { toggle: 'tooltip', placement: 'auto top', title: 'GPG signature (loading...)', 'commit-sha' => commit.sha } }
|
%button{ class: commit_signature_badge_classes('js-loading-gpg-badge'), data: { toggle: 'tooltip', placement: 'auto top', title: 'GPG signature (loading...)', 'commit-sha' => commit.sha } }
|
||||||
%i.fa.fa-spinner.fa-spin
|
|
||||||
|
|
|
@ -39,6 +39,9 @@
|
||||||
%span.dropdown-label {{ n__('Last %d day', 'Last %d days', 30) }}
|
%span.dropdown-label {{ n__('Last %d day', 'Last %d days', 30) }}
|
||||||
%i.fa.fa-chevron-down
|
%i.fa.fa-chevron-down
|
||||||
%ul.dropdown-menu.dropdown-menu-align-right
|
%ul.dropdown-menu.dropdown-menu-align-right
|
||||||
|
%li
|
||||||
|
%a{ "href" => "#", "data-value" => "7" }
|
||||||
|
{{ n__('Last %d day', 'Last %d days', 7) }}
|
||||||
%li
|
%li
|
||||||
%a{ "href" => "#", "data-value" => "30" }
|
%a{ "href" => "#", "data-value" => "30" }
|
||||||
{{ n__('Last %d day', 'Last %d days', 30) }}
|
{{ n__('Last %d day', 'Last %d days', 30) }}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue