Merge branch 'master' into diff-line-comment-vuejs
|
@ -30,6 +30,7 @@
|
|||
/config/secrets.yml
|
||||
/config/sidekiq.yml
|
||||
/coverage/*
|
||||
/coverage-javascript/
|
||||
/db/*.sqlite3
|
||||
/db/*.sqlite3-journal
|
||||
/db/data.yml
|
||||
|
|
100
.gitlab-ci.yml
|
@ -1,7 +1,7 @@
|
|||
image: "ruby:2.1"
|
||||
image: "ruby:2.3.1"
|
||||
|
||||
cache:
|
||||
key: "ruby21"
|
||||
key: "ruby-231"
|
||||
paths:
|
||||
- vendor/apt
|
||||
- vendor/ruby
|
||||
|
@ -138,57 +138,57 @@ spinach 7 10: *spinach-knapsack
|
|||
spinach 8 10: *spinach-knapsack
|
||||
spinach 9 10: *spinach-knapsack
|
||||
|
||||
# Execute all testing suites against Ruby 2.3
|
||||
.ruby-23: &ruby-23
|
||||
image: "ruby:2.3"
|
||||
# Execute all testing suites against Ruby 2.1
|
||||
.ruby-21: &ruby-21
|
||||
image: "ruby:2.1"
|
||||
<<: *use-db
|
||||
only:
|
||||
- master
|
||||
cache:
|
||||
key: "ruby-23"
|
||||
key: "ruby21"
|
||||
paths:
|
||||
- vendor/apt
|
||||
- vendor/ruby
|
||||
|
||||
.rspec-knapsack-ruby23: &rspec-knapsack-ruby23
|
||||
.rspec-knapsack-ruby21: &rspec-knapsack-ruby21
|
||||
<<: *rspec-knapsack
|
||||
<<: *ruby-23
|
||||
<<: *ruby-21
|
||||
|
||||
.spinach-knapsack-ruby23: &spinach-knapsack-ruby23
|
||||
.spinach-knapsack-ruby21: &spinach-knapsack-ruby21
|
||||
<<: *spinach-knapsack
|
||||
<<: *ruby-23
|
||||
<<: *ruby-21
|
||||
|
||||
rspec 0 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 1 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 2 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 3 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 4 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 5 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 6 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 7 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 8 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 9 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 10 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 11 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 12 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 13 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 14 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 15 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 16 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 17 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 18 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 19 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 0 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 1 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 2 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 3 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 4 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 5 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 6 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 7 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 8 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 9 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 10 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 11 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 12 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 13 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 14 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 15 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 16 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 17 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 18 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 19 20 ruby21: *rspec-knapsack-ruby21
|
||||
|
||||
spinach 0 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 1 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 2 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 3 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 4 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 5 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 6 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 7 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 8 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 9 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 0 10 ruby21: *spinach-knapsack-ruby21
|
||||
spinach 1 10 ruby21: *spinach-knapsack-ruby21
|
||||
spinach 2 10 ruby21: *spinach-knapsack-ruby21
|
||||
spinach 3 10 ruby21: *spinach-knapsack-ruby21
|
||||
spinach 4 10 ruby21: *spinach-knapsack-ruby21
|
||||
spinach 5 10 ruby21: *spinach-knapsack-ruby21
|
||||
spinach 6 10 ruby21: *spinach-knapsack-ruby21
|
||||
spinach 7 10 ruby21: *spinach-knapsack-ruby21
|
||||
spinach 8 10 ruby21: *spinach-knapsack-ruby21
|
||||
spinach 9 10 ruby21: *spinach-knapsack-ruby21
|
||||
|
||||
# Other generic tests
|
||||
|
||||
|
@ -222,7 +222,22 @@ teaspoon:
|
|||
stage: test
|
||||
<<: *use-db
|
||||
script:
|
||||
- curl --silent --location https://deb.nodesource.com/setup_6.x | bash -
|
||||
- apt-get install --assume-yes nodejs
|
||||
- npm install --global istanbul
|
||||
- teaspoon
|
||||
artifacts:
|
||||
name: coverage-javascript
|
||||
expire_in: 31d
|
||||
paths:
|
||||
- coverage-javascript/default/
|
||||
|
||||
lint-doc:
|
||||
stage: test
|
||||
image: "phusion/baseimage:latest"
|
||||
before_script: []
|
||||
script:
|
||||
- scripts/lint-doc.sh
|
||||
|
||||
bundler:audit:
|
||||
stage: test
|
||||
|
@ -252,6 +267,9 @@ coverage:
|
|||
|
||||
notify:slack:
|
||||
stage: post-test
|
||||
variables:
|
||||
USE_DB: "false"
|
||||
USE_BUNDLE_INSTALL: "false"
|
||||
script:
|
||||
- ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>"
|
||||
when: on_failure
|
||||
|
@ -266,10 +284,12 @@ pages:
|
|||
stage: pages
|
||||
dependencies:
|
||||
- coverage
|
||||
- teaspoon
|
||||
script:
|
||||
- mv public/ .public/
|
||||
- mkdir public/
|
||||
- mv coverage public/coverage-ruby
|
||||
- mv coverage-javascript/default/ public/coverage-javascript/
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
#
|
||||
# This list is used by git-shortlog to make contributions from the
|
||||
# same person appearing to be so.
|
||||
#
|
||||
|
||||
Achilleas Pipinellis <axilleas@axilleas.me> <axilleas@archlinux.gr>
|
||||
Achilleas Pipinellis <axilleas@axilleas.me> <axilleas@users.noreply.github.com>
|
||||
Dmitriy Zaporozhets <dzaporozhets@gitlab.com> <dmitriy.zaporozhets@gmail.com>
|
||||
Dmitriy Zaporozhets <dzaporozhets@gitlab.com> <dzaporozhets@sphereconsultinginc.com>
|
||||
Douwe Maan <douwe@gitlab.com> <douwe@selenight.nl>
|
||||
Douwe Maan <douwe@gitlab.com> <me@douwe.me>
|
||||
Grzegorz Bizon <grzegorz@gitlab.com> <grzegorz.bizon@ntsn.pl>
|
||||
Grzegorz Bizon <grzegorz@gitlab.com> <grzesiek.bizon@gmail.com>
|
||||
Jacob Vosmaer <jacob@gitlab.com> <contact@jacobvosmaer.nl>
|
||||
Jacob Vosmaer <jacob@gitlab.com> Jacob Vosmaer (GitLab) <jacob@gitlab.com>
|
||||
Jacob Schatz <jschatz@gitlab.com> <jacobschatz@Jacobs-MacBook-Pro.local>
|
||||
Jacob Schatz <jschatz@gitlab.com> <jacobschatz@Jacobs-MBP.fios-router.home>
|
||||
Jacob Schatz <jschatz@gitlab.com> <jschatz1@gmail.com>
|
||||
James Lopez <james@jameslopez.es> <james@gitlab.com>
|
||||
James Lopez <james@jameslopez.es> <james.lopez@vodafone.com>
|
||||
Kamil Trzciński <kamil@gitlab.com> <ayufan@ayufan.eu>
|
||||
Marin Jankovski <maxlazio@gmail.com> <marin@gitlab.com>
|
||||
Phil Hughes <me@iamphill.com> <theephil@gmail.com>
|
||||
Rémy Coutable <remy@rymai.me> <remy@gitlab.com>
|
||||
Robert Schilling <rschilling@student.tugraz.at> <Razer6@users.noreply.github.com>
|
||||
Robert Schilling <rschilling@student.tugraz.at> <schilling.ro@gmail.com>
|
||||
Robert Speicher <robert@gitlab.com> <rspeicher@gmail.com>
|
||||
Stan Hu <stanhu@gmail.com> <stanhu@alum.mit.edu>
|
||||
Stan Hu <stanhu@gmail.com> <stanhu@packetzoom.com>
|
||||
Stan Hu <stanhu@gmail.com> <stanhu@users.noreply.github.com>
|
||||
Stan Hu <stanhu@gmail.com> stanhu <stanhu@gmail.com>
|
||||
Sytse Sijbrandij <sytse@gitlab.com> <sytse+admin@gitlab.com>
|
||||
Sytse Sijbrandij <sytse@gitlab.com> <sytse@dosire.com>
|
||||
Sytse Sijbrandij <sytse@gitlab.com> <sytses@gmail.com>
|
||||
Sytse Sijbrandij <sytse@gitlab.com> dosire <sytse@gitlab.com>
|
12
.rubocop.yml
|
@ -149,19 +149,19 @@ Style/EmptyLinesAroundAccessModifier:
|
|||
|
||||
# Keeps track of empty lines around block bodies.
|
||||
Style/EmptyLinesAroundBlockBody:
|
||||
Enabled: false
|
||||
Enabled: true
|
||||
|
||||
# Keeps track of empty lines around class bodies.
|
||||
Style/EmptyLinesAroundClassBody:
|
||||
Enabled: false
|
||||
Enabled: true
|
||||
|
||||
# Keeps track of empty lines around module bodies.
|
||||
Style/EmptyLinesAroundModuleBody:
|
||||
Enabled: false
|
||||
Enabled: true
|
||||
|
||||
# Keeps track of empty lines around method bodies.
|
||||
Style/EmptyLinesAroundMethodBody:
|
||||
Enabled: false
|
||||
Enabled: true
|
||||
|
||||
# Avoid the use of END blocks.
|
||||
Style/EndBlock:
|
||||
|
@ -373,6 +373,10 @@ Style/SpaceAfterNot:
|
|||
Style/SpaceAfterSemicolon:
|
||||
Enabled: true
|
||||
|
||||
# Use space around equals in parameter default
|
||||
Style/SpaceAroundEqualsInParameterDefault:
|
||||
Enabled: true
|
||||
|
||||
# Use a space around keywords if appropriate.
|
||||
Style/SpaceAroundKeyword:
|
||||
Enabled: true
|
||||
|
|
|
@ -339,13 +339,6 @@ Style/SingleLineBlockParams:
|
|||
Style/SingleLineMethods:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 14
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
# SupportedStyles: space, no_space
|
||||
Style/SpaceAroundEqualsInParameterDefault:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 119
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
|
|
|
@ -1 +1 @@
|
|||
2.1.8
|
||||
2.3.1
|
||||
|
|
63
CHANGELOG
|
@ -1,50 +1,86 @@
|
|||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
v 8.11.0 (unreleased)
|
||||
- Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar)
|
||||
- Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres)
|
||||
- Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres)
|
||||
- Fix the title of the toggle dropdown button. !5515 (herminiotorres)
|
||||
- Rename `markdown_preview` routes to `preview_markdown`. (Christopher Bartz)
|
||||
- Update to Ruby 2.3.1. !4948
|
||||
- Improve diff performance by eliminating redundant checks for text blobs
|
||||
- Ensure that branch names containing escapable characters (e.g. %20) aren't unescaped indiscriminately. !5770 (ewiltshi)
|
||||
- Convert switch icon into icon font (ClemMakesApps)
|
||||
- API: Endpoints for enabling and disabling deploy keys
|
||||
- API: List access requests, request access, approve, and deny access requests to a project or a group. !4833
|
||||
- Use long options for curl examples in documentation !5703 (winniehell)
|
||||
- Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell)
|
||||
- Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell)
|
||||
- Ignore URLs starting with // in Markdown links !5677 (winniehell)
|
||||
- Fix CI status icon link underline (ClemMakesApps)
|
||||
- The Repository class is now instrumented
|
||||
- Fix filter label tooltip HTML rendering (ClemMakesApps)
|
||||
- Cache the commit author in RequestStore to avoid extra lookups in PostReceive
|
||||
- Expand commit message width in repo view (ClemMakesApps)
|
||||
- Cache highlighted diff lines for merge requests
|
||||
- Pre-create all builds for a Pipeline when the new Pipeline is created !5295
|
||||
- Fix of 'Commits being passed to custom hooks are already reachable when using the UI'
|
||||
- Show member roles to all users on members page
|
||||
- Project.visible_to_user is instrumented again
|
||||
- Fix awardable button mutuality loading spinners (ClemMakesApps)
|
||||
- Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable
|
||||
- Optimize maximum user access level lookup in loading of notes
|
||||
- Add "No one can push" as an option for protected branches. !5081
|
||||
- Improve performance of AutolinkFilter#text_parse by using XPath
|
||||
- Add experimental Redis Sentinel support !1877
|
||||
- Fix branches page dropdown sort initial state (ClemMakesApps)
|
||||
- Environments have an url to link to
|
||||
- Various redundant database indexes have been removed
|
||||
- Update `timeago` plugin to use multiple string/locale settings
|
||||
- Remove unused images (ClemMakesApps)
|
||||
- Limit git rev-list output count to one in forced push check
|
||||
- Clean up unused routes (Josef Strzibny)
|
||||
- Fix issue on empty project to allow developers to only push to protected branches if given permission
|
||||
- Add green outline to New Branch button. !5447 (winniehell)
|
||||
- Optimize generating of cache keys for issues and notes
|
||||
- Improve performance of syntax highlighting Markdown code blocks
|
||||
- Update to gitlab_git 10.4.1 and take advantage of preserved Ref objects
|
||||
- Remove delay when hitting "Reply..." button on page with a lot of discussions
|
||||
- Retrieve rendered HTML from cache in one request
|
||||
- Fix renaming repository when name contains invalid chararacters under project settings
|
||||
- Upgrade Grape from 0.13.0 to 0.15.0. !4601
|
||||
- Trigram indexes for the "ci_runners" table have been removed to speed up UPDATE queries
|
||||
- Fix devise deprecation warnings.
|
||||
- Update version_sorter and use new interface for faster tag sorting
|
||||
- Optimize checking if a user has read access to a list of issues !5370
|
||||
- Store all DB secrets in secrets.yml, under descriptive names !5274
|
||||
- Nokogiri's various parsing methods are now instrumented
|
||||
- Add simple identifier to public SSH keys (muteor)
|
||||
- Admin page now references docs instead of a specific file !5600 (AnAverageHuman)
|
||||
- Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363
|
||||
- Fix filter input alignment (ClemMakesApps)
|
||||
- Include old revision in merge request update hooks (Ben Boeckel)
|
||||
- Add build event color in HipChat messages (David Eisner)
|
||||
- Make fork counter always clickable. !5463 (winniehell)
|
||||
- Document that webhook secret token is sent in X-Gitlab-Token HTTP header !5664 (lycoperdon)
|
||||
- Gitlab::Highlight is now instrumented
|
||||
- All created issues, API or WebUI, can be submitted to Akismet for spam check !5333
|
||||
- Allow users to import cross-repository pull requests from GitHub
|
||||
- The overhead of instrumented method calls has been reduced
|
||||
- Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le)
|
||||
- Load project invited groups and members eagerly in `ProjectTeam#fetch_members`
|
||||
- Bump gitlab_git to speedup DiffCollection iterations
|
||||
- Rewrite description of a blocked user in admin settings. (Elias Werberich)
|
||||
- Make branches sortable without push permission !5462 (winniehell)
|
||||
- Check for Ci::Build artifacts at database level on pipeline partial
|
||||
- Convert image diff background image to CSS (ClemMakesApps)
|
||||
- Remove unnecessary index_projects_on_builds_enabled index from the projects table
|
||||
- Make "New issue" button in Issue page less obtrusive !5457 (winniehell)
|
||||
- Gitlab::Metrics.current_transaction needs to be public for RailsQueueDuration
|
||||
- Fix search for notes which belongs to deleted objects
|
||||
- Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska)
|
||||
- Allow branch names ending with .json for graph and network page !5579 (winniehell)
|
||||
- Add the `sprockets-es6` gem
|
||||
- Improve OAuth2 client documentation (muteor)
|
||||
- Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska)
|
||||
- Profile requests when a header is passed
|
||||
- Avoid calculation of line_code and position for _line partial when showing diff notes on discussion tab.
|
||||
|
@ -52,12 +88,36 @@ v 8.11.0 (unreleased)
|
|||
- Add commit stats in commit api. !5517 (dixpac)
|
||||
- Add CI configuration button on project page
|
||||
- Make error pages responsive (Takuya Noguchi)
|
||||
- Fix skip_repo parameter being ignored when destroying a namespace
|
||||
- Change requests_profiles resource constraint to catch virtually any file
|
||||
- Bump gitlab_git to lazy load compare commits
|
||||
- Reduce number of queries made for merge_requests/:id/diffs
|
||||
- Sensible state specific default sort order for issues and merge requests !5453 (tomb0y)
|
||||
- Fix bug where destroying a namespace would not always destroy projects
|
||||
- Fix RequestProfiler::Middleware error when code is reloaded in development
|
||||
- Catch what warden might throw when profiling requests to re-throw it
|
||||
- Avoid commit lookup on diff_helper passing existing local variable to the helper method
|
||||
- Add description to new_issue email and new_merge_request_email in text/plain content type. !5663 (dixpac)
|
||||
- Speed up and reduce memory usage of Commit#repo_changes, Repository#expire_avatar_cache and IrkerWorker
|
||||
- Add unfold links for Side-by-Side view. !5415 (Tim Masliuchenko)
|
||||
- Adds support for pending invitation project members importing projects
|
||||
- Update devise initializer to turn on changed password notification emails. !5648 (tombell)
|
||||
- Avoid to show the original password field when password is automatically set. !5712 (duduribeiro)
|
||||
- Fix importing GitLab projects with an invalid MR source project
|
||||
- Sort folders with submodules in Files view !5521
|
||||
- Each `File::exists?` replaced to `File::exist?` because of deprecate since ruby version 2.2.0
|
||||
- Add auto-completition in pipeline (Katarzyna Kobierska Ula Budziszewska)
|
||||
|
||||
v 8.10.5
|
||||
- Add a data migration to fix some missing timestamps in the members table. !5670
|
||||
- Revert the "Defend against 'Host' header injection" change in the source NGINX templates. !5706
|
||||
- Cache project count for 5 minutes to reduce DB load. !5746 & !5754
|
||||
|
||||
v 8.10.4
|
||||
- Don't close referenced upstream issues from a forked project.
|
||||
- Fixes issue with dropdowns `enter` key not working correctly. !5544
|
||||
- Fix Import/Export project import not working in HA mode. !5618
|
||||
- Fix Import/Export error checking versions. !5638
|
||||
|
||||
v 8.10.3
|
||||
- Fix Import/Export issue importing milestones and labels not associated properly. !5426
|
||||
|
@ -146,6 +206,9 @@ v 8.10.0
|
|||
- Fix check for New Branch button on Issue page. !4630 (winniehell)
|
||||
- Fix GFM autocomplete not working on wiki pages
|
||||
- Fixed enter key not triggering click on first row when searching in a dropdown
|
||||
- Updated dropdowns in issuable form to use new GitLab dropdown style
|
||||
- Make images fit to the size of the viewport !4810
|
||||
- Fix check for New Branch button on Issue page !4630 (winniehell)
|
||||
- Fix MR-auto-close text added to description. !4836
|
||||
- Support U2F devices in Firefox. !5177
|
||||
- Fix issue, preventing users w/o push access to sort tags. !5105 (redetection)
|
||||
|
|
|
@ -336,6 +336,10 @@ request is as follows:
|
|||
1. If your code creates new files on disk please read the
|
||||
[shared files guidelines](doc/development/shared_files.md).
|
||||
1. When writing commit messages please follow [these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) [guidelines](http://chris.beams.io/posts/git-commit/).
|
||||
1. If your merge request adds one or more migrations, make sure to execute all
|
||||
migrations on a fresh database before the MR is reviewed. If the review leads
|
||||
to large changes in the MR, do this again once the review is complete.
|
||||
1. For more complex migrations, write tests.
|
||||
|
||||
The **official merge window** is in the beginning of the month from the 1st to
|
||||
the 7th day of the month. This is the best time to submit an MR and get
|
||||
|
@ -461,8 +465,10 @@ merge request:
|
|||
- multi-line method chaining style **Option B**: dot `.` on previous line
|
||||
- string literal quoting style **Option A**: single quoted by default
|
||||
1. [Rails](https://github.com/bbatsov/rails-style-guide)
|
||||
1. [Newlines styleguide][newlines-styleguide]
|
||||
1. [Testing](doc/development/testing.md)
|
||||
1. [CoffeeScript](https://github.com/thoughtbot/guides/tree/master/style/coffeescript)
|
||||
1. [JavaScript (ES6)](https://github.com/airbnb/javascript)
|
||||
1. [JavaScript (ES5)](https://github.com/airbnb/javascript/tree/master/es5)
|
||||
1. [SCSS styleguide][scss-styleguide]
|
||||
1. [Shell commands](doc/development/shell_commands.md) created by GitLab
|
||||
contributors to enhance security
|
||||
|
@ -532,6 +538,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
|
|||
[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
|
||||
[doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
|
||||
[scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide"
|
||||
[newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide"
|
||||
[gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design
|
||||
[free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12
|
||||
[`gitlab8.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/current/
|
||||
|
|
|
@ -1 +1 @@
|
|||
3.2.1
|
||||
3.3.3
|
||||
|
|
13
Gemfile
|
@ -1,6 +1,6 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem 'rails', '4.2.7'
|
||||
gem 'rails', '4.2.7.1'
|
||||
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
|
||||
|
||||
# Responders respond_to and respond_with
|
||||
|
@ -53,7 +53,7 @@ gem 'browser', '~> 2.2'
|
|||
|
||||
# Extracting information from a git repository
|
||||
# Provide access to Gitlab::Git library
|
||||
gem 'gitlab_git', '~> 10.4.3'
|
||||
gem 'gitlab_git', '~> 10.4.5'
|
||||
|
||||
# LDAP Auth
|
||||
# GitLab fork with several improvements to original library. For full list of changes
|
||||
|
@ -69,7 +69,7 @@ gem 'gollum-rugged_adapter', '~> 0.4.2', require: false
|
|||
gem 'github-linguist', '~> 4.7.0', require: 'linguist'
|
||||
|
||||
# API
|
||||
gem 'grape', '~> 0.13.0'
|
||||
gem 'grape', '~> 0.15.0'
|
||||
gem 'grape-entity', '~> 0.4.2'
|
||||
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
|
||||
|
||||
|
@ -154,7 +154,7 @@ gem 'settingslogic', '~> 2.0.9'
|
|||
|
||||
# Misc
|
||||
|
||||
gem 'version_sorter', '~> 2.0.0'
|
||||
gem 'version_sorter', '~> 2.1.0'
|
||||
|
||||
# Cache
|
||||
gem 'redis-rails', '~> 4.0.0'
|
||||
|
@ -163,9 +163,6 @@ gem 'redis-rails', '~> 4.0.0'
|
|||
gem 'redis', '~> 3.2'
|
||||
gem 'connection_pool', '~> 2.0'
|
||||
|
||||
# Campfire integration
|
||||
gem 'tinder', '~> 1.10.0'
|
||||
|
||||
# HipChat integration
|
||||
gem 'hipchat', '~> 1.5.0'
|
||||
|
||||
|
@ -326,7 +323,7 @@ group :production do
|
|||
gem 'gitlab_meta', '7.0'
|
||||
end
|
||||
|
||||
gem 'newrelic_rpm', '~> 3.14'
|
||||
gem 'newrelic_rpm', '~> 3.16'
|
||||
|
||||
gem 'octokit', '~> 4.3.0'
|
||||
|
||||
|
|
94
Gemfile.lock
|
@ -3,34 +3,34 @@ GEM
|
|||
specs:
|
||||
RedCloth (4.3.2)
|
||||
ace-rails-ap (4.0.2)
|
||||
actionmailer (4.2.7)
|
||||
actionpack (= 4.2.7)
|
||||
actionview (= 4.2.7)
|
||||
activejob (= 4.2.7)
|
||||
actionmailer (4.2.7.1)
|
||||
actionpack (= 4.2.7.1)
|
||||
actionview (= 4.2.7.1)
|
||||
activejob (= 4.2.7.1)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
actionpack (4.2.7)
|
||||
actionview (= 4.2.7)
|
||||
activesupport (= 4.2.7)
|
||||
actionpack (4.2.7.1)
|
||||
actionview (= 4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
rack (~> 1.6)
|
||||
rack-test (~> 0.6.2)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (4.2.7)
|
||||
activesupport (= 4.2.7)
|
||||
actionview (4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
builder (~> 3.1)
|
||||
erubis (~> 2.7.0)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
activejob (4.2.7)
|
||||
activesupport (= 4.2.7)
|
||||
activejob (4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
globalid (>= 0.3.0)
|
||||
activemodel (4.2.7)
|
||||
activesupport (= 4.2.7)
|
||||
activemodel (4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
builder (~> 3.1)
|
||||
activerecord (4.2.7)
|
||||
activemodel (= 4.2.7)
|
||||
activesupport (= 4.2.7)
|
||||
activerecord (4.2.7.1)
|
||||
activemodel (= 4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
arel (~> 6.0)
|
||||
activerecord-session_store (1.0.0)
|
||||
actionpack (>= 4.0, < 5.1)
|
||||
|
@ -38,7 +38,7 @@ GEM
|
|||
multi_json (~> 1.11, >= 1.11.2)
|
||||
rack (>= 1.5.2, < 3)
|
||||
railties (>= 4.0, < 5.1)
|
||||
activesupport (4.2.7)
|
||||
activesupport (4.2.7.1)
|
||||
i18n (~> 0.7)
|
||||
json (~> 1.7, >= 1.7.7)
|
||||
minitest (~> 5.1)
|
||||
|
@ -278,7 +278,7 @@ GEM
|
|||
diff-lcs (~> 1.1)
|
||||
mime-types (>= 1.16, < 3)
|
||||
posix-spawn (~> 0.3)
|
||||
gitlab_git (10.4.3)
|
||||
gitlab_git (10.4.5)
|
||||
activesupport (~> 4.0)
|
||||
charlock_holmes (~> 0.7.3)
|
||||
github-linguist (~> 4.7.0)
|
||||
|
@ -289,7 +289,7 @@ GEM
|
|||
omniauth (~> 1.0)
|
||||
pyu-ruby-sasl (~> 0.0.3.1)
|
||||
rubyntlm (~> 0.3)
|
||||
globalid (0.3.6)
|
||||
globalid (0.3.7)
|
||||
activesupport (>= 4.1.0)
|
||||
gollum-grit_adapter (1.0.1)
|
||||
gitlab-grit (~> 2.7, >= 2.7.1)
|
||||
|
@ -308,7 +308,7 @@ GEM
|
|||
json
|
||||
multi_json
|
||||
request_store (>= 1.0)
|
||||
grape (0.13.0)
|
||||
grape (0.15.0)
|
||||
activesupport
|
||||
builder
|
||||
hashie (>= 2.1.0)
|
||||
|
@ -335,7 +335,6 @@ GEM
|
|||
activesupport (>= 2)
|
||||
nokogiri (~> 1.4)
|
||||
htmlentities (4.3.4)
|
||||
http_parser.rb (0.5.3)
|
||||
httparty (0.13.7)
|
||||
json (~> 1.8)
|
||||
multi_xml (>= 0.5.2)
|
||||
|
@ -404,7 +403,7 @@ GEM
|
|||
nested_form (0.3.2)
|
||||
net-ldap (0.12.1)
|
||||
net-ssh (3.0.1)
|
||||
newrelic_rpm (3.14.1.311)
|
||||
newrelic_rpm (3.16.0.318)
|
||||
nokogiri (1.6.8)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
pkg-config (~> 1.1.7)
|
||||
|
@ -519,16 +518,16 @@ GEM
|
|||
rack
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rails (4.2.7)
|
||||
actionmailer (= 4.2.7)
|
||||
actionpack (= 4.2.7)
|
||||
actionview (= 4.2.7)
|
||||
activejob (= 4.2.7)
|
||||
activemodel (= 4.2.7)
|
||||
activerecord (= 4.2.7)
|
||||
activesupport (= 4.2.7)
|
||||
rails (4.2.7.1)
|
||||
actionmailer (= 4.2.7.1)
|
||||
actionpack (= 4.2.7.1)
|
||||
actionview (= 4.2.7.1)
|
||||
activejob (= 4.2.7.1)
|
||||
activemodel (= 4.2.7.1)
|
||||
activerecord (= 4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
bundler (>= 1.3.0, < 2.0)
|
||||
railties (= 4.2.7)
|
||||
railties (= 4.2.7.1)
|
||||
sprockets-rails
|
||||
rails-deprecated_sanitizer (1.0.3)
|
||||
activesupport (>= 4.2.0.alpha)
|
||||
|
@ -538,9 +537,9 @@ GEM
|
|||
rails-deprecated_sanitizer (>= 1.0.1)
|
||||
rails-html-sanitizer (1.0.3)
|
||||
loofah (~> 2.0)
|
||||
railties (4.2.7)
|
||||
actionpack (= 4.2.7)
|
||||
activesupport (= 4.2.7)
|
||||
railties (4.2.7.1)
|
||||
actionpack (= 4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rainbow (2.1.0)
|
||||
|
@ -672,7 +671,6 @@ GEM
|
|||
redis-namespace (>= 1.5.2)
|
||||
rufus-scheduler (>= 2.0.24)
|
||||
sidekiq (>= 4.0.0)
|
||||
simple_oauth (0.1.9)
|
||||
simplecov (0.12.0)
|
||||
docile (~> 1.1.0)
|
||||
json (>= 1.8, < 3)
|
||||
|
@ -742,21 +740,8 @@ GEM
|
|||
tilt (2.0.5)
|
||||
timecop (0.8.1)
|
||||
timfel-krb5-auth (0.8.3)
|
||||
tinder (1.10.1)
|
||||
eventmachine (~> 1.0)
|
||||
faraday (~> 0.9.0)
|
||||
faraday_middleware (~> 0.9)
|
||||
hashie (>= 1.0)
|
||||
json (~> 1.8.0)
|
||||
mime-types
|
||||
multi_json (~> 1.7)
|
||||
twitter-stream (~> 0.1)
|
||||
turbolinks (2.5.3)
|
||||
coffee-rails
|
||||
twitter-stream (0.1.16)
|
||||
eventmachine (>= 0.12.8)
|
||||
http_parser.rb (~> 0.5.1)
|
||||
simple_oauth (~> 0.1.4)
|
||||
tzinfo (1.2.2)
|
||||
thread_safe (~> 0.1)
|
||||
u2f (0.2.1)
|
||||
|
@ -778,7 +763,7 @@ GEM
|
|||
uniform_notifier (1.10.0)
|
||||
uuid (2.3.8)
|
||||
macaddr (~> 1.0)
|
||||
version_sorter (2.0.0)
|
||||
version_sorter (2.1.0)
|
||||
virtus (1.0.5)
|
||||
axiom-types (~> 0.1)
|
||||
coercible (~> 1.0)
|
||||
|
@ -870,13 +855,13 @@ DEPENDENCIES
|
|||
github-linguist (~> 4.7.0)
|
||||
github-markup (~> 1.4)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab_git (~> 10.4.3)
|
||||
gitlab_git (~> 10.4.5)
|
||||
gitlab_meta (= 7.0)
|
||||
gitlab_omniauth-ldap (~> 1.2.1)
|
||||
gollum-lib (~> 4.2)
|
||||
gollum-rugged_adapter (~> 0.4.2)
|
||||
gon (~> 6.1.0)
|
||||
grape (~> 0.13.0)
|
||||
grape (~> 0.15.0)
|
||||
grape-entity (~> 0.4.2)
|
||||
hamlit (~> 2.5)
|
||||
health_check (~> 2.1.0)
|
||||
|
@ -902,7 +887,7 @@ DEPENDENCIES
|
|||
mysql2 (~> 0.3.16)
|
||||
nested_form (~> 0.3.2)
|
||||
net-ssh (~> 3.0.1)
|
||||
newrelic_rpm (~> 3.14)
|
||||
newrelic_rpm (~> 3.16)
|
||||
nokogiri (~> 1.6.7, >= 1.6.7.2)
|
||||
oauth2 (~> 1.2.0)
|
||||
octokit (~> 4.3.0)
|
||||
|
@ -929,7 +914,7 @@ DEPENDENCIES
|
|||
rack-attack (~> 4.3.1)
|
||||
rack-cors (~> 0.4.0)
|
||||
rack-oauth2 (~> 1.2.1)
|
||||
rails (= 4.2.7)
|
||||
rails (= 4.2.7.1)
|
||||
rails-deprecated_sanitizer (~> 1.0.3)
|
||||
rainbow (~> 2.1.0)
|
||||
rblineprof (~> 0.3.6)
|
||||
|
@ -981,7 +966,6 @@ DEPENDENCIES
|
|||
teaspoon-jasmine (~> 2.2.0)
|
||||
test_after_commit (~> 0.4.2)
|
||||
thin (~> 1.7.0)
|
||||
tinder (~> 1.10.0)
|
||||
turbolinks (~> 2.5.0)
|
||||
u2f (~> 0.2.1)
|
||||
uglifier (~> 2.7.2)
|
||||
|
@ -989,7 +973,7 @@ DEPENDENCIES
|
|||
unf (~> 0.1.4)
|
||||
unicorn (~> 4.9.0)
|
||||
unicorn-worker-killer (~> 0.4.2)
|
||||
version_sorter (~> 2.0.0)
|
||||
version_sorter (~> 2.1.0)
|
||||
virtus (~> 1.0.1)
|
||||
vmstat (~> 2.1.1)
|
||||
web-console (~> 2.0)
|
||||
|
|
Before Width: | Height: | Size: 90 B |
Before Width: | Height: | Size: 167 B |
Before Width: | Height: | Size: 367 B |
Before Width: | Height: | Size: 418 B |
Before Width: | Height: | Size: 222 B |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 197 B |
Before Width: | Height: | Size: 494 B |
Before Width: | Height: | Size: 1.3 KiB |
|
@ -292,7 +292,7 @@
|
|||
$('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded').removeClass('page-sidebar-pinned');
|
||||
$('.navbar-fixed-top').removeClass('header-pinned-nav');
|
||||
}
|
||||
return $document.off('click', '.js-nav-pin').on('click', '.js-nav-pin', function(e) {
|
||||
$document.off('click', '.js-nav-pin').on('click', '.js-nav-pin', function(e) {
|
||||
var $page, $pinBtn, $tooltip, $topNav, doPinNav, tooltipText;
|
||||
e.preventDefault();
|
||||
$pinBtn = $(e.currentTarget);
|
||||
|
@ -320,6 +320,8 @@
|
|||
$tooltip.find('.tooltip-inner').text(tooltipText);
|
||||
return $pinBtn.attr('title', tooltipText).tooltip('fixTitle');
|
||||
});
|
||||
});
|
||||
|
||||
// Custom time ago
|
||||
gl.utils.shortTimeAgo($('.js-short-timeago'));
|
||||
});
|
||||
}).call(this);
|
||||
|
|
|
@ -161,23 +161,11 @@
|
|||
$emojiButton = votesBlock.find("[data-emoji=" + mutualVote + "]").parent();
|
||||
isAlreadyVoted = $emojiButton.hasClass('active');
|
||||
if (isAlreadyVoted) {
|
||||
this.showEmojiLoader($emojiButton);
|
||||
return this.addAward(votesBlock, awardUrl, mutualVote, false, function() {
|
||||
return $emojiButton.removeClass('is-loading');
|
||||
});
|
||||
this.addAward(votesBlock, awardUrl, mutualVote, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AwardsHandler.prototype.showEmojiLoader = function($emojiButton) {
|
||||
var $loader;
|
||||
$loader = $emojiButton.find('.fa-spinner');
|
||||
if (!$loader.length) {
|
||||
$emojiButton.append('<i class="fa fa-spinner fa-spin award-control-icon award-control-icon-loading"></i>');
|
||||
}
|
||||
return $emojiButton.addClass('is-loading');
|
||||
};
|
||||
|
||||
AwardsHandler.prototype.isActive = function($emojiButton) {
|
||||
return $emojiButton.hasClass('active');
|
||||
};
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
$(document).off('click', '.js-unfold');
|
||||
$(document).on('click', '.js-unfold', (function(_this) {
|
||||
return function(event) {
|
||||
var line_number, link, offset, old_line, params, prev_new_line, prev_old_line, ref, ref1, since, target, to, unfold, unfoldBottom;
|
||||
var line_number, link, file, offset, old_line, params, prev_new_line, prev_old_line, ref, ref1, since, target, to, unfold, unfoldBottom;
|
||||
target = $(event.target);
|
||||
unfoldBottom = target.hasClass('js-unfold-bottom');
|
||||
unfold = true;
|
||||
|
@ -31,14 +31,16 @@
|
|||
unfold = false;
|
||||
}
|
||||
}
|
||||
link = target.parents('.diff-file').attr('data-blob-diff-path');
|
||||
file = target.parents('.diff-file');
|
||||
link = file.data('blob-diff-path');
|
||||
params = {
|
||||
since: since,
|
||||
to: to,
|
||||
bottom: unfoldBottom,
|
||||
offset: offset,
|
||||
unfold: unfold,
|
||||
indent: 1
|
||||
indent: 1,
|
||||
view: file.data('view')
|
||||
};
|
||||
return $.get(link, params, function(response) {
|
||||
return target.parent().replaceWith(response);
|
||||
|
@ -48,26 +50,13 @@
|
|||
}
|
||||
|
||||
Diff.prototype.lineNumbers = function(line) {
|
||||
var i, l, len, line_number, line_numbers, lines, results;
|
||||
if (!line.children().length) {
|
||||
return [0, 0];
|
||||
}
|
||||
lines = line.children().slice(0, 2);
|
||||
line_numbers = (function() {
|
||||
var i, len, results;
|
||||
results = [];
|
||||
for (i = 0, len = lines.length; i < len; i++) {
|
||||
l = lines[i];
|
||||
results.push($(l).attr('data-linenumber'));
|
||||
}
|
||||
return results;
|
||||
})();
|
||||
results = [];
|
||||
for (i = 0, len = line_numbers.length; i < len; i++) {
|
||||
line_number = line_numbers[i];
|
||||
results.push(parseInt(line_number));
|
||||
}
|
||||
return results;
|
||||
|
||||
return line.find('.diff-line-num').map(function() {
|
||||
return parseInt($(this).data('linenumber'));
|
||||
});
|
||||
};
|
||||
|
||||
return Diff;
|
||||
|
|
|
@ -173,8 +173,8 @@
|
|||
new Search();
|
||||
break;
|
||||
case 'projects:protected_branches:index':
|
||||
new ProtectedBranchesAccessSelect($(".new_protected_branch"), false, true);
|
||||
new ProtectedBranchesAccessSelect($(".protected-branches-list"), true, false);
|
||||
new gl.ProtectedBranchCreate();
|
||||
new gl.ProtectedBranchEditList();
|
||||
break;
|
||||
}
|
||||
switch (path.first()) {
|
||||
|
@ -186,6 +186,12 @@
|
|||
break;
|
||||
case 'projects':
|
||||
new NamespaceSelects();
|
||||
break;
|
||||
case 'labels':
|
||||
switch (path[2]) {
|
||||
case 'edit':
|
||||
new Labels();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'dashboard':
|
||||
|
@ -211,6 +217,7 @@
|
|||
new ProjectNew();
|
||||
break;
|
||||
case 'show':
|
||||
new Star();
|
||||
new ProjectNew();
|
||||
new ProjectShow();
|
||||
new NotificationsDropdown();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
/*= require markdown_preview */
|
||||
/*= require preview_markdown */
|
||||
|
||||
(function() {
|
||||
this.DropzoneInput = (function() {
|
||||
|
|
|
@ -28,38 +28,43 @@
|
|||
};
|
||||
})(this));
|
||||
timeout = "";
|
||||
this.input.on("keyup", (function(_this) {
|
||||
return function(e) {
|
||||
this.input
|
||||
.on('keydown', function (e) {
|
||||
var keyCode = e.which;
|
||||
|
||||
if (keyCode === 13) {
|
||||
e.preventDefault()
|
||||
}
|
||||
})
|
||||
.on('keyup', function(e) {
|
||||
var keyCode;
|
||||
keyCode = e.which;
|
||||
if (ARROW_KEY_CODES.indexOf(keyCode) >= 0) {
|
||||
return;
|
||||
}
|
||||
if (_this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) {
|
||||
if (this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) {
|
||||
$inputContainer.addClass(HAS_VALUE_CLASS);
|
||||
} else if (_this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) {
|
||||
} else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) {
|
||||
$inputContainer.removeClass(HAS_VALUE_CLASS);
|
||||
}
|
||||
if (keyCode === 13) {
|
||||
return false;
|
||||
}
|
||||
if (_this.options.remote) {
|
||||
if (this.options.remote) {
|
||||
clearTimeout(timeout);
|
||||
return timeout = setTimeout(function() {
|
||||
var blur_field;
|
||||
blur_field = _this.shouldBlur(keyCode);
|
||||
if (blur_field && _this.filterInputBlur) {
|
||||
_this.input.blur();
|
||||
var blurField = this.shouldBlur(keyCode);
|
||||
if (blurField && this.filterInputBlur) {
|
||||
this.input.blur();
|
||||
}
|
||||
return _this.options.query(_this.input.val(), function(data) {
|
||||
return _this.options.callback(data);
|
||||
});
|
||||
}, 250);
|
||||
return this.options.query(this.input.val(), function(data) {
|
||||
return this.options.callback(data);
|
||||
}.bind(this));
|
||||
}.bind(this), 250);
|
||||
} else {
|
||||
return _this.filter(_this.input.val());
|
||||
return this.filter(this.input.val());
|
||||
}
|
||||
};
|
||||
})(this));
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
GitLabDropdownFilter.prototype.shouldBlur = function(keyCode) {
|
||||
|
@ -382,6 +387,7 @@
|
|||
|
||||
GitLabDropdown.prototype.opened = function() {
|
||||
var contentHtml;
|
||||
currentIndex = -1;
|
||||
this.addArrowKeyEvent();
|
||||
if (this.options.setIndeterminateIds) {
|
||||
this.options.setIndeterminateIds.call(this);
|
||||
|
@ -601,7 +607,7 @@
|
|||
return this.dropdown.before($input);
|
||||
};
|
||||
|
||||
GitLabDropdown.prototype.selectRowAtIndex = function(e, index) {
|
||||
GitLabDropdown.prototype.selectRowAtIndex = function(index) {
|
||||
var $el, selector;
|
||||
selector = ".dropdown-content li:not(.divider,.dropdown-header,.separator):eq(" + index + ") a";
|
||||
if (this.dropdown.find(".dropdown-toggle-page").length) {
|
||||
|
@ -609,8 +615,6 @@
|
|||
}
|
||||
$el = $(selector, this.dropdown);
|
||||
if ($el.length) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
return $el.first().trigger('click');
|
||||
}
|
||||
};
|
||||
|
@ -619,7 +623,7 @@
|
|||
var $input, ARROW_KEY_CODES, selector;
|
||||
ARROW_KEY_CODES = [38, 40];
|
||||
$input = this.dropdown.find(".dropdown-input-field");
|
||||
selector = '.dropdown-content li:not(.divider,.dropdown-header,.separator)';
|
||||
selector = '.dropdown-content li:not(.divider,.dropdown-header,.separator):visible';
|
||||
if (this.dropdown.find(".dropdown-toggle-page").length) {
|
||||
selector = ".dropdown-page-one " + selector;
|
||||
}
|
||||
|
@ -647,7 +651,7 @@
|
|||
return false;
|
||||
}
|
||||
if (currentKeyCode === 13 && currentIndex !== -1) {
|
||||
return _this.selectRowAtIndex(e, currentIndex);
|
||||
return _this.selectRowAtIndex($('.is-focused', _this.dropdown).closest('li').index() - 1);
|
||||
}
|
||||
};
|
||||
})(this));
|
||||
|
|
|
@ -8,13 +8,16 @@
|
|||
base.utils = {};
|
||||
}
|
||||
w.gl.utils.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||
|
||||
w.gl.utils.formatDate = function(datetime) {
|
||||
return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
|
||||
};
|
||||
|
||||
w.gl.utils.getDayName = function(date) {
|
||||
return this.days[date.getDay()];
|
||||
};
|
||||
return w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago) {
|
||||
|
||||
w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago) {
|
||||
if (setTimeago == null) {
|
||||
setTimeago = true;
|
||||
}
|
||||
|
@ -31,6 +34,39 @@
|
|||
});
|
||||
}
|
||||
};
|
||||
|
||||
w.gl.utils.shortTimeAgo = function($el) {
|
||||
var shortLocale, tmpLocale;
|
||||
shortLocale = {
|
||||
prefixAgo: null,
|
||||
prefixFromNow: null,
|
||||
suffixAgo: 'ago',
|
||||
suffixFromNow: 'from now',
|
||||
seconds: '1 min',
|
||||
minute: '1 min',
|
||||
minutes: '%d mins',
|
||||
hour: '1 hr',
|
||||
hours: '%d hrs',
|
||||
day: '1 day',
|
||||
days: '%d days',
|
||||
month: '1 month',
|
||||
months: '%d months',
|
||||
year: '1 year',
|
||||
years: '%d years',
|
||||
wordSeparator: ' ',
|
||||
numbers: []
|
||||
};
|
||||
tmpLocale = $.timeago.settings.strings;
|
||||
$el.each(function(el) {
|
||||
var $el1;
|
||||
$el1 = $(this);
|
||||
return $el1.attr('title', gl.utils.formatDate($el.attr('datetime')));
|
||||
});
|
||||
$.timeago.settings.strings = shortLocale;
|
||||
$el.timeago();
|
||||
$.timeago.settings.strings = tmpLocale;
|
||||
};
|
||||
|
||||
})(window);
|
||||
|
||||
}).call(this);
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
};
|
||||
|
||||
MarkdownPreview.prototype.renderMarkdown = function(text, success) {
|
||||
if (!window.markdown_preview_path) {
|
||||
if (!window.preview_markdown_path) {
|
||||
return;
|
||||
}
|
||||
if (text === this.ajaxCache.text) {
|
||||
|
@ -36,7 +36,7 @@
|
|||
}
|
||||
return $.ajax({
|
||||
type: 'POST',
|
||||
url: window.markdown_preview_path,
|
||||
url: window.preview_markdown_path,
|
||||
data: {
|
||||
text: text
|
||||
},
|
|
@ -89,8 +89,14 @@
|
|||
toggleLabel: function(obj, $el) {
|
||||
return $el.text().trim();
|
||||
},
|
||||
clicked: function(e) {
|
||||
return $dropdown.closest('form').submit();
|
||||
clicked: function(selected, $el, e) {
|
||||
e.preventDefault()
|
||||
if ($('input[name="ref"]').length) {
|
||||
var $form = $dropdown.closest('form'),
|
||||
action = $form.attr('action'),
|
||||
divider = action.indexOf('?') < 0 ? '?' : '&';
|
||||
Turbolinks.visit(action + '' + divider + '' + $form.serialize());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
(global => {
|
||||
global.gl = global.gl || {};
|
||||
|
||||
gl.ProtectedBranchAccessDropdown = class {
|
||||
constructor(options) {
|
||||
const { $dropdown, data, onSelect } = options;
|
||||
|
||||
$dropdown.glDropdown({
|
||||
data: data,
|
||||
selectable: true,
|
||||
inputId: $dropdown.data('input-id'),
|
||||
fieldName: $dropdown.data('field-name'),
|
||||
toggleLabel(item) {
|
||||
return item.text;
|
||||
},
|
||||
clicked(item, $el, e) {
|
||||
e.preventDefault();
|
||||
onSelect();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
})(window);
|
|
@ -0,0 +1,56 @@
|
|||
(global => {
|
||||
global.gl = global.gl || {};
|
||||
|
||||
gl.ProtectedBranchCreate = class {
|
||||
constructor() {
|
||||
this.$wrap = this.$form = $('#new_protected_branch');
|
||||
this.buildDropdowns();
|
||||
}
|
||||
|
||||
buildDropdowns() {
|
||||
const $allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
|
||||
const $allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
|
||||
|
||||
// Cache callback
|
||||
this.onSelectCallback = this.onSelect.bind(this);
|
||||
|
||||
// Allowed to Merge dropdown
|
||||
new gl.ProtectedBranchAccessDropdown({
|
||||
$dropdown: $allowedToMergeDropdown,
|
||||
data: gon.merge_access_levels,
|
||||
onSelect: this.onSelectCallback
|
||||
});
|
||||
|
||||
// Allowed to Push dropdown
|
||||
new gl.ProtectedBranchAccessDropdown({
|
||||
$dropdown: $allowedToPushDropdown,
|
||||
data: gon.push_access_levels,
|
||||
onSelect: this.onSelectCallback
|
||||
});
|
||||
|
||||
// Select default
|
||||
$allowedToPushDropdown.data('glDropdown').selectRowAtIndex(0);
|
||||
$allowedToMergeDropdown.data('glDropdown').selectRowAtIndex(0);
|
||||
|
||||
// Protected branch dropdown
|
||||
new ProtectedBranchDropdown({
|
||||
$dropdown: this.$wrap.find('.js-protected-branch-select'),
|
||||
onSelect: this.onSelectCallback
|
||||
});
|
||||
}
|
||||
|
||||
// This will run after clicked callback
|
||||
onSelect() {
|
||||
|
||||
// Enable submit button
|
||||
const $branchInput = this.$wrap.find('input[name="protected_branch[name]"]');
|
||||
const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_level_attributes][access_level]"]');
|
||||
const $allowedToPushInput = this.$wrap.find('input[name="protected_branch[push_access_level_attributes][access_level]"]');
|
||||
|
||||
if ($branchInput.val() && $allowedToMergeInput.val() && $allowedToPushInput.val()){
|
||||
this.$form.find('input[type="submit"]').removeAttr('disabled');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})(window);
|
|
@ -0,0 +1,75 @@
|
|||
class ProtectedBranchDropdown {
|
||||
constructor(options) {
|
||||
this.onSelect = options.onSelect;
|
||||
this.$dropdown = options.$dropdown;
|
||||
this.$dropdownContainer = this.$dropdown.parent();
|
||||
this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer');
|
||||
this.$protectedBranch = this.$dropdownContainer.find('.create-new-protected-branch');
|
||||
|
||||
this.buildDropdown();
|
||||
this.bindEvents();
|
||||
|
||||
// Hide footer
|
||||
this.$dropdownFooter.addClass('hidden');
|
||||
}
|
||||
|
||||
buildDropdown() {
|
||||
this.$dropdown.glDropdown({
|
||||
data: this.getProtectedBranches.bind(this),
|
||||
filterable: true,
|
||||
remote: false,
|
||||
search: {
|
||||
fields: ['title']
|
||||
},
|
||||
selectable: true,
|
||||
toggleLabel(selected) {
|
||||
return (selected && 'id' in selected) ? selected.title : 'Protected Branch';
|
||||
},
|
||||
fieldName: 'protected_branch[name]',
|
||||
text(protectedBranch) {
|
||||
return _.escape(protectedBranch.title);
|
||||
},
|
||||
id(protectedBranch) {
|
||||
return _.escape(protectedBranch.id);
|
||||
},
|
||||
onFilter: this.toggleCreateNewButton.bind(this),
|
||||
clicked: (item, $el, e) => {
|
||||
e.preventDefault();
|
||||
this.onSelect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.$protectedBranch.on('click', this.onClickCreateWildcard.bind(this));
|
||||
}
|
||||
|
||||
onClickCreateWildcard() {
|
||||
this.$dropdown.data('glDropdown').remote.execute();
|
||||
this.$dropdown.data('glDropdown').selectRowAtIndex(0);
|
||||
}
|
||||
|
||||
getProtectedBranches(term, callback) {
|
||||
if (this.selectedBranch) {
|
||||
callback(gon.open_branches.concat(this.selectedBranch));
|
||||
} else {
|
||||
callback(gon.open_branches);
|
||||
}
|
||||
}
|
||||
|
||||
toggleCreateNewButton(branchName) {
|
||||
this.selectedBranch = {
|
||||
title: branchName,
|
||||
id: branchName,
|
||||
text: branchName
|
||||
};
|
||||
|
||||
if (branchName) {
|
||||
this.$dropdownContainer
|
||||
.find('.create-new-protected-branch code')
|
||||
.text(branchName);
|
||||
}
|
||||
|
||||
this.$dropdownFooter.toggleClass('hidden', !branchName);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
(global => {
|
||||
global.gl = global.gl || {};
|
||||
|
||||
gl.ProtectedBranchEdit = class {
|
||||
constructor(options) {
|
||||
this.$wrap = options.$wrap;
|
||||
this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
|
||||
this.$allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
|
||||
|
||||
this.buildDropdowns();
|
||||
}
|
||||
|
||||
buildDropdowns() {
|
||||
|
||||
// Allowed to merge dropdown
|
||||
new gl.ProtectedBranchAccessDropdown({
|
||||
$dropdown: this.$allowedToMergeDropdown,
|
||||
data: gon.merge_access_levels,
|
||||
onSelect: this.onSelect.bind(this)
|
||||
});
|
||||
|
||||
// Allowed to push dropdown
|
||||
new gl.ProtectedBranchAccessDropdown({
|
||||
$dropdown: this.$allowedToPushDropdown,
|
||||
data: gon.push_access_levels,
|
||||
onSelect: this.onSelect.bind(this)
|
||||
});
|
||||
}
|
||||
|
||||
onSelect() {
|
||||
const $allowedToMergeInput = this.$wrap.find(`input[name="${this.$allowedToMergeDropdown.data('fieldName')}"]`);
|
||||
const $allowedToPushInput = this.$wrap.find(`input[name="${this.$allowedToPushDropdown.data('fieldName')}"]`);
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: this.$wrap.data('url'),
|
||||
dataType: 'json',
|
||||
data: {
|
||||
_method: 'PATCH',
|
||||
id: this.$wrap.data('banchId'),
|
||||
protected_branch: {
|
||||
merge_access_level_attributes: {
|
||||
access_level: $allowedToMergeInput.val()
|
||||
},
|
||||
push_access_level_attributes: {
|
||||
access_level: $allowedToPushInput.val()
|
||||
}
|
||||
}
|
||||
},
|
||||
success: () => {
|
||||
this.$wrap.effect('highlight');
|
||||
},
|
||||
error() {
|
||||
$.scrollTo(0);
|
||||
new Flash('Failed to update branch!');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
})(window);
|
|
@ -0,0 +1,17 @@
|
|||
(global => {
|
||||
global.gl = global.gl || {};
|
||||
|
||||
gl.ProtectedBranchEditList = class {
|
||||
constructor() {
|
||||
this.$wrap = $('.protected-branches-list');
|
||||
|
||||
// Build edit forms
|
||||
this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => {
|
||||
new gl.ProtectedBranchEdit({
|
||||
$wrap: $(el)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
})(window);
|
|
@ -1,72 +0,0 @@
|
|||
(function() {
|
||||
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
|
||||
this.ProtectedBranchSelect = (function() {
|
||||
function ProtectedBranchSelect(currentProject) {
|
||||
this.toggleCreateNewButton = bind(this.toggleCreateNewButton, this);
|
||||
this.getProtectedBranches = bind(this.getProtectedBranches, this);
|
||||
$('.dropdown-footer').hide();
|
||||
this.dropdown = $('.js-protected-branch-select').glDropdown({
|
||||
data: this.getProtectedBranches,
|
||||
filterable: true,
|
||||
remote: false,
|
||||
search: {
|
||||
fields: ['title']
|
||||
},
|
||||
selectable: true,
|
||||
toggleLabel: function(selected) {
|
||||
if (selected && 'id' in selected) {
|
||||
return selected.title;
|
||||
} else {
|
||||
return 'Protected Branch';
|
||||
}
|
||||
},
|
||||
fieldName: 'protected_branch[name]',
|
||||
text: function(protected_branch) {
|
||||
return _.escape(protected_branch.title);
|
||||
},
|
||||
id: function(protected_branch) {
|
||||
return _.escape(protected_branch.id);
|
||||
},
|
||||
onFilter: this.toggleCreateNewButton,
|
||||
clicked: function() {
|
||||
return $('.protect-branch-btn').attr('disabled', false);
|
||||
}
|
||||
});
|
||||
$('.create-new-protected-branch').on('click', (function(_this) {
|
||||
return function(event) {
|
||||
_this.dropdown.data('glDropdown').remote.execute();
|
||||
return _this.dropdown.data('glDropdown').selectRowAtIndex(event, 0);
|
||||
};
|
||||
})(this));
|
||||
}
|
||||
|
||||
ProtectedBranchSelect.prototype.getProtectedBranches = function(term, callback) {
|
||||
if (this.selectedBranch) {
|
||||
return callback(gon.open_branches.concat(this.selectedBranch));
|
||||
} else {
|
||||
return callback(gon.open_branches);
|
||||
}
|
||||
};
|
||||
|
||||
ProtectedBranchSelect.prototype.toggleCreateNewButton = function(branchName) {
|
||||
this.selectedBranch = {
|
||||
title: branchName,
|
||||
id: branchName,
|
||||
text: branchName
|
||||
};
|
||||
if (branchName === '') {
|
||||
$('.protected-branch-select-footer-list').addClass('hidden');
|
||||
return $('.dropdown-footer').hide();
|
||||
} else {
|
||||
$('.create-new-protected-branch').text("Create Protected Branch: " + branchName);
|
||||
$('.protected-branch-select-footer-list').removeClass('hidden');
|
||||
return $('.dropdown-footer').show();
|
||||
}
|
||||
};
|
||||
|
||||
return ProtectedBranchSelect;
|
||||
|
||||
})();
|
||||
|
||||
}).call(this);
|
|
@ -1,63 +0,0 @@
|
|||
class ProtectedBranchesAccessSelect {
|
||||
constructor(container, saveOnSelect, selectDefault) {
|
||||
this.container = container;
|
||||
this.saveOnSelect = saveOnSelect;
|
||||
|
||||
this.container.find(".allowed-to-merge").each((i, element) => {
|
||||
var fieldName = $(element).data('field-name');
|
||||
var dropdown = $(element).glDropdown({
|
||||
data: gon.merge_access_levels,
|
||||
selectable: true,
|
||||
fieldName: fieldName,
|
||||
clicked: _.chain(this.onSelect).partial(element).bind(this).value()
|
||||
});
|
||||
|
||||
if (selectDefault) {
|
||||
dropdown.data('glDropdown').selectRowAtIndex(document.createEvent("Event"), 0);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this.container.find(".allowed-to-push").each((i, element) => {
|
||||
var fieldName = $(element).data('field-name');
|
||||
var dropdown = $(element).glDropdown({
|
||||
data: gon.push_access_levels,
|
||||
selectable: true,
|
||||
fieldName: fieldName,
|
||||
clicked: _.chain(this.onSelect).partial(element).bind(this).value()
|
||||
});
|
||||
|
||||
if (selectDefault) {
|
||||
dropdown.data('glDropdown').selectRowAtIndex(document.createEvent("Event"), 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onSelect(dropdown, selected, element, e) {
|
||||
$(dropdown).find('.dropdown-toggle-text').text(selected.text);
|
||||
if (this.saveOnSelect) {
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
url: $(dropdown).data('url'),
|
||||
dataType: "json",
|
||||
data: {
|
||||
_method: 'PATCH',
|
||||
id: $(dropdown).data('id'),
|
||||
protected_branch: {
|
||||
["" + ($(dropdown).data('type')) + "_attributes"]: {
|
||||
"access_level": selected.id
|
||||
}
|
||||
}
|
||||
},
|
||||
success: function() {
|
||||
var row;
|
||||
row = $(e.target);
|
||||
return row.closest('tr').effect('highlight');
|
||||
},
|
||||
error: function() {
|
||||
return new Flash("Failed to update branch!", "alert");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,14 +13,15 @@
|
|||
}
|
||||
$('.js-user-search').each((function(_this) {
|
||||
return function(i, dropdown) {
|
||||
var options = {};
|
||||
var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser;
|
||||
$dropdown = $(dropdown);
|
||||
_this.projectId = $dropdown.data('project-id');
|
||||
_this.showCurrentUser = $dropdown.data('current-user');
|
||||
options.projectId = $dropdown.data('project-id');
|
||||
options.showCurrentUser = $dropdown.data('current-user');
|
||||
showNullUser = $dropdown.data('null-user');
|
||||
showAnyUser = $dropdown.data('any-user');
|
||||
firstUser = $dropdown.data('first-user');
|
||||
_this.authorId = $dropdown.data('author-id');
|
||||
options.authorId = $dropdown.data('author-id');
|
||||
selectedId = $dropdown.data('selected');
|
||||
defaultLabel = $dropdown.data('default-label');
|
||||
issueURL = $dropdown.data('issueUpdate');
|
||||
|
@ -75,7 +76,7 @@
|
|||
data: function(term, callback) {
|
||||
var isAuthorFilter;
|
||||
isAuthorFilter = $('.js-author-search');
|
||||
return _this.users(term, function(users) {
|
||||
return _this.users(term, options, function(users) {
|
||||
var anyUser, index, j, len, name, obj, showDivider;
|
||||
if (term.length === 0) {
|
||||
showDivider = 0;
|
||||
|
@ -185,11 +186,14 @@
|
|||
$('.ajax-users-select').each((function(_this) {
|
||||
return function(i, select) {
|
||||
var firstUser, showAnyUser, showEmailUser, showNullUser;
|
||||
_this.projectId = $(select).data('project-id');
|
||||
_this.groupId = $(select).data('group-id');
|
||||
_this.showCurrentUser = $(select).data('current-user');
|
||||
_this.authorId = $(select).data('author-id');
|
||||
_this.skipUsers = $(select).data('skip-users');
|
||||
var options = {};
|
||||
options.skipLdap = $(select).hasClass('skip_ldap');
|
||||
options.projectId = $(select).data('project-id');
|
||||
options.groupId = $(select).data('group-id');
|
||||
options.showCurrentUser = $(select).data('current-user');
|
||||
options.pushCodeToProtectedBranches = $(select).data('push-code-to-protected-branches');
|
||||
options.authorId = $(select).data('author-id');
|
||||
options.skipUsers = $(select).data('skip-users');
|
||||
showNullUser = $(select).data('null-user');
|
||||
showAnyUser = $(select).data('any-user');
|
||||
showEmailUser = $(select).data('email-user');
|
||||
|
@ -199,7 +203,7 @@
|
|||
multiple: $(select).hasClass('multiselect'),
|
||||
minimumInputLength: 0,
|
||||
query: function(query) {
|
||||
return _this.users(query.term, function(users) {
|
||||
return _this.users(query.term, options, function(users) {
|
||||
var anyUser, data, emailUser, index, j, len, name, nullUser, obj, ref;
|
||||
data = {
|
||||
results: users
|
||||
|
@ -309,7 +313,7 @@
|
|||
});
|
||||
};
|
||||
|
||||
UsersSelect.prototype.users = function(query, callback) {
|
||||
UsersSelect.prototype.users = function(query, options, callback) {
|
||||
var url;
|
||||
url = this.buildUrl(this.usersPath);
|
||||
return $.ajax({
|
||||
|
@ -318,11 +322,13 @@
|
|||
search: query,
|
||||
per_page: 20,
|
||||
active: true,
|
||||
project_id: this.projectId,
|
||||
group_id: this.groupId,
|
||||
current_user: this.showCurrentUser,
|
||||
author_id: this.authorId,
|
||||
skip_users: this.skipUsers
|
||||
project_id: options.projectId || null,
|
||||
group_id: options.groupId || null,
|
||||
skip_ldap: options.skipLdap || null,
|
||||
current_user: options.showCurrentUser || null,
|
||||
push_code_to_protected_branches: options.pushCodeToProtectedBranches || null,
|
||||
author_id: options.authorId || null,
|
||||
skip_users: options.skipUsers || null
|
||||
},
|
||||
dataType: "json"
|
||||
}).done(function(users) {
|
||||
|
|
|
@ -72,6 +72,14 @@
|
|||
&.large {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
&.wide {
|
||||
width: 100%;
|
||||
|
||||
+ .dropdown-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu,
|
||||
|
|
|
@ -114,6 +114,12 @@ ul.content-list {
|
|||
font-size: $list-font-size;
|
||||
color: $list-text-color;
|
||||
|
||||
&.no-description {
|
||||
.title {
|
||||
line-height: $list-text-height;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
@ -134,12 +140,11 @@ ul.content-list {
|
|||
}
|
||||
|
||||
.controls {
|
||||
padding-top: 1px;
|
||||
float: right;
|
||||
|
||||
> .control-text {
|
||||
margin-right: $gl-padding-top;
|
||||
line-height: 40px;
|
||||
line-height: $list-text-height;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
|
@ -150,7 +155,7 @@ ul.content-list {
|
|||
> .btn-group {
|
||||
margin-right: $gl-padding-top;
|
||||
display: inline-block;
|
||||
margin-top: 4px;
|
||||
margin-top: 3px;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&:last-child {
|
||||
|
|
|
@ -23,4 +23,9 @@
|
|||
margin-top: $gl-padding;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ $gl-header-color: $gl-title-color;
|
|||
$list-font-size: $gl-font-size;
|
||||
$list-title-color: $gl-title-color;
|
||||
$list-text-color: $gl-text-color;
|
||||
$list-text-height: 42px;
|
||||
|
||||
/*
|
||||
* Markdown
|
||||
|
|
|
@ -1,5 +1,35 @@
|
|||
.environments {
|
||||
|
||||
.commit-title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.fa-play {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dropdown-new {
|
||||
color: $table-text-gray;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
|
||||
.fa {
|
||||
margin-right: 6px;
|
||||
color: $table-text-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.branch-name {
|
||||
color: $gl-dark-link-color;
|
||||
}
|
||||
}
|
||||
|
||||
.table.builds.environments {
|
||||
min-width: 500px;
|
||||
|
||||
.icon-container {
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,15 +23,9 @@
|
|||
}
|
||||
|
||||
.group-row {
|
||||
&.no-description {
|
||||
.group-name {
|
||||
line-height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
.stats {
|
||||
float: right;
|
||||
line-height: 44px;
|
||||
line-height: $list-text-height;
|
||||
color: $gl-gray;
|
||||
|
||||
span {
|
||||
|
|
|
@ -182,6 +182,17 @@
|
|||
.btn {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
a.btn {
|
||||
padding: 0;
|
||||
|
||||
.has-tooltip {
|
||||
top: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
line-height: 1.1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.label-options-toggle {
|
||||
|
|
|
@ -512,18 +512,12 @@ pre.light-well {
|
|||
.project-row {
|
||||
border-color: $table-border-color;
|
||||
|
||||
&.no-description {
|
||||
.project {
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.project-full-name {
|
||||
@include str-truncated;
|
||||
}
|
||||
|
||||
.controls {
|
||||
line-height: 40px;
|
||||
line-height: $list-text-height;
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
|
@ -662,13 +656,9 @@ pre.light-well {
|
|||
}
|
||||
|
||||
.new_protected_branch {
|
||||
.dropdown {
|
||||
display: inline;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
label {
|
||||
min-width: 120px;
|
||||
margin-top: 6px;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -684,6 +674,21 @@ pre.light-well {
|
|||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-message {
|
||||
margin: 0;
|
||||
border-radius: 0 0 1px 1px;
|
||||
padding: 20px 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.table-bordered {
|
||||
border-radius: 1px;
|
||||
|
||||
th:not(:last-child), td:not(:last-child) {
|
||||
border-right: solid 1px transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-notifications-form {
|
||||
|
|
|
@ -48,9 +48,9 @@ class Admin::GroupsController < Admin::ApplicationController
|
|||
end
|
||||
|
||||
def destroy
|
||||
DestroyGroupService.new(@group, current_user).execute
|
||||
DestroyGroupService.new(@group, current_user).async_execute
|
||||
|
||||
redirect_to admin_groups_path, notice: 'Group was successfully deleted.'
|
||||
redirect_to admin_groups_path, alert: "Group '#{@group.name}' was scheduled for deletion."
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -87,9 +87,9 @@ class GroupsController < Groups::ApplicationController
|
|||
end
|
||||
|
||||
def destroy
|
||||
DestroyGroupService.new(@group, current_user).execute
|
||||
DestroyGroupService.new(@group, current_user).async_execute
|
||||
|
||||
redirect_to root_path, alert: "Group '#{@group.name}' was successfully deleted."
|
||||
redirect_to root_path, alert: "Group '#{@group.name}' was scheduled for deletion."
|
||||
end
|
||||
|
||||
protected
|
||||
|
|
|
@ -12,13 +12,14 @@ class Import::GitlabProjectsController < Import::BaseController
|
|||
return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." })
|
||||
end
|
||||
|
||||
imported_file = project_params[:file].path + "-import"
|
||||
import_upload_path = Gitlab::ImportExport.import_upload_path(filename: project_params[:file].original_filename)
|
||||
|
||||
FileUtils.copy_entry(project_params[:file].path, imported_file)
|
||||
FileUtils.mkdir_p(File.dirname(import_upload_path))
|
||||
FileUtils.copy_entry(project_params[:file].path, import_upload_path)
|
||||
|
||||
@project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id],
|
||||
current_user,
|
||||
File.expand_path(imported_file),
|
||||
import_upload_path,
|
||||
project_params[:path]).execute
|
||||
|
||||
if @project.saved?
|
||||
|
|
|
@ -50,6 +50,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController
|
|||
flash[:notice] = "Password was successfully updated. Please login with it"
|
||||
redirect_to new_user_session_path
|
||||
else
|
||||
@user.reload
|
||||
render 'edit'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,8 +8,9 @@ class Projects::BadgesController < Projects::ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.html { render_404 }
|
||||
|
||||
format.svg do
|
||||
send_data(badge.data, type: badge.type, disposition: 'inline')
|
||||
render 'badge', locals: { badge: badge.template }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -76,6 +76,8 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def diff
|
||||
apply_diff_view_cookie!
|
||||
|
||||
@form = UnfoldForm.new(params)
|
||||
@lines = Gitlab::Highlight.highlight_lines(repository, @ref, @path)
|
||||
@lines = @lines[@form.since - 1..@form.to - 1]
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
class Projects::BranchesController < Projects::ApplicationController
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
include SortingHelper
|
||||
# Authorize
|
||||
before_action :require_non_empty_project
|
||||
before_action :authorize_download_code!
|
||||
before_action :authorize_push_code!, only: [:new, :create, :destroy]
|
||||
|
||||
def index
|
||||
@sort = params[:sort].presence || 'name'
|
||||
@sort = params[:sort].presence || sort_value_name
|
||||
@branches = BranchesFinder.new(@repository, params).execute
|
||||
@branches = Kaminari.paginate_array(@branches).page(params[:page])
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ class Projects::BuildsController < Projects::ApplicationController
|
|||
|
||||
def index
|
||||
@scope = params[:scope]
|
||||
@all_builds = project.builds
|
||||
@all_builds = project.builds.relevant
|
||||
@builds = @all_builds.order('created_at DESC')
|
||||
@builds =
|
||||
case @scope
|
||||
|
|
|
@ -134,8 +134,8 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def define_status_vars
|
||||
@statuses = CommitStatus.where(pipeline: pipelines)
|
||||
@builds = Ci::Build.where(pipeline: pipelines)
|
||||
@statuses = CommitStatus.where(pipeline: pipelines).relevant
|
||||
@builds = Ci::Build.where(pipeline: pipelines).relevant
|
||||
end
|
||||
|
||||
def assign_change_commit_vars(mr_source_branch)
|
||||
|
|
|
@ -12,8 +12,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def new
|
||||
redirect_to namespace_project_deploy_keys_path(@project.namespace,
|
||||
@project)
|
||||
redirect_to namespace_project_deploy_keys_path(@project.namespace, @project)
|
||||
end
|
||||
|
||||
def create
|
||||
|
@ -21,19 +20,16 @@ class Projects::DeployKeysController < Projects::ApplicationController
|
|||
set_index_vars
|
||||
|
||||
if @key.valid? && @project.deploy_keys << @key
|
||||
redirect_to namespace_project_deploy_keys_path(@project.namespace,
|
||||
@project)
|
||||
redirect_to namespace_project_deploy_keys_path(@project.namespace, @project)
|
||||
else
|
||||
render "index"
|
||||
end
|
||||
end
|
||||
|
||||
def enable
|
||||
@key = accessible_keys.find(params[:id])
|
||||
@project.deploy_keys << @key
|
||||
Projects::EnableDeployKeyService.new(@project, current_user, params).execute
|
||||
|
||||
redirect_to namespace_project_deploy_keys_path(@project.namespace,
|
||||
@project)
|
||||
redirect_to namespace_project_deploy_keys_path(@project.namespace, @project)
|
||||
end
|
||||
|
||||
def disable
|
||||
|
@ -45,9 +41,9 @@ class Projects::DeployKeysController < Projects::ApplicationController
|
|||
protected
|
||||
|
||||
def set_index_vars
|
||||
@enabled_keys ||= @project.deploy_keys
|
||||
@enabled_keys ||= @project.deploy_keys
|
||||
|
||||
@available_keys ||= accessible_keys - @enabled_keys
|
||||
@available_keys ||= current_user.accessible_deploy_keys - @enabled_keys
|
||||
@available_project_keys ||= current_user.project_deploy_keys - @enabled_keys
|
||||
@available_public_keys ||= DeployKey.are_public - @enabled_keys
|
||||
|
||||
|
@ -56,10 +52,6 @@ class Projects::DeployKeysController < Projects::ApplicationController
|
|||
@available_public_keys -= @available_project_keys
|
||||
end
|
||||
|
||||
def accessible_keys
|
||||
@accessible_keys ||= current_user.accessible_deploy_keys
|
||||
end
|
||||
|
||||
def deploy_key_params
|
||||
params.require(:deploy_key).permit(:key, :title)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
# This file should be identical in GitLab Community Edition and Enterprise Edition
|
||||
|
||||
class Projects::GitHttpClientController < Projects::ApplicationController
|
||||
include ActionController::HttpAuthentication::Basic
|
||||
include KerberosSpnegoHelper
|
||||
|
||||
attr_reader :user
|
||||
|
||||
# Git clients will not know what authenticity token to send along
|
||||
skip_before_action :verify_authenticity_token
|
||||
skip_before_action :repository
|
||||
before_action :authenticate_user
|
||||
before_action :ensure_project_found!
|
||||
|
||||
private
|
||||
|
||||
def authenticate_user
|
||||
if project && project.public? && download_request?
|
||||
return # Allow access
|
||||
end
|
||||
|
||||
if allow_basic_auth? && basic_auth_provided?
|
||||
login, password = user_name_and_password(request)
|
||||
auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
|
||||
|
||||
if auth_result.type == :ci && download_request?
|
||||
@ci = true
|
||||
elsif auth_result.type == :oauth && !download_request?
|
||||
# Not allowed
|
||||
else
|
||||
@user = auth_result.user
|
||||
end
|
||||
|
||||
if ci? || user
|
||||
return # Allow access
|
||||
end
|
||||
elsif allow_kerberos_spnego_auth? && spnego_provided?
|
||||
@user = find_kerberos_user
|
||||
|
||||
if user
|
||||
send_final_spnego_response
|
||||
return # Allow access
|
||||
end
|
||||
end
|
||||
|
||||
send_challenges
|
||||
render plain: "HTTP Basic: Access denied\n", status: 401
|
||||
end
|
||||
|
||||
def basic_auth_provided?
|
||||
has_basic_credentials?(request)
|
||||
end
|
||||
|
||||
def send_challenges
|
||||
challenges = []
|
||||
challenges << 'Basic realm="GitLab"' if allow_basic_auth?
|
||||
challenges << spnego_challenge if allow_kerberos_spnego_auth?
|
||||
headers['Www-Authenticate'] = challenges.join("\n") if challenges.any?
|
||||
end
|
||||
|
||||
def ensure_project_found!
|
||||
render_not_found if project.blank?
|
||||
end
|
||||
|
||||
def project
|
||||
return @project if defined?(@project)
|
||||
|
||||
project_id, _ = project_id_with_suffix
|
||||
if project_id.blank?
|
||||
@project = nil
|
||||
else
|
||||
@project = Project.find_with_namespace("#{params[:namespace_id]}/#{project_id}")
|
||||
end
|
||||
end
|
||||
|
||||
# This method returns two values so that we can parse
|
||||
# params[:project_id] (untrusted input!) in exactly one place.
|
||||
def project_id_with_suffix
|
||||
id = params[:project_id] || ''
|
||||
|
||||
%w[.wiki.git .git].each do |suffix|
|
||||
if id.end_with?(suffix)
|
||||
# Be careful to only remove the suffix from the end of 'id'.
|
||||
# Accidentally removing it from the middle is how security
|
||||
# vulnerabilities happen!
|
||||
return [id.slice(0, id.length - suffix.length), suffix]
|
||||
end
|
||||
end
|
||||
|
||||
# Something is wrong with params[:project_id]; do not pass it on.
|
||||
[nil, nil]
|
||||
end
|
||||
|
||||
def repository
|
||||
_, suffix = project_id_with_suffix
|
||||
if suffix == '.wiki.git'
|
||||
project.wiki.repository
|
||||
else
|
||||
project.repository
|
||||
end
|
||||
end
|
||||
|
||||
def render_not_found
|
||||
render plain: 'Not Found', status: :not_found
|
||||
end
|
||||
|
||||
def ci?
|
||||
@ci.present?
|
||||
end
|
||||
end
|
|
@ -1,17 +1,6 @@
|
|||
# This file should be identical in GitLab Community Edition and Enterprise Edition
|
||||
|
||||
class Projects::GitHttpController < Projects::ApplicationController
|
||||
include ActionController::HttpAuthentication::Basic
|
||||
include KerberosSpnegoHelper
|
||||
|
||||
attr_reader :user
|
||||
|
||||
# Git clients will not know what authenticity token to send along
|
||||
skip_before_action :verify_authenticity_token
|
||||
skip_before_action :repository
|
||||
before_action :authenticate_user
|
||||
before_action :ensure_project_found!
|
||||
|
||||
class Projects::GitHttpController < Projects::GitHttpClientController
|
||||
# GET /foo/bar.git/info/refs?service=git-upload-pack (git pull)
|
||||
# GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
|
||||
def info_refs
|
||||
|
@ -20,9 +9,9 @@ class Projects::GitHttpController < Projects::ApplicationController
|
|||
elsif receive_pack? && receive_pack_allowed?
|
||||
render_ok
|
||||
elsif http_blocked?
|
||||
render_not_allowed
|
||||
render_http_not_allowed
|
||||
else
|
||||
render_not_found
|
||||
render_denied
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -31,7 +20,7 @@ class Projects::GitHttpController < Projects::ApplicationController
|
|||
if upload_pack? && upload_pack_allowed?
|
||||
render_ok
|
||||
else
|
||||
render_not_found
|
||||
render_denied
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -40,87 +29,14 @@ class Projects::GitHttpController < Projects::ApplicationController
|
|||
if receive_pack? && receive_pack_allowed?
|
||||
render_ok
|
||||
else
|
||||
render_not_found
|
||||
render_denied
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authenticate_user
|
||||
if project && project.public? && upload_pack?
|
||||
return # Allow access
|
||||
end
|
||||
|
||||
if allow_basic_auth? && basic_auth_provided?
|
||||
login, password = user_name_and_password(request)
|
||||
auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
|
||||
|
||||
if auth_result.type == :ci && upload_pack?
|
||||
@ci = true
|
||||
elsif auth_result.type == :oauth && !upload_pack?
|
||||
# Not allowed
|
||||
else
|
||||
@user = auth_result.user
|
||||
end
|
||||
|
||||
if ci? || user
|
||||
return # Allow access
|
||||
end
|
||||
elsif allow_kerberos_spnego_auth? && spnego_provided?
|
||||
@user = find_kerberos_user
|
||||
|
||||
if user
|
||||
send_final_spnego_response
|
||||
return # Allow access
|
||||
end
|
||||
end
|
||||
|
||||
send_challenges
|
||||
render plain: "HTTP Basic: Access denied\n", status: 401
|
||||
end
|
||||
|
||||
def basic_auth_provided?
|
||||
has_basic_credentials?(request)
|
||||
end
|
||||
|
||||
def send_challenges
|
||||
challenges = []
|
||||
challenges << 'Basic realm="GitLab"' if allow_basic_auth?
|
||||
challenges << spnego_challenge if allow_kerberos_spnego_auth?
|
||||
headers['Www-Authenticate'] = challenges.join("\n") if challenges.any?
|
||||
end
|
||||
|
||||
def ensure_project_found!
|
||||
render_not_found if project.blank?
|
||||
end
|
||||
|
||||
def project
|
||||
return @project if defined?(@project)
|
||||
|
||||
project_id, _ = project_id_with_suffix
|
||||
if project_id.blank?
|
||||
@project = nil
|
||||
else
|
||||
@project = Project.find_with_namespace("#{params[:namespace_id]}/#{project_id}")
|
||||
end
|
||||
end
|
||||
|
||||
# This method returns two values so that we can parse
|
||||
# params[:project_id] (untrusted input!) in exactly one place.
|
||||
def project_id_with_suffix
|
||||
id = params[:project_id] || ''
|
||||
|
||||
%w[.wiki.git .git].each do |suffix|
|
||||
if id.end_with?(suffix)
|
||||
# Be careful to only remove the suffix from the end of 'id'.
|
||||
# Accidentally removing it from the middle is how security
|
||||
# vulnerabilities happen!
|
||||
return [id.slice(0, id.length - suffix.length), suffix]
|
||||
end
|
||||
end
|
||||
|
||||
# Something is wrong with params[:project_id]; do not pass it on.
|
||||
[nil, nil]
|
||||
def download_request?
|
||||
upload_pack?
|
||||
end
|
||||
|
||||
def upload_pack?
|
||||
|
@ -143,47 +59,37 @@ class Projects::GitHttpController < Projects::ApplicationController
|
|||
render json: Gitlab::Workhorse.git_http_ok(repository, user)
|
||||
end
|
||||
|
||||
def repository
|
||||
_, suffix = project_id_with_suffix
|
||||
if suffix == '.wiki.git'
|
||||
project.wiki.repository
|
||||
def render_http_not_allowed
|
||||
render plain: access_check.message, status: :forbidden
|
||||
end
|
||||
|
||||
def render_denied
|
||||
if user && user.can?(:read_project, project)
|
||||
render plain: 'Access denied', status: :forbidden
|
||||
else
|
||||
project.repository
|
||||
# Do not leak information about project existence
|
||||
render_not_found
|
||||
end
|
||||
end
|
||||
|
||||
def render_not_found
|
||||
render plain: 'Not Found', status: :not_found
|
||||
end
|
||||
|
||||
def render_not_allowed
|
||||
render plain: download_access.message, status: :forbidden
|
||||
end
|
||||
|
||||
def ci?
|
||||
@ci.present?
|
||||
end
|
||||
|
||||
def upload_pack_allowed?
|
||||
return false unless Gitlab.config.gitlab_shell.upload_pack
|
||||
|
||||
if user
|
||||
download_access.allowed?
|
||||
access_check.allowed?
|
||||
else
|
||||
ci? || project.public?
|
||||
end
|
||||
end
|
||||
|
||||
def access
|
||||
return @access if defined?(@access)
|
||||
|
||||
@access = Gitlab::GitAccess.new(user, project, 'http')
|
||||
@access ||= Gitlab::GitAccess.new(user, project, 'http')
|
||||
end
|
||||
|
||||
def download_access
|
||||
return @download_access if defined?(@download_access)
|
||||
|
||||
@download_access = access.check('git-upload-pack')
|
||||
def access_check
|
||||
# Use the magic string '_any' to indicate we do not know what the
|
||||
# changes are. This is also what gitlab-shell does.
|
||||
@access_check ||= access.check(git_command, '_any')
|
||||
end
|
||||
|
||||
def http_blocked?
|
||||
|
@ -193,8 +99,6 @@ class Projects::GitHttpController < Projects::ApplicationController
|
|||
def receive_pack_allowed?
|
||||
return false unless Gitlab.config.gitlab_shell.receive_pack
|
||||
|
||||
# Skip user authorization on upload request.
|
||||
# It will be done by the pre-receive hook in the repository.
|
||||
user.present?
|
||||
access_check.allowed?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
class Projects::LfsApiController < Projects::GitHttpClientController
|
||||
include LfsHelper
|
||||
|
||||
before_action :require_lfs_enabled!
|
||||
before_action :lfs_check_access!, except: [:deprecated]
|
||||
|
||||
def batch
|
||||
unless objects.present?
|
||||
render_lfs_not_found
|
||||
return
|
||||
end
|
||||
|
||||
if download_request?
|
||||
render json: { objects: download_objects! }
|
||||
elsif upload_request?
|
||||
render json: { objects: upload_objects! }
|
||||
else
|
||||
raise "Never reached"
|
||||
end
|
||||
end
|
||||
|
||||
def deprecated
|
||||
render(
|
||||
json: {
|
||||
message: 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.',
|
||||
documentation_url: "#{Gitlab.config.gitlab.url}/help",
|
||||
},
|
||||
status: 501
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def objects
|
||||
@objects ||= (params[:objects] || []).to_a
|
||||
end
|
||||
|
||||
def existing_oids
|
||||
@existing_oids ||= begin
|
||||
storage_project.lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid)
|
||||
end
|
||||
end
|
||||
|
||||
def download_objects!
|
||||
objects.each do |object|
|
||||
if existing_oids.include?(object[:oid])
|
||||
object[:actions] = download_actions(object)
|
||||
else
|
||||
object[:error] = {
|
||||
code: 404,
|
||||
message: "Object does not exist on the server or you don't have permissions to access it",
|
||||
}
|
||||
end
|
||||
end
|
||||
objects
|
||||
end
|
||||
|
||||
def upload_objects!
|
||||
objects.each do |object|
|
||||
object[:actions] = upload_actions(object) unless existing_oids.include?(object[:oid])
|
||||
end
|
||||
objects
|
||||
end
|
||||
|
||||
def download_actions(object)
|
||||
{
|
||||
download: {
|
||||
href: "#{project.http_url_to_repo}/gitlab-lfs/objects/#{object[:oid]}",
|
||||
header: {
|
||||
Authorization: request.headers['Authorization']
|
||||
}.compact
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def upload_actions(object)
|
||||
{
|
||||
upload: {
|
||||
href: "#{project.http_url_to_repo}/gitlab-lfs/objects/#{object[:oid]}/#{object[:size]}",
|
||||
header: {
|
||||
Authorization: request.headers['Authorization']
|
||||
}.compact
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def download_request?
|
||||
params[:operation] == 'download'
|
||||
end
|
||||
|
||||
def upload_request?
|
||||
params[:operation] == 'upload'
|
||||
end
|
||||
end
|
|
@ -0,0 +1,92 @@
|
|||
class Projects::LfsStorageController < Projects::GitHttpClientController
|
||||
include LfsHelper
|
||||
|
||||
before_action :require_lfs_enabled!
|
||||
before_action :lfs_check_access!
|
||||
|
||||
def download
|
||||
lfs_object = LfsObject.find_by_oid(oid)
|
||||
unless lfs_object && lfs_object.file.exists?
|
||||
render_lfs_not_found
|
||||
return
|
||||
end
|
||||
|
||||
send_file lfs_object.file.path, content_type: "application/octet-stream"
|
||||
end
|
||||
|
||||
def upload_authorize
|
||||
render(
|
||||
json: {
|
||||
StoreLFSPath: "#{Gitlab.config.lfs.storage_path}/tmp/upload",
|
||||
LfsOid: oid,
|
||||
LfsSize: size,
|
||||
},
|
||||
content_type: 'application/json; charset=utf-8'
|
||||
)
|
||||
end
|
||||
|
||||
def upload_finalize
|
||||
unless tmp_filename
|
||||
render_lfs_forbidden
|
||||
return
|
||||
end
|
||||
|
||||
if store_file(oid, size, tmp_filename)
|
||||
head 200
|
||||
else
|
||||
render plain: 'Unprocessable entity', status: 422
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def download_request?
|
||||
action_name == 'download'
|
||||
end
|
||||
|
||||
def upload_request?
|
||||
%w[upload_authorize upload_finalize].include? action_name
|
||||
end
|
||||
|
||||
def oid
|
||||
params[:oid].to_s
|
||||
end
|
||||
|
||||
def size
|
||||
params[:size].to_i
|
||||
end
|
||||
|
||||
def tmp_filename
|
||||
name = request.headers['X-Gitlab-Lfs-Tmp']
|
||||
return if name.include?('/')
|
||||
return unless oid.present? && name.start_with?(oid)
|
||||
name
|
||||
end
|
||||
|
||||
def store_file(oid, size, tmp_file)
|
||||
# Define tmp_file_path early because we use it in "ensure"
|
||||
tmp_file_path = File.join("#{Gitlab.config.lfs.storage_path}/tmp/upload", tmp_file)
|
||||
|
||||
object = LfsObject.find_or_create_by(oid: oid, size: size)
|
||||
file_exists = object.file.exists? || move_tmp_file_to_storage(object, tmp_file_path)
|
||||
file_exists && link_to_project(object)
|
||||
ensure
|
||||
FileUtils.rm_f(tmp_file_path)
|
||||
end
|
||||
|
||||
def move_tmp_file_to_storage(object, path)
|
||||
File.open(path) do |f|
|
||||
object.file = f
|
||||
end
|
||||
|
||||
object.file.store!
|
||||
object.save
|
||||
end
|
||||
|
||||
def link_to_project(object)
|
||||
if object && !object.projects.exists?(storage_project.id)
|
||||
object.projects << storage_project
|
||||
object.save
|
||||
end
|
||||
end
|
||||
end
|
|
@ -160,7 +160,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
@diff_notes_disabled = true
|
||||
|
||||
@pipeline = @merge_request.pipeline
|
||||
@statuses = @pipeline.statuses if @pipeline
|
||||
@statuses = @pipeline.statuses.relevant if @pipeline
|
||||
|
||||
@note_counts = Note.where(commit_id: @commits.map(&:id)).
|
||||
group(:commit_id).count
|
||||
|
@ -362,7 +362,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
@commits_count = @merge_request.commits.count
|
||||
|
||||
@pipeline = @merge_request.pipeline
|
||||
@statuses = @pipeline.statuses if @pipeline
|
||||
@statuses = @pipeline.statuses.relevant if @pipeline
|
||||
|
||||
if @merge_request.locked_long_ago?
|
||||
@merge_request.unlock_mr
|
||||
|
|
|
@ -19,7 +19,7 @@ class Projects::PipelinesController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def create
|
||||
@pipeline = Ci::CreatePipelineService.new(project, current_user, create_params).execute
|
||||
@pipeline = Ci::CreatePipelineService.new(project, current_user, create_params).execute(ignore_skip_ci: true, save_on_errors: false)
|
||||
unless @pipeline.persisted?
|
||||
render 'new'
|
||||
return
|
||||
|
|
|
@ -3,7 +3,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
|
|||
|
||||
def show
|
||||
@ref = params[:ref] || @project.default_branch || 'master'
|
||||
@build_badge = Gitlab::Badge::Build.new(@project, @ref)
|
||||
@build_badge = Gitlab::Badge::Build.new(@project, @ref).metadata
|
||||
end
|
||||
|
||||
def update
|
||||
|
|
|
@ -91,7 +91,7 @@ class Projects::WikisController < Projects::ApplicationController
|
|||
)
|
||||
end
|
||||
|
||||
def markdown_preview
|
||||
def preview_markdown
|
||||
text = params[:text]
|
||||
|
||||
ext = Gitlab::ReferenceExtractor.new(@project, current_user)
|
||||
|
|
|
@ -125,7 +125,7 @@ class ProjectsController < Projects::ApplicationController
|
|||
def destroy
|
||||
return access_denied! unless can?(current_user, :remove_project, @project)
|
||||
|
||||
::Projects::DestroyService.new(@project, current_user, {}).pending_delete!
|
||||
::Projects::DestroyService.new(@project, current_user, {}).async_execute
|
||||
flash[:alert] = "Project '#{@project.name}' will be deleted."
|
||||
|
||||
redirect_to dashboard_projects_path
|
||||
|
@ -238,7 +238,7 @@ class ProjectsController < Projects::ApplicationController
|
|||
}
|
||||
end
|
||||
|
||||
def markdown_preview
|
||||
def preview_markdown
|
||||
text = params[:text]
|
||||
|
||||
ext = Gitlab::ReferenceExtractor.new(@project, current_user)
|
||||
|
|
|
@ -33,7 +33,7 @@ class RegistrationsController < Devise::RegistrationsController
|
|||
|
||||
protected
|
||||
|
||||
def build_resource(hash=nil)
|
||||
def build_resource(hash = nil)
|
||||
super
|
||||
end
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ class SessionsController < Devise::SessionsController
|
|||
# Prevent alert from popping up on the first page shown after authentication.
|
||||
flash[:alert] = nil
|
||||
|
||||
redirect_to user_omniauth_authorize_path(provider.to_sym)
|
||||
redirect_to omniauth_authorize_path(:user, provider)
|
||||
end
|
||||
|
||||
def valid_otp_attempt?(user)
|
||||
|
|
|
@ -163,9 +163,13 @@ module ApplicationHelper
|
|||
# `html_class` argument is provided.
|
||||
#
|
||||
# Returns an HTML-safe String
|
||||
def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago', skip_js: false)
|
||||
def time_ago_with_tooltip(time, placement: 'top', html_class: '', skip_js: false, short_format: false)
|
||||
css_classes = short_format ? 'js-short-timeago' : 'js-timeago'
|
||||
css_classes << " #{html_class}" unless html_class.blank?
|
||||
css_classes << ' js-timeago-pending' unless skip_js
|
||||
|
||||
element = content_tag :time, time.to_s,
|
||||
class: "#{html_class} js-timeago #{"js-timeago-pending" unless skip_js}",
|
||||
class: css_classes,
|
||||
datetime: time.to_time.getutc.iso8601,
|
||||
title: time.to_time.in_time_zone.to_s(:medium),
|
||||
data: { toggle: 'tooltip', placement: placement, container: 'body' }
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
module AvatarsHelper
|
||||
|
||||
def author_avatar(commit_or_event, options = {})
|
||||
user_avatar(options.merge({
|
||||
user: commit_or_event.author,
|
||||
|
@ -8,8 +7,6 @@ module AvatarsHelper
|
|||
}))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_avatar(options = {})
|
||||
avatar_size = options[:size] || 16
|
||||
user_name = options[:user].try(:name) || options[:user_name]
|
||||
|
@ -26,5 +23,4 @@ module AvatarsHelper
|
|||
mail_to(options[:user_email], avatar)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -13,12 +13,11 @@ module DiffHelper
|
|||
end
|
||||
|
||||
def diff_view
|
||||
diff_views = %w(inline parallel)
|
||||
|
||||
if diff_views.include?(cookies[:diff_view])
|
||||
cookies[:diff_view]
|
||||
else
|
||||
diff_views.first
|
||||
@diff_view ||= begin
|
||||
diff_views = %w(inline parallel)
|
||||
diff_view = cookies[:diff_view]
|
||||
diff_view = diff_views.first unless diff_views.include?(diff_view)
|
||||
diff_view.to_sym
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -33,12 +32,23 @@ module DiffHelper
|
|||
options
|
||||
end
|
||||
|
||||
def unfold_bottom_class(bottom)
|
||||
bottom ? 'js-unfold js-unfold-bottom' : ''
|
||||
end
|
||||
def diff_match_line(old_pos, new_pos, text: '', view: :inline, bottom: false)
|
||||
content = content_tag :td, text, class: "line_content match #{view == :inline ? '' : view}"
|
||||
cls = ['diff-line-num', 'unfold', 'js-unfold']
|
||||
cls << 'js-unfold-bottom' if bottom
|
||||
|
||||
def unfold_class(unfold)
|
||||
unfold ? 'unfold js-unfold' : ''
|
||||
html = ''
|
||||
if old_pos
|
||||
html << content_tag(:td, '...', class: cls + ['old_line'], data: { linenumber: old_pos })
|
||||
html << content unless view == :inline
|
||||
end
|
||||
|
||||
if new_pos
|
||||
html << content_tag(:td, '...', class: cls + ['new_line'], data: { linenumber: new_pos })
|
||||
html << content
|
||||
end
|
||||
|
||||
html.html_safe
|
||||
end
|
||||
|
||||
def diff_line_content(line, line_type = nil)
|
||||
|
@ -67,11 +77,11 @@ module DiffHelper
|
|||
end
|
||||
|
||||
def inline_diff_btn
|
||||
diff_btn('Inline', 'inline', diff_view == 'inline')
|
||||
diff_btn('Inline', 'inline', diff_view == :inline)
|
||||
end
|
||||
|
||||
def parallel_diff_btn
|
||||
diff_btn('Side-by-side', 'parallel', diff_view == 'parallel')
|
||||
diff_btn('Side-by-side', 'parallel', diff_view == :parallel)
|
||||
end
|
||||
|
||||
def submodule_link(blob, ref, repository = @repository)
|
||||
|
@ -99,11 +109,11 @@ module DiffHelper
|
|||
end
|
||||
end
|
||||
|
||||
def diff_file_html_data(project, diff_file)
|
||||
commit = commit_for_diff(diff_file)
|
||||
def diff_file_html_data(project, diff_file_path, diff_commit_id)
|
||||
{
|
||||
blob_diff_path: namespace_project_blob_diff_path(project.namespace, project,
|
||||
tree_join(commit.id, diff_file.file_path))
|
||||
tree_join(diff_commit_id, diff_file_path)),
|
||||
view: diff_view
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
module ExploreHelper
|
||||
def filter_projects_path(options={})
|
||||
def filter_projects_path(options = {})
|
||||
exist_opts = {
|
||||
sort: params[:sort],
|
||||
scope: params[:scope],
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
module LfsHelper
|
||||
def require_lfs_enabled!
|
||||
return if Gitlab.config.lfs.enabled
|
||||
|
||||
render(
|
||||
json: {
|
||||
message: 'Git LFS is not enabled on this GitLab server, contact your admin.',
|
||||
documentation_url: "#{Gitlab.config.gitlab.url}/help",
|
||||
},
|
||||
status: 501
|
||||
)
|
||||
end
|
||||
|
||||
def lfs_check_access!
|
||||
return if download_request? && lfs_download_access?
|
||||
return if upload_request? && lfs_upload_access?
|
||||
|
||||
if project.public? || (user && user.can?(:read_project, project))
|
||||
render_lfs_forbidden
|
||||
else
|
||||
render_lfs_not_found
|
||||
end
|
||||
end
|
||||
|
||||
def lfs_download_access?
|
||||
project.public? || ci? || (user && user.can?(:download_code, project))
|
||||
end
|
||||
|
||||
def lfs_upload_access?
|
||||
user && user.can?(:push_code, project)
|
||||
end
|
||||
|
||||
def render_lfs_forbidden
|
||||
render(
|
||||
json: {
|
||||
message: 'Access forbidden. Check your access level.',
|
||||
documentation_url: "#{Gitlab.config.gitlab.url}/help",
|
||||
},
|
||||
content_type: "application/vnd.git-lfs+json",
|
||||
status: 403
|
||||
)
|
||||
end
|
||||
|
||||
def render_lfs_not_found
|
||||
render(
|
||||
json: {
|
||||
message: 'Not found.',
|
||||
documentation_url: "#{Gitlab.config.gitlab.url}/help",
|
||||
},
|
||||
content_type: "application/vnd.git-lfs+json",
|
||||
status: 404
|
||||
)
|
||||
end
|
||||
|
||||
def storage_project
|
||||
@storage_project ||= begin
|
||||
result = project
|
||||
|
||||
loop do
|
||||
break unless result.forked?
|
||||
result = result.forked_from_project
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,12 +6,6 @@ module MembersHelper
|
|||
"#{action}_#{member.type.underscore}".to_sym
|
||||
end
|
||||
|
||||
def default_show_roles(member)
|
||||
can?(current_user, action_member_permission(:update, member), member) ||
|
||||
can?(current_user, action_member_permission(:destroy, member), member) ||
|
||||
can?(current_user, action_member_permission(:admin, member), member.source)
|
||||
end
|
||||
|
||||
def remove_member_message(member, user: nil)
|
||||
user = current_user if defined?(current_user)
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ module SearchHelper
|
|||
Sanitize.clean(str)
|
||||
end
|
||||
|
||||
def search_filter_path(options={})
|
||||
def search_filter_path(options = {})
|
||||
exist_opts = {
|
||||
search: params[:search],
|
||||
project_id: params[:project_id],
|
||||
|
|
|
@ -4,23 +4,11 @@ module TreeHelper
|
|||
#
|
||||
# contents - A Grit::Tree object for the current tree
|
||||
def render_tree(tree)
|
||||
# Render Folders before Files/Submodules
|
||||
# Sort submodules and folders together by name ahead of files
|
||||
folders, files, submodules = tree.trees, tree.blobs, tree.submodules
|
||||
|
||||
tree = ""
|
||||
|
||||
# Render folders if we have any
|
||||
tree << render(partial: 'projects/tree/tree_item', collection: folders,
|
||||
locals: { type: 'folder' }) if folders.present?
|
||||
|
||||
# Render files if we have any
|
||||
tree << render(partial: 'projects/tree/blob_item', collection: files,
|
||||
locals: { type: 'file' }) if files.present?
|
||||
|
||||
# Render submodules if we have any
|
||||
tree << render(partial: 'projects/tree/submodule_item',
|
||||
collection: submodules) if submodules.present?
|
||||
|
||||
items = (folders + submodules).sort_by(&:name) + files
|
||||
tree << render(partial: "projects/tree/tree_row", collection: items) if items.present?
|
||||
tree.html_safe
|
||||
end
|
||||
|
||||
|
|
|
@ -6,6 +6,10 @@ class Ability
|
|||
return [] unless user.is_a?(User)
|
||||
return [] if user.blocked?
|
||||
|
||||
abilities_by_subject_class(user: user, subject: subject)
|
||||
end
|
||||
|
||||
def abilities_by_subject_class(user:, subject:)
|
||||
case subject
|
||||
when CommitStatus then commit_status_abilities(user, subject)
|
||||
when Project then project_abilities(user, subject)
|
||||
|
|
|
@ -16,7 +16,7 @@ module Ci
|
|||
scope :with_artifacts_not_expired, ->() { with_artifacts.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
|
||||
scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
|
||||
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
|
||||
scope :manual_actions, ->() { where(when: :manual) }
|
||||
scope :manual_actions, ->() { where(when: :manual).relevant }
|
||||
|
||||
mount_uploader :artifacts_file, ArtifactUploader
|
||||
mount_uploader :artifacts_metadata, ArtifactUploader
|
||||
|
@ -42,40 +42,35 @@ module Ci
|
|||
end
|
||||
|
||||
def retry(build, user = nil)
|
||||
new_build = Ci::Build.new(status: 'pending')
|
||||
new_build.ref = build.ref
|
||||
new_build.tag = build.tag
|
||||
new_build.options = build.options
|
||||
new_build.commands = build.commands
|
||||
new_build.tag_list = build.tag_list
|
||||
new_build.project = build.project
|
||||
new_build.pipeline = build.pipeline
|
||||
new_build.name = build.name
|
||||
new_build.allow_failure = build.allow_failure
|
||||
new_build.stage = build.stage
|
||||
new_build.stage_idx = build.stage_idx
|
||||
new_build.trigger_request = build.trigger_request
|
||||
new_build.yaml_variables = build.yaml_variables
|
||||
new_build.when = build.when
|
||||
new_build.user = user
|
||||
new_build.environment = build.environment
|
||||
new_build.save
|
||||
new_build = Ci::Build.create(
|
||||
ref: build.ref,
|
||||
tag: build.tag,
|
||||
options: build.options,
|
||||
commands: build.commands,
|
||||
tag_list: build.tag_list,
|
||||
project: build.project,
|
||||
pipeline: build.pipeline,
|
||||
name: build.name,
|
||||
allow_failure: build.allow_failure,
|
||||
stage: build.stage,
|
||||
stage_idx: build.stage_idx,
|
||||
trigger_request: build.trigger_request,
|
||||
yaml_variables: build.yaml_variables,
|
||||
when: build.when,
|
||||
user: user,
|
||||
environment: build.environment,
|
||||
status_event: 'enqueue'
|
||||
)
|
||||
MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
|
||||
new_build
|
||||
end
|
||||
end
|
||||
|
||||
state_machine :status, initial: :pending do
|
||||
state_machine :status do
|
||||
after_transition pending: :running do |build|
|
||||
build.execute_hooks
|
||||
end
|
||||
|
||||
# We use around_transition to create builds for next stage as soon as possible, before the `after_*` is executed
|
||||
around_transition any => [:success, :failed, :canceled] do |build, block|
|
||||
block.call
|
||||
build.pipeline.create_next_builds(build) if build.pipeline
|
||||
end
|
||||
|
||||
after_transition any => [:success, :failed, :canceled] do |build|
|
||||
build.update_coverage
|
||||
build.execute_hooks
|
||||
|
@ -107,7 +102,7 @@ module Ci
|
|||
|
||||
def play(current_user = nil)
|
||||
# Try to queue a current build
|
||||
if self.queue
|
||||
if self.enqueue
|
||||
self.update(user: current_user)
|
||||
self
|
||||
else
|
||||
|
@ -461,7 +456,7 @@ module Ci
|
|||
|
||||
def build_attributes_from_config
|
||||
return {} unless pipeline.config_processor
|
||||
|
||||
|
||||
pipeline.config_processor.build_attributes(name)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,13 +13,51 @@ module Ci
|
|||
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id
|
||||
|
||||
validates_presence_of :sha
|
||||
validates_presence_of :ref
|
||||
validates_presence_of :status
|
||||
validate :valid_commit_sha
|
||||
|
||||
# Invalidate object and save if when touched
|
||||
after_touch :update_state
|
||||
after_save :keep_around_commits
|
||||
|
||||
state_machine :status, initial: :created do
|
||||
event :enqueue do
|
||||
transition created: :pending
|
||||
transition [:success, :failed, :canceled, :skipped] => :running
|
||||
end
|
||||
|
||||
event :run do
|
||||
transition any => :running
|
||||
end
|
||||
|
||||
event :skip do
|
||||
transition any => :skipped
|
||||
end
|
||||
|
||||
event :drop do
|
||||
transition any => :failed
|
||||
end
|
||||
|
||||
event :succeed do
|
||||
transition any => :success
|
||||
end
|
||||
|
||||
event :cancel do
|
||||
transition any => :canceled
|
||||
end
|
||||
|
||||
before_transition [:created, :pending] => :running do |pipeline|
|
||||
pipeline.started_at = Time.now
|
||||
end
|
||||
|
||||
before_transition any => [:success, :failed, :canceled] do |pipeline|
|
||||
pipeline.finished_at = Time.now
|
||||
end
|
||||
|
||||
before_transition do |pipeline|
|
||||
pipeline.update_duration
|
||||
end
|
||||
end
|
||||
|
||||
# ref can't be HEAD or SHA, can only be branch/tag name
|
||||
scope :latest_successful_for, ->(ref = default_branch) do
|
||||
where(ref: ref).success.order(id: :desc).limit(1)
|
||||
|
@ -109,37 +147,6 @@ module Ci
|
|||
trigger_requests.any?
|
||||
end
|
||||
|
||||
def create_builds(user, trigger_request = nil)
|
||||
##
|
||||
# We persist pipeline only if there are builds available
|
||||
#
|
||||
return unless config_processor
|
||||
|
||||
build_builds_for_stages(config_processor.stages, user,
|
||||
'success', trigger_request) && save
|
||||
end
|
||||
|
||||
def create_next_builds(build)
|
||||
return unless config_processor
|
||||
|
||||
# don't create other builds if this one is retried
|
||||
latest_builds = builds.latest
|
||||
return unless latest_builds.exists?(build.id)
|
||||
|
||||
# get list of stages after this build
|
||||
next_stages = config_processor.stages.drop_while { |stage| stage != build.stage }
|
||||
next_stages.delete(build.stage)
|
||||
|
||||
# get status for all prior builds
|
||||
prior_builds = latest_builds.where.not(stage: next_stages)
|
||||
prior_status = prior_builds.status
|
||||
|
||||
# build builds for next stage that has builds available
|
||||
# and save pipeline if we have builds
|
||||
build_builds_for_stages(next_stages, build.user, prior_status,
|
||||
build.trigger_request) && save
|
||||
end
|
||||
|
||||
def retried
|
||||
@retried ||= (statuses.order(id: :desc) - statuses.latest)
|
||||
end
|
||||
|
@ -151,6 +158,14 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
def config_builds_attributes
|
||||
return [] unless config_processor
|
||||
|
||||
config_processor.
|
||||
builds_for_ref(ref, tag?, trigger_requests.first).
|
||||
sort_by { |build| build[:stage_idx] }
|
||||
end
|
||||
|
||||
def has_warnings?
|
||||
builds.latest.ignored.any?
|
||||
end
|
||||
|
@ -182,10 +197,6 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
def skip_ci?
|
||||
git_commit_message =~ /\[(ci skip|skip ci)\]/i if git_commit_message
|
||||
end
|
||||
|
||||
def environments
|
||||
builds.where.not(environment: nil).success.pluck(:environment).uniq
|
||||
end
|
||||
|
@ -207,37 +218,37 @@ module Ci
|
|||
Note.for_commit_id(sha)
|
||||
end
|
||||
|
||||
def process!
|
||||
Ci::ProcessPipelineService.new(project, user).execute(self)
|
||||
end
|
||||
|
||||
def build_updated
|
||||
case latest_builds_status
|
||||
when 'pending' then enqueue
|
||||
when 'running' then run
|
||||
when 'success' then succeed
|
||||
when 'failed' then drop
|
||||
when 'canceled' then cancel
|
||||
when 'skipped' then skip
|
||||
end
|
||||
end
|
||||
|
||||
def predefined_variables
|
||||
[
|
||||
{ key: 'CI_PIPELINE_ID', value: id.to_s, public: true }
|
||||
]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_builds_for_stages(stages, user, status, trigger_request)
|
||||
##
|
||||
# Note that `Array#any?` implements a short circuit evaluation, so we
|
||||
# build builds only for the first stage that has builds available.
|
||||
#
|
||||
stages.any? do |stage|
|
||||
CreateBuildsService.new(self).
|
||||
execute(stage, user, status, trigger_request).
|
||||
any?(&:active?)
|
||||
end
|
||||
def update_duration
|
||||
self.duration = statuses.latest.duration
|
||||
end
|
||||
|
||||
def update_state
|
||||
statuses.reload
|
||||
self.status = if yaml_errors.blank?
|
||||
statuses.latest.status || 'skipped'
|
||||
else
|
||||
'failed'
|
||||
end
|
||||
self.started_at = statuses.started_at
|
||||
self.finished_at = statuses.finished_at
|
||||
self.duration = statuses.latest.duration
|
||||
save
|
||||
private
|
||||
|
||||
def latest_builds_status
|
||||
return 'failed' unless yaml_errors.blank?
|
||||
|
||||
statuses.latest.status || 'skipped'
|
||||
end
|
||||
|
||||
def keep_around_commits
|
||||
|
|
|
@ -5,7 +5,7 @@ class CommitStatus < ActiveRecord::Base
|
|||
self.table_name = 'ci_builds'
|
||||
|
||||
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
|
||||
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id, touch: true
|
||||
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id
|
||||
belongs_to :user
|
||||
|
||||
delegate :commit, to: :pipeline
|
||||
|
@ -25,28 +25,36 @@ class CommitStatus < ActiveRecord::Base
|
|||
scope :ordered, -> { order(:name) }
|
||||
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
|
||||
|
||||
state_machine :status, initial: :pending do
|
||||
event :queue do
|
||||
transition skipped: :pending
|
||||
state_machine :status do
|
||||
event :enqueue do
|
||||
transition [:created, :skipped] => :pending
|
||||
end
|
||||
|
||||
event :run do
|
||||
transition pending: :running
|
||||
end
|
||||
|
||||
event :skip do
|
||||
transition [:created, :pending] => :skipped
|
||||
end
|
||||
|
||||
event :drop do
|
||||
transition [:pending, :running] => :failed
|
||||
transition [:created, :pending, :running] => :failed
|
||||
end
|
||||
|
||||
event :success do
|
||||
transition [:pending, :running] => :success
|
||||
transition [:created, :pending, :running] => :success
|
||||
end
|
||||
|
||||
event :cancel do
|
||||
transition [:pending, :running] => :canceled
|
||||
transition [:created, :pending, :running] => :canceled
|
||||
end
|
||||
|
||||
after_transition pending: :running do |commit_status|
|
||||
after_transition created: [:pending, :running] do |commit_status|
|
||||
commit_status.update_attributes queued_at: Time.now
|
||||
end
|
||||
|
||||
after_transition [:created, :pending] => :running do |commit_status|
|
||||
commit_status.update_attributes started_at: Time.now
|
||||
end
|
||||
|
||||
|
@ -54,7 +62,18 @@ class CommitStatus < ActiveRecord::Base
|
|||
commit_status.update_attributes finished_at: Time.now
|
||||
end
|
||||
|
||||
after_transition [:pending, :running] => :success do |commit_status|
|
||||
# We use around_transition to process pipeline on next stages as soon as possible, before the `after_*` is executed
|
||||
around_transition any => [:success, :failed, :canceled] do |commit_status, block|
|
||||
block.call
|
||||
|
||||
commit_status.pipeline.try(:process!)
|
||||
end
|
||||
|
||||
after_transition do |commit_status, transition|
|
||||
commit_status.pipeline.try(:build_updated) unless transition.loopback?
|
||||
end
|
||||
|
||||
after_transition [:created, :pending, :running] => :success do |commit_status|
|
||||
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status)
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
module FasterCacheKeys
|
||||
# A faster version of Rails' "cache_key" method.
|
||||
#
|
||||
# Rails' default "cache_key" method uses all kind of complex logic to figure
|
||||
# out the cache key. In many cases this complexity and overhead may not be
|
||||
# needed.
|
||||
#
|
||||
# This method does not do any timestamp parsing as this process is quite
|
||||
# expensive and not needed when generating cache keys. This method also relies
|
||||
# on the table name instead of the cache namespace name as the latter uses
|
||||
# complex logic to generate the exact same value (as when using the table
|
||||
# name) in 99% of the cases.
|
||||
def cache_key
|
||||
"#{self.class.table_name}/#{id}-#{read_attribute_before_type_cast(:updated_at)}"
|
||||
end
|
||||
end
|
|
@ -1,18 +1,22 @@
|
|||
module Statuseable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
AVAILABLE_STATUSES = %w(pending running success failed canceled skipped)
|
||||
AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped]
|
||||
STARTED_STATUSES = %w[running success failed skipped]
|
||||
ACTIVE_STATUSES = %w[pending running]
|
||||
COMPLETED_STATUSES = %w[success failed canceled]
|
||||
|
||||
class_methods do
|
||||
def status_sql
|
||||
builds = all.select('count(*)').to_sql
|
||||
success = all.success.select('count(*)').to_sql
|
||||
ignored = all.ignored.select('count(*)').to_sql if all.respond_to?(:ignored)
|
||||
scope = all.relevant
|
||||
builds = scope.select('count(*)').to_sql
|
||||
success = scope.success.select('count(*)').to_sql
|
||||
ignored = scope.ignored.select('count(*)').to_sql if scope.respond_to?(:ignored)
|
||||
ignored ||= '0'
|
||||
pending = all.pending.select('count(*)').to_sql
|
||||
running = all.running.select('count(*)').to_sql
|
||||
canceled = all.canceled.select('count(*)').to_sql
|
||||
skipped = all.skipped.select('count(*)').to_sql
|
||||
pending = scope.pending.select('count(*)').to_sql
|
||||
running = scope.running.select('count(*)').to_sql
|
||||
canceled = scope.canceled.select('count(*)').to_sql
|
||||
skipped = scope.skipped.select('count(*)').to_sql
|
||||
|
||||
deduce_status = "(CASE
|
||||
WHEN (#{builds})=0 THEN NULL
|
||||
|
@ -48,7 +52,8 @@ module Statuseable
|
|||
included do
|
||||
validates :status, inclusion: { in: AVAILABLE_STATUSES }
|
||||
|
||||
state_machine :status, initial: :pending do
|
||||
state_machine :status, initial: :created do
|
||||
state :created, value: 'created'
|
||||
state :pending, value: 'pending'
|
||||
state :running, value: 'running'
|
||||
state :failed, value: 'failed'
|
||||
|
@ -57,6 +62,8 @@ module Statuseable
|
|||
state :skipped, value: 'skipped'
|
||||
end
|
||||
|
||||
scope :created, -> { where(status: 'created') }
|
||||
scope :relevant, -> { where.not(status: 'created') }
|
||||
scope :running, -> { where(status: 'running') }
|
||||
scope :pending, -> { where(status: 'pending') }
|
||||
scope :success, -> { where(status: 'success') }
|
||||
|
@ -68,14 +75,14 @@ module Statuseable
|
|||
end
|
||||
|
||||
def started?
|
||||
!pending? && !canceled? && started_at
|
||||
STARTED_STATUSES.include?(status) && started_at
|
||||
end
|
||||
|
||||
def active?
|
||||
running? || pending?
|
||||
ACTIVE_STATUSES.include?(status)
|
||||
end
|
||||
|
||||
def complete?
|
||||
canceled? || success? || failed?
|
||||
COMPLETED_STATUSES.include?(status)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,6 +7,7 @@ class Issue < ActiveRecord::Base
|
|||
include Sortable
|
||||
include Taskable
|
||||
include Spammable
|
||||
include FasterCacheKeys
|
||||
|
||||
DueDateStruct = Struct.new(:title, :name).freeze
|
||||
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
|
||||
|
|
|
@ -8,6 +8,7 @@ class ProjectMember < Member
|
|||
# Make sure project member points only to project as it source
|
||||
default_value_for :source_type, SOURCE_TYPE
|
||||
validates_format_of :source_type, with: /\AProject\z/
|
||||
validates :access_level, inclusion: { in: Gitlab::Access.values }
|
||||
default_scope { where(source_type: SOURCE_TYPE) }
|
||||
|
||||
scope :in_project, ->(project) { where(source_id: project.id) }
|
||||
|
@ -21,19 +22,19 @@ class ProjectMember < Member
|
|||
# or symbol like :master representing role
|
||||
#
|
||||
# Ex.
|
||||
# add_users_into_projects(
|
||||
# add_users_to_projects(
|
||||
# project_ids,
|
||||
# user_ids,
|
||||
# ProjectMember::MASTER
|
||||
# )
|
||||
#
|
||||
# add_users_into_projects(
|
||||
# add_users_to_projects(
|
||||
# project_ids,
|
||||
# user_ids,
|
||||
# :master
|
||||
# )
|
||||
#
|
||||
def add_users_into_projects(project_ids, user_ids, access, current_user = nil)
|
||||
def add_users_to_projects(project_ids, user_ids, access, current_user = nil)
|
||||
access_level = if roles_hash.has_key?(access)
|
||||
roles_hash[access]
|
||||
elsif roles_hash.values.include?(access.to_i)
|
||||
|
|
|
@ -104,6 +104,7 @@ class MergeRequest < ActiveRecord::Base
|
|||
scope :from_project, ->(project) { where(source_project_id: project.id) }
|
||||
scope :merged, -> { with_state(:merged) }
|
||||
scope :closed_and_merged, -> { with_states(:closed, :merged) }
|
||||
scope :from_source_branches, ->(branches) { where(source_branch: branches) }
|
||||
|
||||
scope :join_project, -> { joins(:target_project) }
|
||||
scope :references_project, -> { references(:target_project) }
|
||||
|
|
|
@ -36,7 +36,7 @@ class MergeRequestDiff < ActiveRecord::Base
|
|||
real_size.presence || raw_diffs.size
|
||||
end
|
||||
|
||||
def raw_diffs(options={})
|
||||
def raw_diffs(options = {})
|
||||
if options[:ignore_whitespace_change]
|
||||
@raw_diffs_no_whitespace ||= begin
|
||||
compare = Gitlab::Git::Compare.new(
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class Namespace < ActiveRecord::Base
|
||||
acts_as_paranoid
|
||||
|
||||
include Sortable
|
||||
include Gitlab::ShellAdapter
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ class Note < ActiveRecord::Base
|
|||
include Mentionable
|
||||
include Awardable
|
||||
include Importable
|
||||
include FasterCacheKeys
|
||||
|
||||
# Attribute containing rendered and redacted Markdown as generated by
|
||||
# Banzai::ObjectRenderer.
|
||||
|
|
|
@ -379,9 +379,10 @@ class Project < ActiveRecord::Base
|
|||
joins(join_body).reorder('join_note_counts.amount DESC')
|
||||
end
|
||||
|
||||
# Deletes gitlab project export files older than 24 hours
|
||||
def remove_gitlab_exports!
|
||||
Gitlab::Popen.popen(%W(find #{Gitlab::ImportExport.storage_path} -not -path #{Gitlab::ImportExport.storage_path} -mmin +1440 -delete))
|
||||
def cached_count
|
||||
Rails.cache.fetch('total_project_count', expires_in: 5.minutes) do
|
||||
Project.count
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -870,10 +871,16 @@ class Project < ActiveRecord::Base
|
|||
|
||||
# Check if current branch name is marked as protected in the system
|
||||
def protected_branch?(branch_name)
|
||||
return true if empty_repo? && default_branch_protected?
|
||||
|
||||
@protected_branches ||= self.protected_branches.to_a
|
||||
ProtectedBranch.matching(branch_name, protected_branches: @protected_branches).present?
|
||||
end
|
||||
|
||||
def user_can_push_to_empty_repo?(user)
|
||||
!default_branch_protected? || team.max_member_access(user.id) > Gitlab::Access::DEVELOPER
|
||||
end
|
||||
|
||||
def forked?
|
||||
!(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
|
||||
end
|
||||
|
@ -992,6 +999,10 @@ class Project < ActiveRecord::Base
|
|||
project_members.find_by(user_id: user)
|
||||
end
|
||||
|
||||
def add_user(user, access_level, current_user = nil)
|
||||
team.add_user(user, access_level, current_user)
|
||||
end
|
||||
|
||||
def default_branch
|
||||
@default_branch ||= repository.root_ref if repository.exists?
|
||||
end
|
||||
|
@ -1154,16 +1165,6 @@ class Project < ActiveRecord::Base
|
|||
@wiki ||= ProjectWiki.new(self, self.owner)
|
||||
end
|
||||
|
||||
def schedule_delete!(user_id, params)
|
||||
# Queue this task for after the commit, so once we mark pending_delete it will run
|
||||
run_after_commit do
|
||||
job_id = ProjectDestroyWorker.perform_async(id, user_id, params)
|
||||
Rails.logger.info("User #{user_id} scheduled destruction of project #{path_with_namespace} with job ID #{job_id}")
|
||||
end
|
||||
|
||||
update_attribute(:pending_delete, true)
|
||||
end
|
||||
|
||||
def running_or_pending_build_count(force: false)
|
||||
Rails.cache.fetch(['projects', id, 'running_or_pending_build_count'], force: force) do
|
||||
builds.running_or_pending.count(:all)
|
||||
|
@ -1265,6 +1266,11 @@ class Project < ActiveRecord::Base
|
|||
|
||||
private
|
||||
|
||||
def default_branch_protected?
|
||||
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL ||
|
||||
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE
|
||||
end
|
||||
|
||||
def authorized_for_user_by_group?(user, min_access_level)
|
||||
member = user.group_members.find_by(source_id: group)
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class CampfireService < Service
|
||||
include HTTParty
|
||||
|
||||
prop_accessor :token, :subdomain, :room
|
||||
validates :token, presence: true, if: :activated?
|
||||
|
||||
|
@ -29,18 +31,53 @@ class CampfireService < Service
|
|||
def execute(data)
|
||||
return unless supported_events.include?(data[:object_kind])
|
||||
|
||||
room = gate.find_room_by_name(self.room)
|
||||
return true unless room
|
||||
|
||||
self.class.base_uri base_uri
|
||||
message = build_message(data)
|
||||
|
||||
room.speak(message)
|
||||
speak(self.room, message, auth)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def gate
|
||||
@gate ||= Tinder::Campfire.new(subdomain, token: token)
|
||||
def base_uri
|
||||
@base_uri ||= "https://#{subdomain}.campfirenow.com"
|
||||
end
|
||||
|
||||
def auth
|
||||
# use a dummy password, as explained in the Campfire API doc:
|
||||
# https://github.com/basecamp/campfire-api#authentication
|
||||
@auth ||= {
|
||||
basic_auth: {
|
||||
username: token,
|
||||
password: 'X'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
# Post a message into a room, returns the message Hash in case of success.
|
||||
# Returns nil otherwise.
|
||||
# https://github.com/basecamp/campfire-api/blob/master/sections/messages.md#create-message
|
||||
def speak(room_name, message, auth)
|
||||
room = rooms(auth).find { |r| r["name"] == room_name }
|
||||
return nil unless room
|
||||
|
||||
path = "/room/#{room["id"]}/speak.json"
|
||||
body = {
|
||||
body: {
|
||||
message: {
|
||||
type: 'TextMessage',
|
||||
body: message
|
||||
}
|
||||
}
|
||||
}
|
||||
res = self.class.post(path, auth.merge(body))
|
||||
res.code == 201 ? res : nil
|
||||
end
|
||||
|
||||
# Returns a list of rooms, or [].
|
||||
# https://github.com/basecamp/campfire-api/blob/master/sections/rooms.md#get-rooms
|
||||
def rooms(auth)
|
||||
res = self.class.get("/rooms.json", auth)
|
||||
res.code == 200 ? res["rooms"] : []
|
||||
end
|
||||
|
||||
def build_message(push)
|
||||
|
|
|
@ -34,7 +34,7 @@ class ProjectTeam
|
|||
end
|
||||
|
||||
def add_users(users, access, current_user = nil)
|
||||
ProjectMember.add_users_into_projects(
|
||||
ProjectMember.add_users_to_projects(
|
||||
[project.id],
|
||||
users,
|
||||
access,
|
||||
|
|
|
@ -601,7 +601,7 @@ class Repository
|
|||
commit(sha)
|
||||
end
|
||||
|
||||
def next_branch(name, opts={})
|
||||
def next_branch(name, opts = {})
|
||||
branch_ids = self.branch_names.map do |n|
|
||||
next 1 if n == name
|
||||
result = n.match(/\A#{name}-([0-9]+)\z/)
|
||||
|
@ -636,9 +636,7 @@ class Repository
|
|||
def tags_sorted_by(value)
|
||||
case value
|
||||
when 'name'
|
||||
# Would be better to use `sort_by` but `version_sorter` only exposes
|
||||
# `sort` and `rsort`
|
||||
VersionSorter.rsort(tag_names).map { |tag_name| find_tag(tag_name) }
|
||||
VersionSorter.rsort(tags) { |tag| tag.name }
|
||||
when 'updated_desc'
|
||||
tags_sorted_by_committed_date.reverse
|
||||
when 'updated_asc'
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
class SpamReport < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
|
||||
validates :user, presence: true
|
||||
end
|
|
@ -23,13 +23,13 @@ class User < ActiveRecord::Base
|
|||
default_value_for :theme_id, gitlab_config.default_theme
|
||||
|
||||
attr_encrypted :otp_secret,
|
||||
key: Gitlab::Application.config.secret_key_base,
|
||||
key: Gitlab::Application.secrets.otp_key_base,
|
||||
mode: :per_attribute_iv_and_salt,
|
||||
insecure_mode: true,
|
||||
algorithm: 'aes-256-cbc'
|
||||
|
||||
devise :two_factor_authenticatable,
|
||||
otp_secret_encryption_key: Gitlab::Application.config.secret_key_base
|
||||
otp_secret_encryption_key: Gitlab::Application.secrets.otp_key_base
|
||||
|
||||
devise :two_factor_backupable, otp_number_of_backup_codes: 10
|
||||
serialize :otp_backup_codes, JSON
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
module Ci
|
||||
class CreateBuildsService
|
||||
def initialize(pipeline)
|
||||
@pipeline = pipeline
|
||||
@config = pipeline.config_processor
|
||||
end
|
||||
|
||||
def execute(stage, user, status, trigger_request = nil)
|
||||
builds_attrs = @config.builds_for_stage_and_ref(stage, @pipeline.ref, @pipeline.tag, trigger_request)
|
||||
|
||||
# check when to create next build
|
||||
builds_attrs = builds_attrs.select do |build_attrs|
|
||||
case build_attrs[:when]
|
||||
when 'on_success'
|
||||
status == 'success'
|
||||
when 'on_failure'
|
||||
status == 'failed'
|
||||
when 'always', 'manual'
|
||||
%w(success failed).include?(status)
|
||||
end
|
||||
end
|
||||
|
||||
# don't create the same build twice
|
||||
builds_attrs.reject! do |build_attrs|
|
||||
@pipeline.builds.find_by(ref: @pipeline.ref,
|
||||
tag: @pipeline.tag,
|
||||
trigger_request: trigger_request,
|
||||
name: build_attrs[:name])
|
||||
end
|
||||
|
||||
builds_attrs.map do |build_attrs|
|
||||
build_attrs.slice!(:name,
|
||||
:commands,
|
||||
:tag_list,
|
||||
:options,
|
||||
:allow_failure,
|
||||
:stage,
|
||||
:stage_idx,
|
||||
:environment,
|
||||
:when,
|
||||
:yaml_variables)
|
||||
|
||||
build_attrs.merge!(pipeline: @pipeline,
|
||||
ref: @pipeline.ref,
|
||||
tag: @pipeline.tag,
|
||||
trigger_request: trigger_request,
|
||||
user: user,
|
||||
project: @pipeline.project)
|
||||
|
||||
# TODO: The proper implementation for this is in
|
||||
# https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5295
|
||||
build_attrs[:status] = 'skipped' if build_attrs[:when] == 'manual'
|
||||
|
||||
##
|
||||
# We do not persist new builds here.
|
||||
# Those will be persisted when @pipeline is saved.
|
||||
#
|
||||
@pipeline.builds.new(build_attrs)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,42 @@
|
|||
module Ci
|
||||
class CreatePipelineBuildsService < BaseService
|
||||
attr_reader :pipeline
|
||||
|
||||
def execute(pipeline)
|
||||
@pipeline = pipeline
|
||||
|
||||
new_builds.map do |build_attributes|
|
||||
create_build(build_attributes)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_build(build_attributes)
|
||||
build_attributes = build_attributes.merge(
|
||||
pipeline: pipeline,
|
||||
project: pipeline.project,
|
||||
ref: pipeline.ref,
|
||||
tag: pipeline.tag,
|
||||
user: current_user,
|
||||
trigger_request: trigger_request
|
||||
)
|
||||
pipeline.builds.create(build_attributes)
|
||||
end
|
||||
|
||||
def new_builds
|
||||
@new_builds ||= pipeline.config_builds_attributes.
|
||||
reject { |build| existing_build_names.include?(build[:name]) }
|
||||
end
|
||||
|
||||
def existing_build_names
|
||||
@existing_build_names ||= pipeline.builds.pluck(:name)
|
||||
end
|
||||
|
||||
def trigger_request
|
||||
return @trigger_request if defined?(@trigger_request)
|
||||
|
||||
@trigger_request ||= pipeline.trigger_requests.first
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,49 +1,101 @@
|
|||
module Ci
|
||||
class CreatePipelineService < BaseService
|
||||
def execute
|
||||
pipeline = project.pipelines.new(params)
|
||||
pipeline.user = current_user
|
||||
attr_reader :pipeline
|
||||
|
||||
unless ref_names.include?(params[:ref])
|
||||
pipeline.errors.add(:base, 'Reference not found')
|
||||
return pipeline
|
||||
def execute(ignore_skip_ci: false, save_on_errors: true, trigger_request: nil)
|
||||
@pipeline = Ci::Pipeline.new(
|
||||
project: project,
|
||||
ref: ref,
|
||||
sha: sha,
|
||||
before_sha: before_sha,
|
||||
tag: tag?,
|
||||
trigger_requests: Array(trigger_request),
|
||||
user: current_user
|
||||
)
|
||||
|
||||
unless project.builds_enabled?
|
||||
return error('Pipeline is disabled')
|
||||
end
|
||||
|
||||
if commit
|
||||
pipeline.sha = commit.id
|
||||
else
|
||||
pipeline.errors.add(:base, 'Commit not found')
|
||||
return pipeline
|
||||
unless trigger_request || can?(current_user, :create_pipeline, project)
|
||||
return error('Insufficient permissions to create a new pipeline')
|
||||
end
|
||||
|
||||
unless can?(current_user, :create_pipeline, project)
|
||||
pipeline.errors.add(:base, 'Insufficient permissions to create a new pipeline')
|
||||
return pipeline
|
||||
unless branch? || tag?
|
||||
return error('Reference not found')
|
||||
end
|
||||
|
||||
unless commit
|
||||
return error('Commit not found')
|
||||
end
|
||||
|
||||
unless pipeline.config_processor
|
||||
pipeline.errors.add(:base, pipeline.yaml_errors || 'Missing .gitlab-ci.yml file')
|
||||
unless pipeline.ci_yaml_file
|
||||
return error('Missing .gitlab-ci.yml file')
|
||||
end
|
||||
return error(pipeline.yaml_errors, save: save_on_errors)
|
||||
end
|
||||
|
||||
if !ignore_skip_ci && skip_ci?
|
||||
pipeline.skip if save_on_errors
|
||||
return pipeline
|
||||
end
|
||||
|
||||
pipeline.save!
|
||||
|
||||
unless pipeline.create_builds(current_user)
|
||||
pipeline.errors.add(:base, 'No builds for this pipeline.')
|
||||
unless pipeline.config_builds_attributes.present?
|
||||
return error('No builds for this pipeline.')
|
||||
end
|
||||
|
||||
pipeline.save
|
||||
pipeline.process!
|
||||
pipeline
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ref_names
|
||||
@ref_names ||= project.repository.ref_names
|
||||
def skip_ci?
|
||||
pipeline.git_commit_message =~ /\[(ci skip|skip ci)\]/i if pipeline.git_commit_message
|
||||
end
|
||||
|
||||
def commit
|
||||
@commit ||= project.commit(params[:ref])
|
||||
@commit ||= project.commit(origin_sha || origin_ref)
|
||||
end
|
||||
|
||||
def sha
|
||||
commit.try(:id)
|
||||
end
|
||||
|
||||
def before_sha
|
||||
params[:checkout_sha] || params[:before] || Gitlab::Git::BLANK_SHA
|
||||
end
|
||||
|
||||
def origin_sha
|
||||
params[:checkout_sha] || params[:after]
|
||||
end
|
||||
|
||||
def origin_ref
|
||||
params[:ref]
|
||||
end
|
||||
|
||||
def branch?
|
||||
project.repository.ref_exists?(Gitlab::Git::BRANCH_REF_PREFIX + ref)
|
||||
end
|
||||
|
||||
def tag?
|
||||
project.repository.ref_exists?(Gitlab::Git::TAG_REF_PREFIX + ref)
|
||||
end
|
||||
|
||||
def ref
|
||||
Gitlab::Git.ref_name(origin_ref)
|
||||
end
|
||||
|
||||
def valid_sha?
|
||||
origin_sha && origin_sha != Gitlab::Git::BLANK_SHA
|
||||
end
|
||||
|
||||
def error(message, save: false)
|
||||
pipeline.errors.add(:base, message)
|
||||
pipeline.drop if save
|
||||
pipeline
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,20 +1,11 @@
|
|||
module Ci
|
||||
class CreateTriggerRequestService
|
||||
def execute(project, trigger, ref, variables = nil)
|
||||
commit = project.commit(ref)
|
||||
return unless commit
|
||||
trigger_request = trigger.trigger_requests.create(variables: variables)
|
||||
|
||||
# check if ref is tag
|
||||
tag = project.repository.find_tag(ref).present?
|
||||
|
||||
pipeline = project.pipelines.create(sha: commit.sha, ref: ref, tag: tag)
|
||||
|
||||
trigger_request = trigger.trigger_requests.create!(
|
||||
variables: variables,
|
||||
pipeline: pipeline,
|
||||
)
|
||||
|
||||
if pipeline.create_builds(nil, trigger_request)
|
||||
pipeline = Ci::CreatePipelineService.new(project, nil, ref: ref).
|
||||
execute(ignore_skip_ci: true, trigger_request: trigger_request)
|
||||
if pipeline.persisted?
|
||||
trigger_request
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
module Ci
|
||||
class ProcessPipelineService < BaseService
|
||||
attr_reader :pipeline
|
||||
|
||||
def execute(pipeline)
|
||||
@pipeline = pipeline
|
||||
|
||||
# This method will ensure that our pipeline does have all builds for all stages created
|
||||
if created_builds.empty?
|
||||
create_builds!
|
||||
end
|
||||
|
||||
new_builds =
|
||||
stage_indexes_of_created_builds.map do |index|
|
||||
process_stage(index)
|
||||
end
|
||||
|
||||
# Return a flag if a when builds got enqueued
|
||||
new_builds.flatten.any?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_builds!
|
||||
Ci::CreatePipelineBuildsService.new(project, current_user).execute(pipeline)
|
||||
end
|
||||
|
||||
def process_stage(index)
|
||||
current_status = status_for_prior_stages(index)
|
||||
|
||||
created_builds_in_stage(index).select do |build|
|
||||
process_build(build, current_status)
|
||||
end
|
||||
end
|
||||
|
||||
def process_build(build, current_status)
|
||||
return false unless Statuseable::COMPLETED_STATUSES.include?(current_status)
|
||||
|
||||
if valid_statuses_for_when(build.when).include?(current_status)
|
||||
build.enqueue
|
||||
true
|
||||
else
|
||||
build.skip
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def valid_statuses_for_when(value)
|
||||
case value
|
||||
when 'on_success'
|
||||
%w[success]
|
||||
when 'on_failure'
|
||||
%w[failed]
|
||||
when 'always'
|
||||
%w[success failed]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def status_for_prior_stages(index)
|
||||
pipeline.builds.where('stage_idx < ?', index).latest.status || 'success'
|
||||
end
|
||||
|
||||
def stage_indexes_of_created_builds
|
||||
created_builds.order(:stage_idx).pluck('distinct stage_idx')
|
||||
end
|
||||
|
||||
def created_builds_in_stage(index)
|
||||
created_builds.where(stage_idx: index)
|
||||
end
|
||||
|
||||
def created_builds
|
||||
pipeline.builds.created
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,69 +0,0 @@
|
|||
class CreateCommitBuildsService
|
||||
def execute(project, user, params)
|
||||
return unless project.builds_enabled?
|
||||
|
||||
before_sha = params[:checkout_sha] || params[:before]
|
||||
sha = params[:checkout_sha] || params[:after]
|
||||
origin_ref = params[:ref]
|
||||
|
||||
ref = Gitlab::Git.ref_name(origin_ref)
|
||||
tag = Gitlab::Git.tag_ref?(origin_ref)
|
||||
|
||||
# Skip branch removal
|
||||
if sha == Gitlab::Git::BLANK_SHA
|
||||
return false
|
||||
end
|
||||
|
||||
@pipeline = Ci::Pipeline.new(
|
||||
project: project,
|
||||
sha: sha,
|
||||
ref: ref,
|
||||
before_sha: before_sha,
|
||||
tag: tag,
|
||||
user: user)
|
||||
|
||||
##
|
||||
# Skip creating pipeline if no gitlab-ci.yml is found
|
||||
#
|
||||
unless @pipeline.ci_yaml_file
|
||||
return false
|
||||
end
|
||||
|
||||
##
|
||||
# Skip creating builds for commits that have [ci skip]
|
||||
# but save pipeline object
|
||||
#
|
||||
if @pipeline.skip_ci?
|
||||
return save_pipeline!
|
||||
end
|
||||
|
||||
##
|
||||
# Skip creating builds when CI config is invalid
|
||||
# but save pipeline object
|
||||
#
|
||||
unless @pipeline.config_processor
|
||||
return save_pipeline!
|
||||
end
|
||||
|
||||
##
|
||||
# Skip creating pipeline object if there are no builds for it.
|
||||
#
|
||||
unless @pipeline.create_builds(user)
|
||||
@pipeline.errors.add(:base, 'No builds created')
|
||||
return false
|
||||
end
|
||||
|
||||
save_pipeline!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
##
|
||||
# Create a new pipeline and touch object to calculate status
|
||||
#
|
||||
def save_pipeline!
|
||||
@pipeline.save!
|
||||
@pipeline.touch
|
||||
@pipeline
|
||||
end
|
||||
end
|
|
@ -18,9 +18,14 @@ class DeleteUserService
|
|||
user.personal_projects.each do |project|
|
||||
# Skip repository removal because we remove directory with namespace
|
||||
# that contain all this repositories
|
||||
::Projects::DestroyService.new(project, current_user, skip_repo: true).pending_delete!
|
||||
::Projects::DestroyService.new(project, current_user, skip_repo: true).async_execute
|
||||
end
|
||||
|
||||
user.destroy
|
||||
# Destroy the namespace after destroying the user since certain methods may depend on the namespace existing
|
||||
namespace = user.namespace
|
||||
user_data = user.destroy
|
||||
namespace.really_destroy!
|
||||
|
||||
user_data
|
||||
end
|
||||
end
|
||||
|
|