Commit 6e6f2313 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'master' into fix/gb/fix-import-export-restoring-associations

* master: (114 commits)
parents 7e117b4c 6d972724
...@@ -431,6 +431,7 @@ ee_compat_check: ...@@ -431,6 +431,7 @@ ee_compat_check:
- master - master
- tags - tags
- /^[\d-]+-stable(-ee)?/ - /^[\d-]+-stable(-ee)?/
- /^security-/
- branches@gitlab-org/gitlab-ee - branches@gitlab-org/gitlab-ee
- branches@gitlab/gitlab-ee - branches@gitlab/gitlab-ee
retry: 0 retry: 0
...@@ -508,7 +509,7 @@ db:rollback-mysql: ...@@ -508,7 +509,7 @@ db:rollback-mysql:
<<: *db-rollback <<: *db-rollback
<<: *use-mysql <<: *use-mysql
.db-seed_fu: &db-seed_fu .gitlab-setup: &gitlab-setup
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs-and-qa <<: *except-docs-and-qa
<<: *pull-cache <<: *pull-cache
...@@ -529,12 +530,12 @@ db:rollback-mysql: ...@@ -529,12 +530,12 @@ db:rollback-mysql:
paths: paths:
- log/development.log - log/development.log
db:seed_fu-pg: gitlab:setup-pg:
<<: *db-seed_fu <<: *gitlab-setup
<<: *use-pg <<: *use-pg
db:seed_fu-mysql: gitlab:setup-mysql:
<<: *db-seed_fu <<: *gitlab-setup
<<: *use-mysql <<: *use-mysql
# Frontend-related jobs # Frontend-related jobs
......
...@@ -3,6 +3,7 @@ inherit_gem: ...@@ -3,6 +3,7 @@ inherit_gem:
- rubocop-default.yml - rubocop-default.yml
inherit_from: .rubocop_todo.yml inherit_from: .rubocop_todo.yml
require: ./rubocop/rubocop
AllCops: AllCops:
TargetRailsVersion: 4.2 TargetRailsVersion: 4.2
...@@ -24,8 +25,10 @@ Gitlab/ModuleWithInstanceVariables: ...@@ -24,8 +25,10 @@ Gitlab/ModuleWithInstanceVariables:
Exclude: Exclude:
# We ignore Rails helpers right now because it's hard to workaround it # We ignore Rails helpers right now because it's hard to workaround it
- app/helpers/**/*_helper.rb - app/helpers/**/*_helper.rb
- ee/app/helpers/**/*_helper.rb
# We ignore Rails mailers right now because it's hard to workaround it # We ignore Rails mailers right now because it's hard to workaround it
- app/mailers/emails/**/*.rb - app/mailers/emails/**/*.rb
- ee/**/emails/**/*.rb
# We ignore spec helpers because it usually doesn't matter # We ignore spec helpers because it usually doesn't matter
- spec/support/**/*.rb - spec/support/**/*.rb
- features/steps/**/*.rb - features/steps/**/*.rb
...@@ -553,7 +553,7 @@ the feature you contribute through all of these steps. ...@@ -553,7 +553,7 @@ the feature you contribute through all of these steps.
1. Description explaining the relevancy (see following item) 1. Description explaining the relevancy (see following item)
1. Working and clean code that is commented where needed 1. Working and clean code that is commented where needed
1. [Unit and system tests][testing] that pass on the CI server 1. [Unit, integration, and system tests][testing] that pass on the CI server
1. Performance/scalability implications have been considered, addressed, and tested 1. Performance/scalability implications have been considered, addressed, and tested
1. [Documented][doc-styleguide] in the `/doc` directory 1. [Documented][doc-styleguide] in the `/doc` directory
1. [Changelog entry added][changelog], if necessary 1. [Changelog entry added][changelog], if necessary
......
...@@ -718,7 +718,7 @@ GEM ...@@ -718,7 +718,7 @@ GEM
redis-store (>= 1.3, < 2) redis-store (>= 1.3, < 2)
redis-namespace (1.5.2) redis-namespace (1.5.2)
redis (~> 3.0, >= 3.0.4) redis (~> 3.0, >= 3.0.4)
redis-rack (2.0.3) redis-rack (2.0.4)
rack (>= 1.5, < 3) rack (>= 1.5, < 3)
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
redis-rails (5.0.2) redis-rails (5.0.2)
......
{"iconCount":186,"spriteSize":84748,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","bookmark","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder-o","folder-open","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","image-comment-light","import","issue-block","issue-child","issue-close","issue-duplicate","issue-external","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","podcast","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]} {"iconCount":189,"spriteSize":85766,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","bookmark","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder-o-open","folder-o","folder-open","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","image-comment-light","import","issue-block","issue-child","issue-close","issue-duplicate","issue-external","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","podcast","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","staged","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","unstaged","user","users","volume-up","warning","work"]}
\ No newline at end of file \ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
<svg xmlns="http://www.w3.org/2000/svg" width="430" height="300"><g fill="none" fill-rule="evenodd" transform="translate(35 29)"><path fill="#EEE" fill-rule="nonzero" d="M90 23a2 2 0 1 1 0-4h10a2 2 0 0 1 0 4H90zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h1a11.98 11.98 0 0 1 9.457 4.612 2 2 0 0 1-3.151 2.464A7.981 7.981 0 0 0 331 23h-1zm9 11.39a2 2 0 0 1 4 0v10a2 2 0 0 1-4 0v-10zm0 180a2 2 0 1 1 4 0V223c0 .56-.038 1.114-.114 1.662a2 2 0 0 1-3.962-.55A8.21 8.21 0 0 0 339 223v-8.61zm-4.769 15.931a2 2 0 0 1 1.618 3.658A11.967 11.967 0 0 1 331 235h-5.782a2 2 0 0 1 0-4H331c1.13 0 2.224-.233 3.231-.679zm-19.013.679a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zM115 231a2 2 0 0 1 0 4h-10a2 2 0 0 1 0-4h10zm-26.2 4c.131-.646.2-1.315.2-2v-2h4a2 2 0 0 1 0 4h-4.2z"/><path fill="#EEE" fill-rule="nonzero" d="M103 211h258a6 6 0 0 0 6-6V63a6 6 0 0 0-6-6H166a5 5 0 0 1-5-5v-8.5a5.5 5.5 0 0 0-5.5-5.5H109a6 6 0 0 0-6 6v167zm62-167.5V52a1 1 0 0 0 1 1h195c5.523 0 10 4.477 10 10v142c0 5.523-4.477 10-10 10H99V44c0-5.523 4.477-10 10-10h46.5a9.5 9.5 0 0 1 9.5 9.5z"/><rect width="40" height="4" x="118" y="78" fill="#6B4FBB" rx="2"/><rect width="30" height="4" x="118" y="90" fill="#EFEDF8" rx="2"/><rect width="30" height="4" x="153" y="90" fill="#E1DBF1" rx="2"/><rect width="150" height="4" x="118" y="102" fill="#EFEDF8" rx="2"/><rect width="90" height="4" x="118" y="114" fill="#E1DBF1" rx="2"/><rect width="60" height="4" x="118" y="138" fill="#EFEDF8" rx="2"/><rect width="20" height="4" x="118" y="150" fill="#6B4FBB" rx="2"/><rect width="20" height="4" x="144" y="150" fill="#C3B8E3" rx="2"/><rect width="20" height="4" x="170" y="150" fill="#E1DBF1" rx="2"/><rect width="130" height="4" x="118" y="162" fill="#EFEDF8" rx="2"/><rect width="30" height="4" x="118" y="174" fill="#C3B8E3" rx="2"/><rect width="30" height="4" x="154" y="174" fill="#EFEDF8" rx="2"/><rect width="30" height="4" x="190" y="174" fill="#EFEDF8" rx="2"/><rect width="40" height="4" x="118" y="186" fill="#E1DBF1" rx="2"/><path fill="#F9F9F9" d="M89 24.292l11.434 19.326v170.326L89 226.336V24.292z"/><path fill="#EEE" fill-rule="nonzero" d="M89 229.286v-5.9l9.434-10.223V44.165L89 28.22v-7.856l13.434 22.707v171.655L89 229.286zM10 4a6 6 0 0 0-6 6v223a6 6 0 0 0 6 6h69a6 6 0 0 0 6-6V10a6 6 0 0 0-6-6H10zm0-4h69c5.523 0 10 4.477 10 10v223c0 5.523-4.477 10-10 10H10c-5.523 0-10-4.477-10-10V10C0 4.477 4.477 0 10 0z"/><circle cx="25" cy="23" r="11" fill="#FEF0E8"/><path fill="#FEE1D3" d="M46 17h16a2 2 0 1 1 0 4H46a2 2 0 1 1 0-4zm0 8h27a2 2 0 1 1 0 4H46a2 2 0 1 1 0-4z"/><path fill="#EEE" d="M16 50h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H30a2 2 0 1 1 0-4zm-4 12h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H40a2 2 0 1 1 0-4zM26 78h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H40a2 2 0 1 1 0-4z"/><g transform="translate(14 110)"><rect width="8" height="8" fill="#FEE1D3" rx="2"/><rect width="28" height="4" x="14" y="2" fill="#FEF0E8" rx="2"/></g><path fill="#EEE" d="M16 140h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H30a2 2 0 1 1 0-4zm-14 14h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H30a2 2 0 1 1 0-4zm-14 14h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H30a2 2 0 1 1 0-4zm-14 14h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H30a2 2 0 1 1 0-4z"/><g transform="translate(24 124)"><rect width="8" height="8" fill="#FEE1D3" rx="2"/><rect width="28" height="4" x="14" y="2" fill="#FEF0E8" rx="2"/></g><g fill="#FC6D26" transform="translate(24 92)"><rect width="8" height="8" rx="2"/><rect width="28" height="4" x="14" y="2" rx="2"/></g><path fill="#FDC4A8" fill-rule="nonzero" d="M152 50.5a4.5 4.5 0 1 1 0-9 4.5 4.5 0 0 1 0 9zm0-3a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="386" height="298" viewBox="0 0 386 298" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M4 51h16v15.997A5.003 5.003 0 0 1 15.003 72H8.997A5.005 5.005 0 0 1 4 66.997V51z"/><rect id="b" width="24" height="10" y="44" rx="3"/></defs><g fill="none" fill-rule="evenodd" transform="translate(0 3)"><g transform="rotate(15 23.151 968.24)"><rect width="53" height="44" fill="#FFF" stroke="#FDE5D8" stroke-width="3" stroke-linecap="round" rx="5"/><path fill="#FDE5D8" d="M29.5 28.3l2.758-3.861c.962-1.347 2.527-1.34 3.484 0l6.516 9.122c.962 1.347.399 2.439-1.252 2.439H17.994c-1.653 0-2.21-1.099-1.252-2.439l6.516-9.122c.962-1.347 2.527-1.34 3.484 0L29.5 28.3z" opacity=".6"/><circle cx="16" cy="16" r="6" fill="#FDB997"/></g><g transform="scale(-1 1) rotate(25 -75.08 -334.15)"><rect width="3" height="11" x="12.45" y="23.45" fill="#6B4FBB" transform="rotate(45 13.95 28.95)" rx="1.5"/><rect width="3" height="14" x="9.45" y="15.45" fill="#6B4FBB" transform="rotate(45 10.95 22.45)" rx="1.5"/><path fill="#FFF" stroke="#E1DCF1" stroke-width="3" d="M16 39.6C6.871 37.747 0 29.676 0 20 0 8.954 8.954 0 20 0s20 8.954 20 20c0 8.955-5.886 16.536-14 19.084v15.91A5.007 5.007 0 0 1 21 60c-2.761 0-5-2.244-5-5.006V39.6zm4-7.6c6.627 0 12-5.373 12-12S26.627 8 20 8 8 13.373 8 20s5.373 12 12 12z"/></g><g transform="scale(1 -1) rotate(-15 -383.616 -172.407)"><path stroke="#FDE5D8" stroke-width="3" d="M1.5 38.5h9V4c0-1.378-1.12-2.5-2.496-2.5H3.996A2.503 2.503 0 0 0 1.5 4v34.5z"/><rect width="2" height="27" x="5" y="7" fill="#FDA77D" opacity=".8" rx="1"/><path stroke="#FDE5D8" stroke-width="3" d="M2.427 41.553h7.146L6 48.699l-3.573-7.146z"/></g><g transform="rotate(-30 420.145 -545.422)"><path fill="#FFF" stroke="#FDE5D8" stroke-width="3" d="M9 3c0-1.657 1.347-3 3-3 1.657 0 3 1.352 3 3v43H9V3z"/><use fill="#FFF" xlink:href="#a"/><path stroke="#FDE5D8" stroke-width="3" d="M5.5 52.5v14.497A3.505 3.505 0 0 0 8.997 70.5h6.006a3.503 3.503 0 0 0 3.497-3.503V52.5h-13z"/><rect width="2" height="14" x="9" y="51" fill="#FDA77D" rx="1"/><rect width="2" height="14" x="13" y="51" fill="#FDA77D" rx="1"/><use fill="#FFF" xlink:href="#b"/><rect width="21" height="7" x="1.5" y="45.5" stroke="#FDE5D8" stroke-width="3" rx="3"/></g><g transform="translate(72 97.488)"><rect width="125" height="160" fill="#FFF" stroke="#E5E5E5" stroke-width="4" stroke-linecap="round" rx="10"/><rect width="125" height="160" x="125" fill="#FFF" stroke="#E5E5E5" stroke-width="4" stroke-linecap="round" rx="10"/><path fill="#FFF" stroke="#E5E5E5" stroke-width="4" d="M7 12.008C7 8.69 9.686 6 12.993 6H125v148H12.993C9.683 154 7 151.305 7 147.992V12.008zm236 0C243 8.69 240.314 6 237.007 6H125v148h112.007c3.31 0 5.993-2.695 5.993-6.008V12.008z" stroke-linecap="round"/><rect width="84" height="42" x="142" y="29" stroke="#EEE" stroke-width="4" rx="3"/><rect width="88" height="4" x="141" y="93" fill="#E5E5E5" rx="2"/><rect width="88" height="4" x="141" y="107" fill="#BFBFBF" rx="2"/><rect width="56" height="4" x="141" y="121" fill="#E5E5E5" rx="2"/><rect width="56" height="4" x="22" y="93" fill="#E5E5E5" rx="2"/><rect width="26" height="4" x="22" y="27" fill="#BFBFBF" rx="2"/><rect width="56" height="4" x="22" y="41" fill="#E5E5E5" rx="2"/><rect width="36" height="4" x="22" y="55" fill="#BFBFBF" rx="2"/><rect width="56" height="4" x="22" y="69" fill="#E5E5E5" rx="2"/><rect width="36" height="4" x="22" y="107" fill="#E5E5E5" rx="2"/><rect width="56" height="4" x="22" y="121" fill="#BFBFBF" rx="2"/></g><path stroke="#B5A7DD" stroke-width="2.5" d="M23.139 182.922l-1.347-.6a2.004 2.004 0 0 1-1.02-2.64l.815-1.831a1.995 1.995 0 0 1 2.645-1.01l1.308.583a9.959 9.959 0 0 1 2.177-1.876l-.376-1.402a2.004 2.004 0 0 1 1.41-2.455l1.937-.519a1.995 1.995 0 0 1 2.449 1.421l.375 1.402a9.959 9.959 0 0 1 2.824.536l.84-1.158a2.004 2.004 0 0 1 2.796-.448l1.622 1.178a1.995 1.995 0 0 1 .437 2.797l-.867 1.193a9.946 9.946 0 0 1 1.341 2.541l1.461-.05a2.004 2.004 0 0 1 2.075 1.926l.07 2.003a1.995 1.995 0 0 1-1.935 2.067l-1.445.05c-.256.93-.644 1.817-1.15 2.632l.944 1.087a2.004 2.004 0 0 1-.191 2.825l-1.513 1.315a1.995 1.995 0 0 1-2.824-.204l-.963-1.108a10.084 10.084 0 0 1-2.776.744l-.28 1.441a2.004 2.004 0 0 1-2.344 1.588l-1.967-.382a1.995 1.995 0 0 1-1.579-2.35l.275-1.414a10.044 10.044 0 0 1-2.312-1.704l-1.277.678a2.004 2.004 0 0 1-2.709-.822l-.94-1.77a1.995 1.995 0 0 1 .833-2.705l1.29-.687a9.946 9.946 0 0 1-.11-2.872zm10.98 4.93a4 4 0 1 0-2.07-7.727 4 4 0 0 0 2.07 7.728z"/><ellipse cx="197" cy="289.988" fill="#F9F9F9" rx="125" ry="4.5"/><path fill="#6B4FBB" d="M164 100.492a3.002 3.002 0 0 1 3.001-3.004H183a3.006 3.006 0 0 1 3.001 3.004v34.988c0 2.213-1.45 2.954-3.24 1.651l-7.76-5.643-7.76 5.643c-1.789 1.302-3.24.566-3.24-1.651v-34.988z"/><g opacity=".2"><path fill="#FC8A51" d="M5.747 234.768l-2.688 1.114c-1.017.422-1.803-.134-1.754-1.228l.128-2.907-1.115-2.688c-.422-1.017.135-1.803 1.229-1.754l2.907.128 2.687-1.115c1.018-.422 1.803.135 1.755 1.229l-.128 2.907 1.114 2.687c.422 1.018-.134 1.803-1.228 1.755l-2.907-.128zM191.564 37.953l-3.72.164c-1.326.059-1.992-.88-1.48-2.115l1.426-3.438-.164-3.72c-.059-1.326.88-1.992 2.115-1.48l3.438 1.426 3.72-.164c1.326-.059 1.992.88 1.48 2.114l-1.426 3.44.164 3.719c.059 1.326-.88 1.992-2.114 1.48l-3.44-1.426z"/><path fill="#6B4FBB" d="M348.789 75.876l-1.967-2.144c-.744-.812-.49-1.74.555-2.07l2.775-.873 2.144-1.967c.812-.744 1.74-.49 2.07.555l.873 2.775 1.967 2.144c.744.812.49 1.74-.555 2.07l-2.775.873-2.144 1.967c-.812.745-1.74.49-2.07-.555l-.873-2.775zm9.261 164.735l-2.907-.125c-1.1-.048-1.577-.884-1.07-1.855l1.344-2.58.126-2.908c.047-1.1.883-1.577 1.855-1.07l2.58 1.344 2.907.126c1.1.047 1.577.883 1.07 1.855l-1.344 2.58-.125 2.907c-.048 1.1-.884 1.577-1.856 1.07l-2.58-1.344zM88.789 75.876l-1.967-2.144c-.744-.812-.49-1.74.555-2.07l2.775-.873 2.144-1.967c.812-.744 1.74-.49 2.07.555l.873 2.775 1.967 2.144c.744.812.49 1.74-.555 2.07l-2.775.873-2.144 1.967c-.812.745-1.74.49-2.07-.555l-.873-2.775z"/></g></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="412" height="260" viewBox="0 0 412 260" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M6.447.894L12 12H0L5.553.894a.5.5 0 0 1 .894 0z"/></defs><g fill="none" fill-rule="evenodd"><path fill="#FEF0E8" fill-rule="nonzero" d="M338 50.287C322.695 41.45 303.124 46.694 294.287 62c-8.836 15.305-3.592 34.876 11.713 43.712 15.306 8.837 34.877 3.593 43.713-11.712 8.837-15.306 3.593-34.877-11.713-43.713zm2-3.464C357.22 56.763 363.118 78.78 353.177 96c-9.941 17.218-31.958 23.118-49.177 13.176-17.218-9.94-23.118-31.958-13.177-49.176C300.764 42.78 322.782 36.88 340 46.823z"/><g transform="rotate(-150 171.003 8.53)"><path fill="#FC6D26" fill-rule="nonzero" d="M4 16v25a2 2 0 1 0 4 0V16H4zm8-4v29a6 6 0 1 1-12 0V12h12z"/><use fill="#D8D8D8" xlink:href="#a"/><path stroke="#FDC4A8" stroke-width="4" d="M6 4.472L3.236 10h5.528L6 4.472z"/><path fill="#FC6D26" d="M9 6L6.447.894a.5.5 0 0 0-.894 0L3 6c.836.628 1.874 1 3 1a4.978 4.978 0 0 0 3-1z"/></g><path fill="#F9F9F9" d="M263.116 237.116A10.002 10.002 0 0 1 254 243h-86c-11.046 0-20-8.954-20-20V121c0-4.056 2.414-7.547 5.884-9.116A9.964 9.964 0 0 0 153 116v106c0 8.837 7.163 16 16 16h90c1.467 0 2.86-.316 4.116-.884z"/><path fill="#EEE" fill-rule="nonzero" d="M214.5 106H163c-5.523 0-10 4.477-10 10v106c0 8.837 7.163 16 16 16h90c5.523 0 10-4.477 10-10v-17.999a10.036 10.036 0 0 1-4 3.167V228a6 6 0 0 1-6 6h-90c-6.627 0-12-5.373-12-12V116a6 6 0 0 1 6-6h7v-4h44.5z"/><path fill="#EEE" fill-rule="nonzero" d="M260 218.268V214h-90a6 6 0 0 0 0 12h86a4 4 0 0 0 4-4v-.268a1.99 1.99 0 0 1-1 .268h-50a2 2 0 0 1 0-4h50c.364 0 .706.097 1 .268zM170 210h90.5a3.5 3.5 0 0 1 3.5 3.5v8.5a8 8 0 0 1-8 8h-86c-5.523 0-10-4.477-10-10s4.477-10 10-10z"/><path fill="#EEE" fill-rule="nonzero" d="M174 110v100h87a6 6 0 0 0 6-6v-88a6 6 0 0 0-6-6h-87zm-4-4h91c5.523 0 10 4.477 10 10v88c0 5.523-4.477 10-10 10h-91V106z"/><path fill="#EFEDF8" d="M230 99h18a6 6 0 0 1 6 6v31.35a3 3 0 0 1-4.68 2.484l-9.277-6.274a1.5 1.5 0 0 0-1.664-.01l-9.731 6.395a3 3 0 0 1-4.648-2.507V105a6 6 0 0 1 6-6z"/><path fill="#C3B8E3" fill-rule="nonzero" d="M236.182 129.207a5.5 5.5 0 0 1 6.102.04l7.716 5.219V105a2 2 0 0 0-2-2h-18a2 2 0 0 0-2 2v29.584l8.182-5.377zM230 99h18a6 6 0 0 1 6 6v31.35a3 3 0 0 1-4.68 2.484l-9.277-6.274a1.5 1.5 0 0 0-1.664-.01l-9.731 6.395a3 3 0 0 1-4.648-2.507V105a6 6 0 0 1 6-6z"/><g fill-rule="nonzero"><path fill="#EFEDF8" d="M156 74c14.912 0 27-12.088 27-27s-12.088-27-27-27-27 12.088-27 27 12.088 27 27 27zm0 4c-17.12 0-31-13.88-31-31s13.88-31 31-31 31 13.88 31 31-13.88 31-31 31z"/><path fill="#6B4FBB" d="M147.535 44.916l-.116 1.086a8.446 8.446 0 0 0 .093 2.44l.2 1.08-2.262 1.202a.495.495 0 0 0-.213.678l.941 1.77c.128.239.434.332.68.201l2.25-1.196.785.775a8.544 8.544 0 0 0 1.967 1.45l.975.522-.486 2.5a.495.495 0 0 0 .392.59l1.968.383a.504.504 0 0 0 .585-.401l.489-2.515 1.086-.13a8.584 8.584 0 0 0 2.363-.633l1.005-.43 1.68 1.933a.495.495 0 0 0 .708.055l1.513-1.315a.504.504 0 0 0 .044-.708l-1.67-1.922.583-.94c.431-.696.761-1.45.978-2.239l.292-1.063 2.547-.089a.495.495 0 0 0 .488-.515l-.07-2.003a.504.504 0 0 0-.523-.48l-2.56.09-.367-1.037a8.446 8.446 0 0 0-1.139-2.159l-.644-.882 1.509-2.076a.495.495 0 0 0-.106-.702l-1.621-1.178a.504.504 0 0 0-.7.116l-1.494 2.057-1.05-.362a8.459 8.459 0 0 0-2.398-.455l-1.1-.047-.66-2.466a.495.495 0 0 0-.613-.36l-1.936.519a.504.504 0 0 0-.35.617l.661 2.466-.93.59a8.459 8.459 0 0 0-1.848 1.594l-.728.838-2.322-1.034a.495.495 0 0 0-.665.25l-.815 1.83a.504.504 0 0 0 .26.661l2.344 1.044zm-3.565 1.697a3.504 3.504 0 0 1-1.78-4.622l.815-1.83a3.495 3.495 0 0 1 4.626-1.77l.346.154c.259-.245.529-.477.81-.697l-.106-.394a3.504 3.504 0 0 1 2.471-4.292l1.936-.519a3.495 3.495 0 0 1 4.286 2.481l.106.395c.353.05.703.116 1.05.198l.222-.306a3.504 3.504 0 0 1 4.89-.78l1.622 1.178a3.495 3.495 0 0 1 .769 4.892l-.258.355c.184.312.354.633.508.962l.42-.014a3.504 3.504 0 0 1 3.625 3.373l.07 2.003a3.495 3.495 0 0 1-3.382 3.618l-.4.014c-.127.332-.27.659-.426.978l.256.294a3.504 3.504 0 0 1-.34 4.941l-1.512 1.315a3.495 3.495 0 0 1-4.94-.351l-.283-.325a11.669 11.669 0 0 1-1.05.28l-.082.424a3.504 3.504 0 0 1-4.103 2.774l-1.967-.382a3.495 3.495 0 0 1-2.765-4.11l.075-.383a11.547 11.547 0 0 1-.858-.633l-.354.188a3.504 3.504 0 0 1-4.738-1.442l-.94-1.77a3.495 3.495 0 0 1 1.453-4.734l.37-.197a11.436 11.436 0 0 1-.041-1.088l-.4-.178zm13.326 5.608a5.5 5.5 0 1 1-2.847-10.625 5.5 5.5 0 0 1 2.847 10.625zm-.776-2.898a2.5 2.5 0 1 0-1.294-4.83 2.5 2.5 0 0 0 1.294 4.83z"/></g><g fill-rule="nonzero"><path fill="#EFEDF8" d="M326.979 222.047c14.403 3.86 29.209-4.688 33.068-19.092 3.86-14.403-4.688-29.209-19.092-33.068-14.403-3.86-29.209 4.688-33.068 19.092-3.86 14.404 4.688 29.209 19.092 33.068zm-1.035 3.864c-16.538-4.431-26.352-21.43-21.92-37.967 4.43-16.538 21.429-26.352 37.966-21.92 16.538 4.43 26.352 21.429 21.92 37.966-4.43 16.538-21.429 26.352-37.966 21.92z"/><path fill="#6B4FBB" d="M329.376 201.598c-4.668-2.621-7.155-8.157-5.706-13.566 1.715-6.402 8.295-10.201 14.697-8.486 6.402 1.716 10.2 8.296 8.485 14.697-1.45 5.41-6.371 8.96-11.725 8.897a3.03 3.03 0 0 1-.074.365l-1.812 6.761a3 3 0 0 1-5.795-1.552l1.812-6.762a3.03 3.03 0 0 1 .118-.354zm3.815-2.733a8 8 0 1 0 4.14-15.455 8 8 0 0 0-4.14 15.455z"/></g><path fill="#FEF0E8" fill-rule="nonzero" d="M91.373 193c17.071-4.574 27.202-22.12 22.628-39.191-4.575-17.071-22.121-27.202-39.192-22.628-17.071 4.574-27.202 22.121-22.628 39.192 4.574 17.071 22.121 27.202 39.192 22.627zm1.035 3.864c-19.204 5.146-38.945-6.25-44.09-25.456-5.146-19.204 6.25-38.945 25.455-44.09 19.205-5.146 38.945 6.25 44.091 25.455 5.146 19.205-6.25 38.945-25.456 44.091z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M70.067 152.122l6.73 25.114 19.318-5.176-6.73-25.114-19.318 5.176zm-1.035-3.864l19.318-5.176a4 4 0 0 1 4.9 2.828l6.729 25.114a4 4 0 0 1-2.829 4.9L77.832 181.1a4 4 0 0 1-4.9-2.829l-6.729-25.114a4 4 0 0 1 2.829-4.899z"/><path fill="#FC6D26" d="M76.898 154.433l7.727-2.07a2 2 0 0 1 1.036 3.863l-7.728 2.07a2 2 0 1 1-1.035-3.863zm1.812 6.761l5.795-1.553a2 2 0 0 1 1.035 3.864l-5.795 1.553a2 2 0 1 1-1.035-3.864zm1.811 6.762l7.728-2.07a2 2 0 0 1 1.035 3.863l-7.727 2.07a2 2 0 1 1-1.036-3.863z"/></g></svg>
\ No newline at end of file
...@@ -74,6 +74,18 @@ const gfmRules = { ...@@ -74,6 +74,18 @@ const gfmRules = {
return `![${el.dataset.title}](${el.getAttribute('src')})`; return `![${el.dataset.title}](${el.getAttribute('src')})`;
}, },
}, },
MermaidFilter: {
'svg.mermaid'(el, text) {
const sourceEl = el.querySelector('text.source');
if (!sourceEl) return false;
return `\`\`\`mermaid\n${CopyAsGFM.nodeToGFM(sourceEl)}\n\`\`\``;
},
'svg.mermaid style, svg.mermaid g'(el, text) {
// We don't want to include the content of these elements in the copied text.
return '';
},
},
MathFilter: { MathFilter: {
'pre.code.math[data-math-style=display]'(el, text) { 'pre.code.math[data-math-style=display]'(el, text) {
return `\`\`\`math\n${text.trim()}\n\`\`\``; return `\`\`\`math\n${text.trim()}\n\`\`\``;
......
...@@ -115,7 +115,7 @@ export default { ...@@ -115,7 +115,7 @@ export default {
}, },
mounted() { mounted() {
const options = gl.issueBoards.getBoardSortableDefaultOptions({ const options = gl.issueBoards.getBoardSortableDefaultOptions({
scroll: document.querySelectorAll('.boards-list')[0], scroll: true,
group: 'issues', group: 'issues',
disabled: this.disabled, disabled: this.disabled,
filter: '.board-list-count, .is-disabled', filter: '.board-list-count, .is-disabled',
......
...@@ -276,13 +276,13 @@ export default class CreateMergeRequestDropdown { ...@@ -276,13 +276,13 @@ export default class CreateMergeRequestDropdown {
let target; let target;
let value; let value;
if (event.srcElement === this.branchInput) { if (event.target === this.branchInput) {
target = 'branch'; target = 'branch';
value = this.branchInput.value; value = this.branchInput.value;
} else if (event.srcElement === this.refInput) { } else if (event.target === this.refInput) {
target = 'ref'; target = 'ref';
value = event.srcElement.value.slice(0, event.srcElement.selectionStart) + value = event.target.value.slice(0, event.target.selectionStart) +
event.srcElement.value.slice(event.srcElement.selectionEnd); event.target.value.slice(event.target.selectionEnd);
} else { } else {
return false; return false;
} }
......
...@@ -77,7 +77,8 @@ export default { ...@@ -77,7 +77,8 @@ export default {
class="group-row" class="group-row"
> >
<div <div
class="group-row-contents"> class="group-row-contents"
:class="{ 'project-row-contents': !isGroup }">
<item-actions <item-actions
v-if="isGroup" v-if="isGroup"
:group="group" :group="group"
...@@ -97,7 +98,7 @@ export default { ...@@ -97,7 +98,7 @@ export default {
/> />
</div> </div>
<div <div
class="avatar-container s40 hidden-xs" class="avatar-container prepend-top-8 prepend-left-5 s24 hidden-xs"
:class="{ 'content-loading': group.isChildrenLoading }" :class="{ 'content-loading': group.isChildrenLoading }"
> >
<a <a
...@@ -106,11 +107,12 @@ export default { ...@@ -106,11 +107,12 @@ export default {
> >
<img <img
v-if="hasAvatar" v-if="hasAvatar"
class="avatar s40" class="avatar s24"
:src="group.avatarUrl" :src="group.avatarUrl"
/> />
<identicon <identicon
v-else v-else
size-class="s24"
:entity-id=group.id :entity-id=group.id
:entity-name="group.name" :entity-name="group.name"
/> />
...@@ -123,7 +125,7 @@ export default { ...@@ -123,7 +125,7 @@ export default {
:href="group.relativePath" :href="group.relativePath"
:title="group.fullName" :title="group.fullName"
class="no-expand" class="no-expand"
data-placement="top" data-placement="bottom"
>{{ >{{
// ending bracket must be by closing tag to prevent // ending bracket must be by closing tag to prevent
// link hover text-decoration from over-extending // link hover text-decoration from over-extending
......
<script> <script>
import { s__ } from '../../locale'; import { s__ } from '~/locale';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
import modal from '../../vue_shared/components/modal.vue'; import icon from '~/vue_shared/components/icon.vue';
import modal from '~/vue_shared/components/modal.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { COMMON_STR } from '../constants'; import { COMMON_STR } from '../constants';
import Icon from '../../vue_shared/components/icon.vue';
export default { export default {
components: { components: {
Icon, icon,
modal, modal,
}, },
directives: { directives: {
...@@ -45,11 +45,9 @@ export default { ...@@ -45,11 +45,9 @@ export default {
onLeaveGroup() { onLeaveGroup() {
this.modalStatus = true; this.modalStatus = true;
}, },
leaveGroup(leaveConfirmed) { leaveGroup() {
this.modalStatus = false; this.modalStatus = false;
if (leaveConfirmed) {
eventHub.$emit('leaveGroup', this.group, this.parentGroup); eventHub.$emit('leaveGroup', this.group, this.parentGroup);
}
}, },
}, },
}; };
...@@ -64,10 +62,9 @@ export default { ...@@ -64,10 +62,9 @@ export default {
:title="editBtnTitle" :title="editBtnTitle"
:aria-label="editBtnTitle" :aria-label="editBtnTitle"
data-container="body" data-container="body"
data-placement="bottom"
class="edit-group btn no-expand"> class="edit-group btn no-expand">
<icon <icon name="settings"/>
name="settings">
</icon>
</a> </a>
<a <a
v-tooltip v-tooltip
...@@ -77,10 +74,9 @@ export default { ...@@ -77,10 +74,9 @@ export default {
:title="leaveBtnTitle" :title="leaveBtnTitle"
:aria-label="leaveBtnTitle" :aria-label="leaveBtnTitle"
data-container="body" data-container="body"
data-placement="bottom"
class="leave-group btn no-expand"> class="leave-group btn no-expand">
<i <icon name="leave"/>
class="fa fa-sign-out"
aria-hidden="true"/>
</a> </a>
<modal <modal
v-show="modalStatus" v-show="modalStatus"
......
<script> <script>
import icon from '~/vue_shared/components/icon.vue';
export default { export default {
props: { props: {
isGroupOpen: { isGroupOpen: {
...@@ -7,9 +9,12 @@ export default { ...@@ -7,9 +9,12 @@ export default {
default: false, default: false,
}, },
}, },
components: {
icon,
},
computed: { computed: {
iconClass() { iconClass() {
return this.isGroupOpen ? 'fa-caret-down' : 'fa-caret-right'; return this.isGroupOpen ? 'angle-down' : 'angle-right';
}, },
}, },
}; };
...@@ -17,9 +22,9 @@ export default { ...@@ -17,9 +22,9 @@ export default {
<template> <template>
<span class="folder-caret"> <span class="folder-caret">
<i <icon
:class="iconClass" :size="12"
class="fa" :name="iconClass"
aria-hidden="true"/> />
</span> </span>
</template> </template>
<script> <script>
import tooltip from '../../vue_shared/directives/tooltip'; import icon from '~/vue_shared/components/icon.vue';
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { ITEM_TYPE, VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE, PROJECT_VISIBILITY_TYPE } from '../constants'; import { ITEM_TYPE, VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE, PROJECT_VISIBILITY_TYPE } from '../constants';
import itemStatsValue from './item_stats_value.vue';
export default { export default {
directives: { components: {
tooltip, icon,
timeAgoTooltip,
itemStatsValue,
}, },
props: { props: {
item: { item: {
...@@ -34,65 +38,47 @@ export default { ...@@ -34,65 +38,47 @@ export default {
<template> <template>
<div class="stats"> <div class="stats">
<span <item-stats-value
v-tooltip
v-if="isGroup" v-if="isGroup"
:title="s__('Subgroups')" css-class="number-subgroups"
class="number-subgroups" icon-name="folder"
data-placement="top" :title="__('Subgroups')"
data-container="body"> :value="item.subgroupCount"
<i
class="fa fa-folder"
aria-hidden="true"
/> />
{{item.subgroupCount}} <item-stats-value
</span>
<span
v-tooltip
v-if="isGroup" v-if="isGroup"
:title="s__('Projects')" css-class="number-projects"
class="number-projects" icon-name="bookmark"
data-placement="top" :title="__('Projects')"
data-container="body"> :value="item.projectCount"
<i
class="fa fa-bookmark"
aria-hidden="true"
/> />
{{item.projectCount}} <item-stats-value
</span>
<span
v-tooltip
v-if="isGroup" v-if="isGroup"
:title="s__('Members')" css-class="number-users"
class="number-users" icon-name="users"
data-placement="top" :title="__('Members')"
data-container="body"> :value="item.memberCount"
<i
class="fa fa-users"
aria-hidden="true"
/> />
{{item.memberCount}} <item-stats-value
</span>
<span
v-if="isProject" v-if="isProject"
class="project-stars"> css-class="project-stars"
<i icon-name="star"
class="fa fa-star" :value="item.starCount"
aria-hidden="true"
/> />
{{item.starCount}} <item-stats-value
</span> css-class="item-visibility"
<span tooltip-placement="left"
v-tooltip :icon-name="visibilityIcon"
:title="visibilityTooltip" :title="visibilityTooltip"
data-placement="left"
data-container="body"
class="item-visibility">
<i
:class="visibilityIcon"
class="fa"
aria-hidden="true"
/> />
</span> <div
class="last-updated"
v-if="isProject"
>
<time-ago-tooltip
tooltip-placement="bottom"
:time="item.updatedAt"
/>
</div>
</div> </div>
</template> </template>
<script>
import tooltip from '~/vue_shared/directives/tooltip';
import icon from '~/vue_shared/components/icon.vue';
export default {
props: {
title: {
type: String,
required: false,
default: '',
},
cssClass: {
type: String,
required: false,
default: '',
},
iconName: {
type: String,
required: true,
},
tooltipPlacement: {
type: String,
required: false,
default: 'bottom',
},
/**
* value could either be number or string
* as `memberCount` is always passed as string
* while `subgroupCount` & `projectCount`
* are always number
*/
value: {
type: [Number, String],
required: false,
default: '',
},
},
directives: {
tooltip,
},
components: {
icon,
},
computed: {
isValuePresent() {
return this.value !== '';
},
},
};
</script>
<template>
<span
v-tooltip
data-container="body"
:data-placement="tooltipPlacement"
:class="cssClass"
:title="title"
>
<icon :name="iconName"/>
<span
v-if="isValuePresent"
class="stat-value"
>
{{value}}
</span>
</span>
</template>
<script> <script>
import icon from '~/vue_shared/components/icon.vue';
import { ITEM_TYPE } from '../constants'; import { ITEM_TYPE } from '../constants';
export default { export default {
components: {
icon,
},
props: { props: {
itemType: { itemType: {
type: String, type: String,
...@@ -16,9 +20,9 @@ export default { ...@@ -16,9 +20,9 @@ export default {
computed: { computed: {
iconClass() { iconClass() {
if (this.itemType === ITEM_TYPE.GROUP) { if (this.itemType === ITEM_TYPE.GROUP) {
return this.isGroupOpen ? 'fa-folder-open' : 'fa-folder'; return this.isGroupOpen ? 'folder-open' : 'folder';
} }
return 'fa-bookmark'; return 'bookmark';
}, },
}, },
}; };
...@@ -26,9 +30,6 @@ export default { ...@@ -26,9 +30,6 @@ export default {
<template> <template>
<span class="item-type-icon"> <span class="item-type-icon">
<i <icon :name="iconClass"/>
:class="iconClass"
class="fa"
aria-hidden="true"/>
</span> </span>
</template> </template>
...@@ -29,7 +29,7 @@ export const PROJECT_VISIBILITY_TYPE = { ...@@ -29,7 +29,7 @@ export const PROJECT_VISIBILITY_TYPE = {
}; };
export const VISIBILITY_TYPE_ICON = { export const VISIBILITY_TYPE_ICON = {
public: 'fa-globe', public: 'earth',
internal: 'fa-shield', internal: 'shield',
private: 'fa-lock', private: 'lock',
}; };
...@@ -91,6 +91,7 @@ export default class GroupsStore { ...@@ -91,6 +91,7 @@ export default class GroupsStore {
subgroupCount: rawGroupItem.subgroup_count, subgroupCount: rawGroupItem.subgroup_count,
memberCount: rawGroupItem.number_users_with_delimiter, memberCount: rawGroupItem.number_users_with_delimiter,
starCount: rawGroupItem.star_count, starCount: rawGroupItem.star_count,
updatedAt: rawGroupItem.updated_at,
}; };
} }
......
...@@ -9,6 +9,12 @@ import repoPreview from './repo_preview.vue'; ...@@ -9,6 +9,12 @@ import repoPreview from './repo_preview.vue';
import repoEditor from './repo_editor.vue'; import repoEditor from './repo_editor.vue';
export default { export default {
props: {
emptyStateSvgPath: {
type: String,
required: true,
},
},
computed: { computed: {
...mapState([ ...mapState([
'currentBlobView', 'currentBlobView',
...@@ -64,7 +70,23 @@ export default { ...@@ -64,7 +70,23 @@ export default {
<template <template
v-else> v-else>
<div class="ide-empty-state"> <div class="ide-empty-state">
<h2 class="clgray">Welcome to the GitLab IDE</h2> <div class="row js-empty-state">
<div class="col-xs-12">
<div class="svg-content svg-250">
<img :src="emptyStateSvgPath">
</div>
</div>
<div class="col-xs-12">
<div class="text-content text-center">
<h4>
Welcome to the GitLab IDE
</h4>
<p>
You can select a file in the left sidebar to begin editing and use the right sidebar to commit your changes.
</p>
</div>
</div>
</div>
</div> </div>
</template> </template>
</div> </div>
......
<script> <script>
import { mapState } from 'vuex'; import { mapState } from 'vuex';
import RepoPreviousDirectory from './repo_prev_directory.vue'; import repoPreviousDirectory from './repo_prev_directory.vue';
import RepoFile from './repo_file.vue'; import repoFile from './repo_file.vue';
import RepoLoadingFile from './repo_loading_file.vue'; import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue';
import { treeList } from '../stores/utils'; import { treeList } from '../stores/utils';
export default { export default {
components: { components: {
'repo-previous-directory': RepoPreviousDirectory, repoPreviousDirectory,
'repo-file': RepoFile, repoFile,
'repo-loading-file': RepoLoadingFile, skeletonLoadingContainer,
}, },
props: { props: {
treeId: { treeId: {
...@@ -19,7 +19,7 @@ export default { ...@@ -19,7 +19,7 @@ export default {
}, },
computed: { computed: {
...mapState([ ...mapState([
'loading', 'trees',
'isRoot', 'isRoot',
]), ]),
...mapState({ ...mapState({
...@@ -34,7 +34,10 @@ export default { ...@@ -34,7 +34,10 @@ export default {
return !this.isRoot && this.fetchedList.length; return !this.isRoot && this.fetchedList.length;
}, },
showLoading() { showLoading() {
return this.loading; if (this.trees[this.treeId]) {
return this.trees[this.treeId].loading;
}
return true;
}, },
}, },
}; };
...@@ -49,11 +52,13 @@ export default { ...@@ -49,11 +52,13 @@ export default {
<repo-previous-directory <repo-previous-directory
v-if="hasPreviousDirectory" v-if="hasPreviousDirectory"
/> />
<repo-loading-file <div
class="multi-file-loading-container"
v-if="showLoading" v-if="showLoading"
v-for="n in 5" v-for="n in 3"
:key="n" :key="n">
/> <skeleton-loading-container/>
</div>
<repo-file <repo-file
v-for="file in fetchedList" v-for="file in fetchedList"
:key="file.key" :key="file.key"
......
...@@ -3,6 +3,7 @@ import { mapState, mapActions } from 'vuex'; ...@@ -3,6 +3,7 @@ import { mapState, mapActions } from 'vuex';
import projectTree from './ide_project_tree.vue'; import projectTree from './ide_project_tree.vue';
import icon from '../../vue_shared/components/icon.vue'; import icon from '../../vue_shared/components/icon.vue';
import panelResizer from '../../vue_shared/components/panel_resizer.vue'; import panelResizer from '../../vue_shared/components/panel_resizer.vue';
import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue';
export default { export default {
data() { data() {
...@@ -14,9 +15,11 @@ export default { ...@@ -14,9 +15,11 @@ export default {
projectTree, projectTree,
icon, icon,
panelResizer, panelResizer,
skeletonLoadingContainer,
}, },
computed: { computed: {
...mapState([ ...mapState([
'loading',
'projects', 'projects',
'leftPanelCollapsed', 'leftPanelCollapsed',
]), ]),
...@@ -32,6 +35,9 @@ export default { ...@@ -32,6 +35,9 @@ export default {
} }
return {}; return {};
}, },
showLoading() {
return this.loading;
},
}, },
methods: { methods: {
...mapActions([ ...mapActions([
...@@ -63,6 +69,13 @@ export default { ...@@ -63,6 +69,13 @@ export default {
:style="panelStyle" :style="panelStyle"
> >
<div class="multi-file-commit-panel-inner"> <div class="multi-file-commit-panel-inner">
<div
class="multi-file-loading-container"
v-if="showLoading"
v-for="n in 3"
:key="n">
<skeleton-loading-container/>
</div>
<project-tree <project-tree
v-for="(project, index) in projects" v-for="(project, index) in projects"
:key="project.id" :key="project.id"
......
...@@ -32,10 +32,10 @@ ...@@ -32,10 +32,10 @@
methods: { methods: {
createNewItem(type) { createNewItem(type) {
this.modalType = type; this.modalType = type;
this.toggleModalOpen(); this.openModal = true;
}, },
toggleModalOpen() { hideModal() {
this.openModal = !this.openModal; this.openModal = false;
}, },
}, },
}; };
...@@ -95,7 +95,7 @@ ...@@ -95,7 +95,7 @@
:branch-id="branch" :branch-id="branch"
:path="path" :path="path"
:parent="parent" :parent="parent"
@toggle="toggleModalOpen" @hide="hideModal"
/> />
</div> </div>
</template> </template>
...@@ -43,10 +43,10 @@ ...@@ -43,10 +43,10 @@
type: this.type, type: this.type,
}); });
this.toggleModalOpen(); this.hideModal();
}, },
toggleModalOpen() { hideModal() {
this.$emit('toggle'); this.$emit('hide');
}, },
}, },
computed: { computed: {
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
:title="modalTitle" :title="modalTitle"
:primary-button-label="buttonLabel" :primary-button-label="buttonLabel"
kind="success" kind="success"
@toggle="toggleModalOpen" @cancel="hideModal"
@submit="createEntryInStore" @submit="createEntryInStore"
> >
<form <form
......
...@@ -110,7 +110,7 @@ export default { ...@@ -110,7 +110,7 @@ export default {
kind="primary" kind="primary"
:title="__('Branch has changed')" :title="__('Branch has changed')"
:text="__('This branch has changed since you started editing. Would you like to create a new branch?')" :text="__('This branch has changed since you started editing. Would you like to create a new branch?')"
@toggle="showNewBranchModal = false" @cancel="showNewBranchModal = false"
@submit="makeCommit(true)" @submit="makeCommit(true)"
/> />
<commit-files-list <commit-files-list
......
...@@ -50,7 +50,7 @@ export default { ...@@ -50,7 +50,7 @@ export default {
kind="warning" kind="warning"
:title="__('Are you sure?')" :title="__('Are you sure?')"
:text="__('Are you sure you want to discard your changes?')" :text="__('Are you sure you want to discard your changes?')"
@toggle="closeDiscardPopup" @cancel="closeDiscardPopup"
@submit="toggleEditMode(true)" @submit="toggleEditMode(true)"
/> />
</div> </div>
......
import Vue from 'vue'; import Vue from 'vue';
import { mapActions } from 'vuex';
import { convertPermissionToBoolean } from '../lib/utils/common_utils';
import ide from './components/ide.vue'; import ide from './components/ide.vue';
import store from './stores'; import store from './stores';
import router from './ide_router'; import router from './ide_router';
import Translate from '../vue_shared/translate'; import Translate from '../vue_shared/translate';
import ContextualSidebar from '../contextual_sidebar';
function initIde(el) { function initIde(el) {
if (!el) return null; if (!el) return null;
...@@ -18,30 +14,13 @@ function initIde(el) { ...@@ -18,30 +14,13 @@ function initIde(el) {
components: { components: {
ide, ide,
}, },
methods: { render(createElement) {
...mapActions([ return createElement('ide', {
'setInitialData', props: {
]), emptyStateSvgPath: el.dataset.emptyStateSvgPath,
},
created() {
const data = el.dataset;
this.setInitialData({
endpoints: {
rootEndpoint: data.url,
newMergeRequestUrl: data.newMergeRequestUrl,
rootUrl: data.rootUrl,
}, },
canCommit: convertPermissionToBoolean(data.canCommit),
onTopOfBranch: convertPermissionToBoolean(data.onTopOfBranch),
path: data.currentPath,
isRoot: convertPermissionToBoolean(data.root),
isInitialRoot: convertPermissionToBoolean(data.root),
}); });
}, },
render(createElement) {
return createElement('ide');
},
}); });
} }
...@@ -50,6 +29,3 @@ const ideElement = document.getElementById('ide'); ...@@ -50,6 +29,3 @@ const ideElement = document.getElementById('ide');
Vue.use(Translate); Vue.use(Translate);
initIde(ideElement); initIde(ideElement);
const contextualSidebar = new ContextualSidebar();
contextualSidebar.bindEvents();
...@@ -8,9 +8,11 @@ export const getProjectData = ( ...@@ -8,9 +8,11 @@ export const getProjectData = (
{ namespace, projectId, force = false } = {}, { namespace, projectId, force = false } = {},
) => new Promise((resolve, reject) => { ) => new Promise((resolve, reject) => {
if (!state.projects[`${namespace}/${projectId}`] || force) { if (!state.projects[`${namespace}/${projectId}`] || force) {
commit(types.TOGGLE_LOADING, state);
service.getProjectData(namespace, projectId) service.getProjectData(namespace, projectId)
.then(res => res.data) .then(res => res.data)
.then((data) => { .then((data) => {
commit(types.TOGGLE_LOADING, state);
commit(types.SET_PROJECT, { projectPath: `${namespace}/${projectId}`, project: data }); commit(types.SET_PROJECT, { projectPath: `${namespace}/${projectId}`, project: data });
if (!state.currentProjectId) commit(types.SET_CURRENT_PROJECT, `${namespace}/${projectId}`); if (!state.currentProjectId) commit(types.SET_CURRENT_PROJECT, `${namespace}/${projectId}`);
resolve(data); resolve(data);
......
...@@ -64,3 +64,12 @@ export const truncate = (string, maxLength) => `${string.substr(0, (maxLength - ...@@ -64,3 +64,12 @@ export const truncate = (string, maxLength) => `${string.substr(0, (maxLength -
export function capitalizeFirstCharacter(text) { export function capitalizeFirstCharacter(text) {
return `${text[0].toUpperCase()}${text.slice(1)}`; return `${text[0].toUpperCase()}${text.slice(1)}`;
} }
/**
* Replaces all html tags from a string with the given replacement.
*
* @param {String} string
* @param {*} replace
* @returns {String}
*/
export const stripeHtml = (string, replace = '') => string.replace(/<[^>]*>/g, replace);
import { timeFormat as time } from 'd3-time-format'; import { timeFormat as time } from 'd3-time-format';
import { timeSecond, timeMinute, timeHour, timeDay, timeMonth, timeYear } from 'd3-time'; import { timeSecond, timeMinute, timeHour, timeDay, timeWeek, timeMonth, timeYear } from 'd3-time';
import { bisector } from 'd3-array'; import { bisector } from 'd3-array';
const d3 = { time, bisector, timeSecond, timeMinute, timeHour, timeDay, timeMonth, timeYear }; const d3 = {
time,
bisector,
timeSecond,
timeMinute,
timeHour,
timeDay,
timeWeek,
timeMonth,
timeYear,
};
export const dateFormat = d3.time('%b %-d, %Y'); export const dateFormat = d3.time('%b %-d, %Y');
export const timeFormat = d3.time('%-I:%M%p'); export const timeFormat = d3.time('%-I:%M%p');
......
<script> <script>
import modal from '../../../vue_shared/components/modal.vue'; import modal from '~/vue_shared/components/modal.vue';
import { __, s__, sprintf } from '../../../locale'; import { __, s__, sprintf } from '~/locale';
import csrf from '../../../lib/utils/csrf'; import csrf from '~/lib/utils/csrf';
export default { export default {
props: { props: {
...@@ -22,7 +22,6 @@ ...@@ -22,7 +22,6 @@
return { return {
enteredPassword: '', enteredPassword: '',
enteredUsername: '', enteredUsername: '',
isOpen: false,
}; };
}, },
components: { components: {
...@@ -69,34 +68,22 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`), ...@@ -69,34 +68,22 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
return this.enteredUsername === this.username; return this.enteredUsername === this.username;
}, },
onSubmit(status) { onSubmit() {
if (status) {
if (!this.canSubmit()) {
return;
}
this.$refs.form.submit(); this.$refs.form.submit();
}
this.toggleOpen(false);
},
toggleOpen(isOpen) {
this.isOpen = isOpen;
}, },
}, },
}; };
</script> </script>
<template> <template>
<div>
<modal <modal
v-if="isOpen" id="delete-account-modal"
:title="s__('Profiles|Delete your account?')" :title="s__('Profiles|Delete your account?')"
:text="text" :text="text"
:kind="`danger ${!canSubmit() && 'disabled'}`" kind="danger"
:primary-button-label="s__('Profiles|Delete account')" :primary-button-label="s__('Profiles|Delete account')"
@toggle="toggleOpen" @submit="onSubmit"
@submit="onSubmit"> :submit-disabled="!canSubmit()">
<template slot="body" slot-scope="props"> <template slot="body" slot-scope="props">
<p v-html="props.text"></p> <p v-html="props.text"></p>
...@@ -135,12 +122,4 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`), ...@@ -135,12 +122,4 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
</template> </template>
</modal> </modal>
<button
type="button"
class="btn btn-danger"
@click="toggleOpen(true)">
{{ s__('Profiles|Delete account') }}
</button>
</div>
</template> </template>
import Vue from 'vue'; import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import deleteAccountModal from './components/delete_account_modal.vue'; import deleteAccountModal from './components/delete_account_modal.vue';
Vue.use(Translate);
const deleteAccountButton = document.getElementById('delete-account-button');
const deleteAccountModalEl = document.getElementById('delete-account-modal'); const deleteAccountModalEl = document.getElementById('delete-account-modal');
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
...@@ -9,6 +14,9 @@ new Vue({ ...@@ -9,6 +14,9 @@ new Vue({
components: { components: {
deleteAccountModal, deleteAccountModal,
}, },
mounted() {
deleteAccountButton.classList.remove('disabled');
},
render(createElement) { render(createElement) {
return createElement('delete-account-modal', { return createElement('delete-account-modal', {
props: { props: {
......
/* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */ /* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */
import Cookies from 'js-cookie';
import Flash from '../flash'; import Flash from '../flash';
import { getPagePath } from '../lib/utils/common_utils'; import { getPagePath } from '../lib/utils/common_utils';
...@@ -7,6 +8,8 @@ import { getPagePath } from '../lib/utils/common_utils'; ...@@ -7,6 +8,8 @@ import { getPagePath } from '../lib/utils/common_utils';
constructor({ form } = {}) { constructor({ form } = {}) {
this.onSubmitForm = this.onSubmitForm.bind(this); this.onSubmitForm = this.onSubmitForm.bind(this);
this.form = form || $('.edit-user'); this.form = form || $('.edit-user');
this.newRepoActivated = Cookies.get('new_repo');
this.setRepoRadio();
this.bindEvents(); this.bindEvents();
this.initAvatarGlCrop(); this.initAvatarGlCrop();
} }
...@@ -25,6 +28,7 @@ import { getPagePath } from '../lib/utils/common_utils'; ...@@ -25,6 +28,7 @@ import { getPagePath } from '../lib/utils/common_utils';
bindEvents() { bindEvents() {
$('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm); $('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm);
$('input[name="user[multi_file]"]').on('change', this.setNewRepoCookie);
$('#user_notification_email').on('change', this.submitForm); $('#user_notification_email').on('change', this.submitForm);
$('#user_notified_of_own_activity').on('change', this.submitForm); $('#user_notified_of_own_activity').on('change', this.submitForm);
$('.update-username').on('ajax:before', this.beforeUpdateUsername); $('.update-username').on('ajax:before', this.beforeUpdateUsername);
...@@ -82,6 +86,23 @@ import { getPagePath } from '../lib/utils/common_utils'; ...@@ -82,6 +86,23 @@ import { getPagePath } from '../lib/utils/common_utils';
} }
}); });
} }
setNewRepoCookie() {
if (this.value === 'off') {
Cookies.remove('new_repo');
} else {
Cookies.set('new_repo', true, { expires_in: 365 });
}
}
setRepoRadio() {
const multiEditRadios = $('input[name="user[multi_file]"]');
if (this.newRepoActivated || this.newRepoActivated === 'true') {
multiEditRadios.filter('[value=on]').prop('checked', true);
} else {
multiEditRadios.filter('[value=off]').prop('checked', true);
}
}
} }
$(function() { $(function() {
......
let hasUserDefinedProjectPath = false; let hasUserDefinedProjectPath = false;
const deriveProjectPathFromUrl = ($projectImportUrl, $projectPath) => { const deriveProjectPathFromUrl = ($projectImportUrl) => {
const $currentProjectPath = $projectImportUrl.parents('.toggle-import-form').find('#project_path');
if (hasUserDefinedProjectPath) { if (hasUserDefinedProjectPath) {
return; return;
} }
...@@ -21,7 +22,7 @@ const deriveProjectPathFromUrl = ($projectImportUrl, $projectPath) => { ...@@ -21,7 +22,7 @@ const deriveProjectPathFromUrl = ($projectImportUrl, $projectPath) => {
// extract everything after the last slash // extract everything after the last slash
const pathMatch = /\/([^/]+)$/.exec(importUrl); const pathMatch = /\/([^/]+)$/.exec(importUrl);
if (pathMatch) { if (pathMatch) {
$projectPath.val(pathMatch[1]); $currentProjectPath.val(pathMatch[1]);
} }
}; };
...@@ -96,7 +97,7 @@ const bindEvents = () => { ...@@ -96,7 +97,7 @@ const bindEvents = () => {
hasUserDefinedProjectPath = $projectPath.val().trim().length > 0; hasUserDefinedProjectPath = $projectPath.val().trim().length > 0;
}); });
$projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl, $projectPath)); $projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl));
}; };
document.addEventListener('DOMContentLoaded', bindEvents); document.addEventListener('DOMContentLoaded', bindEvents);
......
...@@ -24,7 +24,25 @@ export default function renderMermaid($els) { ...@@ -24,7 +24,25 @@ export default function renderMermaid($els) {
}); });
$els.each((i, el) => { $els.each((i, el) => {
mermaid.init(undefined, el); const source = el.textContent;
mermaid.init(undefined, el, (id) => {
const svg = document.getElementById(id);
svg.classList.add('mermaid');
// pre > code > svg
svg.closest('pre').replaceWith(svg);
// We need to add the original source into the DOM to allow Copy-as-GFM
// to access it.
const sourceEl = document.createElement('text');
sourceEl.classList.add('source');
sourceEl.setAttribute('display', 'none');
sourceEl.textContent = source;
svg.appendChild(sourceEl);
});
}); });
}).catch((err) => { }).catch((err) => {
Flash(`Can't load mermaid module: ${err}`); Flash(`Can't load mermaid module: ${err}`);
......
<script>
import simplePoll from '../../../lib/utils/simple_poll';
import eventHub from '../../event_hub';
import statusIcon from '../mr_widget_status_icon';
import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
import Flash from '../../../flash';
export default {
name: 'MRWidgetRebase',
props: {
mr: {
type: Object,
required: true,
},
service: {
type: Object,
required: true,
},
},
components: {
statusIcon,
loadingIcon,
},
data() {
return {
isMakingRequest: false,
rebasingError: null,
};
},
computed: {
status() {
if (this.mr.rebaseInProgress || this.isMakingRequest) {
return 'loading';
}
if (!this.mr.canPushToSourceBranch && !this.mr.rebaseInProgress) {
return 'warning';
}
return 'success';
},
showDisabledButton() {
return ['failed', 'loading'].includes(this.status);
},
},
methods: {
rebase() {
this.isMakingRequest = true;
this.rebasingError = null;
this.service.rebase()
.then(() => {
simplePoll(this.checkRebaseStatus);
})
.catch((error) => {
this.rebasingError = error.merge_error;
this.isMakingRequest = false;
Flash('Something went wrong. Please try again.');
});
},
checkRebaseStatus(continuePolling, stopPolling) {
this.service.poll()
.then(res => res.data)
.then((res) => {
if (res.rebase_in_progress) {
continuePolling();
} else {
this.isMakingRequest = false;
if (res.merge_error && res.merge_error.length) {
this.rebasingError = res.merge_error;
Flash('Something went wrong. Please try again.');
}
eventHub.$emit('MRWidgetUpdateRequested');
stopPolling();
}
})
.catch(() => {
this.isMakingRequest = false;
Flash('Something went wrong. Please try again.');
stopPolling();
});
},
},
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon
:status="status"
:show-disabled-button="showDisabledButton"
/>
<div class="rebase-state-find-class-convention media media-body space-children">
<template v-if="mr.rebaseInProgress || isMakingRequest">
<span class="bold">
Rebase in progress
</span>
</template>
<template v-if="!mr.rebaseInProgress && !mr.canPushToSourceBranch">
<span class="bold">
Fast-forward merge is not possible.
Rebase the source branch onto
<span class="label-branch">{{mr.targetBranch}}</span>
to allow this merge request to be merged.
</span>
</template>
<template v-if="!mr.rebaseInProgress && mr.canPushToSourceBranch && !isMakingRequest">
<div class="accept-merge-holder clearfix js-toggle-container accept-action media space-children">
<button
type="button"
class="btn btn-sm btn-reopen btn-success"
:disabled="isMakingRequest"
@click="rebase">
<loading-icon v-if="isMakingRequest" />
Rebase
</button>
<span
v-if="!rebasingError"
class="bold">
Fast-forward merge is not possible.
Rebase the source branch onto the target branch or merge target
branch into source branch to allow this merge request to be merged.
</span>
<span
v-else
class="bold danger">
{{rebasingError}}
</span>
</div>
</template>
</div>
</div>
</template>
...@@ -32,6 +32,7 @@ export { default as UnresolvedDiscussionsState } from './components/states/mr_wi ...@@ -32,6 +32,7 @@ export { default as UnresolvedDiscussionsState } from './components/states/mr_wi
export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked'; export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked';
export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed'; export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed';
export { default as MergeWhenPipelineSucceedsState } from './components/states/mr_widget_merge_when_pipeline_succeeds'; export { default as MergeWhenPipelineSucceedsState } from './components/states/mr_widget_merge_when_pipeline_succeeds';
export { default as RebaseState } from './components/states/mr_widget_rebase.vue';
export { default as AutoMergeFailed } from './components/states/mr_widget_auto_merge_failed'; export { default as AutoMergeFailed } from './components/states/mr_widget_auto_merge_failed';
export { default as CheckingState } from './components/states/mr_widget_checking'; export { default as CheckingState } from './components/states/mr_widget_checking';
export { default as MRWidgetStore } from './stores/mr_widget_store'; export { default as MRWidgetStore } from './stores/mr_widget_store';
......
...@@ -10,6 +10,7 @@ import { ...@@ -10,6 +10,7 @@ import {
MergedState, MergedState,
ClosedState, ClosedState,
MergingState, MergingState,
RebaseState,
WipState, WipState,
ArchivedState, ArchivedState,
ConflictsState, ConflictsState,
...@@ -79,6 +80,7 @@ export default { ...@@ -79,6 +80,7 @@ export default {
ciEnvironmentsStatusPath: store.ciEnvironmentsStatusPath, ciEnvironmentsStatusPath: store.ciEnvironmentsStatusPath,
statusPath: store.statusPath, statusPath: store.statusPath,
mergeActionsContentPath: store.mergeActionsContentPath, mergeActionsContentPath: store.mergeActionsContentPath,
rebasePath: store.rebasePath,
}; };
return new MRWidgetService(endpoints); return new MRWidgetService(endpoints);
}, },
...@@ -232,6 +234,7 @@ export default { ...@@ -232,6 +234,7 @@ export default {
'mr-widget-pipeline-failed': PipelineFailedState, 'mr-widget-pipeline-failed': PipelineFailedState,
'mr-widget-merge-when-pipeline-succeeds': MergeWhenPipelineSucceedsState, 'mr-widget-merge-when-pipeline-succeeds': MergeWhenPipelineSucceedsState,
'mr-widget-auto-merge-failed': AutoMergeFailed, 'mr-widget-auto-merge-failed': AutoMergeFailed,
'mr-widget-rebase': RebaseState,
}, },
template: ` template: `
<div class="mr-state-widget prepend-top-default"> <div class="mr-state-widget prepend-top-default">
......
...@@ -37,6 +37,10 @@ export default class MRWidgetService { ...@@ -37,6 +37,10 @@ export default class MRWidgetService {
return axios.get(this.endpoints.mergeActionsContentPath); return axios.get(this.endpoints.mergeActionsContentPath);
} }
rebase() {
return axios.post(this.endpoints.rebasePath);
}
static stopEnvironment(url) { static stopEnvironment(url) {
return axios.post(url); return axios.post(url);
} }
......
...@@ -25,6 +25,8 @@ export default function deviseState(data) { ...@@ -25,6 +25,8 @@ export default function deviseState(data) {
return this.mergeError ? stateKey.autoMergeFailed : stateKey.mergeWhenPipelineSucceeds; return this.mergeError ? stateKey.autoMergeFailed : stateKey.mergeWhenPipelineSucceeds;
} else if (!this.canMerge) { } else if (!this.canMerge) {
return stateKey.notAllowedToMerge; return stateKey.notAllowedToMerge;
} else if (this.shouldBeRebased) {
return stateKey.rebase;
} else if (this.canBeMerged) { } else if (this.canBeMerged) {
return stateKey.readyToMerge; return stateKey.readyToMerge;
} }
......
...@@ -26,6 +26,7 @@ export default class MergeRequestStore { ...@@ -26,6 +26,7 @@ export default class MergeRequestStore {
this.divergedCommitsCount = data.diverged_commits_count; this.divergedCommitsCount = data.diverged_commits_count;
this.pipeline = data.pipeline || {}; this.pipeline = data.pipeline || {};
this.deployments = this.deployments || data.deployments || []; this.deployments = this.deployments || data.deployments || [];
this.initRebase(data);
if (data.issues_links) { if (data.issues_links) {
const links = data.issues_links; const links = data.issues_links;
...@@ -124,6 +125,13 @@ export default class MergeRequestStore { ...@@ -124,6 +125,13 @@ export default class MergeRequestStore {
return this.state === stateKey.nothingToMerge; return this.state === stateKey.nothingToMerge;
} }
initRebase(data) {
this.canPushToSourceBranch = data.can_push_to_source_branch;
this.rebaseInProgress = data.rebase_in_progress;
this.approvalsLeft = !data.approved;
this.rebasePath = data.rebase_path;
}
static buildMetrics(metrics) { static buildMetrics(metrics) {
if (!metrics) { if (!metrics) {
return {}; return {};
......
...@@ -17,6 +17,7 @@ const stateToComponentMap = { ...@@ -17,6 +17,7 @@ const stateToComponentMap = {
failedToMerge: 'mr-widget-failed-to-merge', failedToMerge: 'mr-widget-failed-to-merge',
autoMergeFailed: 'mr-widget-auto-merge-failed', autoMergeFailed: 'mr-widget-auto-merge-failed',
shaMismatch: 'mr-widget-sha-mismatch', shaMismatch: 'mr-widget-sha-mismatch',
rebase: 'mr-widget-rebase',
}; };
const statesToShowHelpWidget = [ const statesToShowHelpWidget = [
...@@ -29,6 +30,7 @@ const statesToShowHelpWidget = [ ...@@ -29,6 +30,7 @@ const statesToShowHelpWidget = [
'pipelineFailed', 'pipelineFailed',
'pipelineBlocked', 'pipelineBlocked',
'autoMergeFailed', 'autoMergeFailed',
'rebase',
]; ];
export const stateKey = { export const stateKey = {
...@@ -46,6 +48,7 @@ export const stateKey = { ...@@ -46,6 +48,7 @@ export const stateKey = {
mergeWhenPipelineSucceeds: 'mergeWhenPipelineSucceeds', mergeWhenPipelineSucceeds: 'mergeWhenPipelineSucceeds',
notAllowedToMerge: 'notAllowedToMerge', notAllowedToMerge: 'notAllowedToMerge',
readyToMerge: 'readyToMerge', readyToMerge: 'readyToMerge',
rebase: 'rebase',
}; };
export default { export default {
......
<script>
import { __ } from '~/locale';
/**
* Port of detail_behavior expand button.
*
* @example
* <expand-button>
* <template slot="expanded">
* Text goes here.
* </template>
* </expand-button>
*/
export default {
name: 'expandButton',
data() {
return {
isCollapsed: true,
};
},
computed: {
ariaLabel() {
return __('Click to expand text');
},
},
methods: {
onClick() {
this.isCollapsed = !this.isCollapsed;
},
},
};
</script>
<template>
<span>
<button
type="button"
v-show="isCollapsed"
class="text-expander btn-blank"
:aria-label="ariaLabel"
@click="onClick">
...
</button>
<span v-show="!isCollapsed">
<slot name="expanded"></slot>
</span>
</span>
</template>
...@@ -3,6 +3,10 @@ export default { ...@@ -3,6 +3,10 @@ export default {
name: 'modal', name: 'modal',
props: { props: {
id: {
type: String,
required: false,
},
title: { title: {
type: String, type: String,
required: false, required: false,
...@@ -62,11 +66,11 @@ export default { ...@@ -62,11 +66,11 @@ export default {
}, },
methods: { methods: {
close() { emitCancel(event) {
this.$emit('toggle', false); this.$emit('cancel', event);
}, },
emitSubmit(status) { emitSubmit(event) {
this.$emit('submit', status); this.$emit('submit', event);
}, },
}, },
}; };
...@@ -75,7 +79,9 @@ export default { ...@@ -75,7 +79,9 @@ export default {
<template> <template>
<div class="modal-open"> <div class="modal-open">
<div <div
class="modal show" :id="id"
class="modal"
:class="id ? '' : 'show'"
role="dialog" role="dialog"
tabindex="-1" tabindex="-1"
> >
...@@ -93,7 +99,8 @@ export default { ...@@ -93,7 +99,8 @@ export default {
<button <button
type="button" type="button"
class="close pull-right" class="close pull-right"
@click="close" @click="emitCancel($event)"
data-dismiss="modal"
aria-label="Close" aria-label="Close"
> >
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
...@@ -110,7 +117,8 @@ export default { ...@@ -110,7 +117,8 @@ export default {
type="button" type="button"
class="btn pull-left" class="btn pull-left"
:class="btnCancelKindClass" :class="btnCancelKindClass"
@click="close"> @click="emitCancel($event)"
data-dismiss="modal">
{{ closeButtonLabel }} {{ closeButtonLabel }}
</button> </button>
<button <button
...@@ -119,13 +127,17 @@ export default { ...@@ -119,13 +127,17 @@ export default {
class="btn pull-right js-primary-button" class="btn pull-right js-primary-button"
:disabled="submitDisabled" :disabled="submitDisabled"
:class="btnKindClass" :class="btnKindClass"
@click="emitSubmit(true)"> @click="emitSubmit($event)"
data-dismiss="modal">
{{ primaryButtonLabel }} {{ primaryButtonLabel }}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-backdrop fade in" /> <div
v-if="!id"
class="modal-backdrop fade in">
</div>
</div> </div>
</template> </template>
...@@ -70,7 +70,7 @@ export default { ...@@ -70,7 +70,7 @@ export default {
class="recaptcha-modal js-recaptcha-modal" class="recaptcha-modal js-recaptcha-modal"
:hide-footer="true" :hide-footer="true"
:title="__('Please solve the reCAPTCHA')" :title="__('Please solve the reCAPTCHA')"
@toggle="close" @cancel="close"
> >
<div slot="body"> <div slot="body">
<p> <p>
......
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
vertical-align: top; vertical-align: top;
&.s16 { font-size: 12px; line-height: 1.33; } &.s16 { font-size: 12px; line-height: 1.33; }
&.s24 { font-size: 14px; line-height: 1.8; } &.s24 { font-size: 13px; line-height: 1.8; }
&.s26 { font-size: 20px; line-height: 1.33; } &.s26 { font-size: 20px; line-height: 1.33; }
&.s32 { font-size: 20px; line-height: 30px; } &.s32 { font-size: 20px; line-height: 30px; }
&.s40 { font-size: 16px; line-height: 38px; } &.s40 { font-size: 16px; line-height: 38px; }
......
...@@ -516,7 +516,7 @@ ...@@ -516,7 +516,7 @@
.header-user { .header-user {
.dropdown-menu-nav { .dropdown-menu-nav {
width: auto; width: auto;
min-width: 140px; min-width: 160px;
margin-top: 4px; margin-top: 4px;
color: $gl-text-color; color: $gl-text-color;
left: auto; left: auto;
......
...@@ -126,10 +126,8 @@ ul.content-list { ...@@ -126,10 +126,8 @@ ul.content-list {
} }
.description { .description {
p {
@include str-truncated; @include str-truncated;
margin-bottom: 0; color: $gl-text-color-secondary;
}
} }
.controls { .controls {
...@@ -315,7 +313,7 @@ ul.indent-list { ...@@ -315,7 +313,7 @@ ul.indent-list {
border: 2px solid $white-normal; border: 2px solid $white-normal;
&.identicon { &.identicon {
line-height: 30px; line-height: 15px;
} }
} }
} }
...@@ -349,14 +347,19 @@ ul.indent-list { ...@@ -349,14 +347,19 @@ ul.indent-list {
.folder-caret { .folder-caret {
width: 15px; width: 15px;
svg {
margin-bottom: 2px;
}
} }
.item-type-icon { .item-type-icon {
margin-top: 2px;
width: 20px; width: 20px;
} }
> .group-row:not(.has-children) { > .group-row:not(.has-children) {
.folder-caret .fa { .folder-caret {
opacity: 0; opacity: 0;
} }
} }
...@@ -439,12 +442,61 @@ ul.indent-list { ...@@ -439,12 +442,61 @@ ul.indent-list {
.avatar-container > a { .avatar-container > a {
width: 100%; width: 100%;
text-decoration: none;
} }
&.has-more-items { &.has-more-items {
display: block; display: block;
padding: 20px 10px; padding: 20px 10px;
} }
.stats {
position: relative;
line-height: 46px;
> span {
display: inline-flex;
align-items: center;
height: 16px;
min-width: 30px;
}
> span:last-child {
margin-right: 0;
}
.stat-value {
margin: 2px 0 0 5px;
}
}
.controls {
margin-left: 5px;
> .btn {
margin-right: $btn-xs-side-margin;
}
}
}
.project-row-contents .stats {
line-height: inherit;
> span:first-child {
margin-left: 25px;
}
.item-visibility {
margin-right: 0;
}
.last-updated {
position: absolute;
right: 12px;
min-width: 250px;
text-align: right;
color: $gl-text-color-secondary;
}
} }
} }
...@@ -456,12 +508,12 @@ ul.indent-list { ...@@ -456,12 +508,12 @@ ul.indent-list {
ul.group-list-tree { ul.group-list-tree {
li.group-row { li.group-row {
&.has-description .title { > .group-row-contents .title {
line-height: inherit; line-height: $list-text-height;
} }
&:not(.has-description) .title { &.has-description > .group-row-contents .title {
line-height: $list-text-height; line-height: inherit;
} }
} }
} }
......
...@@ -727,3 +727,8 @@ Popup ...@@ -727,3 +727,8 @@ Popup
$popup-triangle-size: 15px; $popup-triangle-size: 15px;
$popup-triangle-border-size: 1px; $popup-triangle-border-size: 1px;
$popup-box-shadow-color: rgba(90, 90, 90, 0.05); $popup-box-shadow-color: rgba(90, 90, 90, 0.05);
/*
Multi file editor
*/
$border-color-settings: #e1e1e1;
...@@ -20,6 +20,22 @@ ...@@ -20,6 +20,22 @@
} }
} }
.multi-file-editor-options {
label {
margin-right: 20px;
text-align: center;
}
.preview {
font-size: 0;
img {
border: 1px solid $border-color-settings;
border-radius: 4px;
}
}
}
.application-theme { .application-theme {
label { label {
margin-right: 20px; margin-right: 20px;
......
...@@ -92,6 +92,19 @@ ...@@ -92,6 +92,19 @@
padding: 6px 12px; padding: 6px 12px;
} }
.multi-file-loading-container {
margin-top: 10px;
padding: 10px;
.animation-container {
background: $gray-light;
div {
background: $gray-light;
}
}
}
table.table tr td.multi-file-table-name { table.table tr td.multi-file-table-name {
width: 350px; width: 350px;
padding: 6px 12px; padding: 6px 12px;
......
...@@ -5,8 +5,6 @@ module IssuesAction ...@@ -5,8 +5,6 @@ module IssuesAction
# rubocop:disable Gitlab/ModuleWithInstanceVariables # rubocop:disable Gitlab/ModuleWithInstanceVariables
def issues def issues
@finder_type = IssuesFinder @finder_type = IssuesFinder
@label = finder.labels.first
@issues = issuables_collection @issues = issuables_collection
.non_archived .non_archived
.page(params[:page]) .page(params[:page])
......
...@@ -5,7 +5,6 @@ module MergeRequestsAction ...@@ -5,7 +5,6 @@ module MergeRequestsAction
# rubocop:disable Gitlab/ModuleWithInstanceVariables # rubocop:disable Gitlab/ModuleWithInstanceVariables
def merge_requests def merge_requests
@finder_type = MergeRequestsFinder @finder_type = MergeRequestsFinder
@label = finder.labels.first
@merge_requests = issuables_collection.page(params[:page]) @merge_requests = issuables_collection.page(params[:page])
......
...@@ -10,6 +10,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -10,6 +10,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort] before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
before_action :set_issuables_index, only: [:index] before_action :set_issuables_index, only: [:index]
before_action :authenticate_user!, only: [:assign_related_issues] before_action :authenticate_user!, only: [:assign_related_issues]
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
def index def index
@merge_requests = @issuables @merge_requests = @issuables
...@@ -223,6 +224,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -223,6 +224,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
render json: environments render json: environments
end end
def rebase
RebaseWorker.perform_async(@merge_request.id, current_user.id)
render nothing: true, status: 200
end
protected protected
alias_method :subscribable_resource, :merge_request alias_method :subscribable_resource, :merge_request
...@@ -322,4 +329,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -322,4 +329,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@finder_type = MergeRequestsFinder @finder_type = MergeRequestsFinder
super super
end end
def check_user_can_push_to_source_branch!
return access_denied! unless @merge_request.source_branch_exists?
access_check = ::Gitlab::UserAccess
.new(current_user, project: @merge_request.source_project)
.can_push_to_branch?(@merge_request.source_branch)
access_denied! unless access_check
end
end end
...@@ -353,7 +353,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -353,7 +353,7 @@ class ProjectsController < Projects::ApplicationController
end end
def repo_exists? def repo_exists?
project.repository_exists? && !project.empty_repo? && project.repo project.repository_exists? && !project.empty_repo?
rescue Gitlab::Git::Repository::NoRepository rescue Gitlab::Git::Repository::NoRepository
project.repository.expire_exists_cache project.repository.expire_exists_cache
......
...@@ -374,18 +374,13 @@ class IssuableFinder ...@@ -374,18 +374,13 @@ class IssuableFinder
end end
def by_label(items) def by_label(items)
if labels? return items unless labels?
items =
if filter_by_no_label? if filter_by_no_label?
items = items.without_label items.without_label
else else
items = items.with_label(label_names, params[:sort]) items.with_label(label_names, params[:sort])
items_projects = projects(items)
if items_projects
label_ids = LabelsFinder.new(current_user, project_ids: items_projects).execute(skip_authorization: true).select(:id)
items = items.where(labels: { id: label_ids })
end
end
end end
items items
......
class LabelsFinder < UnionFinder class LabelsFinder < UnionFinder
include Gitlab::Utils::StrongMemoize
def initialize(current_user, params = {}) def initialize(current_user, params = {})
@current_user = current_user @current_user = current_user
@params = params @params = params
...@@ -32,6 +34,8 @@ class LabelsFinder < UnionFinder ...@@ -32,6 +34,8 @@ class LabelsFinder < UnionFinder
label_ids << project.labels label_ids << project.labels
end end
end end
elsif only_group_labels?
label_ids << Label.where(group_id: group.id)
else else
label_ids << Label.where(group_id: projects.group_ids) label_ids << Label.where(group_id: projects.group_ids)
label_ids << Label.where(project_id: projects.select(:id)) label_ids << Label.where(project_id: projects.select(:id))
...@@ -51,6 +55,13 @@ class LabelsFinder < UnionFinder ...@@ -51,6 +55,13 @@ class LabelsFinder < UnionFinder
items.where(title: title) items.where(title: title)
end end
def group
strong_memoize(:group) do
group = Group.find(params[:group_id])
authorized_to_read_labels?(group) && group
end
end
def group? def group?
params[:group_id].present? params[:group_id].present?
end end
...@@ -63,6 +74,10 @@ class LabelsFinder < UnionFinder ...@@ -63,6 +74,10 @@ class LabelsFinder < UnionFinder
params[:project_ids].present? params[:project_ids].present?
end end
def only_group_labels?
params[:only_group_labels]
end
def title def title
params[:title] || params[:name] params[:title] || params[:name]
end end
...@@ -96,9 +111,9 @@ class LabelsFinder < UnionFinder ...@@ -96,9 +111,9 @@ class LabelsFinder < UnionFinder
@projects @projects
end end
def authorized_to_read_labels?(project) def authorized_to_read_labels?(label_parent)
return true if skip_authorization return true if skip_authorization
Ability.allowed?(current_user, :read_label, project) Ability.allowed?(current_user, :read_label, label_parent)
end end
end end
...@@ -23,4 +23,12 @@ module BranchesHelper ...@@ -23,4 +23,12 @@ module BranchesHelper
def protected_branch?(project, branch) def protected_branch?(project, branch)
ProtectedBranch.protected?(project, branch.name) ProtectedBranch.protected?(project, branch.name)
end end
def diverging_count_label(count)
if count >= Repository::MAX_DIVERGING_COUNT
"#{Repository::MAX_DIVERGING_COUNT - 1}+"
else
count.to_s
end
end
end end
...@@ -389,7 +389,7 @@ module ProjectsHelper ...@@ -389,7 +389,7 @@ module ProjectsHelper
end end
def add_special_file_path(project, file_name:, commit_message: nil, branch_name: nil, context: nil) def add_special_file_path(project, file_name:, commit_message: nil, branch_name: nil, context: nil)
commit_message ||= s_("CommitMessage|Add %{file_name}") % { file_name: file_name.downcase } commit_message ||= s_("CommitMessage|Add %{file_name}") % { file_name: file_name }
project_new_blob_path( project_new_blob_path(
project, project,
project.default_branch || 'master', project.default_branch || 'master',
......
module DeploymentPlatform
def deployment_platform
@deployment_platform ||=
find_cluster_platform_kubernetes ||
find_kubernetes_service_integration ||
build_cluster_and_deployment_platform
end
private
def find_cluster_platform_kubernetes
clusters.find_by(enabled: true)&.platform_kubernetes
end
def find_kubernetes_service_integration
services.deployment.reorder(nil).find_by(active: true)
end
def build_cluster_and_deployment_platform
return unless kubernetes_service_template
cluster = ::Clusters::Cluster.create(cluster_attributes_from_service_template)
cluster.platform_kubernetes if cluster.persisted?
end
def kubernetes_service_template
@kubernetes_service_template ||= KubernetesService.active.find_by_template
end
def cluster_attributes_from_service_template
{
name: 'kubernetes-template',
projects: [self],
provider_type: :user,
platform_type: :kubernetes,
platform_kubernetes_attributes: platform_kubernetes_attributes_from_service_template
}
end
def platform_kubernetes_attributes_from_service_template
{
api_url: kubernetes_service_template.api_url,
ca_pem: kubernetes_service_template.ca_pem,
token: kubernetes_service_template.token,
namespace: kubernetes_service_template.namespace
}
end
end
...@@ -10,12 +10,12 @@ module RelativePositioning ...@@ -10,12 +10,12 @@ module RelativePositioning
after_save :save_positionable_neighbours after_save :save_positionable_neighbours
end end
def project_ids def min_relative_position
[project.id] self.class.in_parents(parent_ids).minimum(:relative_position)
end end
def max_relative_position def max_relative_position
self.class.in_projects(project_ids).maximum(:relative_position) self.class.in_parents(parent_ids).maximum(:relative_position)
end end
def prev_relative_position def prev_relative_position
...@@ -23,7 +23,7 @@ module RelativePositioning ...@@ -23,7 +23,7 @@ module RelativePositioning
if self.relative_position if self.relative_position
prev_pos = self.class prev_pos = self.class
.in_projects(project_ids) .in_parents(parent_ids)
.where('relative_position < ?', self.relative_position) .where('relative_position < ?', self.relative_position)
.maximum(:relative_position) .maximum(:relative_position)
end end
...@@ -36,7 +36,7 @@ module RelativePositioning ...@@ -36,7 +36,7 @@ module RelativePositioning
if self.relative_position if self.relative_position
next_pos = self.class next_pos = self.class
.in_projects(project_ids) .in_parents(parent_ids)
.where('relative_position > ?', self.relative_position) .where('relative_position > ?', self.relative_position)
.minimum(:relative_position) .minimum(:relative_position)
end end
...@@ -63,7 +63,7 @@ module RelativePositioning ...@@ -63,7 +63,7 @@ module RelativePositioning
pos_after = before.next_relative_position pos_after = before.next_relative_position
if before.shift_after? if before.shift_after?
issue_to_move = self.class.in_projects(project_ids).find_by!(relative_position: pos_after) issue_to_move = self.class.in_parents(parent_ids).find_by!(relative_position: pos_after)
issue_to_move.move_after issue_to_move.move_after
@positionable_neighbours = [issue_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables @positionable_neighbours = [issue_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
...@@ -78,7 +78,7 @@ module RelativePositioning ...@@ -78,7 +78,7 @@ module RelativePositioning
pos_before = after.prev_relative_position pos_before = after.prev_relative_position
if after.shift_before? if after.shift_before?
issue_to_move = self.class.in_projects(project_ids).find_by!(relative_position: pos_before) issue_to_move = self.class.in_parents(parent_ids).find_by!(relative_position: pos_before)
issue_to_move.move_before issue_to_move.move_before
@positionable_neighbours = [issue_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables @positionable_neighbours = [issue_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
...@@ -92,6 +92,10 @@ module RelativePositioning ...@@ -92,6 +92,10 @@ module RelativePositioning
self.relative_position = position_between(max_relative_position || START_POSITION, MAX_POSITION) self.relative_position = position_between(max_relative_position || START_POSITION, MAX_POSITION)
end end
def move_to_start
self.relative_position = position_between(min_relative_position || START_POSITION, MIN_POSITION)
end
# Indicates if there is an issue that should be shifted to free the place # Indicates if there is an issue that should be shifted to free the place
def shift_after? def shift_after?
next_pos = next_relative_position next_pos = next_relative_position
......
...@@ -48,7 +48,18 @@ class Event < ActiveRecord::Base ...@@ -48,7 +48,18 @@ class Event < ActiveRecord::Base
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
belongs_to :project belongs_to :project
belongs_to :target, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
belongs_to :target, -> {
# If the association for "target" defines an "author" association we want to
# eager-load this so Banzai & friends don't end up performing N+1 queries to
# get the authors of notes, issues, etc.
if reflections['events'].active_record.reflect_on_association(:author)
includes(:author)
else
self
end
}, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
has_one :push_event_payload has_one :push_event_payload
# Callbacks # Callbacks
......
...@@ -35,6 +35,8 @@ class Issue < ActiveRecord::Base ...@@ -35,6 +35,8 @@ class Issue < ActiveRecord::Base
validates :project, presence: true validates :project, presence: true
alias_attribute :parent_ids, :project_id
scope :in_projects, ->(project_ids) { where(project_id: project_ids) } scope :in_projects, ->(project_ids) { where(project_id: project_ids) }
scope :assigned, -> { where('EXISTS (SELECT TRUE FROM issue_assignees WHERE issue_id = issues.id)') } scope :assigned, -> { where('EXISTS (SELECT TRUE FROM issue_assignees WHERE issue_id = issues.id)') }
...@@ -78,6 +80,10 @@ class Issue < ActiveRecord::Base ...@@ -78,6 +80,10 @@ class Issue < ActiveRecord::Base
acts_as_paranoid acts_as_paranoid
class << self
alias_method :in_parents, :in_projects
end
def self.reference_prefix def self.reference_prefix
'#' '#'
end end
......
...@@ -156,6 +156,13 @@ class MergeRequest < ActiveRecord::Base ...@@ -156,6 +156,13 @@ class MergeRequest < ActiveRecord::Base
'!' '!'
end end
def rebase_in_progress?
# The source project can be deleted
return false unless source_project
source_project.repository.rebase_in_progress?(id)
end
# Use this method whenever you need to make sure the head_pipeline is synced with the # Use this method whenever you need to make sure the head_pipeline is synced with the
# branch head commit, for example checking if a merge request can be merged. # branch head commit, for example checking if a merge request can be merged.
# For more information check: https://gitlab.com/gitlab-org/gitlab-ce/issues/40004 # For more information check: https://gitlab.com/gitlab-org/gitlab-ce/issues/40004
...@@ -607,7 +614,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -607,7 +614,7 @@ class MergeRequest < ActiveRecord::Base
check_if_can_be_merged check_if_can_be_merged
can_be_merged? can_be_merged? && !should_be_rebased?
end end
def mergeable_state?(skip_ci_check: false) def mergeable_state?(skip_ci_check: false)
......
...@@ -19,6 +19,7 @@ class Project < ActiveRecord::Base ...@@ -19,6 +19,7 @@ class Project < ActiveRecord::Base
include Routable include Routable
include GroupDescendant include GroupDescendant
include Gitlab::SQL::Pattern include Gitlab::SQL::Pattern
include DeploymentPlatform
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
extend Gitlab::CurrentSettings extend Gitlab::CurrentSettings
...@@ -904,12 +905,6 @@ class Project < ActiveRecord::Base ...@@ -904,12 +905,6 @@ class Project < ActiveRecord::Base
@ci_service ||= ci_services.reorder(nil).find_by(active: true) @ci_service ||= ci_services.reorder(nil).find_by(active: true)
end end
# TODO: This will be extended for multiple enviroment clusters
def deployment_platform
@deployment_platform ||= clusters.find_by(enabled: true)&.platform_kubernetes
@deployment_platform ||= services.where(category: :deployment).reorder(nil).find_by(active: true)
end
def monitoring_services def monitoring_services
services.where(category: :monitoring) services.where(category: :monitoring)
end end
...@@ -992,10 +987,6 @@ class Project < ActiveRecord::Base ...@@ -992,10 +987,6 @@ class Project < ActiveRecord::Base
false false
end end
def repo
repository.rugged
end
def url_to_repo def url_to_repo
gitlab_shell.url_to_repo(full_path) gitlab_shell.url_to_repo(full_path)
end end
...@@ -1438,7 +1429,7 @@ class Project < ActiveRecord::Base ...@@ -1438,7 +1429,7 @@ class Project < ActiveRecord::Base
# We'd need to keep track of project full path otherwise directory tree # We'd need to keep track of project full path otherwise directory tree
# created with hashed storage enabled cannot be usefully imported using # created with hashed storage enabled cannot be usefully imported using
# the import rake task. # the import rake task.
repo.config['gitlab.fullpath'] = gl_full_path repository.rugged.config['gitlab.fullpath'] = gl_full_path
rescue Gitlab::Git::Repository::NoRepository => e rescue Gitlab::Git::Repository::NoRepository => e
Rails.logger.error("Error writing to .git/config for project #{full_path} (#{id}): #{e.message}.") Rails.logger.error("Error writing to .git/config for project #{full_path} (#{id}): #{e.message}.")
nil nil
......
...@@ -4,6 +4,7 @@ class Repository ...@@ -4,6 +4,7 @@ class Repository
REF_MERGE_REQUEST = 'merge-requests'.freeze REF_MERGE_REQUEST = 'merge-requests'.freeze
REF_KEEP_AROUND = 'keep-around'.freeze REF_KEEP_AROUND = 'keep-around'.freeze
REF_ENVIRONMENTS = 'environments'.freeze REF_ENVIRONMENTS = 'environments'.freeze
MAX_DIVERGING_COUNT = 1000
RESERVED_REFS_NAMES = %W[ RESERVED_REFS_NAMES = %W[
heads heads
...@@ -278,11 +279,12 @@ class Repository ...@@ -278,11 +279,12 @@ class Repository
cache.fetch(:"diverging_commit_counts_#{branch.name}") do cache.fetch(:"diverging_commit_counts_#{branch.name}") do
# Rugged seems to throw a `ReferenceError` when given branch_names rather # Rugged seems to throw a `ReferenceError` when given branch_names rather
# than SHA-1 hashes # than SHA-1 hashes
number_commits_behind = raw_repository number_commits_behind, number_commits_ahead =
.count_commits_between(branch.dereferenced_target.sha, root_ref_hash) raw_repository.count_commits_between(
root_ref_hash,
number_commits_ahead = raw_repository branch.dereferenced_target.sha,
.count_commits_between(root_ref_hash, branch.dereferenced_target.sha) left_right: true,
max_count: MAX_DIVERGING_COUNT)
{ behind: number_commits_behind, ahead: number_commits_ahead } { behind: number_commits_behind, ahead: number_commits_ahead }
end end
...@@ -781,34 +783,30 @@ class Repository ...@@ -781,34 +783,30 @@ class Repository
end end
def create_dir(user, path, **options) def create_dir(user, path, **options)
options[:user] = user
options[:actions] = [{ action: :create_dir, file_path: path }] options[:actions] = [{ action: :create_dir, file_path: path }]
multi_action(**options) multi_action(user, **options)
end end
def create_file(user, path, content, **options) def create_file(user, path, content, **options)
options[:user] = user
options[:actions] = [{ action: :create, file_path: path, content: content }] options[:actions] = [{ action: :create, file_path: path, content: content }]
multi_action(**options) multi_action(user, **options)
end end
def update_file(user, path, content, **options) def update_file(user, path, content, **options)
previous_path = options.delete(:previous_path) previous_path = options.delete(:previous_path)
action = previous_path && previous_path != path ? :move : :update action = previous_path && previous_path != path ? :move : :update
options[:user] = user
options[:actions] = [{ action: action, file_path: path, previous_path: previous_path, content: content }] options[:actions] = [{ action: action, file_path: path, previous_path: previous_path, content: content }]
multi_action(**options) multi_action(user, **options)
end end
def delete_file(user, path, **options) def delete_file(user, path, **options)
options[:user] = user
options[:actions] = [{ action: :delete, file_path: path }] options[:actions] = [{ action: :delete, file_path: path }]
multi_action(**options) multi_action(user, **options)
end end
def with_cache_hooks def with_cache_hooks
...@@ -822,59 +820,14 @@ class Repository ...@@ -822,59 +820,14 @@ class Repository
result.newrev result.newrev
end end
def with_branch(user, *args) def multi_action(user, **options)
with_cache_hooks do start_project = options.delete(:start_project)
Gitlab::Git::OperationService.new(user, raw_repository).with_branch(*args) do |start_commit|
yield start_commit
end
end
end
# rubocop:disable Metrics/ParameterLists
def multi_action(
user:, branch_name:, message:, actions:,
author_email: nil, author_name: nil,
start_branch_name: nil, start_project: project)
with_branch(
user,
branch_name,
start_branch_name: start_branch_name,
start_repository: start_project.repository.raw_repository) do |start_commit|
index = Gitlab::Git::Index.new(raw_repository)
if start_commit
index.read_tree(start_commit.rugged_commit.tree)
parents = [start_commit.sha]
else
parents = []
end
actions.each do |options|
index.public_send(options.delete(:action), options) # rubocop:disable GitlabSecurity/PublicSend
end
options = {
tree: index.write_tree,
message: message,
parents: parents
}
options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
create_commit(options) if start_project
end options[:start_repository] = start_project.repository.raw_repository
end end
# rubocop:enable Metrics/ParameterLists
def get_committer_and_author(user, email: nil, name: nil)
committer = user_to_committer(user)
author = Gitlab::Git.committer_hash(email: email, name: name) || committer
{ with_cache_hooks { raw.multi_action(user, **options) }
author: author,
committer: committer
}
end end
def can_be_merged?(source_sha, target_branch) def can_be_merged?(source_sha, target_branch)
...@@ -1099,6 +1052,13 @@ class Repository ...@@ -1099,6 +1052,13 @@ class Repository
@project.repository_storage_path @project.repository_storage_path
end end
def rebase(user, merge_request)
raw.rebase(user, merge_request.id, branch: merge_request.source_branch,
branch_sha: merge_request.source_branch_sha,
remote_repository: merge_request.target_project.repository.raw,
remote_branch: merge_request.target_branch)
end
private private
# TODO Generice finder, later split this on finders by Ref or Oid # TODO Generice finder, later split this on finders by Ref or Oid
......
...@@ -44,6 +44,7 @@ class Service < ActiveRecord::Base ...@@ -44,6 +44,7 @@ class Service < ActiveRecord::Base
scope :pipeline_hooks, -> { where(pipeline_events: true, active: true) } scope :pipeline_hooks, -> { where(pipeline_events: true, active: true) }
scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) } scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
scope :external_issue_trackers, -> { issue_trackers.active.without_defaults } scope :external_issue_trackers, -> { issue_trackers.active.without_defaults }
scope :deployment, -> { where(category: 'deployment') }
default_value_for :category, 'common' default_value_for :category, 'common'
...@@ -271,6 +272,10 @@ class Service < ActiveRecord::Base ...@@ -271,6 +272,10 @@ class Service < ActiveRecord::Base
nil nil
end end
def self.find_by_template
find_by(template: true)
end
private private
def cache_project_has_external_issue_tracker def cache_project_has_external_issue_tracker
......
...@@ -794,10 +794,7 @@ class User < ActiveRecord::Base ...@@ -794,10 +794,7 @@ class User < ActiveRecord::Base
# `User.select(:id)` raises # `User.select(:id)` raises
# `ActiveModel::MissingAttributeError: missing attribute: projects_limit` # `ActiveModel::MissingAttributeError: missing attribute: projects_limit`
# without this safeguard! # without this safeguard!
return unless has_attribute?(:projects_limit) return unless has_attribute?(:projects_limit) && projects_limit.nil?
connection_default_value_defined = new_record? && !projects_limit_changed?
return unless projects_limit.nil? || connection_default_value_defined
self.projects_limit = current_application_settings.default_projects_limit self.projects_limit = current_application_settings.default_projects_limit
end end
......
...@@ -28,12 +28,18 @@ class GroupPolicy < BasePolicy ...@@ -28,12 +28,18 @@ class GroupPolicy < BasePolicy
with_options scope: :subject, score: 0 with_options scope: :subject, score: 0
condition(:request_access_enabled) { @subject.request_access_enabled } condition(:request_access_enabled) { @subject.request_access_enabled }
rule { public_group } .enable :read_group rule { public_group }.policy do
enable :read_group
enable :read_list
enable :read_label
end
rule { logged_in_viewable }.enable :read_group rule { logged_in_viewable }.enable :read_group
rule { guest }.policy do rule { guest }.policy do
enable :read_group enable :read_group
enable :upload_file enable :upload_file
enable :read_label
end end
rule { admin } .enable :read_group rule { admin } .enable :read_group
......
...@@ -76,6 +76,12 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated ...@@ -76,6 +76,12 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end end
end end
def rebase_path
if !rebase_in_progress? && should_be_rebased? && user_can_push_to_source_branch?
rebase_project_merge_request_path(project, merge_request)
end
end
def target_branch_tree_path def target_branch_tree_path
if target_branch_exists? if target_branch_exists?
project_tree_path(project, target_branch) project_tree_path(project, target_branch)
...@@ -152,6 +158,10 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated ...@@ -152,6 +158,10 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
user_can_collaborate_with_project? && can_be_cherry_picked? user_can_collaborate_with_project? && can_be_cherry_picked?
end end
def can_push_to_source_branch?
source_branch_exists? && user_can_push_to_source_branch?
end
private private
def conflicts def conflicts
...@@ -174,6 +184,14 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated ...@@ -174,6 +184,14 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end.sort.to_sentence end.sort.to_sentence
end end
def user_can_push_to_source_branch?
return false unless source_branch_exists?
::Gitlab::UserAccess
.new(current_user, project: source_project)
.can_push_to_branch?(source_branch)
end
def user_can_collaborate_with_project? def user_can_collaborate_with_project?
can?(current_user, :push_code, project) || can?(current_user, :push_code, project) ||
(current_user && current_user.already_forked?(project)) (current_user && current_user.already_forked?(project))
......
...@@ -4,4 +4,5 @@ class MergeRequestBasicEntity < IssuableSidebarEntity ...@@ -4,4 +4,5 @@ class MergeRequestBasicEntity < IssuableSidebarEntity
expose :merge_error expose :merge_error
expose :state expose :state
expose :source_branch_exists?, as: :source_branch_exists expose :source_branch_exists?, as: :source_branch_exists
expose :rebase_in_progress?, as: :rebase_in_progress
end end
...@@ -23,6 +23,16 @@ class MergeRequestWidgetEntity < IssuableEntity ...@@ -23,6 +23,16 @@ class MergeRequestWidgetEntity < IssuableEntity
MergeRequestMetricsEntity.new(metrics).as_json MergeRequestMetricsEntity.new(metrics).as_json
end end
expose :rebase_commit_sha
expose :rebase_in_progress?, as: :rebase_in_progress
expose :can_push_to_source_branch do |merge_request|
presenter(merge_request).can_push_to_source_branch?
end
expose :rebase_path do |merge_request|
presenter(merge_request).rebase_path
end
# User entities # User entities
expose :merge_user, using: UserEntity expose :merge_user, using: UserEntity
......
...@@ -4,7 +4,7 @@ module Files ...@@ -4,7 +4,7 @@ module Files
def create_commit! def create_commit!
repository.multi_action( repository.multi_action(
user: current_user, current_user,
message: @commit_message, message: @commit_message,
branch_name: @branch_name, branch_name: @branch_name,
actions: params[:actions], actions: params[:actions],
...@@ -13,6 +13,8 @@ module Files ...@@ -13,6 +13,8 @@ module Files
start_project: @start_project, start_project: @start_project,
start_branch_name: @start_branch start_branch_name: @start_branch
) )
rescue ArgumentError => e
raise_error(e)
end end
private private
...@@ -20,16 +22,7 @@ module Files ...@@ -20,16 +22,7 @@ module Files
def validate! def validate!
super super
params[:actions].each do |action| params[:actions].each { |action| validate_file_status!(action) }
validate_action!(action)
validate_file_status!(action)
end
end
def validate_action!(action)
unless Gitlab::Git::Index::ACTIONS.include?(action[:action].to_s)
raise_error("Unknown action '#{action[:action]}'")
end
end end
def validate_file_status!(action) def validate_file_status!(action)
......
module MergeRequests
class RebaseService < MergeRequests::WorkingCopyBaseService
def execute(merge_request)
@merge_request = merge_request
if rebase
success
else
error('Failed to rebase. Should be done manually')
end
end
def rebase
if merge_request.rebase_in_progress?
log_error('Rebase task canceled: Another rebase is already in progress', save_message_on_model: true)
return false
end
rebase_sha = repository.rebase(current_user, merge_request)
merge_request.update_attributes(rebase_commit_sha: rebase_sha)
true
rescue => e
log_error('Failed to rebase branch:')
log_error(e.message, save_message_on_model: true)
false
end
end
end
module MergeRequests
class WorkingCopyBaseService < MergeRequests::BaseService
attr_reader :merge_request
def source_project
@source_project ||= merge_request.source_project
end
def target_project
@target_project ||= merge_request.target_project
end
def log_error(message, save_message_on_model: false)
Gitlab::GitLogger.error("#{self.class.name} error (#{merge_request.to_reference(full: true)}): #{message}")
merge_request.update(merge_error: message) if save_message_on_model
end
# Don't try to print expensive instance variables.
def inspect
"#<#{self.class} #{merge_request.to_reference(full: true)}>"
end
end
end
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
.ide-flash-container.flash-container .ide-flash-container.flash-container
#ide.ide-loading #ide.ide-loading{ data: {"empty-state-svg-path" => image_path('illustrations/multi_file_editor_empty.svg')} }
.text-center .text-center
= icon('spinner spin 2x') = icon('spinner spin 2x')
%h2.clgray= _('IDE Loading ...') %h2.clgray= _('Loading the GitLab IDE...')
...@@ -56,6 +56,8 @@ ...@@ -56,6 +56,8 @@
= link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username } = link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
%li %li
= link_to "Settings", profile_path = link_to "Settings", profile_path
%li
= link_to "Turn on multi edit", profile_preferences_path
- if current_user - if current_user
%li %li
= link_to "Help", help_path = link_to "Help", help_path
......
...@@ -49,6 +49,12 @@ ...@@ -49,6 +49,12 @@
= link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
Projects Projects
- if current_controller?('ide')
%li.line-separator.hidden-xs
= nav_link(controller: 'ide') do
= link_to '#', class: 'dashboard-shortcuts-web-ide', title: 'Web IDE' do
Web IDE
- if current_user.admin? || Gitlab::Sherlock.enabled? - if current_user.admin? || Gitlab::Sherlock.enabled?
%li.line-separator.hidden-xs %li.line-separator.hidden-xs
- if current_user.admin? - if current_user.admin?
......
...@@ -84,11 +84,13 @@ ...@@ -84,11 +84,13 @@
= s_('Profiles|Deleting an account has the following effects:') = s_('Profiles|Deleting an account has the following effects:')
= render 'users/deletion_guidance', user: current_user = render 'users/deletion_guidance', user: current_user
%button#delete-account-button.btn.btn-danger.disabled{ data: { toggle: 'modal',
target: '#delete-account-modal' } }
= s_('Profiles|Delete account')
#delete-account-modal{ data: { action_url: user_registration_path, #delete-account-modal{ data: { action_url: user_registration_path,
confirm_with_password: ('true' if current_user.confirm_deletion_with_password?), confirm_with_password: ('true' if current_user.confirm_deletion_with_password?),
username: current_user.username } } username: current_user.username } }
%button.btn.btn-danger.disabled
= s_('Profiles|Delete account')
- else - else
- if @user.solo_owned_groups.present? - if @user.solo_owned_groups.present?
%p %p
......
...@@ -3,6 +3,23 @@ ...@@ -3,6 +3,23 @@
= render 'profiles/head' = render 'profiles/head'
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f| = form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f|
.col-lg-4
%h4.prepend-top-0
GitLab multi file editor
%p Unlock an additional editing experience which makes it possible to edit and commit multiple files
.col-lg-8.multi-file-editor-options
= label_tag do
.preview.append-bottom-10= image_tag "multi-editor-off.png"
= f.radio_button :multi_file, "off", checked: true
Off
= label_tag do
.preview.append-bottom-10= image_tag "multi-editor-on.png"
= f.radio_button :multi_file, "on", checked: false
On
.col-sm-12
%hr
.col-lg-4.application-theme .col-lg-4.application-theme
%h4.prepend-top-0 %h4.prepend-top-0
GitLab navigation theme GitLab navigation theme
......
...@@ -10,4 +10,4 @@ ...@@ -10,4 +10,4 @@
No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded.
%br %br
%span.descr %span.descr
When fast-forward merge is not possible, the user must first rebase locally. When fast-forward merge is not possible, the user is given the option to rebase.
...@@ -10,4 +10,4 @@ ...@@ -10,4 +10,4 @@
This way you could make sure that if this merge request would build, after merging to target branch it would also build. This way you could make sure that if this merge request would build, after merging to target branch it would also build.
%br %br
%span.descr %span.descr
When fast-forward merge is not possible, the user must first rebase locally. When fast-forward merge is not possible, the user is given the option to rebase.
...@@ -66,16 +66,16 @@ ...@@ -66,16 +66,16 @@
= icon("trash-o") = icon("trash-o")
- if branch.name != @repository.root_ref - if branch.name != @repository.root_ref
.divergence-graph{ title: s_('%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead') % { number_commits_behind: number_commits_behind, .divergence-graph{ title: s_('%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead') % { number_commits_behind: diverging_count_label(number_commits_behind),
default_branch: @repository.root_ref, default_branch: @repository.root_ref,
number_commits_ahead: number_commits_ahead } } number_commits_ahead: diverging_count_label(number_commits_ahead) } }
.graph-side .graph-side
.bar.bar-behind{ style: "width: #{number_commits_behind * bar_graph_width_factor}%" } .bar.bar-behind{ style: "width: #{number_commits_behind * bar_graph_width_factor}%" }
%span.count.count-behind= number_commits_behind %span.count.count-behind= diverging_count_label(number_commits_behind)
.graph-separator .graph-separator
.graph-side .graph-side
.bar.bar-ahead{ style: "width: #{number_commits_ahead * bar_graph_width_factor}%" } .bar.bar-ahead{ style: "width: #{number_commits_ahead * bar_graph_width_factor}%" }
%span.count.count-ahead= number_commits_ahead %span.count.count-ahead= diverging_count_label(number_commits_ahead)
- if commit - if commit
......
...@@ -11,5 +11,5 @@ ...@@ -11,5 +11,5 @@
%label.text-danger %label.text-danger
= s_('ClusterIntegration|Remove cluster integration') = s_('ClusterIntegration|Remove cluster integration')
%p %p
= s_('ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine.') = s_("ClusterIntegration|Remove this cluster's configuration from this project. This will not delete your actual cluster.")
= link_to(s_('ClusterIntegration|Remove integration'), namespace_project_cluster_path(@project.namespace, @project, @cluster.id), method: :delete, class: 'btn btn-danger', data: { confirm: "Are you sure you want to remove cluster integration from this project? This will not delete your cluster on Google Kubernetes Engine"}) = link_to(s_('ClusterIntegration|Remove integration'), namespace_project_cluster_path(@project.namespace, @project, @cluster.id), method: :delete, class: 'btn btn-danger', data: { confirm: s_("ClusterIntegration|Are you sure you want to remove this cluster's integration? This will not delete your actual cluster.")})
%h4= s_('ClusterIntegration|Enable cluster integration') %h4= s_('ClusterIntegration|Cluster integration')
.settings-content
.settings-content
.hidden.js-cluster-error.alert.alert-danger.alert-block.append-bottom-10{ role: 'alert' } .hidden.js-cluster-error.alert.alert-danger.alert-block.append-bottom-10{ role: 'alert' }
= s_('ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine') = s_('ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine')
%p.js-error-reason %p.js-error-reason
...@@ -11,11 +11,4 @@ ...@@ -11,11 +11,4 @@
.hidden.js-cluster-success.alert.alert-success.alert-block.append-bottom-10{ role: 'alert' } .hidden.js-cluster-success.alert.alert-success.alert-block.append-bottom-10{ role: 'alert' }
= s_('ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster\'s details') = s_('ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster\'s details')
%p %p= s_('ClusterIntegration|Control how your cluster integrates with GitLab')
- if @cluster.enabled?
- if can?(current_user, :update_cluster, @cluster)
= s_('ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab\'s connection to it.')
- else
= s_('ClusterIntegration|Cluster integration is enabled for this project.')
- else
= s_('ClusterIntegration|Cluster integration is disabled for this project.')
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.table-mobile-content .table-mobile-content
= link_to cluster.name, namespace_project_cluster_path(@project.namespace, @project, cluster) = link_to cluster.name, namespace_project_cluster_path(@project.namespace, @project, cluster)
.table-section.section-30 .table-section.section-30
.table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Environment pattern") .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Environment scope")
.table-mobile-content= cluster.environment_scope .table-mobile-content= cluster.environment_scope
.table-section.section-30 .table-section.section-30
.table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Project namespace") .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Project namespace")
......
= form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field| = form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field|
= form_errors(@cluster) = form_errors(@cluster)
.form-group.append-bottom-20 .form-group.append-bottom-20
%h5= s_('ClusterIntegration|Integration status')
%p
- if @cluster.enabled?
- if can?(current_user, :update_cluster, @cluster)
= s_('ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab\'s connection to it.')
- else
= s_('ClusterIntegration|Cluster integration is enabled for this project.')
- else
= s_('ClusterIntegration|Cluster integration is disabled for this project.')
%label.append-bottom-10 %label.append-bottom-10
= field.hidden_field :enabled, { class: 'js-toggle-input'} = field.hidden_field :enabled, { class: 'js-toggle-input'}
...@@ -12,6 +21,13 @@ ...@@ -12,6 +21,13 @@
= sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked') = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
= sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked') = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
.form-group
%h5= s_('ClusterIntegration|Environment scope')
%p
= s_("ClusterIntegration|Choose which of your project's environments will use this cluster.")
= link_to s_("ClusterIntegration|Learn more about environments"), help_page_path('ci/environments')
= field.text_field :environment_scope, class: 'form-control js-select-on-focus', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope')
- if can?(current_user, :update_cluster, @cluster) - if can?(current_user, :update_cluster, @cluster)
.form-group .form-group
= field.submit _('Save'), class: 'btn btn-success' = field.submit _('Save changes'), class: 'btn btn-success'
...@@ -9,10 +9,6 @@ ...@@ -9,10 +9,6 @@
= form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field| = form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field|
= form_errors(@cluster) = form_errors(@cluster)
.form-group
= field.label :environment_scope, s_('ClusterIntegration|Environment scope')
= field.text_field :environment_scope, class: 'form-control js-select-on-focus', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope')
= field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field| = field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field|
.form-group .form-group
= platform_kubernetes_field.label :api_url, s_('ClusterIntegration|API URL') = platform_kubernetes_field.label :api_url, s_('ClusterIntegration|API URL')
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
.table-section.section-30{ role: "rowheader" } .table-section.section-30{ role: "rowheader" }
= s_("ClusterIntegration|Cluster") = s_("ClusterIntegration|Cluster")
.table-section.section-30{ role: "rowheader" } .table-section.section-30{ role: "rowheader" }
= s_("ClusterIntegration|Environment pattern") = s_("ClusterIntegration|Environment scope")
.table-section.section-30{ role: "rowheader" } .table-section.section-30{ role: "rowheader" }
= s_("ClusterIntegration|Project namespace") = s_("ClusterIntegration|Project namespace")
.table-section.section-10{ role: "rowheader" } .table-section.section-10{ role: "rowheader" }
......
...@@ -18,9 +18,9 @@ ...@@ -18,9 +18,9 @@
.js-cluster-application-notice .js-cluster-application-notice
.flash-container .flash-container
%section.settings.no-animate.expanded %section.settings.no-animate.expanded#cluster-integration
= render 'banner' = render 'banner'
= render 'enabled' = render 'integration_form'
.cluster-applications-table#js-cluster-applications .cluster-applications-table#js-cluster-applications
...@@ -41,6 +41,6 @@ ...@@ -41,6 +41,6 @@
%h4= _('Advanced settings') %h4= _('Advanced settings')
%button.btn.js-settings-toggle %button.btn.js-settings-toggle
= expanded ? 'Collapse' : 'Expand' = expanded ? 'Collapse' : 'Expand'
%p= s_('ClusterIntegration|Manage cluster integration on your GitLab project') %p= s_("ClusterIntegration|Advanced options on this cluster's integration")
.settings-content .settings-content
= render 'advanced_settings' = render 'advanced_settings'
...@@ -4,10 +4,6 @@ ...@@ -4,10 +4,6 @@
= field.label :name, s_('ClusterIntegration|Cluster name') = field.label :name, s_('ClusterIntegration|Cluster name')
= field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name') = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name')
.form-group
= field.label :environment_scope, s_('ClusterIntegration|Environment scope')
= field.text_field :environment_scope, class: 'form-control js-select-on-focus', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope')
= field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field| = field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field|
.form-group .form-group
= platform_kubernetes_field.label :api_url, s_('ClusterIntegration|API URL') = platform_kubernetes_field.label :api_url, s_('ClusterIntegration|API URL')
......
...@@ -89,6 +89,7 @@ ...@@ -89,6 +89,7 @@
- project_service - project_service
- propagate_service_template - propagate_service_template
- reactive_caching - reactive_caching
- rebase
- repository_fork - repository_fork
- repository_import - repository_import
- storage_migrator - storage_migrator
......
class RebaseWorker
include ApplicationWorker
def perform(merge_request_id, current_user_id)
current_user = User.find(current_user_id)
merge_request = MergeRequest.find(merge_request_id)
MergeRequests::RebaseService
.new(merge_request.source_project, current_user)
.execute(merge_request)
end
end
---
title: User#projects_limit remove DB default and added NOT NULL constraint
merge_request: 16165
author: Mario de la Ossa
type: fixed
---
title: Fix gitlab-rake gitlab:import:repos import schedule
merge_request: 15931
author:
type: fixed
---
title: Allow user to rebase merge requests.
merge_request:
author:
type: added
---
title: Update groups tree to use GitLab SVG icons, add last updated at information
for projects
merge_request: 15980
author:
type: changed
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment