Commit dec16893 authored by Jacob Vosmaer's avatar Jacob Vosmaer

Merge remote-tracking branch 'dev_gitlab_org/master' into git-http-blacklist

Conflicts:
	CHANGELOG
parents af56c1dd d02a22ba
*.log
*.swp
.DS_Store
.bundle .bundle
.chef
.directory
.envrc
.gitlab_shell_secret
.idea
.rbenv-version
.rbx/ .rbx/
db/*.sqlite3
db/*.sqlite3-journal
log/*.log*
tmp/
.sass-cache/
coverage/*
backups/*
*.swp
public/uploads/
.ruby-version
.ruby-gemset .ruby-gemset
.ruby-version
.rvmrc .rvmrc
.rbenv-version .sass-cache/
.directory .secret
nohup.out
Vagrantfile
.vagrant .vagrant
config/gitlab.yml Vagrantfile
backups/*
config/aws.yml
config/database.yml config/database.yml
config/gitlab.yml
config/initializers/omniauth.rb config/initializers/omniauth.rb
config/initializers/rack_attack.rb config/initializers/rack_attack.rb
config/initializers/smtp_settings.rb config/initializers/smtp_settings.rb
config/unicorn.rb
config/resque.yml config/resque.yml
config/aws.yml config/unicorn.rb
coverage/*
db/*.sqlite3
db/*.sqlite3-journal
db/data.yml db/data.yml
.idea
.DS_Store
.chef
vendor/bundle/*
rails_best_practices_output.html
doc/code/* doc/code/*
.secret
*.log
public/uploads.*
public/assets/
.envrc
dump.rdb dump.rdb
log/*.log*
nohup.out
public/assets/
public/uploads.*
public/uploads/
rails_best_practices_output.html
tags tags
.gitlab_shell_secret tmp/
vendor/bundle/*
v 7.7.0 v 7.7.0
-
-
- Add Jetbrains Teamcity CI service (Jason Lippert)
-
-
- Mention notification level
- Markdown preview in wiki (Yuriy Glukhov)
- Raise group avatar filesize limit to 200kb
- OAuth applications feature
- Show user SSH keys in admin area
- Developer can push to protected branches option
- Set project path instead of project name in create form
- Block Git HTTP access after 10 failed authentication attempts - Block Git HTTP access after 10 failed authentication attempts
-
-
- Updates to the messages returned by API (sponsored by O'Reilly Media)
- New UI layout with side navigation
-
-
-
- Add alert message in case of outdated browser (IE < 10)
-
- Added API support for sorting projects
- Update gitlab_git to version 7.0.0.rc13
v 7.6.0 v 7.6.0
- Fork repository to groups - Fork repository to groups
- New rugged version - New rugged version
- Add CRON=1 backup setting for quiet backups - Add CRON=1 backup setting for quiet backups
- Fix failing wiki restore - Fix failing wiki restore
-
- Add optional Sidekiq MemoryKiller middleware (enabled via SIDEKIQ_MAX_RSS env variable) - Add optional Sidekiq MemoryKiller middleware (enabled via SIDEKIQ_MAX_RSS env variable)
-
-
- Monokai highlighting style now more faithful to original design (Mark Riedesel) - Monokai highlighting style now more faithful to original design (Mark Riedesel)
- Create project with repository in synchrony - Create project with repository in synchrony
- Added ability to create empty repo or import existing one if project does not have repository - Added ability to create empty repo or import existing one if project does not have repository
-
-
- Reactivate highlight.js language autodetection - Reactivate highlight.js language autodetection
- Mobile UI improvements - Mobile UI improvements
-
- Change maximum avatar file size from 100KB to 200KB - Change maximum avatar file size from 100KB to 200KB
- Strict validation for snippet file names - Strict validation for snippet file names
- Enable Markdown preview for issues, merge requests, milestones, and notes (Vinnie Okada) - Enable Markdown preview for issues, merge requests, milestones, and notes (Vinnie Okada)
...@@ -25,9 +42,21 @@ v 7.6.0 ...@@ -25,9 +42,21 @@ v 7.6.0
- Update Sidekiq to version 2.17.8 - Update Sidekiq to version 2.17.8
- Add author filter to project issues and merge requests pages - Add author filter to project issues and merge requests pages
- Atom feed for user activity - Atom feed for user activity
- Support multiple omniauth providers for the same user
- Rendering cross reference in issue title and tooltip for merge request
- Show username in comments
- Possibility to create Milestones or Labels when Issues are disabled
- Fix bug with showing gpg signature in tag
v 7.5.3
- Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2)
v 7.5.2 v 7.5.2
- Don't log Sidekiq arguments by default - Don't log Sidekiq arguments by default
- Fix restore of wiki repositories from backups
v 7.5.1
- Add missing timestamps to 'members' table
v 7.5.0 v 7.5.0
- API: Add support for Hipchat (Kevin Houdebert) - API: Add support for Hipchat (Kevin Houdebert)
...@@ -47,7 +76,7 @@ v 7.5.0 ...@@ -47,7 +76,7 @@ v 7.5.0
- Performance improvements - Performance improvements
- Fix post-receive issue for projects with deleted forks - Fix post-receive issue for projects with deleted forks
- New gitlab-shell version with custom hooks support - New gitlab-shell version with custom hooks support
- Improve code - Improve code
- GitLab CI 5.2+ support (does not support older versions) - GitLab CI 5.2+ support (does not support older versions)
- Fixed bug when you can not push commits starting with 000000 to protected branches - Fixed bug when you can not push commits starting with 000000 to protected branches
- Added a password strength indicator - Added a password strength indicator
......
...@@ -101,6 +101,16 @@ Please ensure you support the feature you contribute through all of these steps. ...@@ -101,6 +101,16 @@ Please ensure you support the feature you contribute through all of these steps.
1. Community questions answered 1. Community questions answered
1. Answers to questions radiated (in docs/wiki/etc.) 1. Answers to questions radiated (in docs/wiki/etc.)
If you add a dependency in GitLab (such as an operating system package) please consider updating the following and note the applicability of each in your merge request:
1. Note the addition in the release blog post (create one if it doesn't exist yet) https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/
1. Upgrade guide, for example https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/7.5-to-7.6.md
1. Upgrader https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/upgrader.md#2-run-gitlab-upgrade-tool
1. Installation guide https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies
1. GitLab Development Kit https://gitlab.com/gitlab-org/gitlab-development-kit
1. Test suite https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/examples/configure_a_runner_to_run_the_gitlab_ce_test_suite.md
1. Omnibus package creator https://gitlab.com/gitlab-org/omnibus-gitlab
## Merge request description format ## Merge request description format
1. What does this MR do? 1. What does this MR do?
...@@ -118,6 +128,7 @@ Please ensure you support the feature you contribute through all of these steps. ...@@ -118,6 +128,7 @@ Please ensure you support the feature you contribute through all of these steps.
1. Can merge without problems (if not please merge `master`, never rebase commits pushed to the remote server) 1. Can merge without problems (if not please merge `master`, never rebase commits pushed to the remote server)
1. Does not break any existing functionality 1. Does not break any existing functionality
1. Fixes one specific issue or implements one specific feature (do not combine things, send separate merge requests if needed) 1. Fixes one specific issue or implements one specific feature (do not combine things, send separate merge requests if needed)
1. Migrations should do only one thing (eg: either create a table, move data 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 since they complicate future changes
......
...@@ -28,10 +28,16 @@ gem 'omniauth-google-oauth2' ...@@ -28,10 +28,16 @@ gem 'omniauth-google-oauth2'
gem 'omniauth-twitter' gem 'omniauth-twitter'
gem 'omniauth-github' gem 'omniauth-github'
gem 'omniauth-shibboleth' gem 'omniauth-shibboleth'
gem 'omniauth-kerberos'
gem 'doorkeeper', '2.0.1'
gem "rack-oauth2", "~> 1.0.5"
# Browser detection
gem "browser"
# 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", '7.0.0.rc12' gem "gitlab_git", '7.0.0.rc13'
# Ruby/Rack Git Smart-HTTP Server Handler # Ruby/Rack Git Smart-HTTP Server Handler
gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack' gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
...@@ -89,7 +95,7 @@ gem "github-markup" ...@@ -89,7 +95,7 @@ gem "github-markup"
gem 'redcarpet', '~> 3.1.2' gem 'redcarpet', '~> 3.1.2'
gem 'RedCloth' gem 'RedCloth'
gem 'rdoc', '~>3.6' gem 'rdoc', '~>3.6'
gem 'org-ruby', '= 0.9.9' gem 'org-ruby', '= 0.9.12'
gem 'creole', '~>0.3.6' gem 'creole', '~>0.3.6'
gem 'wikicloth', '=0.8.1' gem 'wikicloth', '=0.8.1'
gem 'asciidoctor', '= 0.1.4' gem 'asciidoctor', '= 0.1.4'
......
...@@ -37,6 +37,7 @@ GEM ...@@ -37,6 +37,7 @@ GEM
rake (>= 0.8.7) rake (>= 0.8.7)
arel (5.0.1.20140414130214) arel (5.0.1.20140414130214)
asciidoctor (0.1.4) asciidoctor (0.1.4)
attr_required (1.0.0)
awesome_print (1.2.0) awesome_print (1.2.0)
axiom-types (0.0.5) axiom-types (0.0.5)
descendants_tracker (~> 0.0.1) descendants_tracker (~> 0.0.1)
...@@ -49,6 +50,7 @@ GEM ...@@ -49,6 +50,7 @@ GEM
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
bootstrap-sass (3.0.3.0) bootstrap-sass (3.0.3.0)
sass (~> 3.2) sass (~> 3.2)
browser (0.7.2)
builder (3.2.2) builder (3.2.2)
capybara (2.2.1) capybara (2.2.1)
mime-types (>= 1.16) mime-types (>= 1.16)
...@@ -107,6 +109,8 @@ GEM ...@@ -107,6 +109,8 @@ GEM
diff-lcs (1.2.5) diff-lcs (1.2.5)
diffy (3.0.3) diffy (3.0.3)
docile (1.1.5) docile (1.1.5)
doorkeeper (2.0.1)
railties (>= 3.1)
dotenv (0.9.0) dotenv (0.9.0)
dropzonejs-rails (0.4.14) dropzonejs-rails (0.4.14)
rails (> 3.1) rails (> 3.1)
...@@ -120,7 +124,7 @@ GEM ...@@ -120,7 +124,7 @@ GEM
equalizer (0.0.8) equalizer (0.0.8)
erubis (2.7.0) erubis (2.7.0)
escape_utils (0.2.4) escape_utils (0.2.4)
eventmachine (1.0.3) eventmachine (1.0.4)
excon (0.32.1) excon (0.32.1)
execjs (2.0.2) execjs (2.0.2)
expression_parser (0.9.0) expression_parser (0.9.0)
...@@ -158,7 +162,7 @@ GEM ...@@ -158,7 +162,7 @@ GEM
dotenv (>= 0.7) dotenv (>= 0.7)
thor (>= 0.13.6) thor (>= 0.13.6)
formatador (0.2.4) formatador (0.2.4)
gemnasium-gitlab-service (0.2.2) gemnasium-gitlab-service (0.2.3)
rugged (~> 0.19) rugged (~> 0.19)
gherkin-ruby (0.3.1) gherkin-ruby (0.3.1)
racc racc
...@@ -179,7 +183,7 @@ GEM ...@@ -179,7 +183,7 @@ GEM
mime-types (~> 1.19) mime-types (~> 1.19)
gitlab_emoji (0.0.1.1) gitlab_emoji (0.0.1.1)
emoji (~> 1.0.1) emoji (~> 1.0.1)
gitlab_git (7.0.0.rc12) gitlab_git (7.0.0.rc13)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0) gitlab-linguist (~> 3.0)
...@@ -250,6 +254,7 @@ GEM ...@@ -250,6 +254,7 @@ GEM
json (~> 1.8) json (~> 1.8)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
httpauth (0.2.1) httpauth (0.2.1)
httpclient (2.5.3.3)
i18n (0.6.11) i18n (0.6.11)
ice_nine (0.10.0) ice_nine (0.10.0)
jasmine (2.0.2) jasmine (2.0.2)
...@@ -275,7 +280,7 @@ GEM ...@@ -275,7 +280,7 @@ GEM
kaminari (0.15.1) kaminari (0.15.1)
actionpack (>= 3.0.0) actionpack (>= 3.0.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
kgio (2.8.1) kgio (2.9.2)
launchy (2.4.2) launchy (2.4.2)
addressable (~> 2.3) addressable (~> 2.3)
letter_opener (1.1.2) letter_opener (1.1.2)
...@@ -322,6 +327,11 @@ GEM ...@@ -322,6 +327,11 @@ GEM
omniauth-google-oauth2 (0.2.5) omniauth-google-oauth2 (0.2.5)
omniauth (> 1.0) omniauth (> 1.0)
omniauth-oauth2 (~> 1.1) omniauth-oauth2 (~> 1.1)
omniauth-kerberos (0.2.0)
omniauth-multipassword
timfel-krb5-auth (~> 0.8)
omniauth-multipassword (0.4.1)
omniauth (~> 1.0)
omniauth-oauth (1.0.1) omniauth-oauth (1.0.1)
oauth oauth
omniauth (~> 1.0) omniauth (~> 1.0)
...@@ -333,7 +343,7 @@ GEM ...@@ -333,7 +343,7 @@ GEM
omniauth-twitter (1.0.1) omniauth-twitter (1.0.1)
multi_json (~> 1.3) multi_json (~> 1.3)
omniauth-oauth (~> 1.0) omniauth-oauth (~> 1.0)
org-ruby (0.9.9) org-ruby (0.9.12)
rubypants (~> 0.2) rubypants (~> 0.2)
orm_adapter (0.5.0) orm_adapter (0.5.0)
pg (0.15.1) pg (0.15.1)
...@@ -363,6 +373,12 @@ GEM ...@@ -363,6 +373,12 @@ GEM
rack (>= 1.1.3) rack (>= 1.1.3)
rack-mount (0.8.3) rack-mount (0.8.3)
rack (>= 1.0.0) rack (>= 1.0.0)
rack-oauth2 (1.0.8)
activesupport (>= 2.3)
attr_required (>= 0.0.5)
httpclient (>= 2.2.0.2)
multi_json (>= 1.3.6)
rack (>= 1.1)
rack-protection (1.5.1) rack-protection (1.5.1)
rack rack
rack-test (0.6.2) rack-test (0.6.2)
...@@ -393,7 +409,7 @@ GEM ...@@ -393,7 +409,7 @@ GEM
activesupport (= 4.1.1) activesupport (= 4.1.1)
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
raindrops (0.12.0) raindrops (0.13.0)
rake (10.3.2) rake (10.3.2)
raphael-rails (2.1.2) raphael-rails (2.1.2)
rb-fsevent (0.9.3) rb-fsevent (0.9.3)
...@@ -531,6 +547,7 @@ GEM ...@@ -531,6 +547,7 @@ GEM
thread_safe (0.3.4) thread_safe (0.3.4)
tilt (1.4.1) tilt (1.4.1)
timers (1.1.0) timers (1.1.0)
timfel-krb5-auth (0.8)
tinder (1.9.3) tinder (1.9.3)
eventmachine (~> 1.0) eventmachine (~> 1.0)
faraday (~> 0.8) faraday (~> 0.8)
...@@ -598,6 +615,7 @@ DEPENDENCIES ...@@ -598,6 +615,7 @@ DEPENDENCIES
better_errors better_errors
binding_of_caller binding_of_caller
bootstrap-sass (~> 3.0) bootstrap-sass (~> 3.0)
browser
capybara (~> 2.2.1) capybara (~> 2.2.1)
carrierwave carrierwave
coffee-rails coffee-rails
...@@ -610,6 +628,7 @@ DEPENDENCIES ...@@ -610,6 +628,7 @@ DEPENDENCIES
devise (= 3.2.4) devise (= 3.2.4)
devise-async (= 0.9.0) devise-async (= 0.9.0)
diffy (~> 3.0.3) diffy (~> 3.0.3)
doorkeeper (= 2.0.1)
dropzonejs-rails dropzonejs-rails
email_spec email_spec
enumerize enumerize
...@@ -624,7 +643,7 @@ DEPENDENCIES ...@@ -624,7 +643,7 @@ DEPENDENCIES
gitlab-grack (~> 2.0.0.pre) gitlab-grack (~> 2.0.0.pre)
gitlab-linguist (~> 3.0.0) gitlab-linguist (~> 3.0.0)
gitlab_emoji (~> 0.0.1.1) gitlab_emoji (~> 0.0.1.1)
gitlab_git (= 7.0.0.rc12) gitlab_git (= 7.0.0.rc13)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.2.0) gitlab_omniauth-ldap (= 1.2.0)
gollum-lib (~> 3.0.0) gollum-lib (~> 3.0.0)
...@@ -655,9 +674,10 @@ DEPENDENCIES ...@@ -655,9 +674,10 @@ DEPENDENCIES
omniauth (~> 1.1.3) omniauth (~> 1.1.3)
omniauth-github omniauth-github
omniauth-google-oauth2 omniauth-google-oauth2
omniauth-kerberos
omniauth-shibboleth omniauth-shibboleth
omniauth-twitter omniauth-twitter
org-ruby (= 0.9.9) org-ruby (= 0.9.12)
pg pg
poltergeist (~> 1.5.1) poltergeist (~> 1.5.1)
pry pry
...@@ -665,6 +685,7 @@ DEPENDENCIES ...@@ -665,6 +685,7 @@ DEPENDENCIES
rack-attack rack-attack
rack-cors rack-cors
rack-mini-profiler rack-mini-profiler
rack-oauth2 (~> 1.0.5)
rails (~> 4.1.0) rails (~> 4.1.0)
rails_autolink (~> 1.1) rails_autolink (~> 1.1)
rails_best_practices rails_best_practices
......
...@@ -104,3 +104,10 @@ This merge request has been closed because a request for more information has no ...@@ -104,3 +104,10 @@ This merge request has been closed because a request for more information has no
### Accepting merge requests ### Accepting merge requests
Is there a request on [the feature request forum](http://feedback.gitlab.com/forums/176466-general) that is similar to this? If so, can you make a comment with a link to it? Please be aware that new functionality that is not marked [accepting merge/pull requests](http://feedback.gitlab.com/forums/176466-general/status/796455) on the forum might not make it into GitLab. You might be asked to make changes and even after implementing them your feature might still be declined. If you want to reduce the chance of this happening please have a discussion in the forum first. Is there a request on [the feature request forum](http://feedback.gitlab.com/forums/176466-general) that is similar to this? If so, can you make a comment with a link to it? Please be aware that new functionality that is not marked [accepting merge/pull requests](http://feedback.gitlab.com/forums/176466-general/status/796455) on the forum might not make it into GitLab. You might be asked to make changes and even after implementing them your feature might still be declined. If you want to reduce the chance of this happening please have a discussion in the forum first.
### Only accepting merge requests with green tests
We can only accept a merge request if all the tests are green. I've just
restarted the build. When the tests are still not passing after this restart and
you're sure that is does not have anything to do with your code changes, please
rebase with master to see if that solves the issue.
web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"} web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"}
worker: bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,common,default,gitlab_shell worker: bundle exec sidekiq -q post_receive -q mailer -q system_hook -q project_web_hook -q gitlab_shell -q common -q default
...@@ -52,69 +52,30 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a ...@@ -52,69 +52,30 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a
Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/) for the various options. Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/) for the various options.
Since a manual installation is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm). Since a manual installation is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm).
You can access new installation with the login `root` and password `5iveL!fe`, after login you are required to set a unique password.
## Third-party applications ## Third-party applications
There are a lot of applications and API wrappers for GitLab. There are a lot of applications and API wrappers for GitLab.
Find them [on our website](https://about.gitlab.com/applications/). Find them [on our website](https://about.gitlab.com/applications/).
### New versions ## New versions
Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases come out when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG). For more information about the release process see the release [documentation](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/release). Features that will likely be in the next releases can be found on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457). Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases come out when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG). For more information about the release process see the release [documentation](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/release). Features that will likely be in the next releases can be found on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457).
### Upgrading ## Upgrading
For updating the the Omnibus installation please see the [update documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md). For manual installations there is an [upgrader script](doc/update/upgrader.md) and there are [upgrade guides](doc/update). For updating the the Omnibus installation please see the [update documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md). For manual installations there is an [upgrader script](doc/update/upgrader.md) and there are [upgrade guides](doc/update).
## Run in production mode
The Installation guide contains instructions on how to download an init script and run it automatically on boot. You can also start the init script manually:
sudo service gitlab start
or by directly calling the script:
sudo /etc/init.d/gitlab start
Please login with `root` / `5iveL!fe`
## Install a development environment ## Install a development environment
We recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit). We recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
If you do not use the development kit you might need to copy the example development unicorn configuration file If you do not use the GitLab Development Development kit you need to install and setup all the dependencies yourself, this is a lot of work and error prone.
One small thing you also have to do when installing it yourself is to copy the example development unicorn configuration file:
cp config/unicorn.rb.example.development config/unicorn.rb cp config/unicorn.rb.example.development config/unicorn.rb
## Run in development mode Instructions on how to start Gitlab and how to run the tests can be found in the [development section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#development).
Start it with [Foreman](https://github.com/ddollar/foreman)
bundle exec foreman start -p 3000
or start each component separately:
bundle exec rails s
bin/background_jobs start
And surf to [localhost:3000](http://localhost:3000/) and login with `root` / `5iveL!fe`.
## Run the tests
- Run all tests:
bundle exec rake test
- [RSpec](http://rspec.info/) unit and functional tests.
All RSpec tests: `bundle exec rake spec`
Single RSpec file: `bundle exec rspec spec/controllers/commit_controller_spec.rb`
- [Spinach](https://github.com/codegram/spinach) integration tests.
All Spinach tests: `bundle exec rake spinach`
Single Spinach test: `bundle exec spinach features/project/issues/milestones.feature`
## Documentation ## Documentation
......
7.6.0.pre 7.7.0.pre
...@@ -12,7 +12,7 @@ class @Activities ...@@ -12,7 +12,7 @@ class @Activities
toggleFilter: (sender) -> toggleFilter: (sender) ->
sender.parent().toggleClass "inactive" sender.parent().toggleClass "active"
event_filters = $.cookie("event_filter") event_filters = $.cookie("event_filter")
filter = sender.attr("id").split("_")[0] filter = sender.attr("id").split("_")[0]
if event_filters if event_filters
......
@Api = @Api =
groups_path: "/api/:version/groups.json"
group_path: "/api/:version/groups/:id.json"
users_path: "/api/:version/users.json" users_path: "/api/:version/users.json"
user_path: "/api/:version/users/:id.json" user_path: "/api/:version/users/:id.json"
notes_path: "/api/:version/projects/:id/notes.json" notes_path: "/api/:version/projects/:id/notes.json"
...@@ -51,6 +53,33 @@ ...@@ -51,6 +53,33 @@
).done (users) -> ).done (users) ->
callback(users) callback(users)
group: (group_id, callback) ->
url = Api.buildUrl(Api.group_path)
url = url.replace(':id', group_id)
$.ajax(
url: url
data:
private_token: gon.api_token
dataType: "json"
).done (group) ->
callback(group)
# Return groups list. Filtered by query
# Only active groups retrieved
groups: (query, skip_ldap, callback) ->
url = Api.buildUrl(Api.groups_path)
$.ajax(
url: url
data:
private_token: gon.api_token
search: query
per_page: 20
dataType: "json"
).done (groups) ->
callback(groups)
# Return project users list. Filtered by query # Return project users list. Filtered by query
# Only active users retrieved # Only active users retrieved
projectUsers: (project_id, query, callback) -> projectUsers: (project_id, query, callback) ->
......
...@@ -51,12 +51,6 @@ window.ajaxGet = (url) -> ...@@ -51,12 +51,6 @@ window.ajaxGet = (url) ->
window.showAndHide = (selector) -> window.showAndHide = (selector) ->
window.errorMessage = (message) ->
ehtml = $("<p>")
ehtml.addClass("error_message")
ehtml.html(message)
ehtml
window.split = (val) -> window.split = (val) ->
return val.split( /,\s*/ ) return val.split( /,\s*/ )
......
class @GroupsSelect
constructor: ->
$('.ajax-groups-select').each (i, select) =>
skip_ldap = $(select).hasClass('skip_ldap')
$(select).select2
placeholder: "Search for a group"
multiple: $(select).hasClass('multiselect')
minimumInputLength: 0
query: (query) ->
Api.groups query.term, skip_ldap, (groups) ->
data = { results: groups }
query.callback(data)
initSelection: (element, callback) ->
id = $(element).val()
if id isnt ""
Api.group(id, callback)
formatResult: (args...) =>
@formatResult(args...)
formatSelection: (args...) =>
@formatSelection(args...)
dropdownCssClass: "ajax-groups-dropdown"
escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
m
formatResult: (group) ->
if group.avatar_url
avatar = group.avatar_url
else
avatar = gon.default_avatar_url
"<div class='group-result'>
<div class='group-name'>#{group.name}</div>
<div class='group-path'>#{group.path}</div>
</div>"
formatSelection: (group) ->
group.name
class @Issue class @Issue
constructor: -> constructor: ->
$('.edit-issue.inline-update input[type="submit"]').hide() $('.edit-issue.inline-update input[type="submit"]').hide()
$(".issue-box .inline-update").on "change", "select", -> $(".context .inline-update").on "change", "select", ->
$(this).submit() $(this).submit()
$(".issue-box .inline-update").on "change", "#issue_assignee_id", -> $(".context .inline-update").on "change", "#issue_assignee_id", ->
$(this).submit() $(this).submit()
if $("a.btn-close").length if $("a.btn-close").length
......
...@@ -26,9 +26,9 @@ class @MergeRequest ...@@ -26,9 +26,9 @@ class @MergeRequest
initContextWidget: -> initContextWidget: ->
$('.edit-merge_request.inline-update input[type="submit"]').hide() $('.edit-merge_request.inline-update input[type="submit"]').hide()
$(".issue-box .inline-update").on "change", "select", -> $(".context .inline-update").on "change", "select", ->
$(this).submit() $(this).submit()
$(".issue-box .inline-update").on "change", "#merge_request_assignee_id", -> $(".context .inline-update").on "change", "#merge_request_assignee_id", ->
$(this).submit() $(this).submit()
initMergeWidget: -> initMergeWidget: ->
...@@ -89,6 +89,9 @@ class @MergeRequest ...@@ -89,6 +89,9 @@ class @MergeRequest
this.$('.merge-request-tabs .diffs-tab').addClass 'active' this.$('.merge-request-tabs .diffs-tab').addClass 'active'
this.loadDiff() unless @diffs_loaded this.loadDiff() unless @diffs_loaded
this.$('.diffs').show() this.$('.diffs').show()
when 'commits'
this.$('.merge-request-tabs .commits-tab').addClass 'active'
this.$('.commits').show()
else else
this.$('.merge-request-tabs .notes-tab').addClass 'active' this.$('.merge-request-tabs .notes-tab').addClass 'active'
this.$('.notes').show() this.$('.notes').show()
......
...@@ -375,7 +375,7 @@ class @Notes ...@@ -375,7 +375,7 @@ class @Notes
### ###
addDiffNote: (e) => addDiffNote: (e) =>
e.preventDefault() e.preventDefault()
link = e.target link = e.currentTarget
form = $(".js-new-note-form") form = $(".js-new-note-form")
row = $(link).closest("tr") row = $(link).closest("tr")
nextRow = row.next() nextRow = row.next()
......
$ ->
$(":checkbox").change ->
name = $(this).attr("name")
if name == "developers_can_push"
id = $(this).val()
checked = $(this).is(":checked")
url = $(this).data("url")
$.ajax
type: "PUT"
url: url
dataType: "json"
data:
id: id
developers_can_push: checked
success: ->
new Flash("Branch updated.", "notice")
location.reload true
error: ->
new Flash("Failed to update branch!", "alert")
...@@ -46,7 +46,7 @@ class @ContributorsGraph ...@@ -46,7 +46,7 @@ class @ContributorsGraph
class @ContributorsMasterGraph extends ContributorsGraph class @ContributorsMasterGraph extends ContributorsGraph
constructor: (@data) -> constructor: (@data) ->
@width = $('.container').width() - 70 @width = $('.container').width() - 345
@height = 200 @height = 200
@x = null @x = null
@y = null @y = null
...@@ -119,7 +119,7 @@ class @ContributorsMasterGraph extends ContributorsGraph ...@@ -119,7 +119,7 @@ class @ContributorsMasterGraph extends ContributorsGraph
class @ContributorsAuthorGraph extends ContributorsGraph class @ContributorsAuthorGraph extends ContributorsGraph
constructor: (@data) -> constructor: (@data) ->
@width = $('.container').width()/2 - 100 @width = $('.container').width()/2 - 225
@height = 200 @height = 200
@x = null @x = null
@y = null @y = null
......
...@@ -207,24 +207,16 @@ li.note { ...@@ -207,24 +207,16 @@ li.note {
} }
} }
.no-ssh-key-message { .browser-alert {
padding: 10px 0; padding: 10px;
background: #C67;
margin: 0;
color: #FFF;
margin-top: -1px;
text-align: center; text-align: center;
background: #C67;
color: #fff;
font-weight: bold;
a { a {
color: #fff; color: #fff;
text-decoration: underline; text-decoration: underline;
} }
.links-xs {
text-align: center;
font-size: 16px;
padding: 5px;
}
} }
.warning_message { .warning_message {
...@@ -282,7 +274,7 @@ img.emoji { ...@@ -282,7 +274,7 @@ img.emoji {
} }
.navless-container { .navless-container {
margin-top: 20px; margin-top: 68px;
} }
.description-block { .description-block {
...@@ -300,11 +292,17 @@ table { ...@@ -300,11 +292,17 @@ table {
.dashboard-intro-icon { .dashboard-intro-icon {
float: left; float: left;
text-align: center;
font-size: 32px; font-size: 32px;
color: #AAA; color: #AAA;
padding: 5px 0; width: 60px;
width: 50px; }
min-height: 100px;
.dashboard-intro-text {
display: inline-block;
margin-left: -60px;
padding-left: 60px;
width: 100%;
} }
.broadcast-message { .broadcast-message {
...@@ -355,3 +353,9 @@ table { ...@@ -355,3 +353,9 @@ table {
.task-status { .task-status {
margin-left: 10px; margin-left: 10px;
} }
#nprogress .spinner {
top: auto !important;
bottom: 20px !important;
left: 20px !important;
}
...@@ -31,7 +31,12 @@ fieldset legend { ...@@ -31,7 +31,12 @@ fieldset legend {
margin-bottom: 18px; margin-bottom: 18px;
background-color: whitesmoke; background-color: whitesmoke;
border-top: 1px solid #e5e5e5; border-top: 1px solid #e5e5e5;
padding-left: 17%; }
@media (min-width: $screen-sm-min) {
.form-actions {
padding-left: 17%;
}
} }
label { label {
...@@ -88,7 +93,8 @@ label { ...@@ -88,7 +93,8 @@ label {
@include box-shadow(none); @include box-shadow(none);
} }
.issuable-description { .issuable-description,
.wiki-content {
margin-top: 35px; margin-top: 35px;
} }
......
/** /**
* Issue box: * Issue box for showing Open/Closed state:
* Huge block (one per page) for storing title, descripion and other information.
* Used for Issue#show page, MergeRequest#show page etc * Used for Issue#show page, MergeRequest#show page etc
* *
* CLasses:
* .issue-box - Regular box
*/ */
.issue-box { .issue-box {
color: #555; display: inline-block;
margin:20px 0; padding: 7px 13px;
background: $box_bg; font-weight: normal;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09)); margin-right: 5px;
&.issue-box-closed { &.issue-box-closed {
.state { background-color: $bg_danger;
background-color: #F3CECE; color: #FFF;
border-color: $border_danger;
}
.state-label {
background-color: $bg_danger;
color: #FFF;
}
} }
&.issue-box-merged { &.issue-box-merged {
.state { background-color: $bg_primary;
background-color: #B7CEE7; color: #FFF;
border-color: $border_primary;
}
.state-label {
background-color: $bg_primary;
color: #FFF;
}
} }
&.issue-box-open { &.issue-box-open {
.state { background-color: $bg_success;
background-color: #D6F1D7; color: #FFF;
border-color: $bg_success;
}
.state-label {
background-color: $bg_success;
color: #FFF;
}
} }
&.issue-box-expired { &.issue-box-expired {
.state { background: #cea61b;
background-color: #EEE9B3; color: #FFF;
border-color: #faebcc;
}
.state-label {
background: #cea61b;
color: #FFF;
}
}
.control-group {
margin-bottom: 0;
}
.state {
background-color: #f9f9f9;
}
.title {
font-size: 28px;
font-weight: normal;
line-height: 1.5;
margin: 0;
color: #333;
padding: 10px 15px;
}
.context {
border: none;
border-top: 1px solid #eee;
padding: 10px 15px;
// Reset text align for children
.text-right > * { text-align: left; }
@media (max-width: $screen-xs-max) {
// Don't right align on mobile
.text-right { text-align: left; }
.row .col-md-6 {
padding-top: 5px;
}
}
}
.description {
padding: 0 15px 10px 15px;
code {
white-space: pre-wrap;
}
}
.title, .context, .description {
.clearfix {
margin: 0;
}
}
.state-label {
font-size: 14px;
float: left;
font-weight: bold;
padding: 10px 15px;
}
.cross-project-ref {
float: left;
padding: 10px 15px;
}
.creator {
float: right;
padding: 10px 15px;
a {
text-decoration: underline;
}
} }
} }
...@@ -65,6 +65,7 @@ ...@@ -65,6 +65,7 @@
.edit_note, .edit_note,
.issuable-description, .issuable-description,
.milestone-description, .milestone-description,
.wiki-content,
.merge-request-form { .merge-request-form {
.nav-tabs { .nav-tabs {
margin-bottom: 0; margin-bottom: 0;
......
...@@ -116,6 +116,18 @@ select { ...@@ -116,6 +116,18 @@ select {
} }
} }
.group-result {
.group-image {
float: left;
}
.group-name {
font-weight: bold;
}
.group-path {
color: #999;
}
}
.user-result { .user-result {
.user-image { .user-image {
float: left; float: left;
......
table {
&.table {
tr {
td, th {
padding: 8px 10px;
line-height: 20px;
vertical-align: middle;
}
th {
font-weight: normal;
font-size: 15px;
border-bottom: 1px solid #CCC !important;
}
td {
border-color: #F1F1F1 !important;
border-bottom: 1px solid;
}
}
}
}
...@@ -74,6 +74,42 @@ ...@@ -74,6 +74,42 @@
} }
} }
} }
.system-note .timeline-entry-inner {
.timeline-icon {
background: none;
margin-left: 12px;
margin-top: 0;
@include box-shadow(none);
span {
margin: 0 2px;
font-size: 16px;
color: #eeeeee;
}
}
.timeline-content {
background: none;
margin-left: 45px;
padding: 0px 15px;
&:after { border: 0; }
.note-header {
span { font-size: 12px; }
.avatar {
margin-right: 5px;
}
}
.note-text {
font-size: 12px;
margin-left: 20px;
}
}
}
} }
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
......
...@@ -148,6 +148,10 @@ $list-group-active-bg: $bg_primary; ...@@ -148,6 +148,10 @@ $list-group-active-bg: $bg_primary;
color: #666; color: #666;
} }
.nav-compact > li > a {
padding: 6px 12px;
}
.nav-small > li > a { .nav-small > li > a {
padding: 3px 5px; padding: 3px 5px;
font-size: 12px; font-size: 12px;
......
...@@ -4,10 +4,6 @@ html { ...@@ -4,10 +4,6 @@ html {
&.touch .tooltip { display: none !important; } &.touch .tooltip { display: none !important; }
} }
body {
padding-bottom: 20px;
}
.container { .container {
padding-top: 0; padding-top: 0;
z-index: 5; z-index: 5;
......
...@@ -44,6 +44,6 @@ $added: #63c363; ...@@ -44,6 +44,6 @@ $added: #63c363;
$deleted: #f77; $deleted: #f77;
/** /**
* * NProgress customize
*/ */
$nprogress-color: #3498db; $nprogress-color: #c0392b;
...@@ -23,20 +23,6 @@ ...@@ -23,20 +23,6 @@
} }
} }
.dashboard {
.dash-filter {
width: 205px;
float: left;
height: inherit;
}
}
@media (max-width: 1200px) {
.dashboard .dash-filter {
width: 140px;
}
}
.dash-sidebar-tabs { .dash-sidebar-tabs {
margin-bottom: 2px; margin-bottom: 2px;
border: none; border: none;
......
...@@ -140,43 +140,17 @@ ...@@ -140,43 +140,17 @@
} }
} }
/**
* Event filter
*
*/
.event_filter {
position: absolute;
width: 40px;
margin-left: -55px;
.filter_icon {
a {
text-align:center;
background: $bg_primary;
margin-bottom: 10px;
float: left;
padding: 9px 6px;
font-size: 18px;
width: 40px;
color: #FFF;
@include border-radius(3px);
}
&.inactive {
a {
color: #DDD;
background: #f9f9f9;
}
}
}
}
/* /*
* Last push widget * Last push widget
*/ */
.event-last-push { .event-last-push {
overflow: auto;
.event-last-push-text { .event-last-push-text {
@include str-truncated(75%); @include str-truncated(100%);
float:left;
margin-right: -150px;
padding-right: 150px;
line-height: 24px; line-height: 24px;
} }
} }
...@@ -203,3 +177,7 @@ ...@@ -203,3 +177,7 @@
} }
} }
} }
.event_filter li a {
padding: 5px 10px;
}
...@@ -4,9 +4,13 @@ ...@@ -4,9 +4,13 @@
*/ */
header { header {
&.navbar-gitlab { &.navbar-gitlab {
z-index: 100;
margin-bottom: 0; margin-bottom: 0;
min-height: 40px; min-height: 40px;
border: none; border: none;
position: fixed;
top: 0;
width: 100%;
.navbar-inner { .navbar-inner {
filter: none; filter: none;
...@@ -52,8 +56,6 @@ header { ...@@ -52,8 +56,6 @@ header {
border-width: 0; border-width: 0;
font-size: 18px; font-size: 18px;
.app_logo { margin-left: -15px; }
.title { .title {
@include str-truncated(70%); @include str-truncated(70%);
} }
...@@ -84,7 +86,10 @@ header { ...@@ -84,7 +86,10 @@ header {
} }
} }
z-index: 10; .container {
width: 100% !important;
padding-left: 0px;
}
/** /**
* *
...@@ -232,21 +237,6 @@ header { ...@@ -232,21 +237,6 @@ header {
color: #fff; color: #fff;
} }
} }
.app_logo {
.separator {
margin-left: 0;
margin-right: 0;
}
}
.separator {
float: left;
height: 46px;
width: 2px;
margin-left: 10px;
margin-right: 10px;
}
} }
.search .search-input { .search .search-input {
......
...@@ -162,3 +162,7 @@ form.edit-issue { ...@@ -162,3 +162,7 @@ form.edit-issue {
} }
} }
} }
.issue-title {
margin-top: 0;
}
/* Login Page */ /* Login Page */
.login-page { .login-page {
h1 { .container {
font-size: 3em; max-width: 960px;
font-weight: 200;
} }
.login-box{ .navbar-gitlab .container {
padding: 0 15px; max-width: none;
}
.login-heading h3 { .brand-holder {
font-weight: 300; font-size: 18px;
line-height: 2; line-height: 1.5;
}
.login-footer { p {
margin-top: 10px; color: #888;
} }
.btn { h1:first-child {
padding: 12px !important; font-weight: normal;
@extend .btn-block; margin-bottom: 30px;
} }
}
.brand-image {
img { img {
max-width: 100%; max-width: 100%;
margin-bottom: 20px; margin-bottom: 30px;
} }
&.default-brand-image { a {
margin: 0 80px; font-weight: bold;
} }
} }
.login-logo { .login-box{
margin: 10px 0 30px 0; background: #fafafa;
display: block; border-radius: 10px;
box-shadow: 0 0px 2px #CCC;
padding: 15px;
.login-heading h3 {
font-weight: 300;
line-height: 1.5;
margin: 0;
display: none;
}
.login-footer {
margin-top: 10px;
}
a.forgot {
float: right;
padding-top: 6px
}
.nav .active a {
background: transparent;
}
} }
.form-control { .form-control {
background-color: #F5F5F5; font-size: 14px;
font-size: 16px; padding: 10px 8px;
padding: 14px 10px;
width: 100%; width: 100%;
height: auto; height: auto;
...@@ -68,11 +86,6 @@ ...@@ -68,11 +86,6 @@
} }
} }
.login-box a.forgot {
float: right;
padding-top: 6px
}
.devise-errors { .devise-errors {
h2 { h2 {
font-size: 14px; font-size: 14px;
...@@ -80,7 +93,19 @@ ...@@ -80,7 +93,19 @@
} }
} }
.brand-holder { .remember-me {
border-right: 1px solid #EEE; margin-top: -10px;
label {
font-weight: normal;
}
}
}
@media (max-width: $screen-xs-max) {
.login-page {
.col-sm-5.pull-right {
float: none !important;
}
} }
} }
...@@ -11,25 +11,40 @@ ...@@ -11,25 +11,40 @@
} }
} }
.accept-group { .accept-merge-holder {
label { margin-top: 5px;
margin: 5px;
.accept-action {
display: inline-block;
.accept_merge_request {
padding: 10px 20px;
}
}
.accept-control {
display: inline-block;
margin-left: 20px; margin-left: 20px;
padding: 10px 0;
line-height: 20px;
font-weight: bold;
.checkbox {
margin: 0;
}
} }
} }
} }
.merge-request .merge-request-tabs{ @media(min-width: $screen-sm-max) {
border-bottom: 2px solid $border_primary; .merge-request .merge-request-tabs{
margin: 20px 0; margin: 20px 0;
li { li {
a { a {
padding: 15px 40px; padding: 15px 40px;
font-size: 14px; font-size: 14px;
margin-bottom: -2px; }
border-bottom: 2px solid $border_primary;
@include border-radius(0px);
} }
} }
} }
...@@ -106,6 +121,7 @@ ...@@ -106,6 +121,7 @@
.mr-state-widget { .mr-state-widget {
background: $box_bg; background: $box_bg;
margin-bottom: 20px; margin-bottom: 20px;
color: #666;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09)); @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
.ci_widget { .ci_widget {
...@@ -150,7 +166,6 @@ ...@@ -150,7 +166,6 @@
padding: 10px 15px; padding: 10px 15px;
h4 { h4 {
font-size: 20px;
font-weight: normal; font-weight: normal;
} }
...@@ -172,7 +187,3 @@ ...@@ -172,7 +187,3 @@
.merge-request-show-labels .label { .merge-request-show-labels .label {
padding: 6px 10px; padding: 6px 10px;
} }
.mr-commits .commit {
padding: 10px 15px;
}
.main-nav {
background: #f5f5f5;
margin: 20px 0;
margin-top: 0;
padding-top: 4px;
border-bottom: 1px solid #E9E9E9;
ul {
padding: 0;
margin: auto;
.count {
font-weight: normal;
display: inline-block;
height: 15px;
padding: 1px 6px;
height: auto;
font-size: 0.82em;
line-height: 14px;
text-align: center;
color: #777;
background: #eee;
@include border-radius(8px);
}
.label {
background: $hover;
text-shadow: none;
color: $style_color;
}
li {
list-style-type: none;
margin: 0;
display: table-cell;
width: 1%;
&.active {
a {
color: $link_color;
font-weight: bold;
border-bottom: 3px solid $link_color;
}
}
&:hover {
a {
color: $link_hover_color;
border-bottom: 3px solid $link_hover_color;
}
}
}
a {
display: block;
text-align: center;
font-weight: bold;
height: 42px;
line-height: 39px;
color: #777;
text-shadow: 0 1px 1px white;
text-decoration: none;
overflow: hidden;
margin-bottom: -1px;
}
}
@media (max-width: $screen-xs-max) {
font-size: 18px;
margin: 0;
max-height: none;
&, .container {
padding: 0;
border-top: 0;
}
ul {
height: auto;
li {
display: list-item;
width: auto;
padding: 5px 0;
&.active {
background-color: $link_hover_color;
a {
color: #fff;
font-weight: normal;
text-shadow: none;
border: none;
&:after { display: none; }
}
}
}
}
}
}
...@@ -62,6 +62,7 @@ ul.notes { ...@@ -62,6 +62,7 @@ ul.notes {
} }
.note-body { .note-body {
@include md-typography; @include md-typography;
overflow: auto;
} }
.note-header { .note-header {
padding-bottom: 3px; padding-bottom: 3px;
...@@ -155,19 +156,26 @@ ul.notes { ...@@ -155,19 +156,26 @@ ul.notes {
} }
.add-diff-note { .add-diff-note {
background: image-url("diff_note_add.png") no-repeat left 0; margin-top: -4px;
border: none; @include border-radius(40px);
height: 22px; background: #FFF;
margin-left: -65px; padding: 4px;
font-size: 16px;
color: $link_color;
margin-left: -60px;
position: absolute; position: absolute;
width: 22px;
z-index: 10; z-index: 10;
transition: all 0.2s ease;
// "hide" it by default // "hide" it by default
opacity: 0.0; opacity: 0.0;
filter: alpha(opacity=0); filter: alpha(opacity=0);
&:hover { &:hover {
font-size: 24px;
background: $bg_primary;
color: #FFF;
@include show-add-diff-note; @include show-add-diff-note;
} }
} }
......
...@@ -308,3 +308,10 @@ ul.nav.nav-projects-tabs { ...@@ -308,3 +308,10 @@ ul.nav.nav-projects-tabs {
display: none; display: none;
} }
} }
table.table.protected-branches-list tr.no-border {
th, td {
border: 0;
}
}
.page-with-sidebar {
background: #F5F5F5;
}
.sidebar-wrapper {
z-index: 99;
overflow-y: auto;
background: #F5F5F5;
}
.content-wrapper {
width: 100%;
padding: 15px;
background: #FFF;
margin-top: 48px;
}
.nav-sidebar {
margin: 0;
list-style: none;
&.navbar-collapse {
padding: 0px !important;
}
}
.nav-sidebar li a .count {
float: right;
background: #eee;
padding: 0px 8px;
@include border-radius(6px);
}
.nav-sidebar li {
&.active a {
color: #111;
background: #EEE;
font-weight: bold;
&.no-highlight {
background: none;
}
i {
color: #444;
}
}
}
.nav-sidebar li {
&.separate-item {
border-top: 1px solid #ddd;
padding-top: 10px;
margin-top: 10px;
}
a {
color: #555;
display: block;
text-decoration: none;
padding: 6px 15px;
font-size: 13px;
line-height: 20px;
text-shadow: 0 1px 2px #FFF;
padding-left: 20px;
&:hover {
text-decoration: none;
color: #333;
background: #DDD;
}
&:active, &:focus {
text-decoration: none;
}
i {
width: 20px;
color: #888;
margin-right: 23px;
}
}
}
.sidebar-subnav {
margin-left: 0px;
padding-left: 0px;
li {
list-style: none;
}
}
@mixin expanded-sidebar {
.page-with-sidebar {
padding-left: 250px;
}
.sidebar-wrapper {
width: 250px;
position: fixed;
left: 250px;
height: 100%;
margin-left: -250px;
border-right: 1px solid #EAEAEA;
.nav-sidebar {
margin-top: 20px;
position: fixed;
top: 45px;
width: 250px;
}
}
.content-wrapper {
padding: 20px;
}
}
@mixin folded-sidebar {
.page-with-sidebar {
padding-left: 50px;
}
.sidebar-wrapper {
width: 52px;
position: fixed;
top: 0;
left: 0;
height: 100%;
border-right: 1px solid #EAEAEA;
overflow-x: hidden;
.nav-sidebar {
margin-top: 20px;
position: absolute;
top: 45px;
width: 52px;
li a {
padding-left: 18px;
font-size: 14px;
padding: 10px 15px;
text-align: center;
& > span {
display: none;
}
}
}
}
}
@media (max-width: $screen-sm-max) {
@include folded-sidebar;
}
@media(min-width: $screen-sm-max) {
@include expanded-sidebar;
}
...@@ -17,19 +17,6 @@ ...@@ -17,19 +17,6 @@
@include border-radius(0); @include border-radius(0);
tr { tr {
td, th {
padding: 8px 10px;
line-height: 20px;
}
th {
font-weight: normal;
font-size: 15px;
border-bottom: 1px solid #CCC !important;
}
td {
border-color: #F1F1F1 !important;
border-bottom: 1px solid;
}
&:hover { &:hover {
td { td {
background: $hover; background: $hover;
......
...@@ -37,13 +37,3 @@ ...@@ -37,13 +37,3 @@
margin: 0 8px; margin: 0 8px;
} }
.votes-holder {
float: right;
width: 250px;
@media (max-width: $screen-xs-max) {
width: 100%;
margin-top: 5px;
margin-bottom: 10px;
}
}
...@@ -9,17 +9,15 @@ ...@@ -9,17 +9,15 @@
.navbar-inner { .navbar-inner {
background: #F1F1F1; background: #F1F1F1;
border-bottom: 1px solid #DDD; border-bottom: 1px solid #DDD;
.app_logo {
background-color: #DDD;
}
.nav > li > a { .nav > li > a {
color: $style_color; color: $style_color;
} }
.separator {
background: #F9F9F9;
border-left: 1px solid #DDD;
}
} }
} }
} }
.main-nav {
background: #FFF;
}
} }
...@@ -23,9 +23,8 @@ ...@@ -23,9 +23,8 @@
background-color: #436; background-color: #436;
} }
} }
.separator { .app_logo {
background: #436; background-color: #325;
border-left: 1px solid #659;
} }
.nav > li > a { .nav > li > a {
color: #98C; color: #98C;
......
...@@ -23,9 +23,8 @@ ...@@ -23,9 +23,8 @@
background-color: #272727; background-color: #272727;
} }
} }
.separator { .app_logo {
background: #272727; background-color: #222;
border-left: 1px solid #474747;
} }
} }
} }
......
...@@ -23,9 +23,8 @@ ...@@ -23,9 +23,8 @@
background-color: #373D47; background-color: #373D47;
} }
} }
.separator { .app_logo {
background: #373D47; background-color: #24272D;
border-left: 1px solid #575D67;
} }
.nav > li > a { .nav > li > a {
color: #979DA7; color: #979DA7;
......
...@@ -23,9 +23,8 @@ ...@@ -23,9 +23,8 @@
background-color: #018865; background-color: #018865;
} }
} }
.separator { .app_logo {
background: #018865; background-color: #017855;
border-left: 1px solid #11A885;
} }
.nav > li > a { .nav > li > a {
color: #ADC; color: #ADC;
......
...@@ -21,7 +21,7 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -21,7 +21,7 @@ class Admin::GroupsController < Admin::ApplicationController
def create def create
@group = Group.new(group_params) @group = Group.new(group_params)
@group.path = @group.name.dup.parameterize if @group.name @group.name = @group.path.dup unless @group.name
if @group.save if @group.save
@group.add_owner(current_user) @group.add_owner(current_user)
......
class Admin::KeysController < Admin::ApplicationController
before_filter :user, only: [:show, :destroy]
def show
@key = user.keys.find(params[:id])
respond_to do |format|
format.html
format.js { render nothing: true }
end
end
def destroy
key = user.keys.find(params[:id])
respond_to do |format|
if key.destroy
format.html { redirect_to [:admin, user], notice: 'User key was successfully removed.' }
else
format.html { redirect_to [:admin, user], alert: 'Failed to remove user key.' }
end
end
end
protected
def user
@user ||= User.find_by!(username: params[:user_id])
end
def key_params
params.require(:user_id, :id)
end
end
...@@ -11,6 +11,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -11,6 +11,7 @@ class Admin::UsersController < Admin::ApplicationController
def show def show
@personal_projects = user.personal_projects @personal_projects = user.personal_projects
@joined_projects = user.projects.joined(@user) @joined_projects = user.projects.joined(@user)
@keys = user.keys.order('id DESC')
end end
def new def new
...@@ -118,7 +119,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -118,7 +119,7 @@ class Admin::UsersController < Admin::ApplicationController
:email, :remember_me, :bio, :name, :username, :email, :remember_me, :bio, :name, :username,
:skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id, :force_random_password, :skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id, :force_random_password,
:extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key, :extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key,
:projects_limit, :can_create_group, :admin :projects_limit, :can_create_group, :admin, :key_id
) )
end end
end end
...@@ -239,4 +239,63 @@ class ApplicationController < ActionController::Base ...@@ -239,4 +239,63 @@ class ApplicationController < ActionController::Base
redirect_to profile_path, notice: 'Please complete your profile with email address' and return redirect_to profile_path, notice: 'Please complete your profile with email address' and return
end end
end end
def set_filters_params
params[:sort] ||= 'newest'
params[:scope] = 'all' if params[:scope].blank?
params[:state] = 'opened' if params[:state].blank?
@filter_params = params.dup
if @project
@filter_params[:project_id] = @project.id
elsif @group
@filter_params[:group_id] = @group.id
else
# TODO: this filter ignore issues/mr created in public or
# internal repos where you are not a member. Enable this filter
# or improve current implementation to filter only issues you
# created or assigned or mentioned
#@filter_params[:authorized_only] = true
end
@filter_params
end
def set_filter_values(collection)
assignee_id = @filter_params[:assignee_id]
author_id = @filter_params[:author_id]
milestone_id = @filter_params[:milestone_id]
@sort = @filter_params[:sort].try(:humanize)
@assignees = User.where(id: collection.pluck(:assignee_id))
@authors = User.where(id: collection.pluck(:author_id))
@milestones = Milestone.where(id: collection.pluck(:milestone_id))
if assignee_id.present? && !assignee_id.to_i.zero?
@assignee = @assignees.find_by(id: assignee_id)
end
if author_id.present? && !author_id.to_i.zero?
@author = @authors.find_by(id: author_id)
end
if milestone_id.present? && !milestone_id.to_i.zero?
@milestone = @milestones.find_by(id: milestone_id)
end
end
def get_issues_collection
set_filters_params
issues = IssuesFinder.new.execute(current_user, @filter_params)
set_filter_values(issues)
issues
end
def get_merge_requests_collection
set_filters_params
merge_requests = MergeRequestsFinder.new.execute(current_user, @filter_params)
set_filter_values(merge_requests)
merge_requests
end
end end
...@@ -3,8 +3,6 @@ class DashboardController < ApplicationController ...@@ -3,8 +3,6 @@ class DashboardController < ApplicationController
before_filter :load_projects, except: [:projects] before_filter :load_projects, except: [:projects]
before_filter :event_filter, only: :show before_filter :event_filter, only: :show
before_filter :default_filter, only: [:issues, :merge_requests]
def show def show
# Fetch only 30 projects. # Fetch only 30 projects.
...@@ -55,13 +53,13 @@ class DashboardController < ApplicationController ...@@ -55,13 +53,13 @@ class DashboardController < ApplicationController
end end
def merge_requests def merge_requests
@merge_requests = MergeRequestsFinder.new.execute(current_user, params) @merge_requests = get_merge_requests_collection
@merge_requests = @merge_requests.page(params[:page]).per(20) @merge_requests = @merge_requests.page(params[:page]).per(20)
@merge_requests = @merge_requests.preload(:author, :target_project) @merge_requests = @merge_requests.preload(:author, :target_project)
end end
def issues def issues
@issues = IssuesFinder.new.execute(current_user, params) @issues = get_issues_collection
@issues = @issues.page(params[:page]).per(20) @issues = @issues.page(params[:page]).per(20)
@issues = @issues.preload(:author, :project) @issues = @issues.preload(:author, :project)
...@@ -76,10 +74,4 @@ class DashboardController < ApplicationController ...@@ -76,10 +74,4 @@ class DashboardController < ApplicationController
def load_projects def load_projects
@projects = current_user.authorized_projects.sorted_by_activity.non_archived @projects = current_user.authorized_projects.sorted_by_activity.non_archived
end end
def default_filter
params[:scope] = 'assigned-to-me' if params[:scope].blank?
params[:state] = 'opened' if params[:state].blank?
params[:authorized_only] = true
end
end end
...@@ -11,8 +11,6 @@ class GroupsController < ApplicationController ...@@ -11,8 +11,6 @@ class GroupsController < ApplicationController
# Load group projects # Load group projects
before_filter :load_projects, except: [:new, :create, :projects, :edit, :update] before_filter :load_projects, except: [:new, :create, :projects, :edit, :update]
before_filter :default_filter, only: [:issues, :merge_requests]
layout :determine_layout layout :determine_layout
before_filter :set_title, only: [:new, :create] before_filter :set_title, only: [:new, :create]
...@@ -23,7 +21,7 @@ class GroupsController < ApplicationController ...@@ -23,7 +21,7 @@ class GroupsController < ApplicationController
def create def create
@group = Group.new(group_params) @group = Group.new(group_params)
@group.path = @group.name.dup.parameterize if @group.name @group.name = @group.path.dup unless @group.name
if @group.save if @group.save
@group.add_owner(current_user) @group.add_owner(current_user)
...@@ -47,13 +45,13 @@ class GroupsController < ApplicationController ...@@ -47,13 +45,13 @@ class GroupsController < ApplicationController
end end
def merge_requests def merge_requests
@merge_requests = MergeRequestsFinder.new.execute(current_user, params) @merge_requests = get_merge_requests_collection
@merge_requests = @merge_requests.page(params[:page]).per(20) @merge_requests = @merge_requests.page(params[:page]).per(20)
@merge_requests = @merge_requests.preload(:author, :target_project) @merge_requests = @merge_requests.preload(:author, :target_project)
end end
def issues def issues
@issues = IssuesFinder.new.execute(current_user, params) @issues = get_issues_collection
@issues = @issues.page(params[:page]).per(20) @issues = @issues.page(params[:page]).per(20)
@issues = @issues.preload(:author, :project) @issues = @issues.preload(:author, :project)
...@@ -148,18 +146,6 @@ class GroupsController < ApplicationController ...@@ -148,18 +146,6 @@ class GroupsController < ApplicationController
end end
end end
def default_filter
if params[:scope].blank?
if current_user
params[:scope] = 'assigned-to-me'
else
params[:scope] = 'all'
end
end
params[:state] = 'opened' if params[:state].blank?
params[:group_id] = @group.id
end
def group_params def group_params
params.require(:group).permit(:name, :description, :path, :avatar) params.require(:group).permit(:name, :description, :path, :avatar)
end end
......
class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
before_filter :authenticate_user!
layout "profile"
def index
head :forbidden and return
end
def create
@application = Doorkeeper::Application.new(application_params)
if Doorkeeper.configuration.confirm_application_owner?
@application.owner = current_user
end
if @application.save
flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
redirect_to oauth_application_url(@application)
else
render :new
end
end
def destroy
if @application.destroy
flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :destroy])
end
redirect_to applications_profile_url
end
private
def set_application
@application = current_user.oauth_applications.find(params[:id])
end
rescue_from ActiveRecord::RecordNotFound do |exception|
render "errors/not_found", layout: "errors", status: 404
end
end
class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
before_filter :authenticate_resource_owner!
layout "profile"
def new
if pre_auth.authorizable?
if skip_authorization? || matching_token?
auth = authorization.authorize
redirect_to auth.redirect_uri
else
render "doorkeeper/authorizations/new"
end
else
render "doorkeeper/authorizations/error"
end
end
# TODO: Handle raise invalid authorization
def create
redirect_or_render authorization.authorize
end
def destroy
redirect_or_render authorization.deny
end
private
def matching_token?
Doorkeeper::AccessToken.matching_token_for(pre_auth.client,
current_resource_owner.id,
pre_auth.scopes)
end
def redirect_or_render(auth)
if auth.redirectable?
redirect_to auth.redirect_uri
else
render json: auth.body, status: auth.status
end
end
def pre_auth
@pre_auth ||=
Doorkeeper::OAuth::PreAuthorization.new(Doorkeeper.configuration,
server.client_via_uid,
params)
end
def authorization
@authorization ||= strategy.request
end
def strategy
@strategy ||= server.authorization_request(pre_auth.response_type)
end
end
class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicationsController
layout "profile"
def destroy
Doorkeeper::AccessToken.revoke_all_for(params[:id], current_resource_owner)
redirect_to applications_profile_url, notice: I18n.t(:notice, scope: [:doorkeeper, :flash, :authorized_applications, :destroy])
end
end
...@@ -13,6 +13,11 @@ class ProfilesController < ApplicationController ...@@ -13,6 +13,11 @@ class ProfilesController < ApplicationController
def design def design
end end
def applications
@applications = current_user.oauth_applications
@authorized_tokens = current_user.oauth_authorized_tokens
end
def update def update
user_params.except!(:email) if @user.ldap_user? user_params.except!(:email) if @user.ldap_user?
......
...@@ -29,31 +29,4 @@ class Projects::ApplicationController < ApplicationController ...@@ -29,31 +29,4 @@ class Projects::ApplicationController < ApplicationController
redirect_to project_tree_path(@project, @ref), notice: "This action is not allowed unless you are on top of a branch" redirect_to project_tree_path(@project, @ref), notice: "This action is not allowed unless you are on top of a branch"
end end
end end
def set_filter_variables(collection)
params[:sort] ||= 'newest'
params[:scope] = 'all' if params[:scope].blank?
params[:state] = 'opened' if params[:state].blank?
@sort = params[:sort].humanize
assignee_id = params[:assignee_id]
author_id = params[:author_id]
milestone_id = params[:milestone_id]
if assignee_id.present? && !assignee_id.to_i.zero?
@assignee = @project.team.find(assignee_id)
end
if author_id.present? && !author_id.to_i.zero?
@author = @project.team.find(assignee_id)
end
if milestone_id.present? && !milestone_id.to_i.zero?
@milestone = @project.milestones.find(milestone_id)
end
@assignees = User.where(id: collection.pluck(:assignee_id))
@authors = User.where(id: collection.pluck(:author_id))
end
end end
...@@ -18,9 +18,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -18,9 +18,7 @@ class Projects::IssuesController < Projects::ApplicationController
def index def index
terms = params['issue_search'] terms = params['issue_search']
set_filter_variables(@project.issues) @issues = get_issues_collection
@issues = IssuesFinder.new.execute(current_user, params.merge(project_id: @project.id))
@issues = @issues.full_search(terms) if terms.present? @issues = @issues.full_search(terms) if terms.present?
@issues = @issues.page(params[:page]).per(20) @issues = @issues.page(params[:page]).per(20)
......
...@@ -17,9 +17,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -17,9 +17,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort] before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort]
def index def index
set_filter_variables(@project.merge_requests) @merge_requests = get_merge_requests_collection
@merge_requests = MergeRequestsFinder.new.execute(current_user, params.merge(project_id: @project.id))
@merge_requests = @merge_requests.page(params[:page]).per(20) @merge_requests = @merge_requests.page(params[:page]).per(20)
end end
......
...@@ -11,7 +11,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -11,7 +11,7 @@ class Projects::MilestonesController < Projects::ApplicationController
respond_to :html respond_to :html
def index def index
@milestones = case params[:f] @milestones = case params[:state]
when 'all'; @project.milestones.order("state, due_date DESC") when 'all'; @project.milestones.order("state, due_date DESC")
when 'closed'; @project.milestones.closed.order("due_date DESC") when 'closed'; @project.milestones.closed.order("due_date DESC")
else @project.milestones.active.order("due_date ASC") else @project.milestones.active.order("due_date ASC")
......
...@@ -15,6 +15,24 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController ...@@ -15,6 +15,24 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
redirect_to project_protected_branches_path(@project) redirect_to project_protected_branches_path(@project)
end end
def update
protected_branch = @project.protected_branches.find(params[:id])
if protected_branch &&
protected_branch.update_attributes(
developers_can_push: params[:developers_can_push]
)
respond_to do |format|
format.json { render :json => protected_branch, status: :ok }
end
else
respond_to do |format|
format.json { render json: protected_branch.errors, status: :unprocessable_entity }
end
end
end
def destroy def destroy
@project.protected_branches.find(params[:id]).destroy @project.protected_branches.find(params[:id]).destroy
...@@ -27,6 +45,6 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController ...@@ -27,6 +45,6 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
private private
def protected_branch_params def protected_branch_params
params.require(:protected_branch).permit(:name) params.require(:protected_branch).permit(:name, :developers_can_push)
end end
end end
...@@ -42,7 +42,7 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -42,7 +42,7 @@ class Projects::ServicesController < Projects::ApplicationController
:title, :token, :type, :active, :api_key, :subdomain, :title, :token, :type, :active, :api_key, :subdomain,
:room, :recipients, :project_url, :webhook, :room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key, :server :build_key, :server, :teamcity_url, :build_type
) )
end end
end end
...@@ -44,6 +44,9 @@ class ProjectsController < ApplicationController ...@@ -44,6 +44,9 @@ class ProjectsController < ApplicationController
def transfer def transfer
::Projects::TransferService.new(project, current_user, project_params).execute ::Projects::TransferService.new(project, current_user, project_params).execute
if @project.errors[:namespace_id].present?
flash[:alert] = @project.errors[:namespace_id].first
end
end end
def show def show
......
...@@ -114,6 +114,10 @@ module ApplicationHelper ...@@ -114,6 +114,10 @@ module ApplicationHelper
Gitlab::Theme.css_class_by_id(current_user.try(:theme_id)) Gitlab::Theme.css_class_by_id(current_user.try(:theme_id))
end end
def theme_type
Gitlab::Theme.type_css_class_by_id(current_user.try(:theme_id))
end
def user_color_scheme_class def user_color_scheme_class
COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user) COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user)
end end
...@@ -271,4 +275,39 @@ module ApplicationHelper ...@@ -271,4 +275,39 @@ module ApplicationHelper
def promo_url def promo_url
'https://' + promo_host 'https://' + promo_host
end end
def page_filter_path(options={})
exist_opts = {
state: params[:state],
scope: params[:scope],
label_name: params[:label_name],
milestone_id: params[:milestone_id],
assignee_id: params[:assignee_id],
author_id: params[:author_id],
sort: params[:sort],
}
options = exist_opts.merge(options)
path = request.path
path << "?#{options.to_param}"
path
end
def outdated_browser?
browser.ie? && browser.version.to_i < 10
end
def path_to_key(key, admin = false)
if admin
admin_user_key_path(@user, key)
else
profile_key_path(key)
end
end
def redirect_from_root?
request.env['rack.session']['user_return_to'] ==
'/'
end
end end
module DashboardHelper module DashboardHelper
def filter_path(entity, options={})
exist_opts = {
state: params[:state],
scope: params[:scope],
project_id: params[:project_id],
}
options = exist_opts.merge(options)
path = request.path
path << "?#{options.to_param}"
path
end
def entities_per_project(project, entity)
case entity.to_sym
when :issue then @issues.where(project_id: project.id)
when :merge_request then @merge_requests.where(target_project_id: project.id)
else
[]
end.count
end
def projects_dashboard_filter_path(options={}) def projects_dashboard_filter_path(options={})
exist_opts = { exist_opts = {
sort: params[:sort], sort: params[:sort],
scope: params[:scope], scope: params[:scope],
group: params[:group], group: params[:group],
tag: params[:tag],
visibility_level: params[:visibility_level],
} }
options = exist_opts.merge(options) options = exist_opts.merge(options)
...@@ -36,32 +15,11 @@ module DashboardHelper ...@@ -36,32 +15,11 @@ module DashboardHelper
path path
end end
def assigned_entities_count(current_user, entity, scope = nil) def assigned_issues_dashboard_path
items = current_user.send('assigned_' + entity.pluralize) issues_dashboard_path(assignee_id: current_user.id)
get_count(items, scope)
end end
def authored_entities_count(current_user, entity, scope = nil) def assigned_mrs_dashboard_path
items = current_user.send(entity.pluralize) merge_requests_dashboard_path(assignee_id: current_user.id)
get_count(items, scope)
end
def authorized_entities_count(current_user, entity, scope = nil)
items = entity.classify.constantize
get_count(items, scope, true, current_user)
end
protected
def get_count(items, scope, get_authorized = false, current_user = nil)
items = items.opened
if scope.kind_of?(Group)
items = items.of_group(scope)
elsif scope.kind_of?(Project)
items = items.of_projects(scope)
elsif get_authorized
items = items.of_projects(current_user.authorized_projects)
end
items.count
end end
end end
...@@ -117,4 +117,22 @@ module DiffHelper ...@@ -117,4 +117,22 @@ module DiffHelper
[comments_left, comments_right] [comments_left, comments_right]
end end
def inline_diff_btn
params_copy = params.dup
params_copy[:view] = 'inline'
link_to url_for(params_copy), id: "commit-diff-viewtype", class: (params[:view] != 'parallel' ? 'btn active' : 'btn') do
'Inline'
end
end
def parallel_diff_btn
params_copy = params.dup
params_copy[:view] = 'parallel'
link_to url_for(params_copy), id: "commit-diff-viewtype", class: (params[:view] == 'parallel' ? 'btn active' : 'btn') do
'Side-by-side'
end
end
end end
...@@ -21,15 +21,14 @@ module EventsHelper ...@@ -21,15 +21,14 @@ module EventsHelper
def event_filter_link(key, tooltip) def event_filter_link(key, tooltip)
key = key.to_s key = key.to_s
inactive = if @event_filter.active? key active = if @event_filter.active? key
nil 'active'
else end
'inactive'
end
content_tag :div, class: "filter_icon #{inactive}" do content_tag :li, class: "filter_icon #{active}" do
link_to request.path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do link_to request.path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do
content_tag :i, nil, class: icon_for_event[key] content_tag(:i, nil, class: icon_for_event[key]) +
content_tag(:span, ' ' + tooltip)
end end
end end
end end
......
...@@ -6,7 +6,7 @@ module GroupsHelper ...@@ -6,7 +6,7 @@ module GroupsHelper
def leave_group_message(group) def leave_group_message(group)
"Are you sure you want to leave \"#{group}\" group?" "Are you sure you want to leave \"#{group}\" group?"
end end
def should_user_see_group_roles?(user, group) def should_user_see_group_roles?(user, group)
if user if user
user.is_admin? || group.members.exists?(user_id: user.id) user.is_admin? || group.members.exists?(user_id: user.id)
...@@ -33,15 +33,11 @@ module GroupsHelper ...@@ -33,15 +33,11 @@ module GroupsHelper
title title
end end
def group_filter_path(entity, options={}) def group_settings_page?
exist_opts = { if current_controller?('groups')
status: params[:status] current_action?('edit') || current_action?('projects')
} else
false
options = exist_opts.merge(options) end
path = request.path
path << "?#{options.to_param}"
path
end end
end end
module MilestonesHelper
def milestones_filter_path(opts = {})
if @project
project_milestones_path(@project, opts)
elsif @group
group_milestones_path(@group, opts)
end
end
end
...@@ -52,8 +52,11 @@ module NotesHelper ...@@ -52,8 +52,11 @@ module NotesHelper
discussion_id: discussion_id discussion_id: discussion_id
} }
button_tag '', class: 'btn add-diff-note js-add-diff-note-button', button_tag(class: 'btn add-diff-note js-add-diff-note-button',
data: data, title: 'Add a comment to this line' data: data,
title: 'Add a comment to this line') do
content_tag :i, nil, class: 'fa fa-comment-o'
end
end end
def link_to_reply_diff(note) def link_to_reply_diff(note)
......
...@@ -68,48 +68,6 @@ module ProjectsHelper ...@@ -68,48 +68,6 @@ module ProjectsHelper
project_nav_tabs.include? name project_nav_tabs.include? name
end end
def selected_label?(label_name)
params[:label_name].to_s.split(',').include?(label_name)
end
def labels_filter_path(label_name)
label_name =
if selected_label?(label_name)
params[:label_name].split(',').reject { |l| l == label_name }.join(',')
elsif params[:label_name].present?
"#{params[:label_name]},#{label_name}"
else
label_name
end
project_filter_path(label_name: label_name)
end
def label_filter_class(label_name)
if selected_label?(label_name)
'label-filter-item active'
else
'label-filter-item light'
end
end
def project_filter_path(options={})
exist_opts = {
state: params[:state],
scope: params[:scope],
label_name: params[:label_name],
milestone_id: params[:milestone_id],
assignee_id: params[:assignee_id],
sort: params[:sort],
}
options = exist_opts.merge(options)
path = request.path
path << "?#{options.to_param}"
path
end
def project_active_milestones def project_active_milestones
@project.milestones.active.order("due_date, title ASC") @project.milestones.active.order("due_date, title ASC")
end end
......
...@@ -17,4 +17,13 @@ module SelectsHelper ...@@ -17,4 +17,13 @@ module SelectsHelper
project_id = opts[:project_id] || @project.id project_id = opts[:project_id] || @project.id
hidden_field_tag(id, value, class: css_class, 'data-placeholder' => placeholder, 'data-project-id' => project_id) hidden_field_tag(id, value, class: css_class, 'data-placeholder' => placeholder, 'data-project-id' => project_id)
end end
def groups_select_tag(id, opts = {})
css_class = "ajax-groups-select "
css_class << "multiselect " if opts[:multiple]
css_class << (opts[:class] || '')
value = opts[:selected] || ''
hidden_field_tag(id, value, class: css_class)
end
end end
...@@ -28,6 +28,10 @@ module TabHelper ...@@ -28,6 +28,10 @@ module TabHelper
# nav_link(controller: [:tree, :refs]) { "Hello" } # nav_link(controller: [:tree, :refs]) { "Hello" }
# # => '<li class="active">Hello</li>' # # => '<li class="active">Hello</li>'
# #
# # Several paths
# nav_link(path: ['tree#show', 'profile#show']) { "Hello" }
# # => '<li class="active">Hello</li>'
#
# # Shorthand path # # Shorthand path
# nav_link(path: 'tree#show') { "Hello" } # nav_link(path: 'tree#show') { "Hello" }
# # => '<li class="active">Hello</li>' # # => '<li class="active">Hello</li>'
...@@ -38,25 +42,7 @@ module TabHelper ...@@ -38,25 +42,7 @@ module TabHelper
# #
# Returns a list item element String # Returns a list item element String
def nav_link(options = {}, &block) def nav_link(options = {}, &block)
if path = options.delete(:path) klass = active_nav_link?(options) ? 'active' : ''
if path.respond_to?(:each)
c = path.map { |p| p.split('#').first }
a = path.map { |p| p.split('#').last }
else
c, a, _ = path.split('#')
end
else
c = options.delete(:controller)
a = options.delete(:action)
end
if c && a
# When given both options, make sure BOTH are active
klass = current_controller?(*c) && current_action?(*a) ? 'active' : ''
else
# Otherwise check EITHER option
klass = current_controller?(*c) || current_action?(*a) ? 'active' : ''
end
# Add our custom class into the html_options, which may or may not exist # Add our custom class into the html_options, which may or may not exist
# and which may or may not already have a :class key # and which may or may not already have a :class key
...@@ -72,6 +58,34 @@ module TabHelper ...@@ -72,6 +58,34 @@ module TabHelper
end end
end end
def active_nav_link?(options)
if path = options.delete(:path)
unless path.respond_to?(:each)
path = [path]
end
path.any? do |single_path|
current_path?(single_path)
end
else
c = options.delete(:controller)
a = options.delete(:action)
if c && a
# When given both options, make sure BOTH are true
current_controller?(*c) && current_action?(*a)
else
# Otherwise check EITHER option
current_controller?(*c) || current_action?(*a)
end
end
end
def current_path?(path)
c, a, _ = path.split('#')
current_controller?(c) && current_action?(a)
end
def project_tab_class def project_tab_class
return "active" if current_page?(controller: "/projects", action: :edit, id: @project) return "active" if current_page?(controller: "/projects", action: :edit, id: @project)
......
...@@ -53,13 +53,41 @@ module TreeHelper ...@@ -53,13 +53,41 @@ module TreeHelper
File.join(*args) File.join(*args)
end end
def allowed_tree_edit? def allowed_tree_edit?(project = nil, ref = nil)
return false unless @repository.branch_names.include?(@ref) project ||= @project
ref ||= @ref
return false unless project.repository.branch_names.include?(ref)
if @project.protected_branch? @ref if project.protected_branch? ref
can?(current_user, :push_code_to_protected_branches, @project) can?(current_user, :push_code_to_protected_branches, project)
else else
can?(current_user, :push_code, @project) can?(current_user, :push_code, project)
end
end
def edit_blob_link(project, ref, path, options = {})
blob =
begin
project.repository.blob_at(ref, path)
rescue
nil
end
if blob && blob.text?
text = 'Edit'
after = options[:after] || ''
from_mr = options[:from_merge_request_id]
link_opts = {}
link_opts[:from_merge_request_id] = from_mr if from_mr
cls = 'btn btn-small'
if allowed_tree_edit?(project, ref)
link_to text, project_edit_tree_path(project, tree_join(ref, path),
link_opts), class: cls
else
content_tag :span, text, class: cls + ' disabled'
end + after.html_safe
else
''
end end
end end
......
...@@ -174,7 +174,7 @@ class Event < ActiveRecord::Base ...@@ -174,7 +174,7 @@ class Event < ActiveRecord::Base
def valid_push? def valid_push?
data[:ref] && ref_name.present? data[:ref] && ref_name.present?
rescue => ex rescue
false false
end end
......
...@@ -21,7 +21,7 @@ class Group < Namespace ...@@ -21,7 +21,7 @@ class Group < Namespace
has_many :users, through: :group_members has_many :users, through: :group_members
validate :avatar_type, if: ->(user) { user.avatar_changed? } validate :avatar_type, if: ->(user) { user.avatar_changed? }
validates :avatar, file_size: { maximum: 100.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
mount_uploader :avatar, AttachmentUploader mount_uploader :avatar, AttachmentUploader
......
...@@ -48,7 +48,7 @@ class WebHook < ActiveRecord::Base ...@@ -48,7 +48,7 @@ class WebHook < ActiveRecord::Base
verify: false, verify: false,
basic_auth: auth) basic_auth: auth)
end end
rescue SocketError, Errno::ECONNREFUSED => e rescue SocketError, Errno::ECONNREFUSED, Net::OpenTimeout => e
logger.error("WebHook Error => #{e}") logger.error("WebHook Error => #{e}")
false false
end end
......
...@@ -189,7 +189,9 @@ class MergeRequest < ActiveRecord::Base ...@@ -189,7 +189,9 @@ class MergeRequest < ActiveRecord::Base
end end
def automerge!(current_user, commit_message = nil) def automerge!(current_user, commit_message = nil)
MergeRequests::AutoMergeService.new.execute(self, current_user, commit_message) MergeRequests::AutoMergeService.
new(target_project, current_user).
execute(self, commit_message)
end end
def open? def open?
......
...@@ -502,6 +502,6 @@ class Note < ActiveRecord::Base ...@@ -502,6 +502,6 @@ class Note < ActiveRecord::Base
end end
def editable? def editable?
!system !read_attribute(:system)
end end
end end
...@@ -6,12 +6,13 @@ class Notification ...@@ -6,12 +6,13 @@ class Notification
N_PARTICIPATING = 1 N_PARTICIPATING = 1
N_WATCH = 2 N_WATCH = 2
N_GLOBAL = 3 N_GLOBAL = 3
N_MENTION = 4
attr_accessor :target attr_accessor :target
class << self class << self
def notification_levels def notification_levels
[N_DISABLED, N_PARTICIPATING, N_WATCH] [N_DISABLED, N_PARTICIPATING, N_WATCH, N_MENTION]
end end
def options_with_labels def options_with_labels
...@@ -19,12 +20,13 @@ class Notification ...@@ -19,12 +20,13 @@ class Notification
disabled: N_DISABLED, disabled: N_DISABLED,
participating: N_PARTICIPATING, participating: N_PARTICIPATING,
watch: N_WATCH, watch: N_WATCH,
mention: N_MENTION,
global: N_GLOBAL global: N_GLOBAL
} }
end end
def project_notification_levels def project_notification_levels
[N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL] [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL, N_MENTION]
end end
end end
...@@ -48,6 +50,10 @@ class Notification ...@@ -48,6 +50,10 @@ class Notification
target.notification_level == N_GLOBAL target.notification_level == N_GLOBAL
end end
def mention?
target.notification_level == N_MENTION
end
def level def level
target.notification_level target.notification_level
end end
......
...@@ -66,6 +66,7 @@ class Project < ActiveRecord::Base ...@@ -66,6 +66,7 @@ class Project < ActiveRecord::Base
has_one :slack_service, dependent: :destroy has_one :slack_service, dependent: :destroy
has_one :buildbox_service, dependent: :destroy has_one :buildbox_service, dependent: :destroy
has_one :bamboo_service, dependent: :destroy has_one :bamboo_service, dependent: :destroy
has_one :teamcity_service, dependent: :destroy
has_one :pushover_service, dependent: :destroy has_one :pushover_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link has_one :forked_from_project, through: :forked_project_link
...@@ -314,7 +315,8 @@ class Project < ActiveRecord::Base ...@@ -314,7 +315,8 @@ class Project < ActiveRecord::Base
end end
def available_services_names def available_services_names
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack pushover buildbox bamboo) %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla
emails_on_push gemnasium slack pushover buildbox bamboo teamcity)
end end
def gitlab_ci? def gitlab_ci?
...@@ -329,11 +331,6 @@ class Project < ActiveRecord::Base ...@@ -329,11 +331,6 @@ class Project < ActiveRecord::Base
@ci_service ||= ci_services.select(&:activated?).first @ci_service ||= ci_services.select(&:activated?).first
end end
# For compatibility with old code
def code
path
end
def items_for(entity) def items_for(entity)
case entity case entity
when 'issue' then when 'issue' then
...@@ -470,6 +467,10 @@ class Project < ActiveRecord::Base ...@@ -470,6 +467,10 @@ class Project < ActiveRecord::Base
protected_branches_names.include?(branch_name) protected_branches_names.include?(branch_name)
end end
def developers_can_push_to_protected_branch?(branch_name)
protected_branches.any? { |pb| pb.name == branch_name && pb.developers_can_push }
end
def forked? def forked?
!(forked_project_link.nil? || forked_project_link.forked_from_project.nil?) !(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
end end
......
...@@ -35,7 +35,7 @@ class HipchatService < Service ...@@ -35,7 +35,7 @@ class HipchatService < Service
{ type: 'text', name: 'token', placeholder: '' }, { type: 'text', name: 'token', placeholder: '' },
{ type: 'text', name: 'room', placeholder: '' }, { type: 'text', name: 'room', placeholder: '' },
{ type: 'text', name: 'server', { type: 'text', name: 'server',
placeholder: 'Leave blank for default. https://chat.hipchat.com' } placeholder: 'Leave blank for default. https://hipchat.example.com' }
] ]
end end
...@@ -47,7 +47,7 @@ class HipchatService < Service ...@@ -47,7 +47,7 @@ class HipchatService < Service
def gate def gate
options = { api_version: 'v2' } options = { api_version: 'v2' }
options[:server_url] = server unless server.nil? options[:server_url] = server unless server.blank?
@gate ||= HipChat::Client.new(token, options) @gate ||= HipChat::Client.new(token, options)
end end
......
require 'slack-notifier' require 'slack-notifier'
class SlackMessage class SlackMessage
attr_reader :after
attr_reader :before
attr_reader :commits
attr_reader :project_name
attr_reader :project_url
attr_reader :ref
attr_reader :username
def initialize(params) def initialize(params)
@after = params.fetch(:after) @after = params.fetch(:after)
@before = params.fetch(:before) @before = params.fetch(:before)
...@@ -23,14 +31,6 @@ class SlackMessage ...@@ -23,14 +31,6 @@ class SlackMessage
private private
attr_reader :after
attr_reader :before
attr_reader :commits
attr_reader :project_name
attr_reader :project_url
attr_reader :ref
attr_reader :username
def message def message
if new_branch? if new_branch?
new_branch_message new_branch_message
......
class TeamcityService < CiService
include HTTParty
prop_accessor :teamcity_url, :build_type, :username, :password
validates :teamcity_url, presence: true,
format: { with: URI::regexp }, if: :activated?
validates :build_type, presence: true, if: :activated?
validates :username, presence: true,
if: ->(service) { service.password? }, if: :activated?
validates :password, presence: true,
if: ->(service) { service.username? }, if: :activated?
attr_accessor :response
after_save :compose_service_hook, if: :activated?
def compose_service_hook
hook = service_hook || build_service_hook
hook.save
end
def title
'JetBrains TeamCity CI'
end
def description
'A continuous integration and build server'
end
def help
'The build configuration in Teamcity must use the build format '\
'number %build.vcs.number% '\
'you will also want to configure monitoring of all branches so merge '\
'requests build, that setting is in the vsc root advanced settings.'
end
def to_param
'teamcity'
end
def fields
[
{ type: 'text', name: 'teamcity_url',
placeholder: 'TeamCity root URL like https://teamcity.example.com' },
{ type: 'text', name: 'build_type',
placeholder: 'Build configuration ID' },
{ type: 'text', name: 'username',
placeholder: 'A user with permissions to trigger a manual build' },
{ type: 'password', name: 'password' },
]
end
def build_info(sha)
url = URI.parse("#{teamcity_url}/httpAuth/app/rest/builds/"\
"branch:unspecified:any,number:#{sha}")
auth = {
username: username,
password: password,
}
@response = HTTParty.get("#{url}", verify: false, basic_auth: auth)
end
def build_page(sha)
build_info(sha) if @response.nil? || !@response.code
if @response.code != 200
# If actual build link can't be determined,
# send user to build summary page.
"#{teamcity_url}/viewLog.html?buildTypeId=#{build_type}"
else
# If actual build link is available, go to build result page.
built_id = @response['build']['id']
"#{teamcity_url}/viewLog.html?buildId=#{built_id}"\
"&buildTypeId=#{build_type}"
end
end
def commit_status(sha)
build_info(sha) if @response.nil? || !@response.code
return :error unless @response.code == 200 || @response.code == 404
status = if @response.code == 404
'Pending'
else
@response['build']['status']
end
if status.include?('SUCCESS')
'success'
elsif status.include?('FAILURE')
'failed'
elsif status.include?('Pending')
'pending'
else
:error
end
end
def execute(data)
auth = {
username: username,
password: password,
}
branch = data[:ref]
self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue",
body: "<build branchName=\"#{branch}\">"\
"<buildType id=\"#{build_type}\"/>"\
'</build>',
headers: { 'Content-type' => 'application/xml' },
basic_auth: auth
)
end
end
...@@ -106,6 +106,7 @@ class User < ActiveRecord::Base ...@@ -106,6 +106,7 @@ class User < ActiveRecord::Base
has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id, class_name: "Event" has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id, class_name: "Event"
has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue" has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue"
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest" has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
# #
...@@ -564,4 +565,8 @@ class User < ActiveRecord::Base ...@@ -564,4 +565,8 @@ class User < ActiveRecord::Base
namespaces += masters_groups namespaces += masters_groups
end end
end end
def oauth_authorized_tokens
Doorkeeper::AccessToken.where(resource_owner_id: self.id, revoked_at: nil)
end
end end
...@@ -5,15 +5,16 @@ module MergeRequests ...@@ -5,15 +5,16 @@ module MergeRequests
# mark merge request as merged and execute all hooks and notifications # mark merge request as merged and execute all hooks and notifications
# Called when you do merge via GitLab UI # Called when you do merge via GitLab UI
class AutoMergeService < BaseMergeService class AutoMergeService < BaseMergeService
def execute(merge_request, current_user, commit_message) def execute(merge_request, commit_message)
merge_request.lock_mr merge_request.lock_mr
if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message) if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message)
merge_request.merge merge_request.merge
notification.merge_mr(merge_request, current_user) notification_service.merge_mr(merge_request, current_user)
create_merge_event(merge_request, current_user) create_merge_event(merge_request, current_user)
execute_project_hooks(merge_request) create_note(merge_request)
execute_hooks(merge_request)
true true
else else
......
module MergeRequests module MergeRequests
class BaseMergeService class BaseMergeService < MergeRequests::BaseService
private private
def notification
NotificationService.new
end
def create_merge_event(merge_request, current_user) def create_merge_event(merge_request, current_user)
EventCreateService.new.merge_mr(merge_request, current_user) EventCreateService.new.merge_mr(merge_request, current_user)
end end
def execute_project_hooks(merge_request)
if merge_request.project
hook_data = merge_request.to_hook_data(current_user)
merge_request.project.execute_hooks(hook_data, :merge_request_hooks)
end
end
end end
end end
...@@ -13,7 +13,7 @@ module MergeRequests ...@@ -13,7 +13,7 @@ module MergeRequests
merge_request.target_branch ||= merge_request.target_project.default_branch merge_request.target_branch ||= merge_request.target_project.default_branch
unless merge_request.target_branch && merge_request.source_branch unless merge_request.target_branch && merge_request.source_branch
return build_failed(merge_request, "You must select source and target branches") return build_failed(merge_request, nil)
end end
# Generate suggested MR title based on source branch name # Generate suggested MR title based on source branch name
...@@ -59,7 +59,7 @@ module MergeRequests ...@@ -59,7 +59,7 @@ module MergeRequests
end end
def build_failed(merge_request, message) def build_failed(merge_request, message)
merge_request.errors.add(:base, message) merge_request.errors.add(:base, message) unless message.nil?
merge_request.compare_commits = [] merge_request.compare_commits = []
merge_request.can_be_created = false merge_request.can_be_created = false
merge_request merge_request
......
...@@ -6,12 +6,13 @@ module MergeRequests ...@@ -6,12 +6,13 @@ module MergeRequests
# Called when you do merge via command line and push code # Called when you do merge via command line and push code
# to target branch # to target branch
class MergeService < BaseMergeService class MergeService < BaseMergeService
def execute(merge_request, current_user, commit_message) def execute(merge_request, commit_message)
merge_request.merge merge_request.merge
notification.merge_mr(merge_request, current_user) notification_service.merge_mr(merge_request, current_user)
create_merge_event(merge_request, current_user) create_merge_event(merge_request, current_user)
execute_project_hooks(merge_request) create_note(merge_request)
execute_hooks(merge_request)
true true
rescue rescue
......
...@@ -32,7 +32,9 @@ module MergeRequests ...@@ -32,7 +32,9 @@ module MergeRequests
merge_requests.uniq.select(&:source_project).each do |merge_request| merge_requests.uniq.select(&:source_project).each do |merge_request|
MergeRequests::MergeService.new.execute(merge_request, @current_user, nil) MergeRequests::MergeService.
new(merge_request.target_project, @current_user).
execute(merge_request, nil)
end end
end end
......
module Notes
class UpdateService < BaseService
def execute
note = project.notes.find(params[:note_id])
note.note = params[:note]
if note.save
notification_service.new_note(note)
# Skip system notes, like status changes and cross-references.
unless note.system
event_service.leave_note(note, note.author)
# Create a cross-reference note if this Note contains GFM that
# names an issue, merge request, or commit.
note.references.each do |mentioned|
Note.create_cross_reference_note(mentioned, note.noteable,
note.author, note.project)
end
end
end
note
end
end
end
...@@ -144,6 +144,10 @@ class NotificationService ...@@ -144,6 +144,10 @@ class NotificationService
# Merge project watchers # Merge project watchers
recipients = recipients.concat(project_watchers(note.project)).compact.uniq recipients = recipients.concat(project_watchers(note.project)).compact.uniq
# Reject mention users unless mentioned in comment
recipients = reject_mention_users(recipients - note.mentioned_users, note.project)
recipients = recipients + note.mentioned_users
# Reject mutes users # Reject mutes users
recipients = reject_muted_users(recipients, note.project) recipients = reject_muted_users(recipients, note.project)
...@@ -285,13 +289,39 @@ class NotificationService ...@@ -285,13 +289,39 @@ class NotificationService
end end
end end
# Remove users with notification level 'Mentioned'
def reject_mention_users(users, project = nil)
users = users.to_a.compact.uniq
users.reject do |user|
next user.notification.mention? unless project
tm = project.project_members.find_by(user_id: user.id)
if !tm && project.group
tm = project.group.group_members.find_by(user_id: user.id)
end
# reject users who globally set mention notification and has no membership
next user.notification.mention? unless tm
# reject users who set mention notification in project
next true if tm.notification.mention?
# reject users who have N_MENTION in project and disabled in global settings
tm.notification.global? && user.notification.mention?
end
end
def new_resource_email(target, project, method) def new_resource_email(target, project, method)
if target.respond_to?(:participants) if target.respond_to?(:participants)
recipients = target.participants recipients = target.participants
else else
recipients = [] recipients = []
end end
recipients = reject_muted_users(recipients, project) recipients = reject_muted_users(recipients, project)
recipients = reject_mention_users(recipients, project)
recipients = recipients.concat(project_watchers(project)).uniq recipients = recipients.concat(project_watchers(project)).uniq
recipients.delete(target.author) recipients.delete(target.author)
...@@ -302,6 +332,7 @@ class NotificationService ...@@ -302,6 +332,7 @@ class NotificationService
def close_resource_email(target, project, current_user, method) def close_resource_email(target, project, current_user, method)
recipients = reject_muted_users([target.author, target.assignee], project) recipients = reject_muted_users([target.author, target.assignee], project)
recipients = reject_mention_users(recipients, project)
recipients = recipients.concat(project_watchers(project)).uniq recipients = recipients.concat(project_watchers(project)).uniq
recipients.delete(current_user) recipients.delete(current_user)
...@@ -320,6 +351,7 @@ class NotificationService ...@@ -320,6 +351,7 @@ class NotificationService
# reject users with disabled notifications # reject users with disabled notifications
recipients = reject_muted_users(recipients, project) recipients = reject_muted_users(recipients, project)
recipients = reject_mention_users(recipients, project)
# Reject me from recipients if I reassign an item # Reject me from recipients if I reassign an item
recipients.delete(current_user) recipients.delete(current_user)
...@@ -331,6 +363,7 @@ class NotificationService ...@@ -331,6 +363,7 @@ class NotificationService
def reopen_resource_email(target, project, current_user, method, status) def reopen_resource_email(target, project, current_user, method, status)
recipients = reject_muted_users([target.author, target.assignee], project) recipients = reject_muted_users([target.author, target.assignee], project)
recipients = reject_mention_users(recipients, project)
recipients = recipients.concat(project_watchers(project)).uniq recipients = recipients.concat(project_watchers(project)).uniq
recipients.delete(current_user) recipients.delete(current_user)
......
module Oauth2::AccessTokenValidationService
# Results:
VALID = :valid
EXPIRED = :expired
REVOKED = :revoked
INSUFFICIENT_SCOPE = :insufficient_scope
class << self
def validate(token, scopes: [])
if token.expired?
return EXPIRED
elsif token.revoked?
return REVOKED
elsif !self.sufficent_scope?(token, scopes)
return INSUFFICIENT_SCOPE
else
return VALID
end
end
protected
# True if the token's scope is a superset of required scopes,
# or the required scopes is empty.
def sufficent_scope?(token, scopes)
if scopes.blank?
# if no any scopes required, the scopes of token is sufficient.
return true
else
# If there are scopes required, then check whether
# the set of authorized scopes is a superset of the set of required scopes
required_scopes = Set.new(scopes)
authorized_scopes = Set.new(token.scopes)
return authorized_scopes >= required_scopes
end
end
end
end
\ No newline at end of file
...@@ -12,12 +12,17 @@ module Projects ...@@ -12,12 +12,17 @@ module Projects
@project.visibility_level = default_features.visibility_level @project.visibility_level = default_features.visibility_level
end end
# Parametrize path for project # Set project name from path
# if @project.name.present? && @project.path.present?
# Ex. # if both name and path set - everything is ok
# 'GitLab HQ'.parameterize => "gitlab-hq" elsif @project.path.present?
# # Set project name from path
@project.path = @project.name.dup.parameterize unless @project.path.present? @project.name = @project.path.dup
elsif @project.name.present?
# For compatibility - set path from name
# TODO: remove this in 8.0
@project.path = @project.name.dup.parameterize
end
# get namespace id # get namespace id
namespace_id = params[:namespace_id] namespace_id = params[:namespace_id]
......
...@@ -21,17 +21,6 @@ ...@@ -21,17 +21,6 @@
= link_to 'Cancel', admin_groups_path, class: "btn btn-cancel" = link_to 'Cancel', admin_groups_path, class: "btn btn-cancel"
- else - else
.form-group.group_name_holder
= f.label :path, class: 'control-label' do
%span Group path
.col-sm-10
= f.text_field :path, placeholder: "example-group", class: "form-control danger"
.bs-callout.bs-callout-danger
%ul
%li Changing group path can have unintended side effects.
%li Renaming group path will rename directory for all related projects
%li It will change web url for access group and group projects.
%li It will change the git path to repositories under this group.
.form-actions .form-actions
= f.submit 'Save changes', class: "btn btn-primary" = f.submit 'Save changes', class: "btn btn-primary"
= link_to 'Cancel', admin_group_path(@group), class: "btn btn-cancel" = link_to 'Cancel', admin_group_path(@group), class: "btn btn-cancel"
......
= render "profiles/keys/key_details", admin: true
...@@ -20,6 +20,8 @@ ...@@ -20,6 +20,8 @@
%a{"data-toggle" => "tab", href: "#groups"} Groups %a{"data-toggle" => "tab", href: "#groups"} Groups
%li %li
%a{"data-toggle" => "tab", href: "#projects"} Projects %a{"data-toggle" => "tab", href: "#projects"} Projects
%li
%a{"data-toggle" => "tab", href: "#ssh-keys"} SSH keys
.tab-content .tab-content
#account.tab-pane.active #account.tab-pane.active
...@@ -217,3 +219,5 @@ ...@@ -217,3 +219,5 @@
- if tm.respond_to? :project - if tm.respond_to? :project
= link_to project_team_member_path(project, @user), data: { confirm: remove_from_project_team_message(project, @user) }, remote: true, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from project' do = link_to project_team_member_path(project, @user), data: { confirm: remove_from_project_team_message(project, @user) }, remote: true, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from project' do
%i.fa.fa-times %i.fa.fa-times
#ssh-keys.tab-pane
= render 'profiles/keys/key_table', admin: true
.panel.panel-default .panel.panel-default
.panel-heading.clearfix .panel-heading.clearfix
= search_field_tag :filter_group, nil, placeholder: 'Filter by name', class: 'dash-filter form-control' .input-group
- if current_user.can_create_group? = search_field_tag :filter_group, nil, placeholder: 'Filter by name', class: 'dash-filter form-control'
= link_to new_group_path, class: "btn btn-new pull-right" do - if current_user.can_create_group?
%i.fa.fa-plus .input-group-addon
New group = link_to new_group_path, class: "" do
%strong New group
%ul.well-list.dash-list %ul.well-list.dash-list
- groups.each do |group| - groups.each do |group|
%li.group-row %li.group-row
......
.panel.panel-default .panel.panel-default
.panel-heading.clearfix .panel-heading.clearfix
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'dash-filter form-control' .input-group
- if current_user.can_create_project? = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'dash-filter form-control'
= link_to new_project_path, class: "btn btn-new pull-right" do - if current_user.can_create_project?
%i.fa.fa-plus .input-group-addon
New project = link_to new_project_path, class: "" do
%strong New project
%ul.well-list.dash-list %ul.well-list.dash-list
- projects.each do |project| - projects.each do |project|
......
%fieldset .dash-projects-filters.append-bottom-20
%ul.nav.nav-pills.nav-stacked .pull-left.append-right-20
= nav_tab :scope, nil do %ul.nav.nav-pills.nav-compact
= link_to projects_dashboard_filter_path(scope: nil) do = nav_tab :scope, nil do
All = link_to projects_dashboard_filter_path(scope: nil) do
%span.pull-right All
= current_user.authorized_projects.count = nav_tab :scope, 'personal' do
= nav_tab :scope, 'personal' do = link_to projects_dashboard_filter_path(scope: 'personal') do
= link_to projects_dashboard_filter_path(scope: 'personal') do Personal
Personal = nav_tab :scope, 'joined' do
%span.pull-right = link_to projects_dashboard_filter_path(scope: 'joined') do
= current_user.personal_projects.count Joined
= nav_tab :scope, 'joined' do = nav_tab :scope, 'owned' do
= link_to projects_dashboard_filter_path(scope: 'joined') do = link_to projects_dashboard_filter_path(scope: 'owned') do
Joined Owned
%span.pull-right
= current_user.authorized_projects.joined(current_user).count
= nav_tab :scope, 'owned' do
= link_to projects_dashboard_filter_path(scope: 'owned') do
Owned
%span.pull-right
= current_user.owned_projects.count
%fieldset .dropdown.inline.append-right-10
%legend Visibility %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%ul.nav.nav-pills.nav-stacked.nav-small.visibility-filter %i.fa.fa-globe
- Gitlab::VisibilityLevel.values.each do |level| %span.light Visibility:
%li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' } - if params[:visibility_level].present?
= link_to projects_dashboard_filter_path(visibility_level: level) do = visibility_level_label(params[:visibility_level].to_i)
= visibility_level_icon(level) - else
= visibility_level_label(level) Any
%b.caret
%ul.dropdown-menu
%li
= link_to projects_dashboard_filter_path(visibility_level: nil) do
Any
- Gitlab::VisibilityLevel.values.each do |level|
%li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' }
= link_to projects_dashboard_filter_path(visibility_level: level) do
= visibility_level_icon(level)
= visibility_level_label(level)
- if @groups.present? - if @groups.present?
%fieldset .dropdown.inline.append-right-10
%legend Groups %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%ul.nav.nav-pills.nav-stacked.nav-small %i.fa.fa-group
- @groups.each do |group| %span.light Group:
%li{ class: (group.name == params[:group]) ? 'active' : 'light' } - if params[:group].present?
= link_to projects_dashboard_filter_path(group: group.name) do = Group.find_by(name: params[:group]).name
%i.fa.fa-folder-o - else
= group.name Any
%small.pull-right %b.caret
= group.projects.count %ul.dropdown-menu
%li
= link_to projects_dashboard_filter_path(group: nil) do
Any
- @groups.each do |group|
%li{ class: (group.name == params[:group]) ? 'active' : 'light' }
= link_to projects_dashboard_filter_path(group: group.name) do
= group.name
%small.pull-right
= group.projects.count
- if @tags.present? - if @tags.present?
%fieldset .dropdown.inline.append-right-10
%legend Tags %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%ul.nav.nav-pills.nav-stacked.nav-small %i.fa.fa-tags
- @tags.each do |tag| %span.light Tags:
%li{ class: (tag.name == params[:tag]) ? 'active' : 'light' } - if params[:tag].present?
= link_to projects_dashboard_filter_path(scope: params[:scope], tag: tag.name) do = params[:tag]
%i.fa.fa-tag - else
= tag.name Any
%b.caret
%ul.dropdown-menu
%li
= link_to projects_dashboard_filter_path(tag: nil) do
Any
- @tags.each do |tag|
%li{ class: (tag.name == params[:tag]) ? 'active' : 'light' }
= link_to projects_dashboard_filter_path(tag: tag.name) do
%i.fa.fa-tag
= tag.name
.pull-right
.dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%span.light sort:
- if @sort.present?
= @sort.humanize
- else
Name
%b.caret
%ul.dropdown-menu
%li
= link_to projects_dashboard_filter_path(sort: nil) do
Name
= link_to projects_dashboard_filter_path(sort: 'newest') do
= sort_title_recently_created
= link_to projects_dashboard_filter_path(sort: 'oldest') do
= sort_title_oldest_created
= link_to projects_dashboard_filter_path(sort: 'recently_updated') do
= sort_title_recently_updated
= link_to projects_dashboard_filter_path(sort: 'last_updated') do
= sort_title_oldest_updated
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
File mode changed from 100755 to 100644
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