Commit 44b3404e authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'code-from-ce-6-7' into 'master'

Code from CE 6.7

Fixes #75
parents 6fd29312 896159ca
...@@ -35,3 +35,4 @@ doc/code/* ...@@ -35,3 +35,4 @@ doc/code/*
*.log *.log
public/uploads.* public/uploads.*
public/assets/ public/assets/
.envrc
language: ruby language: ruby
env: env:
global: global:
- DB=mysql
- TRAVIS=true - TRAVIS=true
matrix: matrix:
- TASK=spinach - TASK=spinach DB=mysql
- TASK=spec - TASK=spec DB=mysql
- TASK=jasmine:ci - TASK=jasmine:ci DB=mysql
- TASK=spinach DB=postgresql
- TASK=spec DB=postgresql
- TASK=jasmine:ci DB=postgresql
before_install: before_install:
- sudo apt-get install libicu-dev -y - sudo apt-get install libicu-dev -y
branches: branches:
...@@ -15,13 +17,12 @@ branches: ...@@ -15,13 +17,12 @@ branches:
rvm: rvm:
- 2.0.0 - 2.0.0
services: services:
- mysql
- redis-server - redis-server
before_script: 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:setup"
- "bundle exec rake db:seed_fu" - "bundle exec rake db:seed_fu"
- "cp config/database.yml.$DB config/database.yml"
- "cp config/gitlab.yml.example config/gitlab.yml"
script: "bundle exec rake $TASK --trace" script: "bundle exec rake $TASK --trace"
notifications: notifications:
email: false email: false
v 6.7.0
- Increased the example Nginx client_max_body_size from 5MB to 20MB, consider updating it manually on existing installations
- Add support for Gemnasium as a Project Service (Olivier Gonzalez)
- Add edit file button to MergeRequest diff
- Public groups (Jason Hollingsworth)
- Cleaner headers in Notification Emails (Pierre de La Morinerie)
- Blob and tree gfm links to anchors work
- Piwik Integration (Sebastian Winkler)
- Show contribution guide link for new issue form (Jeroen van Baarsen)
- Fix CI status for merge requests from fork
- Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard)
- Converted all the help sections into markdown
- LDAP user filters
- Streamline the content of notification emails (Pierre de La Morinerie)
- Fixes a bug with group member administration (Matt DeTullio)
- Sort tag names using VersionSorter (Robert Speicher)
- Add GFM autocompletion for MergeRequests (Robert Speicher)
- Add webhook when a new tag is pushed (Jeroen van Baarsen)
- Add button for toggling inline comments in diff view
- Add retry feature for repository import
- Reuse the GitLab LDAP connection within each request
- Changed markdown new line behaviour to conform to markdown standards
- Fix global search
- Faster authorized_keys rebuilding in `rake gitlab:shell:setup` (requires gitlab-shell 1.8.5)
- Create and Update MR calls now support the description parameter (Greg Messner)
- Markdown relative links in the wiki link to wiki pages, markdown relative links in repositories link to files in the repository
v 6.6.5
- Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard)
- Hide mr close button for comment form if merge request was closed or inline comment
- Adds ability to reopen closed merge request
v 6.6.4
- Add missing html escape for highlighted code blocks in comments, issues
v 6.6.3
- Fix 500 error when edit yourself from admin area
- Hide private groups for public profiles
v 6.6.2 v 6.6.2
- Fix 500 error on branch/tag create or remove via UI - Fix 500 error on branch/tag create or remove via UI
...@@ -18,7 +57,7 @@ v 6.6.0 ...@@ -18,7 +57,7 @@ v 6.6.0
- Remove snippet expiration - Remove snippet expiration
- Mobile UI improvements (Drew Blessing) - Mobile UI improvements (Drew Blessing)
- Fix block/remove UI for admin::users#show page - Fix block/remove UI for admin::users#show page
- Show users' group membership on users' activity page - Show users' group membership on users' activity page (Robert Djurasaj)
- User pages are visible without login if user is authorized to a public project - User pages are visible without login if user is authorized to a public project
- Markdown rendered headers have id derived from their name and link to their id - Markdown rendered headers have id derived from their name and link to their id
- Improve application to work faster with large groups (100+ members) - Improve application to work faster with large groups (100+ members)
...@@ -27,6 +66,7 @@ v 6.6.0 ...@@ -27,6 +66,7 @@ v 6.6.0
- Restyle Issue#show page and MR#show page - Restyle Issue#show page and MR#show page
- Ability to filter by multiple labels for Issues page - Ability to filter by multiple labels for Issues page
- Rails version to 4.0.3 - Rails version to 4.0.3
- Fixed attachment identifier displaying underneath note text (Jason Blanchard)
v 6.5.1 v 6.5.1
- Fix branch selectbox when create merge request from fork - Fix branch selectbox when create merge request from fork
......
...@@ -22,7 +22,7 @@ Issues and merge requests should be in English and contain appropriate language ...@@ -22,7 +22,7 @@ Issues and merge requests should be in English and contain appropriate language
## Issue tracker ## Issue tracker
To get support for your particular problem please use the channels as detailed in the getting help section of [the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md). Professional [support subscriptions](http://www.gitlab.com/subscription/) and [consulting services](http://www.gitlab.com/consultancy/) are available from [GitLab.com](http://www.gitlab.com/). To get support for your particular problem please use the channels as detailed in the [getting help section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#getting-help). Professional [support subscriptions](http://www.gitlab.com/subscription/) and [consulting services](http://www.gitlab.com/consultancy/) are available from [GitLab.com](http://www.gitlab.com/).
The [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious bugs or misbehavior in the latest [stable or development release of GitLab](MAINTENANCE.md). When submitting an issue please conform to the issue submission guidelines listed below. Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue. The [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious bugs or misbehavior in the latest [stable or development release of GitLab](MAINTENANCE.md). When submitting an issue please conform to the issue submission guidelines listed below. Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue.
...@@ -48,7 +48,7 @@ Please send a merge request with a tested solution or a merge request with a fai ...@@ -48,7 +48,7 @@ Please send a merge request with a tested solution or a merge request with a fai
## Merge requests ## Merge requests
We welcome merge requests with fixes and improvements to GitLab code, tests, and/or documentation. The features we would really like a merge request for are listed with the [status 'accepting merge/merge requests' on our feedback forum](http://feedback.gitlab.com/forums/176466-general/status/796455) but other improvements are also welcome. We welcome merge requests with fixes and improvements to GitLab code, tests, and/or documentation. The features we would really like a merge request for are listed with the [status 'accepting merge requests' on our feedback forum](http://feedback.gitlab.com/forums/176466-general/status/796455) but other improvements are also welcome. If you want to add a new feature that is not marked it is best to first create a feedback issue (if there isn't one already) and leave a comment asking for it to be marked accepting merge requests. Please include screenshots or wireframes if the feature will also change the UI.
### Merge request guidelines ### Merge request guidelines
...@@ -66,25 +66,30 @@ If you can, please submit a merge request with the fix or improvements including ...@@ -66,25 +66,30 @@ If you can, please submit a merge request with the fix or improvements including
1. If the MR changes the UI it should include before and after screenshots 1. If the MR changes the UI it should include before and after screenshots
1. Link relevant [issues](https://gitlab.com/gitlab-org/gitlab-ce/issues) and/or [feedback items](http://feedback.gitlab.com/) from the merge request description and leave a comment on them with a link back to the MR 1. Link relevant [issues](https://gitlab.com/gitlab-org/gitlab-ce/issues) and/or [feedback items](http://feedback.gitlab.com/) from the merge request description and leave a comment on them with a link back to the MR
1. Be prepared to answer questions and incorporate feedback even if requests for this arrive weeks or months after your MR submittion 1. Be prepared to answer questions and incorporate feedback even if requests for this arrive weeks or months after your MR submittion
1. If your MR touches code that executes shell commands, make sure it adheres to the [shell command guidelines]( doc/development/shell_commands.md).
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.com 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? The smaller a MR is the more likely it is it will be merged, 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? The smaller a MR is the more likely it is it will be merged, after that you can send more MR's to enhance it.
The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. 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). Please ensure that your merge request meets the following contribution acceptance criteria.
The best time to submit a MR and get feedback fast.
Before this time the GitLab.com 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. ## Contribution acceptance criteria
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. 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)
We will accept a merge requests if it: 1. Can merge without problems (if not please use: `git rebase master`)
1. Does not break any existing functionality
* Includes proper tests and all tests pass (unless it contains a test exposing a bug in existing code) 1. Fixes one specific issue or implements one specific feature (do not combine things, send separate merge requests if needed)
* Can be merged without problems (if not please use: `git rebase master`) 1. Keeps the GitLab code base clean and well structured
* Do not break any existing functionality 1. Contains functionality we think other users will benefit from too
* Conforms to the [Ruby](https://github.com/bbatsov/ruby-style-guide) and [Rails](https://github.com/bbatsov/rails-style-guide) style guides and best practices 1. Doesn't add configuration options since they complicate future changes
* Fixes one specific issue or implements one specific feature (do not combine things, send separate merge requests if needed) 1. Contains a single commit (please use `git rebase -i` to squash commits)
* Keeps the GitLab code base clean and well structured 1. It conforms to the following style guides
* Contains functionality we think other users will benefit from too
* Doesn't add configuration options since these complicate future changes ## Style guides
* Contains a single commit (please use `git rebase -i` to squash commits)
1. [Ruby style guide](https://github.com/bbatsov/ruby-style-guide)
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). 1. [Rails style guide](https://github.com/bbatsov/rails-style-guide)
1. [CoffeeScript style guide](https://github.com/polarmobile/coffeescript-style-guide)
1. [Shell command guidelines](doc/development/shell_commands.md)
...@@ -15,6 +15,9 @@ gem 'rails-observers' ...@@ -15,6 +15,9 @@ gem 'rails-observers'
gem 'actionpack-page_caching' gem 'actionpack-page_caching'
gem 'actionpack-action_caching' gem 'actionpack-action_caching'
# Default values for AR models
gem "default_value_for", "~> 3.0.0"
# Supported DBs # Supported DBs
gem "mysql2", group: :mysql gem "mysql2", group: :mysql
gem "pg", group: :postgres gem "pg", group: :postgres
...@@ -29,7 +32,7 @@ gem 'omniauth-github' ...@@ -29,7 +32,7 @@ gem 'omniauth-github'
# 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", '~> 5.4.0' gem "gitlab_git", '~> 5.7.1'
# 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'
...@@ -46,7 +49,8 @@ gem "gitlab-linguist", "~> 3.0.0", require: "linguist" ...@@ -46,7 +49,8 @@ gem "gitlab-linguist", "~> 3.0.0", require: "linguist"
# API # API
gem "grape", "~> 0.6.1" gem "grape", "~> 0.6.1"
gem "grape-entity", "~> 0.3.0" # Replace with rubygems when nesteted entities get released
gem "grape-entity", "~> 0.4.1", ref: 'd904381c951e86250c3f44213b349a3dd8e83fb1', git: 'https://github.com/intridea/grape-entity.git'
gem 'rack-cors', require: 'rack/cors' gem 'rack-cors', require: 'rack/cors'
# Email validation # Email validation
...@@ -112,6 +116,7 @@ gem 'settingslogic' ...@@ -112,6 +116,7 @@ gem 'settingslogic'
# Misc # Misc
gem "foreman" gem "foreman"
gem 'version_sorter'
# Cache # Cache
gem "redis-rails" gem "redis-rails"
...@@ -125,6 +130,9 @@ gem "hipchat", "~> 0.14.0" ...@@ -125,6 +130,9 @@ gem "hipchat", "~> 0.14.0"
# Flowdock integration # Flowdock integration
gem "gitlab-flowdock-git-hook", "~> 0.4.2" gem "gitlab-flowdock-git-hook", "~> 0.4.2"
# Gemnasium integration
gem "gemnasium-gitlab-service", "~> 0.2"
# d3 # d3
gem "d3_rails", "~> 3.1.4" gem "d3_rails", "~> 3.1.4"
...@@ -162,7 +170,7 @@ group :development do ...@@ -162,7 +170,7 @@ group :development do
gem "annotate", "~> 2.6.0.beta2" gem "annotate", "~> 2.6.0.beta2"
gem "letter_opener" gem "letter_opener"
gem 'quiet_assets', '~> 1.0.1' gem 'quiet_assets', '~> 1.0.1'
gem 'rack-mini-profiler' gem 'rack-mini-profiler', require: false
# Better errors handler # Better errors handler
gem 'better_errors' gem 'better_errors'
......
...@@ -5,6 +5,15 @@ GIT ...@@ -5,6 +5,15 @@ GIT
specs: specs:
github-markup (0.7.6) github-markup (0.7.6)
GIT
remote: https://github.com/intridea/grape-entity.git
revision: d904381c951e86250c3f44213b349a3dd8e83fb1
ref: d904381c951e86250c3f44213b349a3dd8e83fb1
specs:
grape-entity (0.4.1)
activesupport
multi_json (>= 1.3.2)
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
...@@ -101,6 +110,8 @@ GEM ...@@ -101,6 +110,8 @@ GEM
daemons (1.1.9) daemons (1.1.9)
database_cleaner (1.2.0) database_cleaner (1.2.0)
debug_inspector (0.0.2) debug_inspector (0.0.2)
default_value_for (3.0.0)
activerecord (>= 3.2.0, < 5.0)
descendants_tracker (0.0.3) descendants_tracker (0.0.3)
devise (3.0.4) devise (3.0.4)
bcrypt-ruby (~> 3.0) bcrypt-ruby (~> 3.0)
...@@ -152,6 +163,8 @@ GEM ...@@ -152,6 +163,8 @@ GEM
dotenv (>= 0.7) dotenv (>= 0.7)
thor (>= 0.13.6) thor (>= 0.13.6)
formatador (0.2.4) formatador (0.2.4)
gemnasium-gitlab-service (0.2.1)
rugged (~> 0.19)
gemoji (1.3.1) gemoji (1.3.1)
gherkin-ruby (0.3.1) gherkin-ruby (0.3.1)
racc racc
...@@ -177,7 +190,7 @@ GEM ...@@ -177,7 +190,7 @@ GEM
charlock_holmes (~> 0.6.6) charlock_holmes (~> 0.6.6)
escape_utils (~> 0.2.4) escape_utils (~> 0.2.4)
mime-types (~> 1.19) mime-types (~> 1.19)
gitlab_git (5.4.0) gitlab_git (5.7.1)
activesupport (~> 4.0.0) activesupport (~> 4.0.0)
charlock_holmes (~> 0.6.9) charlock_holmes (~> 0.6.9)
gitlab-grit (~> 2.6.1) gitlab-grit (~> 2.6.1)
...@@ -202,9 +215,6 @@ GEM ...@@ -202,9 +215,6 @@ GEM
rack-accept rack-accept
rack-mount rack-mount
virtus (>= 1.0.0) virtus (>= 1.0.0)
grape-entity (0.3.0)
activesupport
multi_json (>= 1.3.2)
growl (1.0.3) growl (1.0.3)
guard (2.2.4) guard (2.2.4)
formatador (>= 0.2.4) formatador (>= 0.2.4)
...@@ -231,7 +241,7 @@ GEM ...@@ -231,7 +241,7 @@ GEM
httparty httparty
httparty httparty
http_parser.rb (0.5.3) http_parser.rb (0.5.3)
httparty (0.12.0) httparty (0.13.0)
json (~> 1.8) json (~> 1.8)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
httpauth (0.2.0) httpauth (0.2.0)
...@@ -338,7 +348,7 @@ GEM ...@@ -338,7 +348,7 @@ GEM
rack-attack (2.3.0) rack-attack (2.3.0)
rack rack
rack-cors (0.2.9) rack-cors (0.2.9)
rack-mini-profiler (0.1.31) rack-mini-profiler (0.9.0)
rack (>= 1.1.3) rack (>= 1.1.3)
rack-mount (0.8.3) rack-mount (0.8.3)
rack (>= 1.0.0) rack (>= 1.0.0)
...@@ -533,6 +543,7 @@ GEM ...@@ -533,6 +543,7 @@ GEM
raindrops (~> 0.7) raindrops (~> 0.7)
unicorn-worker-killer (0.4.2) unicorn-worker-killer (0.4.2)
unicorn (~> 4) unicorn (~> 4)
version_sorter (1.1.0)
virtus (1.0.1) virtus (1.0.1)
axiom-types (~> 0.0.5) axiom-types (~> 0.0.5)
coercible (~> 1.0) coercible (~> 1.0)
...@@ -568,6 +579,7 @@ DEPENDENCIES ...@@ -568,6 +579,7 @@ DEPENDENCIES
coveralls coveralls
d3_rails (~> 3.1.4) d3_rails (~> 3.1.4)
database_cleaner database_cleaner
default_value_for (~> 3.0.0)
devise (= 3.0.4) devise (= 3.0.4)
devise-async (= 0.8.0) devise-async (= 0.8.0)
email_spec email_spec
...@@ -578,18 +590,19 @@ DEPENDENCIES ...@@ -578,18 +590,19 @@ DEPENDENCIES
fog (~> 1.3.1) fog (~> 1.3.1)
font-awesome-rails (~> 3.2) font-awesome-rails (~> 3.2)
foreman foreman
gemnasium-gitlab-service (~> 0.2)
gemoji (~> 1.3.0) gemoji (~> 1.3.0)
github-markup (~> 0.7.4)! github-markup (~> 0.7.4)!
gitlab-flowdock-git-hook (~> 0.4.2) gitlab-flowdock-git-hook (~> 0.4.2)
gitlab-gollum-lib (~> 1.1.0) gitlab-gollum-lib (~> 1.1.0)
gitlab-grack (~> 2.0.0.pre) gitlab-grack (~> 2.0.0.pre)
gitlab-linguist (~> 3.0.0) gitlab-linguist (~> 3.0.0)
gitlab_git (~> 5.4.0) gitlab_git (~> 5.7.1)
gitlab_meta (= 6.0) gitlab_meta (= 6.0)
gitlab_omniauth-ldap (= 1.0.4) gitlab_omniauth-ldap (= 1.0.4)
gon (~> 5.0.0) gon (~> 5.0.0)
grape (~> 0.6.1) grape (~> 0.6.1)
grape-entity (~> 0.3.0) grape-entity (~> 0.4.1)!
growl growl
guard-rspec guard-rspec
guard-spinach guard-spinach
...@@ -657,4 +670,5 @@ DEPENDENCIES ...@@ -657,4 +670,5 @@ DEPENDENCIES
underscore-rails (~> 1.4.4) underscore-rails (~> 1.4.4)
unicorn (~> 4.6.3) unicorn (~> 4.6.3)
unicorn-worker-killer unicorn-worker-killer
version_sorter
webmock webmock
...@@ -12,7 +12,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co ...@@ -12,7 +12,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co
- Closes invalid issues and merge requests with a comment (duplicates, [feature requests](#feature-requests), [fixed in newer version](#issue-fixed-in-newer-version), [issue report for old version](#issue-report-for-old-version), not a problem in GitLab, etc.) - Closes invalid issues and merge requests with a comment (duplicates, [feature requests](#feature-requests), [fixed in newer version](#issue-fixed-in-newer-version), [issue report for old version](#issue-report-for-old-version), not a problem in GitLab, etc.)
- Assigns appropriate [labels](#how-we-handle-issues) - Assigns appropriate [labels](#how-we-handle-issues)
- Asks for feedback from issue reporter/merge request initiator ([invalid issue reports](#improperly-formatted-issue), [format code](#code-format), etc.) - Asks for feedback from issue reporter/merge request initiator ([invalid issue reports](#improperly-formatted-issue), [format code](#code-format), etc.)
- Asks for feedback from the relevant developer(s) based on the [list of members and their specialities](http://gitlab.org/team/) - Asks for feedback from the relevant developer(s) based on the [list of members and their specialities](https://www.gitlab.com/core-team/)
- Monitors all issues/merge requests for feedback (but especially ones commented on since automatically watching them): - Monitors all issues/merge requests for feedback (but especially ones commented on since automatically watching them):
- Closes issues with no feedback from the reporter for two weeks - Closes issues with no feedback from the reporter for two weeks
- Closes stale merge requests - Closes stale merge requests
...@@ -37,7 +37,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co ...@@ -37,7 +37,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co
## Mentioning people ## Mentioning people
The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](http://gitlab.org/team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person. The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](https://www.gitlab.com/core-team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person.
## Workflow labels ## Workflow labels
...@@ -89,7 +89,7 @@ Please use ``` to format console output, logs, and code as it's very hard to rea ...@@ -89,7 +89,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\]\(http://blog.gitlab.org/). 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://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).
### Improperly formatted merge request ### Improperly formatted merge request
......
...@@ -23,19 +23,17 @@ ...@@ -23,19 +23,17 @@
### Resources ### Resources
* GitLab.org community site: [Homepage](http://gitlab.org) | [Screenshots](http://gitlab.org/screenshots/) | [Blog](http://blog.gitlab.org/) | [Demo](http://demo.gitlabhq.com/users/sign_in) * [GitLab.com](https://www.gitlab.com/) includes information about [subscriptions](https://www.gitlab.com/subscription/), [consultancy](https://www.gitlab.com/consultancy/), the [community](https://www.gitlab.com/community/) and the [hosted GitLab Cloud](https://www.gitlab.com/cloud/).
* GitLab.com commercial services: [Homepage](http://www.gitlab.com/) | [Subscription](http://www.gitlab.com/subscription/) | [Consultancy](http://www.gitlab.com/consultancy/) | [GitLab Cloud](http://www.gitlab.com/cloud/) | [Blog](http://blog.gitlab.com/) * [GitLab Enterprise Edition](https://www.gitlab.com/gitlab-ce/) offers additional features that are useful for larger organizations (100+ users).
* [GitLab Enterprise Edition](https://www.gitlab.com/features/) offers additional features that are useful for larger organizations (100+ users). * [GitLab CI](https://www.gitlab.com/gitlab-ci/) is a continuous integration (CI) server that is easy to integrate with GitLab.
* [GitLab CI](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/README.md) is a continuous integration (CI) server that is easy to integrate with GitLab.
* Unofficial third-party [iPhone app](http://gitlabcontrol.com/) and [Android app](https://play.google.com/store/apps/details?id=com.bd.gitlab&hl=en) for GitLab * Unofficial third-party [iPhone app](http://gitlabcontrol.com/) and [Android app](https://play.google.com/store/apps/details?id=com.bd.gitlab&hl=en) for GitLab
### Requirements ### Requirements
* Ubuntu/Debian** * Ubuntu/Debian/CentOS/RHEL**
* ruby 1.9.3+ * ruby 1.9.3+
* git 1.7.10+ * git 1.7.10+
* redis 2.0+ * redis 2.0+
...@@ -47,7 +45,7 @@ ...@@ -47,7 +45,7 @@
#### Official installation methods #### Official installation methods
* [GitLab packages (beta)](https://www.gitlab.com/downloads/) These packages contain GitLab and all its depencies (PostgreSQL, Redis, Nginx, Unicorn, etc.). They are made with [omnibus-gitlab](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md) that also contains the installation instructions. These packages currently support a reduced selection of GitLab's normal features. For instance, it is not yet possible to create/restore application backups or to use HTTPS. * [GitLab packages](https://www.gitlab.com/downloads/) These packages contain GitLab and all its depencies (Ruby, PostgreSQL, Redis, Nginx, Unicorn, etc.). They are made with [omnibus-gitlab](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md) that also contains the installation instructions.
* [GitLab virtual machine images](https://www.gitlab.com/downloads/) contain an operating system and a preinstalled GitLab. They are made with [GitLab Packer](https://gitlab.com/gitlab-org/gitlab-packer/blob/master/README.md) that also contains the installation instructions. * [GitLab virtual machine images](https://www.gitlab.com/downloads/) contain an operating system and a preinstalled GitLab. They are made with [GitLab Packer](https://gitlab.com/gitlab-org/gitlab-packer/blob/master/README.md) that also contains the installation instructions.
...@@ -155,12 +153,3 @@ or start each component separately ...@@ -155,12 +153,3 @@ or start each component separately
* [Book](http://www.packtpub.com/gitlab-repository-management/book) written by GitLab enthusiast Jonathan M. Hethey is unofficial but it offers a good overview. * [Book](http://www.packtpub.com/gitlab-repository-management/book) written by GitLab enthusiast Jonathan M. Hethey is unofficial but it offers a good overview.
* [Gitter chat room](https://gitter.im/gitlabhq/gitlabhq#) here you can ask questions when you need help. * [Gitter chat room](https://gitter.im/gitlabhq/gitlabhq#) here you can ask questions when you need help.
### Getting in touch
* [Core team](http://gitlab.org/team/)
* [Contributors](http://contributors.gitlab.org/)
* [Community](http://gitlab.org/community/)
6.6.0-ee 6.7.0.pre-ee
$ -> $ ->
$("body").on "click", ".js-details-target", -> $("body").on "click", ".js-details-target", ->
container = $(@).closest(".js-details-container") container = $(@).closest(".js-details-container")
container.toggleClass("open") container.toggleClass("open")
# Show details content. Hides link after click.
#
# %div
# %a.js-details-expand
# %div.js-details-content
#
$("body").on "click", ".js-details-expand", (e) ->
$(@).next('.js-details-content').removeClass("hide")
$(@).hide()
e.preventDefault()
$ -> $ ->
$("body").on "click", ".js-toggler-target", -> $("body").on "click", ".js-toggler-target", ->
container = $(@).closest(".js-toggler-container") container = $(@).closest(".js-toggler-container")
container.toggleClass("on") container.toggleClass("on")
$("body").on "click", ".js-toggle-visibility-link", (e) -> # Toggle button. Show/hide content inside parent container.
# Button does not change visibility. If button has icon - it changes chevron style.
#
# %div.js-toggle-container
# %a.js-toggle-button
# %div.js-toggle-content
#
$("body").on "click", ".js-toggle-button", (e) ->
$(@).find('i'). $(@).find('i').
toggleClass('icon-chevron-down'). toggleClass('icon-chevron-down').
toggleClass('icon-chevron-up') toggleClass('icon-chevron-up')
container = $(".js-toggle-visibility-container")
container.toggleClass("hide")
e.preventDefault()
$("body").on "click", ".js-toggle-button", (e) ->
$(@).closest(".js-toggle-container").find(".js-toggle-content").toggle() $(@).closest(".js-toggle-container").find(".js-toggle-content").toggle()
e.preventDefault() e.preventDefault()
class Commit class Commit
constructor: -> constructor: ->
$('.files .file').each -> $('.files .diff-file').each ->
new CommitFile(this) new CommitFile(this)
@Commit = Commit @Commit = Commit
...@@ -13,6 +13,7 @@ GitLab.GfmAutoComplete = ...@@ -13,6 +13,7 @@ GitLab.GfmAutoComplete =
Members: Members:
template: '<li data-value="${username}">${username} <small>${name}</small></li>' template: '<li data-value="${username}">${username} <small>${name}</small></li>'
# Issues and MergeRequests
Issues: Issues:
template: '<li data-value="${id}"><small>${id}</small> ${title} </li>' template: '<li data-value="${id}"><small>${id}</small> ${title} </li>'
...@@ -46,11 +47,22 @@ GitLab.GfmAutoComplete = ...@@ -46,11 +47,22 @@ GitLab.GfmAutoComplete =
before_save: (issues) -> before_save: (issues) ->
$.map issues, (i) -> id: i.iid, title: sanitize(i.title), search: "#{i.iid} #{i.title}" $.map issues, (i) -> id: i.iid, title: sanitize(i.title), search: "#{i.iid} #{i.title}"
input.atwho
at: '!'
alias: 'mergerequests'
search_key: 'search'
tpl: @Issues.template
callbacks:
before_save: (merges) ->
$.map merges, (m) -> id: m.iid, title: sanitize(m.title), search: "#{m.iid} #{m.title}"
input.one "focus", => input.one "focus", =>
$.getJSON(@dataSource).done (data) -> $.getJSON(@dataSource).done (data) ->
# load members # load members
input.atwho 'load', "@", data.members input.atwho 'load', "@", data.members
# load issues # load issues
input.atwho 'load', "issues", data.issues input.atwho 'load', "issues", data.issues
# load merge requests
input.atwho 'load', "mergerequests", data.mergerequests
# load emojis # load emojis
input.atwho 'load', ":", data.emojis input.atwho 'load', ":", data.emojis
...@@ -62,11 +62,6 @@ $ -> ...@@ -62,11 +62,6 @@ $ ->
$('.remove-row').bind 'ajax:success', -> $('.remove-row').bind 'ajax:success', ->
$(this).closest('li').fadeOut() $(this).closest('li').fadeOut()
# Click a .appear-link, appear-data fadeout
$(".appear-link").on 'click', (e) ->
$('.appear-data').fadeIn()
e.preventDefault()
# Initialize select2 selects # Initialize select2 selects
$('select.select2').select2(width: 'resolve', dropdownAutoWidth: true) $('select.select2').select2(width: 'resolve', dropdownAutoWidth: true)
...@@ -117,14 +112,10 @@ $ -> ...@@ -117,14 +112,10 @@ $ ->
# Commit show suppressed diff # Commit show suppressed diff
$(".content").on "click", ".supp_diff_link", -> $(".diff-content").on "click", ".supp_diff_link", ->
$(@).next('table').show() $(@).next('table').show()
$(@).remove() $(@).remove()
$(".content").on "click", ".js-details-expand", ->
$(@).next('.js-details-contain').removeClass("hide")
$(@).remove()
(($) -> (($) ->
# Disable an element and add the 'disabled' Bootstrap class # Disable an element and add the 'disabled' Bootstrap class
$.fn.extend disable: -> $.fn.extend disable: ->
......
...@@ -10,6 +10,16 @@ ...@@ -10,6 +10,16 @@
query: (query) -> query: (query) ->
Api.projectUsers project_id, query.term, (users) -> Api.projectUsers project_id, query.term, (users) ->
data = { results: users } data = { results: users }
nullUser = {
name: 'Unassigned',
avatar: null,
username: 'none',
id: ''
}
data.results.unshift(nullUser)
query.callback(data) query.callback(data)
initSelection: (element, callback) -> initSelection: (element, callback) ->
...@@ -35,8 +45,13 @@ ...@@ -35,8 +45,13 @@
else else
avatar = gon.relative_url_root + "/assets/no_avatar.png" avatar = gon.relative_url_root + "/assets/no_avatar.png"
if user.id == ''
avatarMarkup = ''
else
avatarMarkup = "<div class='user-image'><img class='avatar s24' src='#{avatar}'></div>"
"<div class='user-result'> "<div class='user-result'>
<div class='user-image'><img class='avatar s24' src='#{avatar}'></div> #{avatarMarkup}
<div class='user-name'>#{user.name}</div> <div class='user-name'>#{user.name}</div>
<div class='user-username'>#{user.username}</div> <div class='user-username'>#{user.username}</div>
</div>" </div>"
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
@import "generic/issue_box.scss"; @import "generic/issue_box.scss";
@import "generic/files.scss"; @import "generic/files.scss";
@import "generic/lists.scss"; @import "generic/lists.scss";
@import "generic/flash.scss";
@import "generic/forms.scss"; @import "generic/forms.scss";
@import "generic/selects.scss"; @import "generic/selects.scss";
@import "generic/highlight.scss"; @import "generic/highlight.scss";
...@@ -47,6 +48,7 @@ ...@@ -47,6 +48,7 @@
@import "sections/header.scss"; @import "sections/header.scss";
@import "sections/nav.scss"; @import "sections/nav.scss";
@import "sections/commits.scss"; @import "sections/commits.scss";
@import "sections/diff.scss";
@import "sections/issues.scss"; @import "sections/issues.scss";
@import "sections/projects.scss"; @import "sections/projects.scss";
@import "sections/snippets.scss"; @import "sections/snippets.scss";
...@@ -68,7 +70,7 @@ ...@@ -68,7 +70,7 @@
@import "sections/groups.scss"; @import "sections/groups.scss";
/** /**
* Code ighlight * Code highlight
*/ */
@import "highlight/white.scss"; @import "highlight/white.scss";
@import "highlight/dark.scss"; @import "highlight/dark.scss";
......
...@@ -2,3 +2,18 @@ ...@@ -2,3 +2,18 @@
background: #f9f9f9; background: #f9f9f9;
padding: 15px; padding: 15px;
} }
.centered-light-block {
text-align: center;
color: #888;
margin: 20px;
}
.nothing-here-block {
text-align: center;
padding: 20px;
color: #666;
font-weight: normal;
font-size: 16px;
line-height: 36px;
}
...@@ -158,6 +158,14 @@ ...@@ -158,6 +158,14 @@
color: #468847; color: #468847;
} }
} }
&.btn-grouped {
margin-right: 7px;
float: left;
&:last-child {
margin-right: 0px;
}
}
} }
.btn-block { .btn-block {
...@@ -169,9 +177,8 @@ ...@@ -169,9 +177,8 @@
} }
} }
.btn,
.btn-group { .btn-group {
&.grouped { &.btn-grouped {
margin-right: 7px; margin-right: 7px;
float: left; float: left;
&:last-child { &:last-child {
......
...@@ -11,8 +11,6 @@ ...@@ -11,8 +11,6 @@
.bgred { background: #F2DEDE!important } .bgred { background: #F2DEDE!important }
/** COMMON CLASSES **/ /** COMMON CLASSES **/
.left { float:left }
.prepend-top-10 { margin-top:10px } .prepend-top-10 { margin-top:10px }
.prepend-top-20 { margin-top:20px } .prepend-top-20 { margin-top:20px }
.prepend-left-10 { margin-left:10px } .prepend-left-10 { margin-left:10px }
...@@ -24,32 +22,9 @@ ...@@ -24,32 +22,9 @@
.append-bottom-20 { margin-bottom:20px } .append-bottom-20 { margin-bottom:20px }
.inline { display: inline-block } .inline { display: inline-block }
.padded { padding:20px } .underlined-link { text-decoration: underline; }
.ipadded { padding:20px!important }
.lborder { border-left:1px solid #eee }
.underlined_link { text-decoration: underline; }
.hint { font-style: italic; color: #999; } .hint { font-style: italic; color: #999; }
.light { color: #888 } .light { color: #888 }
.tiny { font-weight: normal }
.vtop { vertical-align: top !important; }
/** ALERT MESSAGES **/
.alert.alert-disabled {
background: #EEE;
color: #777;
border-color: #DDD;
}
/** HELPERS **/
.nothing_here_message {
text-align: center;
padding: 20px;
color: #666;
font-weight: normal;
font-size: 16px;
line-height: 36px;
}
.slead { .slead {
color: #666; color: #666;
...@@ -59,53 +34,23 @@ ...@@ -59,53 +34,23 @@
line-height: 24px; line-height: 24px;
} }
.tab-content { .tab-content {
overflow: visible; overflow: visible;
} }
@media (max-width: 1200px) { pre {
.only-wide { &.clean {
display: none; background: none;
} border: none;
} margin: 0;
padding: 0;
pre.well-pre {
border: 1px solid #EEE;
background: #f9f9f9;
border-radius: 0;
color: #555;
}
.input-append .btn.active, .input-prepend .btn.active {
background: #CCC;
border-color: #BBB;
text-shadow: 0 1px 1px #fff;
font-weight: bold;
@include box-shadow(inset 0 2px 4px rgba(0,0,0,.15));
}
/** Big Labels **/
.state-label {
font-size: 14px;
padding: 9px 25px;
text-align: center;
text-shadow: none;
margin-right: 20px;
&.state-label-blue {
background: #31708f;
color: #FFF;
}
&.state-label-green {
background: #4A4;
color: #FFF;
} }
&.state-label-red { &.well-pre {
background: #DA4E49; border: 1px solid #EEE;
color: #FFF; background: #f9f9f9;
border-radius: 0;
color: #555;
} }
} }
...@@ -135,31 +80,6 @@ pre.well-pre { ...@@ -135,31 +80,6 @@ pre.well-pre {
} }
/** FLASH message **/ /** FLASH message **/
.flash-container {
display: none;
cursor: pointer;
margin: 0;
text-align: center;
color: #fff;
font-size: 14px;
position: fixed;
bottom: 0;
width: 100%;
opacity: 0.8;
z-index: 100;
.flash-notice {
background: #49C;
padding: 10px;
text-shadow: 0 1px 1px #178;
}
.flash-alert {
background: #C67;
text-shadow: 0 1px 1px #945;
padding: 10px;
}
}
.author_link { .author_link {
color: $link_color; color: $link_color;
} }
...@@ -279,22 +199,6 @@ li.note { ...@@ -279,22 +199,6 @@ li.note {
cursor: pointer; cursor: pointer;
} }
.merge-request,
.issue {
&.today{
background: #EFE;
border-color: #CEC;
}
&.closed {
background: #F5f5f5;
border-color: #E5E5E5;
}
&.merged {
background: #F5f5f5;
border-color: #E5E5E5;
}
}
.git_error_tips { .git_error_tips {
@extend .col-md-6; @extend .col-md-6;
text-align: left; text-align: left;
...@@ -358,15 +262,6 @@ li.note { ...@@ -358,15 +262,6 @@ li.note {
} }
} }
pre {
&.clean {
background: none;
border: none;
margin: 0;
padding: 0;
}
}
.milestone { .milestone {
&.milestone-closed { &.milestone-closed {
background: #eee; background: #eee;
...@@ -403,10 +298,6 @@ img.emoji { ...@@ -403,10 +298,6 @@ img.emoji {
width: 20px; width: 20px;
} }
.appear-data {
display: none;
}
.chart { .chart {
overflow: hidden; overflow: hidden;
height: 220px; height: 220px;
...@@ -459,40 +350,6 @@ table { ...@@ -459,40 +350,6 @@ table {
margin-bottom: 20px; margin-bottom: 20px;
} }
.ajax-users-select {
width: 400px;
&.input-large {
width: 210px;
}
&.input-clamp {
max-width: 100%;
}
}
.user-result {
.user-image {
float: left;
}
.user-name {
}
.user-username {
color: #999;
}
}
.namespace-result {
.namespace-kind {
color: #AAA;
font-weight: normal;
}
.namespace-path {
margin-left: 10px;
font-weight: bolder;
}
}
.btn-sign-in { .btn-sign-in {
margin-top: 7px; margin-top: 7px;
text-shadow: none; text-shadow: none;
......
.flash-container {
display: none;
cursor: pointer;
margin: 0;
text-align: center;
color: #fff;
font-size: 14px;
position: fixed;
bottom: 0;
width: 100%;
opacity: 0.8;
z-index: 100;
.flash-notice {
background: #49C;
padding: 10px;
text-shadow: 0 1px 1px #178;
}
.flash-alert {
background: #C67;
text-shadow: 0 1px 1px #945;
padding: 10px;
}
}
input[type='search'].search-text-input { input[type='search'].search-text-input {
background-image: url("icon-search.png"); background-image: image-url("icon-search.png");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: 10px; background-position: 10px;
padding-left: 25px; padding-left: 25px;
......
...@@ -10,9 +10,61 @@ ...@@ -10,9 +10,61 @@
.issue-box { .issue-box {
color: #666; color: #666;
margin:20px 0; margin:20px 0;
background: #FAFAFA; background: #FFF;
border: 1px solid #EEE; border: 1px solid #EEE;
&.issue-box-closed {
border-color: #DA4E49;
.state {
background-color: #f2dede;
border-color: #ebccd1;
color: #a94442;
.state-label {
background: #DA4E49;
color: #FFF;
}
}
}
&.issue-box-merged {
border-color: #31708f;
.state {
background-color: #d9edf7;
border-color: #bce8f1;
color: #31708f;
.state-label {
background: #31708f;
color: #FFF;
}
}
}
&.issue-box-open {
border-color: #4A4;
.state {
background-color: #dff0d8;
border-color: #d6e9c6;
color: #3c763d;
.state-label {
background: #4A4;
color: #FFF;
}
}
}
&.issue-box-expired {
border-color: #cea61b;
.state {
background-color: #fcf8e3;
border-color: #faebcc;
color: #8a6d3b;
.state-label {
background: #cea61b;
color: #FFF;
}
}
}
.control-group { .control-group {
margin-bottom: 0; margin-bottom: 0;
} }
...@@ -48,4 +100,13 @@ ...@@ -48,4 +100,13 @@
margin: 0; margin: 0;
} }
} }
.state-label {
font-size: 14px;
padding: 1px 25px;
text-align: center;
text-shadow: none;
margin-right: 20px;
display: inline-block;
}
} }
...@@ -87,3 +87,37 @@ select { ...@@ -87,3 +87,37 @@ select {
padding-top: 4px; padding-top: 4px;
} }
} }
.ajax-users-select {
width: 400px;
&.input-large {
width: 210px;
}
&.input-clamp {
max-width: 100%;
}
}
.user-result {
.user-image {
float: left;
}
.user-name {
}
.user-username {
color: #999;
}
}
.namespace-result {
.namespace-kind {
color: #AAA;
font-weight: normal;
}
.namespace-path {
margin-left: 10px;
font-weight: bolder;
}
}
...@@ -105,7 +105,7 @@ a:focus { ...@@ -105,7 +105,7 @@ a:focus {
display: inline-block; display: inline-block;
width: $size; width: $size;
height: $size; height: $size;
background-image: url("icon-link.png"); background-image: image-url("icon-link.png");
background-size: contain; background-size: contain;
background-repeat: no-repeat; background-repeat: no-repeat;
} }
......
...@@ -178,3 +178,9 @@ ...@@ -178,3 +178,9 @@
.shadow { .shadow {
@include box-shadow(0 5px 15px #000); @include box-shadow(0 5px 15px #000);
} }
.wiki, .note-body {
.highlight {
border: 1px solid #DDD;
}
}
...@@ -5,9 +5,7 @@ html { ...@@ -5,9 +5,7 @@ html {
} }
body { body {
-webkit-font-smoothing: antialiased; padding-bottom: 20px;
-moz-osx-font-smoothing: grayscale;
margin-bottom: 20px;
} }
.container { .container {
......
...@@ -10,332 +10,6 @@ ...@@ -10,332 +10,6 @@
} }
} }
.file {
border: 1px solid #CCC;
margin-bottom: 1em;
.header {
@extend .clearfix;
background: #DDD;
border-bottom: 1px solid #CCC;
padding: 5px 5px 5px 10px;
color: #555;
> span {
font-family: $monospace_font;
font-size: 14px;
line-height: 2;
}
.view-file {
font-weight: bold;
float: right;
background-color: #EEE;
}
.commit-short-id {
font-family: $monospace_font;
font-size: smaller;
}
.file-mode {
font-family: $monospace_font;
}
}
.content {
overflow: auto;
overflow-y: hidden;
background: #FFF;
color: #333;
font-size: 12px;
.old {
span.idiff {
background-color: #FAA;
}
}
.new {
span.idiff {
background-color: #AFA;
}
}
table {
width: 100%;
font-family: $monospace_font;
border: none;
margin: 0px;
padding: 0px;
td {
line-height: 18px;
font-size: 12px;
}
}
.old_line, .new_line, .diff_line {
margin: 0px;
padding: 0px;
border: none;
background: #EEE;
color: #666;
padding: 0px 5px;
border-right: 1px solid #ccc;
text-align: right;
min-width: 35px;
max-width: 50px;
width: 35px;
@include user-select(none);
a {
float: left;
width: 35px;
font-weight: normal;
color: #666;
&:hover {
text-decoration: underline;
}
}
&.new {
background: #CFD;
}
&.old {
background: #FDD;
}
}
.diff_line {
padding: 0;
}
.line_holder {
&.old .old_line,
&.old .new_line {
background: #FCC;
border-color: #E7BABA;
}
&.new .old_line,
&.new .new_line {
background: #CFC;
border-color: #B9ECB9;
}
}
.line_content {
display: block;
white-space: pre;
height: 18px;
margin: 0px;
padding: 0px 0.5em;
border: none;
&.new {
background: #CFD;
}
&.old {
background: #FDD;
}
&.matched {
color: #ccc;
background: #fafafa;
}
&.parallel {
display: table-cell;
overflow: hidden;
width: 50%;
}
}
}
.image {
background: #ddd;
text-align: center;
padding: 30px;
.wrap{
display: inline-block;
}
.frame {
display: inline-block;
background-color: #fff;
line-height: 0;
img{
border: 1px solid #FFF;
background: url('trans_bg.gif');
max-width: 100%;
}
&.deleted {
border: 1px solid $deleted;
}
&.added {
border: 1px solid $added;
}
}
.image-info{
font-size: 12px;
margin: 5px 0 0 0;
color: grey;
}
.view.swipe{
position: relative;
.swipe-frame{
display: block;
margin: auto;
position: relative;
}
.swipe-wrap{
overflow: hidden;
border-left: 1px solid #999;
position: absolute;
display: block;
top: 13px;
right: 7px;
}
.frame{
top: 0;
right: 0;
position: absolute;
&.deleted{
margin: 0;
display: block;
top: 13px;
right: 7px;
}
}
.swipe-bar{
display: block;
height: 100%;
width: 15px;
z-index: 100;
position: absolute;
cursor: pointer;
&:hover{
.top-handle{
background-position: -15px 3px;
}
.bottom-handle{
background-position: -15px -11px;
}
};
.top-handle{
display: block;
height: 14px;
width: 15px;
position: absolute;
top: 0px;
background: url('swipemode_sprites.gif') 0 3px no-repeat;
}
.bottom-handle{
display: block;
height: 14px;
width: 15px;
position: absolute;
bottom: 0px;
background: url('swipemode_sprites.gif') 0 -11px no-repeat;
}
}
} //.view.swipe
.view.onion-skin{
.onion-skin-frame{
display: block;
margin: auto;
position: relative;
}
.frame.added, .frame.deleted {
position: absolute;
display: block;
top: 0px;
left: 0px;
}
.controls{
display: block;
height: 14px;
width: 300px;
z-index: 100;
position: absolute;
bottom: 0px;
left: 50%;
margin-left: -150px;
.drag-track{
display: block;
position: absolute;
left: 12px;
height: 10px;
width: 276px;
background: url('onion_skin_sprites.gif') -4px -20px repeat-x;
}
.dragger {
display: block;
position: absolute;
left: 0px;
top: 0px;
height: 14px;
width: 14px;
background: url('onion_skin_sprites.gif') 0px -34px repeat-x;
cursor: pointer;
}
.transparent {
display: block;
position: absolute;
top: 2px;
right: 0px;
height: 10px;
width: 10px;
background: url('onion_skin_sprites.gif') -2px 0px no-repeat;
}
.opaque {
display: block;
position: absolute;
top: 2px;
left: 0px;
height: 10px;
width: 10px;
background: url('onion_skin_sprites.gif') -2px -10px no-repeat;
}
}
} //.view.onion-skin
}
.view-modes{
padding: 10px;
text-align: center;
background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -ms-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
ul, li{
list-style: none;
margin: 0;
padding: 0;
display: inline-block;
}
li{
color: grey;
border-left: 1px solid #c1c1c1;
padding: 0 12px 0 16px;
cursor: pointer;
&:first-child{
border-left: none;
}
&:hover{
text-decoration: underline;
}
&.active{
&:hover{
text-decoration: none;
}
cursor: default;
color: #333;
}
&.disabled{
display: none;
}
}
}
}
/** COMMIT BLOCK **/ /** COMMIT BLOCK **/
.commit-title{ .commit-title{
display: block; display: block;
...@@ -399,7 +73,7 @@ ...@@ -399,7 +73,7 @@
.commits-compare-switch{ .commits-compare-switch{
background: url("switch_icon.png") no-repeat center center; background: image-url("switch_icon.png") no-repeat center center;
width: 32px; width: 32px;
height: 32px; height: 32px;
text-indent: -9999px; text-indent: -9999px;
...@@ -494,6 +168,25 @@ li.commit { ...@@ -494,6 +168,25 @@ li.commit {
text-decoration: underline; text-decoration: underline;
} }
} }
.text-expander {
background: #eee;
color: #555;
padding: 0 5px;
cursor: pointer;
margin-left: 4px;
&:hover {
background-color: #ddd;
}
}
}
.commit-row-description {
font-size: 14px;
border-left: 1px solid #e5e5e5;
padding: 0 15px 0 7px;
margin: 5px 0 10px 5px;
display: none;
} }
.commit-row-info { .commit-row-info {
......
.diff-file {
border: 1px solid #CCC;
margin-bottom: 1em;
.diff-header {
@extend .clearfix;
background: #DDD;
border-bottom: 1px solid #CCC;
padding: 5px 5px 5px 10px;
color: #555;
> span {
font-family: $monospace_font;
font-size: 14px;
line-height: 2;
}
.diff-btn-group {
float: right;
.btn {
background-color: #EEE;
color: #666;
font-weight: bolder;
}
}
.commit-short-id {
font-family: $monospace_font;
font-size: smaller;
}
.file-mode {
font-family: $monospace_font;
}
}
.diff-content {
overflow: auto;
overflow-y: hidden;
background: #FFF;
color: #333;
font-size: 12px;
.old {
span.idiff {
background-color: #FAA;
}
}
.new {
span.idiff {
background-color: #AFA;
}
}
table {
width: 100%;
font-family: $monospace_font;
border: none;
margin: 0px;
padding: 0px;
td {
line-height: 18px;
font-size: 12px;
}
}
.old_line, .new_line, .diff_line {
margin: 0px;
padding: 0px;
border: none;
background: #EEE;
color: #666;
padding: 0px 5px;
border-right: 1px solid #ccc;
text-align: right;
min-width: 35px;
max-width: 50px;
width: 35px;
@include user-select(none);
a {
float: left;
width: 35px;
font-weight: normal;
color: #666;
&:hover {
text-decoration: underline;
}
}
&.new {
background: #CFD;
}
&.old {
background: #FDD;
}
}
.diff_line {
padding: 0;
}
.line_holder {
&.old .old_line,
&.old .new_line {
background: #FCC;
border-color: #E7BABA;
}
&.new .old_line,
&.new .new_line {
background: #CFC;
border-color: #B9ECB9;
}
}
.line_content {
display: block;
white-space: pre;
height: 18px;
margin: 0px;
padding: 0px 0.5em;
border: none;
&.new {
background: #CFD;
}
&.old {
background: #FDD;
}
&.matched {
color: #ccc;
background: #fafafa;
}
&.parallel {
display: table-cell;
overflow: hidden;
width: 50%;
}
}
}
.image {
background: #ddd;
text-align: center;
padding: 30px;
.wrap{
display: inline-block;
}
.frame {
display: inline-block;
background-color: #fff;
line-height: 0;
img{
border: 1px solid #FFF;
background: image-url('trans_bg.gif');
max-width: 100%;
}
&.deleted {
border: 1px solid $deleted;
}
&.added {
border: 1px solid $added;
}
}
.image-info{
font-size: 12px;
margin: 5px 0 0 0;
color: grey;
}
.view.swipe{
position: relative;
.swipe-frame{
display: block;
margin: auto;
position: relative;
}
.swipe-wrap{
overflow: hidden;
border-left: 1px solid #999;
position: absolute;
display: block;
top: 13px;
right: 7px;
}
.frame{
top: 0;
right: 0;
position: absolute;
&.deleted{
margin: 0;
display: block;
top: 13px;
right: 7px;
}
}
.swipe-bar{
display: block;
height: 100%;
width: 15px;
z-index: 100;
position: absolute;
cursor: pointer;
&:hover{
.top-handle{
background-position: -15px 3px;
}
.bottom-handle{
background-position: -15px -11px;
}
};
.top-handle{
display: block;
height: 14px;
width: 15px;
position: absolute;
top: 0px;
background: image-url('swipemode_sprites.gif') 0 3px no-repeat;
}
.bottom-handle{
display: block;
height: 14px;
width: 15px;
position: absolute;
bottom: 0px;
background: image-url('swipemode_sprites.gif') 0 -11px no-repeat;
}
}
} //.view.swipe
.view.onion-skin{
.onion-skin-frame{
display: block;
margin: auto;
position: relative;
}
.frame.added, .frame.deleted {
position: absolute;
display: block;
top: 0px;
left: 0px;
}
.controls{
display: block;
height: 14px;
width: 300px;
z-index: 100;
position: absolute;
bottom: 0px;
left: 50%;
margin-left: -150px;
.drag-track{
display: block;
position: absolute;
left: 12px;
height: 10px;
width: 276px;
background: image-url('onion_skin_sprites.gif') -4px -20px repeat-x;
}
.dragger {
display: block;
position: absolute;
left: 0px;
top: 0px;
height: 14px;
width: 14px;
background: image-url('onion_skin_sprites.gif') 0px -34px repeat-x;
cursor: pointer;
}
.transparent {
display: block;
position: absolute;
top: 2px;
right: 0px;
height: 10px;
width: 10px;
background: image-url('onion_skin_sprites.gif') -2px 0px no-repeat;
}
.opaque {
display: block;
position: absolute;
top: 2px;
left: 0px;
height: 10px;
width: 10px;
background: image-url('onion_skin_sprites.gif') -2px -10px no-repeat;
}
}
} //.view.onion-skin
}
.view-modes{
padding: 10px;
text-align: center;
background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -ms-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
ul, li{
list-style: none;
margin: 0;
padding: 0;
display: inline-block;
}
li{
color: grey;
border-left: 1px solid #c1c1c1;
padding: 0 12px 0 16px;
cursor: pointer;
&:first-child{
border-left: none;
}
&:hover{
text-decoration: underline;
}
&.active{
&:hover{
text-decoration: none;
}
cursor: default;
color: #333;
}
&.disabled{
display: none;
}
}
}
}
...@@ -19,7 +19,7 @@ header { ...@@ -19,7 +19,7 @@ header {
line-height: 32px; line-height: 32px;
padding: 6px 10px; padding: 6px 10px;
&:hover { &:hover, &:focus, &:active {
background: none; background: none;
} }
} }
...@@ -161,7 +161,7 @@ header { ...@@ -161,7 +161,7 @@ header {
} }
.search-input { .search-input {
background-image: url("icon-search.png"); background-image: image-url("icon-search.png");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: 10px; background-position: 10px;
height: inherit; height: inherit;
...@@ -192,7 +192,8 @@ header { ...@@ -192,7 +192,8 @@ header {
color: #AAA; color: #AAA;
text-shadow: 0 1px 0 #444; text-shadow: 0 1px 0 #444;
&:hover { &:hover, &:focus, &:active {
background: none;
color: #FFF; color: #FFF;
} }
} }
......
...@@ -125,3 +125,21 @@ ...@@ -125,3 +125,21 @@
form.edit-issue { form.edit-issue {
margin: 0; margin: 0;
} }
.merge-request,
.issue {
&.today {
background: #EFE;
border-color: #CEC;
}
&.closed {
background: #F5f5f5;
border-color: #E5E5E5;
}
&.merged {
background: #F5f5f5;
border-color: #E5E5E5;
}
}
...@@ -89,3 +89,9 @@ ...@@ -89,3 +89,9 @@
.merge-request-form-info { .merge-request-form-info {
padding-top: 15px; padding-top: 15px;
} }
// hide mr close link for inline diff comment form
.diff-file .close-mr-link,
.diff-file .reopen-mr-link {
display: none;
}
...@@ -47,13 +47,13 @@ ul.notes { ...@@ -47,13 +47,13 @@ ul.notes {
.discussion-body { .discussion-body {
margin-left: 50px; margin-left: 50px;
.file, .diff-file,
.discussion-hidden, .discussion-hidden,
.notes { .notes {
@extend .borders; @extend .borders;
background-color: #F9F9F9; background-color: #F9F9F9;
} }
.file .notes { .diff-file .notes {
/* reset */ /* reset */
background: inherit; background: inherit;
border: none; border: none;
...@@ -87,7 +87,6 @@ ul.notes { ...@@ -87,7 +87,6 @@ ul.notes {
} }
.attachment { .attachment {
font-size: 14px; font-size: 14px;
margin-top: -20px;
} }
.note-body { .note-body {
@include md-typography; @include md-typography;
...@@ -114,7 +113,7 @@ ul.notes { ...@@ -114,7 +113,7 @@ ul.notes {
} }
} }
.file .notes_holder { .diff-file .notes_holder {
font-size: 13px; font-size: 13px;
line-height: 18px; line-height: 18px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
...@@ -184,20 +183,19 @@ ul.notes { ...@@ -184,20 +183,19 @@ ul.notes {
} }
} }
} }
.file .note .note-actions { .diff-file .note .note-actions {
right: 0; right: 0;
top: 0; top: 0;
} }
/** /**
* Line note button on the side of diffs * Line note button on the side of diffs
*/ */
.file tr.line_holder { .diff-file tr.line_holder {
.add-diff-note { .add-diff-note {
background: url("diff_note_add.png") no-repeat left 0; background: image-url("diff_note_add.png") no-repeat left 0;
height: 22px; height: 22px;
margin-left: -65px; margin-left: -65px;
position: absolute; position: absolute;
...@@ -235,22 +233,25 @@ ul.notes { ...@@ -235,22 +233,25 @@ ul.notes {
.reply-btn { .reply-btn {
@extend .btn-primary; @extend .btn-primary;
} }
.file .content tr.line_holder:hover { .diff-file .diff-content {
&> td.line_content { tr.line_holder:hover {
background: $hover !important; &> td.line_content {
border-color: darken($hover, 10%) !important; background: $hover !important;
border-color: darken($hover, 10%) !important;
}
&> td.new_line,
&> td.old_line {
background: darken($hover, 4%) !important;
border-color: darken($hover, 10%) !important;
}
} }
&> td.new_line,
&> td.old_line { tr.line_holder:hover > td .line_note_link {
background: darken($hover, 4%) !important; opacity: 1.0;
border-color: darken($hover, 10%) !important; filter: alpha(opacity=100);
} }
} }
.file .content tr.line_holder:hover > td .line_note_link { .diff-file,
opacity: 1.0;
filter: alpha(opacity=100);
}
.file,
.discussion { .discussion {
.new_note { .new_note {
margin: 0; margin: 0;
...@@ -302,6 +303,7 @@ ul.notes { ...@@ -302,6 +303,7 @@ ul.notes {
@extend .col-md-4; @extend .col-md-4;
@extend .thumbnail; @extend .thumbnail;
margin-left: 45px; margin-left: 45px;
float: none;
} }
......
class Admin::BackgroundJobsController < Admin::ApplicationController class Admin::BackgroundJobsController < Admin::ApplicationController
def show def show
@sidekiq_processes = `ps -U #{Settings.gitlab.user} -o euser,pid,pcpu,pmem,stat,start,command | grep sidekiq | grep -v grep` ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Settings.gitlab.user} -o pid,pcpu,pmem,stat,start,command))
@sidekiq_processes = ps_output.split("\n").grep(/sidekiq/)
end end
end end
...@@ -68,7 +68,9 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -68,7 +68,9 @@ class Admin::UsersController < Admin::ApplicationController
params[:user].delete(:password_confirmation) params[:user].delete(:password_confirmation)
end end
user.admin = (admin && admin.to_i > 0) if admin.present?
user.admin = !admin.to_i.zero?
end
respond_to do |format| respond_to do |format|
if user.update_attributes(params[:user], as: :admin) if user.update_attributes(params[:user], as: :admin)
......
...@@ -155,7 +155,6 @@ class ApplicationController < ActionController::Base ...@@ -155,7 +155,6 @@ class ApplicationController < ActionController::Base
end end
def dev_tools def dev_tools
Rack::MiniProfiler.authorize_request
end end
def default_headers def default_headers
...@@ -182,7 +181,7 @@ class ApplicationController < ActionController::Base ...@@ -182,7 +181,7 @@ class ApplicationController < ActionController::Base
end end
def ldap_security_check def ldap_security_check
if current_user && current_user.ldap_user? && current_user.requires_ldap_check? if current_user && current_user.requires_ldap_check?
gitlab_ldap_access do |access| gitlab_ldap_access do |access|
if access.allowed?(current_user) if access.allowed?(current_user)
access.update_permissions(current_user) access.update_permissions(current_user)
...@@ -232,4 +231,8 @@ class ApplicationController < ActionController::Base ...@@ -232,4 +231,8 @@ class ApplicationController < ActionController::Base
devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:username, :email, :password, :login, :remember_me) } devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:username, :email, :password, :login, :remember_me) }
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :email, :name, :password, :password_confirmation) } devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :email, :name, :password, :password_confirmation) }
end end
def hexdigest(string)
Digest::SHA1.hexdigest string
end
end end
...@@ -22,6 +22,8 @@ class DashboardController < ApplicationController ...@@ -22,6 +22,8 @@ class DashboardController < ApplicationController
@last_push = current_user.recent_push @last_push = current_user.recent_push
@publicish_project_count = Project.publicish(current_user).count
respond_to do |format| respond_to do |format|
format.html format.html
format.json { pager_json("events/_events", @events.count) } format.json { pager_json("events/_events", @events.count) }
...@@ -53,14 +55,15 @@ class DashboardController < ApplicationController ...@@ -53,14 +55,15 @@ class DashboardController < ApplicationController
end end
def merge_requests def merge_requests
@merge_requests = FilteringService.new.execute(MergeRequest, current_user, params) @merge_requests = MergeRequestsFinder.new.execute(current_user, params)
@merge_requests = @merge_requests.page(params[:page]).per(20) @merge_requests = @merge_requests.page(params[:page]).per(20)
@merge_requests = @merge_requests.preload(:author, :target_project)
end end
def issues def issues
@issues = FilteringService.new.execute(Issue, current_user, params) @issues = IssuesFinder.new.execute(current_user, params)
@issues = @issues.page(params[:page]).per(20) @issues = @issues.page(params[:page]).per(20)
@issues = @issues.includes(:author, :project) @issues = @issues.preload(:author, :project)
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -77,5 +80,6 @@ class DashboardController < ApplicationController ...@@ -77,5 +80,6 @@ class DashboardController < ApplicationController
def default_filter def default_filter
params[:scope] = 'assigned-to-me' if params[:scope].blank? params[:scope] = 'assigned-to-me' if params[:scope].blank?
params[:state] = 'opened' if params[:state].blank? params[:state] = 'opened' if params[:state].blank?
params[:authorized_only] = true
end end
end end
class GroupsController < ApplicationController class GroupsController < ApplicationController
skip_before_filter :authenticate_user!, only: [:show, :issues, :members, :merge_requests]
respond_to :html respond_to :html
before_filter :group, except: [:new, :create] before_filter :group, except: [:new, :create]
...@@ -36,7 +37,7 @@ class GroupsController < ApplicationController ...@@ -36,7 +37,7 @@ class GroupsController < ApplicationController
@events = Event.in_projects(project_ids) @events = Event.in_projects(project_ids)
@events = event_filter.apply_filter(@events) @events = event_filter.apply_filter(@events)
@events = @events.limit(20).offset(params[:offset] || 0) @events = @events.limit(20).offset(params[:offset] || 0)
@last_push = current_user.recent_push @last_push = current_user.recent_push if current_user
@shared_projects = @group.shared_projects @shared_projects = @group.shared_projects
...@@ -48,14 +49,15 @@ class GroupsController < ApplicationController ...@@ -48,14 +49,15 @@ class GroupsController < ApplicationController
end end
def merge_requests def merge_requests
@merge_requests = FilteringService.new.execute(MergeRequest, current_user, params) @merge_requests = MergeRequestsFinder.new.execute(current_user, params)
@merge_requests = @merge_requests.page(params[:page]).per(20) @merge_requests = @merge_requests.page(params[:page]).per(20)
@merge_requests = @merge_requests.preload(:author, :target_project)
end end
def issues def issues
@issues = FilteringService.new.execute(Issue, current_user, params) @issues = IssuesFinder.new.execute(current_user, params)
@issues = @issues.page(params[:page]).per(20) @issues = @issues.page(params[:page]).per(20)
@issues = @issues.includes(:author, :project) @issues = @issues.preload(:author, :project)
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -100,17 +102,21 @@ class GroupsController < ApplicationController ...@@ -100,17 +102,21 @@ class GroupsController < ApplicationController
end end
def projects def projects
@projects ||= current_user.authorized_projects.where(namespace_id: group.id).sorted_by_activity @projects ||= ProjectsFinder.new.execute(current_user, group: group).sorted_by_activity.non_archived
end end
def project_ids def project_ids
projects.map(&:id) projects.pluck(:id)
end end
# Dont allow unauthorized access to group # Dont allow unauthorized access to group
def authorize_read_group! def authorize_read_group!
unless @group and (projects.present? or can?(current_user, :read_group, @group)) unless @group and (projects.present? or can?(current_user, :read_group, @group))
return render_404 if current_user.nil?
return authenticate_user!
else
return render_404
end
end end
end end
...@@ -133,13 +139,21 @@ class GroupsController < ApplicationController ...@@ -133,13 +139,21 @@ class GroupsController < ApplicationController
def determine_layout def determine_layout
if [:new, :create].include?(action_name.to_sym) if [:new, :create].include?(action_name.to_sym)
'navless' 'navless'
else elsif current_user
'group' 'group'
else
'public_group'
end end
end end
def default_filter def default_filter
params[:scope] = 'assigned-to-me' if params[:scope].blank? if params[:scope].blank?
if current_user
params[:scope] = 'assigned-to-me'
else
params[:scope] = 'all'
end
end
params[:state] = 'opened' if params[:state].blank? params[:state] = 'opened' if params[:state].blank?
params[:group_id] = @group.id params[:group_id] = @group.id
end end
......
...@@ -2,6 +2,8 @@ class Projects::EditTreeController < Projects::BaseTreeController ...@@ -2,6 +2,8 @@ class Projects::EditTreeController < Projects::BaseTreeController
before_filter :require_branch_head before_filter :require_branch_head
before_filter :blob before_filter :blob
before_filter :authorize_push! before_filter :authorize_push!
before_filter :from_merge_request
before_filter :after_edit_path
def show def show
@last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha @last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
...@@ -12,7 +14,12 @@ class Projects::EditTreeController < Projects::BaseTreeController ...@@ -12,7 +14,12 @@ class Projects::EditTreeController < Projects::BaseTreeController
if result[:status] == :success if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed" flash[:notice] = "Your changes have been successfully committed"
redirect_to project_blob_path(@project, @id)
if from_merge_request
from_merge_request.reload_code
end
redirect_to after_edit_path
else else
flash[:alert] = result[:error] flash[:alert] = result[:error]
render :show render :show
...@@ -24,4 +31,19 @@ class Projects::EditTreeController < Projects::BaseTreeController ...@@ -24,4 +31,19 @@ class Projects::EditTreeController < Projects::BaseTreeController
def blob def blob
@blob ||= @repository.blob_at(@commit.id, @path) @blob ||= @repository.blob_at(@commit.id, @path)
end end
def after_edit_path
@after_edit_path ||=
if from_merge_request
diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) +
"#file-path-#{hexdigest(@path)}"
else
project_blob_path(@project, @id)
end
end
def from_merge_request
# If blob edit was initiated from merge request page
@from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id])
end
end end
...@@ -121,7 +121,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -121,7 +121,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issues_filtered def issues_filtered
params[:scope] = 'all' if params[:scope].blank? params[:scope] = 'all' if params[:scope].blank?
params[:state] = 'opened' if params[:state].blank? params[:state] = 'opened' if params[:state].blank?
@issues = FilteringService.new.execute(Issue, current_user, params.merge(project_id: @project.id)) @issues = IssuesFinder.new.execute(current_user, params.merge(project_id: @project.id))
end end
# Since iids are implemented only in 6.1 # Since iids are implemented only in 6.1
......
...@@ -13,7 +13,7 @@ class Projects::LabelsController < Projects::ApplicationController ...@@ -13,7 +13,7 @@ class Projects::LabelsController < Projects::ApplicationController
def generate def generate
Gitlab::IssuesLabels.generate(@project) Gitlab::IssuesLabels.generate(@project)
redirect_to project_labels_path(@project) redirect_to project_issues_path(@project)
end end
protected protected
......
...@@ -21,7 +21,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -21,7 +21,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
params[:scope] = 'all' if params[:scope].blank? params[:scope] = 'all' if params[:scope].blank?
params[:state] = 'opened' if params[:state].blank? params[:state] = 'opened' if params[:state].blank?
@merge_requests = FilteringService.new.execute(MergeRequest, current_user, params.merge(project_id: @project.id)) @merge_requests = MergeRequestsFinder.new.execute(current_user, params.merge(project_id: @project.id))
@merge_requests = @merge_requests.page(params[:page]).per(20) @merge_requests = @merge_requests.page(params[:page]).per(20)
@sort = params[:sort].humanize @sort = params[:sort].humanize
...@@ -60,7 +60,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -60,7 +60,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def new def new
@merge_request = MergeRequest.new(params[:merge_request]) @merge_request = MergeRequest.new(params[:merge_request])
@merge_request.source_project = @project unless @merge_request.source_project @merge_request.source_project = @project unless @merge_request.source_project
@merge_request.target_project = @project unless @merge_request.target_project @merge_request.target_project ||= (@project.forked_from_project || @project)
@target_branches = @merge_request.target_project.nil? ? [] : @merge_request.target_project.repository.branch_names
@merge_request.target_branch ||= @merge_request.target_project.default_branch
@source_project = @merge_request.source_project @source_project = @merge_request.source_project
@merge_request @merge_request
end end
...@@ -131,7 +135,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -131,7 +135,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def automerge def automerge
return access_denied! unless allowed_to_merge? return access_denied! unless allowed_to_merge?
if @merge_request.opened? && @merge_request.can_be_merged? if @merge_request.open? && @merge_request.can_be_merged?
@merge_request.should_remove_source_branch = params[:should_remove_source_branch] @merge_request.should_remove_source_branch = params[:should_remove_source_branch]
@merge_request.automerge!(current_user, params[:merge_commit_message]) @merge_request.automerge!(current_user, params[:merge_commit_message])
@status = true @status = true
...@@ -162,7 +166,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -162,7 +166,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def ci_status def ci_status
status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha) status = @merge_request.source_project.gitlab_ci_service.commit_status(merge_request.last_commit.sha)
response = {status: status} response = {status: status}
render json: response render json: response
...@@ -226,7 +230,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -226,7 +230,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request_diff = @merge_request.merge_request_diff @merge_request_diff = @merge_request.merge_request_diff
@allowed_to_merge = allowed_to_merge? @allowed_to_merge = allowed_to_merge?
@show_merge_controls = @merge_request.opened? && @commits.any? && @allowed_to_merge @show_merge_controls = @merge_request.open? && @commits.any? && @allowed_to_merge
end end
def allowed_to_merge? def allowed_to_merge?
......
...@@ -5,7 +5,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -5,7 +5,7 @@ class Projects::NotesController < Projects::ApplicationController
before_filter :authorize_admin_note!, only: [:update, :destroy] before_filter :authorize_admin_note!, only: [:update, :destroy]
def index def index
@notes = Notes::LoadService.new(project, current_user, params).execute @notes = NotesFinder.new.execute(project, current_user, params)
notes_json = { notes: [] } notes_json = { notes: [] }
......
...@@ -5,7 +5,7 @@ class ProjectsController < ApplicationController ...@@ -5,7 +5,7 @@ class ProjectsController < ApplicationController
# Authorize # Authorize
before_filter :authorize_read_project!, except: [:index, :new, :create] before_filter :authorize_read_project!, except: [:index, :new, :create]
before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive] before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :retry_import]
before_filter :require_non_empty_project, only: [:blob, :tree, :graph] before_filter :require_non_empty_project, only: [:blob, :tree, :graph]
layout 'navless', only: [:new, :create, :fork] layout 'navless', only: [:new, :create, :fork]
...@@ -21,16 +21,9 @@ class ProjectsController < ApplicationController ...@@ -21,16 +21,9 @@ class ProjectsController < ApplicationController
def create def create
@project = ::Projects::CreateService.new(current_user, params[:project]).execute @project = ::Projects::CreateService.new(current_user, params[:project]).execute
flash[:notice] = 'Project was successfully created.' if @project.saved?
respond_to do |format| respond_to do |format|
flash[:notice] = 'Project was successfully created.' if @project.saved?
format.html do
if @project.saved?
redirect_to @project
else
render "new"
end
end
format.js format.js
end end
end end
...@@ -55,6 +48,11 @@ class ProjectsController < ApplicationController ...@@ -55,6 +48,11 @@ class ProjectsController < ApplicationController
end end
def show def show
if @project.import_in_progress?
redirect_to import_project_path(@project)
return
end
return authenticate_user! unless @project.public? || current_user return authenticate_user! unless @project.public? || current_user
limit = (params[:limit] || 20).to_i limit = (params[:limit] || 20).to_i
...@@ -67,9 +65,7 @@ class ProjectsController < ApplicationController ...@@ -67,9 +65,7 @@ class ProjectsController < ApplicationController
if @project.empty_repo? if @project.empty_repo?
render "projects/empty", layout: user_layout render "projects/empty", layout: user_layout
else else
if current_user @last_push = current_user.recent_push(@project.id) if current_user
@last_push = current_user.recent_push(@project.id)
end
render :show, layout: user_layout render :show, layout: user_layout
end end
end end
...@@ -77,6 +73,28 @@ class ProjectsController < ApplicationController ...@@ -77,6 +73,28 @@ class ProjectsController < ApplicationController
end end
end end
def import
if project.import_finished?
redirect_to @project
return
end
end
def retry_import
unless @project.import_failed?
redirect_to import_project_path(@project)
end
@project.import_url = params[:project][:import_url]
if @project.save
@project.reload
@project.import_retry
end
redirect_to import_project_path(@project)
end
def destroy def destroy
return access_denied! unless can?(current_user, :remove_project, project) return access_denied! unless can?(current_user, :remove_project, project)
...@@ -108,6 +126,7 @@ class ProjectsController < ApplicationController ...@@ -108,6 +126,7 @@ class ProjectsController < ApplicationController
@suggestions = { @suggestions = {
emojis: Emoji.names, emojis: Emoji.names,
issues: @project.issues.select([:iid, :title, :description]), issues: @project.issues.select([:iid, :title, :description]),
mergerequests: @project.merge_requests.select([:iid, :title, :description]),
members: @project.team.members.sort_by(&:username).map { |user| { username: user.username, name: user.name } } members: @project.team.members.sort_by(&:username).map { |user| { username: user.username, name: user.name } }
} }
......
...@@ -6,7 +6,7 @@ class Public::ProjectsController < ApplicationController ...@@ -6,7 +6,7 @@ class Public::ProjectsController < ApplicationController
layout 'public' layout 'public'
def index def index
@projects = Project.public_or_internal_only(current_user) @projects = Project.publicish(current_user)
@projects = @projects.search(params[:search]) if params[:search].present? @projects = @projects.search(params[:search]) if params[:search].present?
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).page(params[:page]).per(20) @projects = @projects.includes(:namespace).page(params[:page]).per(20)
......
...@@ -7,6 +7,7 @@ class SearchController < ApplicationController ...@@ -7,6 +7,7 @@ class SearchController < ApplicationController
if @project 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 @search_results = Search::ProjectService.new(@project, current_user, params).execute
else else
@search_results = Search::GlobalService.new(current_user, params).execute @search_results = Search::GlobalService.new(current_user, params).execute
......
...@@ -19,6 +19,9 @@ class SnippetsController < ApplicationController ...@@ -19,6 +19,9 @@ class SnippetsController < ApplicationController
def user_index def user_index
@user = User.find_by(username: params[:username]) @user = User.find_by(username: params[:username])
render_404 and return unless @user
@snippets = @user.snippets.fresh.non_expired @snippets = @user.snippets.fresh.non_expired
if @user == current_user if @user == current_user
......
class UsersController < ApplicationController class UsersController < ApplicationController
skip_before_filter :authenticate_user!, only: [:show] skip_before_filter :authenticate_user!, only: [:show]
layout :determine_layout layout :determine_layout
def show def show
@user = User.find_by_username!(params[:username]) @user = User.find_by_username!(params[:username])
@projects = @user.authorized_projects.includes(:namespace).select {|project| can?(current_user, :read_project, project)} @projects = @user.authorized_projects.accessible_to(current_user)
if !current_user && @projects.empty? if !current_user && @projects.empty?
return authenticate_user! return authenticate_user!
end end
@events = @user.recent_events.where(project_id: @projects.map(&:id)).limit(20) @groups = @user.groups.accessible_to(current_user)
@events = @user.recent_events.where(project_id: @projects.pluck(:id)).limit(20)
@title = @user.name @title = @user.name
end end
......
# Finders
This type of classes responsible for collectiong items based on different conditions.
To prevent lookup methods in models like this:
```ruby
class Project
def issues_for_user_filtered_by(user, filter)
# A lot of logic not related to project model itself
end
end
issues = project.issues_for_user_filtered_by(user, params)
```
Better use this:
```ruby
issues = IssuesFinder.new.execute(project, user, filter)
```
It will help keep models thiner
# FilteringService class # BaseFinder
# #
# Used to filter Issues and MergeRequests collections by set of params # Used to filter Issues and MergeRequests collections by set of params
# #
...@@ -16,11 +16,10 @@ ...@@ -16,11 +16,10 @@
# label_name: string # label_name: string
# sort: string # sort: string
# #
class FilteringService class BaseFinder
attr_accessor :klass, :current_user, :params attr_accessor :current_user, :params
def execute(klass, current_user, params) def execute(current_user, params)
@klass = klass
@current_user = current_user @current_user = current_user
@params = params @params = params
...@@ -41,16 +40,16 @@ class FilteringService ...@@ -41,16 +40,16 @@ class FilteringService
def init_collection def init_collection
table_name = klass.table_name table_name = klass.table_name
return klass.of_projects(Project.public_only) unless current_user
if project if project
if current_user.can?(:read_project, project) if project.public? || (current_user && current_user.can?(:read_project, project))
project.send(table_name) project.send(table_name)
else else
[] []
end end
else elsif current_user && params[:authorized_only].presence
klass.of_projects(current_user.authorized_projects) klass.of_projects(current_user.authorized_projects)
else
klass.of_projects(Project.accessible_to(current_user))
end end
end end
......
# Finders::Issues class
#
# Used to filter Issues collections by set of params
#
# Arguments:
# current_user - which user use
# params:
# scope: 'created-by-me' or 'assigned-to-me' or 'all'
# state: 'open' or 'closed' or 'all'
# group_id: integer
# project_id: integer
# milestone_id: integer
# assignee_id: integer
# search: string
# label_name: string
# sort: string
#
class IssuesFinder < BaseFinder
def klass
Issue
end
end
# Finders::MergeRequest class
#
# Used to filter MergeRequests collections by set of params
#
# Arguments:
# current_user - which user use
# params:
# scope: 'created-by-me' or 'assigned-to-me' or 'all'
# state: 'open' or 'closed' or 'all'
# group_id: integer
# project_id: integer
# milestone_id: integer
# assignee_id: integer
# search: string
# label_name: string
# sort: string
#
class MergeRequestsFinder < BaseFinder
def klass
MergeRequest
end
end
class NotesFinder
def execute(project, current_user, params)
target_type = params[:target_type]
target_id = params[:target_id]
case target_type
when "commit"
project.notes.for_commit_id(target_id).not_inline.fresh
when "issue"
project.issues.find(target_id).notes.inc_author.fresh
when "merge_request"
project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh
when "snippet"
project.snippets.find(target_id).notes.fresh
end
end
end
class ProjectsFinder
def execute(current_user, options)
group = options[:group]
if group
group_projects(current_user, group)
else
all_projects(current_user)
end
end
private
def group_projects(current_user, group)
if current_user
if group.users.include?(current_user)
# User is group member
#
# Return ALL group projects
group.projects
else
projects_members = UsersProject.where(
project_id: group.projects,
user_id: current_user
)
if projects_members.any?
# User is a project member
#
# Return only:
# public projects
# internal projects
# joined projects
#
group.projects.where(
"projects.id IN (?) OR projects.visibility_level IN (?)",
projects_members.pluck(:project_id),
Project.public_and_internal_levels
)
else
# User has no access to group or group projects
#
# Return only:
# public projects
# internal projects
#
group.projects.public_and_internal_only
end
end
else
# Not authenticated
#
# Return only:
# public projects
group.projects.public_only
end
end
def all_projects
# TODO: implement
raise 'Not implemented yet'
end
end
...@@ -54,7 +54,7 @@ module ApplicationHelper ...@@ -54,7 +54,7 @@ module ApplicationHelper
if group && group.avatar.present? if group && group.avatar.present?
group.avatar.url group.avatar.url
else else
'/assets/no_group_avatar.png' image_path('no_group_avatar.png')
end end
end end
...@@ -89,16 +89,15 @@ module ApplicationHelper ...@@ -89,16 +89,15 @@ module ApplicationHelper
"Never" "Never"
end end
def grouped_options_refs(destination = :tree) def grouped_options_refs
repository = @project.repository repository = @project.repository
options = [ options = [
["Branches", repository.branch_names], ["Branches", repository.branch_names],
["Tags", repository.tag_names] ["Tags", VersionSorter.rsort(repository.tag_names)]
] ]
# If reference is commit id - # If reference is commit id - we should add it to branch/tag selectbox
# we should add it to branch/tag selectbox
if(@ref && !options.flatten.include?(@ref) && if(@ref && !options.flatten.include?(@ref) &&
@ref =~ /^[0-9a-zA-Z]{6,52}$/) @ref =~ /^[0-9a-zA-Z]{6,52}$/)
options << ["Commit", [@ref]] options << ["Commit", [@ref]]
...@@ -147,8 +146,7 @@ module ApplicationHelper ...@@ -147,8 +146,7 @@ module ApplicationHelper
def authbutton(provider, size = 64) def authbutton(provider, size = 64)
file_name = "#{provider.to_s.split('_').first}_#{size}.png" file_name = "#{provider.to_s.split('_').first}_#{size}.png"
image_tag("authbuttons/#{file_name}", image_tag(image_path("authbuttons/#{file_name}"), alt: "Sign in with #{provider.to_s.titleize}")
alt: "Sign in with #{provider.to_s.titleize}")
end end
def simple_sanitize(str) def simple_sanitize(str)
......
...@@ -35,7 +35,6 @@ module GitlabMarkdownHelper ...@@ -35,7 +35,6 @@ module GitlabMarkdownHelper
# see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch- # see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch-
filter_html: true, filter_html: true,
with_toc_data: true, with_toc_data: true,
hard_wrap: true,
safe_links_only: true safe_links_only: true
}.merge(options)) }.merge(options))
@markdown = Redcarpet::Markdown.new(gitlab_renderer, @markdown = Redcarpet::Markdown.new(gitlab_renderer,
...@@ -45,7 +44,7 @@ module GitlabMarkdownHelper ...@@ -45,7 +44,7 @@ module GitlabMarkdownHelper
fenced_code_blocks: true, fenced_code_blocks: true,
autolink: true, autolink: true,
strikethrough: true, strikethrough: true,
lax_html_blocks: true, lax_spacing: true,
space_after_headers: true, space_after_headers: true,
superscript: true) superscript: true)
end end
...@@ -64,8 +63,7 @@ module GitlabMarkdownHelper ...@@ -64,8 +63,7 @@ module GitlabMarkdownHelper
# project_path_with_namespace - namespace/projectname, eg. gitlabhq/gitlabhq # project_path_with_namespace - namespace/projectname, eg. gitlabhq/gitlabhq
# ref - name of the branch or reference, eg. stable # ref - name of the branch or reference, eg. stable
# requested_path - path of request, eg. doc/api/README.md, used in special case when path is pointing to the .md file were the original request is coming from # requested_path - path of request, eg. doc/api/README.md, used in special case when path is pointing to the .md file were the original request is coming from
# wiki - whether the markdown is from wiki or not def create_relative_links(text, project, ref, requested_path)
def create_relative_links(text, project, ref, requested_path, wiki = false)
@path_to_satellite = project.satellite.path @path_to_satellite = project.satellite.path
project_path_with_namespace = project.path_with_namespace project_path_with_namespace = project.path_with_namespace
paths = extract_paths(text) paths = extract_paths(text)
...@@ -124,21 +122,23 @@ module GitlabMarkdownHelper ...@@ -124,21 +122,23 @@ module GitlabMarkdownHelper
end end
def rebuild_path(path_with_namespace, path, requested_path, ref) def rebuild_path(path_with_namespace, path, requested_path, ref)
path.gsub!(/(#.*)/, "")
id = $1 || ""
file_path = relative_file_path(path, requested_path) file_path = relative_file_path(path, requested_path)
[ [
path_with_namespace, path_with_namespace,
path_with_ref(file_path, ref), path_with_ref(file_path, ref),
file_path file_path
].compact.join("/") ].compact.join("/").gsub(/\/*$/, '') + id
end end
# Checks if the path exists in the repo # Checks if the path exists in the repo
# eg. checks if doc/README.md exists, if it doesn't then it is a wiki link # eg. checks if doc/README.md exists, if not then link to blob
def path_with_ref(path, ref) def path_with_ref(path, ref)
if file_exists?(path) if file_exists?(path)
"#{local_path(path)}/#{correct_ref(ref)}" "#{local_path(path)}/#{correct_ref(ref)}"
else else
"wikis" "blob/#{correct_ref(ref)}"
end end
end end
...@@ -154,6 +154,7 @@ module GitlabMarkdownHelper ...@@ -154,6 +154,7 @@ module GitlabMarkdownHelper
# If we are at doc/api and the README.md shown in below the tree view # If we are at doc/api and the README.md shown in below the tree view
# this takes the rquest path(doc/api) and adds users.md so the path looks like doc/api/users.md # this takes the rquest path(doc/api) and adds users.md so the path looks like doc/api/users.md
def build_nested_path(path, request_path) def build_nested_path(path, request_path)
return request_path if path == ""
return path unless request_path return path unless request_path
if local_path(request_path) == "tree" if local_path(request_path) == "tree"
base = request_path.split("/").push(path) base = request_path.split("/").push(path)
...@@ -166,7 +167,7 @@ module GitlabMarkdownHelper ...@@ -166,7 +167,7 @@ module GitlabMarkdownHelper
end end
def file_exists?(path) def file_exists?(path)
return false if path.nil? || path.empty? return false if path.nil?
return @repository.blob_at(current_sha, path).present? || @repository.tree(current_sha, path).entries.any? return @repository.blob_at(current_sha, path).present? || @repository.tree(current_sha, path).entries.any?
end end
......
...@@ -6,6 +6,14 @@ module GroupsHelper ...@@ -6,6 +6,14 @@ module GroupsHelper
def leave_group_message(group) def leave_group_message(group)
"Are you sure you want to leave \"#{group}\" group?" "Are you sure you want to leave \"#{group}\" group?"
end end
def should_user_see_group_roles?(user, group)
if user
user.is_admin? || group.members.exists?(user_id: user.id)
else
false
end
end
def group_head_title def group_head_title
title = @group.name title = @group.name
......
...@@ -85,11 +85,15 @@ module IssuesHelper ...@@ -85,11 +85,15 @@ module IssuesHelper
options_from_collection_for_select(@project.milestones.active, 'id', 'title', object.milestone_id) options_from_collection_for_select(@project.milestones.active, 'id', 'title', object.milestone_id)
end end
def issue_alert_class(issue) def issue_box_class(item)
if issue.closed? if item.respond_to?(:expired?) && item.expired?
'alert-danger' 'issue-box-expired'
elsif item.respond_to?(:merged?) && item.merged?
'issue-box-merged'
elsif item.closed?
'issue-box-closed'
else else
'alert-success' 'issue-box-open'
end end
end end
end end
module MergeRequestsHelper module MergeRequestsHelper
def new_mr_path_from_push_event(event) def new_mr_path_from_push_event(event)
target_project = event.project.forked_from_project || event.project
new_project_merge_request_path( new_project_merge_request_path(
event.project, event.project,
new_mr_from_push_event(event, event.project) new_mr_from_push_event(event, target_project)
) )
end end
...@@ -41,14 +42,4 @@ module MergeRequestsHelper ...@@ -41,14 +42,4 @@ module MergeRequestsHelper
"Branches: #{@merge_request.source_branch} #{separator} #{@merge_request.target_branch}" "Branches: #{@merge_request.source_branch} #{separator} #{@merge_request.target_branch}"
end end
end end
def merge_request_alert_class(merge_request)
if merge_request.merged?
'alert-info'
elsif merge_request.closed?
'alert-danger'
else
'alert-success'
end
end
end end
...@@ -10,7 +10,7 @@ module ProfileHelper ...@@ -10,7 +10,7 @@ module ProfileHelper
end end
def show_profile_social_tab? def show_profile_social_tab?
Gitlab.config.omniauth.enabled && !current_user.ldap_user? enabled_social_providers.any? && !current_user.ldap_user?
end end
def show_profile_remove_tab? def show_profile_remove_tab?
......
...@@ -4,8 +4,7 @@ module SearchHelper ...@@ -4,8 +4,7 @@ module SearchHelper
resources_results = [ resources_results = [
groups_autocomplete(term), groups_autocomplete(term),
projects_autocomplete(term), projects_autocomplete(term)
public_projects_autocomplete(term),
].flatten ].flatten
generic_results = project_autocomplete + default_autocomplete + help_autocomplete generic_results = project_autocomplete + default_autocomplete + help_autocomplete
...@@ -82,17 +81,7 @@ module SearchHelper ...@@ -82,17 +81,7 @@ 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)
current_user.authorized_projects.search_by_title(term).non_archived.limit(limit).map do |p| Project.accessible_to(current_user).search_by_title(term).non_archived.limit(limit).map do |p|
{
label: "project: #{search_result_sanitize(p.name_with_namespace)}",
url: project_path(p)
}
end
end
# Autocomplete results for the current user's projects
def public_projects_autocomplete(term, limit = 5)
Project.public_or_internal_only(current_user).search_by_title(term).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)
......
...@@ -3,7 +3,7 @@ module Emails ...@@ -3,7 +3,7 @@ module Emails
def group_access_granted_email(user_group_id) def group_access_granted_email(user_group_id)
@membership = UsersGroup.find(user_group_id) @membership = UsersGroup.find(user_group_id)
@group = @membership.group @group = @membership.group
@target_url = group_url(@group)
mail(to: @membership.user.email, mail(to: @membership.user.email,
subject: subject("Access to group was granted")) subject: subject("Access to group was granted"))
end end
......
...@@ -3,22 +3,30 @@ module Emails ...@@ -3,22 +3,30 @@ module Emails
def new_issue_email(recipient_id, issue_id) def new_issue_email(recipient_id, issue_id)
@issue = Issue.find(issue_id) @issue = Issue.find(issue_id)
@project = @issue.project @project = @issue.project
mail(to: recipient(recipient_id), subject: subject("New issue ##{@issue.iid}", @issue.title)) @target_url = project_issue_url(@project, @issue)
mail(from: sender(@issue.author_id),
to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})"))
end end
def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id) def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id)
@issue = Issue.find(issue_id) @issue = Issue.find(issue_id)
@previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
@project = @issue.project @project = @issue.project
mail(to: recipient(recipient_id), subject: subject("Changed issue ##{@issue.iid}", @issue.title)) @target_url = project_issue_url(@project, @issue)
mail(from: sender(updated_by_user_id),
to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})"))
end end
def closed_issue_email(recipient_id, issue_id, updated_by_user_id) def closed_issue_email(recipient_id, issue_id, updated_by_user_id)
@issue = Issue.find issue_id @issue = Issue.find issue_id
@project = @issue.project @project = @issue.project
@updated_by = User.find updated_by_user_id @updated_by = User.find updated_by_user_id
mail(to: recipient(recipient_id), @target_url = project_issue_url(@project, @issue)
subject: subject("Closed issue ##{@issue.iid}", @issue.title)) mail(from: sender(updated_by_user_id),
to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})"))
end end
def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id) def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id)
...@@ -26,8 +34,10 @@ module Emails ...@@ -26,8 +34,10 @@ module Emails
@issue_status = status @issue_status = status
@project = @issue.project @project = @issue.project
@updated_by = User.find updated_by_user_id @updated_by = User.find updated_by_user_id
mail(to: recipient(recipient_id), @target_url = project_issue_url(@project, @issue)
subject: subject("Changed issue ##{@issue.iid}", @issue.title)) mail(from: sender(updated_by_user_id),
to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})"))
end end
end end
end end
...@@ -3,27 +3,39 @@ module Emails ...@@ -3,27 +3,39 @@ module Emails
def new_merge_request_email(recipient_id, merge_request_id) def new_merge_request_email(recipient_id, merge_request_id)
@merge_request = MergeRequest.find(merge_request_id) @merge_request = MergeRequest.find(merge_request_id)
@project = @merge_request.project @project = @merge_request.project
mail(to: recipient(recipient_id), subject: subject("New merge request ##{@merge_request.iid}", @merge_request.title)) @target_url = project_merge_request_url(@project, @merge_request)
mail(from: sender(@merge_request.author_id),
to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
end end
def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id) def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id)
@merge_request = MergeRequest.find(merge_request_id) @merge_request = MergeRequest.find(merge_request_id)
@previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
@project = @merge_request.project @project = @merge_request.project
mail(to: recipient(recipient_id), subject: subject("Changed merge request ##{@merge_request.iid}", @merge_request.title)) @target_url = project_merge_request_url(@project, @merge_request)
mail(from: sender(updated_by_user_id),
to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
end end
def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
@merge_request = MergeRequest.find(merge_request_id) @merge_request = MergeRequest.find(merge_request_id)
@updated_by = User.find updated_by_user_id @updated_by = User.find updated_by_user_id
@project = @merge_request.project @project = @merge_request.project
mail(to: recipient(recipient_id), subject: subject("Closed merge request ##{@merge_request.iid}", @merge_request.title)) @target_url = project_merge_request_url(@project, @merge_request)
mail(from: sender(updated_by_user_id),
to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
end end
def merged_merge_request_email(recipient_id, merge_request_id) def merged_merge_request_email(recipient_id, merge_request_id)
@merge_request = MergeRequest.find(merge_request_id) @merge_request = MergeRequest.find(merge_request_id)
@project = @merge_request.project @project = @merge_request.project
mail(to: recipient(recipient_id), subject: subject("Accepted merge request ##{@merge_request.iid}", @merge_request.title)) @target_url = project_merge_request_url(@project, @merge_request)
mail(from: sender(@merge_request.author_id_of_changes),
to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
end end
end end
...@@ -57,7 +69,7 @@ module Emails ...@@ -57,7 +69,7 @@ module Emails
# >> subject('Lorem ipsum', 'Dolor sit amet') # >> subject('Lorem ipsum', 'Dolor sit amet')
# => "GitLab Merge Request | Lorem ipsum | Dolor sit amet" # => "GitLab Merge Request | Lorem ipsum | Dolor sit amet"
def subject(*extra) def subject(*extra)
subject = "GitLab Merge Request |" subject = "Merge Request | "
if @merge_request.for_fork? if @merge_request.for_fork?
subject << "#{@merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} >> #{@merge_request.target_project.name_with_namespace}:#{merge_request.target_branch}" subject << "#{@merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} >> #{@merge_request.target_project.name_with_namespace}:#{merge_request.target_branch}"
else else
......
...@@ -4,27 +4,39 @@ module Emails ...@@ -4,27 +4,39 @@ module Emails
@note = Note.find(note_id) @note = Note.find(note_id)
@commit = @note.noteable @commit = @note.noteable
@project = @note.project @project = @note.project
mail(to: recipient(recipient_id), subject: subject("Note for commit #{@commit.short_id}", @commit.title)) @target_url = project_commit_url(@project, @commit, anchor: "note_#{@note.id}")
mail(from: sender(@note.author_id),
to: recipient(recipient_id),
subject: subject("#{@commit.title} (#{@commit.short_id})"))
end end
def note_issue_email(recipient_id, note_id) def note_issue_email(recipient_id, note_id)
@note = Note.find(note_id) @note = Note.find(note_id)
@issue = @note.noteable @issue = @note.noteable
@project = @note.project @project = @note.project
mail(to: recipient(recipient_id), subject: subject("Note for issue ##{@issue.iid}")) @target_url = project_issue_url(@project, @issue, anchor: "note_#{@note.id}")
mail(from: sender(@note.author_id),
to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})"))
end end
def note_merge_request_email(recipient_id, note_id) def note_merge_request_email(recipient_id, note_id)
@note = Note.find(note_id) @note = Note.find(note_id)
@merge_request = @note.noteable @merge_request = @note.noteable
@project = @note.project @project = @note.project
mail(to: recipient(recipient_id), subject: subject("Note for merge request ##{@merge_request.iid}")) @target_url = project_merge_request_url(@project, @merge_request, anchor: "note_#{@note.id}")
mail(from: sender(@note.author_id),
to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
end end
def note_wall_email(recipient_id, note_id) def note_wall_email(recipient_id, note_id)
@note = Note.find(note_id) @note = Note.find(note_id)
@project = @note.project @project = @note.project
mail(to: recipient(recipient_id), subject: subject("Note on wall")) @target_url = project_wall_url(@note.project, anchor: "note_#{@note.id}")
mail(from: sender(@note.author_id),
to: recipient(recipient_id),
subject: subject("Note on wall"))
end end
end end
end end
...@@ -3,6 +3,7 @@ module Emails ...@@ -3,6 +3,7 @@ module Emails
def new_user_email(user_id, password) def new_user_email(user_id, password)
@user = User.find(user_id) @user = User.find(user_id)
@password = password @password = password
@target_url = user_url(@user)
mail(to: @user.email, subject: subject("Account was created for you")) mail(to: @user.email, subject: subject("Account was created for you"))
end end
...@@ -15,6 +16,7 @@ module Emails ...@@ -15,6 +16,7 @@ module Emails
def new_ssh_key_email(key_id) def new_ssh_key_email(key_id)
@key = Key.find(key_id) @key = Key.find(key_id)
@user = @key.user @user = @key.user
@target_url = user_url(@user)
mail(to: @user.email, subject: subject("SSH key was added to your account")) mail(to: @user.email, subject: subject("SSH key was added to your account"))
end end
end end
......
...@@ -3,6 +3,7 @@ module Emails ...@@ -3,6 +3,7 @@ module Emails
def project_access_granted_email(user_project_id) def project_access_granted_email(user_project_id)
@users_project = UsersProject.find user_project_id @users_project = UsersProject.find user_project_id
@project = @users_project.project @project = @users_project.project
@target_url = project_url(@project)
mail(to: @users_project.user.email, mail(to: @users_project.user.email,
subject: subject("Access to project was granted")) subject: subject("Access to project was granted"))
end end
...@@ -10,6 +11,7 @@ module Emails ...@@ -10,6 +11,7 @@ module Emails
def project_was_moved_email(project_id, user_id) def project_was_moved_email(project_id, user_id)
@user = User.find user_id @user = User.find user_id
@project = Project.find project_id @project = Project.find project_id
@target_url = project_url(@project)
mail(to: @user.email, mail(to: @user.email,
subject: subject("Project was moved")) subject: subject("Project was moved"))
end end
...@@ -21,8 +23,15 @@ module Emails ...@@ -21,8 +23,15 @@ module Emails
@commits = Commit.decorate(compare.commits) @commits = Commit.decorate(compare.commits)
@diffs = compare.diffs @diffs = compare.diffs
@branch = branch @branch = branch
if @commits.length > 1
@target_url = project_compare_url(@project, from: @commits.first, to: @commits.last)
else
@target_url = project_commit_url(@project, @compare.commit)
end
mail(to: recipient, subject: subject("New push to repository")) mail(from: sender(author_id),
to: recipient,
subject: subject("New push to repository"))
end end
end end
end end
...@@ -15,16 +15,33 @@ class Notify < ActionMailer::Base ...@@ -15,16 +15,33 @@ class Notify < ActionMailer::Base
default_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port? default_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port?
default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root
default from: Gitlab.config.gitlab.email_from default from: Proc.new { default_sender_address.format }
default reply_to: "noreply@#{Gitlab.config.gitlab.host}" default reply_to: "noreply@#{Gitlab.config.gitlab.host}"
# Just send email with 3 seconds delay # Just send email with 2 seconds delay
def self.delay def self.delay
delay_for(2.seconds) delay_for(2.seconds)
end end
private private
# The default email address to send emails from
def default_sender_address
address = Mail::Address.new(Gitlab.config.gitlab.email_from)
address.display_name = "GitLab"
address
end
# Return an email address that displays the name of the sender.
# Only the displayed name changes; the actual email address is always the same.
def sender(sender_id)
if sender = User.find(sender_id)
address = default_sender_address
address.display_name = sender.name
address.format
end
end
# Look up a User by their ID and return their email address # Look up a User by their ID and return their email address
# #
# recipient_id - User ID # recipient_id - User ID
...@@ -43,21 +60,21 @@ class Notify < ActionMailer::Base ...@@ -43,21 +60,21 @@ class Notify < ActionMailer::Base
# Examples # Examples
# #
# >> subject('Lorem ipsum') # >> subject('Lorem ipsum')
# => "GitLab | Lorem ipsum" # => "Lorem ipsum"
# #
# # Automatically inserts Project name when @project is set # # Automatically inserts Project name when @project is set
# >> @project = Project.last # >> @project = Project.last
# => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...> # => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
# >> subject('Lorem ipsum') # >> subject('Lorem ipsum')
# => "GitLab | Ruby on Rails | Lorem ipsum " # => "Ruby on Rails | Lorem ipsum "
# #
# # Accepts multiple arguments # # Accepts multiple arguments
# >> subject('Lorem ipsum', 'Dolor sit amet') # >> subject('Lorem ipsum', 'Dolor sit amet')
# => "GitLab | Lorem ipsum | Dolor sit amet" # => "Lorem ipsum | Dolor sit amet"
def subject(*extra) def subject(*extra)
subject = "GitLab" subject = ""
subject << (@project ? " | #{@project.name_with_namespace}" : "") subject << "#{@project.name} | " if @project
subject << " | " + extra.join(' | ') if extra.present? subject << extra.join(' | ') if extra.present?
subject subject
end end
end end
...@@ -43,7 +43,19 @@ class Ability ...@@ -43,7 +43,19 @@ class Ability
:download_code :download_code
] ]
else else
[] group = if subject.kind_of?(Group)
subject
elsif subject.respond_to?(:group)
subject.group
else
nil
end
if group && group.has_projects_accessible_to?(nil)
[:read_group]
else
[]
end
end end
end end
...@@ -172,7 +184,7 @@ class Ability ...@@ -172,7 +184,7 @@ class Ability
def group_abilities user, group def group_abilities user, group
rules = [] rules = []
if group.users.include?(user) || user.admin? if user.admin? || group.users.include?(user) || ProjectsFinder.new.execute(user, group: group).any?
rules << :read_group rules << :read_group
end end
...@@ -228,6 +240,7 @@ class Ability ...@@ -228,6 +240,7 @@ class Ability
can_manage = group_abilities(user, group).include?(:manage_group) can_manage = group_abilities(user, group).include?(:manage_group)
if can_manage && (user != target_user) if can_manage && (user != target_user)
rules << :modify rules << :modify
rules << :destroy
end end
if !group.last_owner?(user) && (can_manage || (user == target_user)) if !group.last_owner?(user) && (can_manage || (user == target_user))
rules << :destroy rules << :destroy
......
...@@ -99,14 +99,16 @@ class Commit ...@@ -99,14 +99,16 @@ class Commit
# #
# cut off, ellipses (`&hellp;`) are prepended to the commit message. # cut off, ellipses (`&hellp;`) are prepended to the commit message.
def description def description
description = safe_message title_end = safe_message.index(/\n/)
@description ||= if (!title_end && safe_message.length > 100) || (title_end && title_end > 100)
"&hellip;".html_safe << safe_message[80..-1]
else
safe_message.split(/\n/, 2)[1].try(:chomp)
end
end
title_end = description.index(/\n/) def description?
if (!title_end && description.length > 100) || (title_end && title_end > 100) description.present?
"&hellip;".html_safe << description[80..-1]
else
description.split(/\n/, 2)[1].try(:chomp)
end
end end
# Regular expression that identifies commit message clauses that trigger issue closing. # Regular expression that identifies commit message clauses that trigger issue closing.
......
...@@ -23,7 +23,8 @@ module Issuable ...@@ -23,7 +23,8 @@ module Issuable
scope :assigned, -> { where("assignee_id IS NOT NULL") } scope :assigned, -> { where("assignee_id IS NOT NULL") }
scope :unassigned, -> { where("assignee_id IS NULL") } scope :unassigned, -> { where("assignee_id IS NULL") }
scope :of_projects, ->(ids) { where(project_id: ids) } scope :of_projects, ->(ids) { where(project_id: ids) }
scope :opened, -> { with_state(:opened, :reopened) }
scope :closed, -> { with_state(:closed) }
delegate :name, delegate :name,
:email, :email,
......
...@@ -35,6 +35,12 @@ class Group < Namespace ...@@ -35,6 +35,12 @@ class Group < Namespace
validates :avatar, file_size: { maximum: 100.kilobytes.to_i } validates :avatar, file_size: { maximum: 100.kilobytes.to_i }
mount_uploader :avatar, AttachmentUploader mount_uploader :avatar, AttachmentUploader
def self.accessible_to(user)
accessible_ids = Project.accessible_to(user).pluck(:namespace_id)
accessible_ids += user.groups.pluck(:id) if user
where(id: accessible_ids)
end
def human_name def human_name
name name
......
...@@ -28,8 +28,6 @@ class Issue < ActiveRecord::Base ...@@ -28,8 +28,6 @@ class Issue < ActiveRecord::Base
scope :of_group, ->(group) { where(project_id: group.project_ids) } scope :of_group, ->(group) { where(project_id: group.project_ids) }
scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) } scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
scope :opened, -> { with_state(:opened, :reopened) }
scope :closed, -> { with_state(:closed) }
attr_accessible :title, :assignee_id, :position, :description, attr_accessible :title, :assignee_id, :position, :description,
:milestone_id, :label_list, :author_id_of_changes, :milestone_id, :label_list, :author_id_of_changes,
...@@ -50,9 +48,7 @@ class Issue < ActiveRecord::Base ...@@ -50,9 +48,7 @@ class Issue < ActiveRecord::Base
end end
state :opened state :opened
state :reopened state :reopened
state :closed state :closed
end end
......
...@@ -53,7 +53,7 @@ class Key < ActiveRecord::Base ...@@ -53,7 +53,7 @@ class Key < ActiveRecord::Base
Tempfile.open('gitlab_key_file') do |file| Tempfile.open('gitlab_key_file') do |file|
file.puts key file.puts key
file.rewind file.rewind
cmd_output, cmd_status = popen("ssh-keygen -lf #{file.path}", '/tmp') cmd_output, cmd_status = popen(%W(ssh-keygen -lf #{file.path}), '/tmp')
end end
if cmd_status.zero? if cmd_status.zero?
......
...@@ -100,8 +100,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -100,8 +100,6 @@ class MergeRequest < ActiveRecord::Base
scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) } scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) }
scope :of_user_team, ->(team) { where("(source_project_id in (:team_project_ids) OR target_project_id in (:team_project_ids) AND assignee_id in (:team_member_ids))", team_project_ids: team.project_ids, team_member_ids: team.member_ids) } scope :of_user_team, ->(team) { where("(source_project_id in (:team_project_ids) OR target_project_id in (:team_project_ids) AND assignee_id in (:team_member_ids))", team_project_ids: team.project_ids, team_member_ids: team.member_ids) }
scope :opened, -> { with_state(:opened) }
scope :closed, -> { with_state(:closed) }
scope :merged, -> { with_state(:merged) } scope :merged, -> { with_state(:merged) }
scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) } scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
...@@ -135,7 +133,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -135,7 +133,7 @@ class MergeRequest < ActiveRecord::Base
end end
def reload_code def reload_code
if merge_request_diff && opened? if merge_request_diff && open?
merge_request_diff.reload_content merge_request_diff.reload_content
end end
end end
...@@ -160,6 +158,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -160,6 +158,10 @@ class MergeRequest < ActiveRecord::Base
MergeRequests::AutoMergeService.new.execute(self, current_user, commit_message) MergeRequests::AutoMergeService.new.execute(self, current_user, commit_message)
end end
def open?
opened? || reopened?
end
def mr_and_commit_notes def mr_and_commit_notes
# Fetch comments only from last 100 commits # Fetch comments only from last 100 commits
commits_for_notes_limit = 100 commits_for_notes_limit = 100
......
...@@ -69,12 +69,6 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -69,12 +69,6 @@ class MergeRequestDiff < ActiveRecord::Base
end end
end end
# When Git::Diff is not able to get diff
# because of git timeout it return this value
def broken_diffs
[Gitlab::Git::Diff::BROKEN_DIFF]
end
# Collect array of Git::Commit objects # Collect array of Git::Commit objects
# between target and source branches # between target and source branches
def unmerged_commits def unmerged_commits
......
...@@ -47,6 +47,14 @@ class Namespace < ActiveRecord::Base ...@@ -47,6 +47,14 @@ class Namespace < ActiveRecord::Base
def self.global_id def self.global_id
'GLN' 'GLN'
end end
def projects_accessible_to(user)
projects.accessible_to(user)
end
def has_projects_accessible_to?(user)
projects_accessible_to(user).present?
end
def to_param def to_param
path path
......
...@@ -28,6 +28,8 @@ class Project < ActiveRecord::Base ...@@ -28,6 +28,8 @@ class Project < ActiveRecord::Base
include Gitlab::VisibilityLevel include Gitlab::VisibilityLevel
extend Enumerize extend Enumerize
default_value_for :archived, false
ActsAsTaggableOn.strict_case_match = true ActsAsTaggableOn.strict_case_match = true
attr_accessible :name, :path, :description, :issues_tracker, :label_list, attr_accessible :name, :path, :description, :issues_tracker, :label_list,
...@@ -53,15 +55,13 @@ class Project < ActiveRecord::Base ...@@ -53,15 +55,13 @@ class Project < ActiveRecord::Base
has_one :hipchat_service, dependent: :destroy has_one :hipchat_service, dependent: :destroy
has_one :flowdock_service, dependent: :destroy has_one :flowdock_service, dependent: :destroy
has_one :assembla_service, dependent: :destroy has_one :assembla_service, dependent: :destroy
has_one :gemnasium_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link has_one :forked_from_project, through: :forked_project_link
# Merge Requests for target project should be removed with it # Merge Requests for target project should be removed with it
has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id" has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id"
# Merge requests from source project should be kept when source project was removed # Merge requests from source project should be kept when source project was removed
has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest
has_many :issues, -> { order "state DESC, created_at DESC" }, dependent: :destroy has_many :issues, -> { order "state DESC, created_at DESC" }, dependent: :destroy
has_many :services, dependent: :destroy has_many :services, dependent: :destroy
has_many :events, dependent: :destroy has_many :events, dependent: :destroy
...@@ -70,10 +70,8 @@ class Project < ActiveRecord::Base ...@@ -70,10 +70,8 @@ class Project < ActiveRecord::Base
has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet" has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet"
has_many :hooks, dependent: :destroy, class_name: "ProjectHook" has_many :hooks, dependent: :destroy, class_name: "ProjectHook"
has_many :protected_branches, dependent: :destroy has_many :protected_branches, dependent: :destroy
has_many :users_projects, dependent: :destroy has_many :users_projects, dependent: :destroy
has_many :users, through: :users_projects has_many :users, through: :users_projects
has_many :deploy_keys_projects, dependent: :destroy has_many :deploy_keys_projects, dependent: :destroy
has_many :deploy_keys, through: :deploy_keys_projects has_many :deploy_keys, through: :deploy_keys_projects
...@@ -96,15 +94,12 @@ class Project < ActiveRecord::Base ...@@ -96,15 +94,12 @@ class Project < ActiveRecord::Base
validates :issues_enabled, :wall_enabled, :merge_requests_enabled, validates :issues_enabled, :wall_enabled, :merge_requests_enabled,
:wiki_enabled, inclusion: { in: [true, false] } :wiki_enabled, inclusion: { in: [true, false] }
validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true
validates :namespace, presence: true validates :namespace, presence: true
validates_uniqueness_of :name, scope: :namespace_id validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id validates_uniqueness_of :path, scope: :namespace_id
validates :import_url, validates :import_url,
format: { with: URI::regexp(%w(git http https)), message: "should be a valid url" }, format: { with: URI::regexp(%w(git http https)), message: "should be a valid url" },
if: :import? if: :import?
validate :check_limit, on: :create validate :check_limit, on: :create
# Scopes # Scopes
...@@ -117,18 +112,57 @@ class Project < ActiveRecord::Base ...@@ -117,18 +112,57 @@ class Project < ActiveRecord::Base
scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") } scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) } scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) } scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) }
scope :public_only, -> { where(visibility_level: PUBLIC) } scope :public_only, -> { where(visibility_level: Project::PUBLIC) }
scope :public_or_internal_only, ->(user) { where("visibility_level IN (:levels)", levels: user ? [ INTERNAL, PUBLIC ] : [ PUBLIC ]) } scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) }
scope :non_archived, -> { where(archived: false) } scope :non_archived, -> { where(archived: false) }
enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab
state_machine :import_status, initial: :none do
event :import_start do
transition :none => :started
end
event :import_finish do
transition :started => :finished
end
event :import_fail do
transition :started => :failed
end
event :import_retry do
transition :failed => :started
end
state :started
state :finished
state :failed
after_transition any => :started, :do => :add_import_job
end
class << self class << self
def public_and_internal_levels
[Project::PUBLIC, Project::INTERNAL]
end
def abandoned def abandoned
where('projects.last_activity_at < ?', 6.months.ago) where('projects.last_activity_at < ?', 6.months.ago)
end end
def publicish(user)
visibility_levels = [Project::PUBLIC]
visibility_levels += [Project::INTERNAL] if user
where(visibility_level: visibility_levels)
end
def accessible_to(user)
accessible_ids = publicish(user).pluck(:id)
accessible_ids += user.authorized_projects.pluck(:id) if user
where(id: accessible_ids)
end
def with_push def with_push
includes(:events).where('events.action = ?', Event::PUSHED) includes(:events).where('events.action = ?', Event::PUSHED)
end end
...@@ -146,15 +180,13 @@ class Project < ActiveRecord::Base ...@@ -146,15 +180,13 @@ class Project < ActiveRecord::Base
end end
def find_with_namespace(id) def find_with_namespace(id)
if id.include?("/") return nil unless id.include?("/")
id = id.split("/")
namespace = Namespace.find_by(path: id.first) id = id.split("/")
return nil unless namespace namespace = Namespace.find_by(path: id.first)
return nil unless namespace
where(namespace_id: namespace.id).find_by(path: id.second)
else where(namespace_id: namespace.id).find_by(path: id.second)
where(path: id, namespace_id: nil).last
end
end end
def visibility_levels def visibility_levels
...@@ -184,12 +216,28 @@ class Project < ActiveRecord::Base ...@@ -184,12 +216,28 @@ class Project < ActiveRecord::Base
id && persisted? id && persisted?
end end
def add_import_job
RepositoryImportWorker.perform_in(2.seconds, id)
end
def import? def import?
import_url.present? import_url.present?
end end
def imported? def imported?
imported import_finished?
end
def import_in_progress?
import? && import_status == 'started'
end
def import_failed?
import_status == 'failed'
end
def import_finished?
import_status == 'finished'
end end
def check_limit def check_limit
...@@ -259,7 +307,7 @@ class Project < ActiveRecord::Base ...@@ -259,7 +307,7 @@ class Project < ActiveRecord::Base
end end
def available_services_names def available_services_names
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push) %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium)
end end
def gitlab_ci? def gitlab_ci?
...@@ -343,18 +391,17 @@ class Project < ActiveRecord::Base ...@@ -343,18 +391,17 @@ class Project < ActiveRecord::Base
branch_name = ref.gsub("refs/heads/", "") branch_name = ref.gsub("refs/heads/", "")
c_ids = self.repository.commits_between(oldrev, newrev).map(&:id) c_ids = self.repository.commits_between(oldrev, newrev).map(&:id)
# Close merge requests
mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a
mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) }
mrs.each { |merge_request| MergeRequests::MergeService.new.execute(merge_request, user, nil) }
# Update code for merge requests into project between project branches # Update code for merge requests into project between project branches
mrs = self.merge_requests.opened.by_branch(branch_name).to_a mrs = self.merge_requests.opened.by_branch(branch_name).to_a
# Update code for merge requests between project and project fork # Update code for merge requests between project and project fork
mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a
mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked } mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked }
# Close merge requests
mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a
mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) }
mrs.each { |merge_request| MergeRequests::MergeService.new.execute(merge_request, user, nil) }
true true
end end
......
...@@ -17,9 +17,10 @@ ...@@ -17,9 +17,10 @@
class ProjectHook < WebHook class ProjectHook < WebHook
belongs_to :project belongs_to :project
attr_accessible :push_events, :issues_events, :merge_requests_events attr_accessible :push_events, :issues_events, :merge_requests_events, :tag_push_events
scope :push_hooks, -> { where(push_events: true) } scope :push_hooks, -> { where(push_events: true) }
scope :tag_push_hooks, -> { where(tag_push_events: true) }
scope :issue_hooks, -> { where(issues_events: true) } scope :issue_hooks, -> { where(issues_events: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true) } scope :merge_request_hooks, -> { where(merge_requests_events: true) }
end end
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
# project_url :string(255) # project_url :string(255)
# subdomain :string(255) # subdomain :string(255)
# room :string(255) # room :string(255)
# api_key :string(255)
# #
class AssemblaService < Service class AssemblaService < Service
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
# project_url :string(255) # project_url :string(255)
# subdomain :string(255) # subdomain :string(255)
# room :string(255) # room :string(255)
# api_key :string(255)
# #
class CampfireService < Service class CampfireService < Service
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
# project_url :string(255) # project_url :string(255)
# subdomain :string(255) # subdomain :string(255)
# room :string(255) # room :string(255)
# api_key :string(255)
# #
class EmailsOnPushService < Service class EmailsOnPushService < Service
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
# project_url :string(255) # project_url :string(255)
# subdomain :string(255) # subdomain :string(255)
# room :string(255) # room :string(255)
# api_key :string(255)
# #
require "flowdock-git-hook" require "flowdock-git-hook"
......
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# token :string(255)
# project_id :integer not null
# created_at :datetime not null
# updated_at :datetime not null
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
# api_key :string(255)
#
require "gemnasium/gitlab_service"
class GemnasiumService < Service
validates :token, :api_key, presence: true, if: :activated?
def title
'Gemnasium'
end
def description
'Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities.'
end
def to_param
'gemnasium'
end
def fields
[
{ type: 'text', name: 'api_key', placeholder: 'Your personal API KEY on gemnasium.com ' },
{ type: 'text', name: 'token', placeholder: 'The project\'s slug on gemnasium.com' }
]
end
def execute(push_data)
repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
Gemnasium::GitlabService.execute(
ref: push_data[:ref],
before: push_data[:before],
after: push_data[:after],
token: token,
api_key: api_key,
repo: repo_path
)
end
end
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
# project_url :string(255) # project_url :string(255)
# subdomain :string(255) # subdomain :string(255)
# room :string(255) # room :string(255)
# api_key :string(255)
# #
class GitlabCiService < Service class GitlabCiService < Service
...@@ -36,7 +37,7 @@ class GitlabCiService < Service ...@@ -36,7 +37,7 @@ class GitlabCiService < Service
end end
def commit_status sha def commit_status sha
response = HTTParty.get(commit_status_path(sha)) response = HTTParty.get(commit_status_path(sha), verify: false)
if response.code == 200 and response["status"] if response.code == 200 and response["status"]
response["status"] response["status"]
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
# project_url :string(255) # project_url :string(255)
# subdomain :string(255) # subdomain :string(255)
# room :string(255) # room :string(255)
# api_key :string(255)
# #
class HipchatService < Service class HipchatService < Service
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
# project_url :string(255) # project_url :string(255)
# subdomain :string(255) # subdomain :string(255)
# room :string(255) # room :string(255)
# api_key :string(255)
# #
class PivotaltrackerService < Service class PivotaltrackerService < Service
......
...@@ -134,6 +134,7 @@ class Repository ...@@ -134,6 +134,7 @@ class Repository
Rails.cache.delete(cache_key(:commit_count)) Rails.cache.delete(cache_key(:commit_count))
Rails.cache.delete(cache_key(:graph_log)) Rails.cache.delete(cache_key(:graph_log))
Rails.cache.delete(cache_key(:readme)) Rails.cache.delete(cache_key(:readme))
Rails.cache.delete(cache_key(:contribution_guide))
end end
def graph_log def graph_log
...@@ -167,6 +168,12 @@ class Repository ...@@ -167,6 +168,12 @@ class Repository
end end
end end
def contribution_guide
Rails.cache.fetch(cache_key(:contribution_guide)) do
tree(:head).contribution_guide
end
end
def head_commit def head_commit
commit(self.root_ref) commit(self.root_ref)
end end
......
...@@ -13,12 +13,15 @@ ...@@ -13,12 +13,15 @@
# project_url :string(255) # project_url :string(255)
# subdomain :string(255) # subdomain :string(255)
# room :string(255) # room :string(255)
# api_key :string(255)
# #
# To add new service you should build a class inherited from Service # To add new service you should build a class inherited from Service
# and implement a set of methods # and implement a set of methods
class Service < ActiveRecord::Base class Service < ActiveRecord::Base
attr_accessible :title, :token, :type, :active default_value_for :active, false
attr_accessible :title, :token, :type, :active, :api_key
belongs_to :project belongs_to :project
has_one :service_hook has_one :service_hook
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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