Commit 4df15fe3 authored by Sytse Sijbrandij's avatar Sytse Sijbrandij

Merge branch 'master' into gitlab-flow

Conflicts:
	CONTRIBUTING.md
parents 6f61cbd1 2ebd2d41
...@@ -38,3 +38,4 @@ public/uploads.* ...@@ -38,3 +38,4 @@ public/uploads.*
public/assets/ public/assets/
.envrc .envrc
dump.rdb dump.rdb
tags
...@@ -5,6 +5,8 @@ targets: ...@@ -5,6 +5,8 @@ targets:
debian-7: &wheezy debian-7: &wheezy
build_dependencies: build_dependencies:
- libicu-dev - libicu-dev
- cmake
- pkg-config
dependencies: dependencies:
- libicu48 - libicu48
- libpcre3 - libpcre3
...@@ -13,6 +15,8 @@ targets: ...@@ -13,6 +15,8 @@ targets:
ubuntu-14.04: ubuntu-14.04:
build_dependencies: build_dependencies:
- libicu-dev - libicu-dev
- cmake
- pkg-config
dependencies: dependencies:
- libicu52 - libicu52
- libpcre3 - libpcre3
...@@ -20,6 +24,8 @@ targets: ...@@ -20,6 +24,8 @@ targets:
centos-6: centos-6:
build_dependencies: build_dependencies:
- libicu-devel - libicu-devel
- cmake
- pkgconfig
dependencies: dependencies:
- libicu - libicu
- pcre - pcre
......
language: ruby
env:
global:
- TRAVIS=true
matrix:
- TASK=spinach_project DB=mysql
- TASK=spinach_other DB=mysql
- TASK=spec:api DB=mysql
- TASK=spec:feature DB=mysql
- TASK=spec:other DB=mysql
- TASK=jasmine:ci DB=mysql
- TASK=spinach_project DB=postgresql
- TASK=spinach_other DB=postgresql
- TASK=spec:api DB=postgresql
- TASK=spec:feature DB=postgresql
- TASK=spec:other DB=postgresql
- TASK=jasmine:ci DB=postgresql
before_install:
- sudo apt-get install libicu-dev -y
install:
- "travis_retry bundle install --deployment --without production --retry 5"
branches:
only:
- 'master'
rvm:
- 2.0.0
services:
- redis-server
before_script:
- "cp config/database.yml.$DB config/database.yml"
- "cp config/gitlab.yml.example config/gitlab.yml"
- "bundle exec rake db:setup"
- "bundle exec rake db:seed_fu"
script: "bundle exec rake $TASK --trace"
notifications:
email: false
v 7.4.0
- Refactored membership logic
- Fix creating new files with web editor
- Improve error reporting on users API (Julien Bianchi)
- Refactor test coverage tools usage. Use SIMPLECOV=true to generate it locally
- Increase unicorn timeout to 60 seconds
- Sort search autocomplete projects by stars count so most popular go first
v 7.3.1
- Fix ref parsing in Gitlab::GitAccess
- Fix error 500 when viewing diff on a file with changed permissions
- Fix adding comments to MR when source branch is master
- Fix error 500 when searching description contains relative link
v 7.3.0 v 7.3.0
- Always set the 'origin' remote in satellite actions - Always set the 'origin' remote in satellite actions
- Write authorized_keys in tmp/ during tests
- Use sockets to connect to Redis
- Add dormant New Relic gem (can be enabled via environment variables)
- Expire Rack sessions after 1 week
- Cleaner signin/signup pages
- Improved comments UI
- Better search with filtering, pagination etc
- Added a checkbox to toggle line wrapping in diff (Yuriy Glukhov)
- Prevent project stars duplication when fork project
- Use the default Unicorn socket backlog value of 1024
- Support Unix domain sockets for Redis
- Store session Redis keys in 'session:gitlab:' namespace
- Deprecate LDAP account takeover based on partial LDAP email / GitLab username match
- Use /bin/sh instead of Bash in bin/web, bin/background_jobs (Pavel Novitskiy)
- Keyboard shortcuts for productivity (Robert Schilling)
- API: filter issues by state (Julien Bianchi)
- API: filter issues by labels (Julien Bianchi)
- Add system hook for ssh key changes
- Add blob permalink link (Ciro Santilli)
- Create annotated tags through UI and API (Sean Edge)
- Snippets search (Charles Bushong)
- Comment new push to existing MR
- Add 'ci' to the blacklist of forbidden names
- Improve text filtering on issues page
- Comment & Close button
- Process git push --all much faster
- Don't allow edit of system notes
- Project wiki search (Ralf Seidler)
- Enabled Shibboleth authentication support (Matus Banas)
- Zen mode (fullscreen) for issues/MR/notes (Robert Schilling)
- Add ability to configure webhook timeout via gitlab.yml (Wes Gurney)
- Sort project merge requests in asc or desc order for updated_at or created_at field (sponsored by O'Reilly Media)
- Add Redis socket support to 'rake gitlab:shell:install'
v 7.2.1
- Delete orphaned labels during label migration (James Brooks)
- Security: prevent XSS with stricter MIME types for raw repo files
v 7.2.0 v 7.2.0
- Explore page - Explore page
......
...@@ -44,7 +44,7 @@ Please send a merge request with a tested solution or a merge request with a fai ...@@ -44,7 +44,7 @@ Please send a merge request with a tested solution or a merge request with a fai
1. **Output of checks** 1. **Output of checks**
* Results of GitLab [Application Check](doc/install/installation.md#check-application-status) (`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`); we will only investigate if the tests are passing * Results of GitLab [Application Check](doc/install/installation.md#check-application-status) (`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`); we will only investigate if the tests are passing
* Version of GitLab you are running; we will only investigate issues in the latest stable and development releases as per the [maintenance policy](MAINTENANCE.md) * Version of GitLab you are running; we will only investigate issues in the latest stable and development releases as per the [maintenance policy](MAINTENANCE.md)
* Add the last commit sha1 of the GitLab version you used to replicate the issue (obtainable from the help page) * Add the last commit SHA-1 of the GitLab version you used to replicate the issue (obtainable from the help page)
* Describe your setup (use relevant parts from `sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`) * Describe your setup (use relevant parts from `sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
1. **Possible fixes**: If you can, link to the line of code that might be responsible for the problem 1. **Possible fixes**: If you can, link to the line of code that might be responsible for the problem
...@@ -62,6 +62,7 @@ If you can, please submit a merge request with the fix or improvements including ...@@ -62,6 +62,7 @@ If you can, please submit a merge request with the fix or improvements including
1. Create a feature branch 1. Create a feature branch
1. Write [tests](README.md#run-the-tests) and code 1. Write [tests](README.md#run-the-tests) and code
1. Add your changes to the [CHANGELOG](CHANGELOG) insert your line at a [random point](doc/workflow/gitlab_flow.md#do-not-order-commits-with-rebase) in the current version 1. Add your changes to the [CHANGELOG](CHANGELOG) insert your line at a [random point](doc/workflow/gitlab_flow.md#do-not-order-commits-with-rebase) in the current version
1. If you are changing the README, some documentation or other things which have no effect on the tests, add `[ci skip]` somewhere in the commit message
1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) 1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
1. Push the commit to your fork 1. Push the commit to your fork
1. Submit a merge request (MR) to the master branch 1. Submit a merge request (MR) to the master branch
...@@ -75,7 +76,7 @@ If you can, please submit a merge request with the fix or improvements including ...@@ -75,7 +76,7 @@ If you can, please submit a merge request with the fix or improvements including
The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. The best time to submit a MR and get feedback fast. Before this time the GitLab B.V. team is still dealing with work that is created by the monthly release such as assisting subscribers with upgrade issues, the release of Enterprise Edition and the upgrade of GitLab Cloud. After the 7th it is already getting closer to the release date of the next version. This means there is less time to fix the issues created by merging large new features. The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. The best time to submit a MR and get feedback fast. Before this time the GitLab B.V. team is still dealing with work that is created by the monthly release such as assisting subscribers with upgrade issues, the release of Enterprise Edition and the upgrade of GitLab Cloud. After the 7th it is already getting closer to the release date of the next version. This means there is less time to fix the issues created by merging large new features.
Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MR's that leads to higher code quality is more important to us than having a mimimal commit log. The smaller a MR is the more likely it is it will be merged (quickly), after that you can send more MR's to enhance it. Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MR's that leads to higher code quality is more important to us than having a minimal commit log. The smaller a MR is the more likely it is it will be merged (quickly), after that you can send more MR's to enhance it.
For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the following contribution acceptance criteria. For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the following contribution acceptance criteria.
...@@ -85,19 +86,19 @@ For examples of feedback on merge requests please look at already [closed merge ...@@ -85,19 +86,19 @@ For examples of feedback on merge requests please look at already [closed merge
1. Are there points in the code the reviewer needs to double check? 1. Are there points in the code the reviewer needs to double check?
1. Why was this MR needed? 1. Why was this MR needed?
1. What are the relevant issue numbers / [Feature requests](http://feedback.gitlab.com/)? 1. What are the relevant issue numbers / [Feature requests](http://feedback.gitlab.com/)?
1. Screenshots (If appropiate) 1. Screenshots (If appropriate)
## Contribution acceptance criteria ## Contribution acceptance criteria
1. The change is as small as possible (see the above paragraph for details) 1. The change is as small as possible (see the above paragraph for details)
1. Include proper tests and make all tests pass (unless it contains a test exposing a bug in existing code) 1. Include proper tests and make all tests pass (unless it contains a test exposing a bug in existing code)
1. Can merge without problems (if not please use: `git rebase master`) 1. Initially contains a single commit (please use `git rebase -i` to squash commits)
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. 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
1. Initially contains a single commit (please use `git rebase -i` to squash commits)
1. Changes after submitting the merge request should be in separate commits (no squashing). You will be asked to squash when the review is over, before merging. 1. Changes after submitting the merge request should be in separate commits (no squashing). You will be asked to squash when the review is over, before merging.
1. It conforms to the following style guides 1. It conforms to the following style guides
...@@ -114,4 +115,4 @@ For examples of feedback on merge requests please look at already [closed merge ...@@ -114,4 +115,4 @@ For examples of feedback on merge requests please look at already [closed merge
1. [Shell commands](doc/development/shell_commands.md) created by GitLab contributors to enhance security 1. [Shell commands](doc/development/shell_commands.md) created by GitLab contributors to enhance security
1. [Markdown](http://www.cirosantilli.com/markdown-styleguide) 1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
This is also the style used by linting tools such as [Rubocop](https://github.com/bbatsov/rubocop), [PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com). This is also the style used by linting tools such as [RuboCop](https://github.com/bbatsov/rubocop), [PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com).
...@@ -27,16 +27,17 @@ gem 'omniauth', "~> 1.1.3" ...@@ -27,16 +27,17 @@ gem 'omniauth', "~> 1.1.3"
gem 'omniauth-google-oauth2' gem 'omniauth-google-oauth2'
gem 'omniauth-twitter' gem 'omniauth-twitter'
gem 'omniauth-github' gem 'omniauth-github'
gem 'omniauth-shibboleth'
# 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", '~> 6.0' gem "gitlab_git", '7.0.0.rc3'
# 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'
# LDAP Auth # LDAP Auth
gem 'gitlab_omniauth-ldap', '1.0.4', require: "omniauth-ldap" gem 'gitlab_omniauth-ldap', '1.1.0', require: "omniauth-ldap"
# Git Wiki # Git Wiki
gem 'gollum-lib', '~> 3.0.0' gem 'gollum-lib', '~> 3.0.0'
...@@ -82,7 +83,7 @@ gem "seed-fu" ...@@ -82,7 +83,7 @@ gem "seed-fu"
gem "github-markup" gem "github-markup"
# Required markup gems by github-markdown # Required markup gems by github-markdown
gem 'redcarpet', '~> 2.2.2' gem 'redcarpet', '~> 3.1.2'
gem 'RedCloth' gem 'RedCloth'
gem 'rdoc', '~>3.6' gem 'rdoc', '~>3.6'
gem 'org-ruby' gem 'org-ruby'
...@@ -156,6 +157,9 @@ gem "rack-attack" ...@@ -156,6 +157,9 @@ gem "rack-attack"
# Ace editor # Ace editor
gem 'ace-rails-ap' gem 'ace-rails-ap'
# Keyboard shortcuts
gem 'mousetrap-rails'
# Semantic UI Sass for Sidebar # Semantic UI Sass for Sidebar
gem 'semantic-ui-sass', '~> 0.16.1.0' gem 'semantic-ui-sass', '~> 0.16.1.0'
...@@ -231,7 +235,7 @@ group :development, :test do ...@@ -231,7 +235,7 @@ group :development, :test do
gem 'jasmine', '2.0.2' gem 'jasmine', '2.0.2'
gem "spring", '1.1.1' gem "spring", '1.1.3'
gem "spring-commands-rspec", '1.0.1' gem "spring-commands-rspec", '1.0.1'
gem "spring-commands-spinach", '1.0.0' gem "spring-commands-spinach", '1.0.0'
end end
...@@ -247,3 +251,5 @@ end ...@@ -247,3 +251,5 @@ end
group :production do group :production do
gem "gitlab_meta", '7.0' gem "gitlab_meta", '7.0'
end end
gem "newrelic_rpm"
...@@ -106,7 +106,7 @@ GEM ...@@ -106,7 +106,7 @@ GEM
devise (~> 3.2) devise (~> 3.2)
diff-lcs (1.2.5) diff-lcs (1.2.5)
diffy (3.0.3) diffy (3.0.3)
docile (1.1.1) docile (1.1.5)
dotenv (0.9.0) dotenv (0.9.0)
dropzonejs-rails (0.4.14) dropzonejs-rails (0.4.14)
rails (> 3.1) rails (> 3.1)
...@@ -168,7 +168,7 @@ GEM ...@@ -168,7 +168,7 @@ GEM
multi_json multi_json
gitlab-grack (2.0.0.pre) gitlab-grack (2.0.0.pre)
rack (~> 1.5.1) rack (~> 1.5.1)
gitlab-grit (2.6.10) gitlab-grit (2.6.11)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
diff-lcs (~> 1.1) diff-lcs (~> 1.1)
mime-types (~> 1.15) mime-types (~> 1.15)
...@@ -179,15 +179,15 @@ GEM ...@@ -179,15 +179,15 @@ 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 (6.2.1) gitlab_git (7.0.0.rc3)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
gitlab-grit (~> 2.6)
gitlab-linguist (~> 3.0) gitlab-linguist (~> 3.0)
rubyzip (~> 1.1)
rugged (~> 0.21.0) rugged (~> 0.21.0)
gitlab_meta (7.0) gitlab_meta (7.0)
gitlab_omniauth-ldap (1.0.4) gitlab_omniauth-ldap (1.1.0)
net-ldap (~> 0.3.1) net-ldap (~> 0.7.0)
omniauth (~> 1.0) omniauth (~> 1.0)
pyu-ruby-sasl (~> 0.0.3.1) pyu-ruby-sasl (~> 0.0.3.1)
rubyntlm (~> 0.1.1) rubyntlm (~> 0.1.1)
...@@ -287,14 +287,16 @@ GEM ...@@ -287,14 +287,16 @@ GEM
mime-types (1.25.1) mime-types (1.25.1)
mini_portile (0.6.0) mini_portile (0.6.0)
minitest (5.3.5) minitest (5.3.5)
mousetrap-rails (1.4.6)
multi_json (1.10.1) multi_json (1.10.1)
multi_xml (0.5.5) multi_xml (0.5.5)
multipart-post (1.2.0) multipart-post (1.2.0)
mysql2 (0.3.16) mysql2 (0.3.16)
net-ldap (0.3.1) net-ldap (0.7.0)
net-scp (1.1.2) net-scp (1.1.2)
net-ssh (>= 2.6.5) net-ssh (>= 2.6.5)
net-ssh (2.8.0) net-ssh (2.8.0)
newrelic_rpm (3.9.4.245)
nokogiri (1.6.2.1) nokogiri (1.6.2.1)
mini_portile (= 0.6.0) mini_portile (= 0.6.0)
nprogress-rails (0.1.2.3) nprogress-rails (0.1.2.3)
...@@ -320,6 +322,8 @@ GEM ...@@ -320,6 +322,8 @@ GEM
omniauth-oauth2 (1.1.1) omniauth-oauth2 (1.1.1)
oauth2 (~> 0.8.0) oauth2 (~> 0.8.0)
omniauth (~> 1.0) omniauth (~> 1.0)
omniauth-shibboleth (1.1.1)
omniauth (>= 1.0.0)
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)
...@@ -391,7 +395,7 @@ GEM ...@@ -391,7 +395,7 @@ GEM
ffi (>= 0.5.0) ffi (>= 0.5.0)
rdoc (3.12.2) rdoc (3.12.2)
json (~> 1.4) json (~> 1.4)
redcarpet (2.2.2) redcarpet (3.1.2)
redis (3.0.6) redis (3.0.6)
redis-actionpack (4.0.0) redis-actionpack (4.0.0)
actionpack (~> 4) actionpack (~> 4)
...@@ -436,6 +440,7 @@ GEM ...@@ -436,6 +440,7 @@ GEM
ruby-progressbar (1.2.0) ruby-progressbar (1.2.0)
rubyntlm (0.1.1) rubyntlm (0.1.1)
rubypants (0.2.0) rubypants (0.2.0)
rubyzip (1.1.6)
rugged (0.21.0) rugged (0.21.0)
safe_yaml (0.9.7) safe_yaml (0.9.7)
sanitize (2.1.0) sanitize (2.1.0)
...@@ -467,7 +472,7 @@ GEM ...@@ -467,7 +472,7 @@ GEM
redis (>= 3.0.4) redis (>= 3.0.4)
redis-namespace (>= 1.3.1) redis-namespace (>= 1.3.1)
simple_oauth (0.1.9) simple_oauth (0.1.9)
simplecov (0.8.2) simplecov (0.9.0)
docile (~> 1.1.0) docile (~> 1.1.0)
multi_json multi_json
simplecov-html (~> 0.8.0) simplecov-html (~> 0.8.0)
...@@ -489,7 +494,7 @@ GEM ...@@ -489,7 +494,7 @@ GEM
capybara (>= 2.0.0) capybara (>= 2.0.0)
railties (>= 3) railties (>= 3)
spinach (>= 0.4) spinach (>= 0.4)
spring (1.1.1) spring (1.1.3)
spring-commands-rspec (1.0.1) spring-commands-rspec (1.0.1)
spring (>= 0.9.1) spring (>= 0.9.1)
spring-commands-spinach (1.0.0) spring-commands-spinach (1.0.0)
...@@ -613,9 +618,9 @@ DEPENDENCIES ...@@ -613,9 +618,9 @@ 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 (~> 6.0) gitlab_git (= 7.0.0.rc3)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.0.4) gitlab_omniauth-ldap (= 1.1.0)
gollum-lib (~> 3.0.0) gollum-lib (~> 3.0.0)
gon (~> 5.0.0) gon (~> 5.0.0)
grape (~> 0.6.1) grape (~> 0.6.1)
...@@ -636,11 +641,14 @@ DEPENDENCIES ...@@ -636,11 +641,14 @@ DEPENDENCIES
launchy launchy
letter_opener letter_opener
minitest (~> 5.3.0) minitest (~> 5.3.0)
mousetrap-rails
mysql2 mysql2
newrelic_rpm
nprogress-rails nprogress-rails
omniauth (~> 1.1.3) omniauth (~> 1.1.3)
omniauth-github omniauth-github
omniauth-google-oauth2 omniauth-google-oauth2
omniauth-shibboleth
omniauth-twitter omniauth-twitter
org-ruby org-ruby
pg pg
...@@ -657,7 +665,7 @@ DEPENDENCIES ...@@ -657,7 +665,7 @@ DEPENDENCIES
rb-fsevent rb-fsevent
rb-inotify rb-inotify
rdoc (~> 3.6) rdoc (~> 3.6)
redcarpet (~> 2.2.2) redcarpet (~> 3.1.2)
redis-rails redis-rails
request_store request_store
rspec-rails rspec-rails
...@@ -676,7 +684,7 @@ DEPENDENCIES ...@@ -676,7 +684,7 @@ DEPENDENCIES
slack-notifier (~> 0.3.2) slack-notifier (~> 0.3.2)
slim slim
spinach-rails spinach-rails
spring (= 1.1.1) spring (= 1.1.3)
spring-commands-rspec (= 1.0.1) spring-commands-rspec (= 1.0.1)
spring-commands-spinach (= 1.0.0) spring-commands-spinach (= 1.0.0)
stamp stamp
......
# A sample Guardfile # A sample Guardfile
# More info at https://github.com/guard/guard#readme # More info at https://github.com/guard/guard#readme
guard 'rspec', cmd: "spring rspec", version: 2, all_on_start: false, all_after_pass: false do guard 'rspec', cmd: "spring rspec", all_on_start: false, all_after_pass: false do
watch(%r{^spec/.+_spec\.rb$}) watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
watch(%r{^lib/api/(.+)\.rb$}) { |m| "spec/requests/api/#{m[1]}_spec.rb" } watch(%r{^lib/api/(.+)\.rb$}) { |m| "spec/requests/api/#{m[1]}_spec.rb" }
...@@ -19,7 +19,7 @@ guard 'rspec', cmd: "spring rspec", version: 2, all_on_start: false, all_after_p ...@@ -19,7 +19,7 @@ guard 'rspec', cmd: "spring rspec", version: 2, all_on_start: false, all_after_p
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" } watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
end end
guard 'spinach' do guard 'spinach', command_prefix: 'spring' do
watch(%r|^features/(.*)\.feature|) watch(%r|^features/(.*)\.feature|)
watch(%r|^features/steps/(.*)([^/]+)\.rb|) do |m| watch(%r|^features/steps/(.*)([^/]+)\.rb|) do |m|
"features/#{m[1]}#{m[2]}.feature" "features/#{m[1]}#{m[2]}.feature"
......
...@@ -87,7 +87,7 @@ Please use ``` to format console output, logs, and code as it's very hard to rea ...@@ -87,7 +87,7 @@ Please use ``` to format console output, logs, and code as it's very hard to rea
### Issue fixed in newer version ### Issue fixed in newer version
Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(https://github.com/gitlabhq/gitlabhq/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Improperly formatted merge request ### Improperly formatted merge request
......
# GitLab # ![logo](https://about.gitlab.com/images/gitlab_logo.png) GitLab
## Open source software to collaborate on code ## Open source software to collaborate on code
![logo](https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/gitlab_logo.png) ![Animated screenshots](https://about.gitlab.com/images/animated/compiled.gif)
![animated-screenshots](https://gist.github.com/fnkr/2f9badd56bfe0ed04ee7/raw/4f48806fbae97f556c2f78d8c2d299c04500cb0d/compiled.gif)
- Manage Git repositories with fine grained access controls that keep your code secure - Manage Git repositories with fine grained access controls that keep your code secure
- Perform code reviews and enhance collaboration with merge requests - Perform code reviews and enhance collaboration with merge requests
...@@ -21,7 +19,9 @@ ...@@ -21,7 +19,9 @@
- [![build status](https://ci.gitlab.org/projects/1/status.png?ref=master)](https://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch) - [![build status](https://ci.gitlab.org/projects/1/status.png?ref=master)](https://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch)
- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.png)](https://codeclimate.com/github/gitlabhq/gitlabhq) - [![Build Status](https://semaphoreapp.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/243338/badge.png)](https://semaphoreapp.com/gitlabhq/gitlabhq)
- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq) - [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq)
...@@ -29,14 +29,14 @@ ...@@ -29,14 +29,14 @@
## Website ## Website
On [www.gitlab.com](https://www.gitlab.com/) you can find more information about: On [about.gitlab.com](https://about.gitlab.com/) you can find more information about:
- [Subscriptions](https://www.gitlab.com/subscription/) - [Subscriptions](https://about.gitlab.com/subscription/)
- [Consultancy](https://www.gitlab.com/consultancy/) - [Consultancy](https://about.gitlab.com/consultancy/)
- [Community](https://www.gitlab.com/community/) - [Community](https://about.gitlab.com/community/)
- [Hosted GitLab.com](https://www.gitlab.com/gitlab-com/) use GitLab as a free service - [Hosted GitLab.com](https://about.gitlab.com/gitlab-com/) use GitLab as a free service
- [GitLab Enterprise Edition](https://www.gitlab.com/gitlab-ee/) with additional features aimed at larger organizations. - [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/) with additional features aimed at larger organizations.
- [GitLab CI](https://www.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab. - [GitLab CI](https://about.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab.
## Third-party applications ## Third-party applications
...@@ -61,11 +61,11 @@ These applications are maintained by contributors, GitLab B.V. does not offer su ...@@ -61,11 +61,11 @@ These applications are maintained by contributors, GitLab B.V. does not offer su
## Installation ## Installation
Please see [the installation page on the GitLab website](https://www.gitlab.com/installation/). Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/).
### 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://www.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
...@@ -85,7 +85,8 @@ Please login with `root` / `5iveL!fe` ...@@ -85,7 +85,8 @@ Please login with `root` / `5iveL!fe`
## Install a development environment ## Install a development environment
We recommend setting up your development environment with [the cookbook](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/README.md#installation). If you do not use the cookbook you might need to copy the example development unicorn configuration file 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
cp config/unicorn.rb.example.development config/unicorn.rb cp config/unicorn.rb.example.development config/unicorn.rb
...@@ -126,7 +127,7 @@ All documentation can be found on [doc.gitlab.com/ce/](http://doc.gitlab.com/ce/ ...@@ -126,7 +127,7 @@ All documentation can be found on [doc.gitlab.com/ce/](http://doc.gitlab.com/ce/
## Getting help ## Getting help
Please see [Getting help for GitLab](https://www.gitlab.com/getting-help/) on our website for the many options to get help. Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on our website for the many options to get help.
## Is it any good? ## Is it any good?
......
7.3.0.pre 7.4.0-pre
...@@ -46,10 +46,10 @@ class Admin ...@@ -46,10 +46,10 @@ class Admin
modal.hide() modal.hide()
$('.change-owner-link').show() $('.change-owner-link').show()
$('li.users_project').bind 'ajax:success', -> $('li.project_member').bind 'ajax:success', ->
Turbolinks.visit(location.href) Turbolinks.visit(location.href)
$('li.users_group').bind 'ajax:success', -> $('li.group_member').bind 'ajax:success', ->
Turbolinks.visit(location.href) Turbolinks.visit(location.href)
@Admin = Admin @Admin = Admin
...@@ -15,22 +15,31 @@ ...@@ -15,22 +15,31 @@
#= require jquery.atwho #= require jquery.atwho
#= require jquery.scrollTo #= require jquery.scrollTo
#= require jquery.blockUI #= require jquery.blockUI
#= require turbolinks
#= require jquery.turbolinks #= require jquery.turbolinks
#= require turbolinks
#= require bootstrap #= require bootstrap
#= require select2 #= require select2
#= require raphael #= require raphael
#= require g.raphael-min #= require g.raphael-min
#= require g.bar-min #= require g.bar-min
#= require chart-lib.min
#= require branch-graph #= require branch-graph
#= require highlight.pack #= require highlight.pack
#= require ace/ace #= require ace/ace
#= require ace/ext-searchbox
#= require d3 #= require d3
#= require underscore #= require underscore
#= require nprogress #= require nprogress
#= require nprogress-turbolinks #= require nprogress-turbolinks
#= require dropzone #= require dropzone
#= require semantic-ui/sidebar #= require semantic-ui/sidebar
#= require mousetrap
#= require mousetrap/pause
#= require shortcuts
#= require shortcuts_navigation
#= require shortcuts_dashboard_navigation
#= require shortcuts_issueable
#= require shortcuts_network
#= require_tree . #= require_tree .
window.slugify = (text) -> window.slugify = (text) ->
...@@ -117,6 +126,13 @@ $ -> ...@@ -117,6 +126,13 @@ $ ->
# Initialize select2 selects # Initialize select2 selects
$('select.select2').select2(width: 'resolve', dropdownAutoWidth: true) $('select.select2').select2(width: 'resolve', dropdownAutoWidth: true)
# Close select2 on escape
$('.js-select2').bind 'select2-close', ->
setTimeout ( ->
$('.select2-container-active').removeClass('select2-container-active')
$(':focus').blur()
), 1
# Initialize tooltips # Initialize tooltips
$('.has_tooltip').tooltip() $('.has_tooltip').tooltip()
...@@ -134,7 +150,6 @@ $ -> ...@@ -134,7 +150,6 @@ $ ->
if (flash = $(".flash-container")).length > 0 if (flash = $(".flash-container")).length > 0
flash.click -> $(@).fadeOut() flash.click -> $(@).fadeOut()
flash.show() flash.show()
setTimeout (-> flash.fadeOut()), 5000
# Disable form buttons while a form is submitting # Disable form buttons while a form is submitting
$('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) -> $('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
...@@ -149,20 +164,6 @@ $ -> ...@@ -149,20 +164,6 @@ $ ->
# Show/Hide the profile menu when hovering the account box # Show/Hide the profile menu when hovering the account box
$('.account-box').hover -> $(@).toggleClass('hover') $('.account-box').hover -> $(@).toggleClass('hover')
# Focus search field by pressing 's' key
$(document).keypress (e) ->
# Don't do anything if typing in an input
return if $(e.target).is(":input")
switch e.which
when 115
$("#search").focus()
e.preventDefault()
when 63
new Shortcuts()
e.preventDefault()
# Commit show suppressed diff # Commit show suppressed diff
$(".diff-content").on "click", ".supp_diff_link", -> $(".diff-content").on "click", ".supp_diff_link", ->
$(@).next('table').show() $(@).next('table').show()
......
$ ->
# Toggle line wrapping in diff.
#
# %div.diff-file
# %input.js-toggle-diff-line-wrap
# %td.line_content
#
$("body").on "click", ".js-toggle-diff-line-wrap", (e) ->
diffFile = $(@).closest(".diff-file")
if $(@).is(":checked")
diffFile.addClass("diff-wrap-lines")
else
diffFile.removeClass("diff-wrap-lines")
class BranchGraph class @BranchGraph
constructor: (@element, @options) -> constructor: (@element, @options) ->
@preparedCommits = {} @preparedCommits = {}
@mtime = 0 @mtime = 0
...@@ -90,11 +90,15 @@ class BranchGraph ...@@ -90,11 +90,15 @@ class BranchGraph
renderPartialGraph: -> renderPartialGraph: ->
start = Math.floor((@element.scrollTop() - @offsetY) / @unitTime) - 10 start = Math.floor((@element.scrollTop() - @offsetY) / @unitTime) - 10
start = 0 if start < 0 if start < 0
isGraphEdge = true
start = 0
end = start + 40 end = start + 40
end = @commits.length if @commits.length < end if @commits.length < end
isGraphEdge = true
end = @commits.length
if @prev_start == -1 or Math.abs(@prev_start - start) > 10 if @prev_start == -1 or Math.abs(@prev_start - start) > 10 or isGraphEdge
i = start i = start
@prev_start = start @prev_start = start
...@@ -120,23 +124,32 @@ class BranchGraph ...@@ -120,23 +124,32 @@ class BranchGraph
@top.toFront() @top.toFront()
bindEvents: -> bindEvents: ->
drag = {}
element = @element element = @element
$(element).scroll (event) => $(element).scroll (event) =>
@renderPartialGraph() @renderPartialGraph()
$(window).on scrollDown: =>
keydown: (event) => @element.scrollTop @element.scrollTop() + 50
# left @renderPartialGraph()
element.scrollLeft element.scrollLeft() - 50 if event.keyCode is 37
# top scrollUp: =>
element.scrollTop element.scrollTop() - 50 if event.keyCode is 38 @element.scrollTop @element.scrollTop() - 50
# right @renderPartialGraph()
element.scrollLeft element.scrollLeft() + 50 if event.keyCode is 39
# bottom scrollLeft: =>
element.scrollTop element.scrollTop() + 50 if event.keyCode is 40 @element.scrollLeft @element.scrollLeft() - 50
@renderPartialGraph() @renderPartialGraph()
scrollRight: =>
@element.scrollLeft @element.scrollLeft() + 50
@renderPartialGraph()
scrollBottom: =>
@element.scrollTop @element.find('svg').height()
scrollTop: =>
@element.scrollTop 0
appendLabel: (x, y, commit) -> appendLabel: (x, y, commit) ->
return unless commit.refs return unless commit.refs
...@@ -325,5 +338,3 @@ Raphael::textWrap = (t, width) -> ...@@ -325,5 +338,3 @@ Raphael::textWrap = (t, width) ->
b = t.getBBox() b = t.getBBox()
h = Math.abs(b.y2) - Math.abs(b.y) + 1 h = Math.abs(b.y2) - Math.abs(b.y) + 1
t.attr y: b.y + h t.attr y: b.y + h
@BranchGraph = BranchGraph
@Chart =
labels: []
values: []
init: (labels, values, title) ->
r = Raphael('activity-chart')
fin = ->
@flag = r.popup(@bar.x, @bar.y, @bar.value or "0").insertBefore(this)
fout = ->
@flag.animate
opacity: 0, 300, -> @remove()
r.text(160, 10, title).attr font: "13px sans-serif"
r.barchart(
10, 20, 560, 200,
[values],
{colors:["#456"]}
).label(labels, true)
.hover(fin, fout)
...@@ -15,49 +15,87 @@ class Dispatcher ...@@ -15,49 +15,87 @@ class Dispatcher
return false return false
path = page.split(':') path = page.split(':')
shortcut_handler = null
switch page switch page
when 'projects:issues:index' when 'projects:issues:index'
Issues.init() Issues.init()
shortcut_handler = new ShortcutsNavigation()
when 'projects:issues:show' when 'projects:issues:show'
new Issue() new Issue()
shortcut_handler = new ShortcutsIssueable()
new ZenMode()
when 'projects:milestones:show' when 'projects:milestones:show'
new Milestone() new Milestone()
when 'projects:issues:new' when 'projects:issues:new','projects:issues:edit'
GitLab.GfmAutoComplete.setup() GitLab.GfmAutoComplete.setup()
when 'projects:merge_requests:new' shortcut_handler = new ShortcutsNavigation()
new ZenMode()
when 'projects:merge_requests:new', 'projects:merge_requests:edit'
GitLab.GfmAutoComplete.setup() GitLab.GfmAutoComplete.setup()
new Diff() new Diff()
shortcut_handler = new ShortcutsNavigation()
new ZenMode()
when 'projects:merge_requests:show' when 'projects:merge_requests:show'
new Diff() new Diff()
shortcut_handler = new ShortcutsIssueable()
new ZenMode()
when "projects:merge_requests:diffs" when "projects:merge_requests:diffs"
new Diff() new Diff()
when 'projects:merge_requests:index'
shortcut_handler = new ShortcutsNavigation()
when 'dashboard:show' when 'dashboard:show'
new Dashboard() new Dashboard()
new Activities() new Activities()
when 'projects:commit:show' when 'projects:commit:show'
new Commit() new Commit()
new Diff() new Diff()
shortcut_handler = new ShortcutsNavigation()
when 'projects:commits:show'
shortcut_handler = new ShortcutsNavigation()
when 'groups:show', 'projects:show' when 'groups:show', 'projects:show'
new Activities() new Activities()
when 'projects:new', 'projects:edit' shortcut_handler = new ShortcutsNavigation()
when 'projects:new'
new Project() new Project()
when 'projects:edit'
new Project()
shortcut_handler = new ShortcutsNavigation()
when 'projects:teams:members:index' when 'projects:teams:members:index'
new TeamMembers() new TeamMembers()
when 'groups:members' when 'groups:members'
new GroupMembers() new GroupMembers()
when 'projects:tree:show' when 'projects:tree:show'
new TreeView() new TreeView()
shortcut_handler = new ShortcutsNavigation()
when 'projects:blob:show' when 'projects:blob:show'
new BlobView() new BlobView()
shortcut_handler = new ShortcutsNavigation()
when 'projects:labels:new', 'projects:labels:edit' when 'projects:labels:new', 'projects:labels:edit'
new Labels() new Labels()
when 'projects:network:show'
# Ensure we don't create a particular shortcut handler here. This is
# already created, where the network graph is created.
shortcut_handler = true
switch path.first() switch path.first()
when 'admin' then new Admin() when 'admin' then new Admin()
when 'dashboard'
shortcut_handler = new ShortcutsDashboardNavigation()
when 'projects' when 'projects'
new Wikis() if path[1] == 'wikis' switch path[1]
when 'wikis'
new Wikis()
shortcut_handler = new ShortcutsNavigation()
when 'snippets', 'labels', 'graphs'
shortcut_handler = new ShortcutsNavigation()
when 'team_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
shortcut_handler = new ShortcutsNavigation()
# If we haven't installed a custom shortcut handler, install the default one
if not shortcut_handler
new Shortcuts()
initSearch: -> initSearch: ->
opts = $('.search-autocomplete-opts') opts = $('.search-autocomplete-opts')
......
...@@ -10,6 +10,5 @@ class Flash ...@@ -10,6 +10,5 @@ class Flash
flash.click -> $(@).fadeOut() flash.click -> $(@).fadeOut()
flash.show() flash.show()
setTimeout (-> flash.fadeOut()), 5000
@Flash = Flash @Flash = Flash
class GroupMembers class GroupMembers
constructor: -> constructor: ->
$('li.users_group').bind 'ajax:success', -> $('li.group_member').bind 'ajax:success', ->
$(this).fadeOut() $(this).fadeOut()
@GroupMembers = GroupMembers @GroupMembers = GroupMembers
......
...@@ -43,25 +43,31 @@ ...@@ -43,25 +43,31 @@
$(".selected_issue").bind "change", Issues.checkChanged $(".selected_issue").bind "change", Issues.checkChanged
# Make sure we trigger ajax request only after user stop typing
initSearch: -> initSearch: ->
form = $("#issue_search_form") @timer = null
last_terms = ""
$("#issue_search").keyup -> $("#issue_search").keyup ->
terms = $(this).val() clearTimeout(@timer);
unless terms is last_terms @timer = setTimeout(Issues.filterResults, 500)
last_terms = terms
if terms.length >= 2 or terms.length is 0 filterResults: =>
$.ajax form = $("#issue_search_form")
type: "GET" search = $("#issue_search").val()
url: location.href $('.issues-holder').css("opacity", '0.5')
data: "issue_search=" + terms issues_url = form.attr('action') + '? '+ form.serialize()
complete: ->
$(".loading").hide() $.ajax
success: (data) -> type: "GET"
$('.issues-holder').html(data.html) url: form.attr('action')
Issues.reload() data: form.serialize()
dataType: "json" complete: ->
$('.issues-holder').css("opacity", '1.0')
success: (data) ->
$('.issues-holder').html(data.html)
# Change url so if user reload a page - search results are saved
History.replaceState {page: issues_url}, document.title, issues_url
Issues.reload()
dataType: "json"
checkChanged: -> checkChanged: ->
checked_issues = $(".selected_issue:checked") checked_issues = $(".selected_issue:checked")
......
...@@ -27,7 +27,7 @@ $(document).ready -> ...@@ -27,7 +27,7 @@ $(document).ready ->
dropzone = $(".div-dropzone").dropzone( dropzone = $(".div-dropzone").dropzone(
url: project_image_path_upload url: project_image_path_upload
dictDefaultMessage: "" dictDefaultMessage: ""
clickable: true clickable: false
paramName: "markdown_img" paramName: "markdown_img"
maxFilesize: 10 maxFilesize: 10
uploadMultiple: false uploadMultiple: false
......
class Network class @Network
constructor: (opts) -> constructor: (opts) ->
$("#filter_ref").click -> $("#filter_ref").click ->
$(this).closest('form').submit() $(this).closest('form').submit()
branch_graph = new BranchGraph($(".network-graph"), opts) @branch_graph = new BranchGraph($(".network-graph"), opts)
vph = $(window).height() - 250 vph = $(window).height() - 250
$('.network-graph').css 'height': (vph + 'px') $('.network-graph').css 'height': (vph + 'px')
@Network = Network
...@@ -16,13 +16,19 @@ class Notes ...@@ -16,13 +16,19 @@ class Notes
$(document).on "ajax:success", ".js-main-target-form", @addNote $(document).on "ajax:success", ".js-main-target-form", @addNote
$(document).on "ajax:success", ".js-discussion-note-form", @addDiscussionNote $(document).on "ajax:success", ".js-discussion-note-form", @addDiscussionNote
# change note in UI after update # change note in UI after update
$(document).on "ajax:success", "form.edit_note", @updateNote $(document).on "ajax:success", "form.edit_note", @updateNote
# Edit note link # Edit note link
$(document).on "click", ".js-note-edit", @showEditForm $(document).on "click", ".js-note-edit", @showEditForm
$(document).on "click", ".note-edit-cancel", @cancelEdit $(document).on "click", ".note-edit-cancel", @cancelEdit
# Reopen and close actions for Issue/MR combined with note form submit
$(document).on "click", ".js-note-target-reopen", @targetReopen
$(document).on "click", ".js-note-target-close", @targetClose
$(document).on "click", ".js-comment-button", @updateCloseButton
$(document).on "keyup", ".js-note-text", @updateTargetButtons
# remove a note (in general) # remove a note (in general)
$(document).on "click", ".js-note-delete", @removeNote $(document).on "click", ".js-note-delete", @removeNote
...@@ -78,7 +84,9 @@ class Notes ...@@ -78,7 +84,9 @@ class Notes
$(document).off "click", ".js-add-diff-note-button" $(document).off "click", ".js-add-diff-note-button"
$(document).off "visibilitychange" $(document).off "visibilitychange"
$(document).off "keypress", @notes_forms $(document).off "keypress", @notes_forms
$(document).off "keyup", ".js-note-text"
$(document).off "click", ".js-note-target-reopen"
$(document).off "click", ".js-note-target-close"
initRefresh: -> initRefresh: ->
clearInterval(Notes.interval) clearInterval(Notes.interval)
...@@ -406,30 +414,6 @@ class Notes ...@@ -406,30 +414,6 @@ class Notes
form.find(".js-note-text").focus() form.find(".js-note-text").focus()
form.addClass "js-discussion-note-form" form.addClass "js-discussion-note-form"
###
General note form setup.
deactivates the submit button when text is empty
hides the preview button when text is empty
setup GFM auto complete
show the form
###
setupNoteForm: (form) =>
disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button")
form.removeClass "js-new-note-form"
form.removeClass "js-new-note-form"
GitLab.GfmAutoComplete.setup()
# setup preview buttons
previewButton = form.find(".js-note-preview-button")
form.find(".js-note-text").on "input", ->
if $(this).val().trim() isnt ""
previewButton.removeClass("turn-off").addClass "turn-on"
else
previewButton.removeClass("turn-on").addClass "turn-off"
form.show()
### ###
Called when clicking on the "add a comment" button on the side of a diff line. Called when clicking on the "add a comment" button on the side of a diff line.
...@@ -502,4 +486,33 @@ class Notes ...@@ -502,4 +486,33 @@ class Notes
visibilityChange: => visibilityChange: =>
@refresh() @refresh()
targetReopen: (e) =>
@submitNoteForm($(e.target).parents('form'))
targetClose: (e) =>
@submitNoteForm($(e.target).parents('form'))
submitNoteForm: (form) =>
noteText = form.find(".js-note-text").val()
if noteText.trim().length > 0
form.submit()
updateCloseButton: (e) =>
textarea = $(e.target)
form = textarea.parents('form')
form.find('.js-note-target-close').text('Close')
updateTargetButtons: (e) =>
textarea = $(e.target)
form = textarea.parents('form')
if textarea.val().trim().length > 0
form.find('.js-note-target-reopen').text('Comment & reopen')
form.find('.js-note-target-close').text('Comment & close')
else
form.find('.js-note-target-reopen').text('Reopen')
form.find('.js-note-target-close').text('Close')
@Notes = Notes @Notes = Notes
...@@ -47,7 +47,7 @@ $ -> ...@@ -47,7 +47,7 @@ $ ->
$(@).parents('.no-ssh-key-message').hide() $(@).parents('.no-ssh-key-message').hide()
e.preventDefault() e.preventDefault()
$('.project-side .star').on 'ajax:success', (e, data, status, xhr) -> $('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) ->
$(@).toggleClass('on').find('.count').html(data.star_count) $(@).toggleClass('on').find('.count').html(data.star_count)
.on 'ajax:error', (e, xhr, status, error) -> .on 'ajax:error', (e, xhr, status, error) ->
new Flash('Star toggle failed. Try again later.', 'alert') new Flash('Star toggle failed. Try again later.', 'alert')
class Shortcuts class @Shortcuts
constructor: -> constructor: ->
@enabledHelp = []
Mousetrap.reset()
Mousetrap.bind('?', @selectiveHelp)
Mousetrap.bind('s', Shortcuts.focusSearch)
selectiveHelp: (e) =>
Shortcuts.showHelp(e, @enabledHelp)
@showHelp: (e, location) ->
if $('#modal-shortcuts').length > 0 if $('#modal-shortcuts').length > 0
$('#modal-shortcuts').modal('show') $('#modal-shortcuts').modal('show')
else else
$.ajax( $.ajax(
url: '/help/shortcuts', url: '/help/shortcuts',
dataType: "script" dataType: 'script',
success: (e) ->
if location and location.length > 0
for l in location
$(l).show()
else
$('.hidden-shortcut').show()
$('.js-more-help-button').remove()
) )
e.preventDefault()
@Shortcuts = Shortcuts @focusSearch: (e) ->
$('#search').focus()
e.preventDefault()
#= require shortcuts
class @ShortcutsDashboardNavigation extends Shortcuts
constructor: ->
super()
Mousetrap.bind('g a', -> ShortcutsDashboardNavigation.findAndollowLink('.shortcuts-activity'))
Mousetrap.bind('g p', -> ShortcutsDashboardNavigation.findAndollowLink('.shortcuts-projects'))
Mousetrap.bind('g i', -> ShortcutsDashboardNavigation.findAndollowLink('.shortcuts-issues'))
Mousetrap.bind('g m', -> ShortcutsDashboardNavigation.findAndollowLink('.shortcuts-merge_requests'))
@findAndollowLink: (selector) ->
link = $(selector).attr('href')
if link
window.location = link
#= require shortcuts_navigation
class @ShortcutsIssueable extends ShortcutsNavigation
constructor: (isMergeRequest) ->
super()
Mousetrap.bind('a', ->
$('.js-assignee').select2('open')
return false
)
Mousetrap.bind('m', ->
$('.js-milestone').select2('open')
return false
)
if isMergeRequest
@enabledHelp.push('.hidden-shortcut.merge_reuests')
else
@enabledHelp.push('.hidden-shortcut.issues')
#= require shortcuts
class @ShortcutsNavigation extends Shortcuts
constructor: ->
super()
Mousetrap.bind('g p', -> ShortcutsNavigation.findAndollowLink('.shortcuts-project'))
Mousetrap.bind('g f', -> ShortcutsNavigation.findAndollowLink('.shortcuts-tree'))
Mousetrap.bind('g c', -> ShortcutsNavigation.findAndollowLink('.shortcuts-commits'))
Mousetrap.bind('g n', -> ShortcutsNavigation.findAndollowLink('.shortcuts-network'))
Mousetrap.bind('g g', -> ShortcutsNavigation.findAndollowLink('.shortcuts-graphs'))
Mousetrap.bind('g i', -> ShortcutsNavigation.findAndollowLink('.shortcuts-issues'))
Mousetrap.bind('g m', -> ShortcutsNavigation.findAndollowLink('.shortcuts-merge_requests'))
Mousetrap.bind('g w', -> ShortcutsNavigation.findAndollowLink('.shortcuts-wiki'))
Mousetrap.bind('g s', -> ShortcutsNavigation.findAndollowLink('.shortcuts-snippets'))
@enabledHelp.push('.hidden-shortcut.project')
@findAndollowLink: (selector) ->
link = $(selector).attr('href')
if link
window.location = link
#= require shortcuts_navigation
class @ShortcutsNetwork extends ShortcutsNavigation
constructor: (@graph) ->
super()
Mousetrap.bind(['left', 'h'], @graph.scrollLeft)
Mousetrap.bind(['right', 'l'], @graph.scrollRight)
Mousetrap.bind(['up', 'k'], @graph.scrollUp)
Mousetrap.bind(['down', 'j'], @graph.scrollDown)
Mousetrap.bind(['shift+up', 'shift+k'], @graph.scrollTop)
Mousetrap.bind(['shift+down', 'shift+j'], @graph.scrollBottom)
@enabledHelp.push('.hidden-shortcut.network')
...@@ -24,22 +24,7 @@ class window.ContributorsStatGraph ...@@ -24,22 +24,7 @@ class window.ContributorsStatGraph
class: 'graph-author-commits-count' class: 'graph-author-commits-count'
}) })
commits.text(author.commits + " commits") commits.text(author.commits + " commits")
additions = $('<span/>', {
class: 'graph-additions'
})
additions.text(author.additions + " ++")
deletions = $('<span/>', {
class: 'graph-deletions'
})
deletions.text(author.deletions + " --")
$('<span/>').append(commits) $('<span/>').append(commits)
.append(" / ")
.append(additions)
.append(" / ")
.append(deletions)
create_author_header: (author) -> create_author_header: (author) ->
list_item = $('<li/>', { list_item = $('<li/>', {
......
...@@ -90,4 +90,4 @@ window.ContributorsStatGraphUtil = ...@@ -90,4 +90,4 @@ window.ContributorsStatGraphUtil =
true true
else else
false false
class @ZenMode
@fullscreen_prefix = 'fullscreen_'
constructor: ->
@active_zen_area = null
@active_checkbox = null
@scroll_position = 0
$(window).scroll =>
if not @active_checkbox
@scroll_position = window.pageYOffset
$('body').on 'change', '.zennable input[type=checkbox]', (e) =>
checkbox = e.currentTarget
if checkbox.checked
# Disable other keyboard shortcuts in ZEN mode
Mousetrap.pause()
@udpateActiveZenArea(checkbox)
else
@exitZenMode()
$(document).on 'keydown', (e) =>
if e.keyCode is $.ui.keyCode.ESCAPE
@exitZenMode()
e.preventDefault()
$(window).on 'hashchange', @updateZenModeFromLocationHash
udpateActiveZenArea: (checkbox) =>
@active_checkbox = $(checkbox)
@active_checkbox.prop('checked', true)
@active_zen_area = @active_checkbox.parent().find('textarea')
@active_zen_area.focus()
window.location.hash = ZenMode.fullscreen_prefix + @active_checkbox.prop('id')
exitZenMode: =>
if @active_zen_area isnt null
Mousetrap.unpause()
@active_checkbox.prop('checked', false)
@active_zen_area = null
@active_checkbox = null
window.location.hash = ''
window.scrollTo(window.pageXOffset, @scroll_position)
checkboxFromLocationHash: (e) ->
id = $.trim(window.location.hash.replace('#' + ZenMode.fullscreen_prefix, ''))
if id
return $('.zennable input[type=checkbox]#' + id)[0]
else
return null
updateZenModeFromLocationHash: (e) =>
checkbox = @checkboxFromLocationHash()
if checkbox
@udpateActiveZenArea(checkbox)
else
@exitZenMode()
...@@ -128,7 +128,7 @@ p.time { ...@@ -128,7 +128,7 @@ p.time {
} }
.highlight_word { .highlight_word {
border-bottom: 2px solid #F90; background: #fafe3d;
} }
.thin_area{ .thin_area{
......
...@@ -60,7 +60,6 @@ ...@@ -60,7 +60,6 @@
.highlight { .highlight {
margin-bottom: 9px; margin-bottom: 9px;
@include border-radius(4px);
> pre { > pre {
margin: 0; margin: 0;
......
.flash-container { .flash-container {
display: none;
cursor: pointer; cursor: pointer;
margin: 0; margin: 0;
text-align: center;
color: #fff;
font-size: 14px; font-size: 14px;
position: fixed;
bottom: 0;
width: 100%; width: 100%;
opacity: 0.8;
z-index: 100; z-index: 100;
.flash-notice { .flash-notice {
background: #49C; @extend .alert;
padding: 10px; @extend .alert-info;
text-shadow: 0 1px 1px #178;
} }
.flash-alert { .flash-alert {
background: #C67; @extend .alert;
text-shadow: 0 1px 1px #945; @extend .alert-danger;
padding: 10px;
} }
} }
textarea {
resize: vertical;
}
input[type='search'].search-text-input { input[type='search'].search-text-input {
background-image: image-url("icon-search.png"); background-image: image-url("icon-search.png");
background-repeat: no-repeat; background-repeat: no-repeat;
...@@ -83,3 +87,140 @@ label { ...@@ -83,3 +87,140 @@ label {
.form-control { .form-control {
@include box-shadow(none); @include box-shadow(none);
} }
.issuable-description {
margin-top: 35px;
}
.zennable {
position: relative;
input {
display: none;
}
.collapse {
display: none;
opacity: 0.5;
&:before {
content: '\f066';
font-family: FontAwesome;
color: #000;
font-size: 28px;
position: relative;
padding: 30px 40px 0 0;
}
&:hover {
opacity: 0.8;
}
}
.expand {
opacity: 0.5;
&:before {
content: '\f065';
font-family: FontAwesome;
color: #000;
font-size: 14px;
line-height: 14px;
padding-right: 20px;
position: relative;
vertical-align: middle;
}
&:hover {
opacity: 0.8;
}
}
input:checked ~ .zen-backdrop .expand {
display: none;
}
input:checked ~ .zen-backdrop .collapse {
display: block;
position: absolute;
top: 0;
}
label {
position: absolute;
top: -26px;
right: 0;
font-variant: small-caps;
text-transform: uppercase;
font-size: 10px;
padding: 4px;
font-weight: 500;
letter-spacing: 1px;
&:before {
display: inline-block;
width: 10px;
height: 14px;
}
}
input:checked ~ .zen-backdrop {
background-color: white;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1031;
textarea {
border: none;
box-shadow: none;
border-radius: 0;
color: #000;
font-size: 20px;
line-height: 26px;
padding: 30px;
display: block;
outline: none;
resize: none;
height: 100vh;
max-width: 900px;
margin: 0 auto;
}
}
.zen-backdrop textarea::-webkit-input-placeholder {
color: white;
}
.zen-backdrop textarea:-moz-placeholder {
color: white;
}
.zen-backdrop textarea::-moz-placeholder {
color: white;
}
.zen-backdrop textarea:-ms-input-placeholder {
color: white;
}
input:checked ~ .zen-backdrop textarea::-webkit-input-placeholder {
color: #999;
}
input:checked ~ .zen-backdrop textarea:-moz-placeholder {
color: #999;
opacity: 1;
}
input:checked ~ .zen-backdrop textarea::-moz-placeholder {
color: #999;
opacity: 1;
}
input:checked ~ .zen-backdrop textarea:-ms-input-placeholder {
color: #999;
}
}
/**
* Styles that apply to both issues and merge requests.
*/
.issue-form, .merge-request-form {
.description {
height: 20em;
}
}
...@@ -8,43 +8,51 @@ ...@@ -8,43 +8,51 @@
*/ */
.issue-box { .issue-box {
color: #666; color: #555;
margin:20px 0; margin:20px 0;
background: #FFF; background: #f9f9f9;
border: 1px solid #EEE; border-top-left-radius: 5px;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05)); @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
&.issue-box-closed { &.issue-box-closed {
border-color: $border_danger;
.state { .state {
background-color: #F3CECE;
border-color: $border_danger;
}
.state-label {
background-color: $bg_danger; background-color: $bg_danger;
color: #FFF; color: #FFF;
border-color: $border_danger;
} }
} }
&.issue-box-merged { &.issue-box-merged {
border-color: $border_primary;
.state { .state {
background-color: #B7CEE7;
border-color: $border_primary;
}
.state-label {
background-color: $bg_primary; background-color: $bg_primary;
color: #FFF; color: #FFF;
border-color: $border_primary;
} }
} }
&.issue-box-open { &.issue-box-open {
border-color: $border_success;
.state { .state {
border-color: $border_success; background-color: #D6F1D7;
border-color: $bg_success;
}
.state-label {
background-color: $bg_success; background-color: $bg_success;
color: #FFF; color: #FFF;
} }
} }
&.issue-box-expired { &.issue-box-expired {
border-color: #cea61b;
.state { .state {
background-color: #EEE9B3;
border-color: #faebcc; border-color: #faebcc;
}
.state-label {
background: #cea61b; background: #cea61b;
color: #FFF; color: #FFF;
} }
...@@ -55,8 +63,7 @@ ...@@ -55,8 +63,7 @@
} }
.state { .state {
border-bottom: 1px solid #DDD; background-color: #f9f9f9;
padding: 10px 15px;
} }
.title { .title {
...@@ -88,6 +95,10 @@ ...@@ -88,6 +95,10 @@
.description { .description {
padding: 0 15px 10px 15px; padding: 0 15px 10px 15px;
code {
white-space: pre-wrap;
}
} }
.title, .context, .description { .title, .context, .description {
...@@ -100,12 +111,14 @@ ...@@ -100,12 +111,14 @@
font-size: 14px; font-size: 14px;
float: left; float: left;
font-weight: bold; font-weight: bold;
padding: 10px 15px;
border-top-left-radius: 5px;
} }
.creator { .creator {
float: right; float: right;
padding: 10px 15px;
a { a {
color: #FFF;
text-decoration: underline; text-decoration: underline;
} }
} }
......
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
&:hover { &:hover {
background: $hover; background: $hover;
border-bottom: 1px solid #ADF; border-bottom: 1px solid darken($hover, 10%);
} }
&:last-child { &:last-child {
......
.timeline {
list-style: none;
padding: 20px 0 20px;
position: relative;
&:before {
top: 0;
bottom: 0;
position: absolute;
content: " ";
width: 3px;
background-color: #eeeeee;
margin-left: 29px;
}
.timeline-entry {
position: relative;
margin-top: 5px;
margin-left: 30px;
margin-bottom: 10px;
clear: both;
&:target {
.timeline-entry-inner .timeline-content {
-webkit-animation:target-note 2s linear;
background: $hover;
}
}
.timeline-entry-inner {
position: relative;
margin-left: -20px;
&:before, &:after {
content: " ";
display: table;
}
.timeline-icon {
margin-top: 2px;
background: #fff;
color: #737881;
float: left;
@include border-radius(40px);
@include box-shadow(0 0 0 3px #EEE);
overflow: hidden;
.avatar {
margin: 0;
padding: 0;
}
}
.timeline-content {
position: relative;
background: #f5f5f6;
padding: 10px 15px;
margin-left: 60px;
&:after {
content: '';
display: block;
position: absolute;
width: 0;
height: 0;
border-style: solid;
border-width: 9px 9px 9px 0;
border-color: transparent #f5f5f6 transparent transparent;
left: 0;
top: 10px;
margin-left: -9px;
}
}
}
}
}
...@@ -40,7 +40,7 @@ a { ...@@ -40,7 +40,7 @@ a {
outline: none; outline: none;
color: $link_color; color: $link_color;
&:hover { &:hover {
text-decoration: none; text-decoration: underline;
color: $link_hover_color; color: $link_hover_color;
} }
...@@ -89,6 +89,8 @@ a:focus { ...@@ -89,6 +89,8 @@ a:focus {
.wiki { .wiki {
@include md-typography; @include md-typography;
word-wrap: break-word;
/* Link to current header. */ /* Link to current header. */
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
position: relative; position: relative;
......
.white { .white {
background-color: #fff;
.line.hll { .line.hll {
background: #FFA; background: #FFA;
} }
.highlight{
border-left: 1px solid #eee;
}
pre { pre {
background-color: #fff; background-color: #fff;
color: #333; color: #333;
...@@ -179,8 +173,16 @@ ...@@ -179,8 +173,16 @@
@include box-shadow(0 5px 15px #000); @include box-shadow(0 5px 15px #000);
} }
.wiki, .note-body { .file-content {
.highlight { &.code .white {
border: 1px solid #DDD; .highlight {
border-left: 1px solid #eee;
}
}
&.wiki .white {
.highlight, pre, .hljs {
background: #F9F9F9;
}
} }
} }
...@@ -16,3 +16,4 @@ body { ...@@ -16,3 +16,4 @@ body {
.container .content { .container .content {
margin: 0 0; margin: 0 0;
} }
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
* General Colors * General Colors
*/ */
$style_color: #474D57; $style_color: #474D57;
$hover: #D9EDF7; $hover: #FFECDB;
/* /*
* Link colors * Link colors
*/ */
$link_color: #446e9b; $link_color: #446e9b;
$link_hover_color: #2FA0BB; $link_hover_color: darken($link-color, 10%);
$btn-border: 1px solid #ccc; $btn-border: 1px solid #ccc;
......
...@@ -244,7 +244,6 @@ li.commit { ...@@ -244,7 +244,6 @@ li.commit {
font-family: inherit; font-family: inherit;
padding-left: $left; padding-left: $left;
position: relative; position: relative;
resize: vertical;
z-index: 2; z-index: 2;
} }
} }
...@@ -60,12 +60,13 @@ ...@@ -60,12 +60,13 @@
} }
.project-row, .group-row { .project-row, .group-row {
padding: 8px 15px !important; padding: 0 !important;
font-size: 14px; font-size: 14px;
line-height: 24px; line-height: 24px;
a { a {
display: block; display: block;
padding: 8px 15px;
} }
.project-name, .group-name { .project-name, .group-name {
...@@ -99,14 +100,9 @@ ...@@ -99,14 +100,9 @@
margin-right: 15px; margin-right: 15px;
font-size: 20px; font-size: 20px;
margin-bottom: 15px; margin-bottom: 15px;
border: 1px solid #EEE;
padding: 8px 12px;
border-radius: 50px;
background: #f5f5f5;
text-align: center;
i { i {
color: #BBB; color: #888;
} }
} }
......
...@@ -125,8 +125,6 @@ ...@@ -125,8 +125,6 @@
} }
.line_content { .line_content {
display: block; display: block;
white-space: pre;
height: 18px;
margin: 0px; margin: 0px;
padding: 0px 0.5em; padding: 0px 0.5em;
border: none; border: none;
...@@ -341,3 +339,12 @@ ...@@ -341,3 +339,12 @@
margin: 0; margin: 0;
border: none; border: none;
} }
.diff-file .line_content {
white-space: pre;
}
.diff-wrap-lines .line_content {
white-space: pre-wrap;
}
...@@ -17,3 +17,56 @@ ...@@ -17,3 +17,56 @@
} }
} }
} }
.shortcut-mappings {
font-size: 12px;
color: #555;
tbody:first-child tr:first-child {
padding-top: 0
}
th {
padding-top: 15px;
font-size: 14px;
line-height: 1.5;
color: #333;
text-align: left
}
td {
padding-top: 3px;
padding-bottom: 3px;
vertical-align: top;
line-height: 20px
}
.shortcut {
padding-right: 10px;
color: #999;
text-align: right;
white-space: nowrap
}
.key {
@extend .label;
@extend .label-inverse;
font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace;
padding: 3px 5px;
}
}
.modal-body {
position: relative;
overflow-y: auto;
padding: 15px;
}
body.modal-open {
overflow: hidden;
}
.modal .modal-dialog {
width: 860px;
}
...@@ -35,39 +35,31 @@ ...@@ -35,39 +35,31 @@
width: 1%; width: 1%;
&.active { &.active {
a { a {
color: #333; color: $link_color;
font-weight: bold; font-weight: bold;
&:after { &:after {
content: ''; content: '';
display: block; display: block;
position: relative; position: relative;
bottom: 8px; bottom: -1px;
left: 50%; border-color: $link_color;
width: 0;
height: 0;
border-color: transparent transparent #333 transparent;
border-style: solid; border-style: solid;
border-width: 6px; border-width: 2px;
margin-left: -6px;
} }
} }
} }
&:hover { &:hover {
a { a {
color: $link_color; color: $link_hover_color;
&:after { &:after {
content: ''; content: '';
display: block; display: block;
position: relative; position: relative;
bottom: 8px; bottom: -1px;
left: 50%; border-color: $link_hover_color;
width: 0;
height: 0;
border-color: transparent transparent $link_color transparent;
border-style: solid; border-style: solid;
border-width: 6px; border-width: 2px;
margin-left: -6px;
} }
} }
} }
...@@ -90,7 +82,6 @@ ...@@ -90,7 +82,6 @@
line-height: 34px; line-height: 34px;
color: #777; color: #777;
text-shadow: 0 1px 1px white; text-shadow: 0 1px 1px white;
padding: 0 10px;
text-decoration: none; text-decoration: none;
padding-top: 2px; padding-top: 2px;
} }
......
...@@ -17,9 +17,12 @@ ul.notes { ...@@ -17,9 +17,12 @@ ul.notes {
.discussion-header, .discussion-header,
.note-header { .note-header {
@extend .cgray; @extend .cgray;
padding-top: 5px;
padding-bottom: 15px; padding-bottom: 15px;
a:hover {
text-decoration: none;
}
.avatar { .avatar {
float: left; float: left;
margin-right: 10px; margin-right: 10px;
...@@ -43,34 +46,19 @@ ul.notes { ...@@ -43,34 +46,19 @@ ul.notes {
} }
.discussion { .discussion {
padding: 10px 0;
overflow: hidden; overflow: hidden;
display: block; display: block;
position:relative; position:relative;
border-bottom: 1px solid #EEE;
.discussion-body {
margin-left: 50px;
}
} }
.note { .note {
padding: 8px 0;
overflow: hidden;
display: block; display: block;
position:relative; position:relative;
border-bottom: 1px solid #eee;
p { color: $style_color; }
.avatar {
margin-top: 3px;
}
.attachment { .attachment {
font-size: 14px; font-size: 14px;
} }
.note-body { .note-body {
@include md-typography; @include md-typography;
margin-left: 43px;
} }
.note-header { .note-header {
padding-bottom: 3px; padding-bottom: 3px;
...@@ -80,11 +68,6 @@ ul.notes { ...@@ -80,11 +68,6 @@ ul.notes {
border-bottom: none; border-bottom: none;
} }
} }
.note:target {
-webkit-animation:target-note 2s linear;
background: #fffff0;
}
} }
.diff-file .notes_holder { .diff-file .notes_holder {
...@@ -99,7 +82,7 @@ ul.notes { ...@@ -99,7 +82,7 @@ ul.notes {
&.notes_line { &.notes_line {
text-align: center; text-align: center;
padding: 10px 0; padding: 10px 0;
background: #eee; background: #FFF;
} }
&.notes_line2 { &.notes_line2 {
text-align: center; text-align: center;
...@@ -111,6 +94,9 @@ ul.notes { ...@@ -111,6 +94,9 @@ ul.notes {
border-width: 1px 0; border-width: 1px 0;
padding-top: 0; padding-top: 0;
vertical-align: top; vertical-align: top;
&.parallel{
border-width: 1px;
}
} }
} }
} }
...@@ -161,8 +147,14 @@ ul.notes { ...@@ -161,8 +147,14 @@ ul.notes {
*/ */
.diff-file tr.line_holder { .diff-file tr.line_holder {
@mixin show-add-diff-note {
filter: alpha(opacity=100);
opacity: 1.0;
}
.add-diff-note { .add-diff-note {
background: image-url("diff_note_add.png") no-repeat left 0; background: image-url("diff_note_add.png") no-repeat left 0;
border: none;
height: 22px; height: 22px;
margin-left: -65px; margin-left: -65px;
position: absolute; position: absolute;
...@@ -174,8 +166,7 @@ ul.notes { ...@@ -174,8 +166,7 @@ ul.notes {
filter: alpha(opacity=0); filter: alpha(opacity=0);
&:hover { &:hover {
opacity: 1.0; @include show-add-diff-note;
filter: alpha(opacity=100);
} }
} }
...@@ -184,8 +175,7 @@ ul.notes { ...@@ -184,8 +175,7 @@ ul.notes {
background: $hover !important; background: $hover !important;
.add-diff-note { .add-diff-note {
opacity: 1.0; @include show-add-diff-note;
filter: alpha(opacity=100);
} }
} }
} }
...@@ -362,3 +352,7 @@ ul.notes { ...@@ -362,3 +352,7 @@ ul.notes {
border-top: 1px solid #DDD; border-top: 1px solid #DDD;
} }
} }
.discussion-notes-count {
font-size: 16px;
}
...@@ -15,62 +15,68 @@ ...@@ -15,62 +15,68 @@
} }
.project-home-panel { .project-home-panel {
border-bottom: 1px solid #DDD; margin-bottom: 15px;
padding-bottom: 15px;
margin-bottom: 30px;
&.empty-project { &.empty-project {
border-bottom: 0px; border-bottom: 0px;
padding-bottom: 15px; padding-bottom: 15px;
margin-bottom: 0px; margin-bottom: 0px;
} }
.project-home-title {
font-size: 18px;
color: #444;
margin: 0;
line-height: 32px;
}
.project-home-dropdown { .project-home-dropdown {
margin-left: 10px; margin-left: 10px;
float: right; float: right;
} }
.project-home-extra {
margin-top: 15px; .project-home-row {
@extend .clearfix;
margin-bottom: 15px;
.project-home-desc { .project-home-desc {
float: left; float: left;
color: #777; color: #666;
margin-bottom: 10px; font-size: 16px;
} }
.project-home-links { .star-fork-buttons {
float: right; float: right;
a { min-width: 200px;
margin-left: 10px; font-size: 14px;
font-weight: 500; font-weight: bold;
.star-buttons, .fork-buttons {
float: right;
margin-left: 20px;
a:hover {
text-decoration: none;
}
.count {
margin-left: 5px;
}
} }
} }
} }
.visibility-level-label { .visibility-level-label {
font-size: 17px; color: #555;
background: #f1f1f1; font-weight: bold;
border-radius: 4px;
color: #444;
position: absolute;
margin-left: -55px;
text-shadow: 0 1px 1px #FFF;
width: 40px;
text-align: center;
padding: 6px;
i { i {
color: inherit; color: inherit;
} }
} }
} }
.project-home-links {
padding: 10px 0px;
float: right;
a {
margin-left: 10px;
font-weight: 500;
}
}
.git-clone-holder { .git-clone-holder {
.project-home-dropdown + & { .project-home-dropdown + & {
margin-right: 45px; margin-right: 45px;
...@@ -159,6 +165,7 @@ ul.nav.nav-projects-tabs { ...@@ -159,6 +165,7 @@ ul.nav.nav-projects-tabs {
li { li {
.project-info { .project-info {
margin-bottom: 10px; margin-bottom: 10px;
overflow: hidden;
} }
.access-icon { .access-icon {
...@@ -195,8 +202,8 @@ ul.nav.nav-projects-tabs { ...@@ -195,8 +202,8 @@ ul.nav.nav-projects-tabs {
white-space: normal; white-space: normal;
text-align: left; text-align: left;
padding: 10px 15px; padding: 10px 15px;
background-color: #F1f1f1; background-color: #F9F9F9;
border-color: #EEE; border-color: #DDD;
&:hover { &:hover {
background-color: #eee; background-color: #eee;
......
.search-results {
.search-result-row {
border-bottom: 1px solid #EEE;
padding-bottom: 10px;
margin-bottom: 10px;
}
}
...@@ -8,7 +8,7 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -8,7 +8,7 @@ class Admin::GroupsController < Admin::ApplicationController
end end
def show def show
@members = @group.members.order("group_access DESC").page(params[:members_page]).per(30) @members = @group.members.order("access_level DESC").page(params[:members_page]).per(30)
@projects = @group.projects.page(params[:projects_page]).per(30) @projects = @group.projects.page(params[:projects_page]).per(30)
end end
...@@ -40,7 +40,7 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -40,7 +40,7 @@ class Admin::GroupsController < Admin::ApplicationController
end end
def project_teams_update def project_teams_update
@group.add_users(params[:user_ids].split(','), params[:group_access]) @group.add_users(params[:user_ids].split(','), params[:access_level])
redirect_to [:admin, @group], notice: 'Users were successfully added.' redirect_to [:admin, @group], notice: 'Users were successfully added.'
end end
......
...@@ -16,10 +16,10 @@ class Admin::ProjectsController < Admin::ApplicationController ...@@ -16,10 +16,10 @@ class Admin::ProjectsController < Admin::ApplicationController
def show def show
if @group if @group
@group_members = @group.members.order("group_access DESC").page(params[:group_members_page]).per(30) @group_members = @group.members.order("access_level DESC").page(params[:group_members_page]).per(30)
end end
@project_members = @project.users_projects.page(params[:project_members_page]).per(30) @project_members = @project.project_members.page(params[:project_members_page]).per(30)
end end
def transfer def transfer
......
class UsersGroupsController < ApplicationController class Groups::GroupMembersController < ApplicationController
before_filter :group before_filter :group
# Authorize # Authorize
...@@ -7,18 +7,18 @@ class UsersGroupsController < ApplicationController ...@@ -7,18 +7,18 @@ class UsersGroupsController < ApplicationController
layout 'group' layout 'group'
def create def create
@group.add_users(params[:user_ids].split(','), params[:group_access]) @group.add_users(params[:user_ids].split(','), params[:access_level])
redirect_to members_group_path(@group), notice: 'Users were successfully added.' redirect_to members_group_path(@group), notice: 'Users were successfully added.'
end end
def update def update
@member = @group.users_groups.find(params[:id]) @member = @group.group_members.find(params[:id])
@member.update_attributes(member_params) @member.update_attributes(member_params)
end end
def destroy def destroy
@users_group = @group.users_groups.find(params[:id]) @users_group = @group.group_members.find(params[:id])
if can?(current_user, :destroy, @users_group) # May fail if last owner. if can?(current_user, :destroy, @users_group) # May fail if last owner.
@users_group.destroy @users_group.destroy
respond_to do |format| respond_to do |format|
...@@ -43,6 +43,6 @@ class UsersGroupsController < ApplicationController ...@@ -43,6 +43,6 @@ class UsersGroupsController < ApplicationController
end end
def member_params def member_params
params.require(:users_group).permit(:group_access, :user_id) params.require(:group_member).permit(:access_level, :user_id)
end end
end end
...@@ -65,15 +65,15 @@ class GroupsController < ApplicationController ...@@ -65,15 +65,15 @@ class GroupsController < ApplicationController
def members def members
@project = group.projects.find(params[:project_id]) if params[:project_id] @project = group.projects.find(params[:project_id]) if params[:project_id]
@members = group.users_groups @members = group.group_members
if params[:search].present? if params[:search].present?
users = group.users.search(params[:search]).to_a users = group.users.search(params[:search]).to_a
@members = @members.where(user_id: users) @members = @members.where(user_id: users)
end end
@members = @members.order('group_access DESC').page(params[:page]).per(50) @members = @members.order('access_level DESC').page(params[:page]).per(50)
@users_group = UsersGroup.new @users_group = GroupMember.new
end end
def edit def edit
......
...@@ -2,11 +2,11 @@ class Profiles::GroupsController < ApplicationController ...@@ -2,11 +2,11 @@ class Profiles::GroupsController < ApplicationController
layout "profile" layout "profile"
def index def index
@user_groups = current_user.users_groups.page(params[:page]).per(20) @user_groups = current_user.group_members.page(params[:page]).per(20)
end end
def leave def leave
@users_group = group.users_groups.where(user_id: current_user.id).first @users_group = group.group_members.where(user_id: current_user.id).first
if can?(current_user, :destroy, @users_group) if can?(current_user, :destroy, @users_group)
@users_group.destroy @users_group.destroy
redirect_to(profile_groups_path, info: "You left #{group.name} group.") redirect_to(profile_groups_path, info: "You left #{group.name} group.")
......
...@@ -3,8 +3,8 @@ class Profiles::NotificationsController < ApplicationController ...@@ -3,8 +3,8 @@ class Profiles::NotificationsController < ApplicationController
def show def show
@notification = current_user.notification @notification = current_user.notification
@users_projects = current_user.users_projects @project_members = current_user.project_members
@users_groups = current_user.users_groups @group_members = current_user.group_members
end end
def update def update
...@@ -14,13 +14,13 @@ class Profiles::NotificationsController < ApplicationController ...@@ -14,13 +14,13 @@ class Profiles::NotificationsController < ApplicationController
current_user.notification_level = params[:notification_level] current_user.notification_level = params[:notification_level]
current_user.save current_user.save
elsif type == 'group' elsif type == 'group'
users_group = current_user.users_groups.find(params[:notification_id]) users_group = current_user.group_members.find(params[:notification_id])
users_group.notification_level = params[:notification_level] users_group.notification_level = params[:notification_level]
users_group.save users_group.save
else else
users_project = current_user.users_projects.find(params[:notification_id]) project_member = current_user.project_members.find(params[:notification_id])
users_project.notification_level = params[:notification_level] project_member.notification_level = params[:notification_level]
users_project.save project_member.save
end end
end end
end end
...@@ -17,13 +17,19 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -17,13 +17,19 @@ class Projects::BranchesController < Projects::ApplicationController
end end
def create def create
@branch = CreateBranchService.new.execute(project, params[:branch_name], params[:ref], current_user) result = CreateBranchService.new(project, current_user).
execute(params[:branch_name], params[:ref])
redirect_to project_tree_path(@project, @branch.name) if result[:status] == :success
@branch = result[:branch]
redirect_to project_tree_path(@project, @branch.name)
else
@error = result[:message]
render action: 'new'
end
end end
def destroy def destroy
DeleteBranchService.new.execute(project, params[:id], current_user) DeleteBranchService.new(project, current_user).execute(params[:id])
@branch_name = params[:id] @branch_name = params[:id]
respond_to do |format| respond_to do |format|
......
...@@ -19,13 +19,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -19,13 +19,7 @@ class Projects::CommitController < Projects::ApplicationController
[] []
end end
begin @diffs = @commit.diffs
@diffs = @commit.diffs
rescue Grit::Git::GitTimeout
@diffs = []
@diff_timeout = true
end
@note = project.build_commit_note(commit) @note = project.build_commit_note(commit)
@notes_count = project.notes.for_commit_id(commit.id).count @notes_count = project.notes.for_commit_id(commit.id).count
@notes = project.notes.for_commit_id(@commit.id).not_inline.fresh @notes = project.notes.for_commit_id(@commit.id).not_inline.fresh
......
...@@ -10,7 +10,8 @@ class Projects::EditTreeController < Projects::BaseTreeController ...@@ -10,7 +10,8 @@ class Projects::EditTreeController < Projects::BaseTreeController
end end
def update def update
result = Files::UpdateService.new(@project, current_user, params, @ref, @path).execute result = Files::UpdateService.
new(@project, current_user, params, @ref, @path).execute
if result[:status] == :success if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed" flash[:notice] = "Your changes have been successfully committed"
...@@ -31,7 +32,7 @@ class Projects::EditTreeController < Projects::BaseTreeController ...@@ -31,7 +32,7 @@ class Projects::EditTreeController < Projects::BaseTreeController
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3',
include_diff_info: true) include_diff_info: true)
@diff = Gitlab::DiffParser.new(diffy.diff.scan(/.*\n/)) @diff_lines = Gitlab::Diff::Parser.new.parse(diffy.diff.scan(/.*\n/))
render layout: false render layout: false
end end
......
...@@ -7,19 +7,34 @@ class Projects::GraphsController < Projects::ApplicationController ...@@ -7,19 +7,34 @@ class Projects::GraphsController < Projects::ApplicationController
def show def show
respond_to do |format| respond_to do |format|
format.html format.html
format.js do format.json do
fetch_graph fetch_graph
end end
end end
end end
def commits
@commits = @project.repository.commits(nil, nil, 2000, 0, true)
@commits_graph = Gitlab::Graphs::Commits.new(@commits)
@commits_per_week_days = @commits_graph.commits_per_week_days
@commits_per_time = @commits_graph.commits_per_time
@commits_per_month = @commits_graph.commits_per_month
end
private private
def fetch_graph def fetch_graph
@log = @project.repository.graph_log.to_json @commits = @project.repository.commits(nil, nil, 6000, 0, true)
@success = true
rescue => ex
@log = [] @log = []
@success = false
@commits.each do |commit|
@log << {
author_name: commit.author_name.force_encoding('UTF-8'),
author_email: commit.author_email.force_encoding('UTF-8'),
date: commit.committed_date.strftime("%Y-%m-%d")
}
end
render json: @log.to_json
end end
end end
...@@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController
terms = params['issue_search'] terms = params['issue_search']
@issues = issues_filtered @issues = issues_filtered
@issues = @issues.where("title LIKE ? OR description LIKE ?", "%#{terms}%", "%#{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)
assignee_id, milestone_id = params[:assignee_id], params[:milestone_id] assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
...@@ -28,7 +28,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -28,7 +28,7 @@ class Projects::IssuesController < Projects::ApplicationController
@milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero? @milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero?
sort_param = params[:sort] || 'newest' sort_param = params[:sort] || 'newest'
@sort = sort_param.humanize unless sort_param.empty? @sort = sort_param.humanize unless sort_param.empty?
@assignees = User.where(id: @project.issues.pluck(:assignee_id)) @assignees = User.where(id: @project.issues.pluck(:assignee_id)).active
respond_to do |format| respond_to do |format|
format.html format.html
......
...@@ -52,7 +52,7 @@ class Projects::LabelsController < Projects::ApplicationController ...@@ -52,7 +52,7 @@ class Projects::LabelsController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html { redirect_to project_labels_path(@project), notice: 'Label was removed' } format.html { redirect_to project_labels_path(@project), notice: 'Label was removed' }
format.js { render nothing: true } format.js
end end
end end
......
...@@ -13,7 +13,7 @@ class Projects::NewTreeController < Projects::BaseTreeController ...@@ -13,7 +13,7 @@ class Projects::NewTreeController < Projects::BaseTreeController
flash[:notice] = "Your changes have been successfully committed" flash[:notice] = "Your changes have been successfully committed"
redirect_to project_blob_path(@project, File.join(@ref, file_path)) redirect_to project_blob_path(@project, File.join(@ref, file_path))
else else
flash[:alert] = result[:error] flash[:alert] = result[:message]
render :show render :show
end end
end end
......
...@@ -30,8 +30,10 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -30,8 +30,10 @@ class Projects::NotesController < Projects::ApplicationController
end end
def update def update
note.update_attributes(note_params) if note.editable?
note.reset_events_cache note.update_attributes(note_params)
note.reset_events_cache
end
respond_to do |format| respond_to do |format|
format.json { render_note_json(note) } format.json { render_note_json(note) }
...@@ -40,8 +42,10 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -40,8 +42,10 @@ class Projects::NotesController < Projects::ApplicationController
end end
def destroy def destroy
note.destroy if note.editable?
note.reset_events_cache note.destroy
note.reset_events_cache
end
respond_to do |format| respond_to do |format|
format.js { render nothing: true } format.js { render nothing: true }
......
...@@ -29,12 +29,10 @@ class Projects::RawController < Projects::ApplicationController ...@@ -29,12 +29,10 @@ class Projects::RawController < Projects::ApplicationController
private private
def get_blob_type def get_blob_type
if @blob.mime_type =~ /html|javascript/ if @blob.text?
'text/plain; charset=utf-8' 'text/plain; charset=utf-8'
elsif @blob.name =~ /(?:msi|exe|rar|r0\d|7z|7zip|zip)$/
'application/octet-stream'
else else
@blob.mime_type 'application/octet-stream'
end end
end end
end end
......
...@@ -4,11 +4,6 @@ class Projects::RepositoriesController < Projects::ApplicationController ...@@ -4,11 +4,6 @@ class Projects::RepositoriesController < Projects::ApplicationController
before_filter :authorize_code_access! before_filter :authorize_code_access!
before_filter :require_non_empty_project before_filter :require_non_empty_project
def stats
@stats = Gitlab::Git::Stats.new(@repository.raw, @repository.root_ref)
@graph = @stats.graph
end
def archive def archive
unless can?(current_user, :download_code, @project) unless can?(current_user, :download_code, @project)
render_404 and return render_404 and return
......
...@@ -13,10 +13,15 @@ class Projects::TagsController < Projects::ApplicationController ...@@ -13,10 +13,15 @@ class Projects::TagsController < Projects::ApplicationController
end end
def create def create
@tag = CreateTagService.new.execute(@project, params[:tag_name], result = CreateTagService.new(@project, current_user).
params[:ref], current_user) execute(params[:tag_name], params[:ref], params[:message])
if result[:status] == :success
redirect_to project_tags_path(@project) @tag = result[:tag]
redirect_to project_tags_path(@project)
else
@error = result[:message]
render action: 'new'
end
end end
def destroy def destroy
...@@ -28,7 +33,7 @@ class Projects::TagsController < Projects::ApplicationController ...@@ -28,7 +33,7 @@ class Projects::TagsController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html { redirect_to project_tags_path } format.html { redirect_to project_tags_path }
format.js { render nothing: true } format.js
end end
end end
end end
...@@ -6,17 +6,17 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -6,17 +6,17 @@ class Projects::TeamMembersController < Projects::ApplicationController
def index def index
@group = @project.group @group = @project.group
@users_projects = @project.users_projects.order('project_access DESC') @project_members = @project.project_members.order('access_level DESC')
end end
def new def new
@user_project_relation = project.users_projects.new @user_project_relation = project.project_members.new
end end
def create def create
users = User.where(id: params[:user_ids].split(',')) users = User.where(id: params[:user_ids].split(','))
@project.team << [users, params[:project_access]] @project.team << [users, params[:access_level]]
if params[:redirect_to] if params[:redirect_to]
redirect_to params[:redirect_to] redirect_to params[:redirect_to]
...@@ -26,7 +26,7 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -26,7 +26,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end end
def update def update
@user_project_relation = project.users_projects.find_by(user_id: member) @user_project_relation = project.project_members.find_by(user_id: member)
@user_project_relation.update_attributes(member_params) @user_project_relation.update_attributes(member_params)
unless @user_project_relation.valid? unless @user_project_relation.valid?
...@@ -36,7 +36,7 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -36,7 +36,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end end
def destroy def destroy
@user_project_relation = project.users_projects.find_by(user_id: member) @user_project_relation = project.project_members.find_by(user_id: member)
@user_project_relation.destroy @user_project_relation.destroy
respond_to do |format| respond_to do |format|
...@@ -46,7 +46,7 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -46,7 +46,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end end
def leave def leave
project.users_projects.find_by(user_id: current_user).destroy project.project_members.find_by(user_id: current_user).destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to :back } format.html { redirect_to :back }
...@@ -69,6 +69,6 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -69,6 +69,6 @@ class Projects::TeamMembersController < Projects::ApplicationController
end end
def member_params def member_params
params.require(:team_member).permit(:user_id, :project_access) params.require(:project_member).permit(:user_id, :access_level)
end end
end end
...@@ -103,7 +103,15 @@ class ProjectsController < ApplicationController ...@@ -103,7 +103,15 @@ class ProjectsController < ApplicationController
::Projects::DestroyService.new(@project, current_user, {}).execute ::Projects::DestroyService.new(@project, current_user, {}).execute
respond_to do |format| respond_to do |format|
format.html { redirect_to root_path } format.html do
flash[:alert] = "Project deleted."
if request.referer.include?("/admin")
redirect_to admin_projects_path
else
redirect_to projects_dashboard_path
end
end
end end
end end
......
...@@ -4,14 +4,33 @@ class SearchController < ApplicationController ...@@ -4,14 +4,33 @@ class SearchController < ApplicationController
def show def show
@project = Project.find_by(id: params[:project_id]) if params[:project_id].present? @project = Project.find_by(id: params[:project_id]) if params[:project_id].present?
@group = Group.find_by(id: params[:group_id]) if params[:group_id].present? @group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
@scope = params[:scope]
@show_snippets = params[:snippets].eql? 'true'
if @project @search_results = if @project
return access_denied! unless can?(current_user, :download_code, @project) return access_denied! unless can?(current_user, :download_code, @project)
@search_results = Search::ProjectService.new(@project, current_user, params).execute unless %w(blobs notes issues merge_requests wiki_blobs).
else include?(@scope)
@search_results = Search::GlobalService.new(current_user, params).execute @scope = 'blobs'
end end
Search::ProjectService.new(@project, current_user, params).execute
elsif @show_snippets
unless %w(snippet_blobs snippet_titles).include?(@scope)
@scope = 'snippet_blobs'
end
Search::SnippetService.new(current_user, params).execute
else
unless %w(projects issues merge_requests).include?(@scope)
@scope = 'projects'
end
Search::GlobalService.new(current_user, params).execute
end
@objects = @search_results.objects(@scope, params[:page])
end end
def autocomplete def autocomplete
......
# BaseFinder # IssuableFinder
# #
# Used to filter Issues and MergeRequests collections by set of params # Used to filter Issues and MergeRequests collections by set of params
# #
...@@ -16,7 +16,9 @@ ...@@ -16,7 +16,9 @@
# label_name: string # label_name: string
# sort: string # sort: string
# #
class BaseFinder require_relative 'projects_finder'
class IssuableFinder
attr_accessor :current_user, :params attr_accessor :current_user, :params
def execute(current_user, params) def execute(current_user, params)
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
# label_name: string # label_name: string
# sort: string # sort: string
# #
class IssuesFinder < BaseFinder class IssuesFinder < IssuableFinder
def klass def klass
Issue Issue
end end
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
# label_name: string # label_name: string
# sort: string # sort: string
# #
class MergeRequestsFinder < BaseFinder class MergeRequestsFinder < IssuableFinder
def klass def klass
MergeRequest MergeRequest
end end
......
...@@ -19,10 +19,8 @@ class ProjectsFinder ...@@ -19,10 +19,8 @@ class ProjectsFinder
# Return ALL group projects # Return ALL group projects
group.projects group.projects
else else
projects_members = UsersProject.where( projects_members = ProjectMember.in_projects(group.projects).
project_id: group.projects, with_user(current_user)
user_id: current_user
)
if projects_members.any? if projects_members.any?
# User is a project member # User is a project member
...@@ -34,7 +32,7 @@ class ProjectsFinder ...@@ -34,7 +32,7 @@ class ProjectsFinder
# #
group.projects.where( group.projects.where(
"projects.id IN (?) OR projects.visibility_level IN (?)", "projects.id IN (?) OR projects.visibility_level IN (?)",
projects_members.pluck(:project_id), projects_members.pluck(:source_id),
Project.public_and_internal_levels Project.public_and_internal_levels
) )
else else
......
...@@ -178,6 +178,8 @@ module ApplicationHelper ...@@ -178,6 +178,8 @@ module ApplicationHelper
def search_placeholder def search_placeholder
if @project && @project.persisted? if @project && @project.persisted?
"Search in this project" "Search in this project"
elsif @snippet || @snippets || @show_snippets
'Search snippets'
elsif @group && @group.persisted? elsif @group && @group.persisted?
"Search in this group" "Search in this group"
else else
...@@ -185,13 +187,6 @@ module ApplicationHelper ...@@ -185,13 +187,6 @@ module ApplicationHelper
end end
end end
def first_line(str)
lines = str.split("\n")
line = lines.first
line += "..." if lines.size > 1
line
end
def broadcast_message def broadcast_message
BroadcastMessage.current BroadcastMessage.current
end end
......
...@@ -16,38 +16,6 @@ module CommitsHelper ...@@ -16,38 +16,6 @@ module CommitsHelper
commit_person_link(commit, options.merge(source: :committer)) commit_person_link(commit, options.merge(source: :committer))
end end
def each_diff_line(diff, index)
Gitlab::DiffParser.new(diff.diff.lines.to_a, diff.new_path)
.each do |full_line, type, line_code, line_new, line_old|
yield(full_line, type, line_code, line_new, line_old)
end
end
def each_diff_line_near(diff, index, expected_line_code)
max_number_of_lines = 16
prev_match_line = nil
prev_lines = []
each_diff_line(diff, index) do |full_line, type, line_code, line_new, line_old|
line = [full_line, type, line_code, line_new, line_old]
if line_code != expected_line_code
if type == "match"
prev_lines.clear
prev_match_line = line
else
prev_lines.push(line)
prev_lines.shift if prev_lines.length >= max_number_of_lines
end
else
yield(prev_match_line) if !prev_match_line.nil?
prev_lines.each { |ln| yield(ln) }
yield(line)
break
end
end
end
def image_diff_class(diff) def image_diff_class(diff)
if diff.deleted_file if diff.deleted_file
"deleted" "deleted"
...@@ -63,14 +31,6 @@ module CommitsHelper ...@@ -63,14 +31,6 @@ module CommitsHelper
escape_javascript(render "projects/commits/#{template}", commit: commit, project: project) unless commit.nil? escape_javascript(render "projects/commits/#{template}", commit: commit, project: project) unless commit.nil?
end end
def diff_line_content(line)
if line.blank?
" &nbsp;"
else
line
end
end
# Breadcrumb links for a Project and, if applicable, a tree path # Breadcrumb links for a Project and, if applicable, a tree path
def commits_breadcrumbs def commits_breadcrumbs
return unless @project && @ref return unless @project && @ref
...@@ -105,82 +65,6 @@ module CommitsHelper ...@@ -105,82 +65,6 @@ module CommitsHelper
branches.sort.map { |branch| link_to(branch, project_tree_path(project, branch)) }.join(", ").html_safe branches.sort.map { |branch| link_to(branch, project_tree_path(project, branch)) }.join(", ").html_safe
end end
def parallel_diff_lines(project, commit, diff, file)
old_file = project.repository.blob_at(commit.parent_id, diff.old_path) if commit.parent_id
deleted_lines = {}
added_lines = {}
each_diff_line(diff, 0) do |line, type, line_code, line_new, line_old|
if type == "old"
deleted_lines[line_old] = { line_code: line_code, type: type, line: line }
elsif type == "new"
added_lines[line_new] = { line_code: line_code, type: type, line: line }
end
end
max_length = old_file ? [old_file.loc, file.loc].max : file.loc
offset1 = 0
offset2 = 0
old_lines = []
new_lines = []
max_length.times do |line_index|
line_index1 = line_index - offset1
line_index2 = line_index - offset2
deleted_line = deleted_lines[line_index1 + 1]
added_line = added_lines[line_index2 + 1]
old_line = old_file.lines[line_index1] if old_file
new_line = file.lines[line_index2]
if deleted_line && added_line
elsif deleted_line
new_line = nil
offset2 += 1
elsif added_line
old_line = nil
offset1 += 1
end
old_lines[line_index] = DiffLine.new
new_lines[line_index] = DiffLine.new
# old
if line_index == 0 && diff.new_file
old_lines[line_index].type = :file_created
old_lines[line_index].content = 'File was created'
elsif deleted_line
old_lines[line_index].type = :deleted
old_lines[line_index].content = old_line
old_lines[line_index].num = line_index1 + 1
old_lines[line_index].code = deleted_line[:line_code]
elsif old_line
old_lines[line_index].type = :no_change
old_lines[line_index].content = old_line
old_lines[line_index].num = line_index1 + 1
else
old_lines[line_index].type = :added
end
# new
if line_index == 0 && diff.deleted_file
new_lines[line_index].type = :file_deleted
new_lines[line_index].content = "File was deleted"
elsif added_line
new_lines[line_index].type = :added
new_lines[line_index].num = line_index2 + 1
new_lines[line_index].content = new_line
new_lines[line_index].code = added_line[:line_code]
elsif new_line
new_lines[line_index].type = :no_change
new_lines[line_index].num = line_index2 + 1
new_lines[line_index].content = new_line
else
new_lines[line_index].type = :deleted
end
end
return old_lines, new_lines
end
def link_to_browse_code(project, commit) def link_to_browse_code(project, commit)
if current_controller?(:projects, :commits) if current_controller?(:projects, :commits)
if @repo.blob_at(commit.id, @path) if @repo.blob_at(commit.id, @path)
...@@ -229,14 +113,6 @@ module CommitsHelper ...@@ -229,14 +113,6 @@ module CommitsHelper
end end
end end
def diff_file_mode_changed?(diff)
diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
end
def unfold_bottom_class(bottom)
(bottom) ? 'js-unfold-bottom' : ''
end
def view_file_btn(commit_sha, diff, project) def view_file_btn(commit_sha, diff, project)
link_to project_blob_path(project, tree_join(commit_sha, diff.new_path)), link_to project_blob_path(project, tree_join(commit_sha, diff.new_path)),
class: 'btn btn-small view-file js-view-file' do class: 'btn btn-small view-file js-view-file' do
......
module DiffHelper module DiffHelper
def safe_diff_files(diffs) def allowed_diff_size
if diff_hard_limit_enabled? if diff_hard_limit_enabled?
diffs.first(Commit::DIFF_HARD_LIMIT_FILES) Commit::DIFF_HARD_LIMIT_FILES
else else
diffs.first(Commit::DIFF_SAFE_FILES) Commit::DIFF_SAFE_FILES
end
end
def safe_diff_files(diffs)
diffs.first(allowed_diff_size).map do |diff|
Gitlab::Diff::File.new(diff)
end end
end end
def show_diff_size_warninig?(diffs) def show_diff_size_warning?(diffs)
safe_diff_files(diffs).size < diffs.size diffs.size > allowed_diff_size
end end
def diff_hard_limit_enabled? def diff_hard_limit_enabled?
...@@ -19,4 +25,96 @@ module DiffHelper ...@@ -19,4 +25,96 @@ module DiffHelper
false false
end end
end end
def generate_line_code(file_path, line)
Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
end
def parallel_diff(diff_file, index)
lines = []
skip_next = false
# Building array of lines
#
# [
# left_type, left_line_number, left_line_content, left_line_code,
# right_line_type, right_line_number, right_line_content, right_line_code
# ]
#
diff_file.diff_lines.each do |line|
full_line = line.text
type = line.type
line_code = generate_line_code(diff_file.file_path, line)
line_new = line.new_pos
line_old = line.old_pos
next_line = diff_file.next_line(line.index)
if next_line
next_line_code = generate_line_code(diff_file.file_path, next_line)
next_type = next_line.type
next_line = next_line.text
end
if type == 'match' || type.nil?
# line in the right panel is the same as in the left one
line = [type, line_old, full_line, line_code, type, line_new, full_line, line_code]
lines.push(line)
elsif type == 'old'
if next_type == 'new'
# Left side has text removed, right side has text added
line = [type, line_old, full_line, line_code, next_type, line_new, next_line, next_line_code]
lines.push(line)
skip_next = true
elsif next_type == 'old' || next_type.nil?
# Left side has text removed, right side doesn't have any change
# No next line code, no new line number, no new line text
line = [type, line_old, full_line, line_code, next_type, nil, "&nbsp;", nil]
lines.push(line)
end
elsif type == 'new'
if skip_next
# Change has been already included in previous line so no need to do it again
skip_next = false
next
else
# Change is only on the right side, left side has no change
line = [nil, nil, "&nbsp;", line_code, type, line_new, full_line, line_code]
lines.push(line)
end
end
end
lines
end
def unfold_bottom_class(bottom)
(bottom) ? 'js-unfold-bottom' : ''
end
def diff_line_content(line)
if line.blank?
" &nbsp;"
else
line
end
end
def line_comments
@line_comments ||= @line_notes.group_by(&:line_code)
end
def organize_comments(type_left, type_right, line_code_left, line_code_right)
comments_left = comments_right = nil
unless type_left.nil? && type_right == 'new'
comments_left = line_comments[line_code_left]
end
unless type_left.nil? && type_right.nil?
comments_right = line_comments[line_code_right]
end
[comments_left, comments_right]
end
end end
...@@ -52,6 +52,8 @@ module EventsHelper ...@@ -52,6 +52,8 @@ module EventsHelper
"#{event.author_name} #{event.push_action_name} #{event.ref_type} #{event.ref_name} at #{event.project_name}" "#{event.author_name} #{event.push_action_name} #{event.ref_type} #{event.ref_name} at #{event.project_name}"
elsif event.membership_changed? elsif event.membership_changed?
"#{event.author_name} #{event.action_name} #{event.project_name}" "#{event.author_name} #{event.action_name} #{event.project_name}"
elsif event.note? && event.note_commit?
"#{event.author_name} commented on #{event.note_target_type} #{event.note_short_commit_id} at #{event.project_name}"
elsif event.note? elsif event.note?
"#{event.author_name} commented on #{event.note_target_type} ##{truncate event.note_target_iid} at #{event.project_name}" "#{event.author_name} commented on #{event.note_target_type} ##{truncate event.note_target_iid} at #{event.project_name}"
else else
...@@ -64,6 +66,8 @@ module EventsHelper ...@@ -64,6 +66,8 @@ module EventsHelper
project_issue_url(event.project, event.issue) project_issue_url(event.project, event.issue)
elsif event.merge_request? elsif event.merge_request?
project_merge_request_url(event.project, event.merge_request) project_merge_request_url(event.project, event.merge_request)
elsif event.note? && event.note_commit?
project_commit_url(event.project, event.note_target)
elsif event.note? elsif event.note?
if event.note_target if event.note_target
if event.note_commit? if event.note_commit?
...@@ -94,6 +98,8 @@ module EventsHelper ...@@ -94,6 +98,8 @@ module EventsHelper
render "events/event_push", event: event render "events/event_push", event: event
elsif event.merge_request? elsif event.merge_request?
render "events/event_merge_request", merge_request: event.merge_request render "events/event_merge_request", merge_request: event.merge_request
elsif event.push?
render "events/event_push", event: event
elsif event.note? elsif event.note?
render "events/event_note", note: event.note render "events/event_note", note: event.note
end end
...@@ -130,7 +136,7 @@ module EventsHelper ...@@ -130,7 +136,7 @@ module EventsHelper
end end
def event_note(text) def event_note(text)
text = first_line(text) text = first_line_in_markdown(text)
text = truncate(text, length: 150) text = truncate(text, length: 150)
sanitize(markdown(text), tags: %w(a img b pre p)) sanitize(markdown(text), tags: %w(a img b pre p))
end end
......
...@@ -51,6 +51,14 @@ module GitlabMarkdownHelper ...@@ -51,6 +51,14 @@ module GitlabMarkdownHelper
@markdown.render(text).html_safe @markdown.render(text).html_safe
end end
def first_line_in_markdown(text)
line = text.split("\n").detect do |i|
i.present? && markdown(i).present?
end
line += '...' unless line.nil?
line
end
def render_wiki_content(wiki_page) def render_wiki_content(wiki_page)
if wiki_page.format == :markdown if wiki_page.format == :markdown
markdown(wiki_page.content) markdown(wiki_page.content)
...@@ -65,7 +73,12 @@ module GitlabMarkdownHelper ...@@ -65,7 +73,12 @@ module GitlabMarkdownHelper
paths.uniq.each do |file_path| paths.uniq.each do |file_path|
# If project does not have repository # If project does not have repository
# its nothing to rebuild # its nothing to rebuild
if @repository.exists? && !@repository.empty? #
# TODO: pass project variable to markdown helper instead of using
# instance variable. Right now it generates invalid path for pages out
# of project scope. Example: search results where can be rendered markdown
# from different projects
if @repository && @repository.exists? && !@repository.empty?
new_path = rebuild_path(file_path) new_path = rebuild_path(file_path)
# Finds quoted path so we don't replace other mentions of the string # Finds quoted path so we don't replace other mentions of the string
# eg. "doc/api" will be replaced and "/home/doc/api/text" won't # eg. "doc/api" will be replaced and "/home/doc/api/text" won't
......
...@@ -52,8 +52,8 @@ module NotesHelper ...@@ -52,8 +52,8 @@ module NotesHelper
discussion_id: discussion_id discussion_id: discussion_id
} }
link_to "", "javascript:;", class: "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'
end end
def link_to_reply_diff(note) def link_to_reply_diff(note)
...@@ -67,11 +67,10 @@ module NotesHelper ...@@ -67,11 +67,10 @@ module NotesHelper
discussion_id: note.discussion_id discussion_id: note.discussion_id
} }
link_to "javascript:;", class: "btn reply-btn js-discussion-reply-button", button_tag class: 'btn reply-btn js-discussion-reply-button',
data: data, title: "Add a reply" do data: data, title: 'Add a reply' do
link_text = "" link_text = content_tag(:i, nil, class: 'icon-comment')
link_text < content_tag(:i, nil, class: 'icon-comment') link_text << ' Reply'
link_text << "Reply" end
end
end end
end end
...@@ -123,7 +123,7 @@ module ProjectsHelper ...@@ -123,7 +123,7 @@ module ProjectsHelper
end end
def link_to_toggle_star(title, starred, signed_in) def link_to_toggle_star(title, starred, signed_in)
cls = 'btn btn-block' cls = 'star-btn'
cls += ' disabled' unless signed_in cls += ' disabled' unless signed_in
toggle_html = content_tag('span', class: 'toggle') do toggle_html = content_tag('span', class: 'toggle') do
...@@ -151,11 +151,19 @@ module ProjectsHelper ...@@ -151,11 +151,19 @@ module ProjectsHelper
content_tag 'span', class: starred ? 'turn-on' : 'turn-off' do content_tag 'span', class: starred ? 'turn-on' : 'turn-off' do
link_to toggle_star_project_path(@project), link_opts do link_to toggle_star_project_path(@project), link_opts do
toggle_html + count_html toggle_html + ' ' + count_html
end end
end end
end end
def link_to_toggle_fork
out = content_tag(:i, '', class: 'icon-code-fork')
out << ' Fork'
out << content_tag(:span, class: 'count') do
@project.forks_count.to_s
end
end
private private
def get_project_nav_tabs(project, current_user) def get_project_nav_tabs(project, current_user)
...@@ -261,4 +269,10 @@ module ProjectsHelper ...@@ -261,4 +269,10 @@ module ProjectsHelper
project_blob_path(project, tree_join(project.default_branch, project.repository.contribution_guide.name)) project_blob_path(project, tree_join(project.default_branch, project.repository.contribution_guide.name))
end end
end end
def hidden_pass_url(original_url)
result = URI(original_url)
result.password = '*****' if result.password.present?
result
end
end end
...@@ -80,7 +80,8 @@ module SearchHelper ...@@ -80,7 +80,8 @@ module SearchHelper
# Autocomplete results for the current user's projects # Autocomplete results for the current user's projects
def projects_autocomplete(term, limit = 5) def projects_autocomplete(term, limit = 5)
ProjectsFinder.new.execute(current_user).search_by_title(term).non_archived.limit(limit).map do |p| ProjectsFinder.new.execute(current_user).search_by_title(term).
sorted_by_stars.non_archived.limit(limit).map do |p|
{ {
label: "project: #{search_result_sanitize(p.name_with_namespace)}", label: "project: #{search_result_sanitize(p.name_with_namespace)}",
url: project_path(p) url: project_path(p)
...@@ -91,4 +92,21 @@ module SearchHelper ...@@ -91,4 +92,21 @@ module SearchHelper
def search_result_sanitize(str) def search_result_sanitize(str)
Sanitize.clean(str) Sanitize.clean(str)
end end
def search_filter_path(options={})
exist_opts = {
search: params[:search],
project_id: params[:project_id],
group_id: params[:group_id],
scope: params[:scope]
}
options = exist_opts.merge(options)
search_path(options)
end
# Sanitize html generated after parsing markdown from issue description or comment
def search_md_sanitize(html)
sanitize(html, tags: %w(a p ol ul li pre code))
end
end end
module Emails module Emails
module Groups module Groups
def group_access_granted_email(user_group_id) def group_access_granted_email(user_group_id)
@membership = UsersGroup.find(user_group_id) @membership = GroupMember.find(user_group_id)
@group = @membership.group @group = @membership.group
@target_url = group_url(@group) @target_url = group_url(@group)
mail(to: @membership.user.email, mail(to: @membership.user.email,
......
module Emails module Emails
module Projects module Projects
def project_access_granted_email(user_project_id) def project_access_granted_email(user_project_id)
@users_project = UsersProject.find user_project_id @project_member = ProjectMember.find user_project_id
@project = @users_project.project @project = @project_member.project
@target_url = project_url(@project) @target_url = project_url(@project)
mail(to: @users_project.user.email, mail(to: @project_member.user.email,
subject: subject("Access to project was granted")) subject: subject("Access to project was granted"))
end end
......
...@@ -14,7 +14,7 @@ class Ability ...@@ -14,7 +14,7 @@ class Ability
when "MergeRequest" then merge_request_abilities(user, subject) when "MergeRequest" then merge_request_abilities(user, subject)
when "Group" then group_abilities(user, subject) when "Group" then group_abilities(user, subject)
when "Namespace" then namespace_abilities(user, subject) when "Namespace" then namespace_abilities(user, subject)
when "UsersGroup" then users_group_abilities(user, subject) when "GroupMember" then users_group_abilities(user, subject)
else [] else []
end.concat(global_abilities(user)) end.concat(global_abilities(user))
end end
......
...@@ -108,4 +108,8 @@ class Commit ...@@ -108,4 +108,8 @@ class Commit
super super
end end
def parents
@parents ||= Commit.decorate(super)
end
end end
...@@ -49,6 +49,10 @@ module Issuable ...@@ -49,6 +49,10 @@ module Issuable
where("LOWER(title) like :query", query: "%#{query.downcase}%") where("LOWER(title) like :query", query: "%#{query.downcase}%")
end end
def full_search(query)
where("LOWER(title) like :query OR LOWER(description) like :query", query: "%#{query.downcase}%")
end
def sort(method) def sort(method)
case method.to_s case method.to_s
when 'newest' then reorder("#{table_name}.created_at DESC") when 'newest' then reorder("#{table_name}.created_at DESC")
......
# == Notifiable concern # == Notifiable concern
# #
# Contains notification functionality shared between UsersProject and UsersGroup # Contains notification functionality
# #
module Notifiable module Notifiable
extend ActiveSupport::Concern extend ActiveSupport::Concern
......
...@@ -17,8 +17,8 @@ require 'carrierwave/orm/activerecord' ...@@ -17,8 +17,8 @@ require 'carrierwave/orm/activerecord'
require 'file_size_validator' require 'file_size_validator'
class Group < Namespace class Group < Namespace
has_many :users_groups, dependent: :destroy has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
has_many :users, through: :users_groups 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: 100.kilobytes.to_i }
...@@ -30,22 +30,22 @@ class Group < Namespace ...@@ -30,22 +30,22 @@ class Group < Namespace
end end
def owners def owners
@owners ||= users_groups.owners.map(&:user) @owners ||= group_members.owners.map(&:user)
end end
def add_users(user_ids, group_access) def add_users(user_ids, access_level)
user_ids.compact.each do |user_id| user_ids.compact.each do |user_id|
user = self.users_groups.find_or_initialize_by(user_id: user_id) user = self.group_members.find_or_initialize_by(user_id: user_id)
user.update_attributes(group_access: group_access) user.update_attributes(access_level: access_level)
end end
end end
def add_user(user, group_access) def add_user(user, access_level)
self.users_groups.create(user_id: user.id, group_access: group_access) self.group_members.create(user_id: user.id, access_level: access_level)
end end
def add_owner(user) def add_owner(user)
self.add_user(user, UsersGroup::OWNER) self.add_user(user, Gitlab::Access::OWNER)
end end
def has_owner?(user) def has_owner?(user)
...@@ -61,7 +61,7 @@ class Group < Namespace ...@@ -61,7 +61,7 @@ class Group < Namespace
end end
def members def members
users_groups group_members
end end
def avatar_type def avatar_type
......
...@@ -23,7 +23,7 @@ class WebHook < ActiveRecord::Base ...@@ -23,7 +23,7 @@ class WebHook < ActiveRecord::Base
default_value_for :merge_requests_events, false default_value_for :merge_requests_events, false
# HTTParty timeout # HTTParty timeout
default_timeout 10 default_timeout Gitlab.config.gitlab.webhook_timeout
validates :url, presence: true, validates :url, presence: true,
format: { with: URI::regexp(%w(http https)), message: "should be a valid url" } format: { with: URI::regexp(%w(http https)), message: "should be a valid url" }
......
...@@ -29,7 +29,9 @@ class Key < ActiveRecord::Base ...@@ -29,7 +29,9 @@ class Key < ActiveRecord::Base
after_create :add_to_shell after_create :add_to_shell
after_create :notify_user after_create :notify_user
after_create :post_create_hook
after_destroy :remove_from_shell after_destroy :remove_from_shell
after_destroy :post_destroy_hook
def strip_white_space def strip_white_space
self.key = key.strip unless key.blank? self.key = key.strip unless key.blank?
...@@ -56,6 +58,10 @@ class Key < ActiveRecord::Base ...@@ -56,6 +58,10 @@ class Key < ActiveRecord::Base
NotificationService.new.new_key(self) NotificationService.new.new_key(self)
end end
def post_create_hook
SystemHooksService.new.execute_hooks_for(self, :create)
end
def remove_from_shell def remove_from_shell
GitlabShellWorker.perform_async( GitlabShellWorker.perform_async(
:remove_key, :remove_key,
...@@ -64,6 +70,10 @@ class Key < ActiveRecord::Base ...@@ -64,6 +70,10 @@ class Key < ActiveRecord::Base
) )
end end
def post_destroy_hook
SystemHooksService.new.execute_hooks_for(self, :destroy)
end
private private
def generate_fingerpint def generate_fingerpint
......
class Member < ActiveRecord::Base
include Notifiable
include Gitlab::Access
belongs_to :user
belongs_to :source, polymorphic: true
validates :user, presence: true
validates :source, presence: true
validates :user_id, uniqueness: { scope: [:source_type, :source_id], message: "already exists in source" }
validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true
scope :guests, -> { where(access_level: GUEST) }
scope :reporters, -> { where(access_level: REPORTER) }
scope :developers, -> { where(access_level: DEVELOPER) }
scope :masters, -> { where(access_level: MASTER) }
scope :owners, -> { where(access_level: OWNER) }
delegate :name, :username, :email, to: :user, prefix: true
end
class GroupMember < Member
SOURCE_TYPE = 'Namespace'
belongs_to :group, class_name: 'Group', foreign_key: 'source_id'
# Make sure group member points only to group as it source
default_value_for :source_type, SOURCE_TYPE
default_value_for :notification_level, Notification::N_GLOBAL
validates_format_of :source_type, with: /\ANamespace\z/
default_scope { where(source_type: SOURCE_TYPE) }
scope :with_group, ->(group) { where(source_id: group.id) }
scope :with_user, ->(user) { where(user_id: user.id) }
after_create :notify_create
after_update :notify_update
def self.access_level_roles
Gitlab::Access.options_with_owner
end
def group
source
end
def access_field
access_level
end
def notify_create
notification_service.new_group_member(self)
end
def notify_update
if access_level_changed?
notification_service.update_group_member(self)
end
end
def notification_service
NotificationService.new
end
end
# == Schema Information class ProjectMember < Member
# SOURCE_TYPE = 'Project'
# Table name: users_projects
#
# id :integer not null, primary key
# user_id :integer not null
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# project_access :integer default(0), not null
# notification_level :integer default(3), not null
#
class UsersProject < ActiveRecord::Base
include Gitlab::ShellAdapter
include Notifiable
include Gitlab::Access
belongs_to :user
belongs_to :project
validates :user, presence: true include Gitlab::ShellAdapter
validates :user_id, uniqueness: { scope: [:project_id], message: "already exists in project" }
validates :project_access, inclusion: { in: Gitlab::Access.values }, presence: true
validates :project, presence: true
delegate :name, :username, :email, to: :user, prefix: true belongs_to :project, class_name: 'Project', foreign_key: 'source_id'
scope :guests, -> { where(project_access: GUEST) }
scope :reporters, -> { where(project_access: REPORTER) }
scope :developers, -> { where(project_access: DEVELOPER) }
scope :masters, -> { where(project_access: MASTER) }
scope :in_project, ->(project) { where(project_id: project.id) } # Make sure project member points only to project as it source
scope :in_projects, ->(projects) { where(project_id: projects.map { |p| p.id }) } default_value_for :source_type, SOURCE_TYPE
scope :with_user, ->(user) { where(user_id: user.id) } default_value_for :notification_level, Notification::N_GLOBAL
validates_format_of :source_type, with: /\AProject\z/
default_scope { where(source_type: SOURCE_TYPE) }
after_create :post_create_hook after_create :post_create_hook
after_update :post_update_hook after_update :post_update_hook
after_destroy :post_destroy_hook after_destroy :post_destroy_hook
scope :in_project, ->(project) { where(source_id: project.id) }
scope :in_projects, ->(projects) { where(source_id: projects.pluck(:id)) }
scope :with_user, ->(user) { where(user_id: user.id) }
class << self class << self
# Add users to project teams with passed access option # Add users to project teams with passed access option
...@@ -50,7 +31,7 @@ class UsersProject < ActiveRecord::Base ...@@ -50,7 +31,7 @@ class UsersProject < ActiveRecord::Base
# add_users_into_projects( # add_users_into_projects(
# project_ids, # project_ids,
# user_ids, # user_ids,
# UsersProject::MASTER # ProjectMember::MASTER
# ) # )
# #
# add_users_into_projects( # add_users_into_projects(
...@@ -60,20 +41,20 @@ class UsersProject < ActiveRecord::Base ...@@ -60,20 +41,20 @@ class UsersProject < ActiveRecord::Base
# ) # )
# #
def add_users_into_projects(project_ids, user_ids, access) def add_users_into_projects(project_ids, user_ids, access)
project_access = if roles_hash.has_key?(access) access_level = if roles_hash.has_key?(access)
roles_hash[access] roles_hash[access]
elsif roles_hash.values.include?(access.to_i) elsif roles_hash.values.include?(access.to_i)
access access
else else
raise "Non valid access" raise "Non valid access"
end end
UsersProject.transaction do ProjectMember.transaction do
project_ids.each do |project_id| project_ids.each do |project_id|
user_ids.each do |user_id| user_ids.each do |user_id|
users_project = UsersProject.new(project_access: project_access, user_id: user_id) member = ProjectMember.new(access_level: access_level, user_id: user_id)
users_project.project_id = project_id member.source_id = project_id
users_project.save member.save
end end
end end
end end
...@@ -84,10 +65,10 @@ class UsersProject < ActiveRecord::Base ...@@ -84,10 +65,10 @@ class UsersProject < ActiveRecord::Base
end end
def truncate_teams(project_ids) def truncate_teams(project_ids)
UsersProject.transaction do ProjectMember.transaction do
users_projects = UsersProject.where(project_id: project_ids) members = ProjectMember.where(source_id: project_ids)
users_projects.each do |users_project| members.each do |member|
users_project.destroy member.destroy
end end
end end
...@@ -110,7 +91,7 @@ class UsersProject < ActiveRecord::Base ...@@ -110,7 +91,7 @@ class UsersProject < ActiveRecord::Base
end end
def access_field def access_field
project_access access_level
end end
def owner? def owner?
...@@ -129,7 +110,7 @@ class UsersProject < ActiveRecord::Base ...@@ -129,7 +110,7 @@ class UsersProject < ActiveRecord::Base
end end
def post_update_hook def post_update_hook
notification_service.update_team_member(self) if self.project_access_changed? notification_service.update_team_member(self) if self.access_level_changed?
end end
def post_destroy_hook def post_destroy_hook
...@@ -149,4 +130,8 @@ class UsersProject < ActiveRecord::Base ...@@ -149,4 +130,8 @@ class UsersProject < ActiveRecord::Base
def system_hook_service def system_hook_service
SystemHooksService.new SystemHooksService.new
end end
def project
source
end
end end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment