Commit ecb1c596 authored by James Lopez's avatar James Lopez

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into fix/token-timing-attack

parents 70623cd4 36730e8e
...@@ -23,6 +23,7 @@ config/gitlab.yml ...@@ -23,6 +23,7 @@ config/gitlab.yml
config/gitlab_ci.yml config/gitlab_ci.yml
config/initializers/rack_attack.rb config/initializers/rack_attack.rb
config/initializers/smtp_settings.rb config/initializers/smtp_settings.rb
config/initializers/relative_url.rb
config/resque.yml config/resque.yml
config/unicorn.rb config/unicorn.rb
config/secrets.yml config/secrets.yml
......
...@@ -3,14 +3,48 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -3,14 +3,48 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.6.0 (unreleased) v 8.6.0 (unreleased)
- Contributions to forked projects are included in calendar - Contributions to forked projects are included in calendar
- Improve the formatting for the user page bio (Connor Shea) - Improve the formatting for the user page bio (Connor Shea)
- Removed the default password from the initial admin account created during
setup. A password can be provided during setup (see installation docs), or
GitLab will ask the user to create a new one upon first visit.
- Fix issue when pushing to projects ending in .wiki - Fix issue when pushing to projects ending in .wiki
- Fix avatar stretching by providing a cropping feature (Johann Pardanaud) - Fix avatar stretching by providing a cropping feature (Johann Pardanaud)
- Don't load all of GitLab in mail_room
- Indicate how much an MR diverged from the target branch (Pierre de La Morinerie)
- Strip leading and trailing spaces in URL validator (evuez) - Strip leading and trailing spaces in URL validator (evuez)
- Return empty array instead of 404 when commit has no statuses in commit status API
- Add support for cross-project label references
- Update documentation to reflect Guest role not being enforced on internal projects - Update documentation to reflect Guest role not being enforced on internal projects
- Allow search for logged out users
- Don't show Issues/MRs from archived projects in Groups view
- Increase the notes polling timeout over time (Roberto Dip)
- Show labels in dashboard and group milestone views
- Add main language of a project in the list of projects (Tiago Botelho)
v 8.5.4
- Do not cache requests for badges (including builds badge)
v 8.5.3
- Flush repository caches before renaming projects
- Sort starred projects on dashboard based on last activity by default
- Show commit message in JIRA mention comment
- Makes issue page and merge request page usable on mobile browsers.
v 8.5.2 v 8.5.2
- Fix sidebar overlapping content when screen width was below 1200px - Fix sidebar overlapping content when screen width was below 1200px
- Don't repeat labels listed on Labels tab
- Bring the "branded appearance" feature from EE to CE
- Fix error 500 when commenting on a commit - Fix error 500 when commenting on a commit
- Show days remaining instead of elapsed time for Milestone
- Fix broken icons on installations with relative URL (Artem Sidorenko)
- Fix issue where tag list wasn't refreshed after deleting a tag
- Fix import from gitlab.com (KazSawada)
- Improve implementation to check read access to forks and add pagination
- Don't show any "2FA required" message if it's not actually required
- Fix help keyboard shortcut on relative URL setups (Artem Sidorenko)
- Update Rails to 4.2.5.2
- Fix permissions for deprecated CI build status badge
- Don't show "Welcome to GitLab" when the search didn't return any projects
- Add Todos documentation
v 8.5.1 v 8.5.1
- Fix group projects styles - Fix group projects styles
...@@ -25,7 +59,7 @@ v 8.5.1 ...@@ -25,7 +59,7 @@ v 8.5.1
- Fix error 500 when trying to mark an already done todo as "done" - Fix error 500 when trying to mark an already done todo as "done"
- Fix an issue where MRs weren't sortable - Fix an issue where MRs weren't sortable
- Issues can now be dragged & dropped into empty milestone lists. This is also - Issues can now be dragged & dropped into empty milestone lists. This is also
possible with MRs possible with MRs
- Changed padding & background color for highlighted notes - Changed padding & background color for highlighted notes
- Re-add the newrelic_rpm gem which was removed without any deprecation or warning (Stan Hu) - Re-add the newrelic_rpm gem which was removed without any deprecation or warning (Stan Hu)
- Update sentry-raven gem to 0.15.6 - Update sentry-raven gem to 0.15.6
......
...@@ -3,24 +3,27 @@ ...@@ -3,24 +3,27 @@
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Contribute to GitLab](#contribute-to-gitlab) - [Contribute to GitLab](#contribute-to-gitlab)
- [Contributor license agreement](#contributor-license-agreement) - [Contributor license agreement](#contributor-license-agreement)
- [Security vulnerability disclosure](#security-vulnerability-disclosure) - [Security vulnerability disclosure](#security-vulnerability-disclosure)
- [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests) - [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests)
- [Helping others](#helping-others) - [Helping others](#helping-others)
- [I want to contribute!](#i-want-to-contribute) - [I want to contribute!](#i-want-to-contribute)
- [Issue tracker](#issue-tracker) - [Implement design & UI elements](#implement-design-ui-elements)
- [Feature proposals](#feature-proposals) - [Design reference](#design-reference)
- [Issue tracker guidelines](#issue-tracker-guidelines) - [UI development kit](#ui-development-kit)
- [Issue weight](#issue-weight) - [Issue tracker](#issue-tracker)
- [Regression issues](#regression-issues) - [Feature proposals](#feature-proposals)
- [Merge requests](#merge-requests) - [Issue tracker guidelines](#issue-tracker-guidelines)
- [Merge request guidelines](#merge-request-guidelines) - [Issue weight](#issue-weight)
- [Merge request description format](#merge-request-description-format) - [Regression issues](#regression-issues)
- [Contribution acceptance criteria](#contribution-acceptance-criteria) - [Merge requests](#merge-requests)
- [Changes for Stable Releases](#changes-for-stable-releases) - [Merge request guidelines](#merge-request-guidelines)
- [Definition of done](#definition-of-done) - [Merge request description format](#merge-request-description-format)
- [Style guides](#style-guides) - [Contribution acceptance criteria](#contribution-acceptance-criteria)
- [Code of conduct](#code-of-conduct) - [Changes for Stable Releases](#changes-for-stable-releases)
- [Definition of done](#definition-of-done)
- [Style guides](#style-guides)
- [Code of conduct](#code-of-conduct)
<!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
...@@ -83,6 +86,22 @@ GitLab. ...@@ -83,6 +86,22 @@ GitLab.
This was inspired by [an article by Kent C. Dodds][medium-up-for-grabs]. This was inspired by [an article by Kent C. Dodds][medium-up-for-grabs].
## Implement design & UI elements
### Design reference
The GitLab design reference can be found in the [gitlab-design] project.
The designs are made using Antetype (`.atype` files). You can use the
[free Antetype viewer (Mac OSX only)] or grab an exported PNG from the design
(the PNG is 1:1).
The current designs can be found in the [`gitlab1.atype` file].
### UI development kit
Implemented UI elements can also be found at https://gitlab.com/help/ui. Please
note that this page isn't comprehensive at this time.
## Issue tracker ## Issue tracker
To get support for your particular problem please use the To get support for your particular problem please use the
...@@ -299,13 +318,14 @@ to us than having a minimal commit log. The smaller an MR is the more likely it ...@@ -299,13 +318,14 @@ to us than having a minimal commit log. The smaller an MR is the more likely it
is it will be merged (quickly). After that you can send more MRs to enhance it. is it will be merged (quickly). After that you can send more MRs to enhance it.
For examples of feedback on merge requests please look at already For examples of feedback on merge requests please look at already
[closed merge requests][closed-merge-requests]. If you would like quick feedback on your merge [closed merge requests][closed-merge-requests]. If you would like quick feedback
request feel free to mention one of the Merge Marshalls of the [core team][core-team]. on your merge request feel free to mention one of the Merge Marshalls in the
[core team][core-team] or one of the
[Merge request coaches](https://about.gitlab.com/team/).
Please ensure that your merge request meets the contribution acceptance criteria. Please ensure that your merge request meets the contribution acceptance criteria.
When having your code reviewed and when reviewing merge requests please take the When having your code reviewed and when reviewing merge requests please take the
[thoughtbot code review guidelines](https://github.com/thoughtbot/guides/tree/master/code-review) [Thoughtbot code review guide] into account.
into account.
### Merge request description format ### Merge request description format
...@@ -343,7 +363,8 @@ description area. Copy-paste it to retain the markdown format. ...@@ -343,7 +363,8 @@ description area. Copy-paste it to retain the markdown format.
to a new table or remove an old table) to aid retrying on failure to a new table or remove an old table) to aid retrying on failure
1. Keeps the GitLab code base clean and well structured 1. Keeps the GitLab code base clean and well structured
1. Contains functionality we think other users will benefit from too 1. Contains functionality we think other users will benefit from too
1. Doesn't add configuration options since they complicate future changes 1. Doesn't add configuration options or settings options since they complicate
making and testing future changes
1. Changes after submitting the merge request should be in separate commits 1. Changes after submitting the merge request should be in separate commits
(no squashing). If necessary, you will be asked to squash when the review is (no squashing). If necessary, you will be asked to squash when the review is
over, before merging. over, before merging.
...@@ -473,3 +494,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor ...@@ -473,3 +494,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout [rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout
[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming [rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
[doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide" [doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
[gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design
[free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12
[`gitlab1.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/gitlab1.atype/
[Thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review
...@@ -6,9 +6,9 @@ gem 'rails-deprecated_sanitizer', '~> 1.0.3' ...@@ -6,9 +6,9 @@ gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with # Responders respond_to and respond_with
gem 'responders', '~> 2.0' gem 'responders', '~> 2.0'
# Specify a sprockets version due to security issue # Specify a sprockets version due to increased performance
# See https://groups.google.com/forum/#!topic/rubyonrails-security/doAVp0YaTqY # See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069
gem 'sprockets', '~> 2.12.3' gem 'sprockets', '~> 3.3.5'
# Default values for AR models # Default values for AR models
gem "default_value_for", "~> 3.0.0" gem "default_value_for", "~> 3.0.0"
...@@ -50,7 +50,7 @@ gem "browser", '~> 1.0.0' ...@@ -50,7 +50,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem "gitlab_git", '~> 8.2' gem "gitlab_git", '~> 9.0'
# LDAP Auth # LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes # GitLab fork with several improvements to original library. For full list of changes
...@@ -75,7 +75,7 @@ gem "kaminari", "~> 0.16.3" ...@@ -75,7 +75,7 @@ gem "kaminari", "~> 0.16.3"
gem "haml-rails", '~> 0.9.0' gem "haml-rails", '~> 0.9.0'
# Files attachments # Files attachments
gem "carrierwave", '~> 0.9.0' gem "carrierwave", '~> 0.10.0'
# Image editing # Image editing
gem "mini_magick", '~> 4.4.0' gem "mini_magick", '~> 4.4.0'
...@@ -260,10 +260,10 @@ group :development, :test do ...@@ -260,10 +260,10 @@ group :development, :test do
gem 'awesome_print', '~> 1.2.0', require: false gem 'awesome_print', '~> 1.2.0', require: false
gem 'fuubar', '~> 2.0.0' gem 'fuubar', '~> 2.0.0'
gem 'database_cleaner', '~> 1.4.0' gem 'database_cleaner', '~> 1.4.0'
gem 'factory_girl_rails', '~> 4.3.0' gem 'factory_girl_rails', '~> 4.6.0'
gem 'rspec-rails', '~> 3.3.0' gem 'rspec-rails', '~> 3.3.0'
gem 'spinach-rails', '~> 0.2.1' gem 'spinach-rails', '~> 0.2.1'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826) # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.7.0' gem 'minitest', '~> 5.7.0'
...@@ -278,7 +278,7 @@ group :development, :test do ...@@ -278,7 +278,7 @@ group :development, :test do
gem 'teaspoon', '~> 1.0.0' gem 'teaspoon', '~> 1.0.0'
gem 'teaspoon-jasmine', '~> 2.2.0' gem 'teaspoon-jasmine', '~> 2.2.0'
gem 'spring', '~> 1.3.6' gem 'spring', '~> 1.6.4'
gem 'spring-commands-rspec', '~> 1.0.4' gem 'spring-commands-rspec', '~> 1.0.4'
gem 'spring-commands-spinach', '~> 1.0.0' gem 'spring-commands-spinach', '~> 1.0.0'
gem 'spring-commands-teaspoon', '~> 0.0.2' gem 'spring-commands-teaspoon', '~> 0.0.2'
......
...@@ -117,10 +117,11 @@ GEM ...@@ -117,10 +117,11 @@ GEM
capybara-screenshot (1.0.11) capybara-screenshot (1.0.11)
capybara (>= 1.0, < 3) capybara (>= 1.0, < 3)
launchy launchy
carrierwave (0.9.0) carrierwave (0.10.0)
activemodel (>= 3.2.0) activemodel (>= 3.2.0)
activesupport (>= 3.2.0) activesupport (>= 3.2.0)
json (>= 1.7) json (>= 1.7)
mime-types (>= 1.16)
cause (0.1) cause (0.1)
charlock_holmes (0.7.3) charlock_holmes (0.7.3)
chunky_png (1.3.5) chunky_png (1.3.5)
...@@ -194,10 +195,10 @@ GEM ...@@ -194,10 +195,10 @@ GEM
excon (0.45.4) excon (0.45.4)
execjs (2.6.0) execjs (2.6.0)
expression_parser (0.9.0) expression_parser (0.9.0)
factory_girl (4.3.0) factory_girl (4.5.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
factory_girl_rails (4.3.0) factory_girl_rails (4.6.0)
factory_girl (~> 4.3.0) factory_girl (~> 4.5.0)
railties (>= 3.0.0) railties (>= 3.0.0)
faraday (0.9.2) faraday (0.9.2)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
...@@ -357,7 +358,7 @@ GEM ...@@ -357,7 +358,7 @@ GEM
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab_emoji (0.3.1) gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1) gemojione (~> 2.2, >= 2.2.1)
gitlab_git (8.2.0) gitlab_git (9.0.0)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
...@@ -407,7 +408,6 @@ GEM ...@@ -407,7 +408,6 @@ GEM
railties (>= 4.0.1) railties (>= 4.0.1)
hashie (3.4.3) hashie (3.4.3)
highline (1.7.8) highline (1.7.8)
hike (1.2.3)
hipchat (1.5.2) hipchat (1.5.2)
httparty httparty
mimemagic mimemagic
...@@ -764,18 +764,15 @@ GEM ...@@ -764,18 +764,15 @@ GEM
capybara (>= 2.0.0) capybara (>= 2.0.0)
railties (>= 3) railties (>= 3)
spinach (>= 0.4) spinach (>= 0.4)
spring (1.3.6) spring (1.6.4)
spring-commands-rspec (1.0.4) spring-commands-rspec (1.0.4)
spring (>= 0.9.1) spring (>= 0.9.1)
spring-commands-spinach (1.0.0) spring-commands-spinach (1.0.0)
spring (>= 0.9.1) spring (>= 0.9.1)
spring-commands-teaspoon (0.0.2) spring-commands-teaspoon (0.0.2)
spring (>= 0.9.1) spring (>= 0.9.1)
sprockets (2.12.4) sprockets (3.3.5)
hike (~> 1.2) rack (> 1, < 3)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sprockets-rails (2.3.3) sprockets-rails (2.3.3)
actionpack (>= 3.0) actionpack (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
...@@ -807,7 +804,7 @@ GEM ...@@ -807,7 +804,7 @@ GEM
rack (~> 1.0) rack (~> 1.0)
thor (0.19.1) thor (0.19.1)
thread_safe (0.3.5) thread_safe (0.3.5)
tilt (1.4.1) tilt (2.0.2)
timfel-krb5-auth (0.8.3) timfel-krb5-auth (0.8.3)
tinder (1.10.1) tinder (1.10.1)
eventmachine (~> 1.0) eventmachine (~> 1.0)
...@@ -902,7 +899,7 @@ DEPENDENCIES ...@@ -902,7 +899,7 @@ DEPENDENCIES
cal-heatmap-rails (~> 3.5.0) cal-heatmap-rails (~> 3.5.0)
capybara (~> 2.4.0) capybara (~> 2.4.0)
capybara-screenshot (~> 1.0.0) capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.9.0) carrierwave (~> 0.10.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
coffee-rails (~> 4.1.0) coffee-rails (~> 4.1.0)
colorize (~> 0.7.0) colorize (~> 0.7.0)
...@@ -920,7 +917,7 @@ DEPENDENCIES ...@@ -920,7 +917,7 @@ DEPENDENCIES
dropzonejs-rails (~> 0.7.1) dropzonejs-rails (~> 0.7.1)
email_reply_parser (~> 0.5.8) email_reply_parser (~> 0.5.8)
email_spec (~> 1.6.0) email_spec (~> 1.6.0)
factory_girl_rails (~> 4.3.0) factory_girl_rails (~> 4.6.0)
ffaker (~> 2.0.0) ffaker (~> 2.0.0)
flay flay
flog flog
...@@ -933,7 +930,7 @@ DEPENDENCIES ...@@ -933,7 +930,7 @@ DEPENDENCIES
github-markup (~> 1.3.1) github-markup (~> 1.3.1)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab_emoji (~> 0.3.0) gitlab_emoji (~> 0.3.0)
gitlab_git (~> 8.2) gitlab_git (~> 9.0)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.1.0) gollum-lib (~> 4.1.0)
...@@ -1020,11 +1017,11 @@ DEPENDENCIES ...@@ -1020,11 +1017,11 @@ DEPENDENCIES
six (~> 0.2.0) six (~> 0.2.0)
slack-notifier (~> 1.2.0) slack-notifier (~> 1.2.0)
spinach-rails (~> 0.2.1) spinach-rails (~> 0.2.1)
spring (~> 1.3.6) spring (~> 1.6.4)
spring-commands-rspec (~> 1.0.4) spring-commands-rspec (~> 1.0.4)
spring-commands-spinach (~> 1.0.0) spring-commands-spinach (~> 1.0.0)
spring-commands-teaspoon (~> 0.0.2) spring-commands-teaspoon (~> 0.0.2)
sprockets (~> 2.12.3) sprockets (~> 3.3.5)
state_machines-activerecord (~> 0.3.0) state_machines-activerecord (~> 0.3.0)
task_list (~> 1.0.2) task_list (~> 1.0.2)
teaspoon (~> 1.0.0) teaspoon (~> 1.0.0)
......
This diff is collapsed.
...@@ -4,4 +4,7 @@ ...@@ -4,4 +4,7 @@
require File.expand_path('../config/application', __FILE__) require File.expand_path('../config/application', __FILE__)
relative_url_conf = File.expand_path('../config/initializers/relative_url', __FILE__)
require relative_url_conf if File.exist?("#{relative_url_conf}.rb")
Gitlab::Application.load_tasks Gitlab::Application.load_tasks
...@@ -220,41 +220,41 @@ $ -> ...@@ -220,41 +220,41 @@ $ ->
.off 'breakpoint:change' .off 'breakpoint:change'
.on 'breakpoint:change', (e, breakpoint) -> .on 'breakpoint:change', (e, breakpoint) ->
if breakpoint is 'sm' or breakpoint is 'xs' if breakpoint is 'sm' or breakpoint is 'xs'
$gutterIcon = $('.gutter-toggle').find('i') $gutterIcon = $('aside .gutter-toggle').find('i')
if $gutterIcon.hasClass('fa-angle-double-right') if $gutterIcon.hasClass('fa-angle-double-right')
$gutterIcon.closest('a').trigger('click') $gutterIcon.closest('a').trigger('click')
$(document) $(document)
.off 'click', 'aside .gutter-toggle' .off 'click', 'aside .gutter-toggle'
.on 'click', 'aside .gutter-toggle', (e) -> .on 'click', 'aside .gutter-toggle', (e, triggered) ->
e.preventDefault() e.preventDefault()
$this = $(this) $this = $(this)
$thisIcon = $this.find 'i' $thisIcon = $this.find 'i'
$allGutterToggleIcons = $('.gutter-toggle i')
if $thisIcon.hasClass('fa-angle-double-right') if $thisIcon.hasClass('fa-angle-double-right')
$thisIcon $allGutterToggleIcons
.removeClass('fa-angle-double-right') .removeClass('fa-angle-double-right')
.addClass('fa-angle-double-left') .addClass('fa-angle-double-left')
$this $('aside.right-sidebar')
.closest('aside')
.removeClass('right-sidebar-expanded') .removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed') .addClass('right-sidebar-collapsed')
$('.page-with-sidebar') $('.page-with-sidebar')
.removeClass('right-sidebar-expanded') .removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed') .addClass('right-sidebar-collapsed')
else else
$thisIcon $allGutterToggleIcons
.removeClass('fa-angle-double-left') .removeClass('fa-angle-double-left')
.addClass('fa-angle-double-right') .addClass('fa-angle-double-right')
$this $('aside.right-sidebar')
.closest('aside')
.removeClass('right-sidebar-collapsed') .removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded') .addClass('right-sidebar-expanded')
$('.page-with-sidebar') $('.page-with-sidebar')
.removeClass('right-sidebar-collapsed') .removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded') .addClass('right-sidebar-expanded')
$.cookie("collapsed_gutter", if not triggered
$('.right-sidebar') $.cookie("collapsed_gutter",
.hasClass('right-sidebar-collapsed'), { path: '/' }) $('.right-sidebar')
.hasClass('right-sidebar-collapsed'), { path: '/' })
bootstrapBreakpoint = undefined; bootstrapBreakpoint = undefined;
checkBootstrapBreakpoints = -> checkBootstrapBreakpoints = ->
......
# Quick Submit behavior # Quick Submit behavior
# #
# When an input field with the `js-quick-submit` class receives a "Meta+Enter" # When a child field of a form with a `js-quick-submit` class receives a
# (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, its parent form is # "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form
# submitted. # is submitted.
# #
#= require extensions/jquery #= require extensions/jquery
# #
# ### Example Markup # ### Example Markup
# #
# <form action="/foo"> # <form action="/foo" class="js-quick-submit">
# <input type="text" class="js-quick-submit" /> # <input type="text" />
# <textarea class="js-quick-submit"></textarea> # <textarea></textarea>
# <input type="submit" value="Submit" />
# </form> # </form>
# #
isMac = ->
navigator.userAgent.match(/Macintosh/)
keyCodeIs = (e, keyCode) ->
return false if (e.originalEvent && e.originalEvent.repeat) || e.repeat
return e.keyCode == keyCode
$(document).on 'keydown.quick_submit', '.js-quick-submit', (e) -> $(document).on 'keydown.quick_submit', '.js-quick-submit', (e) ->
return if (e.originalEvent && e.originalEvent.repeat) || e.repeat return unless keyCodeIs(e, 13) # Enter
return unless e.keyCode == 13 # Enter
if navigator.userAgent.match(/Macintosh/) return unless (e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey) || (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey)
return unless (e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey)
else
return unless (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey)
e.preventDefault() e.preventDefault()
$form = $(e.target).closest('form') $form = $(e.target).closest('form')
$form.find('input[type=submit], button[type=submit]').disable() $form.find('input[type=submit], button[type=submit]').disable()
$form.submit() $form.submit()
# If the user tabs to a submit button on a `js-quick-submit` form, display a
# tooltip to let them know they could've used the hotkey
$(document).on 'keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', (e) ->
return unless keyCodeIs(e, 9) # Tab
if isMac()
title = "You can also press &#8984;-Enter"
else
title = "You can also press Ctrl-Enter"
$this = $(@)
$this.tooltip(
container: 'body'
html: 'true'
placement: 'auto top'
title: title
trigger: 'manual'
).tooltip('show').one('blur', -> $this.tooltip('hide'))
@Dashboard =
init: ->
$(".projects-list-filter").off('keyup')
this.initSearch()
initSearch: ->
@timer = null
$(".projects-list-filter").on('keyup', ->
clearTimeout(@timer)
@timer = setTimeout(Dashboard.filterResults, 500)
)
filterResults: =>
$('.projects-list-holder').fadeTo(250, 0.5)
form = null
form = $("form#project-filter-form")
search = $(".projects-list-filter").val()
project_filter_url = form.attr('action') + '?' + form.serialize()
$.ajax
type: "GET"
url: form.attr('action')
data: form.serialize()
complete: ->
$('.projects-list-holder').fadeTo(250, 1)
success: (data) ->
$('.projects-list-holder').replaceWith(data.html)
# Change url so if user reload a page - search results are saved
history.replaceState {page: project_filter_url}, document.title, project_filter_url
dataType: "json"
...@@ -16,8 +16,6 @@ class Dispatcher ...@@ -16,8 +16,6 @@ class Dispatcher
shortcut_handler = null shortcut_handler = null
switch page switch page
when 'explore:projects:index', 'explore:projects:starred', 'explore:projects:trending'
Dashboard.init()
when 'projects:issues:index' when 'projects:issues:index'
Issues.init() Issues.init()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
...@@ -25,7 +23,7 @@ class Dispatcher ...@@ -25,7 +23,7 @@ class Dispatcher
new Issue() new Issue()
shortcut_handler = new ShortcutsIssuable() shortcut_handler = new ShortcutsIssuable()
new ZenMode() new ZenMode()
when 'projects:milestones:show' when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
new Milestone() new Milestone()
when 'projects:milestones:new', 'projects:milestones:edit' when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode() new ZenMode()
...@@ -59,8 +57,6 @@ class Dispatcher ...@@ -59,8 +57,6 @@ class Dispatcher
when 'projects:merge_requests:index' when 'projects:merge_requests:index'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
MergeRequests.init() MergeRequests.init()
when 'dashboard:show', 'root:show'
Dashboard.init()
when 'dashboard:activity' when 'dashboard:activity'
new Activities() new Activities()
when 'dashboard:projects:starred' when 'dashboard:projects:starred'
...@@ -107,9 +103,6 @@ class Dispatcher ...@@ -107,9 +103,6 @@ class Dispatcher
new ProjectFork() new ProjectFork()
when 'projects:artifacts:browse' when 'projects:artifacts:browse'
new BuildArtifacts() new BuildArtifacts()
when 'users:show'
new User()
new Activities()
switch path.first() switch path.first()
when 'admin' when 'admin'
......
...@@ -195,6 +195,6 @@ class @MergeRequestTabs ...@@ -195,6 +195,6 @@ class @MergeRequestTabs
setTimeout( -> setTimeout( ->
# Only when sidebar is collapsed # Only when sidebar is collapsed
if $gutterIcon.is('.fa-angle-double-right') if $gutterIcon.is('.fa-angle-double-right')
$gutterIcon.closest('a').trigger('click') $gutterIcon.closest('a').trigger('click',[true])
, 0) , 0)
...@@ -69,7 +69,7 @@ class @Milestone ...@@ -69,7 +69,7 @@ class @Milestone
@bindIssuesSorting() @bindIssuesSorting()
@bindMergeRequestSorting() @bindMergeRequestSorting()
@bindTabsSwitching @bindTabsSwitching()
bindIssuesSorting: -> bindIssuesSorting: ->
$("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable( $("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable(
...@@ -104,7 +104,7 @@ class @Milestone ...@@ -104,7 +104,7 @@ class @Milestone
).disableSelection() ).disableSelection()
bindMergeRequestSorting: -> bindTabsSwitching: ->
$('a[data-toggle="tab"]').on 'show.bs.tab', (e) -> $('a[data-toggle="tab"]').on 'show.bs.tab', (e) ->
currentTabClass = $(e.target).data('show') currentTabClass = $(e.target).data('show')
previousTabClass = $(e.relatedTarget).data('show') previousTabClass = $(e.relatedTarget).data('show')
...@@ -112,7 +112,8 @@ class @Milestone ...@@ -112,7 +112,8 @@ class @Milestone
$(previousTabClass).hide() $(previousTabClass).hide()
$(currentTabClass).removeClass('hidden') $(currentTabClass).removeClass('hidden')
$(currentTabClass).show() $(currentTabClass).show()
bindMergeRequestSorting: ->
$("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable( $("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable(
connectWith: ".merge_requests-sortable-list", connectWith: ".merge_requests-sortable-list",
dropOnEmpty: true, dropOnEmpty: true,
......
...@@ -16,11 +16,13 @@ class @Notes ...@@ -16,11 +16,13 @@ class @Notes
@view = view @view = view
@noteable_url = document.URL @noteable_url = document.URL
@notesCountBadge ||= $(".issuable-details").find(".notes-tab .badge") @notesCountBadge ||= $(".issuable-details").find(".notes-tab .badge")
@basePollingInterval = 15000
@maxPollingSteps = 4
@initRefresh()
@setupMainTargetNoteForm()
@cleanBinding() @cleanBinding()
@addBinding() @addBinding()
@setPollingInterval()
@setupMainTargetNoteForm()
@initTaskList() @initTaskList()
addBinding: -> addBinding: ->
...@@ -37,7 +39,7 @@ class @Notes ...@@ -37,7 +39,7 @@ class @Notes
# Reopen and close actions for Issue/MR combined with note form submit # Reopen and close actions for Issue/MR combined with note form submit
$(document).on "click", ".js-comment-button", @updateCloseButton $(document).on "click", ".js-comment-button", @updateCloseButton
$(document).on "keyup", ".js-note-text", @updateTargetButtons $(document).on "keyup input", ".js-note-text", @updateTargetButtons
# remove a note (in general) # remove a note (in general)
$(document).on "click", ".js-note-delete", @removeNote $(document).on "click", ".js-note-delete", @removeNote
...@@ -91,9 +93,11 @@ class @Notes ...@@ -91,9 +93,11 @@ class @Notes
clearInterval(Notes.interval) clearInterval(Notes.interval)
Notes.interval = setInterval => Notes.interval = setInterval =>
@refresh() @refresh()
, 15000 , @pollingInterval
refresh: -> refresh: ->
return if @refreshing is true
refreshing = true
if not document.hidden and document.URL.indexOf(@noteable_url) is 0 if not document.hidden and document.URL.indexOf(@noteable_url) is 0
@getContent() @getContent()
...@@ -105,12 +109,31 @@ class @Notes ...@@ -105,12 +109,31 @@ class @Notes
success: (data) => success: (data) =>
notes = data.notes notes = data.notes
@last_fetched_at = data.last_fetched_at @last_fetched_at = data.last_fetched_at
@setPollingInterval(data.notes.length)
$.each notes, (i, note) => $.each notes, (i, note) =>
if note.discussion_with_diff_html? if note.discussion_with_diff_html?
@renderDiscussionNote(note) @renderDiscussionNote(note)
else else
@renderNote(note) @renderNote(note)
always: =>
@refreshing = false
###
Increase @pollingInterval up to 120 seconds on every function call,
if `shouldReset` has a truthy value, 'null' or 'undefined' the variable
will reset to @basePollingInterval.
Note: this function is used to gradually increase the polling interval
if there aren't new notes coming from the server
###
setPollingInterval: (shouldReset = true) ->
nthInterval = @basePollingInterval * Math.pow(2, @maxPollingSteps - 1)
if shouldReset
@pollingInterval = @basePollingInterval
else if @pollingInterval < nthInterval
@pollingInterval *= 2
@initRefresh()
### ###
Render note in main comments area. Render note in main comments area.
......
@Pager = @Pager =
init: (@limit = 0, preload, @disable = false) -> init: (@limit = 0, preload, @disable = false) ->
@loading = $(".loading") @loading = $('.loading').first()
if preload if preload
@offset = 0 @offset = 0
@getOld() @getOld()
......
...@@ -48,7 +48,7 @@ class @Profile ...@@ -48,7 +48,7 @@ class @Profile
$filename.text($filename.data('label')) $filename.text($filename.data('label'))
$('.js-upload-user-avatar').on 'click', -> $('.js-upload-user-avatar').on 'click', ->
$('.edit_user').submit() $('.edit-user').submit()
$avatarInput.on "change", -> $avatarInput.on "change", ->
form = $(this).closest("form") form = $(this).closest("form")
...@@ -63,3 +63,11 @@ class @Profile ...@@ -63,3 +63,11 @@ class @Profile
fileData = reader.readAsDataURL(this.files[0]) fileData = reader.readAsDataURL(this.files[0])
$ ->
# Extract the SSH Key title from its comment
$(document).on 'focusout.ssh_key', '#key_key', ->
$title = $('#key_title')
comment = $(@).val().match(/^\S+ \S+ (.+)\n?$/)
if comment && comment.length > 1 && $title.val() == ''
$title.val(comment[1]).change()
class @ProjectsList @ProjectsList =
constructor: -> init: ->
$(".projects-list .js-expand").on 'click', (e) -> $(".projects-list-filter").off('keyup')
e.preventDefault() this.initSearch()
list = $(this).closest('.projects-list')
$("#filter_projects").on 'keyup', -> initSearch: ->
ProjectsList.filter_results($("#filter_projects")) @timer = null
$(".projects-list-filter").on('keyup', ->
clearTimeout(@timer)
@timer = setTimeout(ProjectsList.filterResults, 500)
)
@filter_results: ($element) -> filterResults: =>
terms = $element.val() $('.projects-list-holder').fadeTo(250, 0.5)
filterSelector = $element.data('filter-selector') || 'span.filter-title'
if not terms form = null
$(".projects-list li").show() form = $("form#project-filter-form")
$('.gl-pagination').show() search = $(".projects-list-filter").val()
else project_filter_url = form.attr('action') + '?' + form.serialize()
$(".projects-list li").each (index) ->
$this = $(this)
name = $this.find(filterSelector).text()
if name.toLowerCase().indexOf(terms.toLowerCase()) == -1 $.ajax
$this.hide() type: "GET"
else url: form.attr('action')
$this.show() data: form.serialize()
$('.gl-pagination').hide() complete: ->
$('.projects-list-holder').fadeTo(250, 1)
success: (data) ->
$('.projects-list-holder').replaceWith(data.html)
# Change url so if user reload a page - search results are saved
history.replaceState {page: project_filter_url}, document.title, project_filter_url
dataType: "json"
...@@ -13,8 +13,10 @@ class @Shortcuts ...@@ -13,8 +13,10 @@ class @Shortcuts
if $('#modal-shortcuts').length > 0 if $('#modal-shortcuts').length > 0
$('#modal-shortcuts').modal('show') $('#modal-shortcuts').modal('show')
else else
url = '/help/shortcuts'
url = gon.relative_url_root + url if gon.relative_url_root?
$.ajax( $.ajax(
url: '/help/shortcuts', url: url,
dataType: 'script', dataType: 'script',
success: (e) -> success: (e) ->
if location and location.length > 0 if location and location.length > 0
......
class @User class @User
constructor: -> constructor: (@opts) ->
$('.profile-groups-avatars').tooltip("placement": "top") $('.profile-groups-avatars').tooltip("placement": "top")
new ProjectsList()
@initTabs()
$('.hide-project-limit-message').on 'click', (e) -> $('.hide-project-limit-message').on 'click', (e) ->
path = '/' path = '/'
$.cookie('hide_project_limit_message', 'false', { path: path }) $.cookie('hide_project_limit_message', 'false', { path: path })
$(@).parents('.project-limit-message').remove() $(@).parents('.project-limit-message').remove()
e.preventDefault() e.preventDefault()
initTabs: ->
new UserTabs(
parentEl: '.user-profile'
action: @opts.action
)
# UserTabs
#
# Handles persisting and restoring the current tab selection and lazily-loading
# content on the Users#show page.
#
# ### Example Markup
#
# <ul class="nav-links">
# <li class="activity-tab active">
# <a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username">
# Activity
# </a>
# </li>
# <li class="groups-tab">
# <a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups">
# Groups
# </a>
# </li>
# <li class="contributed-tab">
# <a data-action="contributed" data-target="#contributed" data-toggle="tab" href="/u/username/contributed">
# Contributed projects
# </a>
# </li>
# <li class="projects-tab">
# <a data-action="projects" data-target="#projects" data-toggle="tab" href="/u/username/projects">
# Personal projects
# </a>
# </li>
# </ul>
#
# <div class="tab-content">
# <div class="tab-pane" id="activity">
# Activity Content
# </div>
# <div class="tab-pane" id="groups">
# Groups Content
# </div>
# <div class="tab-pane" id="contributed">
# Contributed projects content
# </div>
# <div class="tab-pane" id="projects">
# Projects content
# </div>
# </div>
#
# <div class="loading-status">
# <div class="loading">
# Loading Animation
# </div>
# </div>
#
class @UserTabs
constructor: (opts) ->
{
@action = 'activity'
@defaultAction = 'activity'
@parentEl = $(document)
} = opts
# Make jQuery object if selector is provided
@parentEl = $(@parentEl) if typeof @parentEl is 'string'
# Store the `location` object, allowing for easier stubbing in tests
@_location = location
# Set tab states
@loaded = {}
for item in @parentEl.find('.nav-links a')
@loaded[$(item).attr 'data-action'] = false
# Actions
@actions = Object.keys @loaded
@bindEvents()
# Set active tab
@action = @defaultAction if @action is 'show'
@activateTab(@action)
bindEvents: ->
# Toggle event listeners
@parentEl
.off 'shown.bs.tab', '.nav-links a[data-toggle="tab"]'
.on 'shown.bs.tab', '.nav-links a[data-toggle="tab"]', @tabShown
tabShown: (event) =>
$target = $(event.target)
action = $target.data('action')
source = $target.attr('href')
@setTab(source, action)
@setCurrentAction(action)
activateTab: (action) ->
@parentEl.find(".nav-links .#{action}-tab a").tab('show')
setTab: (source, action) ->
return if @loaded[action] is true
if action is 'activity'
@loadActivities(source)
if action in ['groups', 'contributed', 'projects']
@loadTab(source, action)
loadTab: (source, action) ->
$.ajax
beforeSend: => @toggleLoading(true)
complete: => @toggleLoading(false)
dataType: 'json'
type: 'GET'
url: "#{source}.json"
success: (data) =>
tabSelector = 'div#' + action
@parentEl.find(tabSelector).html(data.html)
@loaded[action] = true
loadActivities: (source) ->
return if @loaded['activity'] is true
$calendarWrap = @parentEl.find('.user-calendar')
$calendarWrap.load($calendarWrap.data('href'))
new Activities()
@loaded['activity'] = true
toggleLoading: (status) ->
@parentEl.find('.loading-status .loading').toggle(status)
setCurrentAction: (action) ->
# Remove possible actions from URL
regExp = new RegExp('\/(' + @actions.join('|') + ')(\.html)?\/?$')
new_state = @_location.pathname
new_state = new_state.replace(/\/+$/, "") # remove trailing slashes
new_state = new_state.replace(regExp, '')
# Append the new action if we're on a tab other than 'activity'
unless action == @defaultAction
new_state += "/#{action}"
# Ensure parameters and hash come along for the ride
new_state += @_location.search + @_location.hash
history.replaceState {turbolinks: true, url: new_state}, document.title, new_state
new_state
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
@import "framework/calendar.scss"; @import "framework/calendar.scss";
@import "framework/callout.scss"; @import "framework/callout.scss";
@import "framework/common.scss"; @import "framework/common.scss";
@import "framework/dropdowns.scss";
@import "framework/files.scss"; @import "framework/files.scss";
@import "framework/filters.scss"; @import "framework/filters.scss";
@import "framework/flash.scss"; @import "framework/flash.scss";
......
...@@ -143,6 +143,19 @@ ...@@ -143,6 +143,19 @@
} }
} }
.btn-transparent {
color: $btn-transparent-color;
background-color: transparent;
border: 0;
&:hover,
&:active,
&:focus {
background-color: transparent;
box-shadow: none;
}
}
.btn-block { .btn-block {
width: 100%; width: 100%;
margin: 0; margin: 0;
......
...@@ -6,11 +6,15 @@ ...@@ -6,11 +6,15 @@
.cdark { color: #444 } .cdark { color: #444 }
/** COMMON CLASSES **/ /** COMMON CLASSES **/
.prepend-top-0 { margin-top: 0; }
.prepend-top-5 { margin-top: 5px; }
.prepend-top-10 { margin-top:10px } .prepend-top-10 { margin-top:10px }
.prepend-top-default { margin-top: $gl-padding !important; } .prepend-top-default { margin-top: $gl-padding !important; }
.prepend-top-20 { margin-top:20px } .prepend-top-20 { margin-top:20px }
.prepend-left-10 { margin-left:10px } .prepend-left-10 { margin-left:10px }
.prepend-left-default { margin-left:$gl-padding }
.prepend-left-20 { margin-left:20px } .prepend-left-20 { margin-left:20px }
.append-right-5 { margin-right: 5px }
.append-right-10 { margin-right:10px } .append-right-10 { margin-right:10px }
.append-right-20 { margin-right:20px } .append-right-20 { margin-right:20px }
.append-bottom-10 { margin-bottom:10px } .append-bottom-10 { margin-bottom:10px }
...@@ -56,25 +60,6 @@ hr { ...@@ -56,25 +60,6 @@ hr {
margin: $gl-padding 0; margin: $gl-padding 0;
} }
.dropdown-menu {
margin: 6px 0 0;
}
.dropdown-menu > li > a {
text-shadow: none;
}
.dropdown-menu-align-right {
left: auto;
right: 0px;
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background: $gl-primary;
color: #FFF;
}
.str-truncated { .str-truncated {
@include str-truncated; @include str-truncated;
} }
...@@ -305,7 +290,7 @@ table { ...@@ -305,7 +290,7 @@ table {
} }
.btn-sign-in { .btn-sign-in {
margin-top: 8px; margin-top: 10px;
text-shadow: none; text-shadow: none;
} }
......
.caret {
display: inline-block;
width: 0;
height: 0;
margin-left: 2px;
vertical-align: middle;
border-top: $caret-width-base dashed $dropdown-caret-color;
border-right: $caret-width-base solid transparent;
border-left: $caret-width-base solid transparent;
}
.dropdown {
position: relative;
}
.open {
.dropdown-menu {
display: block;
}
}
.dropdown-menu {
display: none;
position: absolute;
top: 100%;
left: 0;
z-index: 9999;
width: 240px;
margin-top: 2px;
margin-bottom: 0;
padding: 10px 10px;
font-size: 14px;
font-weight: normal;
background-color: $dropdown-bg;
border: 1px solid $dropdown-border-color;
border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color;
li {
text-align: left;
list-style: none;
}
.divider {
width: 100%;
height: 1px;
margin-top: 8px;
margin-bottom: 8px;
background-color: $dropdown-divider-color;
}
a {
display: block;
position: relative;
padding-left: 10px;
padding-right: 10px;
color: $dropdown-link-color;
line-height: 34px;
text-overflow: ellipsis;
border-radius: 2px;
white-space: nowrap;
overflow: hidden;
&:hover {
background-color: $dropdown-link-hover-bg;
text-decoration: none;
}
}
}
.dropdown-menu-align-right {
left: auto;
right: 0;
}
.dropdown-menu-selectable {
a {
padding-left: 25px;
&.is-active {
&::before {
content: "\f00c";
position: absolute;
left: 4px;
top: 8px;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
}
}
.dropdown-header {
padding-left: 10px;
padding-right: 10px;
color: $dropdown-header-color;
font-size: 13px;
line-height: 22px;
}
...@@ -28,15 +28,15 @@ input[type='search'].search-input { ...@@ -28,15 +28,15 @@ input[type='search'].search-input {
} }
&.search-input:-moz-placeholder { /* Firefox 18- */ &.search-input:-moz-placeholder { /* Firefox 18- */
text-align: center; text-align: center;
} }
&.search-input::-moz-placeholder { /* Firefox 19+ */ &.search-input::-moz-placeholder { /* Firefox 19+ */
text-align: center; text-align: center;
} }
&.search-input:-ms-input-placeholder { &.search-input:-ms-input-placeholder {
text-align: center; text-align: center;
} }
} }
...@@ -69,6 +69,10 @@ label { ...@@ -69,6 +69,10 @@ label {
&.inline-label { &.inline-label {
margin: 0; margin: 0;
} }
&.label-light {
font-weight: 600;
}
} }
.inline-input-group { .inline-input-group {
......
...@@ -5,11 +5,20 @@ ...@@ -5,11 +5,20 @@
*/ */
.status-box { .status-box {
/* Extra small devices (phones, less than 768px) */
/* No media query since this is the default in Bootstrap */
padding: 5px 11px;
margin-top: 4px;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
padding: 0 $gl-btn-padding;
margin-top: 5px;
}
@include border-radius(3px); @include border-radius(3px);
display: block; display: block;
float: left; float: left;
padding: 0 $gl-btn-padding;
margin-top: 5px;
margin-right: 10px; margin-right: 10px;
color: #FFF; color: #FFF;
font-size: $gl-font-size; font-size: $gl-font-size;
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
* *
*/ */
.well-list { .well-list {
position: relative;
margin: 0; margin: 0;
padding: 0; padding: 0;
list-style: none; list-style: none;
......
...@@ -79,6 +79,10 @@ ...@@ -79,6 +79,10 @@
> .dropdown { > .dropdown {
margin-right: $gl-padding-top; margin-right: $gl-padding-top;
display: inline-block; display: inline-block;
&:last-child {
margin-right: 0;
}
} }
> .btn { > .btn {
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
transition-duration: .3s; transition-duration: .3s;
} }
.home { .gitlab-text-container-link {
z-index: 1; z-index: 1;
position: absolute; position: absolute;
left: 0px; left: 0px;
...@@ -27,7 +27,14 @@ ...@@ -27,7 +27,14 @@
} }
&.right-sidebar-expanded { &.right-sidebar-expanded {
padding-right: $gutter_width; /* Extra small devices (phones, less than 768px) */
/* No media query since this is the default in Bootstrap */
padding-right: 0;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
padding-right: $gutter_width;
}
} }
} }
...@@ -199,7 +206,12 @@ ...@@ -199,7 +206,12 @@
padding-left: $sidebar_width; padding-left: $sidebar_width;
&.right-sidebar-collapsed { &.right-sidebar-collapsed {
padding-right: $sidebar_collapsed_width; /* Extra small devices (phones, less than 768px) */
padding-right: 0;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
padding-right: $sidebar_collapsed_width;
}
} }
.sidebar-wrapper { .sidebar-wrapper {
...@@ -225,7 +237,12 @@ ...@@ -225,7 +237,12 @@
padding-left: $sidebar_collapsed_width; padding-left: $sidebar_collapsed_width;
&.right-sidebar-collapsed { &.right-sidebar-collapsed {
padding-right: $sidebar_collapsed_width; /* Extra small devices (phones, less than 768px) */
padding-right: 0;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
padding-right: $sidebar_collapsed_width;
}
} }
.sidebar-wrapper { .sidebar-wrapper {
...@@ -292,7 +309,13 @@ ...@@ -292,7 +309,13 @@
} }
.page-sidebar-collapsed { .page-sidebar-collapsed {
/* Extra small devices (phones, less than 768px) */
@include collapsed-sidebar; @include collapsed-sidebar;
padding-right: 0;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
@include collapsed-sidebar;
}
} }
.page-sidebar-expanded { .page-sidebar-expanded {
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
// Components // Components
@import "bootstrap/component-animations"; @import "bootstrap/component-animations";
@import "bootstrap/dropdowns"; // @import "bootstrap/dropdowns";
@import "bootstrap/button-groups"; @import "bootstrap/button-groups";
@import "bootstrap/input-groups"; @import "bootstrap/input-groups";
@import "bootstrap/navs"; @import "bootstrap/navs";
...@@ -167,12 +167,6 @@ ...@@ -167,12 +167,6 @@
} }
} }
.alert-help {
background-color: $background-color;
border: 1px solid $border-color;
color: $gl-gray;
}
// Typography ================================================================= // Typography =================================================================
.text-primary, .text-primary,
......
...@@ -196,7 +196,7 @@ body { ...@@ -196,7 +196,7 @@ body {
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
color: $gl-header-color; color: $gl-header-color;
font-weight: 500; font-weight: 600;
} }
/** CODE **/ /** CODE **/
......
...@@ -34,6 +34,12 @@ $error-exclamation-point: #E62958; ...@@ -34,6 +34,12 @@ $error-exclamation-point: #E62958;
$border-radius-default: 3px; $border-radius-default: 3px;
$list-title-color: #333333; $list-title-color: #333333;
$list-text-color: #555555; $list-text-color: #555555;
$profile-settings-link-color: $md-link-color;
$btn-transparent-color: #8F8F8F;
$ssh-key-icon-color: #8F8F8F;
$ssh-key-icon-size: 18px;
/* /*
* Color schema * Color schema
...@@ -92,6 +98,9 @@ $border-red-light: #E52C5A; ...@@ -92,6 +98,9 @@ $border-red-light: #E52C5A;
$border-red-normal: #D22852; $border-red-normal: #D22852;
$border-red-dark: #CA264F; $border-red-dark: #CA264F;
$help-well-bg: #FAFAFA;
$help-well-border: #E5E5E5;
/* header */ /* header */
$light-grey-header: #faf9f9; $light-grey-header: #faf9f9;
...@@ -117,3 +126,15 @@ $deleted: #f77; ...@@ -117,3 +126,15 @@ $deleted: #f77;
*/ */
$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace; $monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
$regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif; $regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif;
/*
* Dropdowns
*/
$dropdown-bg: #fff;
$dropdown-link-color: #555;
$dropdown-link-hover-bg: rgba(#000, .04);
$dropdown-border-color: rgba(#000, .1);
$dropdown-shadow-color: rgba(#000, .1);
$dropdown-divider-color: rgba(#000, .1);
$dropdown-header-color: #959494;
$dropdown-caret-color: #54565B;
...@@ -18,7 +18,8 @@ ...@@ -18,7 +18,8 @@
} }
.issue-meta { .issue-meta {
margin-left: 65px display: inline-block;
line-height: 20px;
} }
} }
......
...@@ -151,7 +151,6 @@ ...@@ -151,7 +151,6 @@
} }
} }
.right-sidebar { .right-sidebar {
position: fixed; position: fixed;
top: 58px; top: 58px;
...@@ -184,6 +183,13 @@ ...@@ -184,6 +183,13 @@
} }
&.right-sidebar-collapsed { &.right-sidebar-collapsed {
/* Extra small devices (phones, less than 768px) */
display: none;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
display: block
}
width: $sidebar_collapsed_width; width: $sidebar_collapsed_width;
padding-top: 0; padding-top: 0;
...@@ -247,6 +253,10 @@ ...@@ -247,6 +253,10 @@
} }
} }
.btn-default.gutter-toggle {
margin-top: 4px;
}
.detail-page-description { .detail-page-description {
small { small {
color: $gray-darkest; color: $gray-darkest;
......
...@@ -99,18 +99,17 @@ form.edit-issue { ...@@ -99,18 +99,17 @@ form.edit-issue {
.btn { .btn {
width: 100%; width: 100%;
margin-top: -1px;
&:first-child:not(:last-child) { &:first-child:not(:last-child) {
border-radius: 4px 4px 0 0;
} }
&:not(:first-child):not(:last-child) { &:not(:first-child):not(:last-child) {
border-radius: 0; margin-top: 10px;
} }
&:last-child:not(:first-child) { &:last-child:not(:first-child) {
border-radius: 0 0 4px 4px; margin-top: 10px;
} }
} }
} }
...@@ -134,3 +133,11 @@ form.edit-issue { ...@@ -134,3 +133,11 @@ form.edit-issue {
color: $secondary-text; color: $secondary-text;
margin-left: 52px; margin-left: 52px;
} }
.editor-details {
display: block;
@media (min-width: $screen-sm-min) {
display: inline-block;
}
}
\ No newline at end of file
...@@ -8,6 +8,10 @@ ...@@ -8,6 +8,10 @@
max-width: none; max-width: none;
} }
.flash-container {
margin-bottom: $gl-padding;
}
.brand-holder { .brand-holder {
font-size: 18px; font-size: 18px;
line-height: 1.5; line-height: 1.5;
......
...@@ -19,10 +19,11 @@ li.milestone { ...@@ -19,10 +19,11 @@ li.milestone {
width: 105px; width: 105px;
} }
.issue-row { .issuable-row {
.color-label { .color-label {
border-radius: 2px; border-radius: 2px;
padding: 3px !important; padding: 3px !important;
margin-right: 7px;
} }
// Issue title // Issue title
...@@ -39,25 +40,20 @@ li.milestone { ...@@ -39,25 +40,20 @@ li.milestone {
margin-right: 10px; margin-right: 10px;
} }
.time-elapsed { .remaining-days {
color: $orange-light; color: $orange-light;
} }
} }
.issues-sortable-list { .issues-sortable-list, .merge_requests-sortable-list {
.issue-detail { .issuable-detail {
display: block; display: block;
margin-top: 7px;
.issue-number{ .issuable-number {
color: rgba(0,0,0,0.44); color: rgba(0,0,0,0.44);
margin-right: 5px; margin-right: 5px;
} }
.color-label {
padding: 6px 10px;
margin-right: 7px;
margin-top: 10px;
}
.avatar { .avatar {
float: none; float: none;
} }
......
.global-notifications-form .level-title { .notification-list-item {
font-size: 15px; line-height: 34px;
color: #333;
font-weight: bold;
} }
.notification-icon-holder { .notification {
width: 20px; position: relative;
float: left; top: 1px;
> .fa {
font-size: 18px;
}
} }
.ns-part { .ns-part {
color: $gl-primary; color: $gl-text-green;
} }
.ns-watch { .ns-watch {
......
...@@ -5,12 +5,24 @@ ...@@ -5,12 +5,24 @@
} }
} }
.profile-avatar-form-option { .avatar-image {
hr { @media (min-width: $screen-sm-min) {
margin: 10px 0; float: left;
margin-bottom: 0;
} }
} }
.avatar-file-name {
position: relative;
top: 2px;
display: inline-block;
}
.account-btn-link,
.profile-settings-sidebar a {
color: $profile-settings-link-color;
}
.oauth-buttons { .oauth-buttons {
.btn-group { .btn-group {
margin-right: 10px; margin-right: 10px;
...@@ -42,6 +54,18 @@ ...@@ -42,6 +54,18 @@
} }
} }
.account-well {
padding: 10px 10px;
background-color: $help-well-bg;
border: 1px solid $help-well-border;
border-radius: $border-radius-base;
ul {
padding-left: 20px;
margin-bottom: 0;
}
}
.calendar-hint { .calendar-hint {
margin-top: -12px; margin-top: -12px;
float: right; float: right;
...@@ -79,6 +103,13 @@ ...@@ -79,6 +103,13 @@
margin: auto; margin: auto;
} }
.user-avatar-button {
.file-name {
display: inline-block;
padding-left: 10px;
}
}
.modal-profile-crop { .modal-profile-crop {
.modal-dialog { .modal-dialog {
width: 500px; width: 500px;
...@@ -114,3 +145,33 @@ ...@@ -114,3 +145,33 @@
width: auto; width: auto;
} }
} }
.key-list-item {
.key-list-item-info {
@media (min-width: $screen-sm-min) {
float: left;
}
}
.description {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
.key-icon {
color: $ssh-key-icon-color;
font-size: $ssh-key-icon-size;
line-height: 42px;
}
.key-created-at {
line-height: 42px;
}
.profile-settings-content {
a {
color: $profile-settings-link-color;
}
}
...@@ -49,10 +49,6 @@ ...@@ -49,10 +49,6 @@
} }
} }
.project-home-dropdown {
margin: 13px 0px 0;
}
.notifications-btn { .notifications-btn {
margin-top: -28px; margin-top: -28px;
...@@ -185,29 +181,6 @@ ...@@ -185,29 +181,6 @@
} }
} }
.dropdown-menu {
@include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px);
@include border-radius ($border-radius-default);
border: none;
padding: 10px 0;
font-size: 14px;
font-weight: 100;
li a {
color: #5f697a;
line-height: 30px;
&:hover {
background-color: #3084bb !important;
}
}
i {
margin-right: 8px;
}
}
.project-visibility-level-holder { .project-visibility-level-holder {
.radio { .radio {
margin-bottom: 10px; margin-bottom: 10px;
......
.search-results { .search-results {
.search-result-row { .search-result-row {
border-bottom: 1px solid #DDD; border-bottom: 1px solid $border-color;
padding-bottom: 15px; padding-bottom: $gl-padding;
margin-bottom: 15px; margin-bottom: $gl-padding;
&:last-child {
border-bottom: none;
}
} }
} }
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
.badge.todos-pending-count { .badge.todos-pending-count {
background-color: #7f8fa4; background-color: #7f8fa4;
margin-top: -5px; margin-top: -5px;
font-weight: normal;
} }
} }
} }
......
...@@ -3,6 +3,7 @@ module Ci ...@@ -3,6 +3,7 @@ module Ci
before_action :project before_action :project
before_action :authorize_read_project!, except: [:badge] before_action :authorize_read_project!, except: [:badge]
before_action :no_cache, only: [:badge] before_action :no_cache, only: [:badge]
skip_before_action :authenticate_user!, only: [:badge]
protect_from_forgery protect_from_forgery
def show def show
...@@ -18,6 +19,7 @@ module Ci ...@@ -18,6 +19,7 @@ module Ci
# #
def badge def badge
return render_404 unless @project return render_404 unless @project
image = Ci::ImageForBuildService.new.execute(@project, params) image = Ci::ImageForBuildService.new.execute(@project, params)
send_file image.path, filename: image.name, disposition: 'inline', type:"image/svg+xml" send_file image.path, filename: image.name, disposition: 'inline', type:"image/svg+xml"
end end
......
...@@ -2,7 +2,7 @@ module IssuesAction ...@@ -2,7 +2,7 @@ module IssuesAction
extend ActiveSupport::Concern extend ActiveSupport::Concern
def issues def issues
@issues = get_issues_collection @issues = get_issues_collection.non_archived
@issues = @issues.page(params[:page]).per(ApplicationController::PER_PAGE) @issues = @issues.page(params[:page]).per(ApplicationController::PER_PAGE)
@issues = @issues.preload(:author, :project) @issues = @issues.preload(:author, :project)
......
...@@ -2,7 +2,7 @@ module MergeRequestsAction ...@@ -2,7 +2,7 @@ module MergeRequestsAction
extend ActiveSupport::Concern extend ActiveSupport::Concern
def merge_requests def merge_requests
@merge_requests = get_merge_requests_collection @merge_requests = get_merge_requests_collection.non_archived
@merge_requests = @merge_requests.page(params[:page]).per(ApplicationController::PER_PAGE) @merge_requests = @merge_requests.page(params[:page]).per(ApplicationController::PER_PAGE)
@merge_requests = @merge_requests.preload(:author, :target_project) @merge_requests = @merge_requests.preload(:author, :target_project)
......
...@@ -6,7 +6,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -6,7 +6,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace) @projects = @projects.includes(:namespace)
terms = params['filter_projects'] terms = params[:filter_projects]
if terms.present? if terms.present?
@projects = @projects.search(terms) @projects = @projects.search(terms)
...@@ -31,11 +31,11 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -31,11 +31,11 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
end end
def starred def starred
@projects = current_user.starred_projects @projects = current_user.starred_projects.sorted_by_activity
@projects = @projects.includes(:namespace, :forked_from_project, :tags) @projects = @projects.includes(:namespace, :forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
terms = params['filter_projects'] terms = params[:filter_projects]
if terms.present? if terms.present?
@projects = @projects.search(terms) @projects = @projects.search(terms)
......
...@@ -23,6 +23,14 @@ class PasswordsController < Devise::PasswordsController ...@@ -23,6 +23,14 @@ class PasswordsController < Devise::PasswordsController
end end
end end
def update
super do |resource|
if resource.valid? && resource.require_password?
resource.update_attribute(:password_automatically_set, false)
end
end
end
protected protected
def resource_from_email def resource_from_email
......
...@@ -3,23 +3,21 @@ class Profiles::KeysController < Profiles::ApplicationController ...@@ -3,23 +3,21 @@ class Profiles::KeysController < Profiles::ApplicationController
def index def index
@keys = current_user.keys @keys = current_user.keys
@key = Key.new
end end
def show def show
@key = current_user.keys.find(params[:id]) @key = current_user.keys.find(params[:id])
end end
def new
@key = current_user.keys.new
end
def create def create
@key = current_user.keys.new(key_params) @key = current_user.keys.new(key_params)
if @key.save if @key.save
redirect_to profile_key_path(@key) redirect_to profile_key_path(@key)
else else
render 'new' @keys = current_user.keys.select(&:persisted?)
render :index
end end
end end
......
...@@ -12,11 +12,13 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController ...@@ -12,11 +12,13 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
current_user.save! if current_user.changed? current_user.save! if current_user.changed?
if two_factor_grace_period_expired? if two_factor_authentication_required?
flash.now[:alert] = 'You must enable Two-factor Authentication for your account.' if two_factor_grace_period_expired?
else flash.now[:alert] = 'You must enable Two-factor Authentication for your account.'
grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours else
flash.now[:alert] = "You must enable Two-factor Authentication for your account before #{l(grace_period_deadline)}." grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
flash.now[:alert] = "You must enable Two-factor Authentication for your account before #{l(grace_period_deadline)}."
end
end end
@qr_code = build_qr_code @qr_code = build_qr_code
......
...@@ -7,6 +7,9 @@ class Projects::AvatarsController < Projects::ApplicationController ...@@ -7,6 +7,9 @@ class Projects::AvatarsController < Projects::ApplicationController
@blob = @repository.blob_at_branch('master', @project.avatar_in_git) @blob = @repository.blob_at_branch('master', @project.avatar_in_git)
if @blob if @blob
headers['X-Content-Type-Options'] = 'nosniff' headers['X-Content-Type-Options'] = 'nosniff'
return if cached_blob?
headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob)) headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
headers['Content-Disposition'] = 'inline' headers['Content-Disposition'] = 'inline'
headers['Content-Type'] = safe_content_type(@blob) headers['Content-Type'] = safe_content_type(@blob)
......
class Projects::BadgesController < Projects::ApplicationController class Projects::BadgesController < Projects::ApplicationController
before_action :set_no_cache
def build def build
respond_to do |format| respond_to do |format|
format.html { render_404 } format.html { render_404 }
...@@ -8,4 +10,15 @@ class Projects::BadgesController < Projects::ApplicationController ...@@ -8,4 +10,15 @@ class Projects::BadgesController < Projects::ApplicationController
end end
end end
end end
private
def set_no_cache
expires_now
# Add some deprecated headers for older agents
#
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT'
end
end end
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
# Not to be confused with CommitsController, plural. # Not to be confused with CommitsController, plural.
class Projects::CommitController < Projects::ApplicationController class Projects::CommitController < Projects::ApplicationController
include CreatesCommit include CreatesCommit
include DiffHelper
# Authorize # Authorize
before_action :require_non_empty_project before_action :require_non_empty_project
...@@ -100,12 +101,10 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -100,12 +101,10 @@ class Projects::CommitController < Projects::ApplicationController
def define_show_vars def define_show_vars
return git_not_found! unless commit return git_not_found! unless commit
if params[:w].to_i == 1 opts = diff_options
@diffs = commit.diffs({ ignore_whitespace_change: true }) opts[:ignore_whitespace_change] = true if params[:format] == 'diff'
else
@diffs = commit.diffs
end
@diffs = commit.diffs(opts)
@diff_refs = [commit.parent || commit, commit] @diff_refs = [commit.parent || commit, commit]
@notes_count = commit.notes.count @notes_count = commit.notes.count
......
require 'addressable/uri' require 'addressable/uri'
class Projects::CompareController < Projects::ApplicationController class Projects::CompareController < Projects::ApplicationController
include DiffHelper
# Authorize # Authorize
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :authorize_download_code! before_action :authorize_download_code!
...@@ -11,16 +13,14 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -11,16 +13,14 @@ class Projects::CompareController < Projects::ApplicationController
end end
def show def show
diff_options = { ignore_whitespace_change: true } if params[:w] == '1' compare = CompareService.new.
compare_result = CompareService.new.
execute(@project, @head_ref, @project, @base_ref, diff_options) execute(@project, @head_ref, @project, @base_ref, diff_options)
if compare_result if compare
@commits = Commit.decorate(compare_result.commits, @project) @commits = Commit.decorate(compare.commits, @project)
@diffs = compare_result.diffs
@commit = @project.commit(@head_ref) @commit = @project.commit(@head_ref)
@base_commit = @project.merge_base_commit(@base_ref, @head_ref) @base_commit = @project.merge_base_commit(@base_ref, @head_ref)
@diffs = compare.diffs(diff_options)
@diff_refs = [@base_commit, @commit] @diff_refs = [@base_commit, @commit]
@line_notes = [] @line_notes = []
end end
......
...@@ -6,20 +6,24 @@ class Projects::ForksController < Projects::ApplicationController ...@@ -6,20 +6,24 @@ class Projects::ForksController < Projects::ApplicationController
def index def index
base_query = project.forks.includes(:creator) base_query = project.forks.includes(:creator)
@forks = if current_user @forks = base_query.merge(ProjectsFinder.new.execute(current_user))
base_query.where('projects.visibility_level IN (?) OR projects.id IN (?)',
Project.public_and_internal_levels,
current_user.authorized_projects.pluck(:id))
else
base_query.where('projects.visibility_level = ?', Project::PUBLIC)
end
@total_forks_count = base_query.size @total_forks_count = base_query.size
@private_forks_count = @total_forks_count - @forks.size @private_forks_count = @total_forks_count - @forks.size
@public_forks_count = @total_forks_count - @private_forks_count @public_forks_count = @total_forks_count - @private_forks_count
@sort = params[:sort] || 'id_desc' @sort = params[:sort] || 'id_desc'
@forks = @forks.search(params[:filter_projects]) if params[:filter_projects].present?
@forks = @forks.order_by(@sort).page(params[:page]).per(PER_PAGE) @forks = @forks.order_by(@sort).page(params[:page]).per(PER_PAGE)
respond_to do |format|
format.html
format.json do
render json: {
html: view_to_html_string("projects/forks/_projects", projects: @forks)
}
end
end
end end
def new def new
......
class Projects::MergeRequestsController < Projects::ApplicationController class Projects::MergeRequestsController < Projects::ApplicationController
include DiffHelper
before_action :module_enabled before_action :module_enabled
before_action :merge_request, only: [ before_action :merge_request, only: [
:edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check, :edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check,
...@@ -111,7 +113,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -111,7 +113,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commits = @merge_request.compare_commits.reverse @commits = @merge_request.compare_commits.reverse
@commit = @merge_request.last_commit @commit = @merge_request.last_commit
@base_commit = @merge_request.diff_base_commit @base_commit = @merge_request.diff_base_commit
@diffs = @merge_request.compare_diffs @diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare
@ci_commit = @merge_request.ci_commit @ci_commit = @merge_request.ci_commit
@statuses = @ci_commit.statuses if @ci_commit @statuses = @ci_commit.statuses if @ci_commit
......
...@@ -32,10 +32,6 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -32,10 +32,6 @@ class Projects::MilestonesController < Projects::ApplicationController
end end
def show def show
@issues = @milestone.issues
@users = @milestone.participants.uniq
@merge_requests = @milestone.merge_requests
@labels = @milestone.labels
end end
def create def create
......
...@@ -13,6 +13,8 @@ class Projects::RawController < Projects::ApplicationController ...@@ -13,6 +13,8 @@ class Projects::RawController < Projects::ApplicationController
if @blob if @blob
headers['X-Content-Type-Options'] = 'nosniff' headers['X-Content-Type-Options'] = 'nosniff'
return if cached_blob?
if @blob.lfs_pointer? if @blob.lfs_pointer?
send_lfs_object send_lfs_object
else else
......
...@@ -34,6 +34,11 @@ class Projects::TagsController < Projects::ApplicationController ...@@ -34,6 +34,11 @@ class Projects::TagsController < Projects::ApplicationController
def destroy def destroy
DeleteTagService.new(project, current_user).execute(params[:id]) DeleteTagService.new(project, current_user).execute(params[:id])
redirect_to namespace_project_tags_path(@project.namespace, @project) respond_to do |format|
format.html do
redirect_to namespace_project_tags_path(@project.namespace, @project)
end
format.js
end
end end
end end
class SearchController < ApplicationController class SearchController < ApplicationController
skip_before_action :authenticate_user!, :reject_blocked
include SearchHelper include SearchHelper
layout 'search' layout 'search'
......
...@@ -4,8 +4,10 @@ class SessionsController < Devise::SessionsController ...@@ -4,8 +4,10 @@ class SessionsController < Devise::SessionsController
skip_before_action :check_2fa_requirement, only: [:destroy] skip_before_action :check_2fa_requirement, only: [:destroy]
prepend_before_action :check_initial_setup, only: [:new]
prepend_before_action :authenticate_with_two_factor, only: [:create] prepend_before_action :authenticate_with_two_factor, only: [:create]
prepend_before_action :store_redirect_path, only: [:new] prepend_before_action :store_redirect_path, only: [:new]
before_action :auto_sign_in_with_provider, only: [:new] before_action :auto_sign_in_with_provider, only: [:new]
before_action :load_recaptcha before_action :load_recaptcha
...@@ -33,6 +35,22 @@ class SessionsController < Devise::SessionsController ...@@ -33,6 +35,22 @@ class SessionsController < Devise::SessionsController
private private
# Handle an "initial setup" state, where there's only one user, it's an admin,
# and they require a password change.
def check_initial_setup
return unless User.count == 1
user = User.admins.last
return unless user && user.require_password?
token = user.generate_reset_token
user.save
redirect_to edit_user_password_path(reset_password_token: token),
notice: "Please create a password for your new account."
end
def user_params def user_params
params.require(:user).permit(:login, :password, :remember_me, :otp_attempt) params.require(:user).permit(:login, :password, :remember_me, :otp_attempt)
end end
......
...@@ -3,13 +3,6 @@ class UsersController < ApplicationController ...@@ -3,13 +3,6 @@ class UsersController < ApplicationController
before_action :set_user before_action :set_user
def show def show
@contributed_projects = contributed_projects.joined(@user).reject(&:forked?)
@projects = PersonalProjectsFinder.new(@user).execute(current_user)
@projects = @projects.page(params[:page]).per(PER_PAGE)
@groups = @user.groups.order_id_desc
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -25,6 +18,45 @@ class UsersController < ApplicationController ...@@ -25,6 +18,45 @@ class UsersController < ApplicationController
end end
end end
def groups
load_groups
respond_to do |format|
format.html { render 'show' }
format.json do
render json: {
html: view_to_html_string("shared/groups/_list", groups: @groups)
}
end
end
end
def projects
load_projects
respond_to do |format|
format.html { render 'show' }
format.json do
render json: {
html: view_to_html_string("shared/projects/_list", projects: @projects, remote: true)
}
end
end
end
def contributed
load_contributed_projects
respond_to do |format|
format.html { render 'show' }
format.json do
render json: {
html: view_to_html_string("shared/projects/_list", projects: @contributed_projects)
}
end
end
end
def calendar def calendar
calendar = contributions_calendar calendar = contributions_calendar
@timestamps = calendar.timestamps @timestamps = calendar.timestamps
...@@ -35,12 +67,8 @@ class UsersController < ApplicationController ...@@ -35,12 +67,8 @@ class UsersController < ApplicationController
end end
def calendar_activities def calendar_activities
@calendar_date = Date.parse(params[:date]) rescue nil @calendar_date = Date.parse(params[:date]) rescue Date.today
@events = [] @events = contributions_calendar.events_by_date(@calendar_date)
if @calendar_date
@events = contributions_calendar.events_by_date(@calendar_date)
end
render 'calendar_activities', layout: false render 'calendar_activities', layout: false
end end
...@@ -69,6 +97,20 @@ class UsersController < ApplicationController ...@@ -69,6 +97,20 @@ class UsersController < ApplicationController
limit_recent(20, params[:offset]) limit_recent(20, params[:offset])
end end
def load_projects
@projects =
PersonalProjectsFinder.new(@user).execute(current_user)
.page(params[:page]).per(PER_PAGE)
end
def load_contributed_projects
@contributed_projects = contributed_projects.joined(@user)
end
def load_groups
@groups = @user.groups.order_id_desc
end
def projects_for_current_user def projects_for_current_user
ProjectsFinder.new.execute(current_user) ProjectsFinder.new.execute(current_user)
end end
......
...@@ -263,11 +263,9 @@ class IssuableFinder ...@@ -263,11 +263,9 @@ class IssuableFinder
def by_label(items) def by_label(items)
if labels? if labels?
if filter_by_no_label? if filter_by_no_label?
items = items. items = items.without_label
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 else
items = items.joins(:labels).where(labels: { title: label_names }) items = items.with_label(label_names)
if projects if projects
items = items.where(labels: { project_id: projects }) items = items.where(labels: { project_id: projects })
......
...@@ -152,4 +152,25 @@ module BlobHelper ...@@ -152,4 +152,25 @@ module BlobHelper
'application/octet-stream' 'application/octet-stream'
end end
end end
def cached_blob?
stale = stale?(etag: @blob.id) # The #stale? method sets cache headers.
# Because we are opionated we set the cache headers ourselves.
response.cache_control[:public] = @project.public?
if @ref && @commit && @ref == @commit.id
# This is a link to a commit by its commit SHA. That means that the blob
# is immutable. The only reason to invalidate the cache is if the commit
# was deleted or if the user lost access to the repository.
response.cache_control[:max_age] = Blob::CACHE_TIME_IMMUTABLE
else
# A branch or tag points at this blob. That means that the expected blob
# value may change over time.
response.cache_control[:max_age] = Blob::CACHE_TIME
end
response.etag = @blob.id
!stale
end
end end
...@@ -12,40 +12,20 @@ module DiffHelper ...@@ -12,40 +12,20 @@ module DiffHelper
params[:view] == 'parallel' ? 'parallel' : 'inline' params[:view] == 'parallel' ? 'parallel' : 'inline'
end end
def allowed_diff_size def diff_hard_limit_enabled?
if diff_hard_limit_enabled? params[:force_show_diff].present?
Commit::DIFF_HARD_LIMIT_FILES
else
Commit::DIFF_SAFE_FILES
end
end end
def allowed_diff_lines def diff_options
options = { ignore_whitespace_change: params[:w] == '1' }
if diff_hard_limit_enabled? if diff_hard_limit_enabled?
Commit::DIFF_HARD_LIMIT_LINES options.merge!(Commit.max_diff_options)
else
Commit::DIFF_SAFE_LINES
end end
options
end end
def safe_diff_files(diffs, diff_refs) def safe_diff_files(diffs, diff_refs)
lines = 0 diffs.decorate! { |diff| Gitlab::Diff::File.new(diff, diff_refs) }
safe_files = []
diffs.first(allowed_diff_size).each do |diff|
lines += diff.diff.lines.count
break if lines > allowed_diff_lines
safe_files << Gitlab::Diff::File.new(diff, diff_refs)
end
safe_files
end
def diff_hard_limit_enabled?
# Enabling hard limit allows user to see more diff information
if params[:force_show_diff].present?
true
else
false
end
end end
def generate_line_code(file_path, line) def generate_line_code(file_path, line)
......
...@@ -50,6 +50,8 @@ module GitlabMarkdownHelper ...@@ -50,6 +50,8 @@ module GitlabMarkdownHelper
context[:project] ||= @project context[:project] ||= @project
text = Banzai.pre_process(text, context)
html = Banzai.render(text, context) html = Banzai.render(text, context)
context.merge!( context.merge!(
......
...@@ -10,6 +10,15 @@ module IconsHelper ...@@ -10,6 +10,15 @@ module IconsHelper
options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options) options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options)
end end
def audit_icon(names, options = {})
case names
when "standard"
names = "key"
end
options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options)
end
def spinner(text = nil, visible = false) def spinner(text = nil, visible = false)
css_class = 'loading' css_class = 'loading'
css_class << ' hide' unless visible css_class << ' hide' unless visible
...@@ -37,7 +46,7 @@ module IconsHelper ...@@ -37,7 +46,7 @@ module IconsHelper
else # Gitlab::VisibilityLevel::PUBLIC else # Gitlab::VisibilityLevel::PUBLIC
'globe' 'globe'
end end
name << " fw" if fw name << " fw" if fw
icon(name) icon(name)
......
...@@ -50,19 +50,25 @@ module LabelsHelper ...@@ -50,19 +50,25 @@ module LabelsHelper
@project.labels.pluck(:title) @project.labels.pluck(:title)
end end
def render_colored_label(label) def render_colored_label(label, label_suffix = '')
label_color = label.color || Label::DEFAULT_COLOR label_color = label.color || Label::DEFAULT_COLOR
text_color = text_color_for_bg(label_color) text_color = text_color_for_bg(label_color)
# Intentionally not using content_tag here so that this method can be called # Intentionally not using content_tag here so that this method can be called
# by LabelReferenceFilter # by LabelReferenceFilter
span = %(<span class="label color-label") + span = %(<span class="label color-label") +
%( style="background-color: #{label_color}; color: #{text_color}">) + %(style="background-color: #{label_color}; color: #{text_color}">) +
escape_once(label.name) + '</span>' %(#{escape_once(label.name)}#{label_suffix}</span>)
span.html_safe span.html_safe
end end
def render_colored_cross_project_label(label)
label_suffix = label.project.name_with_namespace
label_suffix = " <i>in #{escape_once(label_suffix)}</i>"
render_colored_label(label, label_suffix)
end
def suggested_colors def suggested_colors
[ [
'#0033CC', '#0033CC',
...@@ -119,5 +125,6 @@ module LabelsHelper ...@@ -119,5 +125,6 @@ module LabelsHelper
end end
# Required for Banzai::Filter::LabelReferenceFilter # Required for Banzai::Filter::LabelReferenceFilter
module_function :render_colored_label, :text_color_for_bg, :escape_once module_function :render_colored_label, :render_colored_cross_project_label,
:text_color_for_bg, :escape_once
end end
...@@ -9,6 +9,32 @@ module MilestonesHelper ...@@ -9,6 +9,32 @@ module MilestonesHelper
end end
end end
def milestones_label_path(opts = {})
if @project
namespace_project_issues_path(@project.namespace, @project, opts)
elsif @group
issues_group_path(@group, opts)
else
issues_dashboard_path(opts)
end
end
def milestones_browse_issuables_path(milestone, type:)
opts = { milestone_title: milestone.title }
if @project
polymorphic_path([@project.namespace.becomes(Namespace), @project, type], opts)
elsif @group
polymorphic_url([type, @group], opts)
else
polymorphic_url([type, :dashboard], opts)
end
end
def milestone_issues_by_label_count(milestone, label, state:)
milestone.issues.with_label(label.title).send(state).size
end
def milestone_progress_bar(milestone) def milestone_progress_bar(milestone)
options = { options = {
class: 'progress-bar progress-bar-success', class: 'progress-bar progress-bar-success',
...@@ -36,4 +62,14 @@ module MilestonesHelper ...@@ -36,4 +62,14 @@ module MilestonesHelper
options_from_collection_for_select(grouped_milestones, 'name', 'title', params[:milestone_title]) options_from_collection_for_select(grouped_milestones, 'name', 'title', params[:milestone_title])
end end
def milestone_remaining_days(milestone)
if milestone.expired?
content_tag(:strong, 'expired')
elsif milestone.due_date
days = milestone.remaining_days
content = content_tag(:strong, days)
content << " #{'day'.pluralize(days)} remaining"
end
end
end end
...@@ -38,12 +38,16 @@ module ProjectsHelper ...@@ -38,12 +38,16 @@ module ProjectsHelper
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] 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 # Build name span tag
author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name] if opts[:by_username]
author_html << content_tag(:span, sanitize("@#{author.username}"), class: opts[:author_class]) if opts[:name]
else
author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name]
end
author_html = author_html.html_safe author_html = author_html.html_safe
if opts[:name] if opts[:name]
link_to(author_html, user_path(author), class: "author_link").html_safe link_to(author_html, user_path(author), class: "author_link #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe
else else
title = opts[:title].sub(":name", sanitize(author.name)) title = opts[:title].sub(":name", sanitize(author.name))
link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe
......
...@@ -188,6 +188,7 @@ class Ability ...@@ -188,6 +188,7 @@ class Ability
def project_dev_rules def project_dev_rules
@project_dev_rules ||= project_report_rules + [ @project_dev_rules ||= project_report_rules + [
:admin_merge_request, :admin_merge_request,
:update_merge_request,
:create_commit_status, :create_commit_status,
:update_commit_status, :update_commit_status,
:create_build, :create_build,
...@@ -212,7 +213,6 @@ class Ability ...@@ -212,7 +213,6 @@ class Ability
@project_master_rules ||= project_dev_rules + [ @project_master_rules ||= project_dev_rules + [
:push_code_to_protected_branches, :push_code_to_protected_branches,
:update_project_snippet, :update_project_snippet,
:update_merge_request,
:admin_milestone, :admin_milestone,
:admin_project_snippet, :admin_project_snippet,
:admin_project_member, :admin_project_member,
......
# Blob is a Rails-specific wrapper around Gitlab::Git::Blob objects # Blob is a Rails-specific wrapper around Gitlab::Git::Blob objects
class Blob < SimpleDelegator class Blob < SimpleDelegator
CACHE_TIME = 60 # Cache raw blobs referred to by a (mutable) ref for 1 minute
CACHE_TIME_IMMUTABLE = 3600 # Cache blobs referred to by an immutable reference for 1 hour
# Wrap a Gitlab::Git::Blob object, or return nil when given nil # Wrap a Gitlab::Git::Blob object, or return nil when given nil
# #
# This method prevents the decorated object from evaluating to "truthy" when # This method prevents the decorated object from evaluating to "truthy" when
......
...@@ -12,12 +12,7 @@ class Commit ...@@ -12,12 +12,7 @@ class Commit
attr_accessor :project attr_accessor :project
# Safe amount of changes (files and lines) in one commit to render DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines]
# Used to prevent 500 error on huge commits by suppressing diff
#
# User can force display of diff above this size
DIFF_SAFE_FILES = 100 unless defined?(DIFF_SAFE_FILES)
DIFF_SAFE_LINES = 5000 unless defined?(DIFF_SAFE_LINES)
# Commits above this size will not be rendered in HTML # Commits above this size will not be rendered in HTML
DIFF_HARD_LIMIT_FILES = 1000 unless defined?(DIFF_HARD_LIMIT_FILES) DIFF_HARD_LIMIT_FILES = 1000 unless defined?(DIFF_HARD_LIMIT_FILES)
...@@ -36,13 +31,20 @@ class Commit ...@@ -36,13 +31,20 @@ class Commit
# Calculate number of lines to render for diffs # Calculate number of lines to render for diffs
def diff_line_count(diffs) def diff_line_count(diffs)
diffs.reduce(0) { |sum, d| sum + d.diff.lines.count } diffs.reduce(0) { |sum, d| sum + Gitlab::Git::Util.count_lines(d.diff) }
end end
# Truncate sha to 8 characters # Truncate sha to 8 characters
def truncate_sha(sha) def truncate_sha(sha)
sha[0..7] sha[0..7]
end end
def max_diff_options
{
max_files: DIFF_HARD_LIMIT_FILES,
max_lines: DIFF_HARD_LIMIT_LINES,
}
end
end end
attr_accessor :raw attr_accessor :raw
......
...@@ -29,15 +29,19 @@ module Issuable ...@@ -29,15 +29,19 @@ module Issuable
scope :assigned, -> { where("assignee_id IS NOT NULL") } scope :assigned, -> { where("assignee_id IS NOT NULL") }
scope :unassigned, -> { where("assignee_id IS NULL") } scope :unassigned, -> { where("assignee_id IS NULL") }
scope :of_projects, ->(ids) { where(project_id: ids) } scope :of_projects, ->(ids) { where(project_id: ids) }
scope :of_milestones, ->(ids) { where(milestone_id: ids) }
scope :opened, -> { with_state(:opened, :reopened) } scope :opened, -> { with_state(:opened, :reopened) }
scope :only_opened, -> { with_state(:opened) } scope :only_opened, -> { with_state(:opened) }
scope :only_reopened, -> { with_state(:reopened) } scope :only_reopened, -> { with_state(:reopened) }
scope :closed, -> { with_state(:closed) } scope :closed, -> { with_state(:closed) }
scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') } scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') }
scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') } scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') }
scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }) }
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :join_project, -> { joins(:project) } scope :join_project, -> { joins(:project) }
scope :references_project, -> { references(:project) } scope :references_project, -> { references(:project) }
scope :non_archived, -> { join_project.merge(Project.non_archived) }
delegate :name, delegate :name,
:email, :email,
......
module Milestoneish
def closed_items_count
issues.closed.size + merge_requests.closed_and_merged.size
end
def total_items_count
issues.size + merge_requests.size
end
def complete?
total_items_count == closed_items_count
end
def percent_complete
((closed_items_count * 100) / total_items_count).abs
rescue ZeroDivisionError
0
end
def remaining_days
return 0 if !due_date || expired?
(due_date - Date.today).to_i
end
end
class DiffLine
attr_accessor :type, :content, :num, :code
end
...@@ -2,16 +2,19 @@ class GlobalLabel ...@@ -2,16 +2,19 @@ class GlobalLabel
attr_accessor :title, :labels attr_accessor :title, :labels
alias_attribute :name, :title alias_attribute :name, :title
delegate :color, :description, to: :@first_label
def self.build_collection(labels) def self.build_collection(labels)
labels = labels.group_by(&:title) labels = labels.group_by(&:title)
labels.map do |title, label| labels.map do |title, labels|
new(title, label) new(title, labels)
end end
end end
def initialize(title, labels) def initialize(title, labels)
@title = title @title = title
@labels = labels @labels = labels
@first_label = labels.find { |lbl| lbl.description.present? } || labels.first
end end
end end
class GlobalMilestone class GlobalMilestone
include Milestoneish
attr_accessor :title, :milestones attr_accessor :title, :milestones
alias_attribute :name, :title alias_attribute :name, :title
...@@ -28,33 +30,7 @@ class GlobalMilestone ...@@ -28,33 +30,7 @@ class GlobalMilestone
end end
def projects def projects
milestones.map { |milestone| milestone.project } @projects ||= Project.for_milestones(milestones.map(&:id))
end
def issue_count
milestones.map { |milestone| milestone.issues.count }.sum
end
def merge_requests_count
milestones.map { |milestone| milestone.merge_requests.count }.sum
end
def open_items_count
milestones.map { |milestone| milestone.open_items_count }.sum
end
def closed_items_count
milestones.map { |milestone| milestone.closed_items_count }.sum
end
def total_items_count
milestones.map { |milestone| milestone.total_items_count }.sum
end
def percent_complete
((closed_items_count * 100) / total_items_count).abs
rescue ZeroDivisionError
0
end end
def state def state
...@@ -76,35 +52,20 @@ class GlobalMilestone ...@@ -76,35 +52,20 @@ class GlobalMilestone
end end
def issues def issues
@issues ||= milestones.map(&:issues).flatten.group_by(&:state) @issues ||= Issue.of_milestones(milestones.map(&:id)).includes(:project)
end end
def merge_requests def merge_requests
@merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state) @merge_requests ||= MergeRequest.of_milestones(milestones.map(&:id)).includes(:target_project)
end end
def participants def participants
@participants ||= milestones.map(&:participants).flatten.compact.uniq @participants ||= milestones.map(&:participants).flatten.compact.uniq
end end
def opened_issues def labels
issues.values_at("opened", "reopened").compact.flatten @labels ||= GlobalLabel.build_collection(milestones.map(&:labels).flatten)
end .sort_by!(&:title)
def closed_issues
issues['closed']
end
def opened_merge_requests
merge_requests.values_at("opened", "reopened").compact.flatten
end
def closed_merge_requests
merge_requests.values_at("closed", "merged", "locked").compact.flatten
end
def complete?
total_items_count == closed_items_count
end end
def due_date def due_date
......
...@@ -27,6 +27,7 @@ class Label < ActiveRecord::Base ...@@ -27,6 +27,7 @@ class Label < ActiveRecord::Base
belongs_to :project belongs_to :project
has_many :label_links, dependent: :destroy has_many :label_links, dependent: :destroy
has_many :issues, through: :label_links, source: :target, source_type: 'Issue' has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest'
validates :color, color: true, allow_blank: false validates :color, color: true, allow_blank: false
validates :project, presence: true, unless: Proc.new { |service| service.template? } validates :project, presence: true, unless: Proc.new { |service| service.template? }
...@@ -47,10 +48,15 @@ class Label < ActiveRecord::Base ...@@ -47,10 +48,15 @@ class Label < ActiveRecord::Base
'~' '~'
end end
##
# Pattern used to extract label references from text # Pattern used to extract label references from text
#
# This pattern supports cross-project references.
#
def self.reference_pattern def self.reference_pattern
%r{ %r{
#{reference_prefix} (#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}
(?: (?:
(?<label_id>\d+) | # Integer-based label ID, or (?<label_id>\d+) | # Integer-based label ID, or
(?<label_name> (?<label_name>
...@@ -61,24 +67,31 @@ class Label < ActiveRecord::Base ...@@ -61,24 +67,31 @@ class Label < ActiveRecord::Base
}x }x
end end
def self.link_reference_pattern
nil
end
##
# Returns the String necessary to reference this Label in Markdown # Returns the String necessary to reference this Label in Markdown
# #
# format - Symbol format to use (default: :id, optional: :name) # format - Symbol format to use (default: :id, optional: :name)
# #
# Note that its argument differs from other objects implementing Referable. If
# a non-Symbol argument is given (such as a Project), it will default to :id.
#
# Examples: # Examples:
# #
# Label.first.to_reference # => "~1" # Label.first.to_reference # => "~1"
# Label.first.to_reference(:name) # => "~\"bug\"" # Label.first.to_reference(format: :name) # => "~\"bug\""
# Label.first.to_reference(project) # => "gitlab-org/gitlab-ce~1"
# #
# Returns a String # Returns a String
def to_reference(format = :id) #
if format == :name && !name.include?('"') def to_reference(from_project = nil, format: :id)
%(#{self.class.reference_prefix}"#{name}") format_reference = label_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}"
if cross_project_reference?(from_project)
project.to_reference + reference
else else
"#{self.class.reference_prefix}#{id}" reference
end end
end end
...@@ -90,7 +103,23 @@ class Label < ActiveRecord::Base ...@@ -90,7 +103,23 @@ class Label < ActiveRecord::Base
issues.closed.count issues.closed.count
end end
def open_merge_requests_count
merge_requests.opened.count
end
def template? def template?
template template
end end
private
def label_format_reference(format = :id)
raise StandardError, 'Unknown format' unless [:id, :name].include?(format)
if format == :name && !name.include?('"')
%("#{name}")
else
id
end
end
end end
...@@ -48,7 +48,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -48,7 +48,7 @@ class MergeRequest < ActiveRecord::Base
after_create :create_merge_request_diff after_create :create_merge_request_diff
after_update :update_merge_request_diff after_update :update_merge_request_diff
delegate :commits, :diffs, :diffs_no_whitespace, to: :merge_request_diff, prefix: nil delegate :commits, :diffs, :real_size, to: :merge_request_diff, prefix: nil
# When this attribute is true some MR validation is ignored # When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests # It allows us to close or modify broken merge requests
...@@ -56,8 +56,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -56,8 +56,7 @@ class MergeRequest < ActiveRecord::Base
# Temporary fields to store compare vars # Temporary fields to store compare vars
# when creating new merge request # when creating new merge request
attr_accessor :can_be_created, :compare_failed, attr_accessor :can_be_created, :compare_commits, :compare
:compare_commits, :compare_diffs
state_machine :state, initial: :opened do state_machine :state, initial: :opened do
event :close do event :close do
...@@ -138,9 +137,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -138,9 +137,7 @@ class MergeRequest < ActiveRecord::Base
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) } scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) } scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) }
scope :of_projects, ->(ids) { where(target_project_id: ids) } scope :of_projects, ->(ids) { where(target_project_id: ids) }
scope :opened, -> { with_states(:opened, :reopened) }
scope :merged, -> { with_state(:merged) } scope :merged, -> { with_state(:merged) }
scope :closed, -> { with_state(:closed) }
scope :closed_and_merged, -> { with_states(:closed, :merged) } scope :closed_and_merged, -> { with_states(:closed, :merged) }
scope :join_project, -> { joins(:target_project) } scope :join_project, -> { joins(:target_project) }
...@@ -182,6 +179,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -182,6 +179,10 @@ class MergeRequest < ActiveRecord::Base
merge_request_diff ? merge_request_diff.first_commit : compare_commits.first merge_request_diff ? merge_request_diff.first_commit : compare_commits.first
end end
def diff_size
merge_request_diff.size
end
def diff_base_commit def diff_base_commit
if merge_request_diff if merge_request_diff
merge_request_diff.base_commit merge_request_diff.base_commit
...@@ -487,6 +488,16 @@ class MergeRequest < ActiveRecord::Base ...@@ -487,6 +488,16 @@ class MergeRequest < ActiveRecord::Base
end end
end end
def state_icon_name
if merged?
"check"
elsif closed?
"times"
else
"circle-o"
end
end
def target_sha def target_sha
@target_sha ||= target_project.repository.commit(target_branch).sha @target_sha ||= target_project.repository.commit(target_branch).sha
end end
...@@ -524,6 +535,29 @@ class MergeRequest < ActiveRecord::Base ...@@ -524,6 +535,29 @@ class MergeRequest < ActiveRecord::Base
end end
end end
def diverged_commits_count
cache = Rails.cache.read(:"merge_request_#{id}_diverged_commits")
if cache.blank? || cache[:source_sha] != source_sha || cache[:target_sha] != target_sha
cache = {
source_sha: source_sha,
target_sha: target_sha,
diverged_commits_count: compute_diverged_commits_count
}
Rails.cache.write(:"merge_request_#{id}_diverged_commits", cache)
end
cache[:diverged_commits_count]
end
def compute_diverged_commits_count
Gitlab::Git::Commit.between(target_project.repository.raw_repository, source_sha, target_sha).size
end
def diverged_from_target_branch?
diverged_commits_count > 0
end
def ci_commit def ci_commit
@ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
end end
......
...@@ -19,14 +19,15 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -19,14 +19,15 @@ class MergeRequestDiff < ActiveRecord::Base
# Prevent store of diff if commits amount more then 500 # Prevent store of diff if commits amount more then 500
COMMITS_SAFE_SIZE = 500 COMMITS_SAFE_SIZE = 500
attr_reader :commits, :diffs, :diffs_no_whitespace
belongs_to :merge_request belongs_to :merge_request
delegate :target_branch, :source_branch, to: :merge_request, prefix: nil delegate :target_branch, :source_branch, to: :merge_request, prefix: nil
state_machine :state, initial: :empty do state_machine :state, initial: :empty do
state :collected state :collected
state :overflow
# Deprecated states: these are no longer used but these values may still occur
# in the database.
state :timeout state :timeout
state :overflow_commits_safe_size state :overflow_commits_safe_size
state :overflow_diff_files_limit state :overflow_diff_files_limit
...@@ -43,19 +44,23 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -43,19 +44,23 @@ class MergeRequestDiff < ActiveRecord::Base
reload_diffs reload_diffs
end end
def diffs def size
@diffs ||= (load_diffs(st_diffs) || []) real_size.presence || diffs.size
end end
def diffs_no_whitespace def diffs(options={})
compare_result = Gitlab::CompareResult.new( if options[:ignore_whitespace_change]
Gitlab::Git::Compare.new( @diffs_no_whitespace ||= begin
self.repository.raw_repository, compare = Gitlab::Git::Compare.new(
self.target_branch, self.repository.raw_repository,
self.source_sha, self.target_branch,
), { ignore_whitespace_change: true } self.source_sha,
) )
@diffs_no_whitespace ||= load_diffs(dump_commits(compare_result.diffs)) compare.diffs(options)
end
else
@diffs ||= load_diffs(st_diffs, options)
end
end end
def commits def commits
...@@ -94,16 +99,18 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -94,16 +99,18 @@ class MergeRequestDiff < ActiveRecord::Base
end end
end end
def load_diffs(raw) def load_diffs(raw, options)
if raw.respond_to?(:map) if raw.respond_to?(:each)
raw.map { |hash| Gitlab::Git::Diff.new(hash) } Gitlab::Git::DiffCollection.new(raw, options)
else
Gitlab::Git::DiffCollection.new([])
end end
end end
# Collect array of Git::Commit objects # Collect array of Git::Commit objects
# between target and source branches # between target and source branches
def unmerged_commits def unmerged_commits
commits = compare_result.commits commits = compare.commits
if commits.present? if commits.present?
commits = Commit.decorate(commits, merge_request.source_project). commits = Commit.decorate(commits, merge_request.source_project).
...@@ -133,27 +140,21 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -133,27 +140,21 @@ class MergeRequestDiff < ActiveRecord::Base
if commits.size.zero? if commits.size.zero?
self.state = :empty self.state = :empty
elsif commits.size > COMMITS_SAFE_SIZE
self.state = :overflow_commits_safe_size
else else
new_diffs = unmerged_diffs diff_collection = unmerged_diffs
end
if new_diffs.any? if diff_collection.overflow?
if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES # Set our state to 'overflow' to make the #empty? and #collected?
self.state = :overflow_diff_files_limit # methods (generated by StateMachine) return false.
new_diffs = new_diffs.first(Commit::DIFF_HARD_LIMIT_LINES) self.state = :overflow
end end
if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES self.real_size = diff_collection.real_size
self.state = :overflow_diff_lines_limit
new_diffs = new_diffs.first(Commit::DIFF_HARD_LIMIT_LINES)
end
end
if new_diffs.present? if diff_collection.any?
new_diffs = dump_commits(new_diffs) new_diffs = dump_diffs(diff_collection)
self.state = :collected self.state = :collected
end
end end
self.st_diffs = new_diffs self.st_diffs = new_diffs
...@@ -166,10 +167,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -166,10 +167,7 @@ class MergeRequestDiff < ActiveRecord::Base
# Collect array of Git::Diff objects # Collect array of Git::Diff objects
# between target and source branches # between target and source branches
def unmerged_diffs def unmerged_diffs
compare_result.diffs || [] compare.diffs(Commit.max_diff_options)
rescue Gitlab::Git::Diff::TimeoutError
self.state = :timeout
[]
end end
def repository def repository
...@@ -181,18 +179,16 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -181,18 +179,16 @@ class MergeRequestDiff < ActiveRecord::Base
source_commit.try(:sha) source_commit.try(:sha)
end end
def compare_result def compare
@compare_result ||= @compare ||=
begin begin
# Update ref for merge request # Update ref for merge request
merge_request.fetch_ref merge_request.fetch_ref
Gitlab::CompareResult.new( Gitlab::Git::Compare.new(
Gitlab::Git::Compare.new( self.repository.raw_repository,
self.repository.raw_repository, self.target_branch,
self.target_branch, self.source_sha
self.source_sha
)
) )
end end
end end
......
...@@ -24,12 +24,13 @@ class Milestone < ActiveRecord::Base ...@@ -24,12 +24,13 @@ class Milestone < ActiveRecord::Base
include Sortable include Sortable
include Referable include Referable
include StripAttribute include StripAttribute
include Milestoneish
belongs_to :project belongs_to :project
has_many :issues has_many :issues
has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
has_many :merge_requests has_many :merge_requests
has_many :participants, through: :issues, source: :assignee has_many :participants, -> { distinct.reorder('users.name') }, through: :issues, source: :assignee
scope :active, -> { with_state(:active) } scope :active, -> { with_state(:active) }
scope :closed, -> { with_state(:closed) } scope :closed, -> { with_state(:closed) }
...@@ -92,37 +93,6 @@ class Milestone < ActiveRecord::Base ...@@ -92,37 +93,6 @@ class Milestone < ActiveRecord::Base
end end
end end
def open_items_count
self.issues.opened.count + self.merge_requests.opened.count
end
def closed_items_count
self.issues.closed.count + self.merge_requests.closed_and_merged.count
end
def total_items_count
self.issues.count + self.merge_requests.count
end
def percent_complete
((closed_items_count * 100) / total_items_count).abs
rescue ZeroDivisionError
0
end
# Returns the elapsed time (in percent) since the Milestone creation date until today.
# If the Milestone doesn't have a due_date then returns 0 since we can't calculate the elapsed time.
# If the Milestone is overdue then it returns 100%.
def percent_time_used
return 0 unless due_date
return 100 if expired?
duration = ((created_at - due_date.to_datetime) / 1.day)
days_elapsed = ((created_at - Time.now) / 1.day)
((days_elapsed.to_f / duration) * 100).floor
end
def expires_at def expires_at
if due_date if due_date
if due_date.past? if due_date.past?
......
...@@ -39,6 +39,7 @@ class Note < ActiveRecord::Base ...@@ -39,6 +39,7 @@ class Note < ActiveRecord::Base
has_many :todos, dependent: :destroy has_many :todos, dependent: :destroy
delegate :gfm_reference, :local_reference, to: :noteable
delegate :name, to: :project, prefix: true delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true delegate :name, :email, to: :author, prefix: true
...@@ -87,7 +88,7 @@ class Note < ActiveRecord::Base ...@@ -87,7 +88,7 @@ class Note < ActiveRecord::Base
next if discussion_ids.include?(note.discussion_id) next if discussion_ids.include?(note.discussion_id)
# don't group notes for the main target # don't group notes for the main target
if !note.for_diff_line? && note.noteable_type == "MergeRequest" if !note.for_diff_line? && note.for_merge_request?
discussions << [note] discussions << [note]
else else
discussions << notes.select do |other_note| discussions << notes.select do |other_note|
...@@ -131,9 +132,11 @@ class Note < ActiveRecord::Base ...@@ -131,9 +132,11 @@ class Note < ActiveRecord::Base
end end
def find_diff def find_diff
return nil unless noteable && noteable.diffs.present? return nil unless noteable
return @diff if defined?(@diff)
@diff ||= noteable.diffs.find do |d| # Don't use ||= because nil is a valid value for @diff
@diff = noteable.diffs(Commit.max_diff_options).find do |d|
Digest::SHA1.hexdigest(d.new_path) == diff_file_index if d.new_path Digest::SHA1.hexdigest(d.new_path) == diff_file_index if d.new_path
end end
end end
...@@ -165,20 +168,16 @@ class Note < ActiveRecord::Base ...@@ -165,20 +168,16 @@ class Note < ActiveRecord::Base
def active? def active?
return true unless self.diff return true unless self.diff
return false unless noteable return false unless noteable
return @active if defined?(@active)
noteable.diffs.each do |mr_diff| diffs = noteable.diffs(Commit.max_diff_options)
next unless mr_diff.new_path == self.diff.new_path notable_diff = diffs.find { |d| d.new_path == self.diff.new_path }
lines = Gitlab::Diff::Parser.new.parse(mr_diff.diff.lines.to_a) return @active = false if notable_diff.nil?
lines.each do |line| parsed_lines = Gitlab::Diff::Parser.new.parse(notable_diff.diff.each_line)
if line.text == diff_line # We cannot use ||= because @active may be false
return true @active = parsed_lines.any? { |line_obj| line_obj.text == diff_line }
end
end
end
false
end end
def outdated? def outdated?
...@@ -263,7 +262,7 @@ class Note < ActiveRecord::Base ...@@ -263,7 +262,7 @@ class Note < ActiveRecord::Base
end end
def diff_lines def diff_lines
@diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.lines) @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
end end
def highlighted_diff_lines def highlighted_diff_lines
...@@ -315,20 +314,6 @@ class Note < ActiveRecord::Base ...@@ -315,20 +314,6 @@ class Note < ActiveRecord::Base
nil nil
end end
# Mentionable override.
def gfm_reference(from_project = nil)
noteable.gfm_reference(from_project)
end
# Mentionable override.
def local_reference
noteable
end
def noteable_type_name
noteable_type.downcase if noteable_type.present?
end
# FIXME: Hack for polymorphic associations with STI # FIXME: Hack for polymorphic associations with STI
# For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations # For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations
def noteable_type=(noteable_type) def noteable_type=(noteable_type)
...@@ -348,10 +333,6 @@ class Note < ActiveRecord::Base ...@@ -348,10 +333,6 @@ class Note < ActiveRecord::Base
Event.reset_event_cache_for(self) Event.reset_event_cache_for(self)
end end
def system?
read_attribute(:system)
end
def downvote? def downvote?
is_award && note == "thumbsdown" is_award && note == "thumbsdown"
end end
...@@ -385,7 +366,7 @@ class Note < ActiveRecord::Base ...@@ -385,7 +366,7 @@ class Note < ActiveRecord::Base
private private
def awards_supported? def awards_supported?
(noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest)) && !for_diff_line? (for_issue? || for_merge_request?) && !for_diff_line?
end end
def contains_emoji_only? def contains_emoji_only?
......
...@@ -215,6 +215,7 @@ class Project < ActiveRecord::Base ...@@ -215,6 +215,7 @@ class Project < ActiveRecord::Base
scope :public_only, -> { where(visibility_level: Project::PUBLIC) } scope :public_only, -> { where(visibility_level: Project::PUBLIC) }
scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) } scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) }
scope :non_archived, -> { where(archived: false) } scope :non_archived, -> { where(archived: false) }
scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
state_machine :import_status, initial: :none do state_machine :import_status, initial: :none do
event :import_start do event :import_start do
...@@ -278,7 +279,7 @@ class Project < ActiveRecord::Base ...@@ -278,7 +279,7 @@ class Project < ActiveRecord::Base
end end
def search_by_title(query) def search_by_title(query)
where('projects.archived = ?', false).where('LOWER(projects.name) LIKE :query', query: "%#{query.downcase}%") non_archived.where('LOWER(projects.name) LIKE :query', query: "%#{query.downcase}%")
end end
def find_with_namespace(id) def find_with_namespace(id)
...@@ -711,6 +712,8 @@ class Project < ActiveRecord::Base ...@@ -711,6 +712,8 @@ class Project < ActiveRecord::Base
old_path_with_namespace = File.join(namespace_dir, path_was) old_path_with_namespace = File.join(namespace_dir, path_was)
new_path_with_namespace = File.join(namespace_dir, path) new_path_with_namespace = File.join(namespace_dir, path)
expire_caches_before_rename(old_path_with_namespace)
if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace) if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace)
# If repository moved successfully we need to send update instructions to users. # If repository moved successfully we need to send update instructions to users.
# However we cannot allow rollback since we moved repository # However we cannot allow rollback since we moved repository
...@@ -739,6 +742,22 @@ class Project < ActiveRecord::Base ...@@ -739,6 +742,22 @@ class Project < ActiveRecord::Base
Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path) Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path)
end end
# Expires various caches before a project is renamed.
def expire_caches_before_rename(old_path)
repo = Repository.new(old_path, self)
wiki = Repository.new("#{old_path}.wiki", self)
if repo.exists?
repo.expire_cache
repo.expire_emptiness_caches
end
if wiki.exists?
wiki.expire_cache
wiki.expire_emptiness_caches
end
end
def hook_attrs def hook_attrs
{ {
name: name, name: name,
......
...@@ -108,7 +108,8 @@ class JiraService < IssueTrackerService ...@@ -108,7 +108,8 @@ class JiraService < IssueTrackerService
}, },
entity: { entity: {
name: noteable_name.humanize.downcase, name: noteable_name.humanize.downcase,
url: entity_url url: entity_url,
title: noteable.title
} }
} }
...@@ -196,10 +197,11 @@ class JiraService < IssueTrackerService ...@@ -196,10 +197,11 @@ class JiraService < IssueTrackerService
user_url = data[:user][:url] user_url = data[:user][:url]
entity_name = data[:entity][:name] entity_name = data[:entity][:name]
entity_url = data[:entity][:url] entity_url = data[:entity][:url]
entity_title = data[:entity][:title]
project_name = data[:project][:name] project_name = data[:project][:name]
message = { message = {
body: "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]." body: %Q{[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]:\n'#{entity_title}'}
} }
unless existing_comment?(issue_name, message[:body]) unless existing_comment?(issue_name, message[:body])
......
...@@ -654,30 +654,38 @@ class Repository ...@@ -654,30 +654,38 @@ class Repository
end end
end end
def revert(user, commit, base_branch, target_branch = nil) def revert(user, commit, base_branch, revert_tree_id = nil)
source_sha = find_branch(base_branch).target source_sha = find_branch(base_branch).target
target_branch ||= base_branch revert_tree_id ||= check_revert_content(commit, base_branch)
args = [commit.id, source_sha]
args << { mainline: 1 } if commit.merge_commit?
revert_index = rugged.revert_commit(*args) return false unless revert_tree_id
return false if revert_index.conflicts?
tree_id = revert_index.write_tree(rugged)
return false unless diff_exists?(source_sha, tree_id)
commit_with_hooks(user, target_branch) do |ref| commit_with_hooks(user, base_branch) do |ref|
committer = user_to_committer(user) committer = user_to_committer(user)
source_sha = Rugged::Commit.create(rugged, source_sha = Rugged::Commit.create(rugged,
message: commit.revert_message, message: commit.revert_message,
author: committer, author: committer,
committer: committer, committer: committer,
tree: tree_id, tree: revert_tree_id,
parents: [rugged.lookup(source_sha)], parents: [rugged.lookup(source_sha)],
update_ref: ref) update_ref: ref)
end end
end end
def check_revert_content(commit, base_branch)
source_sha = find_branch(base_branch).target
args = [commit.id, source_sha]
args << { mainline: 1 } if commit.merge_commit?
revert_index = rugged.revert_commit(*args)
return false if revert_index.conflicts?
tree_id = revert_index.write_tree(rugged)
return false unless diff_exists?(source_sha, tree_id)
tree_id
end
def diff_exists?(sha1, sha2) def diff_exists?(sha1, sha2)
rugged.diff(sha1, sha2).size > 0 rugged.diff(sha1, sha2).size > 0
end end
...@@ -804,6 +812,12 @@ class Repository ...@@ -804,6 +812,12 @@ class Repository
raw_repository.ls_files(actual_ref) raw_repository.ls_files(actual_ref)
end end
def main_language
unless empty?
Linguist::Repository.new(rugged, rugged.head.target_id).language
end
end
private private
def cache def cache
......
...@@ -169,7 +169,7 @@ class User < ActiveRecord::Base ...@@ -169,7 +169,7 @@ class User < ActiveRecord::Base
validates :avatar_crop_x, :avatar_crop_y, :avatar_crop_size, validates :avatar_crop_x, :avatar_crop_y, :avatar_crop_size,
numericality: { only_integer: true }, numericality: { only_integer: true },
presence: true, presence: true,
if: ->(user) { user.avatar? } if: ->(user) { user.avatar? && user.avatar_changed? }
before_validation :generate_password, on: :create before_validation :generate_password, on: :create
before_validation :restricted_signup_domains, on: :create before_validation :restricted_signup_domains, on: :create
...@@ -362,17 +362,19 @@ class User < ActiveRecord::Base ...@@ -362,17 +362,19 @@ class User < ActiveRecord::Base
def disable_two_factor! def disable_two_factor!
update_attributes( update_attributes(
two_factor_enabled: false, two_factor_enabled: false,
encrypted_otp_secret: nil, encrypted_otp_secret: nil,
encrypted_otp_secret_iv: nil, encrypted_otp_secret_iv: nil,
encrypted_otp_secret_salt: nil, encrypted_otp_secret_salt: nil,
otp_backup_codes: nil otp_grace_period_started_at: nil,
otp_backup_codes: nil
) )
end end
def namespace_uniq def namespace_uniq
# Return early if username already failed the first uniqueness validation # Return early if username already failed the first uniqueness validation
return if self.errors[:username].include?('has already been taken') return if self.errors.key?(:username) &&
self.errors[:username].include?('has already been taken')
namespace_name = self.username namespace_name = self.username
existing_namespace = Namespace.by_path(namespace_name) existing_namespace = Namespace.by_path(namespace_name)
......
...@@ -17,28 +17,28 @@ module Commits ...@@ -17,28 +17,28 @@ module Commits
def commit def commit
revert_into = @create_merge_request ? @commit.revert_branch_name : @target_branch revert_into = @create_merge_request ? @commit.revert_branch_name : @target_branch
revert_tree_id = repository.check_revert_content(@commit, @target_branch)
if @create_merge_request if revert_tree_id
# Temporary branch exists and contains the revert commit create_target_branch(revert_into) if @create_merge_request
return success if repository.find_branch(revert_into)
create_target_branch repository.revert(current_user, @commit, revert_into, revert_tree_id)
end success
else
unless repository.revert(current_user, @commit, revert_into)
error_msg = "Sorry, we cannot revert this #{params[:revert_type_title]} automatically. error_msg = "Sorry, we cannot revert this #{params[:revert_type_title]} automatically.
It may have already been reverted, or a more recent commit may have updated some of its content." It may have already been reverted, or a more recent commit may have updated some of its content."
raise ReversionError, error_msg raise ReversionError, error_msg
end end
success
end end
private private
def create_target_branch def create_target_branch(new_branch)
# Temporary branch exists and contains the revert commit
return success if repository.find_branch(new_branch)
result = CreateBranchService.new(@project, current_user) result = CreateBranchService.new(@project, current_user)
.execute(@commit.revert_branch_name, @target_branch, source_project: @source_project) .execute(new_branch, @target_branch, source_project: @source_project)
if result[:status] == :error if result[:status] == :error
raise ReversionError, "There was an error creating the source branch: #{result[:message]}" raise ReversionError, "There was an error creating the source branch: #{result[:message]}"
......
require 'securerandom' require 'securerandom'
# Compare 2 branches for one repo or between repositories # Compare 2 branches for one repo or between repositories
# and return Gitlab::CompareResult object that responds to commits and diffs # and return Gitlab::Git::Compare object that responds to commits and diffs
class CompareService class CompareService
def execute(source_project, source_branch, target_project, target_branch, diff_options = {}) def execute(source_project, source_branch, target_project, target_branch, diff_options = {})
source_commit = source_project.commit(source_branch) source_commit = source_project.commit(source_branch)
...@@ -20,12 +20,10 @@ class CompareService ...@@ -20,12 +20,10 @@ class CompareService
) )
end end
Gitlab::CompareResult.new( Gitlab::Git::Compare.new(
Gitlab::Git::Compare.new( target_project.repository.raw_repository,
target_project.repository.raw_repository, target_branch,
target_branch, source_sha,
source_sha,
), diff_options
) )
end end
end end
...@@ -14,6 +14,7 @@ class GitPushService < BaseService ...@@ -14,6 +14,7 @@ class GitPushService < BaseService
# 3. Recognizes cross-references from commit messages # 3. Recognizes cross-references from commit messages
# 4. Executes the project's web hooks # 4. Executes the project's web hooks
# 5. Executes the project's services # 5. Executes the project's services
# 6. Checks if the project's main language has changed
# #
def execute def execute
@project.repository.after_push_commit(branch_name) @project.repository.after_push_commit(branch_name)
...@@ -42,11 +43,24 @@ class GitPushService < BaseService ...@@ -42,11 +43,24 @@ class GitPushService < BaseService
@push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev]) @push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev])
process_commit_messages process_commit_messages
end end
# Checks if the main language has changed in the project and if so
# it updates it accordingly
update_main_language
# Update merge requests that may be affected by this push. A new branch # Update merge requests that may be affected by this push. A new branch
# could cause the last commit of a merge request to change. # could cause the last commit of a merge request to change.
update_merge_requests update_merge_requests
end end
def update_main_language
current_language = @project.repository.main_language
unless current_language == @project.main_language
return @project.update_attributes(main_language: current_language)
end
true
end
protected protected
def update_merge_requests def update_merge_requests
......
...@@ -5,9 +5,7 @@ module MergeRequests ...@@ -5,9 +5,7 @@ module MergeRequests
# Set MR attributes # Set MR attributes
merge_request.can_be_created = false merge_request.can_be_created = false
merge_request.compare_failed = false
merge_request.compare_commits = [] merge_request.compare_commits = []
merge_request.compare_diffs = []
merge_request.source_project = project unless merge_request.source_project merge_request.source_project = project unless merge_request.source_project
merge_request.target_project ||= (project.forked_from_project || project) merge_request.target_project ||= (project.forked_from_project || project)
merge_request.target_branch ||= merge_request.target_project.default_branch merge_request.target_branch ||= merge_request.target_project.default_branch
...@@ -21,35 +19,23 @@ module MergeRequests ...@@ -21,35 +19,23 @@ module MergeRequests
return build_failed(merge_request, message) return build_failed(merge_request, message)
end end
compare_result = CompareService.new.execute( compare = CompareService.new.execute(
merge_request.source_project, merge_request.source_project,
merge_request.source_branch, merge_request.source_branch,
merge_request.target_project, merge_request.target_project,
merge_request.target_branch, merge_request.target_branch,
) )
commits = compare_result.commits commits = compare.commits
# At this point we decide if merge request can be created # At this point we decide if merge request can be created
# If we have at least one commit to merge -> creation allowed # If we have at least one commit to merge -> creation allowed
if commits.present? if commits.present?
merge_request.compare_commits = Commit.decorate(commits, merge_request.source_project) merge_request.compare_commits = Commit.decorate(commits, merge_request.source_project)
merge_request.can_be_created = true merge_request.can_be_created = true
merge_request.compare_failed = false merge_request.compare = compare
# Try to collect diff for merge request.
diffs = compare_result.diffs
if diffs.present?
merge_request.compare_diffs = diffs
elsif diffs == false
merge_request.can_be_created = false
merge_request.compare_failed = true
end
else else
merge_request.can_be_created = false merge_request.can_be_created = false
merge_request.compare_failed = false
end end
commits = merge_request.compare_commits commits = merge_request.compare_commits
......
...@@ -66,7 +66,7 @@ class SystemNoteService ...@@ -66,7 +66,7 @@ class SystemNoteService
def self.change_label(noteable, project, author, added_labels, removed_labels) def self.change_label(noteable, project, author, added_labels, removed_labels)
labels_count = added_labels.count + removed_labels.count labels_count = added_labels.count + removed_labels.count
references = ->(label) { label.to_reference(:id) } references = ->(label) { label.to_reference(format: :id) }
added_labels = added_labels.map(&references).join(' ') added_labels = added_labels.map(&references).join(' ')
removed_labels = removed_labels.map(&references).join(' ') removed_labels = removed_labels.map(&references).join(' ')
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
%h3.page-title Report abuse %h3.page-title Report abuse
%p Please use this form to report users who create spam issues, comments or behave inappropriately. %p Please use this form to report users who create spam issues, comments or behave inappropriately.
%hr %hr
= form_for @abuse_report, html: { class: 'form-horizontal js-requires-input'} do |f| = form_for @abuse_report, html: { class: 'form-horizontal js-quick-submit js-requires-input'} do |f|
= f.hidden_field :user_id = f.hidden_field :user_id
- if @abuse_report.errors.any? - if @abuse_report.errors.any?
.alert.alert-danger .alert.alert-danger
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
.form-group .form-group
= f.label :message, class: 'control-label' = f.label :message, class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_area :message, class: "form-control js-quick-submit", rows: 2, required: true, value: sanitize(@ref_url) = f.text_area :message, class: "form-control", rows: 2, required: true, value: sanitize(@ref_url)
.help-block .help-block
Explain the problem with this user. If appropriate, provide a link to the relevant issue or comment. Explain the problem with this user. If appropriate, provide a link to the relevant issue or comment.
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
.js-broadcast-message-preview .js-broadcast-message-preview
= render_broadcast_message(@broadcast_message.message.presence || "Your message here") = render_broadcast_message(@broadcast_message.message.presence || "Your message here")
= form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-requires-input'} do |f| = form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-quick-submit js-requires-input'} do |f|
-if @broadcast_message.errors.any? -if @broadcast_message.errors.any?
.alert.alert-danger .alert.alert-danger
- @broadcast_message.errors.full_messages.each do |msg| - @broadcast_message.errors.full_messages.each do |msg|
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
.form-group .form-group
= f.label :message, class: 'control-label' = f.label :message, class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_area :message, class: "form-control js-quick-submit js-autosize", = f.text_area :message, class: "form-control js-autosize",
required: true, required: true,
data: { preview_path: preview_admin_broadcast_messages_path } data: { preview_path: preview_admin_broadcast_messages_path }
.form-group.js-toggle-colors-container .form-group.js-toggle-colors-container
......
- page_title "Keys", @user.name, "Users" - page_title "SSH Keys", @user.name, "Users"
= render 'admin/users/head' = render 'admin/users/head'
= render 'profiles/keys/key_table', admin: true = render 'profiles/keys/key_table', admin: true
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
.nav-controls .nav-controls
= form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field' = search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field', tabindex: "2"
= render 'explore/projects/dropdown' = render 'explore/projects/dropdown'
- if current_user.can_create_project? - if current_user.can_create_project?
= link_to new_project_path, class: 'btn btn-new' do = link_to new_project_path, class: 'btn btn-new' do
......
%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid }
%span.milestone-row
- project = issue.project
%strong #{project.name_with_namespace} &middot;
= link_to [project.namespace.becomes(Namespace), project, issue] do
%span.cgray ##{issue.iid}
= link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
.pull-right.assignee-icon
- if issue.assignee
= image_tag avatar_icon(issue.assignee, 16), class: "avatar s16"
.panel.panel-default
.panel-heading= title
%ul{ class: "well-list issues-sortable-list" }
- if issues
- issues.each do |issue|
= render 'issue', issue: issue
%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid }
%span.milestone-row
- project = merge_request.project
%strong #{project.name_with_namespace} &middot;
= link_to [project.namespace.becomes(Namespace), project, merge_request] do
%span.cgray ##{merge_request.iid}
= link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
.pull-right.assignee-icon
- if merge_request.assignee
= image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16"
.panel.panel-default
.panel-heading= title
%ul{ class: "well-list merge_requests-sortable-list" }
- if merge_requests
- merge_requests.each do |merge_request|
= render 'merge_request', merge_request: merge_request
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } = render 'shared/milestones/milestone',
.row milestone_path: dashboard_milestone_path(milestone.safe_title, title: milestone.title),
.col-sm-6 issues_path: issues_dashboard_path(milestone_title: milestone.title),
%strong merge_requests_path: merge_requests_dashboard_path(milestone_title: milestone.title),
= link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title) milestone: milestone,
.col-sm-6 dashboard: true
.pull-right.light #{milestone.percent_complete}% complete
.row
.col-sm-6
= link_to issues_dashboard_path(milestone_title: milestone.title) do
= pluralize milestone.issue_count, 'Issue'
&middot;
= link_to merge_requests_dashboard_path(milestone_title: milestone.title) do
= pluralize milestone.merge_requests_count, 'Merge Request'
.col-sm-6
= milestone_progress_bar(milestone)
.row
.col-sm-6
.expiration
= render 'shared/milestone_expired', milestone: milestone
.projects
- milestone.milestones.each do |milestone|
= link_to milestone_path(milestone) do
%span.label.label-gray
= milestone.project.name_with_namespace
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment