Merge branch 'master' into reference-pipeline-and-caching
This commit is contained in:
		
						commit
						de0acf3cf7
					
				|  | @ -25,7 +25,6 @@ config/initializers/rack_attack.rb | |||
| config/initializers/smtp_settings.rb | ||||
| config/resque.yml | ||||
| config/unicorn.rb | ||||
| config/mail_room.yml | ||||
| config/secrets.yml | ||||
| coverage/* | ||||
| db/*.sqlite3 | ||||
|  |  | |||
							
								
								
									
										34
									
								
								CHANGELOG
								
								
								
								
							
							
						
						
									
										34
									
								
								CHANGELOG
								
								
								
								
							|  | @ -1,7 +1,28 @@ | |||
| Please view this file on the master branch, on stable branches it's out of date. | ||||
| 
 | ||||
| v 8.2.0 (unreleased) | ||||
|   - Ensure MySQL CI limits DB migrations occur after the fields have been created (Stan Hu) | ||||
|   - Improved performance of replacing references in comments | ||||
|   - Fix duplicate repositories in GitHub import page (Stan Hu) | ||||
|   - Redirect to a default path if HTTP_REFERER is not set (Stan Hu) | ||||
|   - Show last project commit to default branch on project home page | ||||
|   - Highlight comment based on anchor in URL | ||||
|   - Adds ability to remove the forked relationship from project settings screen. (Han Loong Liauw) | ||||
|   - Improved performance of sorting milestone issues | ||||
|   - Allow users to select the Files view as default project view (Cristian Bica) | ||||
| 
 | ||||
| v 8.1.0 (unreleased) | ||||
|   - Send an email to admin email when a user is reported for spam (Jonathan Rochkind) | ||||
|   - Show notifications button when user is member of group rather than project (Grzegorz Bizon) | ||||
|   - Fix bug preventing mentioned issued from being closed when MR is merged using fast-forward merge. | ||||
|   - Fix nonatomic database update potentially causing project star counts to go negative (Stan Hu) | ||||
|   - Fix error preventing displaying of commit data for a directory with a leading dot (Stan Hu) | ||||
|   - Speed up load times of issue detail pages by roughly 1.5x | ||||
|   - If a merge request is to close an issue, show this on the issue page (Zeger-Jan van de Weg) | ||||
|   - Add a system note and update relevant merge requests when a branch is deleted or re-added (Stan Hu) | ||||
|   - Make diff file view easier to use on mobile screens (Stan Hu) | ||||
|   - Improved performance of finding users by username or Email address | ||||
|   - Fix bug where merge request comments created by API would not trigger notifications (Stan Hu) | ||||
|   - Add support for creating directories from Files page (Stan Hu) | ||||
|   - Allow removing of project without confirmation when JavaScript is disabled (Stan Hu) | ||||
|   - Support filtering by "Any" milestone or issue and fix "No Milestone" and "No Label" filters (Stan Hu) | ||||
|  | @ -17,6 +38,8 @@ v 8.1.0 (unreleased) | |||
|   - Fix cases where Markdown did not render links in activity feed (Stan Hu) | ||||
|   - Add first and last to pagination (Zeger-Jan van de Weg) | ||||
|   - Added Commit Status API | ||||
|   - Added Builds View | ||||
|   - Added when to .gitlab-ci.yml | ||||
|   - Show CI status on commit page | ||||
|   - Added CI_BUILD_TAG, _STAGE, _NAME and _TRIGGERED to CI builds | ||||
|   - Show CI status on Your projects page and Starred projects page | ||||
|  | @ -40,7 +63,7 @@ v 8.1.0 (unreleased) | |||
|   - Move CI web hooks page to project settings area | ||||
|   - Fix User Identities API. It now allows you to properly create or update user's identities. | ||||
|   - Add user preference to change layout width (Peter Göbel) | ||||
|   - Use commit status in merge request widget as preffered source of CI status | ||||
|   - Use commit status in merge request widget as preferred source of CI status | ||||
|   - Integrate CI commit and build pages into project pages | ||||
|   - Move CI services page to project settings area | ||||
|   - Add "Quick Submit" behavior to input fields throughout the application. Use | ||||
|  | @ -48,6 +71,7 @@ v 8.1.0 (unreleased) | |||
|   - Fix position of hamburger in header for smaller screens (Han Loong Liauw) | ||||
|   - Fix bug where Emojis in Markdown would truncate remaining text (Sakata Sinji) | ||||
|   - Persist filters when sorting on admin user page (Jerry Lukins) | ||||
|   - Allow dashboard and group issues/MRs to be filtered by label | ||||
|   - Add spellcheck=false to certain input fields | ||||
|   - Invalidate stored service password if the endpoint URL is changed | ||||
|   - Project names are not fully shown if group name is too big, even on group page view | ||||
|  | @ -56,6 +80,14 @@ v 8.1.0 (unreleased) | |||
|   - Only render 404 page from /public | ||||
|   - Hide passwords from services API (Alex Lossent) | ||||
|   - Fix: Images cannot show when projects' path was changed | ||||
|   - Let gitlab-git-http-server generate and serve 'git archive' downloads | ||||
|   - Optimize query when filtering on issuables (Zeger-Jan van de Weg) | ||||
|   - Fix padding of outdated discussion item. | ||||
| 
 | ||||
| v 8.0.5 | ||||
|   - Correct lookup-by-email for LDAP logins | ||||
|   - Fix loading spinner sometimes not being hidden on Merge Request tab switches | ||||
|   - Animate the logo on hover | ||||
| 
 | ||||
| v 8.0.4 | ||||
|   - Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu) | ||||
|  |  | |||
|  | @ -0,0 +1 @@ | |||
| 0.3.0 | ||||
							
								
								
									
										27
									
								
								Gemfile
								
								
								
								
							
							
						
						
									
										27
									
								
								Gemfile
								
								
								
								
							|  | @ -1,13 +1,5 @@ | |||
| source "https://rubygems.org" | ||||
| 
 | ||||
| def darwin_only(require_as) | ||||
|   RUBY_PLATFORM.include?('darwin') && require_as | ||||
| end | ||||
| 
 | ||||
| def linux_only(require_as) | ||||
|   RUBY_PLATFORM.include?('linux') && require_as | ||||
| end | ||||
| 
 | ||||
| gem 'rails', '4.1.12' | ||||
| 
 | ||||
| # Specify a sprockets version due to security issue | ||||
|  | @ -47,7 +39,7 @@ gem "browser", '~> 1.0.0' | |||
| 
 | ||||
| # Extracting information from a git repository | ||||
| # Provide access to Gitlab::Git library | ||||
| gem "gitlab_git", '~> 7.2.18' | ||||
| gem "gitlab_git", '~> 7.2.19' | ||||
| 
 | ||||
| # LDAP Auth | ||||
| # GitLab fork with several improvements to original library. For full list of changes | ||||
|  | @ -102,7 +94,7 @@ gem "seed-fu", '~> 2.3.5' | |||
| gem 'html-pipeline', '~> 1.11.0' | ||||
| gem 'task_list',     '~> 1.0.2', require: 'task_list/railtie' | ||||
| gem 'github-markup', '~> 1.3.1' | ||||
| gem 'redcarpet',     '~> 3.3.2' | ||||
| gem 'redcarpet',     '~> 3.3.3' | ||||
| gem 'RedCloth',      '~> 4.2.9' | ||||
| gem 'rdoc',          '~>3.6' | ||||
| gem 'org-ruby',      '~> 0.9.12' | ||||
|  | @ -196,7 +188,7 @@ gem 'charlock_holmes', '~> 0.6.9.4' | |||
| 
 | ||||
| gem "sass-rails", '~> 4.0.5' | ||||
| gem "coffee-rails", '~> 4.1.0' | ||||
| gem "uglifier", '~> 2.3.2' | ||||
| gem "uglifier", '~> 2.7.2' | ||||
| gem 'turbolinks', '~> 2.5.0' | ||||
| gem 'jquery-turbolinks', '~> 2.0.1' | ||||
| 
 | ||||
|  | @ -224,6 +216,9 @@ group :development do | |||
|   gem 'quiet_assets', '~> 1.0.2' | ||||
|   gem 'rack-mini-profiler', '~> 0.9.0', require: false | ||||
|   gem 'rerun', '~> 0.10.0' | ||||
|   gem 'bullet', require: false | ||||
|   gem 'active_record_query_trace', require: false | ||||
|   gem 'rack-lineprof', platform: :mri | ||||
| 
 | ||||
|   # Better errors handler | ||||
|   gem 'better_errors', '~> 1.0.1' | ||||
|  | @ -290,7 +285,7 @@ gem 'newrelic-grape' | |||
| 
 | ||||
| gem 'octokit', '~> 3.7.0' | ||||
| 
 | ||||
| gem "mail_room", "~> 0.6.0" | ||||
| gem "mail_room", "~> 0.6.1" | ||||
| 
 | ||||
| gem 'email_reply_parser', '~> 0.5.8' | ||||
| 
 | ||||
|  | @ -304,11 +299,3 @@ gem 'oauth2', '~> 1.0.0' | |||
| 
 | ||||
| # Soft deletion | ||||
| gem "paranoia", "~> 2.0" | ||||
| 
 | ||||
| group :development, :test do | ||||
|   gem 'guard-rspec', '~> 4.2.0' | ||||
| 
 | ||||
|   gem 'rb-fsevent', require: darwin_only('rb-fsevent') | ||||
|   gem 'growl',      require: darwin_only('growl') | ||||
|   gem 'rb-inotify', require: linux_only('rb-inotify') | ||||
| end | ||||
|  |  | |||
							
								
								
									
										54
									
								
								Gemfile.lock
								
								
								
								
							
							
						
						
									
										54
									
								
								Gemfile.lock
								
								
								
								
							|  | @ -17,6 +17,7 @@ GEM | |||
|       activesupport (= 4.1.12) | ||||
|       builder (~> 3.1) | ||||
|       erubis (~> 2.7.0) | ||||
|     active_record_query_trace (1.5) | ||||
|     activemodel (4.1.12) | ||||
|       activesupport (= 4.1.12) | ||||
|       builder (~> 3.1) | ||||
|  | @ -87,6 +88,9 @@ GEM | |||
|       terminal-table (~> 1.4) | ||||
|     browser (1.0.0) | ||||
|     builder (3.2.2) | ||||
|     bullet (4.14.9) | ||||
|       activesupport (>= 3.0.0) | ||||
|       uniform_notifier (~> 1.9.0) | ||||
|     byebug (6.0.2) | ||||
|     cal-heatmap-rails (0.0.1) | ||||
|     capybara (2.4.4) | ||||
|  | @ -134,6 +138,7 @@ GEM | |||
|     daemons (1.2.3) | ||||
|     database_cleaner (1.4.1) | ||||
|     debug_inspector (0.0.2) | ||||
|     debugger-ruby_core_source (1.3.8) | ||||
|     default_value_for (3.0.1) | ||||
|       activerecord (>= 3.2.0, < 5.0) | ||||
|     descendants_tracker (0.0.4) | ||||
|  | @ -278,7 +283,7 @@ GEM | |||
|       mime-types (~> 1.19) | ||||
|     gitlab_emoji (0.1.1) | ||||
|       gemojione (~> 2.0) | ||||
|     gitlab_git (7.2.18) | ||||
|     gitlab_git (7.2.19) | ||||
|       activesupport (~> 4.0) | ||||
|       charlock_holmes (~> 0.6) | ||||
|       gitlab-linguist (~> 3.0) | ||||
|  | @ -314,19 +319,6 @@ GEM | |||
|     grape-entity (0.4.8) | ||||
|       activesupport | ||||
|       multi_json (>= 1.3.2) | ||||
|     growl (1.0.3) | ||||
|     guard (2.13.0) | ||||
|       formatador (>= 0.2.4) | ||||
|       listen (>= 2.7, <= 4.0) | ||||
|       lumberjack (~> 1.0) | ||||
|       nenv (~> 0.1) | ||||
|       notiffany (~> 0.0) | ||||
|       pry (>= 0.9.12) | ||||
|       shellany (~> 0.0) | ||||
|       thor (>= 0.18.1) | ||||
|     guard-rspec (4.2.10) | ||||
|       guard (~> 2.1) | ||||
|       rspec (>= 2.14, < 4.0) | ||||
|     haml (4.0.7) | ||||
|       tilt | ||||
|     haml-rails (0.9.0) | ||||
|  | @ -387,12 +379,11 @@ GEM | |||
|       celluloid (~> 0.16.0) | ||||
|       rb-fsevent (>= 0.9.3) | ||||
|       rb-inotify (>= 0.9) | ||||
|     lumberjack (1.0.9) | ||||
|     macaddr (1.7.1) | ||||
|       systemu (~> 2.6.2) | ||||
|     mail (2.6.3) | ||||
|       mime-types (>= 1.16, < 3) | ||||
|     mail_room (0.6.0) | ||||
|     mail_room (0.6.1) | ||||
|     method_source (0.8.2) | ||||
|     mime-types (1.25.1) | ||||
|     mimemagic (0.3.0) | ||||
|  | @ -403,7 +394,6 @@ GEM | |||
|     multi_xml (0.5.5) | ||||
|     multipart-post (2.0.0) | ||||
|     mysql2 (0.3.20) | ||||
|     nenv (0.2.0) | ||||
|     nested_form (0.3.2) | ||||
|     net-ldap (0.11) | ||||
|     net-scp (1.2.1) | ||||
|  | @ -416,9 +406,6 @@ GEM | |||
|     newrelic_rpm (3.9.4.245) | ||||
|     nokogiri (1.6.6.2) | ||||
|       mini_portile (~> 0.6.0) | ||||
|     notiffany (0.0.7) | ||||
|       nenv (~> 0.1) | ||||
|       shellany (~> 0.0) | ||||
|     nprogress-rails (0.1.2.3) | ||||
|     oauth (0.4.7) | ||||
|     oauth2 (1.0.0) | ||||
|  | @ -502,6 +489,10 @@ GEM | |||
|     rack-attack (4.3.0) | ||||
|       rack | ||||
|     rack-cors (0.4.0) | ||||
|     rack-lineprof (0.0.3) | ||||
|       rack (~> 1.5) | ||||
|       rblineprof (~> 0.3.6) | ||||
|       term-ansicolor (~> 1.3) | ||||
|     rack-mini-profiler (0.9.7) | ||||
|       rack (>= 1.1.3) | ||||
|     rack-mount (0.8.3) | ||||
|  | @ -540,13 +531,15 @@ GEM | |||
|     rb-fsevent (0.9.5) | ||||
|     rb-inotify (0.9.5) | ||||
|       ffi (>= 0.5.0) | ||||
|     rblineprof (0.3.6) | ||||
|       debugger-ruby_core_source (~> 1.3) | ||||
|     rbvmomi (1.8.2) | ||||
|       builder | ||||
|       nokogiri (>= 1.4.1) | ||||
|       trollop | ||||
|     rdoc (3.12.2) | ||||
|       json (~> 1.4) | ||||
|     redcarpet (3.3.2) | ||||
|     redcarpet (3.3.3) | ||||
|     redis (3.2.1) | ||||
|     redis-actionpack (4.0.0) | ||||
|       actionpack (~> 4) | ||||
|  | @ -647,7 +640,6 @@ GEM | |||
|     sexp_processor (4.6.0) | ||||
|     sham_rack (1.3.6) | ||||
|       rack | ||||
|     shellany (0.0.1) | ||||
|     shoulda-matchers (2.8.0) | ||||
|       activesupport (>= 3.0.0) | ||||
|     sidekiq (3.3.0) | ||||
|  | @ -741,7 +733,7 @@ GEM | |||
|       simple_oauth (~> 0.1.4) | ||||
|     tzinfo (1.2.2) | ||||
|       thread_safe (~> 0.1) | ||||
|     uglifier (2.3.3) | ||||
|     uglifier (2.7.2) | ||||
|       execjs (>= 0.3.0) | ||||
|       json (>= 1.8.0) | ||||
|     underscore-rails (1.4.4) | ||||
|  | @ -755,6 +747,7 @@ GEM | |||
|     unicorn-worker-killer (0.4.3) | ||||
|       get_process_mem (~> 0) | ||||
|       unicorn (~> 4) | ||||
|     uniform_notifier (1.9.0) | ||||
|     uuid (2.3.8) | ||||
|       macaddr (~> 1.0) | ||||
|     version_sorter (2.0.0) | ||||
|  | @ -784,6 +777,7 @@ PLATFORMS | |||
| DEPENDENCIES | ||||
|   RedCloth (~> 4.2.9) | ||||
|   ace-rails-ap (~> 2.0.1) | ||||
|   active_record_query_trace | ||||
|   activerecord-deprecated_finders (~> 1.0.3) | ||||
|   activerecord-session_store (~> 0.1.0) | ||||
|   acts-as-taggable-on (~> 3.4) | ||||
|  | @ -800,6 +794,7 @@ DEPENDENCIES | |||
|   bootstrap-sass (~> 3.0) | ||||
|   brakeman (= 3.0.1) | ||||
|   browser (~> 1.0.0) | ||||
|   bullet | ||||
|   byebug | ||||
|   cal-heatmap-rails (~> 0.0.1) | ||||
|   capybara (~> 2.4.0) | ||||
|  | @ -834,15 +829,13 @@ DEPENDENCIES | |||
|   gitlab-flowdock-git-hook (~> 1.0.1) | ||||
|   gitlab-linguist (~> 3.0.1) | ||||
|   gitlab_emoji (~> 0.1) | ||||
|   gitlab_git (~> 7.2.18) | ||||
|   gitlab_git (~> 7.2.19) | ||||
|   gitlab_meta (= 7.0) | ||||
|   gitlab_omniauth-ldap (~> 1.2.1) | ||||
|   gollum-lib (~> 4.0.2) | ||||
|   gon (~> 5.0.0) | ||||
|   grape (~> 0.6.1) | ||||
|   grape-entity (~> 0.4.2) | ||||
|   growl | ||||
|   guard-rspec (~> 4.2.0) | ||||
|   haml-rails (~> 0.9.0) | ||||
|   hipchat (~> 1.5.0) | ||||
|   html-pipeline (~> 1.11.0) | ||||
|  | @ -854,7 +847,7 @@ DEPENDENCIES | |||
|   jquery-ui-rails (~> 4.2.1) | ||||
|   kaminari (~> 0.16.3) | ||||
|   letter_opener (~> 1.1.2) | ||||
|   mail_room (~> 0.6.0) | ||||
|   mail_room (~> 0.6.1) | ||||
|   minitest (~> 5.7.0) | ||||
|   mousetrap-rails (~> 1.4.6) | ||||
|   mysql2 (~> 0.3.16) | ||||
|  | @ -882,14 +875,13 @@ DEPENDENCIES | |||
|   quiet_assets (~> 1.0.2) | ||||
|   rack-attack (~> 4.3.0) | ||||
|   rack-cors (~> 0.4.0) | ||||
|   rack-lineprof | ||||
|   rack-mini-profiler (~> 0.9.0) | ||||
|   rack-oauth2 (~> 1.0.5) | ||||
|   rails (= 4.1.12) | ||||
|   raphael-rails (~> 2.1.2) | ||||
|   rb-fsevent | ||||
|   rb-inotify | ||||
|   rdoc (~> 3.6) | ||||
|   redcarpet (~> 3.3.2) | ||||
|   redcarpet (~> 3.3.3) | ||||
|   redis-rails (~> 4.0.0) | ||||
|   request_store (~> 1.2.0) | ||||
|   rerun (~> 0.10.0) | ||||
|  | @ -926,7 +918,7 @@ DEPENDENCIES | |||
|   thin (~> 1.6.1) | ||||
|   tinder (~> 1.10.0) | ||||
|   turbolinks (~> 2.5.0) | ||||
|   uglifier (~> 2.3.2) | ||||
|   uglifier (~> 2.7.2) | ||||
|   underscore-rails (~> 1.4.4) | ||||
|   unf (~> 0.1.4) | ||||
|   unicorn (~> 4.8.2) | ||||
|  |  | |||
|  | @ -10,17 +10,17 @@ | |||
|                 <g id="Fill-1-+-Group-24"> | ||||
|                     <g id="Group-24"> | ||||
|                         <g id="Group"> | ||||
|                             <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329"></path> | ||||
|                             <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26"></path> | ||||
|                             <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326"></path> | ||||
|                             <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329"></path> | ||||
|                             <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26"></path> | ||||
|                             <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326"></path> | ||||
|                             <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329"></path> | ||||
|                             <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329" class="tanuki-shape"></path> | ||||
|                             <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26" class="tanuki-shape"></path> | ||||
|                             <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326" class="tanuki-shape"></path> | ||||
|                             <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329" class="tanuki-shape"></path> | ||||
|                             <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26" class="tanuki-shape"></path> | ||||
|                             <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326" class="tanuki-shape"></path> | ||||
|                             <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329" class="tanuki-shape"></path> | ||||
|                         </g> | ||||
|                     </g> | ||||
|                 </g> | ||||
|             </g> | ||||
|         </g> | ||||
|     </g> | ||||
| </svg> | ||||
| </svg> | ||||
|  |  | |||
| Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.4 KiB | 
|  | @ -68,8 +68,8 @@ class @MergeRequestTabs | |||
| 
 | ||||
|   scrollToElement: (container) -> | ||||
|     if window.location.hash | ||||
|       top = $(container + " " + window.location.hash).offset().top | ||||
|       $('body').scrollTo(top) | ||||
|       $el = $("#{container} #{window.location.hash}") | ||||
|       $('body').scrollTo($el.offset().top) if $el.length | ||||
| 
 | ||||
|   # Activate a tab based on the current action | ||||
|   activateTab: (action) -> | ||||
|  | @ -127,7 +127,7 @@ class @MergeRequestTabs | |||
|         document.getElementById('commits').innerHTML = data.html | ||||
|         $('.js-timeago').timeago() | ||||
|         @commitsLoaded = true | ||||
|         @scrollToElement(".commits") | ||||
|         @scrollToElement("#commits") | ||||
| 
 | ||||
|   loadDiff: (source) -> | ||||
|     return if @diffsLoaded | ||||
|  | @ -137,7 +137,7 @@ class @MergeRequestTabs | |||
|       success: (data) => | ||||
|         document.getElementById('diffs').innerHTML = data.html | ||||
|         @diffsLoaded = true | ||||
|         @scrollToElement(".diffs") | ||||
|         @scrollToElement("#diffs") | ||||
| 
 | ||||
|   # Show or hide the loading spinner | ||||
|   # | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ class @ShortcutsNavigation extends Shortcuts | |||
|     Mousetrap.bind('g e', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity')) | ||||
|     Mousetrap.bind('g f', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-tree')) | ||||
|     Mousetrap.bind('g c', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-commits')) | ||||
|     Mousetrap.bind('g b', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-builds')) | ||||
|     Mousetrap.bind('g n', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-network')) | ||||
|     Mousetrap.bind('g g', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-graphs')) | ||||
|     Mousetrap.bind('g i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-issues')) | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ | |||
|   line-height: 36px; | ||||
| } | ||||
| 
 | ||||
| .content-block, | ||||
| .gray-content-block { | ||||
|   margin: -$gl-padding; | ||||
|   background-color: $background-color; | ||||
|  | @ -27,6 +28,10 @@ | |||
|   border-bottom: 1px solid $border-color; | ||||
|   color: $gl-gray; | ||||
| 
 | ||||
|   &.white { | ||||
|     background-color: white; | ||||
|   } | ||||
| 
 | ||||
|   &.top-block { | ||||
|     border-top: none; | ||||
|   } | ||||
|  | @ -60,3 +65,48 @@ | |||
|     line-height: 42px; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .cover-block { | ||||
|   text-align: center; | ||||
|   background: #f7f8fa; | ||||
|   margin: -$gl-padding; | ||||
|   margin-bottom: 0; | ||||
|   padding: 44px $gl-padding; | ||||
|   border-bottom: 1px solid $border-color; | ||||
|   position: relative; | ||||
| 
 | ||||
|   .avatar-holder { | ||||
|     margin-bottom: 16px; | ||||
| 
 | ||||
|     .avatar, .identicon { | ||||
|       margin: 0 auto; | ||||
|       float: none; | ||||
|     } | ||||
| 
 | ||||
|     .identicon { | ||||
|       @include border-radius(50%); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .cover-title { | ||||
|     color: $gl-header-color; | ||||
|     margin: 0; | ||||
|     font-size: 23px; | ||||
|     font-weight: normal; | ||||
|     margin: 16px 0 5px 0; | ||||
|     color: #4c4e54; | ||||
|     font-size: 23px; | ||||
|     line-height: 1.1; | ||||
|   } | ||||
| 
 | ||||
|   .cover-desc { | ||||
|     padding: 0 $gl-padding; | ||||
|     color: $gl-text-color; | ||||
|   } | ||||
| 
 | ||||
|   .cover-controls { | ||||
|     position: absolute; | ||||
|     top: 10px; | ||||
|     right: 10px; | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -10,6 +10,10 @@ | |||
|   border-bottom: 1px solid #E7E9EE; | ||||
|   margin-bottom: 1em; | ||||
| 
 | ||||
|   &.readme-holder { | ||||
|     border-bottom: 0; | ||||
|   } | ||||
| 
 | ||||
|   table { | ||||
|     @extend .table; | ||||
|   } | ||||
|  | @ -94,7 +98,6 @@ | |||
|           border-right: none; | ||||
|         } | ||||
|         background: #fff; | ||||
|         padding: 10px $gl-padding; | ||||
|       } | ||||
|       .lines { | ||||
|         pre { | ||||
|  |  | |||
|  | @ -107,7 +107,7 @@ ul.content-list { | |||
| 
 | ||||
|   > li { | ||||
|     padding: $gl-padding; | ||||
|     border-color: #f1f2f4; | ||||
|     border-color: $table-border-color; | ||||
|     margin-left: -$gl-padding; | ||||
|     margin-right: -$gl-padding; | ||||
|     color: $gl-gray; | ||||
|  |  | |||
|  | @ -32,7 +32,7 @@ | |||
| } | ||||
| 
 | ||||
| .select2-results .select2-result-label { | ||||
|   padding: 16px; | ||||
|   padding: 9px; | ||||
| } | ||||
| 
 | ||||
| .select2-drop{ | ||||
|  | @ -143,4 +143,4 @@ | |||
| 
 | ||||
| .ajax-users-dropdown { | ||||
|   min-width: 250px !important; | ||||
| } | ||||
| } | ||||
|  | @ -242,6 +242,9 @@ | |||
|       img { | ||||
|         width: 36px; | ||||
|         height: 36px; | ||||
|       } | ||||
| 
 | ||||
|       #tanuki-logo, img { | ||||
|         float: left; | ||||
|       } | ||||
| 
 | ||||
|  | @ -265,3 +268,13 @@ | |||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| .tanuki-shape { | ||||
|   transition: all 0.8s; | ||||
| 
 | ||||
|   &:hover { | ||||
|     fill: rgb(255, 255, 255); | ||||
|     transition: all 0.1s; | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,3 +1,9 @@ | |||
| .table-holder { | ||||
|   margin: -$gl-padding; | ||||
|   margin-top: 0; | ||||
|   margin-bottom: 0; | ||||
| } | ||||
| 
 | ||||
| table { | ||||
|   &.table { | ||||
|     .dropdown-menu a { | ||||
|  | @ -18,15 +24,17 @@ table { | |||
| 
 | ||||
|     tr { | ||||
|       td, th { | ||||
|         padding: 8px 10px; | ||||
|         padding: 10px $gl-padding; | ||||
|         line-height: 20px; | ||||
|         vertical-align: middle; | ||||
|       } | ||||
| 
 | ||||
|       th { | ||||
|         font-weight: normal; | ||||
|         font-size: 15px; | ||||
|         border-bottom: 1px solid $border-color !important; | ||||
|       } | ||||
| 
 | ||||
|       td { | ||||
|         border-color: $table-border-color !important; | ||||
|         border-bottom: 1px solid; | ||||
|  |  | |||
|  | @ -6,13 +6,17 @@ | |||
| 
 | ||||
|   .timeline-entry { | ||||
|     padding: $gl-padding; | ||||
|     border-color: #f1f2f4; | ||||
|     border-color: $table-border-color; | ||||
|     margin-left: -$gl-padding; | ||||
|     margin-right: -$gl-padding; | ||||
|     color: $gl-gray; | ||||
|     border-bottom: 1px solid #ECEEF1; | ||||
|     border-right: 1px solid #ECEEF1; | ||||
| 
 | ||||
|     &:target { | ||||
|       background: $hover; | ||||
|     } | ||||
| 
 | ||||
|     &:last-child { | ||||
|       border-bottom: none; | ||||
|     } | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| @mixin md-typography { | ||||
|   color: $md-text-color; | ||||
|   word-wrap: break-word; | ||||
| 
 | ||||
|   a { | ||||
|     color: $md-link-color; | ||||
|  | @ -17,7 +18,6 @@ | |||
|     font-family: $monospace_font; | ||||
|     white-space: pre; | ||||
|     word-wrap: normal; | ||||
|     padding: 1px 2px; | ||||
|   } | ||||
| 
 | ||||
|   kbd { | ||||
|  | @ -73,6 +73,8 @@ | |||
|   } | ||||
| 
 | ||||
|   blockquote { | ||||
|     color: #7f8fa4; | ||||
|     font-size: inherit; | ||||
|     padding: 8px 21px; | ||||
|     margin: 12px 0 12px; | ||||
|     border-left: 3px solid #e7e9ed; | ||||
|  | @ -80,7 +82,7 @@ | |||
| 
 | ||||
|   blockquote p { | ||||
|     color: #7f8fa4 !important; | ||||
|     font-size: 15px; | ||||
|     font-size: inherit; | ||||
|     line-height: 1.5; | ||||
|   } | ||||
| 
 | ||||
|  | @ -101,9 +103,9 @@ | |||
| 
 | ||||
|   pre { | ||||
|     margin: 12px 0 12px 0 !important; | ||||
|     background-color: #f8fafc !important; | ||||
|     background-color: #f8fafc; | ||||
|     font-size: 13px !important; | ||||
|     color: #5b6169 !important; | ||||
|     color: #5b6169; | ||||
|     line-height: 1.6em !important; | ||||
|     @include border-radius(2px); | ||||
|   } | ||||
|  | @ -112,9 +114,9 @@ | |||
|     font-weight: inherit; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   ul { | ||||
|     color: #5c5d5e; | ||||
|   ul, ol { | ||||
|     padding: 0; | ||||
|     margin: 6px 0 6px 18px !important; | ||||
|   } | ||||
| 
 | ||||
|   li { | ||||
|  | @ -136,6 +138,33 @@ | |||
|       text-decoration: none; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /* Link to current header. */ | ||||
|   h1, h2, h3, h4, h5, h6 { | ||||
|     position: relative; | ||||
| 
 | ||||
|     a.anchor { | ||||
|       // Setting `display: none` would prevent the anchor being scrolled to, so | ||||
|       // instead we set the height to 0 and it gets updated on hover. | ||||
|       height: 0; | ||||
|     } | ||||
| 
 | ||||
|     &:hover > a.anchor { | ||||
|       $size: 16px; | ||||
|       position: absolute; | ||||
|       right: 100%; | ||||
|       top: 50%; | ||||
|       margin-top: -$size/2; | ||||
|       margin-right: 0px; | ||||
|       padding-right: 20px; | ||||
|       display: inline-block; | ||||
|       width: $size; | ||||
|       height: $size; | ||||
|       background-image: image-url("icon-link.png"); | ||||
|       background-size: contain; | ||||
|       background-repeat: no-repeat; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -202,53 +231,11 @@ a > code { | |||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Wiki typography | ||||
|  * Apply Markdown typography | ||||
|  * | ||||
|  */ | ||||
| .wiki { | ||||
|   @include md-typography; | ||||
| 
 | ||||
|   word-wrap: break-word; | ||||
|   padding: 7px; | ||||
| 
 | ||||
|   /* Link to current header. */ | ||||
|   h1, h2, h3, h4, h5, h6 { | ||||
|     position: relative; | ||||
| 
 | ||||
|     a.anchor { | ||||
|       // Setting `display: none` would prevent the anchor being scrolled to, so | ||||
|       // instead we set the height to 0 and it gets updated on hover. | ||||
|       height: 0; | ||||
|     } | ||||
| 
 | ||||
|     &:hover > a.anchor { | ||||
|       $size: 16px; | ||||
|       position: absolute; | ||||
|       right: 100%; | ||||
|       top: 50%; | ||||
|       margin-top: -$size/2; | ||||
|       margin-right: 0px; | ||||
|       padding-right: 20px; | ||||
|       display: inline-block; | ||||
|       width: $size; | ||||
|       height: $size; | ||||
|       background-image: image-url("icon-link.png"); | ||||
|       background-size: contain; | ||||
|       background-repeat: no-repeat; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   ul,ol { | ||||
|     padding: 0; | ||||
|     margin: 6px 0 6px 18px !important; | ||||
|   } | ||||
|   ol { | ||||
|     color: #5c5d5e; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .md-area { | ||||
|   @include md-typography; | ||||
| } | ||||
| 
 | ||||
| .md { | ||||
|  | @ -261,6 +248,7 @@ a > code { | |||
|  */ | ||||
| textarea.js-gfm-input { | ||||
|   font-family: $monospace_font; | ||||
|   color: $gl-text-color; | ||||
| } | ||||
| 
 | ||||
| .md-preview { | ||||
|  |  | |||
|  | @ -1,11 +1,10 @@ | |||
| /* https://github.com/MozMorris/tomorrow-pygments */ | ||||
| pre.code.highlight.dark, | ||||
| .code.dark { | ||||
| 
 | ||||
|   background-color: #1d1f21; | ||||
|   color: #c5c8c6; | ||||
|   background-color: #1d1f21 !important; | ||||
|   color: #c5c8c6 !important; | ||||
| 
 | ||||
|   pre.code, | ||||
|   pre.highlight, | ||||
|   .line-numbers, | ||||
|   .line-numbers a { | ||||
|     background-color: #1d1f21 !important; | ||||
|  | @ -23,8 +22,8 @@ pre.code.highlight.dark, | |||
| 
 | ||||
|   // Search result highlight | ||||
|   span.highlight_word { | ||||
|     background: #ffe792; | ||||
|     color: #000000; | ||||
|     background-color: #ffe792 !important; | ||||
|     color: #000000 !important; | ||||
|   } | ||||
| 
 | ||||
|   .hll { background-color: #373b41 } | ||||
|  |  | |||
|  | @ -1,15 +1,14 @@ | |||
| /* https://github.com/richleland/pygments-css/blob/master/monokai.css */ | ||||
| pre.code.monokai, | ||||
| .code.monokai { | ||||
| 
 | ||||
|   background: #272822; | ||||
|   color: #f8f8f2; | ||||
|   background-color: #272822 !important; | ||||
|   color: #f8f8f2 !important; | ||||
| 
 | ||||
|   pre.highlight, | ||||
|   .line-numbers, | ||||
|   .line-numbers a { | ||||
|     background:#272822 !important; | ||||
|     color:#f8f8f2 !important; | ||||
|     background-color :#272822 !important; | ||||
|     color: #f8f8f2 !important; | ||||
|   } | ||||
| 
 | ||||
|   pre.code { | ||||
|  | @ -23,8 +22,8 @@ pre.code.monokai, | |||
| 
 | ||||
|   // Search result highlight | ||||
|   span.highlight_word { | ||||
|     background: #ffe792; | ||||
|     color: #000000; | ||||
|     background-color: #ffe792 !important; | ||||
|     color: #000000 !important; | ||||
|   } | ||||
| 
 | ||||
|   .hll { background-color: #49483e } | ||||
|  |  | |||
|  | @ -1,11 +1,10 @@ | |||
| /* https://gist.github.com/qguv/7936275 */ | ||||
| pre.code.highlight.solarized-dark, | ||||
| .code.solarized-dark { | ||||
| 
 | ||||
|   background-color: #002b36; | ||||
|   color: #93a1a1; | ||||
|   background-color: #002b36 !important; | ||||
|   color: #93a1a1 !important; | ||||
| 
 | ||||
|   pre.code, | ||||
|   pre.highlight, | ||||
|   .line-numbers, | ||||
|   .line-numbers a { | ||||
|     background-color: #002b36 !important; | ||||
|  | @ -23,7 +22,7 @@ pre.code.highlight.solarized-dark, | |||
| 
 | ||||
|   // Search result highlight | ||||
|   span.highlight_word { | ||||
|     background: #094554; | ||||
|     background-color: #094554 !important; | ||||
|   } | ||||
| 
 | ||||
|   /* Solarized Dark | ||||
|  |  | |||
|  | @ -1,11 +1,10 @@ | |||
| /* https://gist.github.com/qguv/7936275 */ | ||||
| pre.code.highlight.solarized-light, | ||||
| .code.solarized-light { | ||||
| 
 | ||||
|   background-color: #fdf6e3; | ||||
|   color: #586e75; | ||||
|   background-color: #fdf6e3 !important; | ||||
|   color: #586e75 !important; | ||||
| 
 | ||||
|   pre.code, | ||||
|   pre.highlight, | ||||
|   .line-numbers, | ||||
|   .line-numbers a { | ||||
|     background-color: #fdf6e3 !important; | ||||
|  | @ -23,7 +22,7 @@ pre.code.highlight.solarized-light, | |||
| 
 | ||||
|   // Search result highlight | ||||
|   span.highlight_word { | ||||
|     background: #eee8d5; | ||||
|     background-color: #eee8d5 !important; | ||||
|   } | ||||
| 
 | ||||
|   /* Solarized Light | ||||
|  |  | |||
|  | @ -1,24 +1,20 @@ | |||
| /* https://github.com/aahan/pygments-github-style */ | ||||
| pre.code.highlight.white, | ||||
| .code.white { | ||||
|   background-color: #f8fafc; | ||||
|   font-size: 13px; | ||||
|   color: #5b6169; | ||||
|   line-height: 1.6em; | ||||
| 
 | ||||
|   background-color: #f8fafc !important; | ||||
|   color: #5b6169 !important; | ||||
| 
 | ||||
|   pre.highlight, | ||||
|   .line-numbers, | ||||
|   .line-numbers a { | ||||
|     background-color: $background-color !important; | ||||
|     color: $gl-gray !important; | ||||
|   } | ||||
| 
 | ||||
|   pre.highlight { | ||||
|     background-color: #fff !important; | ||||
|     color: #333 !important; | ||||
|   } | ||||
| 
 | ||||
|   pre.code { | ||||
|     border-left: 1px solid $border-color; | ||||
|     background-color: #fff !important; | ||||
|     color: #333 !important; | ||||
|   } | ||||
| 
 | ||||
|   // highlight line via anchor | ||||
|  | @ -28,7 +24,7 @@ pre.code.highlight.white, | |||
| 
 | ||||
|   // Search result highlight | ||||
|   span.highlight_word { | ||||
|     background: #fafe3d; | ||||
|     background-color: #fafe3d !important; | ||||
|   } | ||||
| 
 | ||||
|   .hll { background-color: #f8f8f8 } | ||||
|  |  | |||
|  | @ -6,11 +6,6 @@ | |||
|     line-height: 1.5; | ||||
|   } | ||||
| 
 | ||||
|   .wide-table-holder { | ||||
|     margin-left: -$gl-padding; | ||||
|     margin-right: -$gl-padding; | ||||
|   } | ||||
| 
 | ||||
|   .builds, | ||||
|   .projects-table { | ||||
|     .light { | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ | |||
|   padding: $gl-padding; | ||||
|   margin-left: -$gl-padding; | ||||
|   margin-right: -$gl-padding; | ||||
|   border-bottom: 1px solid #f1f2f4; | ||||
|   border-bottom: 1px solid $table-border-color; | ||||
|   color: #7f8fa4; | ||||
| 
 | ||||
|   &.event-inline { | ||||
|  |  | |||
|  | @ -68,3 +68,7 @@ body.modal-open { | |||
| .modal .modal-dialog { | ||||
|   width: 860px; | ||||
| } | ||||
| 
 | ||||
| .documentation { | ||||
|   padding: 7px; | ||||
| } | ||||
|  |  | |||
|  | @ -132,6 +132,11 @@ form.edit-issue { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| .issue-closed-by-widget { | ||||
|   padding: 16px 0; | ||||
|   margin: 0px; | ||||
| } | ||||
| 
 | ||||
| .issue-form .select2-container { | ||||
|   width: 250px !important; | ||||
| } | ||||
|  |  | |||
|  | @ -30,7 +30,6 @@ ul.notes { | |||
|   .discussion-header, | ||||
|   .note-header { | ||||
|     @extend .cgray; | ||||
|     padding-bottom: 15px; | ||||
| 
 | ||||
|     a:hover { | ||||
|       text-decoration: none; | ||||
|  | @ -75,6 +74,10 @@ ul.notes { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .discussion-body { | ||||
|     padding-top: 15px; | ||||
|   } | ||||
| 
 | ||||
|   .discussion { | ||||
|     overflow: hidden; | ||||
|     display: block; | ||||
|  |  | |||
|  | @ -47,3 +47,9 @@ | |||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .calendar-hint { | ||||
|   margin-top: -12px; | ||||
|   float: right; | ||||
|   font-size: 12px; | ||||
| } | ||||
|  |  | |||
|  | @ -457,7 +457,7 @@ pre.light-well { | |||
| 
 | ||||
|   .project-row { | ||||
|     padding: $gl-padding; | ||||
|     border-color: #f1f2f4; | ||||
|     border-color: $table-border-color; | ||||
|     margin-left: -$gl-padding; | ||||
|     margin-right: -$gl-padding; | ||||
| 
 | ||||
|  | @ -511,3 +511,38 @@ pre.light-well { | |||
|     margin-top: -1px; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .project-last-commit { | ||||
|   margin: 0 7px; | ||||
| 
 | ||||
|   .ci-status { | ||||
|     margin-right: 16px; | ||||
|   } | ||||
| 
 | ||||
|   .commit-row-message { | ||||
|     color: $gl-gray; | ||||
|   } | ||||
| 
 | ||||
|   .commit_short_id { | ||||
|     margin-right: 5px; | ||||
|     color: $gl-link-color; | ||||
|     font-weight: 600; | ||||
|   } | ||||
| 
 | ||||
|   .commit-author-link { | ||||
|     margin-left: 7px; | ||||
|     text-decoration: none; | ||||
|     .avatar { | ||||
|       float: none; | ||||
|       margin-right: 4px; | ||||
|     } | ||||
| 
 | ||||
|     .commit-author-name { | ||||
|       font-weight: 600; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .project-show-readme .readme-holder { | ||||
|   border-top: 0; | ||||
| } | ||||
|  |  | |||
|  | @ -1,25 +1,11 @@ | |||
| .tree-holder { | ||||
|   .tree-table-holder { | ||||
|     margin-left: -$gl-padding; | ||||
|     margin-right: -$gl-padding; | ||||
|   } | ||||
| 
 | ||||
|   .tree_progress { | ||||
|     display: none; | ||||
|     margin: 20px; | ||||
|     &.loading { | ||||
|       display: block; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .tree-table { | ||||
|     margin-bottom: 0; | ||||
| 
 | ||||
|     tr { | ||||
|       > td, > th { | ||||
|         padding: 10px $gl-padding; | ||||
|         line-height: 32px; | ||||
|         border-color: $table-border-color !important; | ||||
|       } | ||||
| 
 | ||||
|       &:hover { | ||||
|  |  | |||
|  | @ -9,6 +9,10 @@ class AbuseReportsController < ApplicationController | |||
|     @abuse_report.reporter = current_user | ||||
| 
 | ||||
|     if @abuse_report.save | ||||
|       if current_application_settings.admin_notification_email.present? | ||||
|         AbuseReportMailer.delay.notify(@abuse_report.id) | ||||
|       end | ||||
| 
 | ||||
|       message = "Thank you for your report. A GitLab administrator will look into it shortly." | ||||
|       redirect_to root_path, notice: message | ||||
|     else | ||||
|  |  | |||
|  | @ -55,6 +55,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController | |||
|       :default_snippet_visibility, | ||||
|       :restricted_signup_domains_raw, | ||||
|       :version_check_enabled, | ||||
|       :admin_notification_email, | ||||
|       :user_oauth_applications, | ||||
|       restricted_visibility_levels: [], | ||||
|       import_sources: [] | ||||
|  |  | |||
|  | @ -19,7 +19,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController | |||
|     BroadcastMessage.find(params[:id]).destroy | ||||
| 
 | ||||
|     respond_to do |format| | ||||
|       format.html { redirect_to :back } | ||||
|       format.html { redirect_back_or_default(default: { action: 'index' }) } | ||||
|       format.js { render nothing: true } | ||||
|     end | ||||
|   end | ||||
|  |  | |||
|  | @ -35,7 +35,7 @@ class Admin::HooksController < Admin::ApplicationController | |||
|     } | ||||
|     @hook.execute(data, 'system_hooks') | ||||
| 
 | ||||
|     redirect_to :back | ||||
|     redirect_back_or_default | ||||
|   end | ||||
| 
 | ||||
|   def hook_params | ||||
|  |  | |||
|  | @ -39,7 +39,13 @@ class Admin::ServicesController < Admin::ApplicationController | |||
|   end | ||||
| 
 | ||||
|   def application_services_params | ||||
|     params.permit(:id, | ||||
|     application_services_params = params.permit(:id, | ||||
|       service: Projects::ServicesController::ALLOWED_PARAMS) | ||||
|     if application_services_params[:service].is_a?(Hash) | ||||
|       Projects::ServicesController::FILTER_BLANK_PARAMS.each do |param| | ||||
|         application_services_params[:service].delete(param) if application_services_params[:service][param].blank?  | ||||
|       end | ||||
|     end | ||||
|     application_services_params | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -33,33 +33,33 @@ class Admin::UsersController < Admin::ApplicationController | |||
| 
 | ||||
|   def block | ||||
|     if user.block | ||||
|       redirect_to :back, notice: "Successfully blocked" | ||||
|       redirect_back_or_admin_user(notice: "Successfully blocked") | ||||
|     else | ||||
|       redirect_to :back, alert: "Error occurred. User was not blocked" | ||||
|       redirect_back_or_admin_user(alert: "Error occurred. User was not blocked") | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def unblock | ||||
|     if user.activate | ||||
|       redirect_to :back, notice: "Successfully unblocked" | ||||
|       redirect_back_or_admin_user(notice: "Successfully unblocked") | ||||
|     else | ||||
|       redirect_to :back, alert: "Error occurred. User was not unblocked" | ||||
|       redirect_back_or_admin_user(alert: "Error occurred. User was not unblocked") | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def unlock | ||||
|     if user.unlock_access! | ||||
|       redirect_to :back, alert: "Successfully unlocked" | ||||
|       redirect_back_or_admin_user(alert: "Successfully unlocked") | ||||
|     else | ||||
|       redirect_to :back, alert: "Error occurred. User was not unlocked" | ||||
|       redirect_back_or_admin_user(alert: "Error occurred. User was not unlocked") | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def confirm | ||||
|     if user.confirm | ||||
|       redirect_to :back, notice: "Successfully confirmed" | ||||
|       redirect_back_or_admin_user(notice: "Successfully confirmed") | ||||
|     else | ||||
|       redirect_to :back, alert: "Error occurred. User was not confirmed" | ||||
|       redirect_back_or_admin_user(alert: "Error occurred. User was not confirmed") | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|  | @ -138,7 +138,7 @@ class Admin::UsersController < Admin::ApplicationController | |||
|     user.update_secondary_emails! | ||||
| 
 | ||||
|     respond_to do |format| | ||||
|       format.html { redirect_to :back, notice: "Successfully removed email." } | ||||
|       format.html { redirect_back_or_admin_user(notice: "Successfully removed email.") } | ||||
|       format.js { render nothing: true } | ||||
|     end | ||||
|   end | ||||
|  | @ -157,4 +157,12 @@ class Admin::UsersController < Admin::ApplicationController | |||
|       :projects_limit, :can_create_group, :admin, :key_id | ||||
|     ) | ||||
|   end | ||||
| 
 | ||||
|   def redirect_back_or_admin_user(options = {}) | ||||
|     redirect_back_or_default(default: default_route, options: options) | ||||
|   end | ||||
| 
 | ||||
|   def default_route | ||||
|     [:admin, @user] | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -33,6 +33,10 @@ class ApplicationController < ActionController::Base | |||
|     render_404 | ||||
|   end | ||||
| 
 | ||||
|   def redirect_back_or_default(default: root_path, options: {}) | ||||
|     redirect_to request.referer.present? ? :back : default, options | ||||
|   end | ||||
| 
 | ||||
|   protected | ||||
| 
 | ||||
|   # From https://github.com/plataformatec/devise/wiki/How-To:-Simple-Token-Authentication-Example | ||||
|  | @ -150,7 +154,7 @@ class ApplicationController < ActionController::Base | |||
|   end | ||||
| 
 | ||||
|   def git_not_found! | ||||
|     render "errors/git_not_found", layout: "errors", status: 404 | ||||
|     render html: "errors/git_not_found", layout: "errors", status: 404 | ||||
|   end | ||||
| 
 | ||||
|   def method_missing(method_sym, *arguments, &block) | ||||
|  |  | |||
|  | @ -1,12 +1,17 @@ | |||
| module Ci | ||||
|   class ProjectsController < Ci::ApplicationController | ||||
|     before_action :project | ||||
|     before_action :authenticate_user!, except: [:build, :badge] | ||||
|     before_action :authorize_access_project!, except: [:badge] | ||||
|     before_action :project, except: [:index] | ||||
|     before_action :authenticate_user!, except: [:index, :build, :badge] | ||||
|     before_action :authorize_access_project!, except: [:index, :badge] | ||||
|     before_action :authorize_manage_project!, only: [:toggle_shared_runners, :dumped_yaml] | ||||
|     before_action :no_cache, only: [:badge] | ||||
|     protect_from_forgery | ||||
| 
 | ||||
|     def show | ||||
|       # Temporary compatibility with CI badges pointing to CI project page | ||||
|       redirect_to namespace_project_path(project.gl_project.namespace, project.gl_project) | ||||
|     end | ||||
| 
 | ||||
|     # Project status badge | ||||
|     # Image with build status for sha or ref | ||||
|     def badge | ||||
|  |  | |||
|  | @ -11,10 +11,6 @@ class Import::GithubController < Import::BaseController | |||
| 
 | ||||
|   def status | ||||
|     @repos = client.repos | ||||
|     client.orgs.each do |org| | ||||
|       @repos += client.org_repos(org.login) | ||||
|     end | ||||
| 
 | ||||
|     @already_added_projects = current_user.created_projects.where(import_type: "github") | ||||
|     already_added_projects_names = @already_added_projects.pluck(:import_source) | ||||
| 
 | ||||
|  |  | |||
|  | @ -10,18 +10,18 @@ class Import::GoogleCodeController < Import::BaseController | |||
|     dump_file = params[:dump_file] | ||||
| 
 | ||||
|     unless dump_file.respond_to?(:read) | ||||
|       return redirect_to :back, alert: "You need to upload a Google Takeout archive." | ||||
|       return redirect_back_or_default(options: { alert: "You need to upload a Google Takeout archive." }) | ||||
|     end | ||||
| 
 | ||||
|     begin | ||||
|       dump = JSON.parse(dump_file.read) | ||||
|     rescue | ||||
|       return redirect_to :back, alert: "The uploaded file is not a valid Google Takeout archive." | ||||
|       return redirect_back_or_default(options: { alert: "The uploaded file is not a valid Google Takeout archive." }) | ||||
|     end | ||||
| 
 | ||||
|     client = Gitlab::GoogleCodeImport::Client.new(dump) | ||||
|     unless client.valid? | ||||
|       return redirect_to :back, alert: "The uploaded file is not a valid Google Takeout archive." | ||||
|       return redirect_back_or_default(options: { alert: "The uploaded file is not a valid Google Takeout archive." }) | ||||
|     end | ||||
| 
 | ||||
|     session[:google_code_dump] = dump | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ class InvitesController < ApplicationController | |||
| 
 | ||||
|       redirect_to path, notice: "You have been granted #{member.human_access} access to #{label}." | ||||
|     else | ||||
|       redirect_to :back, alert: "The invitation could not be accepted." | ||||
|       redirect_back_or_default(options: { alert: "The invitation could not be accepted." }) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|  | @ -31,7 +31,7 @@ class InvitesController < ApplicationController | |||
| 
 | ||||
|       redirect_to path, notice: "You have declined the invitation to join #{label}." | ||||
|     else | ||||
|       redirect_to :back, alert: "The invitation could not be declined." | ||||
|       redirect_back_or_default(options: { alert: "The invitation could not be declined." }) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ class Profiles::NotificationsController < Profiles::ApplicationController | |||
|           flash[:alert] = "Failed to save new settings" | ||||
|         end | ||||
| 
 | ||||
|         redirect_to :back | ||||
|         redirect_back_or_default(default: profile_notifications_path) | ||||
|       end | ||||
| 
 | ||||
|       format.js | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ class ProfilesController < Profiles::ApplicationController | |||
|     end | ||||
| 
 | ||||
|     respond_to do |format| | ||||
|       format.html { redirect_to :back } | ||||
|       format.html { redirect_back_or_default(default: { action: 'show' }) } | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,11 +1,32 @@ | |||
| class Projects::BuildsController < Projects::ApplicationController | ||||
|   before_action :ci_project | ||||
|   before_action :build | ||||
|   before_action :build, except: [:index, :cancel_all] | ||||
| 
 | ||||
|   before_action :authorize_admin_project!, except: [:show, :status] | ||||
|   before_action :authorize_admin_project!, except: [:index, :show, :status] | ||||
| 
 | ||||
|   layout "project" | ||||
| 
 | ||||
|   def index | ||||
|     @scope = params[:scope] | ||||
|     @all_builds = project.ci_builds | ||||
|     @builds = | ||||
|       case @scope | ||||
|       when 'all' | ||||
|         @all_builds | ||||
|       when 'finished' | ||||
|         @all_builds.finished | ||||
|       else | ||||
|         @all_builds.running_or_pending | ||||
|       end | ||||
|     @builds = @builds.order('created_at DESC').page(params[:page]).per(30) | ||||
|   end | ||||
| 
 | ||||
|   def cancel_all | ||||
|     @project.ci_builds.running_or_pending.each(&:cancel) | ||||
| 
 | ||||
|     redirect_to namespace_project_builds_path(project.namespace, project) | ||||
|   end | ||||
| 
 | ||||
|   def show | ||||
|     @builds = @ci_project.commits.find_by_sha(@build.sha).builds.order('id DESC') | ||||
|     @builds = @builds.where("id not in (?)", @build.id).page(params[:page]).per(20) | ||||
|  |  | |||
|  | @ -30,7 +30,7 @@ class Projects::CiServicesController < Projects::ApplicationController | |||
|       message = { alert: 'We tried to test the service but error occurred' } | ||||
|     end | ||||
| 
 | ||||
|     redirect_to :back, message | ||||
|     redirect_back_or_default(options: message) | ||||
|   end | ||||
| 
 | ||||
|   private | ||||
|  |  | |||
|  | @ -24,7 +24,7 @@ class Projects::CiWebHooksController < Projects::ApplicationController | |||
|   def test | ||||
|     Ci::TestHookService.new.execute(hook, current_user) | ||||
| 
 | ||||
|     redirect_to :back | ||||
|     redirect_back_or_default(default: { action: 'index' }) | ||||
|   end | ||||
| 
 | ||||
|   def destroy | ||||
|  |  | |||
|  | @ -17,9 +17,10 @@ class Projects::CompareController < Projects::ApplicationController | |||
|       execute(@project, head_ref, @project, base_ref) | ||||
| 
 | ||||
|     if compare_result | ||||
|       @commits = compare_result.commits | ||||
|       @commits = Commit.decorate(compare_result.commits, @project) | ||||
|       @diffs = compare_result.diffs | ||||
|       @commit = @commits.last | ||||
|       @first_commit = @commits.first | ||||
|       @line_notes = [] | ||||
|     end | ||||
|   end | ||||
|  |  | |||
|  | @ -46,7 +46,7 @@ class Projects::DeployKeysController < Projects::ApplicationController | |||
|   def disable | ||||
|     @project.deploy_keys_projects.find_by(deploy_key_id: params[:id]).destroy | ||||
| 
 | ||||
|     redirect_to :back | ||||
|     redirect_back_or_default(default: { action: 'index' }) | ||||
|   end | ||||
| 
 | ||||
|   protected | ||||
|  |  | |||
|  | @ -37,7 +37,7 @@ class Projects::HooksController < Projects::ApplicationController | |||
|       flash[:alert] = 'Hook execution failed. Ensure the project has commits.' | ||||
|     end | ||||
| 
 | ||||
|     redirect_to :back | ||||
|     redirect_back_or_default(default: { action: 'index' }) | ||||
|   end | ||||
| 
 | ||||
|   def destroy | ||||
|  |  | |||
|  | @ -14,6 +14,9 @@ class Projects::IssuesController < Projects::ApplicationController | |||
|   # Allow issues bulk update | ||||
|   before_action :authorize_admin_issues!, only: [:bulk_update] | ||||
| 
 | ||||
|   # Cross-reference merge requests | ||||
|   before_action :closed_by_merge_requests, only: [:show] | ||||
| 
 | ||||
|   respond_to :html | ||||
| 
 | ||||
|   def index | ||||
|  | @ -57,7 +60,7 @@ class Projects::IssuesController < Projects::ApplicationController | |||
|   def show | ||||
|     @participants = @issue.participants(current_user) | ||||
|     @note = @project.notes.new(noteable: @issue) | ||||
|     @notes = @issue.notes.inc_author.fresh | ||||
|     @notes = @issue.notes.with_associations.fresh | ||||
|     @noteable = @issue | ||||
| 
 | ||||
|     respond_with(@issue) | ||||
|  | @ -103,7 +106,7 @@ class Projects::IssuesController < Projects::ApplicationController | |||
| 
 | ||||
|   def bulk_update | ||||
|     result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute | ||||
|     redirect_to :back, notice: "#{result[:count]} issues updated" | ||||
|     redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" }) | ||||
|   end | ||||
| 
 | ||||
|   def toggle_subscription | ||||
|  | @ -112,6 +115,10 @@ class Projects::IssuesController < Projects::ApplicationController | |||
|     render nothing: true | ||||
|   end | ||||
| 
 | ||||
|   def closed_by_merge_requests | ||||
|     @closed_by_merge_requests ||= @issue.closed_by_merge_requests(current_user) | ||||
|   end | ||||
| 
 | ||||
|   protected | ||||
| 
 | ||||
|   def issue | ||||
|  |  | |||
|  | @ -56,6 +56,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController | |||
| 
 | ||||
|   def diffs | ||||
|     @commit = @merge_request.last_commit | ||||
|     @first_commit = @merge_request.first_commit | ||||
| 
 | ||||
|     @comments_allowed = @reply_allowed = true | ||||
|     @comments_target = { | ||||
|       noteable_type: 'MergeRequest', | ||||
|  | @ -89,7 +91,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController | |||
|     @target_project = merge_request.target_project | ||||
|     @source_project = merge_request.source_project | ||||
|     @commits = @merge_request.compare_commits | ||||
|     @commit = @merge_request.compare_commits.last | ||||
|     @commit = @merge_request.last_commit | ||||
|     @first_commit = @merge_request.first_commit | ||||
|     @diffs = @merge_request.compare_diffs | ||||
|     @note_counts = Note.where(commit_id: @commits.map(&:id)). | ||||
|       group(:commit_id).count | ||||
|  | @ -259,7 +262,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController | |||
|     @commits = @merge_request.commits | ||||
| 
 | ||||
|     @merge_request_diff = @merge_request.merge_request_diff | ||||
|      | ||||
| 
 | ||||
|     if @merge_request.locked_long_ago? | ||||
|       @merge_request.unlock_mr | ||||
|       @merge_request.close | ||||
|  |  | |||
|  | @ -75,11 +75,7 @@ class Projects::MilestonesController < Projects::ApplicationController | |||
|   end | ||||
| 
 | ||||
|   def sort_issues | ||||
|     @issues = @milestone.issues.where(id: params['sortable_issue']) | ||||
|     @issues.each do |issue| | ||||
|       issue.position = params['sortable_issue'].index(issue.id.to_s) + 1 | ||||
|       issue.save | ||||
|     end | ||||
|     @milestone.sort_issues(params['sortable_issue'].map(&:to_i)) | ||||
| 
 | ||||
|     render json: { saved: true } | ||||
|   end | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ class Projects::NotesController < Projects::ApplicationController | |||
| 
 | ||||
|     respond_to do |format| | ||||
|       format.json { render_note_json(@note) } | ||||
|       format.html { redirect_to :back } | ||||
|       format.html { redirect_back_or_default } | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|  | @ -34,7 +34,7 @@ class Projects::NotesController < Projects::ApplicationController | |||
| 
 | ||||
|     respond_to do |format| | ||||
|       format.json { render_note_json(@note) } | ||||
|       format.html { redirect_to :back } | ||||
|       format.html { redirect_back_or_default } | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|  |  | |||
|  | @ -72,7 +72,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController | |||
| 
 | ||||
|   def leave | ||||
|     if @project.namespace == current_user.namespace | ||||
|       return redirect_to(:back, alert: 'You can not leave your own project. Transfer or delete the project.') | ||||
|       message = 'You can not leave your own project. Transfer or delete the project.' | ||||
|       return redirect_back_or_default(default: { action: 'index' }, options: { alert: message }) | ||||
|     end | ||||
| 
 | ||||
|     @project.project_members.find_by(user_id: current_user).destroy | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ class Projects::RefsController < Projects::ApplicationController | |||
|   include TreeHelper | ||||
| 
 | ||||
|   before_action :require_non_empty_project | ||||
|   before_action :validate_ref_id | ||||
|   before_action :assign_ref_vars | ||||
|   before_action :authorize_download_code! | ||||
| 
 | ||||
|  | @ -71,4 +72,10 @@ class Projects::RefsController < Projects::ApplicationController | |||
|       format.js | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   private | ||||
| 
 | ||||
|   def validate_ref_id | ||||
|     return not_found! if params[:id].present? && params[:id] !~ Gitlab::Regex.git_reference_regex | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -11,18 +11,9 @@ class Projects::RepositoriesController < Projects::ApplicationController | |||
|   end | ||||
| 
 | ||||
|   def archive | ||||
|     begin | ||||
|       file_path = ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute | ||||
|     rescue | ||||
|       return head :not_found | ||||
|     end | ||||
| 
 | ||||
|     if file_path | ||||
|       # Send file to user | ||||
|       response.headers["Content-Length"] = File.open(file_path).size.to_s | ||||
|       send_file file_path | ||||
|     else | ||||
|       redirect_to request.fullpath | ||||
|     end | ||||
|     render json: ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute | ||||
|   rescue => ex | ||||
|     logger.error("#{self.class.name}: #{ex}") | ||||
|     return git_not_found! | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -9,6 +9,10 @@ class Projects::ServicesController < Projects::ApplicationController | |||
|                     :note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url, | ||||
|                     :notify, :color, | ||||
|                     :server_host, :server_port, :default_irc_uri, :enable_ssl_verification] | ||||
| 
 | ||||
|   # Parameters to ignore if no value is specified | ||||
|   FILTER_BLANK_PARAMS = [:password] | ||||
| 
 | ||||
|   # Authorize | ||||
|   before_action :authorize_admin_project! | ||||
|   before_action :service, only: [:edit, :update, :test] | ||||
|  | @ -48,7 +52,7 @@ class Projects::ServicesController < Projects::ApplicationController | |||
|       message = { alert: error_message } | ||||
|     end | ||||
| 
 | ||||
|     redirect_to :back, message | ||||
|     redirect_back_or_default(options: message) | ||||
|   end | ||||
| 
 | ||||
|   private | ||||
|  | @ -59,7 +63,9 @@ class Projects::ServicesController < Projects::ApplicationController | |||
| 
 | ||||
|   def service_params | ||||
|     service_params = params.require(:service).permit(ALLOWED_PARAMS) | ||||
|     service_params.delete("password") if service_params["password"].blank? | ||||
|     FILTER_BLANK_PARAMS.each do |param| | ||||
|       service_params.delete(param) if service_params[param].blank? | ||||
|     end | ||||
|     service_params | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -1,11 +1,14 @@ | |||
| class ProjectsController < ApplicationController | ||||
|   include ExtractsPath | ||||
| 
 | ||||
|   prepend_before_filter :render_go_import, only: [:show] | ||||
|   skip_before_action :authenticate_user!, only: [:show, :activity] | ||||
|   before_action :project, except: [:new, :create] | ||||
|   before_action :repository, except: [:new, :create] | ||||
|   before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists? | ||||
| 
 | ||||
|   # Authorize | ||||
|   before_action :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive] | ||||
|   before_action :authorize_admin_project!, only: [:edit, :update] | ||||
|   before_action :event_filter, only: [:show, :activity] | ||||
| 
 | ||||
|   layout :determine_layout | ||||
|  | @ -56,6 +59,8 @@ class ProjectsController < ApplicationController | |||
|   end | ||||
| 
 | ||||
|   def transfer | ||||
|     return access_denied! unless can?(current_user, :change_namespace, @project) | ||||
| 
 | ||||
|     namespace = Namespace.find_by(id: params[:new_namespace_id]) | ||||
|     ::Projects::TransferService.new(project, current_user).execute(namespace) | ||||
| 
 | ||||
|  | @ -64,6 +69,15 @@ class ProjectsController < ApplicationController | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def remove_fork | ||||
|     return access_denied! unless can?(current_user, :remove_fork_project, @project) | ||||
| 
 | ||||
|     if @project.forked? | ||||
|       @project.forked_project_link.destroy | ||||
|       flash[:notice] = 'The fork relationship has been removed.' | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def activity | ||||
|     respond_to do |format| | ||||
|       format.html | ||||
|  | @ -87,7 +101,7 @@ class ProjectsController < ApplicationController | |||
|             render 'projects/empty' | ||||
|           else | ||||
|             if current_user | ||||
|               @membership = @project.project_member_by_id(current_user.id) | ||||
|               @membership = @project.team.find_member(current_user.id) | ||||
|             end | ||||
| 
 | ||||
|             render :show | ||||
|  | @ -139,6 +153,7 @@ class ProjectsController < ApplicationController | |||
| 
 | ||||
|   def archive | ||||
|     return access_denied! unless can?(current_user, :archive_project, @project) | ||||
| 
 | ||||
|     @project.archive! | ||||
| 
 | ||||
|     respond_to do |format| | ||||
|  | @ -148,6 +163,7 @@ class ProjectsController < ApplicationController | |||
| 
 | ||||
|   def unarchive | ||||
|     return access_denied! unless can?(current_user, :archive_project, @project) | ||||
| 
 | ||||
|     @project.unarchive! | ||||
| 
 | ||||
|     respond_to do |format| | ||||
|  | @ -225,4 +241,14 @@ class ProjectsController < ApplicationController | |||
| 
 | ||||
|     render "go_import", layout: false | ||||
|   end | ||||
| 
 | ||||
|   def repo_exists? | ||||
|     project.repository_exists? && !project.empty_repo? | ||||
|   end | ||||
| 
 | ||||
|   # Override get_id from ExtractsPath, which returns the branch and file path  | ||||
|   # for the blob/tree, which in this case is just the root of the default branch. | ||||
|   def get_id | ||||
|     project.repository.root_ref | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -53,15 +53,36 @@ class IssuableFinder | |||
|       end | ||||
|   end | ||||
| 
 | ||||
|   def project? | ||||
|     params[:project_id].present? | ||||
|   end | ||||
| 
 | ||||
|   def project | ||||
|     return @project if defined?(@project) | ||||
| 
 | ||||
|     @project = | ||||
|       if params[:project_id].present? | ||||
|         Project.find(params[:project_id]) | ||||
|       else | ||||
|         nil | ||||
|       end | ||||
|     if project? | ||||
|       @project = Project.find(params[:project_id]) | ||||
|        | ||||
|       unless Ability.abilities.allowed?(current_user, :read_project, @project) | ||||
|         @project = nil | ||||
|       end  | ||||
|     else | ||||
|       @project = nil | ||||
|     end | ||||
| 
 | ||||
|     @project | ||||
|   end | ||||
| 
 | ||||
|   def projects | ||||
|     return @projects if defined?(@projects) | ||||
| 
 | ||||
|     if project? | ||||
|       project | ||||
|     elsif current_user && params[:authorized_only].presence && !current_user_related? | ||||
|       current_user.authorized_projects | ||||
|     else | ||||
|       ProjectsFinder.new.execute(current_user) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def search | ||||
|  | @ -72,7 +93,7 @@ class IssuableFinder | |||
|     params[:milestone_title].present? | ||||
|   end | ||||
| 
 | ||||
|   def no_milestones? | ||||
|   def filter_by_no_milestone? | ||||
|     milestones? && params[:milestone_title] == Milestone::None.title | ||||
|   end | ||||
| 
 | ||||
|  | @ -81,12 +102,22 @@ class IssuableFinder | |||
| 
 | ||||
|     @milestones = | ||||
|       if milestones? | ||||
|         Milestone.where(title: params[:milestone_title]) | ||||
|         scope = Milestone.where(project_id: projects) | ||||
| 
 | ||||
|         scope.where(title: params[:milestone_title]) | ||||
|       else | ||||
|         nil | ||||
|       end | ||||
|   end | ||||
| 
 | ||||
|   def labels? | ||||
|     params[:label_name].present? | ||||
|   end | ||||
| 
 | ||||
|   def filter_by_no_label? | ||||
|     labels? && params[:label_name] == Label::None.title | ||||
|   end | ||||
| 
 | ||||
|   def assignee? | ||||
|     params[:assignee_id].present? | ||||
|   end | ||||
|  | @ -120,19 +151,7 @@ class IssuableFinder | |||
|   private | ||||
| 
 | ||||
|   def init_collection | ||||
|     table_name = klass.table_name | ||||
| 
 | ||||
|     if project | ||||
|       if Ability.abilities.allowed?(current_user, :read_project, project) | ||||
|         project.send(table_name) | ||||
|       else | ||||
|         [] | ||||
|       end | ||||
|     elsif current_user && params[:authorized_only].presence && !current_user_related? | ||||
|       klass.of_projects(current_user.authorized_projects).references(:project) | ||||
|     else | ||||
|       klass.of_projects(ProjectsFinder.new.execute(current_user)).references(:project) | ||||
|     end | ||||
|     klass.all | ||||
|   end | ||||
| 
 | ||||
|   def by_scope(items) | ||||
|  | @ -170,7 +189,12 @@ class IssuableFinder | |||
|   end | ||||
| 
 | ||||
|   def by_project(items) | ||||
|     items = items.of_projects(project.id) if project | ||||
|     items = | ||||
|       if projects | ||||
|         items.of_projects(projects).references(:project) | ||||
|       else | ||||
|         items.none | ||||
|       end | ||||
| 
 | ||||
|     items | ||||
|   end | ||||
|  | @ -185,18 +209,6 @@ class IssuableFinder | |||
|     items.sort(params[:sort]) | ||||
|   end | ||||
| 
 | ||||
|   def by_milestone(items) | ||||
|     if milestones? | ||||
|       if no_milestones? | ||||
|         items = items.where(milestone_id: [-1, nil]) | ||||
|       else | ||||
|         items = items.where(milestone_id: milestones.try(:pluck, :id)) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     items | ||||
|   end | ||||
| 
 | ||||
|   def by_assignee(items) | ||||
|     if assignee? | ||||
|       items = items.where(assignee_id: assignee.try(:id)) | ||||
|  | @ -213,20 +225,36 @@ class IssuableFinder | |||
|     items | ||||
|   end | ||||
| 
 | ||||
|   def by_label(items) | ||||
|     if params[:label_name].present? | ||||
|       if params[:label_name] == Label::None.title | ||||
|         item_ids = LabelLink.where(target_type: klass.name).pluck(:target_id) | ||||
|   def by_milestone(items) | ||||
|     if milestones? | ||||
|       if filter_by_no_milestone? | ||||
|         items = items.where(milestone_id: [-1, nil]) | ||||
|       else | ||||
|         items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] }) | ||||
| 
 | ||||
|         items = items.where('id NOT IN (?)', item_ids) | ||||
|         if projects | ||||
|           items = items.where(milestones: { project_id: projects }) | ||||
|         end | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     items | ||||
|   end | ||||
| 
 | ||||
|   def by_label(items) | ||||
|     if labels? | ||||
|       if filter_by_no_label? | ||||
|         items = items. | ||||
|           joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{klass.name}' AND label_links.target_id = #{klass.table_name}.id"). | ||||
|           where(label_links: { id: nil }) | ||||
|       else | ||||
|         label_names = params[:label_name].split(",") | ||||
| 
 | ||||
|         item_ids = LabelLink.joins(:label). | ||||
|           where('labels.title in (?)', label_names). | ||||
|           where(target_type: klass.name).pluck(:target_id) | ||||
|         items = items.joins(:labels).where(labels: { title: label_names }) | ||||
| 
 | ||||
|         items = items.where(id: item_ids) | ||||
|         if projects | ||||
|           items = items.where(labels: { project_id: projects }) | ||||
|         end | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|  |  | |||
|  | @ -16,6 +16,6 @@ module AppearancesHelper | |||
|   end | ||||
| 
 | ||||
|   def brand_header_logo | ||||
|     image_tag 'logo.svg' | ||||
|     render 'shared/logo.svg' | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -68,13 +68,17 @@ module ApplicationHelper | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def avatar_icon(user_email = '', size = nil) | ||||
|     user = User.find_by(email: user_email) | ||||
|   def avatar_icon(user_or_email = nil, size = nil) | ||||
|     if user_or_email.is_a?(User) | ||||
|       user = user_or_email | ||||
|     else | ||||
|       user = User.find_by(email: user_or_email) | ||||
|     end | ||||
| 
 | ||||
|     if user | ||||
|       user.avatar_url(size) || default_avatar | ||||
|     else | ||||
|       gravatar_icon(user_email, size) | ||||
|       gravatar_icon(user_or_email, size) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|  |  | |||
|  | @ -170,7 +170,8 @@ module DiffHelper | |||
| 
 | ||||
|   def commit_for_diff(diff) | ||||
|     if diff.deleted_file | ||||
|       @merge_request ? @merge_request.commits.last : @commit.parents.first | ||||
|       first_commit = @first_commit || @commit | ||||
|       first_commit.parent | ||||
|     else | ||||
|       @commit | ||||
|     end | ||||
|  |  | |||
|  | @ -25,6 +25,10 @@ module GitlabRoutingHelper | |||
|     namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref) | ||||
|   end | ||||
| 
 | ||||
|   def project_builds_path(project, *args) | ||||
|     namespace_project_builds_path(project.namespace, project, *args) | ||||
|   end | ||||
| 
 | ||||
|   def activity_project_path(project, *args) | ||||
|     activity_namespace_project_path(project.namespace, project, *args) | ||||
|   end | ||||
|  |  | |||
|  | @ -83,6 +83,10 @@ module IssuesHelper | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def merge_requests_sentence(merge_requests) | ||||
|     merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ') | ||||
|   end | ||||
| 
 | ||||
|   # Required for Gitlab::Markdown::IssueReferenceFilter | ||||
|   module_function :url_for_issue | ||||
| end | ||||
|  |  | |||
|  | @ -92,11 +92,19 @@ module LabelsHelper | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def project_labels_options(project) | ||||
|     labels = project.labels.to_a | ||||
|     labels.unshift(Label::None) | ||||
|     labels.unshift(Label::Any) | ||||
|     options_from_collection_for_select(labels, 'name', 'title', params[:label_name]) | ||||
|   def projects_labels_options | ||||
|     labels = | ||||
|       if @project | ||||
|         @project.labels | ||||
|       else | ||||
|         Label.where(project_id: @projects) | ||||
|       end | ||||
| 
 | ||||
|     grouped_labels = Labels::GroupService.new(labels).execute | ||||
|     grouped_labels.unshift(Label::None) | ||||
|     grouped_labels.unshift(Label::Any) | ||||
| 
 | ||||
|     options_from_collection_for_select(grouped_labels, 'name', 'title', params[:label_name]) | ||||
|   end | ||||
| 
 | ||||
|   # Required for Gitlab::Markdown::LabelReferenceFilter | ||||
|  |  | |||
|  | @ -47,7 +47,7 @@ module MergeRequestsHelper | |||
|   end | ||||
| 
 | ||||
|   def issues_sentence(issues) | ||||
|     issues.map { |i| "##{i.iid}" }.to_sentence | ||||
|     issues.map(&:to_reference).to_sentence | ||||
|   end | ||||
| 
 | ||||
|   def mr_change_branches_path(merge_request) | ||||
|  |  | |||
|  | @ -34,7 +34,8 @@ module PreferencesHelper | |||
|   def project_view_choices | ||||
|     [ | ||||
|       ['Readme (default)', :readme], | ||||
|       ['Activity view', :activity] | ||||
|       ['Activity view', :activity], | ||||
|       ['Files view', :files] | ||||
|     ] | ||||
|   end | ||||
| 
 | ||||
|  | @ -46,8 +47,7 @@ module PreferencesHelper | |||
|     Gitlab::ColorSchemes.for_user(current_user).css_class | ||||
|   end | ||||
| 
 | ||||
|   def prefer_readme? | ||||
|     !current_user || | ||||
|       current_user.project_view == 'readme' | ||||
|   def default_project_view | ||||
|     current_user ? current_user.project_view : 'readme' | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ module ProjectsHelper | |||
|     author_html =  "" | ||||
| 
 | ||||
|     # Build avatar image tag | ||||
|     author_html << image_tag(avatar_icon(author.try(:email), opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar] | ||||
|     author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar] | ||||
| 
 | ||||
|     # Build name span tag | ||||
|     author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name] | ||||
|  | @ -70,6 +70,10 @@ module ProjectsHelper | |||
|     "You are going to transfer #{project.name_with_namespace} to another owner. Are you ABSOLUTELY sure?" | ||||
|   end | ||||
| 
 | ||||
|   def remove_fork_project_message(project) | ||||
|     "You are going to remove the fork relationship to source project #{@project.forked_from_project.name_with_namespace}.  Are you ABSOLUTELY sure?" | ||||
|   end | ||||
| 
 | ||||
|   def project_nav_tabs | ||||
|     @nav_tabs ||= get_project_nav_tabs(@project, current_user) | ||||
|   end | ||||
|  | @ -113,6 +117,10 @@ module ProjectsHelper | |||
|       nav_tabs << :merge_requests | ||||
|     end | ||||
| 
 | ||||
|     if project.gitlab_ci? && can?(current_user, :read_build, project) | ||||
|       nav_tabs << :builds | ||||
|     end | ||||
| 
 | ||||
|     if can?(current_user, :admin_project, project) | ||||
|       nav_tabs << :settings | ||||
|     end | ||||
|  |  | |||
|  | @ -13,4 +13,17 @@ module RunnersHelper | |||
|                   title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago" | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def runner_link(runner) | ||||
|     display_name = truncate(runner.display_name, length: 15) | ||||
|     id = "\##{runner.id}" | ||||
| 
 | ||||
|     if current_user && current_user.admin | ||||
|       link_to ci_admin_runner_path(runner) do | ||||
|         display_name + id | ||||
|       end | ||||
|     else | ||||
|       display_name + id | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -0,0 +1,12 @@ | |||
| class AbuseReportMailer < BaseMailer | ||||
|   include Gitlab::CurrentSettings | ||||
| 
 | ||||
|   def notify(abuse_report_id) | ||||
|     @abuse_report = AbuseReport.find(abuse_report_id) | ||||
| 
 | ||||
|     mail( | ||||
|       to:       current_application_settings.admin_notification_email,  | ||||
|       subject:  "#{@abuse_report.user.name} (#{@abuse_report.user.username}) was reported for abuse" | ||||
|     ) | ||||
|   end | ||||
| end | ||||
|  | @ -41,6 +41,7 @@ class Ability | |||
|           :read_project_member, | ||||
|           :read_merge_request, | ||||
|           :read_note, | ||||
|           :read_build, | ||||
|           :download_code | ||||
|         ] | ||||
| 
 | ||||
|  | @ -127,6 +128,7 @@ class Ability | |||
|         :read_project_member, | ||||
|         :read_merge_request, | ||||
|         :read_note, | ||||
|         :read_build, | ||||
|         :create_project, | ||||
|         :create_issue, | ||||
|         :create_note | ||||
|  | @ -187,7 +189,8 @@ class Ability | |||
|         :change_visibility_level, | ||||
|         :rename_project, | ||||
|         :remove_project, | ||||
|         :archive_project | ||||
|         :archive_project, | ||||
|         :remove_fork_project | ||||
|       ] | ||||
|     end | ||||
| 
 | ||||
|  |  | |||
|  | @ -44,6 +44,10 @@ class ApplicationSetting < ActiveRecord::Base | |||
|     allow_blank: true, | ||||
|     format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" } | ||||
| 
 | ||||
|   validates :admin_notification_email, | ||||
|     allow_blank: true, | ||||
|     email: true | ||||
| 
 | ||||
|   validates_each :restricted_visibility_levels do |record, attr, value| | ||||
|     unless value.nil? | ||||
|       value.each do |level| | ||||
|  |  | |||
|  | @ -93,10 +93,7 @@ module Ci | |||
|           Ci::WebHookService.new.build_end(build) | ||||
|         end | ||||
| 
 | ||||
|         if build.commit.should_create_next_builds?(build) | ||||
|           build.commit.create_next_builds(build.ref, build.tag, build.user, build.trigger_request) | ||||
|         end | ||||
| 
 | ||||
|         build.commit.create_next_builds(build) | ||||
|         project.execute_services(build) | ||||
| 
 | ||||
|         if project.coverage_enabled? | ||||
|  |  | |||
|  | @ -24,6 +24,8 @@ module Ci | |||
|     has_many :builds, class_name: 'Ci::Build' | ||||
|     has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' | ||||
| 
 | ||||
|     scope :ordered, -> { order('CASE WHEN ci_commits.committed_at IS NULL THEN 0 ELSE 1 END', :committed_at, :id) } | ||||
| 
 | ||||
|     validates_presence_of :sha | ||||
|     validate :valid_commit_sha | ||||
| 
 | ||||
|  | @ -89,19 +91,28 @@ module Ci | |||
|     def create_builds(ref, tag, user, trigger_request = nil) | ||||
|       return unless config_processor | ||||
|       config_processor.stages.any? do |stage| | ||||
|         CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present? | ||||
|         CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request, 'success').present? | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     def create_next_builds(ref, tag, user, trigger_request) | ||||
|     def create_next_builds(build) | ||||
|       return unless config_processor | ||||
| 
 | ||||
|       stages = builds.where(ref: ref, tag: tag, trigger_request: trigger_request).group_by(&:stage) | ||||
|       # don't create other builds if this one is retried | ||||
|       latest_builds = builds.similar(build).latest | ||||
|       return unless latest_builds.exists?(build.id) | ||||
| 
 | ||||
|       config_processor.stages.any? do |stage| | ||||
|         unless stages.include?(stage) | ||||
|           CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present? | ||||
|         end | ||||
|       # get list of stages after this build | ||||
|       next_stages = config_processor.stages.drop_while { |stage| stage != build.stage } | ||||
|       next_stages.delete(build.stage) | ||||
| 
 | ||||
|       # get status for all prior builds | ||||
|       prior_builds = latest_builds.reject { |other_build| next_stages.include?(other_build.stage) } | ||||
|       status = Ci::Status.get_status(prior_builds) | ||||
| 
 | ||||
|       # create builds for next stages based | ||||
|       next_stages.any? do |stage| | ||||
|         CreateBuildsService.new.execute(self, stage, build.ref, build.tag, build.user, build.trigger_request, status).present? | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|  | @ -130,24 +141,7 @@ module Ci | |||
|         return 'failed' | ||||
|       end | ||||
| 
 | ||||
|       @status ||= begin | ||||
|         latest = latest_statuses | ||||
|         latest.reject! { |status| status.try(&:allow_failure?) } | ||||
| 
 | ||||
|         if latest.none? | ||||
|           'skipped' | ||||
|         elsif latest.all?(&:success?) | ||||
|           'success' | ||||
|         elsif latest.all?(&:pending?) | ||||
|           'pending' | ||||
|         elsif latest.any?(&:running?) || latest.any?(&:pending?) | ||||
|           'running' | ||||
|         elsif latest.all?(&:canceled?) | ||||
|           'canceled' | ||||
|         else | ||||
|           'failed' | ||||
|         end | ||||
|       end | ||||
|       @status ||= Ci::Status.get_status(latest_statuses) | ||||
|     end | ||||
| 
 | ||||
|     def pending? | ||||
|  | @ -217,16 +211,6 @@ module Ci | |||
|       update!(committed_at: DateTime.now) | ||||
|     end | ||||
| 
 | ||||
|     def should_create_next_builds?(build) | ||||
|       # don't create other builds if this one is retried | ||||
|       other_builds = builds.similar(build).latest | ||||
|       return false unless other_builds.include?(build) | ||||
| 
 | ||||
|       other_builds.all? do |build| | ||||
|         build.success? || build.ignored? | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     private | ||||
| 
 | ||||
|     def save_yaml_error(error) | ||||
|  |  | |||
|  | @ -205,7 +205,7 @@ module Ci | |||
|     end | ||||
| 
 | ||||
|     def commits | ||||
|       gl_project.ci_commits | ||||
|       gl_project.ci_commits.ordered | ||||
|     end | ||||
| 
 | ||||
|     def builds | ||||
|  |  | |||
|  | @ -59,7 +59,7 @@ module Ci | |||
|     end | ||||
| 
 | ||||
|     def display_name | ||||
|       return token unless !description.blank? | ||||
|       return short_sha unless !description.blank? | ||||
| 
 | ||||
|       description | ||||
|     end | ||||
|  | @ -95,7 +95,7 @@ module Ci | |||
|     end | ||||
| 
 | ||||
|     def short_sha | ||||
|       token[0...10] | ||||
|       token[0...8] if token | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -164,6 +164,14 @@ class Commit | |||
|     @committer ||= User.find_by_any_email(committer_email) | ||||
|   end | ||||
| 
 | ||||
|   def parents | ||||
|     @parents ||= parent_ids.map { |id| project.commit(id) } | ||||
|   end | ||||
| 
 | ||||
|   def parent | ||||
|     @parent ||= project.commit(self.parent_id) if self.parent_id | ||||
|   end | ||||
| 
 | ||||
|   def notes | ||||
|     project.notes.for_commit_id(self.id) | ||||
|   end | ||||
|  | @ -181,10 +189,6 @@ class Commit | |||
|     @raw.short_id(7) | ||||
|   end | ||||
| 
 | ||||
|   def parents | ||||
|     @parents ||= Commit.decorate(super, project) | ||||
|   end | ||||
| 
 | ||||
|   def ci_commit | ||||
|     project.ci_commit(sha) | ||||
|   end | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ class CommitStatus < ActiveRecord::Base | |||
|   scope :success, -> { where(status: 'success') } | ||||
|   scope :failed, -> { where(status: 'failed')  } | ||||
|   scope :running_or_pending, -> { where(status:[:running, :pending]) } | ||||
|   scope :finished, -> { where(status:[:success, :failed, :canceled]) } | ||||
|   scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) } | ||||
|   scope :ordered, -> { order(:ref, :stage_idx, :name) } | ||||
|   scope :for_ref, ->(ref) { where(ref: ref) } | ||||
|  | @ -27,7 +28,7 @@ class CommitStatus < ActiveRecord::Base | |||
|     end | ||||
| 
 | ||||
|     event :drop do | ||||
|       transition running: :failed | ||||
|       transition [:pending, :running] => :failed | ||||
|     end | ||||
| 
 | ||||
|     event :success do | ||||
|  |  | |||
|  | @ -48,7 +48,7 @@ module Issuable | |||
| 
 | ||||
|     attr_mentionable :title  | ||||
|     attr_mentionable :description, cache: true | ||||
|     participant :author, :assignee, :notes | ||||
|     participant :author, :assignee, :notes_with_associations | ||||
|   end | ||||
| 
 | ||||
|   module ClassMethods | ||||
|  | @ -86,6 +86,10 @@ module Issuable | |||
|     assignee_id_changed? | ||||
|   end | ||||
| 
 | ||||
|   def open? | ||||
|     opened? || reopened? | ||||
|   end | ||||
| 
 | ||||
|   # | ||||
|   # Votes | ||||
|   # | ||||
|  | @ -177,6 +181,10 @@ module Issuable | |||
|     self.class.to_s.underscore | ||||
|   end | ||||
| 
 | ||||
|   def notes_with_associations | ||||
|     notes.includes(:author, :project) | ||||
|   end | ||||
| 
 | ||||
|   private | ||||
| 
 | ||||
|   def filter_superceded_votes(votes, notes) | ||||
|  |  | |||
|  | @ -60,12 +60,12 @@ module Mentionable | |||
|   end | ||||
| 
 | ||||
|   def mentioned_users(current_user = nil, load_lazy_references: true) | ||||
|     all_references(current_user).users | ||||
|     all_references(current_user, load_lazy_references: load_lazy_references).users | ||||
|   end | ||||
| 
 | ||||
|   # Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference. | ||||
|   def referenced_mentionables(current_user = self.author, text = nil) | ||||
|     refs = all_references(current_user, text) | ||||
|   def referenced_mentionables(current_user = self.author, text = nil, load_lazy_references: true) | ||||
|     refs = all_references(current_user, text, load_lazy_references: load_lazy_references) | ||||
|     (refs.issues + refs.merge_requests + refs.commits) - [local_reference] | ||||
|   end | ||||
| 
 | ||||
|  |  | |||
|  | @ -64,7 +64,7 @@ class Group < Namespace | |||
|   end | ||||
| 
 | ||||
|   def owners | ||||
|     @owners ||= group_members.owners.map(&:user) | ||||
|     @owners ||= group_members.owners.includes(:user).map(&:user) | ||||
|   end | ||||
| 
 | ||||
|   def add_users(user_ids, access_level, current_user = nil) | ||||
|  |  | |||
|  | @ -0,0 +1,9 @@ | |||
| class GroupLabel | ||||
|   attr_accessor :title, :labels | ||||
|   alias_attribute :name, :title | ||||
| 
 | ||||
|   def initialize(title, labels) | ||||
|     @title = title | ||||
|     @labels = labels | ||||
|   end | ||||
| end | ||||
|  | @ -1,5 +1,5 @@ | |||
| class GroupMilestone | ||||
| 
 | ||||
|   attr_accessor :title, :milestones | ||||
|   alias_attribute :name, :title | ||||
| 
 | ||||
|   def initialize(title, milestones) | ||||
|  | @ -7,18 +7,10 @@ class GroupMilestone | |||
|     @milestones = milestones | ||||
|   end | ||||
| 
 | ||||
|   def title | ||||
|     @title | ||||
|   end | ||||
| 
 | ||||
|   def safe_title | ||||
|     @title.parameterize | ||||
|   end | ||||
| 
 | ||||
|   def milestones | ||||
|     @milestones | ||||
|   end | ||||
| 
 | ||||
|    | ||||
|   def projects | ||||
|     milestones.map { |milestone| milestone.project } | ||||
|   end | ||||
|  |  | |||
|  | @ -95,4 +95,14 @@ class Issue < ActiveRecord::Base | |||
|   def source_project | ||||
|     project | ||||
|   end | ||||
| 
 | ||||
|   # From all notes on this issue, we'll select the system notes about linked | ||||
|   # merge requests. Of those, the MRs closing `self` are returned. | ||||
|   def closed_by_merge_requests(current_user = nil) | ||||
|     return [] unless open? | ||||
| 
 | ||||
|     notes.system.flat_map do |note| | ||||
|       note.all_references(current_user).merge_requests | ||||
|     end.uniq.select { |mr| mr.open? && mr.closes_issue?(self) } | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -40,7 +40,7 @@ class MergeRequest < ActiveRecord::Base | |||
|   after_create :create_merge_request_diff | ||||
|   after_update :update_merge_request_diff | ||||
| 
 | ||||
|   delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil | ||||
|   delegate :commits, :diffs, to: :merge_request_diff, prefix: nil | ||||
| 
 | ||||
|   # When this attribute is true some MR validation is ignored | ||||
|   # It allows us to close or modify broken merge requests | ||||
|  | @ -157,6 +157,18 @@ class MergeRequest < ActiveRecord::Base | |||
|     reference | ||||
|   end | ||||
| 
 | ||||
|   def last_commit | ||||
|     merge_request_diff ? merge_request_diff.last_commit : compare_commits.last | ||||
|   end  | ||||
| 
 | ||||
|   def first_commit | ||||
|     merge_request_diff ? merge_request_diff.first_commit : compare_commits.first | ||||
|   end  | ||||
| 
 | ||||
|   def last_commit_short_sha | ||||
|     last_commit.short_id | ||||
|   end | ||||
| 
 | ||||
|   def validate_branches | ||||
|     if target_project == source_project && target_branch == source_branch | ||||
|       errors.add :branch_conflict, "You can not use same project/branch for source and target" | ||||
|  | @ -222,10 +234,6 @@ class MergeRequest < ActiveRecord::Base | |||
|     self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last | ||||
|   end | ||||
| 
 | ||||
|   def open? | ||||
|     opened? || reopened? | ||||
|   end | ||||
| 
 | ||||
|   def work_in_progress? | ||||
|     !!(title =~ /\A\[?WIP\]?:? /i) | ||||
|   end | ||||
|  | @ -294,6 +302,10 @@ class MergeRequest < ActiveRecord::Base | |||
|     target_project | ||||
|   end | ||||
| 
 | ||||
|   def closes_issue?(issue) | ||||
|     closes_issues.include?(issue) | ||||
|   end | ||||
| 
 | ||||
|   # Return the set of issues that will be closed if this merge request is accepted. | ||||
|   def closes_issues(current_user = self.author) | ||||
|     if target_branch == project.default_branch | ||||
|  |  | |||
|  | @ -55,6 +55,10 @@ class MergeRequestDiff < ActiveRecord::Base | |||
|     commits.first | ||||
|   end | ||||
| 
 | ||||
|   def first_commit | ||||
|     commits.last | ||||
|   end | ||||
| 
 | ||||
|   def last_commit_short_sha | ||||
|     @last_commit_short_sha ||= last_commit.short_id | ||||
|   end | ||||
|  | @ -163,7 +167,8 @@ class MergeRequestDiff < ActiveRecord::Base | |||
|         merge_request.fetch_ref | ||||
| 
 | ||||
|         # Get latest sha of branch from source project | ||||
|         source_sha = merge_request.source_project.commit(source_branch).sha | ||||
|         source_commit = merge_request.source_project.commit(source_branch) | ||||
|         source_sha = source_commit.try(:sha) | ||||
| 
 | ||||
|         Gitlab::CompareResult.new( | ||||
|           Gitlab::Git::Compare.new( | ||||
|  |  | |||
|  | @ -105,4 +105,36 @@ class Milestone < ActiveRecord::Base | |||
|   def author_id | ||||
|     nil | ||||
|   end | ||||
| 
 | ||||
|   # Sorts the issues for the given IDs. | ||||
|   # | ||||
|   # This method runs a single SQL query using a CASE statement to update the | ||||
|   # position of all issues in the current milestone (scoped to the list of IDs). | ||||
|   # | ||||
|   # Given the ids [10, 20, 30] this method produces a SQL query something like | ||||
|   # the following: | ||||
|   # | ||||
|   #     UPDATE issues | ||||
|   #     SET position = CASE | ||||
|   #       WHEN id = 10 THEN 1 | ||||
|   #       WHEN id = 20 THEN 2 | ||||
|   #       WHEN id = 30 THEN 3 | ||||
|   #       ELSE position | ||||
|   #     END | ||||
|   #     WHERE id IN (10, 20, 30); | ||||
|   # | ||||
|   # This method expects that the IDs given in `ids` are already Fixnums. | ||||
|   def sort_issues(ids) | ||||
|     pairs = [] | ||||
| 
 | ||||
|     ids.each_with_index do |id, index| | ||||
|       pairs << id | ||||
|       pairs << index + 1 | ||||
|     end | ||||
| 
 | ||||
|     conditions = 'WHEN id = ? THEN ? ' * ids.length | ||||
| 
 | ||||
|     issues.where(id: ids). | ||||
|       update_all(["position = CASE #{conditions} ELSE position END", *pairs]) | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -60,6 +60,11 @@ class Note < ActiveRecord::Base | |||
|   scope :inc_author_project, ->{ includes(:project, :author) } | ||||
|   scope :inc_author, ->{ includes(:author) } | ||||
| 
 | ||||
|   scope :with_associations, -> do | ||||
|     includes(:author, :noteable, :updated_by, | ||||
|              project: [:project_members, { group: [:group_members] }]) | ||||
|   end | ||||
| 
 | ||||
|   serialize :st_diff | ||||
|   before_create :set_diff, if: ->(n) { n.line_code.present? } | ||||
| 
 | ||||
|  |  | |||
|  | @ -119,7 +119,7 @@ class Project < ActiveRecord::Base | |||
|   has_many :deploy_keys, through: :deploy_keys_projects | ||||
|   has_many :users_star_projects, dependent: :destroy | ||||
|   has_many :starrers, through: :users_star_projects, source: :user | ||||
|   has_many :ci_commits, ->() { order('CASE WHEN ci_commits.committed_at IS NULL THEN 0 ELSE 1 END', :committed_at, :id) }, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id | ||||
|   has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id | ||||
|   has_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build' | ||||
| 
 | ||||
|   has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" | ||||
|  |  | |||
|  | @ -48,7 +48,7 @@ class BambooService < CiService | |||
|   end | ||||
| 
 | ||||
|   def reset_password | ||||
|     if prop_updated?(:bamboo_url) | ||||
|     if bamboo_url_changed? && !password_touched? | ||||
|       self.password = nil | ||||
|     end | ||||
|   end | ||||
|  |  | |||
|  | @ -45,7 +45,7 @@ class TeamcityService < CiService | |||
|   end | ||||
| 
 | ||||
|   def reset_password | ||||
|     if prop_updated?(:teamcity_url) | ||||
|     if teamcity_url_changed? && !password_touched? | ||||
|       self.password = nil | ||||
|     end | ||||
|   end | ||||
|  |  | |||
|  | @ -139,15 +139,28 @@ class ProjectTeam | |||
|     Gitlab::Access.options.key max_member_access(user_id) | ||||
|   end | ||||
| 
 | ||||
|   # This method assumes project and group members are eager loaded for optimal | ||||
|   # performance. | ||||
|   def max_member_access(user_id) | ||||
|     access = [] | ||||
|     access << project.project_members.find_by(user_id: user_id).try(:access_field) | ||||
| 
 | ||||
|     if group | ||||
|       access << group.group_members.find_by(user_id: user_id).try(:access_field) | ||||
|     project.project_members.each do |member| | ||||
|       if member.user_id == user_id | ||||
|         access << member.access_field if member.access_field | ||||
|         break | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     access.compact.max | ||||
|     if group | ||||
|       group.group_members.each do |member| | ||||
|         if member.user_id == user_id | ||||
|           access << member.access_field if member.access_field | ||||
|           break | ||||
|         end | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     access.max | ||||
|   end | ||||
| 
 | ||||
|   private | ||||
|  |  | |||
|  | @ -8,6 +8,14 @@ class Repository | |||
| 
 | ||||
|   attr_accessor :raw_repository, :path_with_namespace, :project | ||||
| 
 | ||||
|   def self.clean_old_archives | ||||
|     repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path | ||||
| 
 | ||||
|     return unless File.directory?(repository_downloads_path) | ||||
| 
 | ||||
|     Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete)) | ||||
|   end | ||||
| 
 | ||||
|   def initialize(path_with_namespace, default_branch = nil, project = nil) | ||||
|     @path_with_namespace = path_with_namespace | ||||
|     @project = project | ||||
|  | @ -269,14 +277,6 @@ class Repository | |||
|   end | ||||
| 
 | ||||
|   # Remove archives older than 2 hours | ||||
|   def clean_old_archives | ||||
|     repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path | ||||
| 
 | ||||
|     return unless File.directory?(repository_downloads_path) | ||||
| 
 | ||||
|     Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete)) | ||||
|   end | ||||
| 
 | ||||
|   def branches_sorted_by(value) | ||||
|     case value | ||||
|     when 'recently_updated' | ||||
|  | @ -312,13 +312,7 @@ class Repository | |||
|   end | ||||
| 
 | ||||
|   def blob_for_diff(commit, diff) | ||||
|     file = blob_at(commit.id, diff.new_path) | ||||
| 
 | ||||
|     unless file | ||||
|       file = prev_blob_for_diff(commit, diff) | ||||
|     end | ||||
| 
 | ||||
|     file | ||||
|     blob_at(commit.id, diff.file_path) | ||||
|   end | ||||
| 
 | ||||
|   def prev_blob_for_diff(commit, diff) | ||||
|  | @ -480,6 +474,10 @@ class Repository | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def merge_base(first_commit_id, second_commit_id) | ||||
|     rugged.merge_base(first_commit_id, second_commit_id) | ||||
|   end | ||||
| 
 | ||||
|   def search_files(query, ref) | ||||
|     offset = 2 | ||||
|     args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref}) | ||||
|  |  | |||
|  | @ -33,6 +33,8 @@ class Service < ActiveRecord::Base | |||
| 
 | ||||
|   after_initialize :initialize_properties | ||||
| 
 | ||||
|   after_commit :reset_updated_properties | ||||
| 
 | ||||
|   belongs_to :project | ||||
|   has_one :service_hook | ||||
| 
 | ||||
|  | @ -103,6 +105,7 @@ class Service < ActiveRecord::Base | |||
| 
 | ||||
|   # Provide convenient accessor methods | ||||
|   # for each serialized property. | ||||
|   # Also keep track of updated properties in a similar way as ActiveModel::Dirty | ||||
|   def self.prop_accessor(*args) | ||||
|     args.each do |arg| | ||||
|       class_eval %{ | ||||
|  | @ -111,21 +114,39 @@ class Service < ActiveRecord::Base | |||
|         end | ||||
| 
 | ||||
|         def #{arg}=(value) | ||||
|           updated_properties['#{arg}'] = #{arg} unless #{arg}_changed? | ||||
|           self.properties['#{arg}'] = value | ||||
|         end | ||||
| 
 | ||||
|         def #{arg}_changed? | ||||
|           #{arg}_touched? && #{arg} != #{arg}_was | ||||
|         end | ||||
| 
 | ||||
|         def #{arg}_touched? | ||||
|           updated_properties.include?('#{arg}') | ||||
|         end | ||||
| 
 | ||||
|         def #{arg}_was | ||||
|           updated_properties['#{arg}'] | ||||
|         end | ||||
|       } | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   # ActiveRecord does not provide a mechanism to track changes in serialized keys. | ||||
|   # This is why we need to perform extra query to do it mannually. | ||||
|   def prop_updated?(prop_name) | ||||
|     relation_name = self.type.underscore | ||||
|     previous_value = project.send(relation_name).send(prop_name) | ||||
|     return false if previous_value.nil? | ||||
|     previous_value != send(prop_name) | ||||
|   # Returns a hash of the properties that have been assigned a new value since last save, | ||||
|   # indicating their original values (attr => original value). | ||||
|   # ActiveRecord does not provide a mechanism to track changes in serialized keys,  | ||||
|   # so we need a specific implementation for service properties. | ||||
|   # This allows to track changes to properties set with the accessor methods, | ||||
|   # but not direct manipulation of properties hash. | ||||
|   def updated_properties | ||||
|     @updated_properties ||= ActiveSupport::HashWithIndifferentAccess.new | ||||
|   end | ||||
| 
 | ||||
|   def reset_updated_properties | ||||
|     @updated_properties = nil | ||||
|   end | ||||
|    | ||||
|   def async_execute(data) | ||||
|     return unless supported_events.include?(data[:object_kind]) | ||||
| 
 | ||||
|  |  | |||
|  | @ -68,6 +68,7 @@ class User < ActiveRecord::Base | |||
|   include Referable | ||||
|   include Sortable | ||||
|   include TokenAuthenticatable | ||||
|   include CaseSensitivity | ||||
| 
 | ||||
|   default_value_for :admin, false | ||||
|   default_value_for :can_create_group, gitlab_config.default_can_create_group | ||||
|  | @ -182,7 +183,7 @@ class User < ActiveRecord::Base | |||
| 
 | ||||
|   # User's Project preference | ||||
|   # Note: When adding an option, it MUST go on the end of the array. | ||||
|   enum project_view: [:readme, :activity] | ||||
|   enum project_view: [:readme, :activity, :files] | ||||
| 
 | ||||
|   alias_attribute :private_token, :authentication_token | ||||
| 
 | ||||
|  | @ -273,8 +274,13 @@ class User < ActiveRecord::Base | |||
|     end | ||||
| 
 | ||||
|     def by_login(login) | ||||
|       where('lower(username) = :value OR lower(email) = :value', | ||||
|             value: login.to_s.downcase).first | ||||
|       return nil unless login | ||||
| 
 | ||||
|       if login.include?('@'.freeze) | ||||
|         unscoped.iwhere(email: login).take | ||||
|       else | ||||
|         unscoped.iwhere(username: login).take | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     def find_by_username!(username) | ||||
|  | @ -700,12 +706,15 @@ class User < ActiveRecord::Base | |||
|   end | ||||
| 
 | ||||
|   def toggle_star(project) | ||||
|     user_star_project = users_star_projects. | ||||
|       where(project: project, user: self).take | ||||
|     if user_star_project | ||||
|       user_star_project.destroy | ||||
|     else | ||||
|       UsersStarProject.create!(project: project, user: self) | ||||
|     UsersStarProject.transaction do | ||||
|       user_star_project = users_star_projects. | ||||
|           where(project: project, user: self).lock(true).first | ||||
| 
 | ||||
|       if user_star_project | ||||
|         user_star_project.destroy | ||||
|       else | ||||
|         UsersStarProject.create!(project: project, user: self) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,19 +7,12 @@ class ArchiveRepositoryService | |||
|   end | ||||
| 
 | ||||
|   def execute(options = {}) | ||||
|     project.repository.clean_old_archives | ||||
|     RepositoryArchiveCacheWorker.perform_async | ||||
| 
 | ||||
|     raise "No archive file path" unless file_path | ||||
|     metadata = project.repository.archive_metadata(ref, storage_path, format) | ||||
|     raise "Repository or ref not found" if metadata.empty? | ||||
| 
 | ||||
|     return file_path if archived? | ||||
| 
 | ||||
|     unless archiving? | ||||
|       RepositoryArchiveWorker.perform_async(project.id, ref, format) | ||||
|     end | ||||
| 
 | ||||
|     archived = wait_until_archived(options[:timeout] || 5.0) | ||||
| 
 | ||||
|     file_path if archived | ||||
|     metadata | ||||
|   end | ||||
| 
 | ||||
|   private | ||||
|  | @ -27,36 +20,4 @@ class ArchiveRepositoryService | |||
|   def storage_path | ||||
|     Gitlab.config.gitlab.repository_downloads_path | ||||
|   end | ||||
| 
 | ||||
|   def file_path | ||||
|     @file_path ||= project.repository.archive_file_path(ref, storage_path, format) | ||||
|   end | ||||
| 
 | ||||
|   def pid_file_path | ||||
|     @pid_file_path ||= project.repository.archive_pid_file_path(ref, storage_path, format) | ||||
|   end | ||||
| 
 | ||||
|   def archived? | ||||
|     File.exist?(file_path) | ||||
|   end | ||||
| 
 | ||||
|   def archiving? | ||||
|     File.exist?(pid_file_path) | ||||
|   end | ||||
| 
 | ||||
|   def wait_until_archived(timeout = 5.0) | ||||
|     return archived? if timeout == 0.0 | ||||
|      | ||||
|     t1 = Time.now | ||||
| 
 | ||||
|     begin | ||||
|       sleep 0.1 | ||||
| 
 | ||||
|       success = archived? | ||||
| 
 | ||||
|       t2 = Time.now | ||||
|     end until success || t2 - t1 >= timeout | ||||
| 
 | ||||
|     success | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -1,8 +1,20 @@ | |||
| module Ci | ||||
|   class CreateBuildsService | ||||
|     def execute(commit, stage, ref, tag, user, trigger_request) | ||||
|     def execute(commit, stage, ref, tag, user, trigger_request, status) | ||||
|       builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag) | ||||
| 
 | ||||
|       # check when to create next build | ||||
|       builds_attrs = builds_attrs.select do |build_attrs| | ||||
|         case build_attrs[:when] | ||||
|         when 'on_success' | ||||
|           status == 'success' | ||||
|         when 'on_failure' | ||||
|           status == 'failed' | ||||
|         when 'always' | ||||
|           %w(success failed).include?(status) | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       builds_attrs.map do |build_attrs| | ||||
|         # don't create the same build twice | ||||
|         unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name]) | ||||
|  |  | |||
|  | @ -49,10 +49,13 @@ class GitPushService | |||
|     elsif push_to_existing_branch?(ref, oldrev) | ||||
|       # Collect data for this git push | ||||
|       @push_commits = project.repository.commits_between(oldrev, newrev) | ||||
|       project.update_merge_requests(oldrev, newrev, ref, @user) | ||||
|       process_commit_messages(ref) | ||||
|     end | ||||
| 
 | ||||
|     # Update merge requests that may be affected by this push. A new branch | ||||
|     # could cause the last commit of a merge request to change. | ||||
|     project.update_merge_requests(oldrev, newrev, ref, @user) | ||||
| 
 | ||||
|     @push_data = build_push_data(oldrev, newrev, ref) | ||||
| 
 | ||||
|     # If CI was disabled but .gitlab-ci.yml file was pushed | ||||
|  | @ -76,7 +79,7 @@ class GitPushService | |||
| 
 | ||||
|     authors = Hash.new do |hash, commit| | ||||
|       email = commit.author_email | ||||
|       return hash[email] if hash.has_key?(email) | ||||
|       next hash[email] if hash.has_key?(email) | ||||
| 
 | ||||
|       hash[email] = commit_user(commit) | ||||
|     end | ||||
|  |  | |||
|  | @ -0,0 +1,26 @@ | |||
| module Labels | ||||
|   class GroupService < ::BaseService | ||||
|     def initialize(project_labels) | ||||
|       @project_labels = project_labels.group_by(&:title) | ||||
|     end | ||||
| 
 | ||||
|     def execute | ||||
|       build(@project_labels) | ||||
|     end | ||||
| 
 | ||||
|     def label(title) | ||||
|       if title | ||||
|         group_label = @project_labels[title].group_by(&:title) | ||||
|         build(group_label).first | ||||
|       else | ||||
|         nil | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     private | ||||
| 
 | ||||
|     def build(label) | ||||
|       label.map { |title, labels| GroupLabel.new(title, labels) } | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -6,6 +6,7 @@ module MergeRequests | |||
|   # | ||||
|   class PostMergeService < MergeRequests::BaseService | ||||
|     def execute(merge_request) | ||||
|       close_issues(merge_request) | ||||
|       merge_request.mark_as_merged | ||||
|       create_merge_event(merge_request, current_user) | ||||
|       create_note(merge_request) | ||||
|  | @ -15,6 +16,15 @@ module MergeRequests | |||
| 
 | ||||
|     private | ||||
| 
 | ||||
|     def close_issues(merge_request) | ||||
|       return unless merge_request.target_branch == project.default_branch | ||||
| 
 | ||||
|       closed_issues = merge_request.closes_issues(current_user) | ||||
|       closed_issues.each do |issue| | ||||
|         Issues::CloseService.new(project, current_user, {}).execute(issue, merge_request) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     def create_merge_event(merge_request, current_user) | ||||
|       EventCreateService.new.merge_mr(merge_request, current_user) | ||||
|     end | ||||
|  |  | |||
|  | @ -6,12 +6,20 @@ module MergeRequests | |||
|       @oldrev, @newrev = oldrev, newrev | ||||
|       @branch_name = Gitlab::Git.ref_name(ref) | ||||
|       @fork_merge_requests = @project.fork_merge_requests.opened | ||||
|       @commits = @project.repository.commits_between(oldrev, newrev) | ||||
|       @commits = [] | ||||
| 
 | ||||
|       # Leave a system note if a branch were deleted/added | ||||
|       if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev) | ||||
|         comment_mr_branch_presence_changed | ||||
|         comment_mr_with_commits if @commits.present? | ||||
|       else | ||||
|         @commits = @project.repository.commits_between(oldrev, newrev) | ||||
|         comment_mr_with_commits | ||||
|         close_merge_requests | ||||
|       end | ||||
| 
 | ||||
|       close_merge_requests | ||||
|       reload_merge_requests | ||||
|       execute_mr_web_hooks | ||||
|       comment_mr_with_commits | ||||
| 
 | ||||
|       true | ||||
|     end | ||||
|  | @ -31,7 +39,6 @@ module MergeRequests | |||
|         commit_ids.include?(merge_request.last_commit.id) | ||||
|       end | ||||
| 
 | ||||
| 
 | ||||
|       merge_requests.uniq.select(&:source_project).each do |merge_request| | ||||
|         MergeRequests::PostMergeService. | ||||
|           new(merge_request.target_project, @current_user). | ||||
|  | @ -70,13 +77,38 @@ module MergeRequests | |||
|       end | ||||
|     end | ||||
| 
 | ||||
|     # Add comment about branches being deleted or added to merge requests | ||||
|     def comment_mr_branch_presence_changed | ||||
|       presence = Gitlab::Git.blank_ref?(@oldrev) ? :add : :delete | ||||
| 
 | ||||
|       merge_requests_for_source_branch.each do |merge_request| | ||||
|         last_commit = merge_request.last_commit | ||||
| 
 | ||||
|         # Only look at changed commits in restore branch case | ||||
|         unless Gitlab::Git.blank_ref?(@newrev) | ||||
|           begin | ||||
|             # Since any number of commits could have been made to the restored branch, | ||||
|             # find the common root to see what has been added. | ||||
|             common_ref = @project.repository.merge_base(last_commit.id, @newrev) | ||||
|             # If the a commit no longer exists in this repo, gitlab_git throws | ||||
|             # a Rugged::OdbError. This is fixed in https://gitlab.com/gitlab-org/gitlab_git/merge_requests/52 | ||||
|             @commits = @project.repository.commits_between(common_ref, @newrev) if common_ref | ||||
|           rescue | ||||
|           end | ||||
| 
 | ||||
|           # Prevent system notes from seeing a blank SHA | ||||
|           @oldrev = nil | ||||
|         end | ||||
| 
 | ||||
|         SystemNoteService.change_branch_presence( | ||||
|             merge_request, merge_request.project, @current_user, | ||||
|             :source, @branch_name, presence) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     # Add comment about pushing new commits to merge requests | ||||
|     def comment_mr_with_commits | ||||
|       merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a | ||||
|       merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a | ||||
|       merge_requests = filter_merge_requests(merge_requests) | ||||
| 
 | ||||
|       merge_requests.each do |merge_request| | ||||
|       merge_requests_for_source_branch.each do |merge_request| | ||||
|         mr_commit_ids = Set.new(merge_request.commits.map(&:id)) | ||||
| 
 | ||||
|         new_commits, existing_commits = @commits.partition do |commit| | ||||
|  | @ -91,14 +123,7 @@ module MergeRequests | |||
| 
 | ||||
|     # Call merge request webhook with update branches | ||||
|     def execute_mr_web_hooks | ||||
|       merge_requests = @project.origin_merge_requests.opened | ||||
|         .where(source_branch: @branch_name) | ||||
|         .to_a | ||||
|       merge_requests += @fork_merge_requests.where(source_branch: @branch_name) | ||||
|         .to_a | ||||
|       merge_requests = filter_merge_requests(merge_requests) | ||||
| 
 | ||||
|       merge_requests.each do |merge_request| | ||||
|       merge_requests_for_source_branch.each do |merge_request| | ||||
|         execute_hooks(merge_request, 'update') | ||||
|       end | ||||
|     end | ||||
|  | @ -106,5 +131,13 @@ module MergeRequests | |||
|     def filter_merge_requests(merge_requests) | ||||
|       merge_requests.uniq.select(&:source_project) | ||||
|     end | ||||
| 
 | ||||
|     def merge_requests_for_source_branch | ||||
|       @source_merge_requests ||= begin | ||||
|         merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a | ||||
|         merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a | ||||
|         filter_merge_requests(merge_requests) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue