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 ...
This commit is contained in:
commit
dc1d269f67
418 changed files with 7559 additions and 4250 deletions
|
@ -210,6 +210,13 @@ rake brakeman: *exec
|
|||
rake flay: *exec
|
||||
license_finder: *exec
|
||||
rake downtime_check: *exec
|
||||
rake ce_to_ee_merge_check:
|
||||
<<: *exec
|
||||
only:
|
||||
- branches
|
||||
except:
|
||||
- tags
|
||||
allow_failure: yes
|
||||
|
||||
rake db:migrate:reset:
|
||||
stage: test
|
||||
|
@ -255,6 +262,12 @@ lint-doc:
|
|||
script:
|
||||
- scripts/lint-doc.sh
|
||||
|
||||
bundler:check:
|
||||
stage: test
|
||||
<<: *ruby-static-analysis
|
||||
script:
|
||||
- bundle check
|
||||
|
||||
bundler:audit:
|
||||
stage: test
|
||||
<<: *ruby-static-analysis
|
||||
|
@ -293,6 +306,17 @@ coverage:
|
|||
- coverage/index.html
|
||||
- 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:
|
||||
|
@ -325,3 +349,16 @@ pages:
|
|||
- public
|
||||
only:
|
||||
- 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
|
||||
|
|
67
CHANGELOG
67
CHANGELOG
|
@ -1,43 +1,67 @@
|
|||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
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
|
||||
- Adding members no longer silently fails when there is extra whitespace
|
||||
- Update runner version only when updating contacted_at
|
||||
- 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.2 (GIT TRACE logging)
|
||||
- Use gitlab-shell v3.6.6
|
||||
- Add `/projects/visible` API endpoint (Ben Boeckel)
|
||||
- Fix centering of custom header logos (Ashley Dumaine)
|
||||
- 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
|
||||
- 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)
|
||||
- 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
|
||||
- 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)
|
||||
- Add tag shortcut from the Commit page. !6543
|
||||
- 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)
|
||||
- Replace unique keyframes mixin with keyframe mixin with specific names (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
|
||||
- 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
|
||||
- API: Ability to retrieve version information (Robert Schilling)
|
||||
- Fix permission for setting an issue's due date
|
||||
- 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"
|
||||
- Expose expires_at field when sharing project on API
|
||||
- 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)
|
||||
- Add Issue Board API support (andrebsguedes)
|
||||
- Allow the Koding integration to be configured through the API
|
||||
- Add new issue button to each list on Issues Board
|
||||
- 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)
|
||||
- Fix todos page mobile viewport layout (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)
|
||||
- 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
|
||||
- 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)
|
||||
- Use a ConnectionPool for Rails.cache on Sidekiq servers
|
||||
- Replace `alias_method_chain` with `Module#prepend`
|
||||
|
@ -57,28 +81,54 @@ v 8.13.0 (unreleased)
|
|||
- Fix Long commit messages overflow viewport in file tree
|
||||
- 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
|
||||
- Add disabled delete button to protected branches (ClemMakesApps)
|
||||
- Add broadcast messages and alerts below sub-nav
|
||||
- Better empty state for Groups view
|
||||
- API: New /users/:id/events endpoint
|
||||
- Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe)
|
||||
- Replace bootstrap caret with fontawesome caret (ClemMakesApps)
|
||||
- Fix unnecessary escaping of reserved HTML characters in milestone title. !6533
|
||||
- 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
|
||||
- Make searching for commits case insensitive
|
||||
- Fix resolved discussion display in side-by-side diff view !6575
|
||||
- Optimize GitHub importing for speed and memory
|
||||
- API: expose pipeline data in builds API (!6502, Guilherme Salazar)
|
||||
- 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
|
||||
- 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 Pipeline list commit column width should be adjusted
|
||||
- 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)
|
||||
- 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)
|
||||
- Allow empty merge requests !6384 (Artem Sidorenko)
|
||||
- Grouped pipeline dropdown is a scrollable container
|
||||
- Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi)
|
||||
- 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.5 (unreleased)
|
||||
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
|
||||
- Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. !6294 (lukehowell)
|
||||
|
@ -93,7 +143,7 @@ v 8.12.4
|
|||
- Fix failed project deletion when feature visibility set to private. !6688
|
||||
- Prevent claiming associated model IDs via import.
|
||||
- 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
|
||||
- Update Gitlab Shell to support low IO priority for storage moves
|
||||
|
@ -119,6 +169,7 @@ v 8.12.1
|
|||
- Fix issue with search filter labels not displaying
|
||||
|
||||
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
|
||||
- Only check :can_resolve permission if the note is resolvable
|
||||
- Bump fog-aws to v0.11.0 to support ap-south-1 region
|
||||
|
@ -307,6 +358,10 @@ v 8.12.0
|
|||
- Fix non-master branch readme display in tree view
|
||||
- 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
|
||||
- Respect the fork_project permission when forking projects
|
||||
- Set a restrictive CORS policy on the API for credentialed requests
|
||||
|
@ -532,6 +587,10 @@ v 8.11.0
|
|||
- Update gitlab_git gem to 10.4.7
|
||||
- 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
|
||||
- Respect the fork_project permission when forking projects
|
||||
- Set a restrictive CORS policy on the API for credentialed requests
|
||||
|
|
|
@ -1 +1 @@
|
|||
3.6.3
|
||||
3.6.6
|
||||
|
|
12
Gemfile
12
Gemfile
|
@ -51,7 +51,7 @@ gem 'browser', '~> 2.2'
|
|||
|
||||
# Extracting information from a git repository
|
||||
# Provide access to Gitlab::Git library
|
||||
gem 'gitlab_git', '~> 10.6.7'
|
||||
gem 'gitlab_git', '~> 10.6.8'
|
||||
|
||||
# LDAP Auth
|
||||
# GitLab fork with several improvements to original library. For full list of changes
|
||||
|
@ -101,7 +101,7 @@ gem 'seed-fu', '~> 2.3.5'
|
|||
# Markdown and HTML processing
|
||||
gem 'html-pipeline', '~> 1.11.0'
|
||||
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 'RedCloth', '~> 4.3.2'
|
||||
gem 'rdoc', '~>3.6'
|
||||
|
@ -225,7 +225,7 @@ gem 'gon', '~> 6.1.0'
|
|||
gem 'jquery-atwho-rails', '~> 1.3.2'
|
||||
gem 'jquery-rails', '~> 4.1.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 'virtus', '~> 1.0.1'
|
||||
gem 'net-ssh', '~> 3.0.1'
|
||||
|
@ -262,6 +262,8 @@ group :development do
|
|||
|
||||
# thin instead webrick
|
||||
gem 'thin', '~> 1.7.0'
|
||||
|
||||
gem 'activerecord_sane_schema_dumper', '0.2'
|
||||
end
|
||||
|
||||
group :development, :test do
|
||||
|
@ -324,7 +326,7 @@ gem 'newrelic_rpm', '~> 3.16'
|
|||
|
||||
gem 'octokit', '~> 4.3.0'
|
||||
|
||||
gem 'mail_room', '~> 0.8'
|
||||
gem 'mail_room', '~> 0.8.1'
|
||||
|
||||
gem 'email_reply_parser', '~> 0.5.8'
|
||||
|
||||
|
@ -341,7 +343,7 @@ gem 'oauth2', '~> 1.2.0'
|
|||
gem 'paranoia', '~> 2.0'
|
||||
|
||||
# Health check
|
||||
gem 'health_check', '~> 2.1.0'
|
||||
gem 'health_check', '~> 2.2.0'
|
||||
|
||||
# System information
|
||||
gem 'vmstat', '~> 2.2'
|
||||
|
|
20
Gemfile.lock
20
Gemfile.lock
|
@ -38,6 +38,8 @@ GEM
|
|||
multi_json (~> 1.11, >= 1.11.2)
|
||||
rack (>= 1.5.2, < 3)
|
||||
railties (>= 4.0, < 5.1)
|
||||
activerecord_sane_schema_dumper (0.2)
|
||||
rails (>= 4, < 5)
|
||||
activesupport (4.2.7.1)
|
||||
i18n (~> 0.7)
|
||||
json (~> 1.7, >= 1.7.7)
|
||||
|
@ -280,7 +282,8 @@ GEM
|
|||
diff-lcs (~> 1.1)
|
||||
mime-types (>= 1.16, < 3)
|
||||
posix-spawn (~> 0.3)
|
||||
gitlab_git (10.6.7)
|
||||
gitlab-markup (1.5.0)
|
||||
gitlab_git (10.6.8)
|
||||
activesupport (~> 4.0)
|
||||
charlock_holmes (~> 0.7.3)
|
||||
github-linguist (~> 4.7.0)
|
||||
|
@ -334,7 +337,7 @@ GEM
|
|||
thor
|
||||
tilt
|
||||
hashie (3.4.4)
|
||||
health_check (2.1.0)
|
||||
health_check (2.2.1)
|
||||
rails (>= 4.0)
|
||||
hipchat (1.5.2)
|
||||
httparty
|
||||
|
@ -399,7 +402,7 @@ GEM
|
|||
systemu (~> 2.6.2)
|
||||
mail (2.6.4)
|
||||
mime-types (>= 1.16, < 4)
|
||||
mail_room (0.8.0)
|
||||
mail_room (0.8.1)
|
||||
method_source (0.8.2)
|
||||
mime-types (2.99.3)
|
||||
mimemagic (0.3.0)
|
||||
|
@ -805,6 +808,7 @@ DEPENDENCIES
|
|||
RedCloth (~> 4.3.2)
|
||||
ace-rails-ap (~> 4.1.0)
|
||||
activerecord-session_store (~> 1.0.0)
|
||||
activerecord_sane_schema_dumper (= 0.2)
|
||||
acts-as-taggable-on (~> 4.0)
|
||||
addressable (~> 2.3.8)
|
||||
after_commit_queue (~> 1.3.0)
|
||||
|
@ -861,9 +865,9 @@ DEPENDENCIES
|
|||
gemnasium-gitlab-service (~> 0.2)
|
||||
gemojione (~> 3.0)
|
||||
github-linguist (~> 4.7.0)
|
||||
github-markup (~> 1.4)
|
||||
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)
|
||||
gollum-lib (~> 4.2)
|
||||
gollum-rugged_adapter (~> 0.4.2)
|
||||
|
@ -872,7 +876,7 @@ DEPENDENCIES
|
|||
grape-entity (~> 0.4.2)
|
||||
haml_lint (~> 0.18.2)
|
||||
hamlit (~> 2.6.1)
|
||||
health_check (~> 2.1.0)
|
||||
health_check (~> 2.2.0)
|
||||
hipchat (~> 1.5.0)
|
||||
html-pipeline (~> 1.11.0)
|
||||
httparty (~> 0.13.3)
|
||||
|
@ -889,7 +893,7 @@ DEPENDENCIES
|
|||
license_finder (~> 2.1.0)
|
||||
licensee (~> 8.0.0)
|
||||
loofah (~> 2.0.3)
|
||||
mail_room (~> 0.8)
|
||||
mail_room (~> 0.8.1)
|
||||
method_source (~> 0.8)
|
||||
minitest (~> 5.7.0)
|
||||
mousetrap-rails (~> 1.4.6)
|
||||
|
@ -934,7 +938,7 @@ DEPENDENCIES
|
|||
redis (~> 3.2)
|
||||
redis-namespace (~> 1.5.2)
|
||||
redis-rails (~> 4.0.0)
|
||||
request_store (~> 1.3.0)
|
||||
request_store (~> 1.3)
|
||||
rerun (~> 0.11.0)
|
||||
responders (~> 2.0)
|
||||
rouge (~> 2.0)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# GitLab
|
||||
|
||||
[![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)
|
||||
[![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](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)
|
||||
[![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42)
|
||||
|
||||
|
|
|
@ -6,11 +6,10 @@
|
|||
groupProjectsPath: "/api/:version/groups/:id/projects.json",
|
||||
projectsPath: "/api/:version/projects.json?simple=true",
|
||||
labelsPath: "/:namespace_path/:project_path/labels",
|
||||
licensePath: "/api/:version/licenses/:key",
|
||||
gitignorePath: "/api/:version/gitignores/:key",
|
||||
gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key",
|
||||
licensePath: "/api/:version/templates/licenses/:key",
|
||||
gitignorePath: "/api/:version/templates/gitignores/:key",
|
||||
gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key",
|
||||
issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key",
|
||||
|
||||
group: function(group_id, callback) {
|
||||
var url = Api.buildUrl(Api.groupPath)
|
||||
.replace(':id', group_id);
|
||||
|
|
|
@ -28,12 +28,13 @@ $(() => {
|
|||
state: Store.state,
|
||||
loading: true,
|
||||
endpoint: $boardApp.dataset.endpoint,
|
||||
boardId: $boardApp.dataset.boardId,
|
||||
disabled: $boardApp.dataset.disabled === 'true',
|
||||
issueLinkBase: $boardApp.dataset.issueLinkBase
|
||||
},
|
||||
init: Store.create.bind(Store),
|
||||
created () {
|
||||
gl.boardService = new BoardService(this.endpoint);
|
||||
gl.boardService = new BoardService(this.endpoint, this.boardId);
|
||||
},
|
||||
ready () {
|
||||
Store.disabled = this.disabled;
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
class BoardService {
|
||||
constructor (root) {
|
||||
constructor (root, boardId) {
|
||||
Vue.http.options.root = root;
|
||||
|
||||
this.lists = Vue.resource(`${root}/lists{/id}`, {}, {
|
||||
this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, {
|
||||
generate: {
|
||||
method: 'POST',
|
||||
url: `${root}/lists/generate.json`
|
||||
url: `${root}/${boardId}/lists/generate.json`
|
||||
}
|
||||
});
|
||||
this.issue = Vue.resource(`${root}/issues{/id}`, {});
|
||||
this.issues = Vue.resource(`${root}/lists{/id}/issues`, {});
|
||||
this.issue = Vue.resource(`${root}/${boardId}/issues{/id}`, {});
|
||||
this.issues = Vue.resource(`${root}/${boardId}/lists{/id}/issues`, {});
|
||||
|
||||
Vue.http.interceptors.push((request, next) => {
|
||||
request.headers['X-CSRF-Token'] = $.rails.csrfToken();
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
shortcut_handler = null;
|
||||
switch (page) {
|
||||
case 'projects:boards:show':
|
||||
case 'projects:boards:index':
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
break;
|
||||
case 'projects:merge_requests:index':
|
||||
|
@ -126,6 +127,9 @@
|
|||
new TreeView();
|
||||
}
|
||||
break;
|
||||
case 'projects:pipelines:show':
|
||||
new gl.Pipelines();
|
||||
break;
|
||||
case 'groups:activity':
|
||||
new Activities();
|
||||
break;
|
||||
|
|
|
@ -52,37 +52,27 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
setup: function(input) {
|
||||
setup: _.debounce(function(input) {
|
||||
// Add GFM auto-completion to all input fields, that accept GFM input.
|
||||
this.input = input || $('.js-gfm-input');
|
||||
// destroy previous instances
|
||||
this.destroyAtWho();
|
||||
// set up instances
|
||||
this.setupAtWho();
|
||||
if (this.dataSource) {
|
||||
if (!this.dataLoading && !this.cachedData) {
|
||||
this.dataLoading = true;
|
||||
setTimeout((function(_this) {
|
||||
return function() {
|
||||
var fetch;
|
||||
fetch = _this.fetchData(_this.dataSource);
|
||||
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) {
|
||||
return this.loadData(this.cachedData);
|
||||
}
|
||||
|
||||
if (this.dataSource && !this.dataLoading && !this.cachedData) {
|
||||
this.dataLoading = true;
|
||||
return this.fetchData(this.dataSource)
|
||||
.done((data) => {
|
||||
this.dataLoading = false;
|
||||
this.loadData(data);
|
||||
});
|
||||
};
|
||||
|
||||
if (this.cachedData != null) {
|
||||
return this.loadData(this.cachedData);
|
||||
}
|
||||
},
|
||||
}, 1000),
|
||||
setupAtWho: function() {
|
||||
// Emoji
|
||||
this.input.atwho({
|
||||
|
|
|
@ -738,6 +738,7 @@
|
|||
return false;
|
||||
}
|
||||
if (currentKeyCode === 13 && currentIndex !== -1) {
|
||||
e.preventDefault();
|
||||
_this.selectRowAtIndex();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -292,7 +292,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
if (page === 'projects:boards:show') {
|
||||
if ($('html').hasClass('issue-boards-page')) {
|
||||
return;
|
||||
}
|
||||
if ($dropdown.hasClass('js-multiselect')) {
|
||||
|
@ -334,7 +334,7 @@
|
|||
page = $('body').data('page');
|
||||
isIssueIndex = page === 'projects:issues:index';
|
||||
isMRIndex = page === 'projects:merge_requests:index';
|
||||
if (page === 'projects:boards:show') {
|
||||
if ($('html').hasClass('issue-boards-page')) {
|
||||
if (label.isAny) {
|
||||
gl.issueBoards.BoardsStore.state.filters['label_name'] = [];
|
||||
}
|
||||
|
|
|
@ -1,7 +1,26 @@
|
|||
(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; };
|
||||
|
||||
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) {
|
||||
// Initialize MergeRequestWidget behavior
|
||||
//
|
||||
|
@ -10,17 +29,23 @@
|
|||
// ci_status_url - String, URL to use to check CI status
|
||||
//
|
||||
this.opts = opts;
|
||||
this.$widgetBody = $('.mr-widget-body');
|
||||
$('#modal_merge_info').modal({
|
||||
show: false
|
||||
});
|
||||
this.firstCICheck = true;
|
||||
this.readyForCICheck = false;
|
||||
this.readyForCIEnvironmentCheck = false;
|
||||
this.cancel = false;
|
||||
clearInterval(this.fetchBuildStatusInterval);
|
||||
clearInterval(this.fetchBuildEnvironmentStatusInterval);
|
||||
this.clearEventListeners();
|
||||
this.addEventListeners();
|
||||
this.getCIStatus(false);
|
||||
this.getCIEnvironmentsStatus();
|
||||
this.retrieveSuccessIcon();
|
||||
this.pollCIStatus();
|
||||
this.pollCIEnvironmentsStatus();
|
||||
notifyPermissions();
|
||||
}
|
||||
|
||||
|
@ -41,6 +66,7 @@
|
|||
page = $('body').data('page').split(':').last();
|
||||
if (allowedPages.indexOf(page) < 0) {
|
||||
clearInterval(_this.fetchBuildStatusInterval);
|
||||
clearInterval(_this.fetchBuildEnvironmentStatusInterval);
|
||||
_this.cancelPolling();
|
||||
return _this.clearEventListeners();
|
||||
}
|
||||
|
@ -48,6 +74,12 @@
|
|||
})(this));
|
||||
};
|
||||
|
||||
MergeRequestWidget.prototype.retrieveSuccessIcon = function() {
|
||||
const $ciSuccessIcon = $('.js-success-icon');
|
||||
this.$ciSuccessIcon = $ciSuccessIcon.html();
|
||||
$ciSuccessIcon.remove();
|
||||
}
|
||||
|
||||
MergeRequestWidget.prototype.mergeInProgress = function(deleteSourceBranch) {
|
||||
if (deleteSourceBranch == null) {
|
||||
deleteSourceBranch = false;
|
||||
|
@ -62,7 +94,7 @@
|
|||
urlSuffix = deleteSourceBranch ? '?deleted_source_branch=true' : '';
|
||||
return window.location.href = window.location.pathname + urlSuffix;
|
||||
} 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 {
|
||||
callback = function() {
|
||||
return merge_request_widget.mergeInProgress(deleteSourceBranch);
|
||||
|
@ -118,6 +150,7 @@
|
|||
if (data.status === '') {
|
||||
return;
|
||||
}
|
||||
if (data.environments && data.environments.length) _this.renderEnvironments(data.environments);
|
||||
if (_this.firstCICheck || data.status !== _this.opts.ci_status && (data.status != null)) {
|
||||
_this.opts.ci_status = data.status;
|
||||
_this.showCIStatus(data.status);
|
||||
|
@ -150,6 +183,41 @@
|
|||
})(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) {
|
||||
var allowed_states;
|
||||
if (state == null) {
|
||||
|
@ -190,4 +258,4 @@
|
|||
|
||||
})();
|
||||
|
||||
}).call(this);
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -110,7 +110,7 @@
|
|||
e.preventDefault();
|
||||
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.updateFiltersUrl();
|
||||
e.preventDefault();
|
||||
|
|
|
@ -1,24 +1,40 @@
|
|||
(function() {
|
||||
function toggleGraph() {
|
||||
const $pipelineBtn = $(this).closest('.toggle-pipeline-btn');
|
||||
const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph');
|
||||
const $btnText = $(this).find('.toggle-btn-text');
|
||||
const $icon = $(this).find('.fa');
|
||||
((global) => {
|
||||
|
||||
$($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed');
|
||||
class Pipelines {
|
||||
constructor() {
|
||||
$(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph);
|
||||
this.addMarginToBuildColumns();
|
||||
}
|
||||
|
||||
const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed');
|
||||
const expandIcon = 'fa-caret-down';
|
||||
const hideIcon = 'fa-caret-up';
|
||||
toggleGraph() {
|
||||
const $pipelineBtn = $(this).closest('.toggle-pipeline-btn');
|
||||
const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph');
|
||||
const $btnText = $(this).find('.toggle-btn-text');
|
||||
const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed');
|
||||
|
||||
if(graphCollapsed) {
|
||||
$btnText.text('Expand');
|
||||
$icon.removeClass(hideIcon).addClass(expandIcon);
|
||||
} else {
|
||||
$btnText.text('Hide');
|
||||
$icon.removeClass(expandIcon).addClass(hideIcon);
|
||||
$($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed');
|
||||
|
||||
|
||||
graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide')
|
||||
}
|
||||
|
||||
addMarginToBuildColumns() {
|
||||
const $secondChildBuildNode = $('.build:nth-child(2)');
|
||||
if ($secondChildBuildNode.length) {
|
||||
const $firstChildBuildNode = $secondChildBuildNode.prev('.build');
|
||||
const $multiBuildColumn = $secondChildBuildNode.closest('.stage-column');
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
$(document).on('click', '.toggle-pipeline-btn', toggleGraph);
|
||||
})();
|
||||
global.Pipelines = Pipelines;
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
this.ProjectNew = (function() {
|
||||
function ProjectNew() {
|
||||
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) {
|
||||
return function() {
|
||||
|
|
|
@ -160,7 +160,7 @@
|
|||
selectedId = user.id;
|
||||
return;
|
||||
}
|
||||
if (page === 'projects:boards:show') {
|
||||
if ($('html').hasClass('issue-boards-page')) {
|
||||
selectedId = user.id;
|
||||
gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id;
|
||||
gl.issueBoards.BoardsStore.updateFiltersUrl();
|
||||
|
@ -261,10 +261,11 @@
|
|||
}
|
||||
}
|
||||
if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) {
|
||||
var trimmed = query.term.trim();
|
||||
emailUser = {
|
||||
name: "Invite \"" + query.term + "\"",
|
||||
username: query.term,
|
||||
id: query.term
|
||||
username: trimmed,
|
||||
id: trimmed
|
||||
};
|
||||
data.results.unshift(emailUser);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
@include border-radius($avatar_radius);
|
||||
border-radius: $avatar_radius;
|
||||
border: 1px solid rgba(0, 0, 0, .1);
|
||||
|
||||
&.avatar-inline {
|
||||
|
@ -17,7 +17,7 @@
|
|||
}
|
||||
|
||||
&.avatar-tile {
|
||||
@include border-radius(0);
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -133,7 +133,7 @@
|
|||
}
|
||||
|
||||
.identicon {
|
||||
@include border-radius(50%);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@mixin btn-default {
|
||||
@include border-radius(3px);
|
||||
border-radius: 3px;
|
||||
font-size: $gl-font-size;
|
||||
font-weight: 500;
|
||||
padding: $gl-vert-padding $gl-btn-padding;
|
||||
|
@ -8,7 +8,7 @@
|
|||
&:active {
|
||||
outline: none;
|
||||
background-color: $btn-active-gray;
|
||||
@include box-shadow($gl-btn-active-background);
|
||||
box-shadow: $gl-btn-active-background;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@
|
|||
|
||||
&:active,
|
||||
&.active {
|
||||
@include box-shadow ($gl-btn-active-background);
|
||||
box-shadow: $gl-btn-active-background;
|
||||
|
||||
background-color: $dark;
|
||||
border-color: $border-dark;
|
||||
|
@ -279,7 +279,7 @@
|
|||
}
|
||||
|
||||
.active {
|
||||
@include box-shadow($gl-btn-active-background);
|
||||
box-shadow: $gl-btn-active-background;
|
||||
|
||||
border: 1px solid #c6cacf !important;
|
||||
background-color: #e4e7ed !important;
|
||||
|
|
|
@ -73,7 +73,7 @@ label {
|
|||
}
|
||||
|
||||
.form-control {
|
||||
@include box-shadow(none);
|
||||
box-shadow: none;
|
||||
border-radius: 3px;
|
||||
padding: $gl-vert-padding $gl-input-padding;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
margin-top: 5px;
|
||||
}
|
||||
|
||||
@include border-radius(3px);
|
||||
border-radius: 3px;
|
||||
display: block;
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
|
|
|
@ -1,15 +1,3 @@
|
|||
@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) {
|
||||
fill: $path-color;
|
||||
transition: all 0.8s;
|
||||
|
@ -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-left-ear,
|
||||
|
@ -67,7 +33,7 @@
|
|||
}
|
||||
|
||||
.tanuki-left-cheek {
|
||||
@include unique-keyframes {
|
||||
@include include-keyframes(animate-tanuki-left-cheek) {
|
||||
0%, 10%, 100% {
|
||||
fill: lighten($tanuki-yellow, 25%);
|
||||
}
|
||||
|
@ -78,15 +44,29 @@
|
|||
}
|
||||
|
||||
.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 {
|
||||
@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 {
|
||||
@include unique-keyframes {
|
||||
@include include-keyframes(animate-tanuki-nose) {
|
||||
20%, 70% {
|
||||
fill: $tanuki-red;
|
||||
}
|
||||
|
@ -97,15 +77,29 @@
|
|||
}
|
||||
|
||||
.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 {
|
||||
@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 {
|
||||
@include unique-keyframes {
|
||||
@include include-keyframes(animate-tanuki-right-cheek) {
|
||||
40% {
|
||||
fill: $tanuki-yellow;
|
||||
}
|
||||
|
@ -115,4 +109,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
}
|
||||
|
||||
.markdown-area {
|
||||
@include border-radius(0);
|
||||
border-radius: 0;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
min-height: 140px;
|
||||
|
|
|
@ -1,14 +1,3 @@
|
|||
/**
|
||||
* Generic mixins
|
||||
*/
|
||||
@mixin box-shadow($shadow) {
|
||||
box-shadow: $shadow;
|
||||
}
|
||||
|
||||
@mixin border-radius($radius) {
|
||||
border-radius: $radius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefilled mixins
|
||||
* Mixins with fixed values
|
||||
|
@ -95,3 +84,10 @@
|
|||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin include-keyframes($animation-name) {
|
||||
@include webkit-prefix(animation-name, $animation-name);
|
||||
@include keyframes($animation-name) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,5 +133,5 @@
|
|||
font-size: 20px;
|
||||
color: #777;
|
||||
z-index: 100;
|
||||
@include box-shadow(0 1px 2px #ddd);
|
||||
box-shadow: 0 1px 2px #ddd;
|
||||
}
|
||||
|
|
|
@ -46,8 +46,8 @@
|
|||
}
|
||||
|
||||
.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);
|
||||
@include border-radius ($border-radius-default);
|
||||
box-shadow: rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0;
|
||||
border-radius: $border-radius-default;
|
||||
border: none;
|
||||
min-width: 175px;
|
||||
}
|
||||
|
@ -72,7 +72,7 @@
|
|||
|
||||
.select2-container-active {
|
||||
.select2-choice, .select2-choices {
|
||||
@include box-shadow(none);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,13 +82,13 @@
|
|||
outline: 0;
|
||||
background-image: none;
|
||||
background-color: $white-dark;
|
||||
@include box-shadow($gl-btn-active-gradient);
|
||||
box-shadow: $gl-btn-active-gradient;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-container-multi {
|
||||
.select2-choices {
|
||||
@include border-radius($border-radius-default);
|
||||
border-radius: $border-radius-default;
|
||||
border-color: $input-border;
|
||||
background: none;
|
||||
|
||||
|
@ -123,7 +123,7 @@
|
|||
&.select2-container-active .select2-choices,
|
||||
&.select2-dropdown-open .select2-choices {
|
||||
border-color: $border-white-normal;
|
||||
@include box-shadow($gl-btn-active-gradient);
|
||||
box-shadow: $gl-btn-active-gradient;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,7 +157,7 @@
|
|||
background-repeat: no-repeat;
|
||||
background-position: right 0 bottom 6px;
|
||||
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;
|
||||
|
||||
&:focus {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
&.page-sidebar-pinned {
|
||||
.sidebar-wrapper {
|
||||
@include box-shadow(none);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
|||
width: 0;
|
||||
overflow: hidden;
|
||||
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 @@
|
|||
.count {
|
||||
float: right;
|
||||
padding: 0 8px;
|
||||
@include border-radius(6px);
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -116,7 +116,7 @@
|
|||
font-size: 13px;
|
||||
line-height: 1.6em;
|
||||
overflow-x: auto;
|
||||
@include border-radius(2px);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
p > code {
|
||||
|
|
|
@ -17,8 +17,10 @@ $white-normal: #ededed;
|
|||
$white-dark: #ececec;
|
||||
|
||||
$gray-light: #fafafa;
|
||||
$gray-lighter: #f9f9f9;
|
||||
$gray-normal: #f5f5f5;
|
||||
$gray-dark: #ededed;
|
||||
$gray-darker: #eee;
|
||||
$gray-darkest: #c9c9c9;
|
||||
|
||||
$green-light: #38ae67;
|
||||
|
@ -33,6 +35,8 @@ $blue-medium-light: #3498cb;
|
|||
$blue-medium: #2f8ebf;
|
||||
$blue-medium-dark: #2d86b4;
|
||||
|
||||
$blue-light-transparent: rgba(44, 159, 216, 0.05);
|
||||
|
||||
$orange-light: #fc8a51;
|
||||
$orange-normal: #e75e40;
|
||||
$orange-dark: #ce5237;
|
||||
|
@ -91,6 +95,7 @@ $table-text-gray: #8f8f8f;
|
|||
$gl-font-size: 15px;
|
||||
$gl-title-color: #333;
|
||||
$gl-text-color: #5c5c5c;
|
||||
$gl-text-color-light: #8c8c8c;
|
||||
$gl-text-green: #4a2;
|
||||
$gl-text-red: #d12f19;
|
||||
$gl-text-orange: #d90;
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
|
||||
.bordered-box {
|
||||
border: 1px solid $border-color;
|
||||
@include border-radius($border-radius-default);
|
||||
border-radius: $border-radius-default;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.file-editor {
|
||||
#editor {
|
||||
border: none;
|
||||
@include border-radius(0);
|
||||
border-radius: 0;
|
||||
height: 500px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
|
|
@ -91,7 +91,7 @@
|
|||
float: right;
|
||||
border: 1px solid #eee;
|
||||
padding: 5px;
|
||||
@include border-radius(5px);
|
||||
border-radius: 5px;
|
||||
background: $gray-light;
|
||||
margin-left: 10px;
|
||||
top: -6px;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.suggest-colors {
|
||||
margin-top: 5px;
|
||||
a {
|
||||
@include border-radius(4px);
|
||||
border-radius: 4px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: inline-block;
|
||||
|
@ -17,7 +17,7 @@
|
|||
overflow: hidden;
|
||||
|
||||
a {
|
||||
@include border-radius(0);
|
||||
border-radius: 0;
|
||||
width: (100% / 7);
|
||||
margin-right: 0;
|
||||
margin-bottom: -5px;
|
||||
|
|
|
@ -73,12 +73,12 @@
|
|||
height: auto;
|
||||
|
||||
&.top {
|
||||
@include border-radius(5px 5px 0 0);
|
||||
border-radius: 5px 5px 0 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
@include border-radius(0 0 5px 5px);
|
||||
border-radius: 0 0 5px 5px;
|
||||
border-top: 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
@ -86,7 +86,7 @@
|
|||
&.middle {
|
||||
border-top: 0;
|
||||
margin-bottom: 0;
|
||||
@include border-radius(0);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
&:active, &:focus {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
background: $background-color;
|
||||
color: $gl-gray;
|
||||
border: 1px solid $border-color;
|
||||
@include border-radius(2px);
|
||||
border-radius: 2px;
|
||||
|
||||
form {
|
||||
margin-bottom: 0;
|
||||
|
@ -121,6 +121,10 @@
|
|||
color: #5c5d5e;
|
||||
}
|
||||
|
||||
.js-deployment-link {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.mr-widget-body {
|
||||
h4 {
|
||||
font-weight: 600;
|
||||
|
@ -204,6 +208,18 @@
|
|||
word-break: break-all;
|
||||
}
|
||||
|
||||
.commits-empty {
|
||||
text-align: center;
|
||||
|
||||
h4 {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
svg {
|
||||
width: 230px;
|
||||
}
|
||||
}
|
||||
|
||||
.mr-list {
|
||||
.merge-request {
|
||||
padding: 10px 15px;
|
||||
|
@ -389,8 +405,12 @@
|
|||
padding: 16px;
|
||||
}
|
||||
|
||||
.content-block {
|
||||
border-top: 1px solid $border-color;
|
||||
padding: $gl-padding-top $gl-padding;
|
||||
}
|
||||
|
||||
.comments-disabled-notif {
|
||||
padding: 10px 16px;
|
||||
.btn {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
@ -401,10 +421,6 @@
|
|||
margin: 0 7px;
|
||||
}
|
||||
|
||||
.comments-disabled-notif {
|
||||
border-top: 1px solid $border-color;
|
||||
}
|
||||
|
||||
.dropdown-title {
|
||||
color: $gl-text-color;
|
||||
}
|
||||
|
|
|
@ -334,7 +334,7 @@ ul.notes {
|
|||
|
||||
.add-diff-note {
|
||||
margin-top: -4px;
|
||||
@include border-radius(40px);
|
||||
border-radius: 40px;
|
||||
background: #fff;
|
||||
padding: 4px;
|
||||
font-size: 16px;
|
||||
|
|
|
@ -303,16 +303,41 @@
|
|||
.stage-column {
|
||||
display: inline-block;
|
||||
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 {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.stage-name {
|
||||
margin-bottom: 15px;
|
||||
margin: 0 0 15px 10px;
|
||||
font-weight: bold;
|
||||
width: 150px;
|
||||
width: 176px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -321,17 +346,23 @@
|
|||
.build {
|
||||
border: 1px solid $border-color;
|
||||
position: relative;
|
||||
padding: 6px 10px;
|
||||
padding: 7px 10px 8px;
|
||||
border-radius: 30px;
|
||||
width: 150px;
|
||||
width: 186px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&:hover {
|
||||
background-color: $gray-lighter;
|
||||
.dropdown-menu-toggle {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&.playable {
|
||||
background-color: $gray-light;
|
||||
|
||||
svg {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
height: 13px;
|
||||
width: 20px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
|
||||
|
@ -342,10 +373,20 @@
|
|||
}
|
||||
|
||||
.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 {
|
||||
width: 110px;
|
||||
width: 135px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -356,44 +397,56 @@
|
|||
}
|
||||
|
||||
a {
|
||||
color: $layout-link-gray;
|
||||
color: $gl-text-color-light;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
.ci-status-text {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu-toggle {
|
||||
border: none;
|
||||
width: auto;
|
||||
padding: 0;
|
||||
color: $layout-link-gray;
|
||||
color: $gl-text-color-light;
|
||||
flex-grow: 1;
|
||||
|
||||
.ci-status-text {
|
||||
width: 80px;
|
||||
max-width: 112px;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.grouped-pipeline-dropdown {
|
||||
padding: 8px 0;
|
||||
width: 200px;
|
||||
width: 186px;
|
||||
left: auto;
|
||||
right: -214px;
|
||||
right: -197px;
|
||||
top: -9px;
|
||||
max-height: 245px;
|
||||
overflow-y: scroll;
|
||||
|
||||
a:hover {
|
||||
.ci-status-text {
|
||||
text-decoration: none;
|
||||
ul {
|
||||
max-height: 245px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $gl-text-color;
|
||||
padding: 7px 8px 8px;
|
||||
|
||||
&:hover {
|
||||
background-color: $blue-light-transparent;
|
||||
border-radius: 3px;
|
||||
|
||||
.ci-status-text {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.ci-status-text {
|
||||
width: 145px;
|
||||
width: 112px;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
|
@ -426,9 +479,10 @@
|
|||
}
|
||||
|
||||
.badge {
|
||||
background-color: $gray-dark;
|
||||
color: $layout-link-gray;
|
||||
background-color: $gray-darker;
|
||||
color: $gl-text-color-light;
|
||||
font-weight: normal;
|
||||
margin-left: $btn-xs-side-margin;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -442,10 +496,10 @@
|
|||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: -69px;
|
||||
top: 48%;
|
||||
right: -48px;
|
||||
border-top: 2px solid $border-color;
|
||||
width: 69px;
|
||||
width: 48px;
|
||||
height: 1px;
|
||||
}
|
||||
}
|
||||
|
@ -454,25 +508,25 @@
|
|||
&:not(:first-child) {
|
||||
&::after, &::before {
|
||||
content: '';
|
||||
top: -47px;
|
||||
top: -49px;
|
||||
position: absolute;
|
||||
border-bottom: 2px solid $border-color;
|
||||
width: 20px;
|
||||
height: 65px;
|
||||
width: 25px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
// Right connecting curves
|
||||
&::after {
|
||||
right: -20px;
|
||||
right: -25px;
|
||||
border-right: 2px solid $border-color;
|
||||
border-radius: 0 0 15px;
|
||||
border-radius: 0 0 20px;
|
||||
}
|
||||
|
||||
// Left connecting curves
|
||||
&::before {
|
||||
left: -20px;
|
||||
left: -25px;
|
||||
border-left: 2px solid $border-color;
|
||||
border-radius: 0 0 0 15px;
|
||||
border-radius: 0 0 0 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -480,7 +534,7 @@
|
|||
&:nth-child(2) {
|
||||
&::after, &::before {
|
||||
height: 29px;
|
||||
top: -10px;
|
||||
top: -9px;
|
||||
}
|
||||
.curve {
|
||||
display: block;
|
||||
|
@ -538,20 +592,20 @@
|
|||
width: 21px;
|
||||
height: 25px;
|
||||
position: absolute;
|
||||
top: -29px;
|
||||
top: -32px;
|
||||
border-top: 2px solid $border-color;
|
||||
}
|
||||
|
||||
&::after {
|
||||
left: -39px;
|
||||
left: -44px;
|
||||
border-right: 2px solid $border-color;
|
||||
border-radius: 0 15px;
|
||||
border-radius: 0 20px;
|
||||
}
|
||||
|
||||
&::before {
|
||||
right: -39px;
|
||||
right: -44px;
|
||||
border-left: 2px solid $border-color;
|
||||
border-radius: 15px 0 0;
|
||||
border-radius: 20px 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
text-align: center;
|
||||
|
||||
.preview {
|
||||
@include border-radius(4px);
|
||||
border-radius: 4px;
|
||||
|
||||
height: 80px;
|
||||
margin-bottom: 10px;
|
||||
|
@ -47,7 +47,7 @@
|
|||
width: 160px;
|
||||
|
||||
img {
|
||||
@include border-radius(4px);
|
||||
border-radius: 4px;
|
||||
|
||||
max-width: 100%;
|
||||
}
|
||||
|
|
|
@ -354,7 +354,7 @@ a.deploy-project-label {
|
|||
justify-content: flex-start;
|
||||
|
||||
.fork-thumbnail {
|
||||
@include border-radius($border-radius-base);
|
||||
border-radius: $border-radius-base;
|
||||
background-color: $white-light;
|
||||
border: 1px solid $border-white-light;
|
||||
height: 202px;
|
||||
|
@ -371,7 +371,7 @@ a.deploy-project-label {
|
|||
background-color: $gray-light;
|
||||
border: 1px solid $gray-dark;
|
||||
margin: 0 auto;
|
||||
@include border-radius(50%);
|
||||
border-radius: 50%;
|
||||
i {
|
||||
font-size: 100px;
|
||||
color: $gray-dark;
|
||||
|
@ -390,7 +390,7 @@ a.deploy-project-label {
|
|||
}
|
||||
|
||||
img {
|
||||
@include border-radius(50%);
|
||||
border-radius: 50%;
|
||||
max-width: 100px;
|
||||
}
|
||||
}
|
||||
|
@ -496,7 +496,7 @@ pre.light-well {
|
|||
}
|
||||
|
||||
.light-well {
|
||||
@include border-radius (2px);
|
||||
border-radius: 2px;
|
||||
|
||||
color: #5b6169;
|
||||
font-size: 13px;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
margin-right: 10px;
|
||||
border: 1px solid #eee;
|
||||
white-space: nowrap;
|
||||
@include border-radius(4px);
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
module Ci
|
||||
class ApplicationController < ::ApplicationController
|
||||
def self.railtie_helpers_paths
|
||||
"app/helpers/ci"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
module Ci
|
||||
class LintsController < ApplicationController
|
||||
class LintsController < ::ApplicationController
|
||||
before_action :authenticate_user!
|
||||
|
||||
def show
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
module Ci
|
||||
class ProjectsController < Ci::ApplicationController
|
||||
class ProjectsController < ::ApplicationController
|
||||
before_action :project
|
||||
before_action :no_cache, only: [:badge]
|
||||
before_action :authorize_read_project!, except: [:badge, :index]
|
||||
|
|
|
@ -21,8 +21,7 @@ class Explore::ProjectsController < Explore::ApplicationController
|
|||
end
|
||||
|
||||
def trending
|
||||
@projects = TrendingProjectsFinder.new.execute
|
||||
@projects = filter_projects(@projects)
|
||||
@projects = filter_projects(Project.trending)
|
||||
@projects = @projects.page(params[:page])
|
||||
|
||||
respond_to do |format|
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
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
|
|
@ -1,65 +0,0 @@
|
|||
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
|
|||
end
|
||||
|
||||
def create
|
||||
list = project.board.lists.find(params[:list_id])
|
||||
service = ::Boards::Issues::CreateService.new(project, current_user, issue_params)
|
||||
issue = service.execute(list)
|
||||
issue = service.execute
|
||||
|
||||
if issue.valid?
|
||||
render json: serialize_as_json(issue)
|
||||
|
@ -60,15 +59,15 @@ module Projects
|
|||
end
|
||||
|
||||
def filter_params
|
||||
params.merge(id: params[:list_id])
|
||||
params.merge(board_id: params[:board_id], id: params[:list_id])
|
||||
end
|
||||
|
||||
def move_params
|
||||
params.permit(:id, :from_list_id, :to_list_id)
|
||||
params.permit(:board_id, :id, :from_list_id, :to_list_id)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def serialize_as_json(resource)
|
||||
|
|
|
@ -5,11 +5,11 @@ module Projects
|
|||
before_action :authorize_read_list!, only: [:index]
|
||||
|
||||
def index
|
||||
render json: serialize_as_json(project.board.lists)
|
||||
render json: serialize_as_json(board.lists)
|
||||
end
|
||||
|
||||
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?
|
||||
render json: serialize_as_json(list)
|
||||
|
@ -19,7 +19,7 @@ module Projects
|
|||
end
|
||||
|
||||
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)
|
||||
|
||||
if service.execute(list)
|
||||
|
@ -30,8 +30,8 @@ module Projects
|
|||
end
|
||||
|
||||
def destroy
|
||||
list = project.board.lists.destroyable.find(params[:id])
|
||||
service = ::Boards::Lists::DestroyService.new(project, current_user, params)
|
||||
list = board.lists.destroyable.find(params[:id])
|
||||
service = ::Boards::Lists::DestroyService.new(project, current_user)
|
||||
|
||||
if service.execute(list)
|
||||
head :ok
|
||||
|
@ -43,8 +43,8 @@ module Projects
|
|||
def generate
|
||||
service = ::Boards::Lists::GenerateService.new(project, current_user)
|
||||
|
||||
if service.execute
|
||||
render json: serialize_as_json(project.board.lists.movable)
|
||||
if service.execute(board)
|
||||
render json: serialize_as_json(board.lists.movable)
|
||||
else
|
||||
head :unprocessable_entity
|
||||
end
|
||||
|
@ -60,6 +60,10 @@ module Projects
|
|||
return render_403 unless can?(current_user, :read_list, project)
|
||||
end
|
||||
|
||||
def board
|
||||
@board ||= project.boards.find(params[:board_id])
|
||||
end
|
||||
|
||||
def list_params
|
||||
params.require(:list).permit(:label_id)
|
||||
end
|
||||
|
|
|
@ -1,12 +1,28 @@
|
|||
class Projects::BoardsController < Projects::ApplicationController
|
||||
include IssuableCollections
|
||||
|
||||
respond_to :html
|
||||
|
||||
before_action :authorize_read_board!, only: [:show]
|
||||
before_action :authorize_read_board!, only: [:index, :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
|
||||
::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
|
||||
|
||||
private
|
||||
|
@ -14,4 +30,8 @@ class Projects::BoardsController < Projects::ApplicationController
|
|||
def authorize_read_board!
|
||||
return access_denied! unless can?(current_user, :read_board, project)
|
||||
end
|
||||
|
||||
def serialize_as_json(resource)
|
||||
resource.as_json(only: [:id])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -38,12 +38,12 @@ class Projects::GraphsController < Projects::ApplicationController
|
|||
|
||||
@languages = @languages.map do |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),
|
||||
label: name,
|
||||
color: "##{color}",
|
||||
highlight: "##{color}"
|
||||
color: color,
|
||||
highlight: color
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
before_action :module_enabled
|
||||
before_action :merge_request, only: [
|
||||
: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 :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :builds, :pipelines]
|
||||
|
@ -31,6 +31,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
# Allow modify merge_request
|
||||
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]
|
||||
|
||||
def index
|
||||
|
@ -354,6 +356,25 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
render layout: false
|
||||
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
|
||||
pipeline = @merge_request.pipeline
|
||||
if pipeline
|
||||
|
@ -382,6 +403,30 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
render json: response
|
||||
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
|
||||
|
||||
def selected_target_project
|
||||
|
|
|
@ -20,6 +20,8 @@ class Projects::TagsController < Projects::ApplicationController
|
|||
def show
|
||||
@tag = @repository.find_tag(params[:id])
|
||||
|
||||
return render_404 unless @tag
|
||||
|
||||
@release = @project.releases.find_or_initialize_by(tag: @tag.name)
|
||||
@commit = @repository.commit(@tag.target)
|
||||
end
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
class SnippetsController < ApplicationController
|
||||
include ToggleAwardEmoji
|
||||
|
||||
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
|
||||
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download]
|
||||
|
||||
# Allow read snippet
|
||||
before_action :authorize_read_snippet!, only: [:show, :raw]
|
||||
before_action :authorize_read_snippet!, only: [:show, :raw, :download]
|
||||
|
||||
# Allow modify snippet
|
||||
before_action :authorize_update_snippet!, only: [:edit, :update]
|
||||
|
@ -12,7 +12,7 @@ class SnippetsController < ApplicationController
|
|||
# Allow destroy snippet
|
||||
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'
|
||||
respond_to :html
|
||||
|
@ -75,6 +75,14 @@ class SnippetsController < ApplicationController
|
|||
)
|
||||
end
|
||||
|
||||
def download
|
||||
send_data(
|
||||
@snippet.content,
|
||||
type: 'text/plain; charset=utf-8',
|
||||
filename: @snippet.sanitized_file_name
|
||||
)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def snippet
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
# 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
|
12
app/helpers/boards_helper.rb
Normal file
12
app/helpers/boards_helper.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
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
|
|||
#
|
||||
# See http://clipboardjs.com/#usage
|
||||
def clipboard_button(data = {})
|
||||
css_class = data[:class] || 'btn-clipboard btn-transparent'
|
||||
data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
|
||||
content_tag :button,
|
||||
icon('clipboard'),
|
||||
class: "btn btn-clipboard",
|
||||
class: "btn #{css_class}",
|
||||
data: data,
|
||||
type: :button,
|
||||
title: "Copy to Clipboard"
|
||||
title: 'Copy to Clipboard'
|
||||
end
|
||||
|
||||
def http_clone_button(project, placement = 'right', append_link: true)
|
||||
|
|
|
@ -72,6 +72,19 @@ module MergeRequestsHelper
|
|||
)
|
||||
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)
|
||||
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
|
|||
def version_index(merge_request_diff)
|
||||
@merge_request_diffs.size - @merge_request_diffs.index(merge_request_diff)
|
||||
end
|
||||
|
||||
def different_base?(version1, version2)
|
||||
version1 && version2 && version1.base_commit_sha != version2.base_commit_sha
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,7 @@ module Ci
|
|||
extend Ci::Model
|
||||
include HasStatus
|
||||
include Importable
|
||||
include AfterCommitQueue
|
||||
|
||||
self.table_name = 'ci_commits'
|
||||
|
||||
|
@ -56,6 +57,10 @@ module Ci
|
|||
pipeline.finished_at = Time.now
|
||||
end
|
||||
|
||||
before_transition do |pipeline|
|
||||
pipeline.update_duration
|
||||
end
|
||||
|
||||
after_transition [:created, :pending] => :running do |pipeline|
|
||||
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)
|
||||
|
@ -66,8 +71,8 @@ module Ci
|
|||
update_all(latest_build_finished_at: pipeline.finished_at)
|
||||
end
|
||||
|
||||
before_transition do |pipeline|
|
||||
pipeline.update_duration
|
||||
after_transition [:created, :pending, :running] => :success do |pipeline|
|
||||
pipeline.run_after_commit { PipelineSuccessWorker.perform_async(id) }
|
||||
end
|
||||
|
||||
after_transition do |pipeline, transition|
|
||||
|
@ -292,9 +297,9 @@ module Ci
|
|||
# Merge requests for which the current pipeline is running against
|
||||
# the merge request's latest commit.
|
||||
def merge_requests
|
||||
@merge_requests ||=
|
||||
project.merge_requests.where(source_branch: ref).
|
||||
select { |merge_request| merge_request.pipeline.try(:id) == id }
|
||||
@merge_requests ||= project.merge_requests
|
||||
.where(source_branch: self.ref)
|
||||
.select { |merge_request| merge_request.pipeline.try(:id) == self.id }
|
||||
end
|
||||
|
||||
def merge_requests_with_active_first
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
class CommitStatus < ActiveRecord::Base
|
||||
include HasStatus
|
||||
include Importable
|
||||
include AfterCommitQueue
|
||||
|
||||
self.table_name = 'ci_builds'
|
||||
|
||||
|
@ -85,25 +86,24 @@ class CommitStatus < ActiveRecord::Base
|
|||
end
|
||||
|
||||
after_transition do |commit_status, transition|
|
||||
commit_status.pipeline.try do |pipeline|
|
||||
break if transition.loopback?
|
||||
next if transition.loopback?
|
||||
|
||||
if commit_status.complete?
|
||||
ProcessPipelineWorker.perform_async(pipeline.id)
|
||||
commit_status.run_after_commit do
|
||||
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
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
after_transition [:created, :pending, :running] => :success do |commit_status|
|
||||
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -11,9 +11,10 @@ class Compare
|
|||
end
|
||||
end
|
||||
|
||||
def initialize(compare, project)
|
||||
def initialize(compare, project, straight: false)
|
||||
@compare = compare
|
||||
@project = project
|
||||
@straight = straight
|
||||
end
|
||||
|
||||
def commits
|
||||
|
@ -45,6 +46,18 @@ class Compare
|
|||
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)
|
||||
@compare.diffs(*args)
|
||||
end
|
||||
|
@ -58,9 +71,9 @@ class Compare
|
|||
|
||||
def diff_refs
|
||||
Gitlab::Diff::DiffRefs.new(
|
||||
base_sha: base_commit.try(:sha),
|
||||
start_sha: start_commit.try(:sha),
|
||||
head_sha: commit.try(:sha)
|
||||
base_sha: @straight ? start_commit_sha : base_commit_sha,
|
||||
start_sha: start_commit_sha,
|
||||
head_sha: head_commit_sha
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,8 @@ class CycleAnalytics
|
|||
include Gitlab::Database::Median
|
||||
include Gitlab::Database::DateTime
|
||||
|
||||
DEPLOYMENT_METRIC_STAGES = %i[production staging]
|
||||
|
||||
def initialize(project, from:)
|
||||
@project = project
|
||||
@from = from
|
||||
|
@ -66,7 +68,7 @@ class CycleAnalytics
|
|||
# cycle analytics stage.
|
||||
interval_query = Arel::Nodes::As.new(
|
||||
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)
|
||||
end
|
||||
|
@ -75,7 +77,7 @@ class CycleAnalytics
|
|||
# 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
|
||||
# automatically excluded.
|
||||
def base_query
|
||||
def base_query_for(name)
|
||||
arel_table = MergeRequestsClosingIssues.arel_table
|
||||
|
||||
# Load issues
|
||||
|
@ -91,7 +93,11 @@ class CycleAnalytics
|
|||
join(MergeRequest::Metrics.arel_table).
|
||||
on(MergeRequest.arel_table[:id].eq(MergeRequest::Metrics.arel_table[:merge_request_id]))
|
||||
|
||||
# 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))
|
||||
if DEPLOYMENT_METRIC_STAGES.include?(name)
|
||||
# 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))
|
||||
end
|
||||
|
||||
query
|
||||
end
|
||||
end
|
||||
|
|
|
@ -40,7 +40,14 @@ class Deployment < ActiveRecord::Base
|
|||
def includes_commit?(commit)
|
||||
return false unless commit
|
||||
|
||||
project.repository.is_ancestor?(commit.id, sha)
|
||||
# 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)
|
||||
rescue Rugged::OdbError
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def update_merge_request_metrics!
|
||||
|
@ -77,6 +84,10 @@ class Deployment < ActiveRecord::Base
|
|||
take
|
||||
end
|
||||
|
||||
def formatted_deployment_time
|
||||
created_at.to_time.in_time_zone.to_s(:medium)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ref_path
|
||||
|
|
|
@ -48,7 +48,22 @@ class Environment < ActiveRecord::Base
|
|||
self.name == "production"
|
||||
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
|
||||
"refs/environments/#{Shellwords.shellescape(name)}"
|
||||
end
|
||||
|
||||
def formatted_external_url
|
||||
return nil unless external_url
|
||||
|
||||
external_url.gsub(/\A.*?:\/\//, '')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -68,8 +68,10 @@ class Event < ActiveRecord::Base
|
|||
true
|
||||
elsif issue? || issue_note?
|
||||
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
|
||||
((merge_request? || note?) && target.present?) || milestone?
|
||||
milestone?
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -280,6 +282,10 @@ class Event < ActiveRecord::Base
|
|||
note? && target && target.for_issue?
|
||||
end
|
||||
|
||||
def merge_request_note?
|
||||
note? && target && target.for_merge_request?
|
||||
end
|
||||
|
||||
def project_snippet_note?
|
||||
target.for_snippet?
|
||||
end
|
||||
|
|
|
@ -692,12 +692,15 @@ class MergeRequest < ActiveRecord::Base
|
|||
def environments
|
||||
return [] unless diff_head_commit
|
||||
|
||||
environments = source_project.environments_for(
|
||||
source_branch, diff_head_commit)
|
||||
environments += target_project.environments_for(
|
||||
target_branch, diff_head_commit, with_tags: true)
|
||||
@environments ||=
|
||||
begin
|
||||
environments = source_project.environments_for(
|
||||
source_branch, diff_head_commit)
|
||||
environments += target_project.environments_for(
|
||||
target_branch, diff_head_commit, with_tags: true)
|
||||
|
||||
environments.uniq
|
||||
environments.uniq
|
||||
end
|
||||
end
|
||||
|
||||
def state_human_name
|
||||
|
|
|
@ -6,6 +6,9 @@ class MergeRequestDiff < ActiveRecord::Base
|
|||
# Prevent store of diff if commits amount more then 500
|
||||
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
|
||||
|
||||
state_machine :state, initial: :empty do
|
||||
|
@ -164,12 +167,24 @@ class MergeRequestDiff < ActiveRecord::Base
|
|||
self == merge_request.merge_request_diff
|
||||
end
|
||||
|
||||
def compare_with(sha)
|
||||
CompareService.new.execute(project, head_commit_sha, project, sha)
|
||||
def compare_with(sha, straight: true)
|
||||
# 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
|
||||
|
||||
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)
|
||||
commits.map(&:to_hash)
|
||||
end
|
||||
|
@ -200,7 +215,7 @@ class MergeRequestDiff < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def load_diffs(raw, options)
|
||||
if raw.respond_to?(:each)
|
||||
if valid_raw_diff?(raw)
|
||||
if paths = options[:paths]
|
||||
raw = raw.select do |diff|
|
||||
paths.include?(diff[:old_path]) || paths.include?(diff[:new_path])
|
||||
|
|
|
@ -61,15 +61,13 @@ class Namespace < ActiveRecord::Base
|
|||
def clean_path(path)
|
||||
path = path.dup
|
||||
# Get the email username by removing everything after an `@` sign.
|
||||
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/, "")
|
||||
path.gsub!(/@.*\z/, "")
|
||||
# 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.
|
||||
# Work around that by setting their username to "blank", followed by a counter.
|
||||
|
|
|
@ -16,6 +16,9 @@ class Project < ActiveRecord::Base
|
|||
|
||||
extend Gitlab::ConfigHelper
|
||||
|
||||
class BoardLimitExceeded < StandardError; end
|
||||
|
||||
NUMBER_OF_PERMITTED_BOARDS = 1
|
||||
UNKNOWN_IMPORT_URL = 'http://unknown.git'
|
||||
|
||||
cache_markdown_field :description, pipeline: :description
|
||||
|
@ -65,8 +68,7 @@ class Project < ActiveRecord::Base
|
|||
belongs_to :namespace
|
||||
|
||||
has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id'
|
||||
|
||||
has_one :board, dependent: :destroy
|
||||
has_many :boards, before_add: :validate_board_limit, dependent: :destroy
|
||||
|
||||
# Project services
|
||||
has_many :services
|
||||
|
@ -376,19 +378,9 @@ class Project < ActiveRecord::Base
|
|||
%r{(?<project>#{name_pattern}/#{name_pattern})}
|
||||
end
|
||||
|
||||
def trending(since = 1.month.ago)
|
||||
# By counting in the JOIN we don't expose the GROUP BY to the outer query.
|
||||
# This means that calls such as "any?" and "count" just return a number of
|
||||
# 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')
|
||||
def trending
|
||||
joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id').
|
||||
reorder('trending_projects.id ASC')
|
||||
end
|
||||
|
||||
def cached_count
|
||||
|
@ -838,11 +830,6 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def update_merge_requests(oldrev, newrev, ref, user)
|
||||
MergeRequests::RefreshService.new(self, user).
|
||||
execute(oldrev, newrev, ref)
|
||||
end
|
||||
|
||||
def valid_repo?
|
||||
repository.exists?
|
||||
rescue
|
||||
|
@ -1350,4 +1337,8 @@ class Project < ActiveRecord::Base
|
|||
|
||||
shared_projects.any?
|
||||
end
|
||||
|
||||
def validate_board_limit(board)
|
||||
raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ class ProjectGroupLink < ActiveRecord::Base
|
|||
belongs_to :group
|
||||
|
||||
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_access, presence: true
|
||||
validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true
|
||||
|
|
|
@ -111,8 +111,10 @@ class Repository
|
|||
def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
|
||||
ref ||= root_ref
|
||||
|
||||
# Limited to 1000 commits for now, could be parameterized?
|
||||
args = %W(#{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset} --max-count #{limit} --grep=#{query})
|
||||
args = %W(
|
||||
#{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?
|
||||
|
||||
git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:chomp)
|
||||
|
@ -717,6 +719,14 @@ class Repository
|
|||
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)
|
||||
args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
|
||||
names = Gitlab::Popen.popen(args, path_to_repo).first
|
||||
|
@ -1014,7 +1024,8 @@ class Repository
|
|||
root_ref_commit = commit(root_ref)
|
||||
|
||||
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
|
||||
nil
|
||||
end
|
||||
|
|
35
app/models/trending_project.rb
Normal file
35
app/models/trending_project.rb
Normal file
|
@ -0,0 +1,35 @@
|
|||
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
|
|||
end
|
||||
|
||||
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?
|
||||
return unless self.projects_limit.nil? || connection_default_value_defined
|
||||
|
||||
|
|
|
@ -40,7 +40,6 @@ class ProjectPolicy < BasePolicy
|
|||
can! :read_milestone
|
||||
can! :read_project_snippet
|
||||
can! :read_project_member
|
||||
can! :read_merge_request
|
||||
can! :read_note
|
||||
can! :create_project
|
||||
can! :create_issue
|
||||
|
@ -63,6 +62,7 @@ class ProjectPolicy < BasePolicy
|
|||
can! :read_pipeline
|
||||
can! :read_environment
|
||||
can! :read_deployment
|
||||
can! :read_merge_request
|
||||
end
|
||||
|
||||
# Permissions given when an user is team member of a project
|
||||
|
@ -98,7 +98,6 @@ class ProjectPolicy < BasePolicy
|
|||
can! :admin_milestone
|
||||
can! :admin_project_snippet
|
||||
can! :admin_project_member
|
||||
can! :admin_merge_request
|
||||
can! :admin_note
|
||||
can! :admin_wiki
|
||||
can! :admin_project
|
||||
|
@ -118,6 +117,7 @@ class ProjectPolicy < BasePolicy
|
|||
can! :read_container_image
|
||||
can! :build_download_code
|
||||
can! :build_read_container_image
|
||||
can! :read_merge_request
|
||||
end
|
||||
|
||||
def owner_access!
|
||||
|
@ -139,11 +139,18 @@ class ProjectPolicy < BasePolicy
|
|||
def team_access!(user)
|
||||
access = project.team.max_member_access(user.id)
|
||||
|
||||
guest_access! if access >= Gitlab::Access::GUEST
|
||||
reporter_access! if access >= Gitlab::Access::REPORTER
|
||||
team_member_reporter_access! if access >= Gitlab::Access::REPORTER
|
||||
developer_access! if access >= Gitlab::Access::DEVELOPER
|
||||
master_access! if access >= Gitlab::Access::MASTER
|
||||
return if access < Gitlab::Access::GUEST
|
||||
guest_access!
|
||||
|
||||
return if access < Gitlab::Access::REPORTER
|
||||
reporter_access!
|
||||
team_member_reporter_access!
|
||||
|
||||
return if access < Gitlab::Access::DEVELOPER
|
||||
developer_access!
|
||||
|
||||
return if access < Gitlab::Access::MASTER
|
||||
master_access!
|
||||
end
|
||||
|
||||
def archived_access!
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
module Boards
|
||||
class BaseService < ::BaseService
|
||||
delegate :board, to: :project
|
||||
end
|
||||
end
|
|
@ -1,16 +1,21 @@
|
|||
module Boards
|
||||
class CreateService < Boards::BaseService
|
||||
class CreateService < BaseService
|
||||
def execute
|
||||
create_board! unless project.board.present?
|
||||
project.board
|
||||
if project.boards.empty?
|
||||
create_board!
|
||||
else
|
||||
project.boards.first
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_board!
|
||||
project.create_board
|
||||
project.board.lists.create(list_type: :backlog)
|
||||
project.board.lists.create(list_type: :done)
|
||||
board = project.boards.create
|
||||
board.lists.create(list_type: :backlog)
|
||||
board.lists.create(list_type: :done)
|
||||
|
||||
board
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
module Boards
|
||||
module Issues
|
||||
class CreateService < Boards::BaseService
|
||||
def execute(list)
|
||||
params.merge!(label_ids: [list.label_id])
|
||||
create_issue
|
||||
class CreateService < BaseService
|
||||
def execute
|
||||
create_issue(params.merge(label_ids: [list.label_id]))
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Boards
|
||||
module Issues
|
||||
class ListService < Boards::BaseService
|
||||
class ListService < BaseService
|
||||
def execute
|
||||
issues = IssuesFinder.new(current_user, filter_params).execute
|
||||
issues = without_board_labels(issues) unless list.movable?
|
||||
|
@ -10,6 +10,10 @@ module Boards
|
|||
|
||||
private
|
||||
|
||||
def board
|
||||
@board ||= project.boards.find(params[:board_id])
|
||||
end
|
||||
|
||||
def list
|
||||
@list ||= board.lists.find(params[:id])
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Boards
|
||||
module Issues
|
||||
class MoveService < Boards::BaseService
|
||||
class MoveService < BaseService
|
||||
def execute(issue)
|
||||
return false unless can?(current_user, :update_issue, issue)
|
||||
return false unless valid_move?
|
||||
|
@ -10,6 +10,10 @@ module Boards
|
|||
|
||||
private
|
||||
|
||||
def board
|
||||
@board ||= project.boards.find(params[:board_id])
|
||||
end
|
||||
|
||||
def valid_move?
|
||||
moving_from_list.present? && moving_to_list.present? &&
|
||||
moving_from_list != moving_to_list
|
||||
|
@ -49,7 +53,7 @@ module Boards
|
|||
if moving_to_list.movable?
|
||||
moving_from_list.label_id
|
||||
else
|
||||
board.lists.movable.pluck(:label_id)
|
||||
project.boards.joins(:lists).merge(List.movable).pluck(:label_id)
|
||||
end
|
||||
|
||||
Array(label_ids).compact
|
||||
|
|
14
app/services/boards/list_service.rb
Normal file
14
app/services/boards/list_service.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
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
|
|
@ -1,23 +1,23 @@
|
|||
module Boards
|
||||
module Lists
|
||||
class CreateService < Boards::BaseService
|
||||
def execute
|
||||
class CreateService < BaseService
|
||||
def execute(board)
|
||||
List.transaction do
|
||||
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
|
||||
|
||||
private
|
||||
|
||||
def next_position
|
||||
def next_position(board)
|
||||
max_position = board.lists.movable.maximum(:position)
|
||||
max_position.nil? ? 0 : max_position.succ
|
||||
end
|
||||
|
||||
def create_list(label, position)
|
||||
def create_list(board, label, position)
|
||||
board.lists.create(label: label, list_type: :label, position: position)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
module Boards
|
||||
module Lists
|
||||
class DestroyService < Boards::BaseService
|
||||
class DestroyService < BaseService
|
||||
def execute(list)
|
||||
return false unless list.destroyable?
|
||||
|
||||
@board = list.board
|
||||
|
||||
list.with_lock do
|
||||
decrement_higher_lists(list)
|
||||
remove_list(list)
|
||||
|
@ -12,6 +14,8 @@ module Boards
|
|||
|
||||
private
|
||||
|
||||
attr_reader :board
|
||||
|
||||
def decrement_higher_lists(list)
|
||||
board.lists.movable.where('position > ?', list.position)
|
||||
.update_all('position = position - 1')
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
module Boards
|
||||
module Lists
|
||||
class GenerateService < Boards::BaseService
|
||||
def execute
|
||||
class GenerateService < BaseService
|
||||
def execute(board)
|
||||
return false unless board.lists.movable.empty?
|
||||
|
||||
List.transaction do
|
||||
label_params.each { |params| create_list(params) }
|
||||
label_params.each { |params| create_list(board, params) }
|
||||
end
|
||||
|
||||
true
|
||||
|
@ -13,9 +13,9 @@ module Boards
|
|||
|
||||
private
|
||||
|
||||
def create_list(params)
|
||||
def create_list(board, 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
|
||||
|
||||
def find_or_create_label(params)
|
||||
|
|
9
app/services/boards/lists/list_service.rb
Normal file
9
app/services/boards/lists/list_service.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
module Boards
|
||||
module Lists
|
||||
class ListService < BaseService
|
||||
def execute(board)
|
||||
board.lists
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,8 @@
|
|||
module Boards
|
||||
module Lists
|
||||
class MoveService < Boards::BaseService
|
||||
class MoveService < BaseService
|
||||
def execute(list)
|
||||
@board = list.board
|
||||
@old_position = list.position
|
||||
@new_position = params[:position]
|
||||
|
||||
|
@ -16,7 +17,7 @@ module Boards
|
|||
|
||||
private
|
||||
|
||||
attr_reader :old_position, :new_position
|
||||
attr_reader :board, :old_position, :new_position
|
||||
|
||||
def valid_move?
|
||||
new_position.present? && new_position != old_position &&
|
||||
|
|
|
@ -16,6 +16,8 @@ module Ci
|
|||
process_stage(index)
|
||||
end
|
||||
|
||||
@pipeline.update_status
|
||||
|
||||
# Return a flag if a when builds got enqueued
|
||||
new_builds.flatten.any?
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@ require 'securerandom'
|
|||
# Compare 2 branches for one repo or between repositories
|
||||
# and return Gitlab::Git::Compare object that responds to commits and diffs
|
||||
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)
|
||||
return unless source_commit
|
||||
|
||||
|
@ -23,9 +23,10 @@ class CompareService
|
|||
raw_compare = Gitlab::Git::Compare.new(
|
||||
target_project.repository.raw_repository,
|
||||
target_branch,
|
||||
source_sha
|
||||
source_sha,
|
||||
straight
|
||||
)
|
||||
|
||||
Compare.new(raw_compare, target_project)
|
||||
Compare.new(raw_compare, target_project, straight: straight)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -63,13 +63,12 @@ class GitPushService < BaseService
|
|||
protected
|
||||
|
||||
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)
|
||||
SystemHooksService.new.execute_hooks(build_push_data_system_hook.dup, :push_hooks)
|
||||
@project.execute_hooks(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)
|
||||
end
|
||||
|
||||
|
@ -148,16 +147,6 @@ class GitPushService < BaseService
|
|||
push_commits)
|
||||
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?
|
||||
# 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])
|
||||
|
|
|
@ -2,14 +2,14 @@ module MergeRequests
|
|||
class AddTodoWhenBuildFailsService < MergeRequests::BaseService
|
||||
# Adds a todo to the parent merge_request when a CI build fails
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
# Closes any pending build failed todos for the parent MRs when a build is retried
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
|
35
app/services/merge_requests/assign_issues_service.rb
Normal file
35
app/services/merge_requests/assign_issues_service.rb
Normal file
|
@ -0,0 +1,35 @@
|
|||
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
|
|||
super(:merge_request)
|
||||
end
|
||||
|
||||
def merge_request_from(commit_status)
|
||||
branches = commit_status.ref
|
||||
def merge_requests_for(branch)
|
||||
origin_merge_requests = @project.origin_merge_requests
|
||||
.opened.where(source_branch: branch).to_a
|
||||
|
||||
# This is for ref-less builds
|
||||
branches ||= @project.repository.branch_names_contains(commit_status.sha)
|
||||
fork_merge_requests = @project.fork_merge_requests
|
||||
.opened.where(source_branch: branch).to_a
|
||||
|
||||
return [] if branches.blank?
|
||||
|
||||
merge_requests = @project.origin_merge_requests.opened.where(source_branch: branches).to_a
|
||||
merge_requests += @project.fork_merge_requests.opened.where(source_branch: branches).to_a
|
||||
|
||||
merge_requests.uniq.select(&:source_project)
|
||||
(origin_merge_requests + fork_merge_requests)
|
||||
.uniq.select(&:source_project)
|
||||
end
|
||||
|
||||
def each_merge_request(commit_status)
|
||||
merge_request_from(commit_status).each do |merge_request|
|
||||
def pipeline_merge_requests(pipeline)
|
||||
merge_requests_for(pipeline.ref).each do |merge_request|
|
||||
next unless pipeline == merge_request.pipeline
|
||||
|
||||
yield merge_request
|
||||
end
|
||||
end
|
||||
|
||||
def commit_status_merge_requests(commit_status)
|
||||
merge_requests_for(commit_status.ref).each do |merge_request|
|
||||
pipeline = merge_request.pipeline
|
||||
|
||||
next unless pipeline
|
||||
next unless pipeline.sha == commit_status.sha
|
||||
|
||||
yield merge_request, pipeline
|
||||
yield merge_request
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ module MergeRequests
|
|||
merge_request = MergeRequest.new(params)
|
||||
|
||||
# Set MR attributes
|
||||
merge_request.can_be_created = false
|
||||
merge_request.can_be_created = true
|
||||
merge_request.compare_commits = []
|
||||
merge_request.source_project = project unless merge_request.source_project
|
||||
|
||||
|
@ -22,6 +22,12 @@ module MergeRequests
|
|||
return build_failed(merge_request, message)
|
||||
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(
|
||||
merge_request.source_project,
|
||||
merge_request.source_branch,
|
||||
|
@ -29,17 +35,8 @@ module MergeRequests
|
|||
merge_request.target_branch,
|
||||
)
|
||||
|
||||
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
|
||||
else
|
||||
merge_request.can_be_created = false
|
||||
end
|
||||
merge_request.compare_commits = compare.commits
|
||||
merge_request.compare = compare
|
||||
|
||||
set_title_and_description(merge_request)
|
||||
end
|
||||
|
@ -89,6 +86,8 @@ module MergeRequests
|
|||
end
|
||||
end
|
||||
|
||||
merge_request.title = merge_request.wip_title if commits.empty?
|
||||
|
||||
merge_request
|
||||
end
|
||||
|
||||
|
|
|
@ -18,12 +18,13 @@ module MergeRequests
|
|||
merge_request.save
|
||||
end
|
||||
|
||||
# Triggers the automatic merge of merge_request once the build succeeds
|
||||
def trigger(commit_status)
|
||||
each_merge_request(commit_status) do |merge_request, pipeline|
|
||||
# Triggers the automatic merge of merge_request once the pipeline succeeds
|
||||
def trigger(pipeline)
|
||||
return unless pipeline.success?
|
||||
|
||||
pipeline_merge_requests(pipeline) do |merge_request|
|
||||
next unless merge_request.merge_when_build_succeeds?
|
||||
next unless merge_request.mergeable?
|
||||
next unless pipeline.success?
|
||||
|
||||
MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params)
|
||||
end
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue