Commit dc1d269f authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into pipeline-emails

* upstream/master: (237 commits)
  Grapify boards API
  Add test, fix merge error
  Use local assigns to get the dropdown title
  Updated issuable dropdown titles
  Added safety check for formatted values
  Minor style improvement
  Fixed conflict and corrected teaspoon test
  Rename method in test
  Moved ci_status environments logic to new action ci_envrionments_status and set up frontend polling
  Refactor ci_status on MergeRequestController
  Fix indenting error in HAML
  Show what time ago a MR was deployed
  Fixed missing links
  Fixed missing links
  Refactor merge requests revisions
  Add link to update docs for source installations
  Grapify todos API
  Link to review apps example from docs
  fix grafana_configuration.md move link
  Do not run before_script, artifacts, cache in trigger_docs job
  ...
parents b5f9d4c4 ca3bef55
...@@ -210,6 +210,13 @@ rake brakeman: *exec ...@@ -210,6 +210,13 @@ rake brakeman: *exec
rake flay: *exec rake flay: *exec
license_finder: *exec license_finder: *exec
rake downtime_check: *exec rake downtime_check: *exec
rake ce_to_ee_merge_check:
<<: *exec
only:
- branches
except:
- tags
allow_failure: yes
rake db:migrate:reset: rake db:migrate:reset:
stage: test stage: test
...@@ -255,6 +262,12 @@ lint-doc: ...@@ -255,6 +262,12 @@ lint-doc:
script: script:
- scripts/lint-doc.sh - scripts/lint-doc.sh
bundler:check:
stage: test
<<: *ruby-static-analysis
script:
- bundle check
bundler:audit: bundler:audit:
stage: test stage: test
<<: *ruby-static-analysis <<: *ruby-static-analysis
...@@ -293,6 +306,17 @@ coverage: ...@@ -293,6 +306,17 @@ coverage:
- coverage/index.html - coverage/index.html
- coverage/assets/ - coverage/assets/
# Trigger docs build
trigger_docs:
stage: post-test
before_script: []
cache: {}
artifacts: {}
script:
- "curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master https://gitlab.com/api/v3/projects/38069/trigger/builds"
only:
- master
# Notify slack in the end # Notify slack in the end
notify:slack: notify:slack:
...@@ -325,3 +349,16 @@ pages: ...@@ -325,3 +349,16 @@ pages:
- public - public
only: only:
- master - master
# Insurance in case a gem needed by one of our releases gets yanked from
# rubygems.org in the future.
cache gems:
only:
- tags
variables:
SETUP_DB: "false"
script:
- bundle package --all --all-platforms
artifacts:
paths:
- vendor/cache
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.13.0 (unreleased) v 8.13.0 (unreleased)
- Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675)
- Respond with 404 Not Found for non-existent tags (Linus Thiel)
- Truncate long labels with ellipsis in labels page - Truncate long labels with ellipsis in labels page
- Adding members no longer silently fails when there is extra whitespace
- Update runner version only when updating contacted_at - Update runner version only when updating contacted_at
- Add link from system note to compare with previous version - Add link from system note to compare with previous version
- Improve issue load time performance by avoiding ORDER BY in find_by call - Use gitlab-shell v3.6.6
- Use gitlab-shell v3.6.2 (GIT TRACE logging)
- Add `/projects/visible` API endpoint (Ben Boeckel) - Add `/projects/visible` API endpoint (Ben Boeckel)
- Fix centering of custom header logos (Ashley Dumaine) - Fix centering of custom header logos (Ashley Dumaine)
- ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup
- Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun)
- Updating verbiage on git basics to be more intuitive
- Clarify documentation for Runners API (Gennady Trafimenkov)
- Change user & group landing page routing from /u/:username to /:username
- Prevent running GfmAutocomplete setup for each diff note !6569
- Added documentation for .gitattributes files
- AbstractReferenceFilter caches project_refs on RequestStore when active - AbstractReferenceFilter caches project_refs on RequestStore when active
- Replaced the check sign to arrow in the show build view. !6501 - Replaced the check sign to arrow in the show build view. !6501
- Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar)
- Fix Error 500 when viewing old merge requests with bad diff data
- Create a new /templates namespace for the /licenses, /gitignores and /gitlab_ci_ymls API endpoints. !5717 (tbalthazar)
- Speed-up group milestones show page - Speed-up group milestones show page
- Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps)
- Extract project#update_merge_requests and SystemHooks to its own worker from GitPushService
- Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs)
- Add tag shortcut from the Commit page. !6543 - Add tag shortcut from the Commit page. !6543
- Keep refs for each deployment - Keep refs for each deployment
- Allow browsing branches that end with '.atom'
- Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller)
- Replace unique keyframes mixin with keyframe mixin with specific names (ClemMakesApps)
- Add more tests for calendar contribution (ClemMakesApps) - Add more tests for calendar contribution (ClemMakesApps)
- Update Gitlab Shell to fix some problems with moving projects between storages
- Cache rendered markdown in the database, rather than Redis - Cache rendered markdown in the database, rather than Redis
- Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references
- Do not alter 'force_remove_source_branch' options on MergeRequest unless specified
- Simplify Mentionable concern instance methods - Simplify Mentionable concern instance methods
- API: Ability to retrieve version information (Robert Schilling)
- Fix permission for setting an issue's due date - Fix permission for setting an issue's due date
- API: Multi-file commit !6096 (mahcsig) - API: Multi-file commit !6096 (mahcsig)
- Unicode emoji are now converted to images
- Revert "Label list shows all issues (opened or closed) with that label" - Revert "Label list shows all issues (opened or closed) with that label"
- Expose expires_at field when sharing project on API - Expose expires_at field when sharing project on API
- Fix VueJS template tags being rendered in code comments - Fix VueJS template tags being rendered in code comments
- Added copy file path button to merge request diff files
- Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell) - Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell)
- Add Issue Board API support (andrebsguedes) - Add Issue Board API support (andrebsguedes)
- Allow the Koding integration to be configured through the API - Allow the Koding integration to be configured through the API
- Add new issue button to each list on Issues Board - Add new issue button to each list on Issues Board
- Added soft wrap button to repository file/blob editor - Added soft wrap button to repository file/blob editor
- Update namespace validation to forbid reserved names (.git and .atom) (Will Starms)
- Show the time ago a merge request was deployed to an environment
- Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps)
- Fix todos page mobile viewport layout (ClemMakesApps) - Fix todos page mobile viewport layout (ClemMakesApps)
- Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps)
- Remove redundant mixins (ClemMakesApps)
- Added 'Download' button to the Snippets page (Justin DiPierro)
- Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison)
- Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska)
- Fix that manual jobs would no longer block jobs in the next stage. !6604 - Fix that manual jobs would no longer block jobs in the next stage. !6604
- Add configurable email subject suffix (Fu Xu) - Add configurable email subject suffix (Fu Xu)
- Use defined colour for a language when available !6748 (nilsding)
- Added tooltip to fork count on project show page. (Justin DiPierro) - Added tooltip to fork count on project show page. (Justin DiPierro)
- Use a ConnectionPool for Rails.cache on Sidekiq servers - Use a ConnectionPool for Rails.cache on Sidekiq servers
- Replace `alias_method_chain` with `Module#prepend` - Replace `alias_method_chain` with `Module#prepend`
...@@ -57,28 +81,54 @@ v 8.13.0 (unreleased) ...@@ -57,28 +81,54 @@ v 8.13.0 (unreleased)
- Fix Long commit messages overflow viewport in file tree - Fix Long commit messages overflow viewport in file tree
- Revert avoid touching file system on Build#artifacts? - Revert avoid touching file system on Build#artifacts?
- Stop using a Redis lease when updating the project activity timestamp whenever a new event is created - Stop using a Redis lease when updating the project activity timestamp whenever a new event is created
- Add disabled delete button to protected branches (ClemMakesApps)
- Add broadcast messages and alerts below sub-nav - Add broadcast messages and alerts below sub-nav
- Better empty state for Groups view - Better empty state for Groups view
- API: New /users/:id/events endpoint
- Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe)
- Replace bootstrap caret with fontawesome caret (ClemMakesApps) - Replace bootstrap caret with fontawesome caret (ClemMakesApps)
- Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533
- Add organization field to user profile - Add organization field to user profile
- Ignore deployment for statistics in Cycle Analytics, except in staging and production stages
- Fix enter key when navigating search site search dropdown. !6643 (Brennan Roberts)
- Fix deploy status responsiveness error !6633 - Fix deploy status responsiveness error !6633
- Make searching for commits case insensitive
- Fix resolved discussion display in side-by-side diff view !6575 - Fix resolved discussion display in side-by-side diff view !6575
- Optimize GitHub importing for speed and memory - Optimize GitHub importing for speed and memory
- API: expose pipeline data in builds API (!6502, Guilherme Salazar) - API: expose pipeline data in builds API (!6502, Guilherme Salazar)
- Notify the Merger about merge after successful build (Dimitris Karakasilis) - Notify the Merger about merge after successful build (Dimitris Karakasilis)
- Reorder issue and merge request titles to show IDs first. !6503 (Greg Laubenstein)
- Reduce queries needed to find users using their SSH keys when pushing commits - Reduce queries needed to find users using their SSH keys when pushing commits
- Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) - Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska)
- Fix broken repository 500 errors in project list - Fix broken repository 500 errors in project list
- Fix Pipeline list commit column width should be adjusted - Fix Pipeline list commit column width should be adjusted
- Close todos when accepting merge requests via the API !6486 (tonygambone) - Close todos when accepting merge requests via the API !6486 (tonygambone)
- Ability to batch assign issues relating to a merge request to the author. !5725 (jamedjo)
- Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer)
- Retouch environments list and deployments list - Retouch environments list and deployments list
- Add multiple command support for all label related slash commands !6780 (barthc)
- Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Add Container Registry on/off status to Admin Area !6638 (the-undefined)
- Allow empty merge requests !6384 (Artem Sidorenko)
- Grouped pipeline dropdown is a scrollable container - Grouped pipeline dropdown is a scrollable container
- Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi)
v 8.12.5 (unreleased) - Fixes padding in all clipboard icons that have .btn class
- Fix a typo in doc/api/labels.md
- API: all unknown routing will be handled with 404 Not Found
- Make guests unable to view MRs on private projects
v 8.12.7
- Use gitlab-markup gem instead of github-markup to fix `.rst` file rendering. !6659
v 8.12.6
- Update mailroom to 0.8.1 in Gemfile.lock !6814
v 8.12.5
- Switch from request to env in ::API::Helpers. !6615
- Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread. !6714
- Improve issue load time performance by avoiding ORDER BY in find_by call. !6724
- Add a new gitlab:users:clear_all_authentication_tokens task. !6745
- Don't send Private-Token (API authentication) headers to Sentry
- Share projects via the API only with groups the authenticated user can access
v 8.12.4 v 8.12.4
- Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. !6294 (lukehowell) - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. !6294 (lukehowell)
...@@ -93,7 +143,7 @@ v 8.12.4 ...@@ -93,7 +143,7 @@ v 8.12.4
- Fix failed project deletion when feature visibility set to private. !6688 - Fix failed project deletion when feature visibility set to private. !6688
- Prevent claiming associated model IDs via import. - Prevent claiming associated model IDs via import.
- Set GitLab project exported file permissions to owner only - Set GitLab project exported file permissions to owner only
- Change user & group landing page routing from /u/:username to /:username - Improve the way merge request versions are compared with each other
v 8.12.3 v 8.12.3
- Update Gitlab Shell to support low IO priority for storage moves - Update Gitlab Shell to support low IO priority for storage moves
...@@ -119,6 +169,7 @@ v 8.12.1 ...@@ -119,6 +169,7 @@ v 8.12.1
- Fix issue with search filter labels not displaying - Fix issue with search filter labels not displaying
v 8.12.0 v 8.12.0
- Removes inconsistency regarding tagging immediatelly as merged once you create a new branch. !6408
- Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251
- Only check :can_resolve permission if the note is resolvable - Only check :can_resolve permission if the note is resolvable
- Bump fog-aws to v0.11.0 to support ap-south-1 region - Bump fog-aws to v0.11.0 to support ap-south-1 region
...@@ -307,6 +358,10 @@ v 8.12.0 ...@@ -307,6 +358,10 @@ v 8.12.0
- Fix non-master branch readme display in tree view - Fix non-master branch readme display in tree view
- Add UX improvements for merge request version diffs - Add UX improvements for merge request version diffs
v 8.11.9
- Don't send Private-Token (API authentication) headers to Sentry
- Share projects via the API only with groups the authenticated user can access
v 8.11.8 v 8.11.8
- Respect the fork_project permission when forking projects - Respect the fork_project permission when forking projects
- Set a restrictive CORS policy on the API for credentialed requests - Set a restrictive CORS policy on the API for credentialed requests
...@@ -532,6 +587,10 @@ v 8.11.0 ...@@ -532,6 +587,10 @@ v 8.11.0
- Update gitlab_git gem to 10.4.7 - Update gitlab_git gem to 10.4.7
- Simplify SQL queries of marking a todo as done - Simplify SQL queries of marking a todo as done
v 8.10.12
- Don't send Private-Token (API authentication) headers to Sentry
- Share projects via the API only with groups the authenticated user can access
v 8.10.11 v 8.10.11
- Respect the fork_project permission when forking projects - Respect the fork_project permission when forking projects
- Set a restrictive CORS policy on the API for credentialed requests - Set a restrictive CORS policy on the API for credentialed requests
......
...@@ -51,7 +51,7 @@ gem 'browser', '~> 2.2' ...@@ -51,7 +51,7 @@ gem 'browser', '~> 2.2'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem 'gitlab_git', '~> 10.6.7' gem 'gitlab_git', '~> 10.6.8'
# LDAP Auth # LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes # GitLab fork with several improvements to original library. For full list of changes
...@@ -101,7 +101,7 @@ gem 'seed-fu', '~> 2.3.5' ...@@ -101,7 +101,7 @@ gem 'seed-fu', '~> 2.3.5'
# Markdown and HTML processing # Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0' gem 'html-pipeline', '~> 1.11.0'
gem 'deckar01-task_list', '1.0.5', require: 'task_list/railtie' gem 'deckar01-task_list', '1.0.5', require: 'task_list/railtie'
gem 'github-markup', '~> 1.4' gem 'gitlab-markup', '~> 1.5.0'
gem 'redcarpet', '~> 3.3.3' gem 'redcarpet', '~> 3.3.3'
gem 'RedCloth', '~> 4.3.2' gem 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~>3.6' gem 'rdoc', '~>3.6'
...@@ -225,7 +225,7 @@ gem 'gon', '~> 6.1.0' ...@@ -225,7 +225,7 @@ gem 'gon', '~> 6.1.0'
gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.1.0' gem 'jquery-rails', '~> 4.1.0'
gem 'jquery-ui-rails', '~> 5.0.0' gem 'jquery-ui-rails', '~> 5.0.0'
gem 'request_store', '~> 1.3.0' gem 'request_store', '~> 1.3'
gem 'select2-rails', '~> 3.5.9' gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1' gem 'virtus', '~> 1.0.1'
gem 'net-ssh', '~> 3.0.1' gem 'net-ssh', '~> 3.0.1'
...@@ -262,6 +262,8 @@ group :development do ...@@ -262,6 +262,8 @@ group :development do
# thin instead webrick # thin instead webrick
gem 'thin', '~> 1.7.0' gem 'thin', '~> 1.7.0'
gem 'activerecord_sane_schema_dumper', '0.2'
end end
group :development, :test do group :development, :test do
...@@ -324,7 +326,7 @@ gem 'newrelic_rpm', '~> 3.16' ...@@ -324,7 +326,7 @@ gem 'newrelic_rpm', '~> 3.16'
gem 'octokit', '~> 4.3.0' gem 'octokit', '~> 4.3.0'
gem 'mail_room', '~> 0.8' gem 'mail_room', '~> 0.8.1'
gem 'email_reply_parser', '~> 0.5.8' gem 'email_reply_parser', '~> 0.5.8'
...@@ -341,7 +343,7 @@ gem 'oauth2', '~> 1.2.0' ...@@ -341,7 +343,7 @@ gem 'oauth2', '~> 1.2.0'
gem 'paranoia', '~> 2.0' gem 'paranoia', '~> 2.0'
# Health check # Health check
gem 'health_check', '~> 2.1.0' gem 'health_check', '~> 2.2.0'
# System information # System information
gem 'vmstat', '~> 2.2' gem 'vmstat', '~> 2.2'
......
...@@ -38,6 +38,8 @@ GEM ...@@ -38,6 +38,8 @@ GEM
multi_json (~> 1.11, >= 1.11.2) multi_json (~> 1.11, >= 1.11.2)
rack (>= 1.5.2, < 3) rack (>= 1.5.2, < 3)
railties (>= 4.0, < 5.1) railties (>= 4.0, < 5.1)
activerecord_sane_schema_dumper (0.2)
rails (>= 4, < 5)
activesupport (4.2.7.1) activesupport (4.2.7.1)
i18n (~> 0.7) i18n (~> 0.7)
json (~> 1.7, >= 1.7.7) json (~> 1.7, >= 1.7.7)
...@@ -280,7 +282,8 @@ GEM ...@@ -280,7 +282,8 @@ GEM
diff-lcs (~> 1.1) diff-lcs (~> 1.1)
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab_git (10.6.7) gitlab-markup (1.5.0)
gitlab_git (10.6.8)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
...@@ -334,7 +337,7 @@ GEM ...@@ -334,7 +337,7 @@ GEM
thor thor
tilt tilt
hashie (3.4.4) hashie (3.4.4)
health_check (2.1.0) health_check (2.2.1)
rails (>= 4.0) rails (>= 4.0)
hipchat (1.5.2) hipchat (1.5.2)
httparty httparty
...@@ -399,7 +402,7 @@ GEM ...@@ -399,7 +402,7 @@ GEM
systemu (~> 2.6.2) systemu (~> 2.6.2)
mail (2.6.4) mail (2.6.4)
mime-types (>= 1.16, < 4) mime-types (>= 1.16, < 4)
mail_room (0.8.0) mail_room (0.8.1)
method_source (0.8.2) method_source (0.8.2)
mime-types (2.99.3) mime-types (2.99.3)
mimemagic (0.3.0) mimemagic (0.3.0)
...@@ -805,6 +808,7 @@ DEPENDENCIES ...@@ -805,6 +808,7 @@ DEPENDENCIES
RedCloth (~> 4.3.2) RedCloth (~> 4.3.2)
ace-rails-ap (~> 4.1.0) ace-rails-ap (~> 4.1.0)
activerecord-session_store (~> 1.0.0) activerecord-session_store (~> 1.0.0)
activerecord_sane_schema_dumper (= 0.2)
acts-as-taggable-on (~> 4.0) acts-as-taggable-on (~> 4.0)
addressable (~> 2.3.8) addressable (~> 2.3.8)
after_commit_queue (~> 1.3.0) after_commit_queue (~> 1.3.0)
...@@ -861,9 +865,9 @@ DEPENDENCIES ...@@ -861,9 +865,9 @@ DEPENDENCIES
gemnasium-gitlab-service (~> 0.2) gemnasium-gitlab-service (~> 0.2)
gemojione (~> 3.0) gemojione (~> 3.0)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
github-markup (~> 1.4)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab_git (~> 10.6.7) gitlab-markup (~> 1.5.0)
gitlab_git (~> 10.6.8)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.2) gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.2) gollum-rugged_adapter (~> 0.4.2)
...@@ -872,7 +876,7 @@ DEPENDENCIES ...@@ -872,7 +876,7 @@ DEPENDENCIES
grape-entity (~> 0.4.2) grape-entity (~> 0.4.2)
haml_lint (~> 0.18.2) haml_lint (~> 0.18.2)
hamlit (~> 2.6.1) hamlit (~> 2.6.1)
health_check (~> 2.1.0) health_check (~> 2.2.0)
hipchat (~> 1.5.0) hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0) html-pipeline (~> 1.11.0)
httparty (~> 0.13.3) httparty (~> 0.13.3)
...@@ -889,7 +893,7 @@ DEPENDENCIES ...@@ -889,7 +893,7 @@ DEPENDENCIES
license_finder (~> 2.1.0) license_finder (~> 2.1.0)
licensee (~> 8.0.0) licensee (~> 8.0.0)
loofah (~> 2.0.3) loofah (~> 2.0.3)
mail_room (~> 0.8) mail_room (~> 0.8.1)
method_source (~> 0.8) method_source (~> 0.8)
minitest (~> 5.7.0) minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6) mousetrap-rails (~> 1.4.6)
...@@ -934,7 +938,7 @@ DEPENDENCIES ...@@ -934,7 +938,7 @@ DEPENDENCIES
redis (~> 3.2) redis (~> 3.2)
redis-namespace (~> 1.5.2) redis-namespace (~> 1.5.2)
redis-rails (~> 4.0.0) redis-rails (~> 4.0.0)
request_store (~> 1.3.0) request_store (~> 1.3)
rerun (~> 0.11.0) rerun (~> 0.11.0)
responders (~> 2.0) responders (~> 2.0)
rouge (~> 2.0) rouge (~> 2.0)
......
# GitLab # GitLab
[![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) [![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
[![coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) [![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](http://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby)
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
[![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) [![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42)
......
...@@ -6,11 +6,10 @@ ...@@ -6,11 +6,10 @@
groupProjectsPath: "/api/:version/groups/:id/projects.json", groupProjectsPath: "/api/:version/groups/:id/projects.json",
projectsPath: "/api/:version/projects.json?simple=true", projectsPath: "/api/:version/projects.json?simple=true",
labelsPath: "/:namespace_path/:project_path/labels", labelsPath: "/:namespace_path/:project_path/labels",
licensePath: "/api/:version/licenses/:key", licensePath: "/api/:version/templates/licenses/:key",
gitignorePath: "/api/:version/gitignores/:key", gitignorePath: "/api/:version/templates/gitignores/:key",
gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key", gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key",
issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key", issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key",
group: function(group_id, callback) { group: function(group_id, callback) {
var url = Api.buildUrl(Api.groupPath) var url = Api.buildUrl(Api.groupPath)
.replace(':id', group_id); .replace(':id', group_id);
......
...@@ -28,12 +28,13 @@ $(() => { ...@@ -28,12 +28,13 @@ $(() => {
state: Store.state, state: Store.state,
loading: true, loading: true,
endpoint: $boardApp.dataset.endpoint, endpoint: $boardApp.dataset.endpoint,
boardId: $boardApp.dataset.boardId,
disabled: $boardApp.dataset.disabled === 'true', disabled: $boardApp.dataset.disabled === 'true',
issueLinkBase: $boardApp.dataset.issueLinkBase issueLinkBase: $boardApp.dataset.issueLinkBase
}, },
init: Store.create.bind(Store), init: Store.create.bind(Store),
created () { created () {
gl.boardService = new BoardService(this.endpoint); gl.boardService = new BoardService(this.endpoint, this.boardId);
}, },
ready () { ready () {
Store.disabled = this.disabled; Store.disabled = this.disabled;
......
class BoardService { class BoardService {
constructor (root) { constructor (root, boardId) {
Vue.http.options.root = root; Vue.http.options.root = root;
this.lists = Vue.resource(`${root}/lists{/id}`, {}, { this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, {
generate: { generate: {
method: 'POST', method: 'POST',
url: `${root}/lists/generate.json` url: `${root}/${boardId}/lists/generate.json`
} }
}); });
this.issue = Vue.resource(`${root}/issues{/id}`, {}); this.issue = Vue.resource(`${root}/${boardId}/issues{/id}`, {});
this.issues = Vue.resource(`${root}/lists{/id}/issues`, {}); this.issues = Vue.resource(`${root}/${boardId}/lists{/id}/issues`, {});
Vue.http.interceptors.push((request, next) => { Vue.http.interceptors.push((request, next) => {
request.headers['X-CSRF-Token'] = $.rails.csrfToken(); request.headers['X-CSRF-Token'] = $.rails.csrfToken();
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
shortcut_handler = null; shortcut_handler = null;
switch (page) { switch (page) {
case 'projects:boards:show': case 'projects:boards:show':
case 'projects:boards:index':
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
break; break;
case 'projects:merge_requests:index': case 'projects:merge_requests:index':
...@@ -126,6 +127,9 @@ ...@@ -126,6 +127,9 @@
new TreeView(); new TreeView();
} }
break; break;
case 'projects:pipelines:show':
new gl.Pipelines();
break;
case 'groups:activity': case 'groups:activity':
new Activities(); new Activities();
break; break;
......
...@@ -52,37 +52,27 @@ ...@@ -52,37 +52,27 @@
} }
} }
}, },
setup: function(input) { setup: _.debounce(function(input) {
// Add GFM auto-completion to all input fields, that accept GFM input. // Add GFM auto-completion to all input fields, that accept GFM input.
this.input = input || $('.js-gfm-input'); this.input = input || $('.js-gfm-input');
// destroy previous instances // destroy previous instances
this.destroyAtWho(); this.destroyAtWho();
// set up instances // set up instances
this.setupAtWho(); this.setupAtWho();
if (this.dataSource) {
if (!this.dataLoading && !this.cachedData) { if (this.dataSource && !this.dataLoading && !this.cachedData) {
this.dataLoading = true; this.dataLoading = true;
setTimeout((function(_this) { return this.fetchData(this.dataSource)
return function() { .done((data) => {
var fetch; this.dataLoading = false;
fetch = _this.fetchData(_this.dataSource); this.loadData(data);
return fetch.done(function(data) {
_this.dataLoading = false;
return _this.loadData(data);
}); });
}; };
// We should wait until initializations are done
// and only trigger the last .setup since
// The previous .dataSource belongs to the previous issuable
// and the last one will have the **proper** .dataSource property
// TODO: Make this a singleton and turn off events when moving to another page
})(this), 1000);
}
if (this.cachedData != null) { if (this.cachedData != null) {
return this.loadData(this.cachedData); return this.loadData(this.cachedData);
} }
} }, 1000),
},
setupAtWho: function() { setupAtWho: function() {
// Emoji // Emoji
this.input.atwho({ this.input.atwho({
......
...@@ -738,6 +738,7 @@ ...@@ -738,6 +738,7 @@
return false; return false;
} }
if (currentKeyCode === 13 && currentIndex !== -1) { if (currentKeyCode === 13 && currentIndex !== -1) {
e.preventDefault();
_this.selectRowAtIndex(); _this.selectRowAtIndex();
} }
}; };
......
...@@ -292,7 +292,7 @@ ...@@ -292,7 +292,7 @@
return; return;
} }
if (page === 'projects:boards:show') { if ($('html').hasClass('issue-boards-page')) {
return; return;
} }
if ($dropdown.hasClass('js-multiselect')) { if ($dropdown.hasClass('js-multiselect')) {
...@@ -334,7 +334,7 @@ ...@@ -334,7 +334,7 @@
page = $('body').data('page'); page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index'; isIssueIndex = page === 'projects:issues:index';
isMRIndex = page === 'projects:merge_requests:index'; isMRIndex = page === 'projects:merge_requests:index';
if (page === 'projects:boards:show') { if ($('html').hasClass('issue-boards-page')) {
if (label.isAny) { if (label.isAny) {
gl.issueBoards.BoardsStore.state.filters['label_name'] = []; gl.issueBoards.BoardsStore.state.filters['label_name'] = [];
} }
......
(function() { ((global) => {
var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
this.MergeRequestWidget = (function() { const DEPLOYMENT_TEMPLATE = `<div class="mr-widget-heading" id="<%- id %>">
<div class="ci_widget ci-success">
<%= ci_success_icon %>
<span>
Deployed to
<a href="<%- url %>" target="_blank" class="environment">
<%- name %>
</a>
<span class="js-environment-timeago" data-toggle="tooltip" data-placement="top" data-title="<%- deployed_at_formatted %>">
<%- deployed_at %>
</span>
<a class="js-environment-link" href="<%- external_url %>" target="_blank">
<i class="fa fa-external-link"></i>
View on <%- external_url_formatted %>
</a>
</span>
</div>
</div>`;
global.MergeRequestWidget = (function() {
function MergeRequestWidget(opts) { function MergeRequestWidget(opts) {
// Initialize MergeRequestWidget behavior // Initialize MergeRequestWidget behavior
// //
...@@ -10,17 +29,23 @@ ...@@ -10,17 +29,23 @@
// ci_status_url - String, URL to use to check CI status // ci_status_url - String, URL to use to check CI status
// //
this.opts = opts; this.opts = opts;
this.$widgetBody = $('.mr-widget-body');
$('#modal_merge_info').modal({ $('#modal_merge_info').modal({
show: false show: false
}); });
this.firstCICheck = true; this.firstCICheck = true;
this.readyForCICheck = false; this.readyForCICheck = false;
this.readyForCIEnvironmentCheck = false;
this.cancel = false; this.cancel = false;
clearInterval(this.fetchBuildStatusInterval); clearInterval(this.fetchBuildStatusInterval);
clearInterval(this.fetchBuildEnvironmentStatusInterval);
this.clearEventListeners(); this.clearEventListeners();
this.addEventListeners(); this.addEventListeners();
this.getCIStatus(false); this.getCIStatus(false);
this.getCIEnvironmentsStatus();
this.retrieveSuccessIcon();
this.pollCIStatus(); this.pollCIStatus();
this.pollCIEnvironmentsStatus();
notifyPermissions(); notifyPermissions();
} }
...@@ -41,6 +66,7 @@ ...@@ -41,6 +66,7 @@
page = $('body').data('page').split(':').last(); page = $('body').data('page').split(':').last();
if (allowedPages.indexOf(page) < 0) { if (allowedPages.indexOf(page) < 0) {
clearInterval(_this.fetchBuildStatusInterval); clearInterval(_this.fetchBuildStatusInterval);
clearInterval(_this.fetchBuildEnvironmentStatusInterval);
_this.cancelPolling(); _this.cancelPolling();
return _this.clearEventListeners(); return _this.clearEventListeners();
} }
...@@ -48,6 +74,12 @@ ...@@ -48,6 +74,12 @@
})(this)); })(this));
}; };
MergeRequestWidget.prototype.retrieveSuccessIcon = function() {
const $ciSuccessIcon = $('.js-success-icon');
this.$ciSuccessIcon = $ciSuccessIcon.html();
$ciSuccessIcon.remove();
}
MergeRequestWidget.prototype.mergeInProgress = function(deleteSourceBranch) { MergeRequestWidget.prototype.mergeInProgress = function(deleteSourceBranch) {
if (deleteSourceBranch == null) { if (deleteSourceBranch == null) {
deleteSourceBranch = false; deleteSourceBranch = false;
...@@ -62,7 +94,7 @@ ...@@ -62,7 +94,7 @@
urlSuffix = deleteSourceBranch ? '?deleted_source_branch=true' : ''; urlSuffix = deleteSourceBranch ? '?deleted_source_branch=true' : '';
return window.location.href = window.location.pathname + urlSuffix; return window.location.href = window.location.pathname + urlSuffix;
} else if (data.merge_error) { } else if (data.merge_error) {
return $('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>"); return this.$widgetBody.html("<h4>" + data.merge_error + "</h4>");
} else { } else {
callback = function() { callback = function() {
return merge_request_widget.mergeInProgress(deleteSourceBranch); return merge_request_widget.mergeInProgress(deleteSourceBranch);
...@@ -118,6 +150,7 @@ ...@@ -118,6 +150,7 @@
if (data.status === '') { if (data.status === '') {
return; return;
} }
if (data.environments && data.environments.length) _this.renderEnvironments(data.environments);
if (_this.firstCICheck || data.status !== _this.opts.ci_status && (data.status != null)) { if (_this.firstCICheck || data.status !== _this.opts.ci_status && (data.status != null)) {
_this.opts.ci_status = data.status; _this.opts.ci_status = data.status;
_this.showCIStatus(data.status); _this.showCIStatus(data.status);
...@@ -150,6 +183,41 @@ ...@@ -150,6 +183,41 @@
})(this)); })(this));
}; };
MergeRequestWidget.prototype.pollCIEnvironmentsStatus = function() {
this.fetchBuildEnvironmentStatusInterval = setInterval(() => {
if (!this.readyForCIEnvironmentCheck) return;
this.getCIEnvironmentsStatus();
this.readyForCIEnvironmentCheck = false;
}, 300000);
};
MergeRequestWidget.prototype.getCIEnvironmentsStatus = function() {
$.getJSON(this.opts.ci_environments_status_url, (environments) => {
if (this.cancel) return;
this.readyForCIEnvironmentCheck = true;
if (environments && environments.length) this.renderEnvironments(environments);
});
};
MergeRequestWidget.prototype.renderEnvironments = function(environments) {
for (let i = 0; i < environments.length; i++) {
const environment = environments[i];
if ($(`.mr-state-widget #${ environment.id }`).length) return;
const $template = $(DEPLOYMENT_TEMPLATE);
if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove();
if (environment.deployed_at && environment.deployed_at_formatted) {
environment.deployed_at = $.timeago(environment.deployed_at) + '.';
} else {
$('.js-environment-timeago', $template).remove();
environment.name += '.';
}
environment.ci_success_icon = this.$ciSuccessIcon;
const templateString = _.unescape($template[0].outerHTML);
const template = _.template(templateString)(environment)
this.$widgetBody.before(template);
}
};
MergeRequestWidget.prototype.showCIStatus = function(state) { MergeRequestWidget.prototype.showCIStatus = function(state) {
var allowed_states; var allowed_states;
if (state == null) { if (state == null) {
...@@ -190,4 +258,4 @@ ...@@ -190,4 +258,4 @@
})(); })();
}).call(this); })(window.gl || (window.gl = {}));
...@@ -110,7 +110,7 @@ ...@@ -110,7 +110,7 @@
e.preventDefault(); e.preventDefault();
return; return;
} }
if (page === 'projects:boards:show') { if ($('html').hasClass('issue-boards-page')) {
gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = selected.name; gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = selected.name;
gl.issueBoards.BoardsStore.updateFiltersUrl(); gl.issueBoards.BoardsStore.updateFiltersUrl();
e.preventDefault(); e.preventDefault();
......
(function() { ((global) => {
function toggleGraph() {
class Pipelines {
constructor() {
$(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph);
this.addMarginToBuildColumns();
}
toggleGraph() {
const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); const $pipelineBtn = $(this).closest('.toggle-pipeline-btn');
const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph');
const $btnText = $(this).find('.toggle-btn-text'); const $btnText = $(this).find('.toggle-btn-text');
const $icon = $(this).find('.fa'); const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed');
$($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed');
const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed');
const expandIcon = 'fa-caret-down'; graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide')
const hideIcon = 'fa-caret-up'; }
if(graphCollapsed) { addMarginToBuildColumns() {
$btnText.text('Expand'); const $secondChildBuildNode = $('.build:nth-child(2)');
$icon.removeClass(hideIcon).addClass(expandIcon); if ($secondChildBuildNode.length) {
} else { const $firstChildBuildNode = $secondChildBuildNode.prev('.build');
$btnText.text('Hide'); const $multiBuildColumn = $secondChildBuildNode.closest('.stage-column');
$icon.removeClass(expandIcon).addClass(hideIcon); const $previousColumn = $multiBuildColumn.prev('.stage-column');
$multiBuildColumn.addClass('left-margin');
$firstChildBuildNode.addClass('left-connector');
$previousColumn.each(function() {
$this = $(this);
if ($('.build', $this).length === 1) $this.addClass('no-margin');
});
} }
$('.pipeline-graph').removeClass('hidden');
} }
}
global.Pipelines = Pipelines;
$(document).on('click', '.toggle-pipeline-btn', toggleGraph); })(window.gl || (window.gl = {}));
})();
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
this.ProjectNew = (function() { this.ProjectNew = (function() {
function ProjectNew() { function ProjectNew() {
this.toggleSettings = bind(this.toggleSettings, this); this.toggleSettings = bind(this.toggleSettings, this);
this.$selects = $('.features select'); this.$selects = $('.features select').filter(function () {
return $(this).data('field');
});
$('.project-edit-container').on('ajax:before', (function(_this) { $('.project-edit-container').on('ajax:before', (function(_this) {
return function() { return function() {
......
...@@ -160,7 +160,7 @@ ...@@ -160,7 +160,7 @@
selectedId = user.id; selectedId = user.id;
return; return;
} }
if (page === 'projects:boards:show') { if ($('html').hasClass('issue-boards-page')) {
selectedId = user.id; selectedId = user.id;
gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id; gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id;
gl.issueBoards.BoardsStore.updateFiltersUrl(); gl.issueBoards.BoardsStore.updateFiltersUrl();
...@@ -261,10 +261,11 @@ ...@@ -261,10 +261,11 @@
} }
} }
if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) { if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) {
var trimmed = query.term.trim();
emailUser = { emailUser = {
name: "Invite \"" + query.term + "\"", name: "Invite \"" + query.term + "\"",
username: query.term, username: trimmed,
id: query.term id: trimmed
}; };
data.results.unshift(emailUser); data.results.unshift(emailUser);
} }
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
width: 40px; width: 40px;
height: 40px; height: 40px;
padding: 0; padding: 0;
@include border-radius($avatar_radius); border-radius: $avatar_radius;
border: 1px solid rgba(0, 0, 0, .1); border: 1px solid rgba(0, 0, 0, .1);
&.avatar-inline { &.avatar-inline {
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
} }
&.avatar-tile { &.avatar-tile {
@include border-radius(0); border-radius: 0;
border: none; border: none;
} }
......
...@@ -133,7 +133,7 @@ ...@@ -133,7 +133,7 @@
} }
.identicon { .identicon {
@include border-radius(50%); border-radius: 50%;
} }
} }
......
@mixin btn-default { @mixin btn-default {
@include border-radius(3px); border-radius: 3px;
font-size: $gl-font-size; font-size: $gl-font-size;
font-weight: 500; font-weight: 500;
padding: $gl-vert-padding $gl-btn-padding; padding: $gl-vert-padding $gl-btn-padding;
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
&:active { &:active {
outline: none; outline: none;
background-color: $btn-active-gray; background-color: $btn-active-gray;
@include box-shadow($gl-btn-active-background); box-shadow: $gl-btn-active-background;
} }
} }
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
&:active, &:active,
&.active { &.active {
@include box-shadow ($gl-btn-active-background); box-shadow: $gl-btn-active-background;
background-color: $dark; background-color: $dark;
border-color: $border-dark; border-color: $border-dark;
...@@ -279,7 +279,7 @@ ...@@ -279,7 +279,7 @@
} }
.active { .active {
@include box-shadow($gl-btn-active-background); box-shadow: $gl-btn-active-background;
border: 1px solid #c6cacf !important; border: 1px solid #c6cacf !important;
background-color: #e4e7ed !important; background-color: #e4e7ed !important;
......
...@@ -73,7 +73,7 @@ label { ...@@ -73,7 +73,7 @@ label {
} }
.form-control { .form-control {
@include box-shadow(none); box-shadow: none;
border-radius: 3px; border-radius: 3px;
padding: $gl-vert-padding $gl-input-padding; padding: $gl-vert-padding $gl-input-padding;
} }
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
margin-top: 5px; margin-top: 5px;
} }
@include border-radius(3px); border-radius: 3px;
display: block; display: block;
float: left; float: left;
margin-right: 10px; margin-right: 10px;
......
@mixin unique-keyframes {
$animation-name: unique-id();
@include webkit-prefix(animation-name, $animation-name);
@-webkit-keyframes #{$animation-name} {
@content;
}
@keyframes #{$animation-name} {
@content;
}
}
@mixin tanuki-logo-colors($path-color) { @mixin tanuki-logo-colors($path-color) {
fill: $path-color; fill: $path-color;
transition: all 0.8s; transition: all 0.8s;
...@@ -20,28 +8,6 @@ ...@@ -20,28 +8,6 @@
} }
} }
@mixin tanuki-second-highlight-animations($tanuki-color) {
@include unique-keyframes {
10%, 80% {
fill: #{$tanuki-color}
}
20%, 90% {
fill: lighten($tanuki-color, 25%);
}
}
}
@mixin tanuki-forth-highlight-animations($tanuki-color) {
@include unique-keyframes {
30%, 60% {
fill: #{$tanuki-color};
}
40%, 70% {
fill: lighten($tanuki-color, 25%);
}
}
}
.tanuki-logo { .tanuki-logo {
.tanuki-left-ear, .tanuki-left-ear,
...@@ -67,7 +33,7 @@ ...@@ -67,7 +33,7 @@
} }
.tanuki-left-cheek { .tanuki-left-cheek {
@include unique-keyframes { @include include-keyframes(animate-tanuki-left-cheek) {
0%, 10%, 100% { 0%, 10%, 100% {
fill: lighten($tanuki-yellow, 25%); fill: lighten($tanuki-yellow, 25%);
} }
...@@ -78,15 +44,29 @@ ...@@ -78,15 +44,29 @@
} }
.tanuki-left-eye { .tanuki-left-eye {
@include tanuki-second-highlight-animations($tanuki-orange); @include include-keyframes(animate-tanuki-left-eye) {
10%, 80% {
fill: $tanuki-orange;
}
20%, 90% {
fill: lighten($tanuki-orange, 25%);
}
}
} }
.tanuki-left-ear { .tanuki-left-ear {
@include tanuki-second-highlight-animations($tanuki-red); @include include-keyframes(animate-tanuki-left-ear) {
10%, 80% {
fill: $tanuki-red;
}
20%, 90% {
fill: lighten($tanuki-red, 25%);
}
}
} }
.tanuki-nose { .tanuki-nose {
@include unique-keyframes { @include include-keyframes(animate-tanuki-nose) {
20%, 70% { 20%, 70% {
fill: $tanuki-red; fill: $tanuki-red;
} }
...@@ -97,15 +77,29 @@ ...@@ -97,15 +77,29 @@
} }
.tanuki-right-eye { .tanuki-right-eye {
@include tanuki-forth-highlight-animations($tanuki-orange); @include include-keyframes(animate-tanuki-right-eye) {
30%, 60% {
fill: $tanuki-orange;
}
40%, 70% {
fill: lighten($tanuki-orange, 25%);
}
}
} }
.tanuki-right-ear { .tanuki-right-ear {
@include tanuki-forth-highlight-animations($tanuki-red); @include include-keyframes(animate-tanuki-right-ear) {
30%, 60% {
fill: $tanuki-red;
}
40%, 70% {
fill: lighten($tanuki-red, 25%);
}
}
} }
.tanuki-right-cheek { .tanuki-right-cheek {
@include unique-keyframes { @include include-keyframes(animate-tanuki-right-cheek) {
40% { 40% {
fill: $tanuki-yellow; fill: $tanuki-yellow;
} }
......
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
} }
.markdown-area { .markdown-area {
@include border-radius(0); border-radius: 0;
background: #fff; background: #fff;
border: 1px solid #ddd; border: 1px solid #ddd;
min-height: 140px; min-height: 140px;
......
/**
* Generic mixins
*/
@mixin box-shadow($shadow) {
box-shadow: $shadow;
}
@mixin border-radius($radius) {
border-radius: $radius;
}
/** /**
* Prefilled mixins * Prefilled mixins
* Mixins with fixed values * Mixins with fixed values
...@@ -95,3 +84,10 @@ ...@@ -95,3 +84,10 @@
@content; @content;
} }
} }
@mixin include-keyframes($animation-name) {
@include webkit-prefix(animation-name, $animation-name);
@include keyframes($animation-name) {
@content;
}
}
...@@ -133,5 +133,5 @@ ...@@ -133,5 +133,5 @@
font-size: 20px; font-size: 20px;
color: #777; color: #777;
z-index: 100; z-index: 100;
@include box-shadow(0 1px 2px #ddd); box-shadow: 0 1px 2px #ddd;
} }
...@@ -46,8 +46,8 @@ ...@@ -46,8 +46,8 @@
} }
.select2-drop { .select2-drop {
@include box-shadow(rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0); box-shadow: rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0;
@include border-radius ($border-radius-default); border-radius: $border-radius-default;
border: none; border: none;
min-width: 175px; min-width: 175px;
} }
...@@ -72,7 +72,7 @@ ...@@ -72,7 +72,7 @@
.select2-container-active { .select2-container-active {
.select2-choice, .select2-choices { .select2-choice, .select2-choices {
@include box-shadow(none); box-shadow: none;
} }
} }
...@@ -82,13 +82,13 @@ ...@@ -82,13 +82,13 @@
outline: 0; outline: 0;
background-image: none; background-image: none;
background-color: $white-dark; background-color: $white-dark;
@include box-shadow($gl-btn-active-gradient); box-shadow: $gl-btn-active-gradient;
} }
} }
.select2-container-multi { .select2-container-multi {
.select2-choices { .select2-choices {
@include border-radius($border-radius-default); border-radius: $border-radius-default;
border-color: $input-border; border-color: $input-border;
background: none; background: none;
...@@ -123,7 +123,7 @@ ...@@ -123,7 +123,7 @@
&.select2-container-active .select2-choices, &.select2-container-active .select2-choices,
&.select2-dropdown-open .select2-choices { &.select2-dropdown-open .select2-choices {
border-color: $border-white-normal; border-color: $border-white-normal;
@include box-shadow($gl-btn-active-gradient); box-shadow: $gl-btn-active-gradient;
} }
} }
...@@ -157,7 +157,7 @@ ...@@ -157,7 +157,7 @@
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: right 0 bottom 6px; background-position: right 0 bottom 6px;
border: 1px solid $input-border; border: 1px solid $input-border;
@include border-radius($border-radius-default); border-radius: $border-radius-default;
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
&:focus { &:focus {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
&.page-sidebar-pinned { &.page-sidebar-pinned {
.sidebar-wrapper { .sidebar-wrapper {
@include box-shadow(none); box-shadow: none;
} }
} }
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
width: 0; width: 0;
overflow: hidden; overflow: hidden;
transition: width $sidebar-transition-duration; transition: width $sidebar-transition-duration;
@include box-shadow(2px 0 16px 0 $black-transparent); box-shadow: 2px 0 16px 0 $black-transparent;
} }
} }
...@@ -100,7 +100,7 @@ ...@@ -100,7 +100,7 @@
.count { .count {
float: right; float: right;
padding: 0 8px; padding: 0 8px;
@include border-radius(6px); border-radius: 6px;
} }
} }
......
...@@ -116,7 +116,7 @@ ...@@ -116,7 +116,7 @@
font-size: 13px; font-size: 13px;
line-height: 1.6em; line-height: 1.6em;
overflow-x: auto; overflow-x: auto;
@include border-radius(2px); border-radius: 2px;
} }
p > code { p > code {
......
...@@ -17,8 +17,10 @@ $white-normal: #ededed; ...@@ -17,8 +17,10 @@ $white-normal: #ededed;
$white-dark: #ececec; $white-dark: #ececec;
$gray-light: #fafafa; $gray-light: #fafafa;
$gray-lighter: #f9f9f9;
$gray-normal: #f5f5f5; $gray-normal: #f5f5f5;
$gray-dark: #ededed; $gray-dark: #ededed;
$gray-darker: #eee;
$gray-darkest: #c9c9c9; $gray-darkest: #c9c9c9;
$green-light: #38ae67; $green-light: #38ae67;
...@@ -33,6 +35,8 @@ $blue-medium-light: #3498cb; ...@@ -33,6 +35,8 @@ $blue-medium-light: #3498cb;
$blue-medium: #2f8ebf; $blue-medium: #2f8ebf;
$blue-medium-dark: #2d86b4; $blue-medium-dark: #2d86b4;
$blue-light-transparent: rgba(44, 159, 216, 0.05);
$orange-light: #fc8a51; $orange-light: #fc8a51;
$orange-normal: #e75e40; $orange-normal: #e75e40;
$orange-dark: #ce5237; $orange-dark: #ce5237;
...@@ -91,6 +95,7 @@ $table-text-gray: #8f8f8f; ...@@ -91,6 +95,7 @@ $table-text-gray: #8f8f8f;
$gl-font-size: 15px; $gl-font-size: 15px;
$gl-title-color: #333; $gl-title-color: #333;
$gl-text-color: #5c5c5c; $gl-text-color: #5c5c5c;
$gl-text-color-light: #8c8c8c;
$gl-text-green: #4a2; $gl-text-green: #4a2;
$gl-text-red: #d12f19; $gl-text-red: #d12f19;
$gl-text-orange: #d90; $gl-text-orange: #d90;
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
.bordered-box { .bordered-box {
border: 1px solid $border-color; border: 1px solid $border-color;
@include border-radius($border-radius-default); border-radius: $border-radius-default;
} }
......
.file-editor { .file-editor {
#editor { #editor {
border: none; border: none;
@include border-radius(0); border-radius: 0;
height: 500px; height: 500px;
margin: 0; margin: 0;
padding: 0; padding: 0;
......
...@@ -91,7 +91,7 @@ ...@@ -91,7 +91,7 @@
float: right; float: right;
border: 1px solid #eee; border: 1px solid #eee;
padding: 5px; padding: 5px;
@include border-radius(5px); border-radius: 5px;
background: $gray-light; background: $gray-light;
margin-left: 10px; margin-left: 10px;
top: -6px; top: -6px;
......
.suggest-colors { .suggest-colors {
margin-top: 5px; margin-top: 5px;
a { a {
@include border-radius(4px); border-radius: 4px;
width: 30px; width: 30px;
height: 30px; height: 30px;
display: inline-block; display: inline-block;
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
overflow: hidden; overflow: hidden;
a { a {
@include border-radius(0); border-radius: 0;
width: (100% / 7); width: (100% / 7);
margin-right: 0; margin-right: 0;
margin-bottom: -5px; margin-bottom: -5px;
......
...@@ -73,12 +73,12 @@ ...@@ -73,12 +73,12 @@
height: auto; height: auto;
&.top { &.top {
@include border-radius(5px 5px 0 0); border-radius: 5px 5px 0 0;
margin-bottom: 0; margin-bottom: 0;
} }
&.bottom { &.bottom {
@include border-radius(0 0 5px 5px); border-radius: 0 0 5px 5px;
border-top: 0; border-top: 0;
margin-bottom: 20px; margin-bottom: 20px;
} }
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
&.middle { &.middle {
border-top: 0; border-top: 0;
margin-bottom: 0; margin-bottom: 0;
@include border-radius(0); border-radius: 0;
} }
&:active, &:focus { &:active, &:focus {
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
background: $background-color; background: $background-color;
color: $gl-gray; color: $gl-gray;
border: 1px solid $border-color; border: 1px solid $border-color;
@include border-radius(2px); border-radius: 2px;
form { form {
margin-bottom: 0; margin-bottom: 0;
...@@ -121,6 +121,10 @@ ...@@ -121,6 +121,10 @@
color: #5c5d5e; color: #5c5d5e;
} }
.js-deployment-link {
display: inline-block;
}
.mr-widget-body { .mr-widget-body {
h4 { h4 {
font-weight: 600; font-weight: 600;
...@@ -204,6 +208,18 @@ ...@@ -204,6 +208,18 @@
word-break: break-all; word-break: break-all;
} }
.commits-empty {
text-align: center;
h4 {
padding-top: 20px;
padding-bottom: 10px;
}
svg {
width: 230px;
}
}
.mr-list { .mr-list {
.merge-request { .merge-request {
padding: 10px 15px; padding: 10px 15px;
...@@ -389,8 +405,12 @@ ...@@ -389,8 +405,12 @@
padding: 16px; padding: 16px;
} }
.content-block {
border-top: 1px solid $border-color;
padding: $gl-padding-top $gl-padding;
}
.comments-disabled-notif { .comments-disabled-notif {
padding: 10px 16px;
.btn { .btn {
margin-left: 5px; margin-left: 5px;
} }
...@@ -401,10 +421,6 @@ ...@@ -401,10 +421,6 @@
margin: 0 7px; margin: 0 7px;
} }
.comments-disabled-notif {
border-top: 1px solid $border-color;
}
.dropdown-title { .dropdown-title {
color: $gl-text-color; color: $gl-text-color;
} }
......
...@@ -334,7 +334,7 @@ ul.notes { ...@@ -334,7 +334,7 @@ ul.notes {
.add-diff-note { .add-diff-note {
margin-top: -4px; margin-top: -4px;
@include border-radius(40px); border-radius: 40px;
background: #fff; background: #fff;
padding: 4px; padding: 4px;
font-size: 16px; font-size: 16px;
......
...@@ -303,16 +303,41 @@ ...@@ -303,16 +303,41 @@
.stage-column { .stage-column {
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
margin-right: 65px;
&:not(:last-child) {
margin-right: 44px;
}
&.left-margin {
&:not(:first-child) {
margin-left: 44px;
.left-connector {
&::before {
content: '';
position: absolute;
top: 48%;
left: -48px;
border-top: 2px solid $border-color;
width: 48px;
height: 1px;
}
}
}
}
&.no-margin {
margin: 0;
}
li { li {
list-style: none; list-style: none;
} }
.stage-name { .stage-name {
margin-bottom: 15px; margin: 0 0 15px 10px;
font-weight: bold; font-weight: bold;
width: 150px; width: 176px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
...@@ -321,17 +346,23 @@ ...@@ -321,17 +346,23 @@
.build { .build {
border: 1px solid $border-color; border: 1px solid $border-color;
position: relative; position: relative;
padding: 6px 10px; padding: 7px 10px 8px;
border-radius: 30px; border-radius: 30px;
width: 150px; width: 186px;
margin-bottom: 10px; margin-bottom: 10px;
&:hover {
background-color: $gray-lighter;
.dropdown-menu-toggle {
background-color: transparent;
}
}
&.playable { &.playable {
background-color: $gray-light;
svg { svg {
height: 12px; height: 13px;
width: 12px; width: 20px;
position: relative; position: relative;
top: 1px; top: 1px;
...@@ -342,10 +373,20 @@ ...@@ -342,10 +373,20 @@
} }
.build-content { .build-content {
width: 130px; display: -ms-flexbox;
display: -webkit-flex;
display: flex;
width: 164px;
.ci-status-icon {
svg {
height: 20px;
width: 20px;
}
}
.ci-status-text { .ci-status-text {
width: 110px; width: 135px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
...@@ -356,44 +397,56 @@ ...@@ -356,44 +397,56 @@
} }
a { a {
color: $layout-link-gray; color: $gl-text-color-light;
text-decoration: none; text-decoration: none;
&:hover {
.ci-status-text {
text-decoration: underline;
}
}
} }
.dropdown-menu-toggle { .dropdown-menu-toggle {
border: none; border: none;
width: auto; width: auto;
padding: 0; padding: 0;
color: $layout-link-gray; color: $gl-text-color-light;
flex-grow: 1;
.ci-status-text { .ci-status-text {
width: 80px; max-width: 112px;
width: auto;
} }
} }
.grouped-pipeline-dropdown { .grouped-pipeline-dropdown {
padding: 8px 0; padding: 8px 0;
width: 200px; width: 186px;
left: auto; left: auto;
right: -214px; right: -197px;
top: -9px; top: -9px;
ul {
max-height: 245px; max-height: 245px;
overflow-y: scroll; overflow: auto;
}
a {
color: $gl-text-color;
padding: 7px 8px 8px;
&:hover {
background-color: $blue-light-transparent;
border-radius: 3px;
a:hover {
.ci-status-text { .ci-status-text {
text-decoration: none; text-decoration: none;
} }
} }
}
svg {
width: 14px;
height: 14px;
}
.ci-status-text { .ci-status-text {
width: 145px; width: 112px;
} }
.arrow { .arrow {
...@@ -426,9 +479,10 @@ ...@@ -426,9 +479,10 @@
} }
.badge { .badge {
background-color: $gray-dark; background-color: $gray-darker;
color: $layout-link-gray; color: $gl-text-color-light;
font-weight: normal; font-weight: normal;
margin-left: $btn-xs-side-margin;
} }
} }
...@@ -442,10 +496,10 @@ ...@@ -442,10 +496,10 @@
&::after { &::after {
content: ''; content: '';
position: absolute; position: absolute;
top: 50%; top: 48%;
right: -69px; right: -48px;
border-top: 2px solid $border-color; border-top: 2px solid $border-color;
width: 69px; width: 48px;
height: 1px; height: 1px;
} }
} }
...@@ -454,25 +508,25 @@ ...@@ -454,25 +508,25 @@
&:not(:first-child) { &:not(:first-child) {
&::after, &::before { &::after, &::before {
content: ''; content: '';
top: -47px; top: -49px;
position: absolute; position: absolute;
border-bottom: 2px solid $border-color; border-bottom: 2px solid $border-color;
width: 20px; width: 25px;
height: 65px; height: 69px;
} }
// Right connecting curves // Right connecting curves
&::after { &::after {
right: -20px; right: -25px;
border-right: 2px solid $border-color; border-right: 2px solid $border-color;
border-radius: 0 0 15px; border-radius: 0 0 20px;
} }
// Left connecting curves // Left connecting curves
&::before { &::before {
left: -20px; left: -25px;
border-left: 2px solid $border-color; border-left: 2px solid $border-color;
border-radius: 0 0 0 15px; border-radius: 0 0 0 20px;
} }
} }
...@@ -480,7 +534,7 @@ ...@@ -480,7 +534,7 @@
&:nth-child(2) { &:nth-child(2) {
&::after, &::before { &::after, &::before {
height: 29px; height: 29px;
top: -10px; top: -9px;
} }
.curve { .curve {
display: block; display: block;
...@@ -538,20 +592,20 @@ ...@@ -538,20 +592,20 @@
width: 21px; width: 21px;
height: 25px; height: 25px;
position: absolute; position: absolute;
top: -29px; top: -32px;
border-top: 2px solid $border-color; border-top: 2px solid $border-color;
} }
&::after { &::after {
left: -39px; left: -44px;
border-right: 2px solid $border-color; border-right: 2px solid $border-color;
border-radius: 0 15px; border-radius: 0 20px;
} }
&::before { &::before {
right: -39px; right: -44px;
border-left: 2px solid $border-color; border-left: 2px solid $border-color;
border-radius: 15px 0 0; border-radius: 20px 0 0;
} }
} }
} }
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
text-align: center; text-align: center;
.preview { .preview {
@include border-radius(4px); border-radius: 4px;
height: 80px; height: 80px;
margin-bottom: 10px; margin-bottom: 10px;
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
width: 160px; width: 160px;
img { img {
@include border-radius(4px); border-radius: 4px;
max-width: 100%; max-width: 100%;
} }
......
...@@ -354,7 +354,7 @@ a.deploy-project-label { ...@@ -354,7 +354,7 @@ a.deploy-project-label {
justify-content: flex-start; justify-content: flex-start;
.fork-thumbnail { .fork-thumbnail {
@include border-radius($border-radius-base); border-radius: $border-radius-base;
background-color: $white-light; background-color: $white-light;
border: 1px solid $border-white-light; border: 1px solid $border-white-light;
height: 202px; height: 202px;
...@@ -371,7 +371,7 @@ a.deploy-project-label { ...@@ -371,7 +371,7 @@ a.deploy-project-label {
background-color: $gray-light; background-color: $gray-light;
border: 1px solid $gray-dark; border: 1px solid $gray-dark;
margin: 0 auto; margin: 0 auto;
@include border-radius(50%); border-radius: 50%;
i { i {
font-size: 100px; font-size: 100px;
color: $gray-dark; color: $gray-dark;
...@@ -390,7 +390,7 @@ a.deploy-project-label { ...@@ -390,7 +390,7 @@ a.deploy-project-label {
} }
img { img {
@include border-radius(50%); border-radius: 50%;
max-width: 100px; max-width: 100px;
} }
} }
...@@ -496,7 +496,7 @@ pre.light-well { ...@@ -496,7 +496,7 @@ pre.light-well {
} }
.light-well { .light-well {
@include border-radius (2px); border-radius: 2px;
color: #5b6169; color: #5b6169;
font-size: 13px; font-size: 13px;
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
margin-right: 10px; margin-right: 10px;
border: 1px solid #eee; border: 1px solid #eee;
white-space: nowrap; white-space: nowrap;
@include border-radius(4px); border-radius: 4px;
&:hover { &:hover {
text-decoration: none; text-decoration: none;
......
module Ci
class ApplicationController < ::ApplicationController
def self.railtie_helpers_paths
"app/helpers/ci"
end
end
end
module Ci module Ci
class LintsController < ApplicationController class LintsController < ::ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
def show def show
......
module Ci module Ci
class ProjectsController < Ci::ApplicationController class ProjectsController < ::ApplicationController
before_action :project before_action :project
before_action :no_cache, only: [:badge] before_action :no_cache, only: [:badge]
before_action :authorize_read_project!, except: [:badge, :index] before_action :authorize_read_project!, except: [:badge, :index]
......
...@@ -21,8 +21,7 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -21,8 +21,7 @@ class Explore::ProjectsController < Explore::ApplicationController
end end
def trending def trending
@projects = TrendingProjectsFinder.new.execute @projects = filter_projects(Project.trending)
@projects = filter_projects(@projects)
@projects = @projects.page(params[:page]) @projects = @projects.page(params[:page])
respond_to do |format| respond_to do |format|
......
class NamespacesController < ApplicationController
skip_before_action :authenticate_user!
def show
namespace = Namespace.find_by(path: params[:id])
if namespace
if namespace.is_a?(Group)
group = namespace
else
user = namespace.owner
end
end
if user
redirect_to user_path(user)
elsif group && can?(current_user, :read_group, group)
redirect_to group_path(group)
elsif current_user.nil?
authenticate_user!
else
render_404
end
end
end
class Projects::BoardListsController < Projects::ApplicationController
respond_to :json
before_action :authorize_admin_list!
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
def create
list = Boards::Lists::CreateService.new(project, current_user, list_params).execute
if list.valid?
render json: list.as_json(only: [:id, :list_type, :position], methods: [:title], include: { label: { only: [:id, :title, :description, :color, :priority] } })
else
render json: list.errors, status: :unprocessable_entity
end
end
def update
service = Boards::Lists::MoveService.new(project, current_user, move_params)
if service.execute
head :ok
else
head :unprocessable_entity
end
end
def destroy
service = Boards::Lists::DestroyService.new(project, current_user, params)
if service.execute
head :ok
else
head :unprocessable_entity
end
end
def generate
service = Boards::Lists::GenerateService.new(project, current_user)
if service.execute
render json: project.board.lists.label.as_json(only: [:id, :list_type, :position], methods: [:title], include: { label: { only: [:id, :title, :description, :color, :priority] } })
else
head :unprocessable_entity
end
end
private
def authorize_admin_list!
return render_403 unless can?(current_user, :admin_list, project)
end
def list_params
params.require(:list).permit(:label_id)
end
def move_params
params.require(:list).permit(:position).merge(id: params[:id])
end
def record_not_found(exception)
render json: { error: exception.message }, status: :not_found
end
end
...@@ -16,9 +16,8 @@ module Projects ...@@ -16,9 +16,8 @@ module Projects
end end
def create def create
list = project.board.lists.find(params[:list_id])
service = ::Boards::Issues::CreateService.new(project, current_user, issue_params) service = ::Boards::Issues::CreateService.new(project, current_user, issue_params)
issue = service.execute(list) issue = service.execute
if issue.valid? if issue.valid?
render json: serialize_as_json(issue) render json: serialize_as_json(issue)
...@@ -60,15 +59,15 @@ module Projects ...@@ -60,15 +59,15 @@ module Projects
end end
def filter_params def filter_params
params.merge(id: params[:list_id]) params.merge(board_id: params[:board_id], id: params[:list_id])
end end
def move_params def move_params
params.permit(:id, :from_list_id, :to_list_id) params.permit(:board_id, :id, :from_list_id, :to_list_id)
end end
def issue_params def issue_params
params.require(:issue).permit(:title).merge(request: request) params.require(:issue).permit(:title).merge(board_id: params[:board_id], list_id: params[:list_id], request: request)
end end
def serialize_as_json(resource) def serialize_as_json(resource)
......
...@@ -5,11 +5,11 @@ module Projects ...@@ -5,11 +5,11 @@ module Projects
before_action :authorize_read_list!, only: [:index] before_action :authorize_read_list!, only: [:index]
def index def index
render json: serialize_as_json(project.board.lists) render json: serialize_as_json(board.lists)
end end
def create def create
list = ::Boards::Lists::CreateService.new(project, current_user, list_params).execute list = ::Boards::Lists::CreateService.new(project, current_user, list_params).execute(board)
if list.valid? if list.valid?
render json: serialize_as_json(list) render json: serialize_as_json(list)
...@@ -19,7 +19,7 @@ module Projects ...@@ -19,7 +19,7 @@ module Projects
end end
def update def update
list = project.board.lists.movable.find(params[:id]) list = board.lists.movable.find(params[:id])
service = ::Boards::Lists::MoveService.new(project, current_user, move_params) service = ::Boards::Lists::MoveService.new(project, current_user, move_params)
if service.execute(list) if service.execute(list)
...@@ -30,8 +30,8 @@ module Projects ...@@ -30,8 +30,8 @@ module Projects
end end
def destroy def destroy
list = project.board.lists.destroyable.find(params[:id]) list = board.lists.destroyable.find(params[:id])
service = ::Boards::Lists::DestroyService.new(project, current_user, params) service = ::Boards::Lists::DestroyService.new(project, current_user)
if service.execute(list) if service.execute(list)
head :ok head :ok
...@@ -43,8 +43,8 @@ module Projects ...@@ -43,8 +43,8 @@ module Projects
def generate def generate
service = ::Boards::Lists::GenerateService.new(project, current_user) service = ::Boards::Lists::GenerateService.new(project, current_user)
if service.execute if service.execute(board)
render json: serialize_as_json(project.board.lists.movable) render json: serialize_as_json(board.lists.movable)
else else
head :unprocessable_entity head :unprocessable_entity
end end
...@@ -60,6 +60,10 @@ module Projects ...@@ -60,6 +60,10 @@ module Projects
return render_403 unless can?(current_user, :read_list, project) return render_403 unless can?(current_user, :read_list, project)
end end
def board
@board ||= project.boards.find(params[:board_id])
end
def list_params def list_params
params.require(:list).permit(:label_id) params.require(:list).permit(:label_id)
end end
......
class Projects::BoardsController < Projects::ApplicationController class Projects::BoardsController < Projects::ApplicationController
include IssuableCollections include IssuableCollections
respond_to :html before_action :authorize_read_board!, only: [:index, :show]
before_action :authorize_read_board!, only: [:show] def index
@boards = ::Boards::ListService.new(project, current_user).execute
respond_to do |format|
format.html
format.json do
render json: serialize_as_json(@boards)
end
end
end
def show def show
::Boards::CreateService.new(project, current_user).execute @board = project.boards.find(params[:id])
respond_to do |format|
format.html
format.json do
render json: serialize_as_json(@board)
end
end
end end
private private
...@@ -14,4 +30,8 @@ class Projects::BoardsController < Projects::ApplicationController ...@@ -14,4 +30,8 @@ class Projects::BoardsController < Projects::ApplicationController
def authorize_read_board! def authorize_read_board!
return access_denied! unless can?(current_user, :read_board, project) return access_denied! unless can?(current_user, :read_board, project)
end end
def serialize_as_json(resource)
resource.as_json(only: [:id])
end
end end
...@@ -38,12 +38,12 @@ class Projects::GraphsController < Projects::ApplicationController ...@@ -38,12 +38,12 @@ class Projects::GraphsController < Projects::ApplicationController
@languages = @languages.map do |language| @languages = @languages.map do |language|
name, share = language name, share = language
color = Digest::SHA256.hexdigest(name)[0...6] color = Linguist::Language[name].color || "##{Digest::SHA256.hexdigest(name)[0...6]}"
{ {
value: (share.to_f * 100 / total).round(2), value: (share.to_f * 100 / total).round(2),
label: name, label: name,
color: "##{color}", color: color,
highlight: "##{color}" highlight: color
} }
end end
......
...@@ -10,7 +10,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -10,7 +10,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :module_enabled before_action :module_enabled
before_action :merge_request, only: [ before_action :merge_request, only: [
:edit, :update, :show, :diffs, :commits, :conflicts, :builds, :pipelines, :merge, :merge_check, :edit, :update, :show, :diffs, :commits, :conflicts, :builds, :pipelines, :merge, :merge_check,
:ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts :ci_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues
] ]
before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds, :pipelines] before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds, :pipelines]
before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :builds, :pipelines] before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :builds, :pipelines]
...@@ -31,6 +31,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -31,6 +31,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
# Allow modify merge_request # Allow modify merge_request
before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :remove_wip, :sort] before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :remove_wip, :sort]
before_action :authenticate_user!, only: [:assign_related_issues]
before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :resolve_conflicts] before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :resolve_conflicts]
def index def index
...@@ -354,6 +356,25 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -354,6 +356,25 @@ class Projects::MergeRequestsController < Projects::ApplicationController
render layout: false render layout: false
end end
def assign_related_issues
result = MergeRequests::AssignIssuesService.new(project, current_user, merge_request: @merge_request).execute
respond_to do |format|
format.html do
case result[:count]
when 0
flash[:error] = "Failed to assign you issues related to the merge request"
when 1
flash[:notice] = "1 issue has been assigned to you"
else
flash[:notice] = "#{result[:count]} issues have been assigned to you"
end
redirect_to(merge_request_path(@merge_request))
end
end
end
def ci_status def ci_status
pipeline = @merge_request.pipeline pipeline = @merge_request.pipeline
if pipeline if pipeline
...@@ -382,6 +403,30 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -382,6 +403,30 @@ class Projects::MergeRequestsController < Projects::ApplicationController
render json: response render json: response
end end
def ci_environments_status
environments =
begin
@merge_request.environments.map do |environment|
next unless can?(current_user, :read_environment, environment)
project = environment.project
deployment = environment.first_deployment_for(@merge_request.diff_head_commit)
{
id: environment.id,
name: environment.name,
url: namespace_project_environment_path(project.namespace, project, environment),
external_url: environment.external_url,
external_url_formatted: environment.formatted_external_url,
deployed_at: deployment.try(:created_at),
deployed_at_formatted: deployment.try(:formatted_deployment_time)
}
end.compact
end
render json: environments
end
protected protected
def selected_target_project def selected_target_project
......
...@@ -20,6 +20,8 @@ class Projects::TagsController < Projects::ApplicationController ...@@ -20,6 +20,8 @@ class Projects::TagsController < Projects::ApplicationController
def show def show
@tag = @repository.find_tag(params[:id]) @tag = @repository.find_tag(params[:id])
return render_404 unless @tag
@release = @project.releases.find_or_initialize_by(tag: @tag.name) @release = @project.releases.find_or_initialize_by(tag: @tag.name)
@commit = @repository.commit(@tag.target) @commit = @repository.commit(@tag.target)
end end
......
class SnippetsController < ApplicationController class SnippetsController < ApplicationController
include ToggleAwardEmoji include ToggleAwardEmoji
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download]
# Allow read snippet # Allow read snippet
before_action :authorize_read_snippet!, only: [:show, :raw] before_action :authorize_read_snippet!, only: [:show, :raw, :download]
# Allow modify snippet # Allow modify snippet
before_action :authorize_update_snippet!, only: [:edit, :update] before_action :authorize_update_snippet!, only: [:edit, :update]
...@@ -12,7 +12,7 @@ class SnippetsController < ApplicationController ...@@ -12,7 +12,7 @@ class SnippetsController < ApplicationController
# Allow destroy snippet # Allow destroy snippet
before_action :authorize_admin_snippet!, only: [:destroy] before_action :authorize_admin_snippet!, only: [:destroy]
skip_before_action :authenticate_user!, only: [:index, :show, :raw] skip_before_action :authenticate_user!, only: [:index, :show, :raw, :download]
layout 'snippets' layout 'snippets'
respond_to :html respond_to :html
...@@ -75,6 +75,14 @@ class SnippetsController < ApplicationController ...@@ -75,6 +75,14 @@ class SnippetsController < ApplicationController
) )
end end
def download
send_data(
@snippet.content,
type: 'text/plain; charset=utf-8',
filename: @snippet.sanitized_file_name
)
end
protected protected
def snippet def snippet
......
# Finder for retrieving public trending projects in a given time range.
class TrendingProjectsFinder
# current_user - The currently logged in User, if any.
# last_months - The number of months to limit the trending data to.
def execute(months_limit = 1)
Rails.cache.fetch(cache_key_for(months_limit), expires_in: 1.day) do
Project.public_only.trending(months_limit.months.ago)
end
end
private
def cache_key_for(months)
"trending_projects/#{months}"
end
end
module BoardsHelper
def board_data
board = @board || @boards.first
{
endpoint: namespace_project_boards_path(@project.namespace, @project),
board_id: board.id,
disabled: !can?(current_user, :admin_list, @project),
issue_link_base: namespace_project_issues_path(@project.namespace, @project)
}
end
end
...@@ -15,13 +15,14 @@ module ButtonHelper ...@@ -15,13 +15,14 @@ module ButtonHelper
# #
# See http://clipboardjs.com/#usage # See http://clipboardjs.com/#usage
def clipboard_button(data = {}) def clipboard_button(data = {})
css_class = data[:class] || 'btn-clipboard btn-transparent'
data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
content_tag :button, content_tag :button,
icon('clipboard'), icon('clipboard'),
class: "btn btn-clipboard", class: "btn #{css_class}",
data: data, data: data,
type: :button, type: :button,
title: "Copy to Clipboard" title: 'Copy to Clipboard'
end end
def http_clone_button(project, placement = 'right', append_link: true) def http_clone_button(project, placement = 'right', append_link: true)
......
...@@ -72,6 +72,19 @@ module MergeRequestsHelper ...@@ -72,6 +72,19 @@ module MergeRequestsHelper
) )
end end
def mr_assign_issues_link
issues = MergeRequests::AssignIssuesService.new(@project,
current_user,
merge_request: @merge_request,
closes_issues: mr_closes_issues
).assignable_issues
path = assign_related_issues_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
if issues.present?
pluralize_this_issue = issues.count > 1 ? "these issues" : "this issue"
link_to "Assign yourself to #{pluralize_this_issue}", path, method: :post
end
end
def source_branch_with_namespace(merge_request) def source_branch_with_namespace(merge_request)
branch = link_to(merge_request.source_branch, namespace_project_commits_path(merge_request.source_project.namespace, merge_request.source_project, merge_request.source_branch)) branch = link_to(merge_request.source_branch, namespace_project_commits_path(merge_request.source_project.namespace, merge_request.source_project, merge_request.source_branch))
...@@ -110,4 +123,8 @@ module MergeRequestsHelper ...@@ -110,4 +123,8 @@ module MergeRequestsHelper
def version_index(merge_request_diff) def version_index(merge_request_diff)
@merge_request_diffs.size - @merge_request_diffs.index(merge_request_diff) @merge_request_diffs.size - @merge_request_diffs.index(merge_request_diff)
end end
def different_base?(version1, version2)
version1 && version2 && version1.base_commit_sha != version2.base_commit_sha
end
end end
...@@ -3,6 +3,7 @@ module Ci ...@@ -3,6 +3,7 @@ module Ci
extend Ci::Model extend Ci::Model
include HasStatus include HasStatus
include Importable include Importable
include AfterCommitQueue
self.table_name = 'ci_commits' self.table_name = 'ci_commits'
...@@ -56,6 +57,10 @@ module Ci ...@@ -56,6 +57,10 @@ module Ci
pipeline.finished_at = Time.now pipeline.finished_at = Time.now
end end
before_transition do |pipeline|
pipeline.update_duration
end
after_transition [:created, :pending] => :running do |pipeline| after_transition [:created, :pending] => :running do |pipeline|
MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)). MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)).
update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: nil) update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: nil)
...@@ -66,8 +71,8 @@ module Ci ...@@ -66,8 +71,8 @@ module Ci
update_all(latest_build_finished_at: pipeline.finished_at) update_all(latest_build_finished_at: pipeline.finished_at)
end end
before_transition do |pipeline| after_transition [:created, :pending, :running] => :success do |pipeline|
pipeline.update_duration pipeline.run_after_commit { PipelineSuccessWorker.perform_async(id) }
end end
after_transition do |pipeline, transition| after_transition do |pipeline, transition|
...@@ -292,9 +297,9 @@ module Ci ...@@ -292,9 +297,9 @@ module Ci
# Merge requests for which the current pipeline is running against # Merge requests for which the current pipeline is running against
# the merge request's latest commit. # the merge request's latest commit.
def merge_requests def merge_requests
@merge_requests ||= @merge_requests ||= project.merge_requests
project.merge_requests.where(source_branch: ref). .where(source_branch: self.ref)
select { |merge_request| merge_request.pipeline.try(:id) == id } .select { |merge_request| merge_request.pipeline.try(:id) == self.id }
end end
def merge_requests_with_active_first def merge_requests_with_active_first
......
class CommitStatus < ActiveRecord::Base class CommitStatus < ActiveRecord::Base
include HasStatus include HasStatus
include Importable include Importable
include AfterCommitQueue
self.table_name = 'ci_builds' self.table_name = 'ci_builds'
...@@ -85,25 +86,24 @@ class CommitStatus < ActiveRecord::Base ...@@ -85,25 +86,24 @@ class CommitStatus < ActiveRecord::Base
end end
after_transition do |commit_status, transition| after_transition do |commit_status, transition|
commit_status.pipeline.try do |pipeline| next if transition.loopback?
break if transition.loopback?
if commit_status.complete? commit_status.run_after_commit do
ProcessPipelineWorker.perform_async(pipeline.id) pipeline.try do |pipeline|
if complete?
PipelineProcessWorker.perform_async(pipeline.id)
else
PipelineUpdateWorker.perform_async(pipeline.id)
end end
UpdatePipelineWorker.perform_async(pipeline.id)
end end
true
end end
after_transition [:created, :pending, :running] => :success do |commit_status|
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status)
end end
after_transition any => :failed do |commit_status| after_transition any => :failed do |commit_status|
MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.pipeline.project, nil).execute(commit_status) commit_status.run_after_commit do
MergeRequests::AddTodoWhenBuildFailsService
.new(pipeline.project, nil).execute(self)
end
end end
end end
......
...@@ -11,9 +11,10 @@ class Compare ...@@ -11,9 +11,10 @@ class Compare
end end
end end
def initialize(compare, project) def initialize(compare, project, straight: false)
@compare = compare @compare = compare
@project = project @project = project
@straight = straight
end end
def commits def commits
...@@ -45,6 +46,18 @@ class Compare ...@@ -45,6 +46,18 @@ class Compare
end end
end end
def start_commit_sha
start_commit.try(:sha)
end
def base_commit_sha
base_commit.try(:sha)
end
def head_commit_sha
commit.try(:sha)
end
def raw_diffs(*args) def raw_diffs(*args)
@compare.diffs(*args) @compare.diffs(*args)
end end
...@@ -58,9 +71,9 @@ class Compare ...@@ -58,9 +71,9 @@ class Compare
def diff_refs def diff_refs
Gitlab::Diff::DiffRefs.new( Gitlab::Diff::DiffRefs.new(
base_sha: base_commit.try(:sha), base_sha: @straight ? start_commit_sha : base_commit_sha,
start_sha: start_commit.try(:sha), start_sha: start_commit_sha,
head_sha: commit.try(:sha) head_sha: head_commit_sha
) )
end end
end end
...@@ -2,6 +2,8 @@ class CycleAnalytics ...@@ -2,6 +2,8 @@ class CycleAnalytics
include Gitlab::Database::Median include Gitlab::Database::Median
include Gitlab::Database::DateTime include Gitlab::Database::DateTime
DEPLOYMENT_METRIC_STAGES = %i[production staging]
def initialize(project, from:) def initialize(project, from:)
@project = project @project = project
@from = from @from = from
...@@ -66,7 +68,7 @@ class CycleAnalytics ...@@ -66,7 +68,7 @@ class CycleAnalytics
# cycle analytics stage. # cycle analytics stage.
interval_query = Arel::Nodes::As.new( interval_query = Arel::Nodes::As.new(
cte_table, cte_table,
subtract_datetimes(base_query, end_time_attrs, start_time_attrs, name.to_s)) subtract_datetimes(base_query_for(name), end_time_attrs, start_time_attrs, name.to_s))
median_datetime(cte_table, interval_query, name) median_datetime(cte_table, interval_query, name)
end end
...@@ -75,7 +77,7 @@ class CycleAnalytics ...@@ -75,7 +77,7 @@ class CycleAnalytics
# closes the given issue) with issue and merge request metrics included. The metrics # closes the given issue) with issue and merge request metrics included. The metrics
# are loaded with an inner join, so issues / merge requests without metrics are # are loaded with an inner join, so issues / merge requests without metrics are
# automatically excluded. # automatically excluded.
def base_query def base_query_for(name)
arel_table = MergeRequestsClosingIssues.arel_table arel_table = MergeRequestsClosingIssues.arel_table
# Load issues # Load issues
...@@ -91,7 +93,11 @@ class CycleAnalytics ...@@ -91,7 +93,11 @@ class CycleAnalytics
join(MergeRequest::Metrics.arel_table). join(MergeRequest::Metrics.arel_table).
on(MergeRequest.arel_table[:id].eq(MergeRequest::Metrics.arel_table[:merge_request_id])) on(MergeRequest.arel_table[:id].eq(MergeRequest::Metrics.arel_table[:merge_request_id]))
if DEPLOYMENT_METRIC_STAGES.include?(name)
# Limit to merge requests that have been deployed to production after `@from` # Limit to merge requests that have been deployed to production after `@from`
query.where(MergeRequest::Metrics.arel_table[:first_deployed_to_production_at].gteq(@from)) query.where(MergeRequest::Metrics.arel_table[:first_deployed_to_production_at].gteq(@from))
end end
query
end
end end
...@@ -40,7 +40,14 @@ class Deployment < ActiveRecord::Base ...@@ -40,7 +40,14 @@ class Deployment < ActiveRecord::Base
def includes_commit?(commit) def includes_commit?(commit)
return false unless commit return false unless commit
# Before 8.10, deployments didn't have keep-around refs. Any deployment
# created before then could have a `sha` referring to a commit that no
# longer exists in the repository, so just ignore those.
begin
project.repository.is_ancestor?(commit.id, sha) project.repository.is_ancestor?(commit.id, sha)
rescue Rugged::OdbError
false
end
end end
def update_merge_request_metrics! def update_merge_request_metrics!
...@@ -77,6 +84,10 @@ class Deployment < ActiveRecord::Base ...@@ -77,6 +84,10 @@ class Deployment < ActiveRecord::Base
take take
end end
def formatted_deployment_time
created_at.to_time.in_time_zone.to_s(:medium)
end
private private
def ref_path def ref_path
......
...@@ -48,7 +48,22 @@ class Environment < ActiveRecord::Base ...@@ -48,7 +48,22 @@ class Environment < ActiveRecord::Base
self.name == "production" self.name == "production"
end end
def first_deployment_for(commit)
ref = project.repository.ref_name_for_sha(ref_path, commit.sha)
return nil unless ref
deployment_id = ref.split('/').last
deployments.find(deployment_id)
end
def ref_path def ref_path
"refs/environments/#{Shellwords.shellescape(name)}" "refs/environments/#{Shellwords.shellescape(name)}"
end end
def formatted_external_url
return nil unless external_url
external_url.gsub(/\A.*?:\/\//, '')
end
end end
...@@ -68,8 +68,10 @@ class Event < ActiveRecord::Base ...@@ -68,8 +68,10 @@ class Event < ActiveRecord::Base
true true
elsif issue? || issue_note? elsif issue? || issue_note?
Ability.allowed?(user, :read_issue, note? ? note_target : target) Ability.allowed?(user, :read_issue, note? ? note_target : target)
elsif merge_request? || merge_request_note?
Ability.allowed?(user, :read_merge_request, note? ? note_target : target)
else else
((merge_request? || note?) && target.present?) || milestone? milestone?
end end
end end
...@@ -280,6 +282,10 @@ class Event < ActiveRecord::Base ...@@ -280,6 +282,10 @@ class Event < ActiveRecord::Base
note? && target && target.for_issue? note? && target && target.for_issue?
end end
def merge_request_note?
note? && target && target.for_merge_request?
end
def project_snippet_note? def project_snippet_note?
target.for_snippet? target.for_snippet?
end end
......
...@@ -692,6 +692,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -692,6 +692,8 @@ class MergeRequest < ActiveRecord::Base
def environments def environments
return [] unless diff_head_commit return [] unless diff_head_commit
@environments ||=
begin
environments = source_project.environments_for( environments = source_project.environments_for(
source_branch, diff_head_commit) source_branch, diff_head_commit)
environments += target_project.environments_for( environments += target_project.environments_for(
...@@ -699,6 +701,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -699,6 +701,7 @@ class MergeRequest < ActiveRecord::Base
environments.uniq environments.uniq
end end
end
def state_human_name def state_human_name
if merged? if merged?
......
...@@ -6,6 +6,9 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -6,6 +6,9 @@ class MergeRequestDiff < ActiveRecord::Base
# Prevent store of diff if commits amount more then 500 # Prevent store of diff if commits amount more then 500
COMMITS_SAFE_SIZE = 100 COMMITS_SAFE_SIZE = 100
# Valid types of serialized diffs allowed by Gitlab::Git::Diff
VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta]
belongs_to :merge_request belongs_to :merge_request
state_machine :state, initial: :empty do state_machine :state, initial: :empty do
...@@ -164,12 +167,24 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -164,12 +167,24 @@ class MergeRequestDiff < ActiveRecord::Base
self == merge_request.merge_request_diff self == merge_request.merge_request_diff
end end
def compare_with(sha) def compare_with(sha, straight: true)
CompareService.new.execute(project, head_commit_sha, project, sha) # When compare merge request versions we want diff A..B instead of A...B
# so we handle cases when user does squash and rebase of the commits between versions.
# For this reason we set straight to true by default.
CompareService.new.execute(project, head_commit_sha, project, sha, straight: straight)
end end
private private
# Old GitLab implementations may have generated diffs as ["--broken-diff"].
# Avoid an error 500 by ignoring bad elements. See:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/20776
def valid_raw_diff?(raw)
return false unless raw.respond_to?(:each)
raw.any? { |element| VALID_CLASSES.include?(element.class) }
end
def dump_commits(commits) def dump_commits(commits)
commits.map(&:to_hash) commits.map(&:to_hash)
end end
...@@ -200,7 +215,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -200,7 +215,7 @@ class MergeRequestDiff < ActiveRecord::Base
end end
def load_diffs(raw, options) def load_diffs(raw, options)
if raw.respond_to?(:each) if valid_raw_diff?(raw)
if paths = options[:paths] if paths = options[:paths]
raw = raw.select do |diff| raw = raw.select do |diff|
paths.include?(diff[:old_path]) || paths.include?(diff[:new_path]) paths.include?(diff[:old_path]) || paths.include?(diff[:new_path])
......
...@@ -62,14 +62,12 @@ class Namespace < ActiveRecord::Base ...@@ -62,14 +62,12 @@ class Namespace < ActiveRecord::Base
path = path.dup path = path.dup
# Get the email username by removing everything after an `@` sign. # Get the email username by removing everything after an `@` sign.
path.gsub!(/@.*\z/, "") path.gsub!(/@.*\z/, "")
# Usernames can't end in .git, so remove it.
path.gsub!(/\.git\z/, "")
# Remove dashes at the start of the username.
path.gsub!(/\A-+/, "")
# Remove periods at the end of the username.
path.gsub!(/\.+\z/, "")
# Remove everything that's not in the list of allowed characters. # Remove everything that's not in the list of allowed characters.
path.gsub!(/[^a-zA-Z0-9_\-\.]/, "") path.gsub!(/[^a-zA-Z0-9_\-\.]/, "")
# Remove trailing violations ('.atom', '.git', or '.')
path.gsub!(/(\.atom|\.git|\.)*\z/, "")
# Remove leading violations ('-')
path.gsub!(/\A\-+/, "")
# Users with the great usernames of "." or ".." would end up with a blank username. # Users with the great usernames of "." or ".." would end up with a blank username.
# Work around that by setting their username to "blank", followed by a counter. # Work around that by setting their username to "blank", followed by a counter.
......
...@@ -16,6 +16,9 @@ class Project < ActiveRecord::Base ...@@ -16,6 +16,9 @@ class Project < ActiveRecord::Base
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
class BoardLimitExceeded < StandardError; end
NUMBER_OF_PERMITTED_BOARDS = 1
UNKNOWN_IMPORT_URL = 'http://unknown.git' UNKNOWN_IMPORT_URL = 'http://unknown.git'
cache_markdown_field :description, pipeline: :description cache_markdown_field :description, pipeline: :description
...@@ -65,8 +68,7 @@ class Project < ActiveRecord::Base ...@@ -65,8 +68,7 @@ class Project < ActiveRecord::Base
belongs_to :namespace belongs_to :namespace
has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id'
has_many :boards, before_add: :validate_board_limit, dependent: :destroy
has_one :board, dependent: :destroy
# Project services # Project services
has_many :services has_many :services
...@@ -376,19 +378,9 @@ class Project < ActiveRecord::Base ...@@ -376,19 +378,9 @@ class Project < ActiveRecord::Base
%r{(?<project>#{name_pattern}/#{name_pattern})} %r{(?<project>#{name_pattern}/#{name_pattern})}
end end
def trending(since = 1.month.ago) def trending
# By counting in the JOIN we don't expose the GROUP BY to the outer query. joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id').
# This means that calls such as "any?" and "count" just return a number of reorder('trending_projects.id ASC')
# the total count, instead of the counts grouped per project as a Hash.
join_body = "INNER JOIN (
SELECT project_id, COUNT(*) AS amount
FROM notes
WHERE created_at >= #{sanitize(since)}
AND system IS FALSE
GROUP BY project_id
) join_note_counts ON projects.id = join_note_counts.project_id"
joins(join_body).reorder('join_note_counts.amount DESC')
end end
def cached_count def cached_count
...@@ -838,11 +830,6 @@ class Project < ActiveRecord::Base ...@@ -838,11 +830,6 @@ class Project < ActiveRecord::Base
end end
end end
def update_merge_requests(oldrev, newrev, ref, user)
MergeRequests::RefreshService.new(self, user).
execute(oldrev, newrev, ref)
end
def valid_repo? def valid_repo?
repository.exists? repository.exists?
rescue rescue
...@@ -1350,4 +1337,8 @@ class Project < ActiveRecord::Base ...@@ -1350,4 +1337,8 @@ class Project < ActiveRecord::Base
shared_projects.any? shared_projects.any?
end end
def validate_board_limit(board)
raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS
end
end end
...@@ -10,7 +10,7 @@ class ProjectGroupLink < ActiveRecord::Base ...@@ -10,7 +10,7 @@ class ProjectGroupLink < ActiveRecord::Base
belongs_to :group belongs_to :group
validates :project_id, presence: true validates :project_id, presence: true
validates :group_id, presence: true validates :group, presence: true
validates :group_id, uniqueness: { scope: [:project_id], message: "already shared with this group" } validates :group_id, uniqueness: { scope: [:project_id], message: "already shared with this group" }
validates :group_access, presence: true validates :group_access, presence: true
validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true
......
...@@ -111,8 +111,10 @@ class Repository ...@@ -111,8 +111,10 @@ class Repository
def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0) def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
ref ||= root_ref ref ||= root_ref
# Limited to 1000 commits for now, could be parameterized? args = %W(
args = %W(#{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset} --max-count #{limit} --grep=#{query}) #{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset}
--max-count #{limit} --grep=#{query} --regexp-ignore-case
)
args = args.concat(%W(-- #{path})) if path.present? args = args.concat(%W(-- #{path})) if path.present?
git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:chomp) git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:chomp)
...@@ -717,6 +719,14 @@ class Repository ...@@ -717,6 +719,14 @@ class Repository
end end
end end
def ref_name_for_sha(ref_path, sha)
args = %W(#{Gitlab.config.git.bin_path} for-each-ref --count=1 #{ref_path} --contains #{sha})
# Not found -> ["", 0]
# Found -> ["b8d95eb4969eefacb0a58f6a28f6803f8070e7b9 commit\trefs/environments/production/77\n", 0]
Gitlab::Popen.popen(args, path_to_repo).first.split.last
end
def refs_contains_sha(ref_type, sha) def refs_contains_sha(ref_type, sha)
args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha}) args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
names = Gitlab::Popen.popen(args, path_to_repo).first names = Gitlab::Popen.popen(args, path_to_repo).first
...@@ -1014,7 +1024,8 @@ class Repository ...@@ -1014,7 +1024,8 @@ class Repository
root_ref_commit = commit(root_ref) root_ref_commit = commit(root_ref)
if branch_commit if branch_commit
is_ancestor?(branch_commit.id, root_ref_commit.id) same_head = branch_commit.id == root_ref_commit.id
!same_head && is_ancestor?(branch_commit.id, root_ref_commit.id)
else else
nil nil
end end
......
class TrendingProject < ActiveRecord::Base
belongs_to :project
# The number of months to include in the trending calculation.
MONTHS_TO_INCLUDE = 1
# The maximum number of projects to include in the trending set.
PROJECTS_LIMIT = 100
# Populates the trending projects table with the current list of trending
# projects.
def self.refresh!
# The calculation **must** run in a transaction. If the removal of data and
# insertion of new data were to run separately a user might end up with an
# empty list of trending projects for a short period of time.
transaction do
delete_all
timestamp = connection.quote(MONTHS_TO_INCLUDE.months.ago)
connection.execute <<-EOF.strip_heredoc
INSERT INTO #{table_name} (project_id)
SELECT project_id
FROM notes
INNER JOIN projects ON projects.id = notes.project_id
WHERE notes.created_at >= #{timestamp}
AND notes.system IS FALSE
AND projects.visibility_level = #{Gitlab::VisibilityLevel::PUBLIC}
GROUP BY project_id
ORDER BY count(*) DESC
LIMIT #{PROJECTS_LIMIT};
EOF
end
end
end
...@@ -589,6 +589,11 @@ class User < ActiveRecord::Base ...@@ -589,6 +589,11 @@ class User < ActiveRecord::Base
end end
def set_projects_limit def set_projects_limit
# `User.select(:id)` raises
# `ActiveModel::MissingAttributeError: missing attribute: projects_limit`
# without this safeguard!
return unless self.has_attribute?(:projects_limit)
connection_default_value_defined = new_record? && !projects_limit_changed? connection_default_value_defined = new_record? && !projects_limit_changed?
return unless self.projects_limit.nil? || connection_default_value_defined return unless self.projects_limit.nil? || connection_default_value_defined
......
...@@ -40,7 +40,6 @@ class ProjectPolicy < BasePolicy ...@@ -40,7 +40,6 @@ class ProjectPolicy < BasePolicy
can! :read_milestone can! :read_milestone
can! :read_project_snippet can! :read_project_snippet
can! :read_project_member can! :read_project_member
can! :read_merge_request
can! :read_note can! :read_note
can! :create_project can! :create_project
can! :create_issue can! :create_issue
...@@ -63,6 +62,7 @@ class ProjectPolicy < BasePolicy ...@@ -63,6 +62,7 @@ class ProjectPolicy < BasePolicy
can! :read_pipeline can! :read_pipeline
can! :read_environment can! :read_environment
can! :read_deployment can! :read_deployment
can! :read_merge_request
end end
# Permissions given when an user is team member of a project # Permissions given when an user is team member of a project
...@@ -98,7 +98,6 @@ class ProjectPolicy < BasePolicy ...@@ -98,7 +98,6 @@ class ProjectPolicy < BasePolicy
can! :admin_milestone can! :admin_milestone
can! :admin_project_snippet can! :admin_project_snippet
can! :admin_project_member can! :admin_project_member
can! :admin_merge_request
can! :admin_note can! :admin_note
can! :admin_wiki can! :admin_wiki
can! :admin_project can! :admin_project
...@@ -118,6 +117,7 @@ class ProjectPolicy < BasePolicy ...@@ -118,6 +117,7 @@ class ProjectPolicy < BasePolicy
can! :read_container_image can! :read_container_image
can! :build_download_code can! :build_download_code
can! :build_read_container_image can! :build_read_container_image
can! :read_merge_request
end end
def owner_access! def owner_access!
...@@ -139,11 +139,18 @@ class ProjectPolicy < BasePolicy ...@@ -139,11 +139,18 @@ class ProjectPolicy < BasePolicy
def team_access!(user) def team_access!(user)
access = project.team.max_member_access(user.id) access = project.team.max_member_access(user.id)
guest_access! if access >= Gitlab::Access::GUEST return if access < Gitlab::Access::GUEST
reporter_access! if access >= Gitlab::Access::REPORTER guest_access!
team_member_reporter_access! if access >= Gitlab::Access::REPORTER
developer_access! if access >= Gitlab::Access::DEVELOPER return if access < Gitlab::Access::REPORTER
master_access! if access >= Gitlab::Access::MASTER reporter_access!
team_member_reporter_access!
return if access < Gitlab::Access::DEVELOPER
developer_access!
return if access < Gitlab::Access::MASTER
master_access!
end end
def archived_access! def archived_access!
......
module Boards
class BaseService < ::BaseService
delegate :board, to: :project
end
end
module Boards module Boards
class CreateService < Boards::BaseService class CreateService < BaseService
def execute def execute
create_board! unless project.board.present? if project.boards.empty?
project.board create_board!
else
project.boards.first
end
end end
private private
def create_board! def create_board!
project.create_board board = project.boards.create
project.board.lists.create(list_type: :backlog) board.lists.create(list_type: :backlog)
project.board.lists.create(list_type: :done) board.lists.create(list_type: :done)
board
end end
end end
end end
module Boards module Boards
module Issues module Issues
class CreateService < Boards::BaseService class CreateService < BaseService
def execute(list) def execute
params.merge!(label_ids: [list.label_id]) create_issue(params.merge(label_ids: [list.label_id]))
create_issue
end end
private private
def create_issue def board
@board ||= project.boards.find(params.delete(:board_id))
end
def list
@list ||= board.lists.find(params.delete(:list_id))
end
def create_issue(params)
::Issues::CreateService.new(project, current_user, params).execute ::Issues::CreateService.new(project, current_user, params).execute
end end
end end
......
module Boards module Boards
module Issues module Issues
class ListService < Boards::BaseService class ListService < BaseService
def execute def execute
issues = IssuesFinder.new(current_user, filter_params).execute issues = IssuesFinder.new(current_user, filter_params).execute
issues = without_board_labels(issues) unless list.movable? issues = without_board_labels(issues) unless list.movable?
...@@ -10,6 +10,10 @@ module Boards ...@@ -10,6 +10,10 @@ module Boards
private private
def board
@board ||= project.boards.find(params[:board_id])
end
def list def list
@list ||= board.lists.find(params[:id]) @list ||= board.lists.find(params[:id])
end end
......
module Boards module Boards
module Issues module Issues
class MoveService < Boards::BaseService class MoveService < BaseService
def execute(issue) def execute(issue)
return false unless can?(current_user, :update_issue, issue) return false unless can?(current_user, :update_issue, issue)
return false unless valid_move? return false unless valid_move?
...@@ -10,6 +10,10 @@ module Boards ...@@ -10,6 +10,10 @@ module Boards
private private
def board
@board ||= project.boards.find(params[:board_id])
end
def valid_move? def valid_move?
moving_from_list.present? && moving_to_list.present? && moving_from_list.present? && moving_to_list.present? &&
moving_from_list != moving_to_list moving_from_list != moving_to_list
...@@ -49,7 +53,7 @@ module Boards ...@@ -49,7 +53,7 @@ module Boards
if moving_to_list.movable? if moving_to_list.movable?
moving_from_list.label_id moving_from_list.label_id
else else
board.lists.movable.pluck(:label_id) project.boards.joins(:lists).merge(List.movable).pluck(:label_id)
end end
Array(label_ids).compact Array(label_ids).compact
......
module Boards
class ListService < BaseService
def execute
create_board! if project.boards.empty?
project.boards
end
private
def create_board!
Boards::CreateService.new(project, current_user).execute
end
end
end
module Boards module Boards
module Lists module Lists
class CreateService < Boards::BaseService class CreateService < BaseService
def execute def execute(board)
List.transaction do List.transaction do
label = project.labels.find(params[:label_id]) label = project.labels.find(params[:label_id])
position = next_position position = next_position(board)
create_list(label, position) create_list(board, label, position)
end end
end end
private private
def next_position def next_position(board)
max_position = board.lists.movable.maximum(:position) max_position = board.lists.movable.maximum(:position)
max_position.nil? ? 0 : max_position.succ max_position.nil? ? 0 : max_position.succ
end end
def create_list(label, position) def create_list(board, label, position)
board.lists.create(label: label, list_type: :label, position: position) board.lists.create(label: label, list_type: :label, position: position)
end end
end end
......
module Boards module Boards
module Lists module Lists
class DestroyService < Boards::BaseService class DestroyService < BaseService
def execute(list) def execute(list)
return false unless list.destroyable? return false unless list.destroyable?
@board = list.board
list.with_lock do list.with_lock do
decrement_higher_lists(list) decrement_higher_lists(list)
remove_list(list) remove_list(list)
...@@ -12,6 +14,8 @@ module Boards ...@@ -12,6 +14,8 @@ module Boards
private private
attr_reader :board
def decrement_higher_lists(list) def decrement_higher_lists(list)
board.lists.movable.where('position > ?', list.position) board.lists.movable.where('position > ?', list.position)
.update_all('position = position - 1') .update_all('position = position - 1')
......
module Boards module Boards
module Lists module Lists
class GenerateService < Boards::BaseService class GenerateService < BaseService
def execute def execute(board)
return false unless board.lists.movable.empty? return false unless board.lists.movable.empty?
List.transaction do List.transaction do
label_params.each { |params| create_list(params) } label_params.each { |params| create_list(board, params) }
end end
true true
...@@ -13,9 +13,9 @@ module Boards ...@@ -13,9 +13,9 @@ module Boards
private private
def create_list(params) def create_list(board, params)
label = find_or_create_label(params) label = find_or_create_label(params)
Lists::CreateService.new(project, current_user, label_id: label.id).execute Lists::CreateService.new(project, current_user, label_id: label.id).execute(board)
end end
def find_or_create_label(params) def find_or_create_label(params)
......
module Boards
module Lists
class ListService < BaseService
def execute(board)
board.lists
end
end
end
end
module Boards module Boards
module Lists module Lists
class MoveService < Boards::BaseService class MoveService < BaseService
def execute(list) def execute(list)
@board = list.board
@old_position = list.position @old_position = list.position
@new_position = params[:position] @new_position = params[:position]
...@@ -16,7 +17,7 @@ module Boards ...@@ -16,7 +17,7 @@ module Boards
private private
attr_reader :old_position, :new_position attr_reader :board, :old_position, :new_position
def valid_move? def valid_move?
new_position.present? && new_position != old_position && new_position.present? && new_position != old_position &&
......
...@@ -16,6 +16,8 @@ module Ci ...@@ -16,6 +16,8 @@ module Ci
process_stage(index) process_stage(index)
end end
@pipeline.update_status
# Return a flag if a when builds got enqueued # Return a flag if a when builds got enqueued
new_builds.flatten.any? new_builds.flatten.any?
end end
......
...@@ -3,7 +3,7 @@ require 'securerandom' ...@@ -3,7 +3,7 @@ require 'securerandom'
# Compare 2 branches for one repo or between repositories # Compare 2 branches for one repo or between repositories
# and return Gitlab::Git::Compare object that responds to commits and diffs # and return Gitlab::Git::Compare object that responds to commits and diffs
class CompareService class CompareService
def execute(source_project, source_branch, target_project, target_branch) def execute(source_project, source_branch, target_project, target_branch, straight: false)
source_commit = source_project.commit(source_branch) source_commit = source_project.commit(source_branch)
return unless source_commit return unless source_commit
...@@ -23,9 +23,10 @@ class CompareService ...@@ -23,9 +23,10 @@ class CompareService
raw_compare = Gitlab::Git::Compare.new( raw_compare = Gitlab::Git::Compare.new(
target_project.repository.raw_repository, target_project.repository.raw_repository,
target_branch, target_branch,
source_sha source_sha,
straight
) )
Compare.new(raw_compare, target_project) Compare.new(raw_compare, target_project, straight: straight)
end end
end end
...@@ -63,13 +63,12 @@ class GitPushService < BaseService ...@@ -63,13 +63,12 @@ class GitPushService < BaseService
protected protected
def update_merge_requests def update_merge_requests
@project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user) UpdateMergeRequestsWorker.perform_async(@project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref])
EventCreateService.new.push(@project, current_user, build_push_data) EventCreateService.new.push(@project, current_user, build_push_data)
SystemHooksService.new.execute_hooks(build_push_data_system_hook.dup, :push_hooks)
@project.execute_hooks(build_push_data.dup, :push_hooks) @project.execute_hooks(build_push_data.dup, :push_hooks)
@project.execute_services(build_push_data.dup, :push_hooks) @project.execute_services(build_push_data.dup, :push_hooks)
Ci::CreatePipelineService.new(project, current_user, build_push_data).execute Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute
ProjectCacheWorker.perform_async(@project.id) ProjectCacheWorker.perform_async(@project.id)
end end
...@@ -148,16 +147,6 @@ class GitPushService < BaseService ...@@ -148,16 +147,6 @@ class GitPushService < BaseService
push_commits) push_commits)
end end
def build_push_data_system_hook
@push_data_system ||= Gitlab::DataBuilder::Push.build(
@project,
current_user,
params[:oldrev],
params[:newrev],
params[:ref],
[])
end
def push_to_existing_branch? def push_to_existing_branch?
# Return if this is not a push to a branch (e.g. new commits) # Return if this is not a push to a branch (e.g. new commits)
Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev]) Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev])
......
...@@ -2,14 +2,14 @@ module MergeRequests ...@@ -2,14 +2,14 @@ module MergeRequests
class AddTodoWhenBuildFailsService < MergeRequests::BaseService class AddTodoWhenBuildFailsService < MergeRequests::BaseService
# Adds a todo to the parent merge_request when a CI build fails # Adds a todo to the parent merge_request when a CI build fails
def execute(commit_status) def execute(commit_status)
each_merge_request(commit_status) do |merge_request| commit_status_merge_requests(commit_status) do |merge_request|
todo_service.merge_request_build_failed(merge_request) todo_service.merge_request_build_failed(merge_request)
end end
end end
# Closes any pending build failed todos for the parent MRs when a build is retried # Closes any pending build failed todos for the parent MRs when a build is retried
def close(commit_status) def close(commit_status)
each_merge_request(commit_status) do |merge_request| commit_status_merge_requests(commit_status) do |merge_request|
todo_service.merge_request_build_retried(merge_request) todo_service.merge_request_build_retried(merge_request)
end end
end end
......
module MergeRequests
class AssignIssuesService < BaseService
def assignable_issues
@assignable_issues ||= begin
if current_user == merge_request.author
closes_issues.select do |issue|
!issue.assignee_id? && can?(current_user, :admin_issue, issue)
end
else
[]
end
end
end
def execute
assignable_issues.each do |issue|
Issues::UpdateService.new(issue.project, current_user, assignee_id: current_user.id).execute(issue)
end
{
count: assignable_issues.count
}
end
private
def merge_request
params[:merge_request]
end
def closes_issues
@closes_issues ||= params[:closes_issues] || merge_request.closes_issues(current_user)
end
end
end
...@@ -42,28 +42,33 @@ module MergeRequests ...@@ -42,28 +42,33 @@ module MergeRequests
super(:merge_request) super(:merge_request)
end end
def merge_request_from(commit_status) def merge_requests_for(branch)
branches = commit_status.ref origin_merge_requests = @project.origin_merge_requests
.opened.where(source_branch: branch).to_a
# This is for ref-less builds fork_merge_requests = @project.fork_merge_requests
branches ||= @project.repository.branch_names_contains(commit_status.sha) .opened.where(source_branch: branch).to_a
return [] if branches.blank? (origin_merge_requests + fork_merge_requests)
.uniq.select(&:source_project)
end
merge_requests = @project.origin_merge_requests.opened.where(source_branch: branches).to_a def pipeline_merge_requests(pipeline)
merge_requests += @project.fork_merge_requests.opened.where(source_branch: branches).to_a merge_requests_for(pipeline.ref).each do |merge_request|
next unless pipeline == merge_request.pipeline
merge_requests.uniq.select(&:source_project) yield merge_request
end
end end
def each_merge_request(commit_status) def commit_status_merge_requests(commit_status)
merge_request_from(commit_status).each do |merge_request| merge_requests_for(commit_status.ref).each do |merge_request|
pipeline = merge_request.pipeline pipeline = merge_request.pipeline
next unless pipeline next unless pipeline
next unless pipeline.sha == commit_status.sha next unless pipeline.sha == commit_status.sha
yield merge_request, pipeline yield merge_request
end end
end end
end end
......
...@@ -4,7 +4,7 @@ module MergeRequests ...@@ -4,7 +4,7 @@ module MergeRequests
merge_request = MergeRequest.new(params) merge_request = MergeRequest.new(params)
# Set MR attributes # Set MR attributes
merge_request.can_be_created = false merge_request.can_be_created = true
merge_request.compare_commits = [] merge_request.compare_commits = []
merge_request.source_project = project unless merge_request.source_project merge_request.source_project = project unless merge_request.source_project
...@@ -22,6 +22,12 @@ module MergeRequests ...@@ -22,6 +22,12 @@ module MergeRequests
return build_failed(merge_request, message) return build_failed(merge_request, message)
end end
if merge_request.source_project == merge_request.target_project &&
merge_request.target_branch == merge_request.source_branch
return build_failed(merge_request, 'You must select different branches')
end
compare = CompareService.new.execute( compare = CompareService.new.execute(
merge_request.source_project, merge_request.source_project,
merge_request.source_branch, merge_request.source_branch,
...@@ -29,17 +35,8 @@ module MergeRequests ...@@ -29,17 +35,8 @@ module MergeRequests
merge_request.target_branch, merge_request.target_branch,
) )
commits = compare.commits merge_request.compare_commits = compare.commits
# At this point we decide if merge request can be created
# If we have at least one commit to merge -> creation allowed
if commits.present?
merge_request.compare_commits = commits
merge_request.can_be_created = true
merge_request.compare = compare merge_request.compare = compare
else
merge_request.can_be_created = false
end
set_title_and_description(merge_request) set_title_and_description(merge_request)
end end
...@@ -89,6 +86,8 @@ module MergeRequests ...@@ -89,6 +86,8 @@ module MergeRequests
end end
end end
merge_request.title = merge_request.wip_title if commits.empty?
merge_request merge_request
end end
......
...@@ -18,12 +18,13 @@ module MergeRequests ...@@ -18,12 +18,13 @@ module MergeRequests
merge_request.save merge_request.save
end end
# Triggers the automatic merge of merge_request once the build succeeds # Triggers the automatic merge of merge_request once the pipeline succeeds
def trigger(commit_status) def trigger(pipeline)
each_merge_request(commit_status) do |merge_request, pipeline| return unless pipeline.success?
pipeline_merge_requests(pipeline) do |merge_request|
next unless merge_request.merge_when_build_succeeds? next unless merge_request.merge_when_build_succeeds?
next unless merge_request.mergeable? next unless merge_request.mergeable?
next unless pipeline.success?
MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params) MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params)
end end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment