Commit d73c718a authored by Sacred Seven's avatar Sacred Seven

Merged 7.6.1

parents e09f4b70 0286222e
v 7.5.1 v 7.6.0
- Add missing timestamps to 'members' table - Fork repository to groups
- New rugged version
- Add CRON=1 backup setting for quiet backups
- Fix failing wiki restore
- Add optional Sidekiq MemoryKiller middleware (enabled via SIDEKIQ_MAX_RSS env variable)
- Monokai highlighting style now more faithful to original design (Mark Riedesel)
- Create project with repository in synchrony
- Added ability to create empty repo or import existing one if project does not have repository
- Reactivate highlight.js language autodetection
- Mobile UI improvements
- Change maximum avatar file size from 100KB to 200KB
- Strict validation for snippet file names
- Enable Markdown preview for issues, merge requests, milestones, and notes (Vinnie Okada)
- In the docker directory is a container template based on the Omnibus packages.
- Update Sidekiq to version 2.17.8
- Add author filter to project issues and merge requests pages
- Atom feed for user activity
- Support multiple omniauth providers for the same user
- Rendering cross reference in issue title and tooltip for merge request
- Show username in comments
- Possibility to create Milestones or Labels when Issues are disabled
- Fix bug with showing gpg signature in tag
v 7.5.2
- Don't log Sidekiq arguments by default
v 7.5.0 v 7.5.0
- API: Add support for Hipchat (Kevin Houdebert) - API: Add support for Hipchat (Kevin Houdebert)
- Add time zone configuration on gitlab.yml (Sullivan Senechal) - Add time zone configuration in gitlab.yml (Sullivan Senechal)
- Fix LDAP authentication for Git HTTP access - Fix LDAP authentication for Git HTTP access
- Run 'GC.start' after every EmailsOnPushWorker job - Run 'GC.start' after every EmailsOnPushWorker job
- Fix LDAP config lookup for provider 'ldap' - Fix LDAP config lookup for provider 'ldap'
...@@ -25,9 +49,9 @@ v 7.5.0 ...@@ -25,9 +49,9 @@ v 7.5.0
- Added a password strength indicator - Added a password strength indicator
- Change project name and path in one form - Change project name and path in one form
- Display renamed files in diff views (Vinnie Okada) - Display renamed files in diff views (Vinnie Okada)
- Add timezone configuration to gitlab.yml
- Fix raw view for public snippets - Fix raw view for public snippets
- Use secret token with GitLab internal API. - Use secret token with GitLab internal API.
- Add missing timestamps to 'members' table
v 7.4.3 v 7.4.3
- Fix raw snippets view - Fix raw snippets view
...@@ -54,6 +78,7 @@ v 7.4.0 ...@@ -54,6 +78,7 @@ v 7.4.0
- Do not delete tmp/repositories itself during clean-up, only its contents - Do not delete tmp/repositories itself during clean-up, only its contents
- Support for backup uploads to remote storage - Support for backup uploads to remote storage
- Prevent notes polling when there are not notes - Prevent notes polling when there are not notes
- Internal ForkService: Prepare support for fork to a given namespace
- API: Add support for forking a project via the API (Bernhard Kaindl) - API: Add support for forking a project via the API (Bernhard Kaindl)
- API: filter project issues by milestone (Julien Bianchi) - API: filter project issues by milestone (Julien Bianchi)
- Fail harder in the backup script - Fail harder in the backup script
......
...@@ -37,7 +37,7 @@ Please send a merge request with a tested solution or a merge request with a fai ...@@ -37,7 +37,7 @@ Please send a merge request with a tested solution or a merge request with a fai
**[Search the issues](https://gitlab.com/gitlab-org/gitlab-ce/issues)** for similar entries before submitting your own, there's a good chance somebody else had the same issue. Show your support with `:+1:` and/or join the discussion. Please submit issues in the following format (as the first post): **[Search the issues](https://gitlab.com/gitlab-org/gitlab-ce/issues)** for similar entries before submitting your own, there's a good chance somebody else had the same issue. Show your support with `:+1:` and/or join the discussion. Please submit issues in the following format (as the first post):
1. **Summary:** Summarize your issue in one sentence (what goes wrong, what did you expect to happen) 1. **Summary:** Summarize your issue in one sentence (what goes wrong, what did you expect to happen)
1. **Steps to reproduce:** How can we reproduce the issue, preferably on the [GitLab development virtual machine with vagrant](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/doc/development.md) (start your issue with: `vagrant destroy && vagrant up && vagrant ssh`) 1. **Steps to reproduce:** How can we reproduce the issue
1. **Expected behavior:** Describe your issue in detail 1. **Expected behavior:** Describe your issue in detail
1. **Observed behavior** 1. **Observed behavior**
1. **Relevant logs and/or screenshots:** Please use code blocks (\`\`\`) to format console output, logs, and code as it's very hard to read otherwise. 1. **Relevant logs and/or screenshots:** Please use code blocks (\`\`\`) to format console output, logs, and code as it's very hard to read otherwise.
...@@ -75,20 +75,39 @@ If you can, please submit a merge request with the fix or improvements including ...@@ -75,20 +75,39 @@ If you can, please submit a merge request with the fix or improvements including
1. Link relevant [issues](https://gitlab.com/gitlab-org/gitlab-ce/issues) and/or [feature requests](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 [feature requests](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 submission 1. Be prepared to answer questions and incorporate feedback even if requests for this arrive weeks or months after your MR submission
1. If your MR touches code that executes shell commands, make sure it adheres to the [shell command guidelines]( doc/development/shell_commands.md). 1. If your MR touches code that executes shell commands, make sure it adheres to the [shell command guidelines]( doc/development/shell_commands.md).
1. Also have a look at the [shell command guidelines](doc/development/shell_commands.md) if your code reads or opens files, or handles paths to files on disk.
The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. The best time to submit a MR and get feedback fast. Before this time the GitLab B.V. team is still dealing with work that is created by the monthly release such as assisting subscribers with upgrade issues, the release of Enterprise Edition and the upgrade of GitLab Cloud. After the 7th it is already getting closer to the release date of the next version. This means there is less time to fix the issues created by merging large new features. The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. The best time to submit a MR and get feedback fast. Before this time the GitLab B.V. team is still dealing with work that is created by the monthly release such as assisting subscribers with upgrade issues, the release of Enterprise Edition and the upgrade of GitLab Cloud. After the 7th it is already getting closer to the release date of the next version. This means there is less time to fix the issues created by merging large new features.
Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MR's that leads to higher code quality is more important to us than having a minimal commit log. The smaller a MR is the more likely it is it will be merged (quickly), after that you can send more MR's to enhance it. Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MR's that leads to higher code quality is more important to us than having a minimal commit log. The smaller a MR is the more likely it is it will be merged (quickly), after that you can send more MR's to enhance it.
For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the following contribution acceptance criteria. For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the contribution acceptance criteria.
**Please format your merge request description as follows:** ## Definition of done
If you contribute to GitLab please know that changes involve more than just code.
We have the following [definition of done](http://guide.agilealliance.org/guide/definition-of-done.html).
Please ensure you support the feature you contribute through all of these steps.
1. Description explaning the relevancy (see following item)
1. Working and clean code that is commented where needed
1. Unit and integration tests that pass on the CI server
1. Documented in the /doc directory
1. Changelog entry added
1. Reviewed and any concerns are addressed
1. Merged by the project lead
1. Added to the release blog article
1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/) if relevant
1. Community questions answered
1. Answers to questions radiated (in docs/wiki/etc.)
## Merge request description format
1. What does this MR do? 1. What does this MR do?
1. Are there points in the code the reviewer needs to double check? 1. Are there points in the code the reviewer needs to double check?
1. Why was this MR needed? 1. Why was this MR needed?
1. What are the relevant issue numbers / [Feature requests](http://feedback.gitlab.com/)? 1. What are the relevant issue numbers / [Feature requests](http://feedback.gitlab.com/)?
1. Screenshots (If appropriate) 1. Screenshots (if relevant)
## Contribution acceptance criteria ## Contribution acceptance criteria
...@@ -123,3 +142,17 @@ For examples of feedback on merge requests please look at already [closed merge ...@@ -123,3 +142,17 @@ For examples of feedback on merge requests please look at already [closed merge
1. [Markdown](http://www.cirosantilli.com/markdown-styleguide) 1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
This is also the style used by linting tools such as [RuboCop](https://github.com/bbatsov/rubocop), [PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com). This is also the style used by linting tools such as [RuboCop](https://github.com/bbatsov/rubocop), [PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com).
## Code of conduct
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
Instances of abusive, harassing, or otherwise unacceptable behavior can be
reported by emailing contact@gitlab.com
This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
...@@ -31,10 +31,11 @@ gem 'omniauth-google-oauth2' ...@@ -31,10 +31,11 @@ gem 'omniauth-google-oauth2'
gem 'omniauth-twitter' gem 'omniauth-twitter'
gem 'omniauth-github' gem 'omniauth-github'
gem 'omniauth-shibboleth' gem 'omniauth-shibboleth'
gem 'omniauth-kerberos'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem "gitlab_git", '7.0.0.rc11' gem "gitlab_git", '7.0.0.rc12'
# 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'
...@@ -115,7 +116,7 @@ gem "acts-as-taggable-on" ...@@ -115,7 +116,7 @@ gem "acts-as-taggable-on"
# Background jobs # Background jobs
gem 'slim' gem 'slim'
gem 'sinatra', require: nil gem 'sinatra', require: nil
gem 'sidekiq', '2.17.0' gem 'sidekiq', '2.17.8'
# HTTP requests # HTTP requests
gem "httparty" gem "httparty"
...@@ -137,7 +138,7 @@ gem "redis-rails" ...@@ -137,7 +138,7 @@ gem "redis-rails"
gem 'tinder', '~> 1.9.2' gem 'tinder', '~> 1.9.2'
# HipChat integration # HipChat integration
gem "hipchat", "~> 0.14.0" gem "hipchat", "~> 1.4.0"
# Flowdock integration # Flowdock integration
gem "gitlab-flowdock-git-hook", "~> 0.4.2" gem "gitlab-flowdock-git-hook", "~> 0.4.2"
......
...@@ -78,7 +78,7 @@ GEM ...@@ -78,7 +78,7 @@ GEM
coffee-script-source (1.6.3) coffee-script-source (1.6.3)
colored (1.2) colored (1.2)
colorize (0.5.8) colorize (0.5.8)
connection_pool (1.2.0) connection_pool (2.1.0)
coveralls (0.7.0) coveralls (0.7.0)
multi_json (~> 1.3) multi_json (~> 1.3)
rest-client rest-client
...@@ -179,11 +179,11 @@ GEM ...@@ -179,11 +179,11 @@ GEM
mime-types (~> 1.19) mime-types (~> 1.19)
gitlab_emoji (0.0.1.1) gitlab_emoji (0.0.1.1)
emoji (~> 1.0.1) emoji (~> 1.0.1)
gitlab_git (7.0.0.rc11) gitlab_git (7.0.0.rc12)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0) gitlab-linguist (~> 3.0)
rugged (~> 0.21.0) rugged (~> 0.21.2)
gitlab_meta (7.0) gitlab_meta (7.0)
gitlab_omniauth-ldap (1.2.0) gitlab_omniauth-ldap (1.2.0)
net-ldap (~> 0.9) net-ldap (~> 0.9)
...@@ -235,8 +235,7 @@ GEM ...@@ -235,8 +235,7 @@ GEM
railties (>= 4.0.1) railties (>= 4.0.1)
hashie (2.1.2) hashie (2.1.2)
hike (1.2.3) hike (1.2.3)
hipchat (0.14.0) hipchat (1.4.0)
httparty
httparty httparty
html-pipeline (1.11.0) html-pipeline (1.11.0)
activesupport (>= 2) activesupport (>= 2)
...@@ -282,7 +281,7 @@ GEM ...@@ -282,7 +281,7 @@ GEM
addressable (~> 2.3) addressable (~> 2.3)
letter_opener (1.1.2) letter_opener (1.1.2)
launchy (~> 2.2) launchy (~> 2.2)
libv8 (3.16.14.3) libv8 (3.16.14.7)
listen (2.3.1) listen (2.3.1)
celluloid (>= 0.15.2) celluloid (>= 0.15.2)
rb-fsevent (>= 0.9.3) rb-fsevent (>= 0.9.3)
...@@ -324,6 +323,11 @@ GEM ...@@ -324,6 +323,11 @@ GEM
omniauth-google-oauth2 (0.2.5) omniauth-google-oauth2 (0.2.5)
omniauth (> 1.0) omniauth (> 1.0)
omniauth-oauth2 (~> 1.1) omniauth-oauth2 (~> 1.1)
omniauth-kerberos (0.2.0)
omniauth-multipassword
timfel-krb5-auth (~> 0.8)
omniauth-multipassword (0.4.1)
omniauth (~> 1.0)
omniauth-oauth (1.0.1) omniauth-oauth (1.0.1)
oauth oauth
omniauth (~> 1.0) omniauth (~> 1.0)
...@@ -404,7 +408,7 @@ GEM ...@@ -404,7 +408,7 @@ GEM
rdoc (3.12.2) rdoc (3.12.2)
json (~> 1.4) json (~> 1.4)
redcarpet (3.1.2) redcarpet (3.1.2)
redis (3.0.6) redis (3.1.0)
redis-actionpack (4.0.0) redis-actionpack (4.0.0)
actionpack (~> 4) actionpack (~> 4)
redis-rack (~> 1.5.0) redis-rack (~> 1.5.0)
...@@ -412,8 +416,8 @@ GEM ...@@ -412,8 +416,8 @@ GEM
redis-activesupport (4.0.0) redis-activesupport (4.0.0)
activesupport (~> 4) activesupport (~> 4)
redis-store (~> 1.1.0) redis-store (~> 1.1.0)
redis-namespace (1.4.1) redis-namespace (1.5.1)
redis (~> 3.0.4) redis (~> 3.0, >= 3.0.4)
redis-rack (1.5.0) redis-rack (1.5.0)
rack (~> 1.5) rack (~> 1.5)
redis-store (~> 1.1.0) redis-store (~> 1.1.0)
...@@ -448,7 +452,7 @@ GEM ...@@ -448,7 +452,7 @@ GEM
ruby-progressbar (1.2.0) ruby-progressbar (1.2.0)
rubyntlm (0.4.0) rubyntlm (0.4.0)
rubypants (0.2.0) rubypants (0.2.0)
rugged (0.21.0) rugged (0.21.2)
safe_yaml (0.9.7) safe_yaml (0.9.7)
sanitize (2.1.0) sanitize (2.1.0)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
...@@ -472,12 +476,12 @@ GEM ...@@ -472,12 +476,12 @@ GEM
sexp_processor (4.4.0) sexp_processor (4.4.0)
shoulda-matchers (2.1.0) shoulda-matchers (2.1.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
sidekiq (2.17.0) sidekiq (2.17.8)
celluloid (>= 0.15.2) celluloid (= 0.15.2)
connection_pool (>= 1.0.0) connection_pool (~> 2.0)
json json
redis (>= 3.0.4) redis (~> 3.1)
redis-namespace (>= 1.3.1) redis-namespace (~> 1.3)
simple_oauth (0.1.9) simple_oauth (0.1.9)
simplecov (0.9.0) simplecov (0.9.0)
docile (~> 1.1.0) docile (~> 1.1.0)
...@@ -533,6 +537,7 @@ GEM ...@@ -533,6 +537,7 @@ GEM
thread_safe (0.3.4) thread_safe (0.3.4)
tilt (1.4.1) tilt (1.4.1)
timers (1.1.0) timers (1.1.0)
timfel-krb5-auth (0.8)
tinder (1.9.3) tinder (1.9.3)
eventmachine (~> 1.0) eventmachine (~> 1.0)
faraday (~> 0.8) faraday (~> 0.8)
...@@ -626,7 +631,7 @@ DEPENDENCIES ...@@ -626,7 +631,7 @@ DEPENDENCIES
gitlab-grack (~> 2.0.0.pre) gitlab-grack (~> 2.0.0.pre)
gitlab-linguist (~> 3.0.0) gitlab-linguist (~> 3.0.0)
gitlab_emoji (~> 0.0.1.1) gitlab_emoji (~> 0.0.1.1)
gitlab_git (= 7.0.0.rc11) gitlab_git (= 7.0.0.rc12)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.2.0) gitlab_omniauth-ldap (= 1.2.0)
gollum-lib (~> 3.0.0) gollum-lib (~> 3.0.0)
...@@ -637,7 +642,7 @@ DEPENDENCIES ...@@ -637,7 +642,7 @@ DEPENDENCIES
guard-rspec guard-rspec
guard-spinach guard-spinach
haml-rails haml-rails
hipchat (~> 0.14.0) hipchat (~> 1.4.0)
html-pipeline-gitlab (~> 0.1.0) html-pipeline-gitlab (~> 0.1.0)
httparty httparty
jalalidate (~> 0.3.3) jalalidate (~> 0.3.3)
...@@ -658,6 +663,7 @@ DEPENDENCIES ...@@ -658,6 +663,7 @@ DEPENDENCIES
omniauth (~> 1.1.3) omniauth (~> 1.1.3)
omniauth-github omniauth-github
omniauth-google-oauth2 omniauth-google-oauth2
omniauth-kerberos
omniauth-shibboleth omniauth-shibboleth
omniauth-twitter omniauth-twitter
org-ruby (= 0.9.9) org-ruby (= 0.9.9)
...@@ -687,7 +693,7 @@ DEPENDENCIES ...@@ -687,7 +693,7 @@ DEPENDENCIES
semantic-ui-sass (~> 0.16.1.0) semantic-ui-sass (~> 0.16.1.0)
settingslogic settingslogic
shoulda-matchers (~> 2.1.0) shoulda-matchers (~> 2.1.0)
sidekiq (= 2.17.0) sidekiq (= 2.17.8)
simplecov simplecov
sinatra sinatra
six six
......
...@@ -104,3 +104,10 @@ This merge request has been closed because a request for more information has no ...@@ -104,3 +104,10 @@ This merge request has been closed because a request for more information has no
### Accepting merge requests ### Accepting merge requests
Is there a request on [the feature request forum](http://feedback.gitlab.com/forums/176466-general) that is similar to this? If so, can you make a comment with a link to it? Please be aware that new functionality that is not marked [accepting merge/pull requests](http://feedback.gitlab.com/forums/176466-general/status/796455) on the forum might not make it into GitLab. You might be asked to make changes and even after implementing them your feature might still be declined. If you want to reduce the chance of this happening please have a discussion in the forum first. Is there a request on [the feature request forum](http://feedback.gitlab.com/forums/176466-general) that is similar to this? If so, can you make a comment with a link to it? Please be aware that new functionality that is not marked [accepting merge/pull requests](http://feedback.gitlab.com/forums/176466-general/status/796455) on the forum might not make it into GitLab. You might be asked to make changes and even after implementing them your feature might still be declined. If you want to reduce the chance of this happening please have a discussion in the forum first.
### Only accepting merge requests with green tests
We can only accept a merge request if all the tests are green. I've just
restarted the build. When the tests are still not passing after this restart and
you're sure that is does not have anything to do with your code changes, please
rebase with master to see if that solves the issue.
...@@ -52,69 +52,30 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a ...@@ -52,69 +52,30 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a
Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/) for the various options. Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/) for the various options.
Since a manual installation is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm). Since a manual installation is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm).
You can access new installation with the login `root` and password `5iveL!fe`, after login you are required to set a unique password.
## Third-party applications ## Third-party applications
There are a lot of applications and API wrappers for GitLab. There are a lot of applications and API wrappers for GitLab.
Find them [on our website](https://about.gitlab.com/applications/). Find them [on our website](https://about.gitlab.com/applications/).
### New versions ## New versions
Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases come out when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG). For more information about the release process see the release [documentation](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/release). Features that will likely be in the next releases can be found on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457). Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases come out when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG). For more information about the release process see the release [documentation](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/release). Features that will likely be in the next releases can be found on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457).
### Upgrading ## Upgrading
For updating the the Omnibus installation please see the [update documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md). For manual installations there is an [upgrader script](doc/update/upgrader.md) and there are [upgrade guides](doc/update). For updating the the Omnibus installation please see the [update documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md). For manual installations there is an [upgrader script](doc/update/upgrader.md) and there are [upgrade guides](doc/update).
## Run in production mode
The Installation guide contains instructions on how to download an init script and run it automatically on boot. You can also start the init script manually:
sudo service gitlab start
or by directly calling the script:
sudo /etc/init.d/gitlab start
Please login with `root` / `5iveL!fe`
## Install a development environment ## Install a development environment
We recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit). We recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
If you do not use the development kit you might need to copy the example development unicorn configuration file If you do not use the GitLab Development Development kit you need to install and setup all the dependencies yourself, this is a lot of work and error prone.
One small thing you also have to do when installing it yourself is to copy the example development unicorn configuration file:
cp config/unicorn.rb.example.development config/unicorn.rb cp config/unicorn.rb.example.development config/unicorn.rb
## Run in development mode Instructions on how to start Gitlab and how to run the tests can be found in the [development section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#development).
Start it with [Foreman](https://github.com/ddollar/foreman)
bundle exec foreman start -p 3000
or start each component separately:
bundle exec rails s
bin/background_jobs start
And surf to [localhost:3000](http://localhost:3000/) and login with `root` / `5iveL!fe`.
## Run the tests
- Run all tests:
bundle exec rake test
- [RSpec](http://rspec.info/) unit and functional tests.
All RSpec tests: `bundle exec rake spec`
Single RSpec file: `bundle exec rspec spec/controllers/commit_controller_spec.rb`
- [Spinach](https://github.com/codegram/spinach) integration tests.
All Spinach tests: `bundle exec rake spinach`
Single Spinach test: `bundle exec spinach features/project/issues/milestones.feature`
## Documentation ## Documentation
...@@ -131,4 +92,4 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on ...@@ -131,4 +92,4 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on
## Is it awesome? ## Is it awesome?
Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua. Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua.
[These people](https://twitter.com/gitlabhq/favorites) seem to like it. [These people](https://twitter.com/gitlab/favorites) seem to like it.
7.5.1 7.6.1
\ No newline at end of file \ No newline at end of file
...@@ -75,6 +75,8 @@ class Dispatcher ...@@ -75,6 +75,8 @@ class Dispatcher
# Ensure we don't create a particular shortcut handler here. This is # Ensure we don't create a particular shortcut handler here. This is
# already created, where the network graph is created. # already created, where the network graph is created.
shortcut_handler = true shortcut_handler = true
when 'projects:forks:new'
new ProjectFork()
when 'users:show' when 'users:show'
new User() new User()
......
...@@ -24,6 +24,51 @@ $(document).ready -> ...@@ -24,6 +24,51 @@ $(document).ready ->
"opacity": 0 "opacity": 0
"display": "none" "display": "none"
# Preview button
$(document).off "click", ".js-md-preview-button"
$(document).on "click", ".js-md-preview-button", (e) ->
###
Shows the Markdown preview.
Lets the server render GFM into Html and displays it.
###
e.preventDefault()
form = $(this).closest("form")
# toggle tabs
form.find(".js-md-write-button").parent().removeClass "active"
form.find(".js-md-preview-button").parent().addClass "active"
# toggle content
form.find(".md-write-holder").hide()
form.find(".md-preview-holder").show()
preview = form.find(".js-md-preview")
mdText = form.find(".markdown-area").val()
if mdText.trim().length is 0
preview.text "Nothing to preview."
else
preview.text "Loading..."
$.get($(this).data("url"),
md_text: mdText
).success (previewData) ->
preview.html previewData
# Write button
$(document).off "click", ".js-md-write-button"
$(document).on "click", ".js-md-write-button", (e) ->
###
Shows the Markdown textarea.
###
e.preventDefault()
form = $(this).closest("form")
# toggle tabs
form.find(".js-md-write-button").parent().addClass "active"
form.find(".js-md-preview-button").parent().removeClass "active"
# toggle content
form.find(".md-write-holder").show()
form.find(".md-preview-holder").hide()
dropzone = $(".div-dropzone").dropzone( dropzone = $(".div-dropzone").dropzone(
url: project_image_path_upload url: project_image_path_upload
dictDefaultMessage: "" dictDefaultMessage: ""
......
...@@ -36,12 +36,6 @@ class @Notes ...@@ -36,12 +36,6 @@ class @Notes
# delete note attachment # delete note attachment
$(document).on "click", ".js-note-attachment-delete", @removeAttachment $(document).on "click", ".js-note-attachment-delete", @removeAttachment
# Preview button
$(document).on "click", ".js-note-preview-button", @previewNote
# Preview button
$(document).on "click", ".js-note-write-button", @writeNote
# reset main target form after submit # reset main target form after submit
$(document).on "ajax:complete", ".js-main-target-form", @resetMainTargetForm $(document).on "ajax:complete", ".js-main-target-form", @resetMainTargetForm
...@@ -77,8 +71,6 @@ class @Notes ...@@ -77,8 +71,6 @@ class @Notes
$(document).off "click", ".note-edit-cancel" $(document).off "click", ".note-edit-cancel"
$(document).off "click", ".js-note-delete" $(document).off "click", ".js-note-delete"
$(document).off "click", ".js-note-attachment-delete" $(document).off "click", ".js-note-attachment-delete"
$(document).off "click", ".js-note-preview-button"
$(document).off "click", ".js-note-write-button"
$(document).off "ajax:complete", ".js-main-target-form" $(document).off "ajax:complete", ".js-main-target-form"
$(document).off "click", ".js-choose-note-attachment-button" $(document).off "click", ".js-choose-note-attachment-button"
$(document).off "click", ".js-discussion-reply-button" $(document).off "click", ".js-discussion-reply-button"
...@@ -165,47 +157,6 @@ class @Notes ...@@ -165,47 +157,6 @@ class @Notes
# cleanup after successfully creating a diff/discussion note # cleanup after successfully creating a diff/discussion note
@removeDiscussionNoteForm(form) @removeDiscussionNoteForm(form)
###
Shows write note textarea.
###
writeNote: (e) ->
e.preventDefault()
form = $(this).closest("form")
# toggle tabs
form.find(".js-note-write-button").parent().addClass "active"
form.find(".js-note-preview-button").parent().removeClass "active"
# toggle content
form.find(".note-write-holder").show()
form.find(".note-preview-holder").hide()
###
Shows the note preview.
Lets the server render GFM into Html and displays it.
###
previewNote: (e) ->
e.preventDefault()
form = $(this).closest("form")
# toggle tabs
form.find(".js-note-write-button").parent().removeClass "active"
form.find(".js-note-preview-button").parent().addClass "active"
# toggle content
form.find(".note-write-holder").hide()
form.find(".note-preview-holder").show()
preview = form.find(".js-note-preview")
noteText = form.find(".js-note-text").val()
if noteText.trim().length is 0
preview.text "Nothing to preview."
else
preview.text "Loading..."
$.post($(this).data("url"),
note: noteText
).success (previewData) ->
preview.html previewData
### ###
Called in response the main target form has been successfully submitted. Called in response the main target form has been successfully submitted.
...@@ -220,7 +171,7 @@ class @Notes ...@@ -220,7 +171,7 @@ class @Notes
form.find(".js-errors").remove() form.find(".js-errors").remove()
# reset text and preview # reset text and preview
form.find(".js-note-write-button").click() form.find(".js-md-write-button").click()
form.find(".js-note-text").val("").trigger "input" form.find(".js-note-text").val("").trigger "input"
### ###
...@@ -270,8 +221,8 @@ class @Notes ...@@ -270,8 +221,8 @@ class @Notes
form.removeClass "js-new-note-form" form.removeClass "js-new-note-form"
# setup preview buttons # setup preview buttons
form.find(".js-note-write-button, .js-note-preview-button").tooltip placement: "left" form.find(".js-md-write-button, .js-md-preview-button").tooltip placement: "left"
previewButton = form.find(".js-note-preview-button") previewButton = form.find(".js-md-preview-button")
form.find(".js-note-text").on "input", -> form.find(".js-note-text").on "input", ->
if $(this).val().trim() isnt "" if $(this).val().trim() isnt ""
previewButton.removeClass("turn-off").addClass "turn-on" previewButton.removeClass("turn-off").addClass "turn-on"
......
class @ProjectFork
constructor: ->
$('.fork-thumbnail a').on 'click', ->
$('.fork-namespaces').hide()
$('.save-project-loader').show()
...@@ -330,10 +330,6 @@ table { ...@@ -330,10 +330,6 @@ table {
} }
} }
@media (max-width: $screen-xs-max) {
.container .content { margin-top: 20px; }
}
.wiki .highlight, .note-body .highlight { .wiki .highlight, .note-body .highlight {
margin-bottom: 9px; margin-bottom: 9px;
} }
......
...@@ -42,7 +42,6 @@ ...@@ -42,7 +42,6 @@
} }
.file-content { .file-content {
background: #fff; background: #fff;
font-size: 11px;
&.image_file { &.image_file {
background: #eee; background: #eee;
...@@ -54,8 +53,6 @@ ...@@ -54,8 +53,6 @@
} }
&.wiki { &.wiki {
font-size: 14px;
line-height: 1.6;
padding: 25px; padding: 25px;
.highlight { .highlight {
......
...@@ -59,6 +59,10 @@ ...@@ -59,6 +59,10 @@
pre { pre {
white-space: pre; white-space: pre;
word-wrap: normal; word-wrap: normal;
code {
font-family: $monospace_font;
}
} }
} }
} }
...@@ -113,6 +113,11 @@ ...@@ -113,6 +113,11 @@
padding: 10px 15px; padding: 10px 15px;
} }
.cross-project-ref {
float: left;
padding: 10px 15px;
}
.creator { .creator {
float: right; float: right;
padding: 10px 15px; padding: 10px 15px;
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
opacity: 0; opacity: 0;
font-size: 50px; font-size: 50px;
transition: opacity 200ms ease-in-out; transition: opacity 200ms ease-in-out;
pointer-events: none;
} }
.div-dropzone-spinner { .div-dropzone-spinner {
...@@ -50,3 +51,28 @@ ...@@ -50,3 +51,28 @@
margin-bottom: 0; margin-bottom: 0;
transition: opacity 200ms ease-in-out; transition: opacity 200ms ease-in-out;
} }
.md-preview-holder {
background: #FFF;
border: 1px solid #ddd;
min-height: 100px;
padding: 5px;
font-size: 14px;
box-shadow: none;
}
.new_note,
.edit_note,
.issuable-description,
.milestone-description,
.merge-request-form {
.nav-tabs {
margin-bottom: 0;
border: none;
li a,
li.active a {
border: 1px solid #DDD;
}
}
}
/** Common mobile (screen XS) styles **/
@media (max-width: $screen-xs-max) {
.container .content {
margin-top: 20px;
}
.nav.nav-tabs > li > a {
padding: 10px;
font-size: 12px;
margin-right: 3px;
.badge {
display: none;
}
}
}
...@@ -75,3 +75,20 @@ ...@@ -75,3 +75,20 @@
} }
} }
} }
@media (max-width: $screen-xs-max) {
.timeline {
&:before {
background: none;
}
.timeline-entry .timeline-entry-inner {
.timeline-icon {
display: none;
}
.timeline-content {
margin-left: 0;
}
}
}
}
...@@ -29,28 +29,30 @@ ...@@ -29,28 +29,30 @@
.hljs-tag, .hljs-tag,
.hljs-tag .hljs-title, .hljs-tag .hljs-title,
.hljs-keyword,
.hljs-literal,
.hljs-strong, .hljs-strong,
.hljs-change, .hljs-change,
.hljs-winutils, .hljs-winutils,
.hljs-flow, .hljs-flow,
.lisp .hljs-title, .lisp .hljs-title,
.clojure .hljs-built_in, .clojure .hljs-built_in,
.hljs-keyword,
.nginx .hljs-title, .nginx .hljs-title,
.tex .hljs-special { .tex .hljs-special {
color: #F92672; color: #F92672;
} }
.hljs { .hljs {
color: #DDD; color: #F8F8F2;
} }
.hljs .hljs-constant, .asciidoc .hljs-code,
.asciidoc .hljs-code { .markdown .hljs-code,
.hljs-literal,
.hljs-function .hljs-keyword {
color: #66D9EF; color: #66D9EF;
} }
.hljs-code, .hljs-code,
.hljs-class .hljs-title, .hljs-class .hljs-title,
.hljs-header { .hljs-header {
...@@ -62,18 +64,27 @@ ...@@ -62,18 +64,27 @@
.hljs-symbol, .hljs-symbol,
.hljs-symbol .hljs-string, .hljs-symbol .hljs-string,
.hljs-value, .hljs-value,
.hljs-constant,
.hljs-number,
.hljs-regexp { .hljs-regexp {
color: #BF79DB; color: #AE81FF;
}
.hljs-string {
color: #E6DB74;
}
.hljs-params {
color: #fd971f;
} }
.hljs-link_url, .hljs-link_url,
.hljs-tag .hljs-value, .hljs-tag .hljs-value,
.hljs-string,
.hljs-bullet, .hljs-bullet,
.hljs-subst, .hljs-subst,
.hljs-title, .hljs-title,
.hljs-emphasis, .hljs-emphasis,
.haskell .hljs-type, .hljs-type,
.hljs-preprocessor, .hljs-preprocessor,
.hljs-pragma, .hljs-pragma,
.ruby .hljs-class .hljs-parent, .ruby .hljs-class .hljs-parent,
...@@ -99,12 +110,12 @@ ...@@ -99,12 +110,12 @@
} }
.hljs-comment, .hljs-comment,
.java .hljs-annotation, .hljs-annotation,
.smartquote, .smartquote,
.hljs-blockquote, .hljs-blockquote,
.hljs-horizontal_rule, .hljs-horizontal_rule,
.python .hljs-decorator,
.hljs-template_comment, .hljs-template_comment,
.hljs-decorator,
.hljs-pi, .hljs-pi,
.hljs-doctype, .hljs-doctype,
.hljs-deletion, .hljs-deletion,
......
...@@ -58,8 +58,8 @@ ...@@ -58,8 +58,8 @@
} }
@mixin md-typography { @mixin md-typography {
font-size: 14px; font-size: 15px;
line-height: 1.6; line-height: 1.5;
img { img {
max-width: 100%; max-width: 100%;
...@@ -93,7 +93,7 @@ ...@@ -93,7 +93,7 @@
blockquote p { blockquote p {
color: #888; color: #888;
font-size: 14px; font-size: 15px;
line-height: 1.5; line-height: 1.5;
} }
......
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
.event-title { .event-title {
@include str-truncated(72%); @include str-truncated(72%);
color: #333; color: #333;
font-weight: normal; font-weight: 500;
font-size: 14px; font-size: 14px;
.author_name { .author_name {
color: #333; color: #333;
...@@ -56,12 +56,9 @@ ...@@ -56,12 +56,9 @@
.event-body { .event-body {
margin-left: 35px; margin-left: 35px;
margin-right: 100px; margin-right: 100px;
color: #777;
.event-info {
color: #666;
}
.event-note { .event-note {
color: #666;
margin-top: 5px; margin-top: 5px;
.md { .md {
...@@ -72,7 +69,7 @@ ...@@ -72,7 +69,7 @@
border: none; border: none;
background: #f9f9f9; background: #f9f9f9;
border-radius: 0; border-radius: 0;
color: #666; color: #777;
margin: 0 20px; margin: 0 20px;
} }
...@@ -120,7 +117,6 @@ ...@@ -120,7 +117,6 @@
padding: 3px; padding: 3px;
padding-left: 0; padding-left: 0;
border: none; border: none;
color: #666;
.commit-row-title { .commit-row-title {
font-size: 12px; font-size: 12px;
} }
...@@ -186,7 +182,24 @@ ...@@ -186,7 +182,24 @@
} }
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
.event-item .event-title { .event-item {
@include str-truncated(65%); .event-title {
white-space: normal;
overflow: visible;
max-width: 100%;
}
.avatar {
display: none;
}
.event-body {
margin: 0;
border-left: 2px solid #DDD;
padding-left: 10px;
}
.event-item-timestamp {
display: none;
}
} }
} }
...@@ -59,6 +59,7 @@ header { ...@@ -59,6 +59,7 @@ header {
} }
.navbar-collapse { .navbar-collapse {
margin-top: 47px;
padding-right: 0; padding-right: 0;
padding-left: 0; padding-left: 0;
} }
......
...@@ -151,4 +151,14 @@ form.edit-issue { ...@@ -151,4 +151,14 @@ form.edit-issue {
} }
} }
} }
.issue {
&:hover .issue-actions {
display: none !important;
}
.issue-updated-at {
display: none;
}
}
} }
...@@ -63,7 +63,6 @@ ...@@ -63,7 +63,6 @@
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
font-size: 18px; font-size: 18px;
margin: 0; margin: 0;
max-height: none; max-height: none;
&, .container { &, .container {
...@@ -86,6 +85,7 @@ ...@@ -86,6 +85,7 @@
color: #fff; color: #fff;
font-weight: normal; font-weight: normal;
text-shadow: none; text-shadow: none;
border: none;
&:after { display: none; } &:after { display: none; }
} }
......
...@@ -36,12 +36,15 @@ ul.notes { ...@@ -36,12 +36,15 @@ ul.notes {
font-size: 13px; font-size: 13px;
} }
.author { .author {
color: #555; color: #333;
font-weight: bold; font-weight: bold;
font-size: 14px; font-size: 14px;
&:hover { &:hover {
color: $link_hover_color; color: $link_color;
}
} }
.author-username {
font-size: 14px;
} }
} }
...@@ -224,7 +227,6 @@ ul.notes { ...@@ -224,7 +227,6 @@ ul.notes {
margin-bottom: 0; margin-bottom: 0;
} }
.note-preview-holder,
.note_text { .note_text {
background: #FFF; background: #FFF;
border: 1px solid #ddd; border: 1px solid #ddd;
...@@ -243,15 +245,6 @@ ul.notes { ...@@ -243,15 +245,6 @@ ul.notes {
.note_text { .note_text {
width: 100%; width: 100%;
} }
.nav-tabs {
margin-bottom: 0;
border: none;
li a,
li.active a {
border: 1px solid #DDD;
}
}
} }
/* loading indicator */ /* loading indicator */
......
...@@ -270,3 +270,41 @@ ul.nav.nav-projects-tabs { ...@@ -270,3 +270,41 @@ ul.nav.nav-projects-tabs {
color: #999; color: #999;
} }
} }
.fork-namespaces {
.thumbnail {
&.fork-exists-thumbnail {
border-color: #EEE;
.caption {
color: #999;
}
}
&.fork-thumbnail {
border-color: #AAA;
&:hover {
background-color: $hover;
}
}
a {
text-decoration: none;
}
}
}
@media (max-width: $screen-xs-max) {
.project-home-panel {
.star-fork-buttons {
padding-top: 10px;
padding-right: 15px;
}
}
.project-home-links {
display: none;
}
}
...@@ -42,10 +42,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -42,10 +42,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def handle_omniauth def handle_omniauth
if current_user if current_user
# Change a logged-in user's authentication method: # Add new authentication method
current_user.extern_uid = oauth['uid'] current_user.identities.find_or_create_by(extern_uid: oauth['uid'], provider: oauth['provider'])
current_user.provider = oauth['provider']
current_user.save
redirect_to profile_path redirect_to profile_path
else else
@user = Gitlab::OAuth::User.new(oauth) @user = Gitlab::OAuth::User.new(oauth)
...@@ -67,8 +65,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -67,8 +65,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
end end
end end
rescue StandardError rescue ForbiddenAction => e
flash[:notice] = "There's no such user!" flash[:notice] = e.message
redirect_to new_user_session_path redirect_to new_user_session_path
end end
......
...@@ -29,4 +29,31 @@ class Projects::ApplicationController < ApplicationController ...@@ -29,4 +29,31 @@ class Projects::ApplicationController < ApplicationController
redirect_to project_tree_path(@project, @ref), notice: "This action is not allowed unless you are on top of a branch" redirect_to project_tree_path(@project, @ref), notice: "This action is not allowed unless you are on top of a branch"
end end
end end
def set_filter_variables(collection)
params[:sort] ||= 'newest'
params[:scope] = 'all' if params[:scope].blank?
params[:state] = 'opened' if params[:state].blank?
@sort = params[:sort].humanize
assignee_id = params[:assignee_id]
author_id = params[:author_id]
milestone_id = params[:milestone_id]
if assignee_id.present? && !assignee_id.to_i.zero?
@assignee = @project.team.find(assignee_id)
end
if author_id.present? && !author_id.to_i.zero?
@author = @project.team.find(assignee_id)
end
if milestone_id.present? && !milestone_id.to_i.zero?
@milestone = @project.milestones.find(milestone_id)
end
@assignees = User.where(id: collection.pluck(:assignee_id))
@authors = User.where(id: collection.pluck(:author_id))
end
end end
class Projects::ForksController < Projects::ApplicationController
# Authorize
before_filter :authorize_download_code!
before_filter :require_non_empty_project
def new
@namespaces = current_user.manageable_namespaces
@namespaces.delete(@project.namespace)
end
def create
namespace = Namespace.find(params[:namespace_id])
@forked_project = ::Projects::ForkService.new(project, current_user, namespace: namespace).execute
if @forked_project.saved? && @forked_project.forked?
redirect_to(@forked_project, notice: 'Project was successfully forked.')
else
@title = 'Fork project'
render :error
end
end
end
...@@ -26,6 +26,7 @@ class Projects::HooksController < Projects::ApplicationController ...@@ -26,6 +26,7 @@ class Projects::HooksController < Projects::ApplicationController
def test def test
if !@project.empty_repo? if !@project.empty_repo?
status = TestHookService.new.execute(hook, current_user) status = TestHookService.new.execute(hook, current_user)
if status if status
flash[:notice] = 'Hook successfully executed.' flash[:notice] = 'Hook successfully executed.'
else else
......
class Projects::ImportsController < Projects::ApplicationController
# Authorize
before_filter :authorize_admin_project!
before_filter :require_no_repo
before_filter :redirect_if_progress, except: :show
def new
end
def create
@project.import_url = params[:project][:import_url]
if @project.save
@project.reload
if @project.import_failed?
@project.import_retry
else
@project.import_start
end
end
redirect_to project_import_path(@project)
end
def show
unless @project.import_in_progress?
if @project.import_finished?
redirect_to(@project) and return
else
redirect_to new_project_import_path(@project) and return
end
end
end
private
def require_no_repo
if @project.repository_exists?
redirect_to(@project) and return
end
end
def redirect_if_progress
if @project.import_in_progress?
redirect_to project_import_path(@project) and return
end
end
end
...@@ -18,18 +18,12 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -18,18 +18,12 @@ class Projects::IssuesController < Projects::ApplicationController
def index def index
terms = params['issue_search'] terms = params['issue_search']
set_filter_variables(@project.issues)
@issues = issues_filtered @issues = IssuesFinder.new.execute(current_user, params.merge(project_id: @project.id))
@issues = @issues.full_search(terms) if terms.present? @issues = @issues.full_search(terms) if terms.present?
@issues = @issues.page(params[:page]).per(20) @issues = @issues.page(params[:page]).per(20)
assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
@assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero?
@milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero?
sort_param = params[:sort] || 'newest'
@sort = sort_param.humanize unless sort_param.empty?
@assignees = User.where(id: @project.issues.pluck(:assignee_id)).active
respond_to do |format| respond_to do |format|
format.html format.html
format.atom { render layout: false } format.atom { render layout: false }
...@@ -127,12 +121,6 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -127,12 +121,6 @@ class Projects::IssuesController < Projects::ApplicationController
return render_404 unless @project.issues_enabled return render_404 unless @project.issues_enabled
end end
def issues_filtered
params[:scope] = 'all' if params[:scope].blank?
params[:state] = 'opened' if params[:state].blank?
@issues = IssuesFinder.new.execute(current_user, params.merge(project_id: @project.id))
end
# Since iids are implemented only in 6.1 # Since iids are implemented only in 6.1
# user may navigate to issue page using old global ids. # user may navigate to issue page using old global ids.
# #
......
...@@ -17,18 +17,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -17,18 +17,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort] before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort]
def index def index
params[:sort] ||= 'newest' set_filter_variables(@project.merge_requests)
params[:scope] = 'all' if params[:scope].blank?
params[:state] = 'opened' if params[:state].blank?
@merge_requests = MergeRequestsFinder.new.execute(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
assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
@assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero?
@milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero?
@assignees = User.where(id: @project.merge_requests.pluck(:assignee_id))
end end
def show def show
...@@ -225,6 +217,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -225,6 +217,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@allowed_to_merge = allowed_to_merge? @allowed_to_merge = allowed_to_merge?
@show_merge_controls = @merge_request.open? && @commits.any? && @allowed_to_merge @show_merge_controls = @merge_request.open? && @commits.any? && @allowed_to_merge
@source_branch = @merge_request.source_project.repository.find_branch(@merge_request.source_branch).try(:name) @source_branch = @merge_request.source_project.repository.find_branch(@merge_request.source_branch).try(:name)
if @merge_request.locked_long_ago?
@merge_request.unlock_mr
@merge_request.close
end
end end
def allowed_to_merge? def allowed_to_merge?
......
...@@ -103,7 +103,9 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -103,7 +103,9 @@ class Projects::MilestonesController < Projects::ApplicationController
end end
def module_enabled def module_enabled
return render_404 unless @project.issues_enabled unless @project.issues_enabled || @project.merge_requests_enabled
return render_404
end
end end
def milestone_params def milestone_params
......
...@@ -61,10 +61,6 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -61,10 +61,6 @@ class Projects::NotesController < Projects::ApplicationController
end end
end end
def preview
render text: view_context.markdown(params[:note])
end
private private
def note def note
......
class Projects::RepositoriesController < Projects::ApplicationController class Projects::RepositoriesController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_download_code! before_filter :authorize_download_code!
before_filter :require_non_empty_project before_filter :require_non_empty_project, except: :create
before_filter :authorize_admin_project!, only: :create
def create
@project.create_repository
redirect_to @project
end
def archive def archive
unless can?(current_user, :download_code, @project) unless can?(current_user, :download_code, @project)
......
...@@ -42,7 +42,7 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -42,7 +42,7 @@ class Projects::ServicesController < Projects::ApplicationController
:title, :token, :type, :active, :api_key, :subdomain, :title, :token, :type, :active, :api_key, :subdomain,
:room, :recipients, :project_url, :webhook, :room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key :build_key, :server
) )
end end
end end
...@@ -68,7 +68,7 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -68,7 +68,7 @@ class Projects::SnippetsController < Projects::ApplicationController
@snippet.content, @snippet.content,
type: 'text/plain; charset=utf-8', type: 'text/plain; charset=utf-8',
disposition: 'inline', disposition: 'inline',
filename: @snippet.file_name filename: @snippet.sanitized_file_name
) )
end end
......
...@@ -4,7 +4,7 @@ class ProjectsController < ApplicationController ...@@ -4,7 +4,7 @@ class ProjectsController < ApplicationController
before_filter :repository, except: [:new, :create] before_filter :repository, except: [:new, :create]
# Authorize # Authorize
before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :retry_import] before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
layout 'navless', only: [:new, :create, :fork] layout 'navless', only: [:new, :create, :fork]
before_filter :set_title, only: [:new, :create] before_filter :set_title, only: [:new, :create]
...@@ -19,10 +19,11 @@ class ProjectsController < ApplicationController ...@@ -19,10 +19,11 @@ class ProjectsController < ApplicationController
def create def create
@project = ::Projects::CreateService.new(current_user, project_params).execute @project = ::Projects::CreateService.new(current_user, project_params).execute
flash[:notice] = 'Project was successfully created.' if @project.saved?
respond_to do |format| if @project.saved?
format.js redirect_to project_path(@project), notice: 'Project was successfully created.'
else
render 'new'
end end
end end
...@@ -47,7 +48,7 @@ class ProjectsController < ApplicationController ...@@ -47,7 +48,7 @@ class ProjectsController < ApplicationController
def show def show
if @project.import_in_progress? if @project.import_in_progress?
redirect_to import_project_path(@project) redirect_to project_import_path(@project)
return return
end end
...@@ -60,37 +61,20 @@ class ProjectsController < ApplicationController ...@@ -60,37 +61,20 @@ class ProjectsController < ApplicationController
respond_to do |format| respond_to do |format|
format.html do format.html do
if @project.repository_exists?
if @project.empty_repo? if @project.empty_repo?
render "projects/empty", layout: user_layout render "projects/empty", layout: user_layout
else else
@last_push = current_user.recent_push(@project.id) if current_user @last_push = current_user.recent_push(@project.id) if current_user
render :show, layout: user_layout render :show, layout: user_layout
end end
end else
format.json { pager_json("events/_events", @events.count) } render "projects/no_repo", layout: user_layout
end
end
def import
if @project.import_finished?
redirect_to @project
return
end end
end end
def retry_import format.json { pager_json("events/_events", @events.count) }
unless @project.import_failed?
redirect_to import_project_path(@project)
end
@project.import_url = project_params[:import_url]
if @project.save
@project.reload
@project.import_retry
end end
redirect_to import_project_path(@project)
end end
def destroy def destroy
...@@ -111,22 +95,6 @@ class ProjectsController < ApplicationController ...@@ -111,22 +95,6 @@ class ProjectsController < ApplicationController
end end
end end
def fork
@forked_project = ::Projects::ForkService.new(project, current_user).execute
respond_to do |format|
format.html do
if @forked_project.saved? && @forked_project.forked?
redirect_to(@forked_project, notice: 'Project was successfully forked.')
else
@title = 'Fork project'
render "fork"
end
end
format.js
end
end
def autocomplete_sources def autocomplete_sources
note_type = params['type'] note_type = params['type']
note_id = params['type_id'] note_id = params['type_id']
...@@ -179,6 +147,10 @@ class ProjectsController < ApplicationController ...@@ -179,6 +147,10 @@ class ProjectsController < ApplicationController
render json: { star_count: @project.star_count } render json: { star_count: @project.star_count }
end end
def markdown_preview
render text: view_context.markdown(params[:md_text])
end
private private
def upload_path def upload_path
......
...@@ -79,7 +79,7 @@ class SnippetsController < ApplicationController ...@@ -79,7 +79,7 @@ class SnippetsController < ApplicationController
@snippet.content, @snippet.content,
type: 'text/plain; charset=utf-8', type: 'text/plain; charset=utf-8',
disposition: 'inline', disposition: 'inline',
filename: @snippet.file_name filename: @snippet.sanitized_file_name
) )
end end
......
...@@ -20,9 +20,14 @@ class UsersController < ApplicationController ...@@ -20,9 +20,14 @@ class UsersController < ApplicationController
# Get user activity feed for projects common for both users # Get user activity feed for projects common for both users
@events = @user.recent_events. @events = @user.recent_events.
where(project_id: authorized_projects_ids).limit(20) where(project_id: authorized_projects_ids).limit(30)
@title = @user.name @title = @user.name
respond_to do |format|
format.html
format.atom { render layout: false }
end
end end
def determine_layout def determine_layout
......
...@@ -33,6 +33,7 @@ class IssuableFinder ...@@ -33,6 +33,7 @@ class IssuableFinder
items = by_search(items) items = by_search(items)
items = by_milestone(items) items = by_milestone(items)
items = by_assignee(items) items = by_assignee(items)
items = by_author(items)
items = by_label(items) items = by_label(items)
items = sort(items) items = sort(items)
end end
...@@ -125,6 +126,14 @@ class IssuableFinder ...@@ -125,6 +126,14 @@ class IssuableFinder
items items
end end
def by_author(items)
if params[:author_id].present?
items = items.where(author_id: (params[:author_id] == '0' ? nil : params[:author_id]))
end
items
end
def by_label(items) def by_label(items)
if params[:label_name].present? if params[:label_name].present?
label_names = params[:label_name].split(",") label_names = params[:label_name].split(",")
......
module BlobHelper module BlobHelper
def highlightjs_class(blob_name) def highlightjs_class(blob_name)
if blob_name.include?('.')
ext = blob_name.split('.').last
return 'language-' + ext
else
if no_highlight_files.include?(blob_name.downcase) if no_highlight_files.include?(blob_name.downcase)
'no-highlight' 'no-highlight'
else else
blob_name.downcase blob_name.downcase
end end
end end
end
def no_highlight_files def no_highlight_files
%w(credits changelog copying copyright license authors) %w(credits changelog copying copyright license authors)
......
...@@ -145,4 +145,26 @@ module EventsHelper ...@@ -145,4 +145,26 @@ module EventsHelper
rescue rescue
"--broken encoding" "--broken encoding"
end end
def event_to_atom(xml, event)
if event.proper?
xml.entry do
event_link = event_feed_url(event)
event_title = event_feed_title(event)
event_summary = event_feed_summary(event)
xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
xml.link href: event_link
xml.title truncate(event_title, length: 80)
xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(event.author_email)
xml.author do |author|
xml.name event.author_name
xml.email event.author_email
end
xml.summary(type: "xhtml") { |x| x << event_summary unless event_summary.nil? }
end
end
end
end end
module GitHelper
def strip_gpg_signature(text)
text.gsub(/-----BEGIN PGP SIGNATURE-----(.*)-----END PGP SIGNATURE-----/m, "")
end
end
...@@ -256,4 +256,16 @@ module GitlabMarkdownHelper ...@@ -256,4 +256,16 @@ module GitlabMarkdownHelper
truncated truncated
end end
end end
def cross_project_reference(project, entity)
path = project.path_with_namespace
if entity.kind_of?(Issue)
[path, entity.iid].join('#')
elsif entity.kind_of?(MergeRequest)
[path, entity.iid].join('!')
else
raise 'Not supported type'
end
end
end end
...@@ -113,4 +113,19 @@ module IssuesHelper ...@@ -113,4 +113,19 @@ module IssuesHelper
'issue-box-open' 'issue-box-open'
end end
end end
def issue_to_atom(xml, issue)
xml.entry do
xml.id project_issue_url(issue.project, issue)
xml.link href: project_issue_url(issue.project, issue)
xml.title truncate(issue.title, length: 80)
xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(issue.author_email)
xml.author do |author|
xml.name issue.author_name
xml.email issue.author_email
end
xml.summary issue.title
end
end
end end
...@@ -25,4 +25,12 @@ module NamespacesHelper ...@@ -25,4 +25,12 @@ module NamespacesHelper
hidden_field_tag(id, value, class: css_class) hidden_field_tag(id, value, class: css_class)
end end
def namespace_icon(namespace, size = 40)
if namespace.kind_of?(Group)
group_icon(namespace.path)
else
avatar_icon(namespace.owner.email, size)
end
end
end end
...@@ -16,4 +16,8 @@ module OauthHelper ...@@ -16,4 +16,8 @@ module OauthHelper
[:twitter, :github, :google_oauth2].include?(name.to_sym) [:twitter, :github, :google_oauth2].include?(name.to_sym)
end end
end end
def additional_providers
enabled_oauth_providers.reject{|provider| provider.to_s.starts_with?('ldap')}
end
end end
module ProfileHelper module ProfileHelper
def oauth_active_class(provider) def oauth_active_class(provider)
if current_user.provider == provider.to_s if current_user.identities.exists?(provider: provider.to_s)
'active' 'active'
end end
end end
...@@ -10,10 +10,10 @@ module ProfileHelper ...@@ -10,10 +10,10 @@ module ProfileHelper
end end
def show_profile_social_tab? def show_profile_social_tab?
enabled_social_providers.any? && !current_user.ldap_user? enabled_social_providers.any?
end end
def show_profile_remove_tab? def show_profile_remove_tab?
gitlab_config.signup_enabled && !current_user.ldap_user? gitlab_config.signup_enabled
end end
end end
module SortingHelper
def sort_title_oldest_updated
'Oldest updated'
end
def sort_title_recently_updated
'Recently updated'
end
def sort_title_oldest_created
'Oldest created'
end
def sort_title_recently_created
'Recently created'
end
end
module Emails module Emails
module Profile module Profile
def new_user_email(user_id, password, token = nil) def new_user_email(user_id, token = nil)
@user = User.find(user_id) @user = User.find(user_id)
@password = password
@target_url = user_url(@user) @target_url = user_url(@user)
@token = token @token = token
mail(to: @user.email, subject: subject("Account was created for you")) mail(to: @user.email, subject: subject("Account was created for you"))
......
...@@ -26,6 +26,14 @@ class Notify < ActionMailer::Base ...@@ -26,6 +26,14 @@ class Notify < ActionMailer::Base
delay_for(2.seconds) delay_for(2.seconds)
end end
def test_email(recepient_email, subject, body)
mail(to: recepient_email,
subject: subject,
body: body.html_safe,
content_type: 'text/html'
)
end
private private
# The default email address to send emails from # The default email address to send emails from
......
...@@ -10,12 +10,12 @@ class Commit ...@@ -10,12 +10,12 @@ class Commit
# Used to prevent 500 error on huge commits by suppressing diff # Used to prevent 500 error on huge commits by suppressing diff
# #
# User can force display of diff above this size # User can force display of diff above this size
DIFF_SAFE_FILES = 100 DIFF_SAFE_FILES = 100 unless defined?(DIFF_SAFE_FILES)
DIFF_SAFE_LINES = 5000 DIFF_SAFE_LINES = 5000 unless defined?(DIFF_SAFE_LINES)
# Commits above this size will not be rendered in HTML # Commits above this size will not be rendered in HTML
DIFF_HARD_LIMIT_FILES = 1000 DIFF_HARD_LIMIT_FILES = 1000 unless defined?(DIFF_HARD_LIMIT_FILES)
DIFF_HARD_LIMIT_LINES = 50000 DIFF_HARD_LIMIT_LINES = 50000 unless defined?(DIFF_HARD_LIMIT_LINES)
class << self class << self
def decorate(commits) def decorate(commits)
......
...@@ -32,7 +32,10 @@ class WebHook < ActiveRecord::Base ...@@ -32,7 +32,10 @@ class WebHook < ActiveRecord::Base
def execute(data) def execute(data)
parsed_url = URI.parse(url) parsed_url = URI.parse(url)
if parsed_url.userinfo.blank? if parsed_url.userinfo.blank?
WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }, verify: false) WebHook.post(url,
body: data.to_json,
headers: { "Content-Type" => "application/json" },
verify: false)
else else
post_url = url.gsub("#{parsed_url.userinfo}@", "") post_url = url.gsub("#{parsed_url.userinfo}@", "")
auth = { auth = {
...@@ -45,6 +48,9 @@ class WebHook < ActiveRecord::Base ...@@ -45,6 +48,9 @@ class WebHook < ActiveRecord::Base
verify: false, verify: false,
basic_auth: auth) basic_auth: auth)
end end
rescue SocketError, Errno::ECONNREFUSED => e
logger.error("WebHook Error => #{e}")
false
end end
def async_execute(data) def async_execute(data)
......
class Identity < ActiveRecord::Base
belongs_to :user
validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider}
end
\ No newline at end of file
...@@ -70,6 +70,16 @@ class MergeRequest < ActiveRecord::Base ...@@ -70,6 +70,16 @@ class MergeRequest < ActiveRecord::Base
transition locked: :reopened transition locked: :reopened
end end
after_transition any => :locked do |merge_request, transition|
merge_request.locked_at = Time.now
merge_request.save
end
after_transition :locked => (any - :locked) do |merge_request, transition|
merge_request.locked_at = nil
merge_request.save
end
state :opened state :opened
state :reopened state :reopened
state :closed state :closed
...@@ -336,4 +346,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -336,4 +346,8 @@ class MergeRequest < ActiveRecord::Base
source_project.repository.branch_names source_project.repository.branch_names
end end
end end
def locked_long_ago?
locked_at && locked_at < (Time.now - 1.day)
end
end end
...@@ -90,4 +90,8 @@ class Namespace < ActiveRecord::Base ...@@ -90,4 +90,8 @@ class Namespace < ActiveRecord::Base
def kind def kind
type == 'Group' ? 'group' : 'user' type == 'Group' ? 'group' : 'user'
end end
def find_fork_of(project)
projects.joins(:forked_project_link).where('forked_project_links.forked_from_project_id = ?', project.id).first
end
end end
...@@ -502,6 +502,6 @@ class Note < ActiveRecord::Base ...@@ -502,6 +502,6 @@ class Note < ActiveRecord::Base
end end
def editable? def editable?
!system !read_attribute(:system)
end end
end end
...@@ -136,7 +136,7 @@ class Project < ActiveRecord::Base ...@@ -136,7 +136,7 @@ class Project < ActiveRecord::Base
state_machine :import_status, initial: :none do state_machine :import_status, initial: :none do
event :import_start do event :import_start do
transition :none => :started transition [:none, :finished] => :started
end end
event :import_finish do event :import_finish do
...@@ -390,14 +390,8 @@ class Project < ActiveRecord::Base ...@@ -390,14 +390,8 @@ class Project < ActiveRecord::Base
end end
def execute_services(data) def execute_services(data)
services.each do |service| services.select(&:active).each do |service|
service.async_execute(data)
# Call service hook only if it is active
begin
service.execute(data) if service.active
rescue => e
logger.error(e)
end
end end
end end
...@@ -586,4 +580,25 @@ class Project < ActiveRecord::Base ...@@ -586,4 +580,25 @@ class Project < ActiveRecord::Base
def origin_merge_requests def origin_merge_requests
merge_requests.where(source_project_id: self.id) merge_requests.where(source_project_id: self.id)
end end
def create_repository
if gitlab_shell.add_repository(path_with_namespace)
true
else
errors.add(:base, "Failed to create repository")
false
end
end
def repository_exists?
!!repository.exists?
end
def create_wiki
ProjectWiki.new(self, self.owner).wiki
true
rescue ProjectWiki::CouldNotCreateWikiError => ex
errors.add(:base, "Failed create wiki")
false
end
end end
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
class HipchatService < Service class HipchatService < Service
MAX_COMMITS = 3 MAX_COMMITS = 3
prop_accessor :token, :room prop_accessor :token, :room, :server
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
def title def title
'Hipchat' 'HipChat'
end end
def description def description
...@@ -33,7 +33,9 @@ class HipchatService < Service ...@@ -33,7 +33,9 @@ class HipchatService < Service
def fields def fields
[ [
{ type: 'text', name: 'token', placeholder: '' }, { type: 'text', name: 'token', placeholder: '' },
{ type: 'text', name: 'room', placeholder: '' } { type: 'text', name: 'room', placeholder: '' },
{ type: 'text', name: 'server',
placeholder: 'Leave blank for default. https://chat.hipchat.com' }
] ]
end end
...@@ -44,7 +46,9 @@ class HipchatService < Service ...@@ -44,7 +46,9 @@ class HipchatService < Service
private private
def gate def gate
@gate ||= HipChat::Client.new(token) options = { api_version: 'v2' }
options[:server_url] = server unless server.nil?
@gate ||= HipChat::Client.new(token, options)
end end
def create_message(push) def create_message(push)
......
...@@ -5,7 +5,7 @@ class ProjectWiki ...@@ -5,7 +5,7 @@ class ProjectWiki
'Markdown' => :markdown, 'Markdown' => :markdown,
'RDoc' => :rdoc, 'RDoc' => :rdoc,
'AsciiDoc' => :asciidoc 'AsciiDoc' => :asciidoc
} } unless defined?(MARKUPS)
class CouldNotCreateWikiError < StandardError; end class CouldNotCreateWikiError < StandardError; end
......
...@@ -82,4 +82,8 @@ class Service < ActiveRecord::Base ...@@ -82,4 +82,8 @@ class Service < ActiveRecord::Base
} }
end end
end end
def async_execute(data)
Sidekiq::Client.enqueue(ProjectServiceWorker, id, data)
end
end end
...@@ -29,7 +29,9 @@ class Snippet < ActiveRecord::Base ...@@ -29,7 +29,9 @@ class Snippet < ActiveRecord::Base
validates :author, presence: true validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 } validates :title, presence: true, length: { within: 0..255 }
validates :file_name, presence: true, length: { within: 0..255 } validates :file_name, presence: true, length: { within: 0..255 },
format: { with: Gitlab::Regex.path_regex,
message: Gitlab::Regex.path_regex_message }
validates :content, presence: true validates :content, presence: true
validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values } validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
...@@ -62,6 +64,10 @@ class Snippet < ActiveRecord::Base ...@@ -62,6 +64,10 @@ class Snippet < ActiveRecord::Base
file_name file_name
end end
def sanitized_file_name
file_name.gsub(/[^a-zA-Z0-9_\-\.]+/, '')
end
def mode def mode
nil nil
end end
......
...@@ -79,6 +79,7 @@ class User < ActiveRecord::Base ...@@ -79,6 +79,7 @@ class User < ActiveRecord::Base
# Profile # Profile
has_many :keys, dependent: :destroy has_many :keys, dependent: :destroy
has_many :emails, dependent: :destroy has_many :emails, dependent: :destroy
has_many :identities, dependent: :destroy
# Groups # Groups
has_many :members, dependent: :destroy has_many :members, dependent: :destroy
...@@ -113,7 +114,6 @@ class User < ActiveRecord::Base ...@@ -113,7 +114,6 @@ class User < ActiveRecord::Base
validates :name, presence: true validates :name, presence: true
validates :email, presence: true, email: {strict_mode: true}, uniqueness: true validates :email, presence: true, email: {strict_mode: true}, uniqueness: true
validates :bio, length: { maximum: 255 }, allow_blank: true validates :bio, length: { maximum: 255 }, allow_blank: true
validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider}
validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0} validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0}
validates :username, presence: true, uniqueness: { case_sensitive: false }, validates :username, presence: true, uniqueness: { case_sensitive: false },
exclusion: { in: Gitlab::Blacklist.path }, exclusion: { in: Gitlab::Blacklist.path },
...@@ -124,7 +124,7 @@ class User < ActiveRecord::Base ...@@ -124,7 +124,7 @@ class User < ActiveRecord::Base
validate :namespace_uniq, if: ->(user) { user.username_changed? } validate :namespace_uniq, if: ->(user) { user.username_changed? }
validate :avatar_type, if: ->(user) { user.avatar_changed? } validate :avatar_type, if: ->(user) { user.avatar_changed? }
validate :unique_email, if: ->(user) { user.email_changed? } validate :unique_email, if: ->(user) { user.email_changed? }
validates :avatar, file_size: { maximum: 100.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
before_validation :generate_password, on: :create before_validation :generate_password, on: :create
before_validation :sanitize_attrs before_validation :sanitize_attrs
...@@ -178,7 +178,6 @@ class User < ActiveRecord::Base ...@@ -178,7 +178,6 @@ class User < ActiveRecord::Base
scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) } scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') } scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
scope :ldap, -> { where('provider LIKE ?', 'ldap%') }
scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active } scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
# #
...@@ -407,7 +406,11 @@ class User < ActiveRecord::Base ...@@ -407,7 +406,11 @@ class User < ActiveRecord::Base
end end
def ldap_user? def ldap_user?
extern_uid && provider.start_with?('ldap') identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
end
def ldap_identity
@ldap_identity ||= identities.find_by(["provider LIKE ?", "ldap%"])
end end
def accessible_deploy_keys def accessible_deploy_keys
...@@ -551,4 +554,14 @@ class User < ActiveRecord::Base ...@@ -551,4 +554,14 @@ class User < ActiveRecord::Base
UsersStarProject.create!(project: project, user: self) UsersStarProject.create!(project: project, user: self)
end end
end end
def manageable_namespaces
@manageable_namespaces ||=
begin
namespaces = []
namespaces << namespace
namespaces += owned_groups
namespaces += masters_groups
end
end
end end
...@@ -3,6 +3,7 @@ module MergeRequests ...@@ -3,6 +3,7 @@ module MergeRequests
def execute(oldrev, newrev, ref) def execute(oldrev, newrev, ref)
return true unless ref =~ /heads/ return true unless ref =~ /heads/
@oldrev, @newrev = oldrev, newrev
@branch_name = ref.gsub("refs/heads/", "") @branch_name = ref.gsub("refs/heads/", "")
@fork_merge_requests = @project.fork_merge_requests.opened @fork_merge_requests = @project.fork_merge_requests.opened
@commits = @project.repository.commits_between(oldrev, newrev) @commits = @project.repository.commits_between(oldrev, newrev)
...@@ -35,6 +36,10 @@ module MergeRequests ...@@ -35,6 +36,10 @@ module MergeRequests
end end
end end
def force_push?
Gitlab::ForcePushCheck.force_push?(@project, @oldrev, @newrev)
end
# Refresh merge request diff if we push to source or target branch of merge request # Refresh merge request diff if we push to source or target branch of merge request
# Note: we should update merge requests from forks too # Note: we should update merge requests from forks too
def reload_merge_requests def reload_merge_requests
...@@ -43,8 +48,22 @@ module MergeRequests ...@@ -43,8 +48,22 @@ module MergeRequests
merge_requests = filter_merge_requests(merge_requests) merge_requests = filter_merge_requests(merge_requests)
merge_requests.each do |merge_request| merge_requests.each do |merge_request|
if merge_request.source_branch == @branch_name || force_push?
merge_request.reload_code merge_request.reload_code
merge_request.mark_as_unchecked merge_request.mark_as_unchecked
else
mr_commit_ids = merge_request.commits.map(&:id)
push_commit_ids = @commits.map(&:id)
matches = mr_commit_ids & push_commit_ids
if matches.any?
merge_request.reload_code
merge_request.mark_as_unchecked
else
merge_request.mark_as_unchecked
end
end
end end
end end
......
...@@ -107,7 +107,7 @@ class NotificationService ...@@ -107,7 +107,7 @@ class NotificationService
# Notify new user with email after creation # Notify new user with email after creation
def new_user(user, token = nil) def new_user(user, token = nil)
# Don't email omniauth created users # Don't email omniauth created users
mailer.new_user_email(user.id, user.password, token) unless user.extern_uid? mailer.new_user_email(user.id, token) unless user.identities.any?
end end
# Notify users on new note in system # Notify users on new note in system
......
...@@ -37,35 +37,22 @@ module Projects ...@@ -37,35 +37,22 @@ module Projects
@project.creator = current_user @project.creator = current_user
if @project.save Project.transaction do
log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"") @project.save
system_hook_service.execute_hooks_for(@project, :create)
unless @project.group unless @project.import?
@project.team << [current_user, :master] unless @project.create_repository
raise 'Failed to create repository'
end
end end
@project.update_column(:last_activity_at, @project.created_at)
if @project.import?
@project.import_start
else
GitlabShellWorker.perform_async(
:add_repository,
@project.path_with_namespace
)
end end
if @project.persisted?
if @project.wiki_enabled? if @project.wiki_enabled?
begin @project.create_wiki
# force the creation of a wiki,
ProjectWiki.new(@project, @project.owner).wiki
rescue ProjectWiki::CouldNotCreateWikiError => ex
# Prevent project observer crash
# if failed to create wiki
nil
end
end end
after_create_actions
end end
@project @project
...@@ -84,5 +71,20 @@ module Projects ...@@ -84,5 +71,20 @@ module Projects
namespace = Namespace.find_by(id: namespace_id) namespace = Namespace.find_by(id: namespace_id)
current_user.can?(:create_projects, namespace) current_user.can?(:create_projects, namespace)
end end
def after_create_actions
log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"")
system_hook_service.execute_hooks_for(@project, :create)
unless @project.group
@project.team << [current_user, :master]
end
@project.update_column(:last_activity_at, @project.created_at)
if @project.import?
@project.import_start
end
end
end end
end end
...@@ -2,11 +2,9 @@ module Projects ...@@ -2,11 +2,9 @@ module Projects
class ForkService < BaseService class ForkService < BaseService
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
def initialize(project, user)
@from_project, @current_user = project, user
end
def execute def execute
@from_project = @project
project_params = { project_params = {
visibility_level: @from_project.visibility_level, visibility_level: @from_project.visibility_level,
description: @from_project.description, description: @from_project.description,
...@@ -15,8 +13,18 @@ module Projects ...@@ -15,8 +13,18 @@ module Projects
project = Project.new(project_params) project = Project.new(project_params)
project.name = @from_project.name project.name = @from_project.name
project.path = @from_project.path project.path = @from_project.path
project.namespace = current_user.namespace project.creator = @current_user
project.creator = current_user
if namespace = @params[:namespace]
project.namespace = namespace
else
project.namespace = @current_user.namespace
end
unless @current_user.can?(:create_projects, project.namespace)
project.errors.add(:namespace, 'insufficient access rights')
return project
end
# If the project cannot save, we do not want to trigger the project destroy # If the project cannot save, we do not want to trigger the project destroy
# as this can have the side effect of deleting a repo attached to an existing # as this can have the side effect of deleting a repo attached to an existing
...@@ -27,7 +35,7 @@ module Projects ...@@ -27,7 +35,7 @@ module Projects
#First save the DB entries as they can be rolled back if the repo fork fails #First save the DB entries as they can be rolled back if the repo fork fails
project.build_forked_project_link(forked_to_project_id: project.id, forked_from_project_id: @from_project.id) project.build_forked_project_link(forked_to_project_id: project.id, forked_from_project_id: @from_project.id)
if project.save if project.save
project.team << [current_user, :master] project.team << [@current_user, :master]
end end
#Now fork the repo #Now fork the repo
unless gitlab_shell.fork_repository(@from_project.path_with_namespace, project.namespace.path) unless gitlab_shell.fork_repository(@from_project.path_with_namespace, project.namespace.path)
...@@ -42,8 +50,8 @@ module Projects ...@@ -42,8 +50,8 @@ module Projects
else else
project.errors.add(:base, "Invalid fork destination") project.errors.add(:base, "Invalid fork destination")
end end
project
project
end end
end end
end end
...@@ -2,8 +2,5 @@ class TestHookService ...@@ -2,8 +2,5 @@ class TestHookService
def execute(hook, current_user) def execute(hook, current_user)
data = GitPushService.new.sample_data(hook.project, current_user) data = GitPushService.new.sample_data(hook.project, current_user)
hook.execute(data) hook.execute(data)
true
rescue SocketError
false
end end
end end
...@@ -26,10 +26,6 @@ class AttachmentUploader < CarrierWave::Uploader::Base ...@@ -26,10 +26,6 @@ class AttachmentUploader < CarrierWave::Uploader::Base
Gitlab.config.gitlab.relative_url_root + "/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}" Gitlab.config.gitlab.relative_url_root + "/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}"
end end
def url
Gitlab.config.gitlab.relative_url_root + super unless super.nil?
end
def file_storage? def file_storage?
self.class.storage == CarrierWave::Storage::File self.class.storage == CarrierWave::Storage::File
end end
......
...@@ -56,13 +56,13 @@ ...@@ -56,13 +56,13 @@
= link_to admin_projects_path(sort: nil) do = link_to admin_projects_path(sort: nil) do
Name Name
= link_to admin_projects_path(sort: 'newest') do = link_to admin_projects_path(sort: 'newest') do
Newest = sort_title_recently_created
= link_to admin_projects_path(sort: 'oldest') do = link_to admin_projects_path(sort: 'oldest') do
Oldest = sort_title_oldest_created
= link_to admin_projects_path(sort: 'recently_updated') do = link_to admin_projects_path(sort: 'recently_updated') do
Recently updated = sort_title_recently_updated
= link_to admin_projects_path(sort: 'last_updated') do = link_to admin_projects_path(sort: 'last_updated') do
Last updated = sort_title_oldest_updated
= link_to admin_projects_path(sort: 'largest_repository') do = link_to admin_projects_path(sort: 'largest_repository') do
Largest repository Largest repository
= link_to 'New Project', new_project_path, class: "btn btn-new" = link_to 'New Project', new_project_path, class: "btn btn-new"
......
...@@ -49,9 +49,10 @@ ...@@ -49,9 +49,10 @@
= link_to admin_users_path(sort: 'oldest_sign_in') do = link_to admin_users_path(sort: 'oldest_sign_in') do
Oldest sign in Oldest sign in
= link_to admin_users_path(sort: 'recently_created') do = link_to admin_users_path(sort: 'recently_created') do
Recently created = sort_title_recently_created
= link_to admin_users_path(sort: 'late_created') do = link_to admin_users_path(sort: 'late_created') do
Late created = sort_title_oldest_created
= link_to 'New User', new_admin_user_path, class: "btn btn-new" = link_to 'New User', new_admin_user_path, class: "btn btn-new"
%ul.well-list %ul.well-list
- @users.each do |user| - @users.each do |user|
......
...@@ -95,7 +95,7 @@ ...@@ -95,7 +95,7 @@
%li %li
%span.light LDAP uid: %span.light LDAP uid:
%strong %strong
= @user.extern_uid = @user.ldap_identity.extern_uid
- if @user.created_by - if @user.created_by
%li %li
......
xml.instruct! xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
xml.title "#{current_user.name} issues" xml.title "#{current_user.name} issues"
xml.link :href => issues_dashboard_url(:atom, :private_token => current_user.private_token), :rel => "self", :type => "application/atom+xml" xml.link href: issues_dashboard_url(:atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml"
xml.link :href => issues_dashboard_url(:private_token => current_user.private_token), :rel => "alternate", :type => "text/html" xml.link href: issues_dashboard_url(private_token: current_user.private_token), rel: "alternate", type: "text/html"
xml.id issues_dashboard_url(:private_token => current_user.private_token) xml.id issues_dashboard_url(private_token: current_user.private_token)
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue| @issues.each do |issue|
xml.entry do issue_to_atom(xml, issue)
xml.id project_issue_url(issue.project, issue)
xml.link :href => project_issue_url(issue.project, issue)
xml.title truncate(issue.title, :length => 80)
xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
xml.author do |author|
xml.name issue.author_name
xml.email issue.author_email
end
xml.summary issue.title
end
end end
end end
...@@ -14,13 +14,14 @@ ...@@ -14,13 +14,14 @@
= link_to projects_dashboard_filter_path(sort: nil) do = link_to projects_dashboard_filter_path(sort: nil) do
Name Name
= link_to projects_dashboard_filter_path(sort: 'newest') do = link_to projects_dashboard_filter_path(sort: 'newest') do
Newest = sort_title_recently_created
= link_to projects_dashboard_filter_path(sort: 'oldest') do = link_to projects_dashboard_filter_path(sort: 'oldest') do
Oldest = sort_title_oldest_created
= link_to projects_dashboard_filter_path(sort: 'recently_updated') do = link_to projects_dashboard_filter_path(sort: 'recently_updated') do
Recently updated = sort_title_recently_updated
= link_to projects_dashboard_filter_path(sort: 'last_updated') do = link_to projects_dashboard_filter_path(sort: 'last_updated') do
Last updated = sort_title_oldest_updated
%p.light %p.light
All projects you have access to are listed here. Public projects are not included here unless you are a member All projects you have access to are listed here. Public projects are not included here unless you are a member
%hr %hr
......
xml.instruct! xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
xml.title "Dashboard feed#{" - #{current_user.name}" if current_user.name.present?}" xml.title "Dashboard feed#{" - #{current_user.name}" if current_user.name.present?}"
xml.link :href => dashboard_url(:atom), :rel => "self", :type => "application/atom+xml" xml.link href: dashboard_url(:atom), rel: "self", type: "application/atom+xml"
xml.link :href => dashboard_url, :rel => "alternate", :type => "text/html" xml.link href: dashboard_url, rel: "alternate", type: "text/html"
xml.id projects_url xml.id projects_url
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event| @events.each do |event|
if event.proper? event_to_atom(xml, event)
xml.entry do
event_link = event_feed_url(event)
event_title = event_feed_title(event)
event_summary = event_feed_summary(event)
xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
xml.link :href => event_link
xml.title truncate(event_title, :length => 80)
xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email)
xml.author do |author|
xml.name event.author_name
xml.email event.author_email
end
xml.summary(:type => "xhtml") { |x| x << event_summary unless event_summary.nil? }
end
end
end end
end end
- providers = (enabled_oauth_providers - [:ldap]) - providers = additional_providers
- if providers.present? - if providers.present?
.bs-callout.bs-callout-info{:'data-no-turbolink' => 'data-no-turbolink'} .bs-callout.bs-callout-info{:'data-no-turbolink' => 'data-no-turbolink'}
%span Sign in with: &nbsp; %span Sign in with: &nbsp;
......
...@@ -20,13 +20,13 @@ ...@@ -20,13 +20,13 @@
= link_to explore_groups_path(sort: nil) do = link_to explore_groups_path(sort: nil) do
Name Name
= link_to explore_groups_path(sort: 'newest') do = link_to explore_groups_path(sort: 'newest') do
Newest = sort_title_recently_created
= link_to explore_groups_path(sort: 'oldest') do = link_to explore_groups_path(sort: 'oldest') do
Oldest = sort_title_oldest_created
= link_to explore_groups_path(sort: 'recently_updated') do = link_to explore_groups_path(sort: 'recently_updated') do
Recently updated = sort_title_recently_updated
= link_to explore_groups_path(sort: 'last_updated') do = link_to explore_groups_path(sort: 'last_updated') do
Last updated = sort_title_oldest_updated
%hr %hr
......
...@@ -20,13 +20,13 @@ ...@@ -20,13 +20,13 @@
= link_to explore_projects_path(sort: nil) do = link_to explore_projects_path(sort: nil) do
Name Name
= link_to explore_projects_path(sort: 'newest') do = link_to explore_projects_path(sort: 'newest') do
Newest = sort_title_recently_created
= link_to explore_projects_path(sort: 'oldest') do = link_to explore_projects_path(sort: 'oldest') do
Oldest = sort_title_oldest_created
= link_to explore_projects_path(sort: 'recently_updated') do = link_to explore_projects_path(sort: 'recently_updated') do
Recently updated = sort_title_recently_updated
= link_to explore_projects_path(sort: 'last_updated') do = link_to explore_projects_path(sort: 'last_updated') do
Last updated = sort_title_oldest_updated
%hr %hr
.public-projects .public-projects
......
...@@ -7,18 +7,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear ...@@ -7,18 +7,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue| @issues.each do |issue|
xml.entry do issue_to_atom(xml, issue)
xml.id project_issue_url(issue.project, issue)
xml.link :href => project_issue_url(issue.project, issue)
xml.title truncate(issue.title, :length => 80)
xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
xml.author do |author|
xml.name issue.author_name
xml.email issue.author_email
end
xml.summary issue.title
end
end end
end end
xml.instruct! xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
xml.title "Group feed - #{@group.name}" xml.title "Group feed - #{@group.name}"
xml.link :href => group_path(@group, :atom), :rel => "self", :type => "application/atom+xml" xml.link href: group_path(@group, :atom), rel: "self", type: "application/atom+xml"
xml.link :href => group_path(@group), :rel => "alternate", :type => "text/html" xml.link href: group_path(@group), rel: "alternate", type: "text/html"
xml.id projects_url xml.id projects_url
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event| @events.each do |event|
if event.proper? event_to_atom(xml, event)
xml.entry do
event_link = event_feed_url(event)
event_title = event_feed_title(event)
xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
xml.link :href => event_link
xml.title truncate(event_title, :length => 80)
xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email)
xml.author do |author|
xml.name event.author_name
xml.email event.author_email
end
xml.summary event_title
end
end
end end
end end
...@@ -4,7 +4,16 @@ ...@@ -4,7 +4,16 @@
= hidden_field_tag :group_id, @group.try(:id) = hidden_field_tag :group_id, @group.try(:id)
- if @project && @project.persisted? - if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id = hidden_field_tag :project_id, @project.id
- if current_controller?(:issues)
= hidden_field_tag :scope, 'issues'
- elsif current_controller?(:merge_requests)
= hidden_field_tag :scope, 'merge_requests'
- elsif current_controller?(:wikis)
= hidden_field_tag :scope, 'wiki_blobs'
- else
= hidden_field_tag :search_code, true = hidden_field_tag :search_code, true
- if @snippet || @snippets - if @snippet || @snippets
= hidden_field_tag :snippets, true = hidden_field_tag :snippets, true
= hidden_field_tag :repository_ref, @ref = hidden_field_tag :repository_ref, @ref
......
...@@ -83,7 +83,7 @@ ...@@ -83,7 +83,7 @@
&nbsp; &nbsp;
%span.file_name.js-avatar-filename File name... %span.file_name.js-avatar-filename File name...
= f.file_field :avatar, class: "js-user-avatar-input hidden" = f.file_field :avatar, class: "js-user-avatar-input hidden"
.light The maximum file size allowed is 100KB. .light The maximum file size allowed is 200KB.
- if @user.avatar? - if @user.avatar?
%hr %hr
= link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar" = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar"
......
...@@ -16,11 +16,11 @@ ...@@ -16,11 +16,11 @@
- unless @project.empty_repo? - unless @project.empty_repo?
.fork-buttons .fork-buttons
- if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace - if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace
- if current_user.already_forked?(@project) - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to project_path(current_user.fork_of(@project)), title: 'Go to my fork' do = link_to project_path(current_user.fork_of(@project)), title: 'Go to my fork' do
= link_to_toggle_fork = link_to_toggle_fork
- else - else
= link_to fork_project_path(@project), title: "Fork project", method: "POST" do = link_to new_project_fork_path(@project), title: "Fork project" do
= link_to_toggle_fork = link_to_toggle_fork
.star-buttons .star-buttons
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
- else - else
= link_to_toggle_star('You must sign in to star a project.', false, false) = link_to_toggle_star('You must sign in to star a project.', false, false)
.project-home-row .project-home-row.hidden-xs
- if current_user && !empty_repo - if current_user && !empty_repo
.project-home-dropdown .project-home-dropdown
= render "dropdown" = render "dropdown"
......
.issues-filters
.dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%i.fa.fa-user
%span.light assignee:
- if @assignee.present?
%strong= @assignee.name
- elsif params[:assignee_id] == "0"
Unassigned
- else
Any
%b.caret
%ul.dropdown-menu
%li
= link_to project_filter_path(assignee_id: nil) do
Any
= link_to project_filter_path(assignee_id: 0) do
Unassigned
- @assignees.sort_by(&:name).each do |user|
%li
= link_to project_filter_path(assignee_id: user.id) do
= image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
%span{ dir: :auto }= user.name
.dropdown.inline.prepend-left-10
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%i.fa.fa-user
%span.light author:
- if @author.present?
%strong{ dir: :auto }= @author.name
- elsif params[:author_id] == "0"
Unassigned
- else
Any
%b.caret
%ul.dropdown-menu
%li
= link_to project_filter_path(author_id: nil) do
Any
= link_to project_filter_path(author_id: 0) do
Unassigned
- @authors.sort_by(&:name).each do |user|
%li
= link_to project_filter_path(author_id: user.id) do
= image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
%span{ dir: :auto }= user.name
.dropdown.inline.prepend-left-10
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%i.fa.fa-clock-o
%span.light milestone:
- if @milestone.present?
%strong{ dir: :auto }= @milestone.title
- elsif params[:milestone_id] == "0"
None (backlog)
- else
Any
%b.caret
%ul.dropdown-menu
%li
= link_to project_filter_path(milestone_id: nil) do
Any
= link_to project_filter_path(milestone_id: 0) do
None (backlog)
- project_active_milestones.each do |milestone|
%li
= link_to project_filter_path(milestone_id: milestone.id) do
%strong{ dir: :auto }= milestone.title
%small.light{ dir: :auto }= milestone.expires_at
.pull-right
= render 'shared/sort_dropdown'
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
.form-group.issuable-description .form-group.issuable-description
= f.label :description, 'Description', class: 'control-label' = f.label :description, 'Description', class: 'control-label'
.col-sm-10 .col-sm-10
= render layout: 'projects/md_preview' do
= render 'projects/zen', f: f, attr: :description, = render 'projects/zen', f: f, attr: :description,
classes: 'description form-control' classes: 'description form-control'
.col-sm-12.hint .col-sm-12.hint
...@@ -23,6 +25,7 @@ ...@@ -23,6 +25,7 @@
.pull-right .pull-right
Attach images (JPG, PNG, GIF) by dragging &amp; dropping Attach images (JPG, PNG, GIF) by dragging &amp; dropping
or #{link_to 'selecting them', '#', class: 'markdown-selector' }. or #{link_to 'selecting them', '#', class: 'markdown-selector' }.
.clearfix .clearfix
.error-alert .error-alert
%hr %hr
...@@ -49,7 +52,7 @@ ...@@ -49,7 +52,7 @@
- else - else
%span.light No open milestones available. %span.light No open milestones available.
&nbsp; &nbsp;
= link_to 'Create new milestone', new_project_milestone_path(issuable.project) = link_to 'Create new milestone', new_project_milestone_path(issuable.project), target: :blank
.form-group .form-group
= f.label :label_ids, class: 'control-label' do = f.label :label_ids, class: 'control-label' do
%i.icon-tag %i.icon-tag
...@@ -61,7 +64,7 @@ ...@@ -61,7 +64,7 @@
- else - else
%span.light No labels yet. %span.light No labels yet.
&nbsp; &nbsp;
= link_to 'Create new label', new_project_label_path(issuable.project) = link_to 'Create new label', new_project_label_path(issuable.project), target: :blank
.form-actions .form-actions
- if issuable.new_record? - if issuable.new_record?
......
%ul.nav.nav-tabs %ul.nav.nav-tabs
- if project_nav_tab? :issues
= nav_link(controller: :issues) do = nav_link(controller: :issues) do
= link_to project_issues_path(@project), class: "tab" do = link_to project_issues_path(@project), class: "tab" do
Browse Issues Issues
- if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do
= link_to project_merge_requests_path(@project), class: "tab" do
Merge Requests
= nav_link(controller: :milestones) do = nav_link(controller: :milestones) do
= link_to 'Milestones', project_milestones_path(@project), class: "tab" = link_to 'Milestones', project_milestones_path(@project), class: "tab"
= nav_link(controller: :labels) do = nav_link(controller: :labels) do
...@@ -14,7 +19,7 @@ ...@@ -14,7 +19,7 @@
- if current_controller?(:issues) - if current_controller?(:issues)
- if current_user - if current_user
%li %li.hidden-xs
= link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do = link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do
%i.fa.fa-rss %i.fa.fa-rss
...@@ -22,6 +27,8 @@ ...@@ -22,6 +27,8 @@
.pull-right .pull-right
%button.btn.btn-default.sidebar-expand-button %button.btn.btn-default.sidebar-expand-button
%i.icon.fa.fa-list %i.icon.fa.fa-list
.pull-left
= form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do = form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do
.append-right-10.hidden-xs.hidden-sm .append-right-10.hidden-xs.hidden-sm
= search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300', dir: :auto } = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300', dir: :auto }
...@@ -30,7 +37,19 @@ ...@@ -30,7 +37,19 @@
= hidden_field_tag :assignee_id, params['assignee_id'] = hidden_field_tag :assignee_id, params['assignee_id']
= hidden_field_tag :milestone_id, params['milestone_id'] = hidden_field_tag :milestone_id, params['milestone_id']
= hidden_field_tag :label_id, params['label_id'] = hidden_field_tag :label_id, params['label_id']
- if can? current_user, :write_issue, @project - if can? current_user, :write_issue, @project
= link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do = link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do
%i.fa.fa-plus %i.fa.fa-plus
New Issue New Issue
- if current_controller?(:merge_requests)
%li.pull-right
.pull-right
%button.btn.btn-default.sidebar-expand-button
%i.icon.fa.fa-list
- if can? current_user, :write_merge_request, @project
= link_to new_project_merge_request_path(@project), class: "btn btn-new pull-left", title: "New Merge Request" do
%i.fa.fa-plus
New Merge Request
%ul.nav.nav-tabs
%li.active
= link_to '#md-write-holder', class: 'js-md-write-button' do
Write
%li
= link_to '#md-preview-holder', class: 'js-md-preview-button',
data: { url: markdown_preview_project_path(@project) } do
Preview
%div
.md-write-holder
= yield
.md-preview-holder.hide
.js-md-preview
...@@ -20,9 +20,9 @@ ...@@ -20,9 +20,9 @@
= link_to project_branches_path(sort: nil) do = link_to project_branches_path(sort: nil) do
Name Name
= link_to project_branches_path(sort: 'recently_updated') do = link_to project_branches_path(sort: 'recently_updated') do
Recently updated = sort_title_recently_updated
= link_to project_branches_path(sort: 'last_updated') do = link_to project_branches_path(sort: 'last_updated') do
Last updated = sort_title_oldest_updated
%hr %hr
- unless @branches.empty? - unless @branches.empty?
%ul.bordered-list.top-list.all-branches %ul.bordered-list.top-list.all-branches
......
- if @project.saved?
- if @project.import?
:plain
location.href = "#{import_project_path(@project)}";
- else
:plain
location.href = "#{project_path(@project)}";
- else
:plain
$(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
$('.project-submit').enable();
$('.save-project-loader').hide();
$('.project-edit-container').show();
.alert.alert-danger.alert-block
%h4
%i.fa.fa-code-fork
Fork Error!
%p
You tried to fork
= link_to_project @project
but it failed for the following reason:
- if @forked_project && @forked_project.errors.any?
%p
&ndash;
= @forked_project.errors.full_messages.first
%p
= link_to fork_project_path(@project), title: "Fork", class: "btn", method: "POST" do
%i.fa.fa-code-fork
Try to Fork again
- if @forked_project && !@forked_project.saved?
.alert.alert-danger.alert-block
%h4
%i.fa.fa-code-fork
Fork Error!
%p
You tried to fork
= link_to_project @project
but it failed for the following reason:
- if @forked_project && @forked_project.errors.any?
%p
&ndash;
= @forked_project.errors.full_messages.first
%p
= link_to new_project_fork_path(@project), title: "Fork", class: "btn" do
%i.fa.fa-code-fork
Try to Fork again
%h3.page-title Fork project
%p.lead Select namespace where to fork this project
%hr
.fork-namespaces
- @namespaces.in_groups_of(6, false) do |group|
.row
- group.each do |namespace|
.col-md-2.col-sm-3
- if fork = namespace.find_fork_of(@project)
.thumbnail.fork-exists-thumbnail
= link_to project_path(fork), title: "Visit project fork", class: 'has_tooltip' do
= image_tag namespace_icon(namespace, 200)
.caption
%h4=namespace.human_name
%p
= namespace.path
- else
.thumbnail.fork-thumbnail
= link_to project_fork_path(@project, namespace_id: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do
= image_tag namespace_icon(namespace, 200)
.caption
%h4=namespace.human_name
%p
= namespace.path
%p.light
Fork is a copy of a project repository.
%br
Forking a repository allows you to do changes without affecting the original project.
.save-project-loader.hide
.center
%h2
%i.fa.fa-spinner.fa-spin
Forking repository
%p Please wait a moment, this page will automatically refresh when ready.
- if @project.import_in_progress?
.save-project-loader
.center
%h2
%i.fa.fa-spinner.fa-spin
Import in progress.
%p.monospace git clone --bare #{hidden_pass_url(@project.import_url)}
%p Please wait while we import the repository for you. Refresh at will.
:javascript
new ProjectImport();
- elsif @project.import_failed?
.save-project-loader
.center
%h2
Import failed. Retry?
%hr
- if can?(current_user, :admin_project, @project)
= form_for @project, url: retry_import_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f|
.form-group.import-url-data
= f.label :import_url, class: 'control-label' do
%span Import existing git repo
.col-sm-10
= f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
.bs-callout.bs-callout-info
This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
%br
The import will time out after 4 minutes. For big repositories, use a clone/push combination.
For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}
.form-actions
= f.submit 'Retry import', class: "btn btn-create", tabindex: 4
%h3.page-title
- if @project.import_failed?
Import failed. Retry?
- else
Import repository
%hr
= form_for @project, url: project_import_path(@project), method: :post, html: { class: 'form-horizontal' } do |f|
.form-group.import-url-data
= f.label :import_url, class: 'control-label' do
%span Import existing git repo
.col-sm-10
= f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
.bs-callout.bs-callout-info
This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
%br
The import will time out after 4 minutes. For big repositories, use a clone/push combination.
For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}
.form-actions
= f.submit 'Start import', class: "btn btn-create", tabindex: 4
.save-project-loader
.center
%h2
%i.fa.fa-spinner.fa-spin
Import in progress.
%p.monospace git clone --bare #{hidden_pass_url(@project.import_url)}
%p Please wait while we import the repository for you. Refresh at will.
:javascript
new ProjectImport();
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
= check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue", disabled: !can?(current_user, :modify_issue, issue) = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue", disabled: !can?(current_user, :modify_issue, issue)
.issue-title .issue-title
%span.light= "##{issue.iid}"
%span.str-truncated %span.str-truncated
= link_to_gfm issue.title, project_issue_path(issue.project, issue), class: "row_title", dir: 'auto' = link_to_gfm issue.title, project_issue_path(issue.project, issue), class: "row_title", dir: 'auto'
- if issue.closed? - if issue.closed?
...@@ -12,6 +11,7 @@ ...@@ -12,6 +11,7 @@
CLOSED CLOSED
.issue-info .issue-info
%span.light= "##{issue.iid}"
- if issue.assignee - if issue.assignee
assigned to #{link_to_member(@project, issue.assignee)} assigned to #{link_to_member(@project, issue.assignee)}
- if issue.votes_count > 0 - if issue.votes_count > 0
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
%span.task-status %span.task-status
= issue.task_status = issue.task_status
.pull-right .pull-right.issue-updated-at
%small updated #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_update_ago')} %small updated #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_update_ago')}
.issue-labels .issue-labels
......
.append-bottom-10 .append-bottom-10
.check-all-holder .check-all-holder
= check_box_tag "check_all_issues", nil, false, class: "check_all_issues left" = check_box_tag "check_all_issues", nil, false, class: "check_all_issues left"
.issues-filters = render 'projects/issuable_filter'
.dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%i.fa.fa-user
%span.light assignee:
- if @assignee.present?
%strong= @assignee.name
- elsif params[:assignee_id] == "0"
Unassigned
- else
Any
%b.caret
%ul.dropdown-menu
%li
= link_to project_filter_path(assignee_id: nil) do
Any
= link_to project_filter_path(assignee_id: 0) do
Unassigned
- @assignees.sort_by(&:name).each do |user|
%li
= link_to project_filter_path(assignee_id: user.id) do
= image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
= user.name
.dropdown.inline.prepend-left-10
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%i.fa.fa-clock-o
%span.light milestone:
- if @milestone.present?
%strong= @milestone.title
- elsif params[:milestone_id] == "0"
None (backlog)
- else
Any
%b.caret
%ul.dropdown-menu
%li
= link_to project_filter_path(milestone_id: nil) do
Any
= link_to project_filter_path(milestone_id: 0) do
None (backlog)
- project_active_milestones.each do |milestone|
%li
= link_to project_filter_path(milestone_id: milestone.id) do
%strong= milestone.title
%small.light= milestone.expires_at
.pull-right
= render 'shared/sort_dropdown'
.clearfix .clearfix
.issues_bulk_update.hide .issues_bulk_update.hide
......
...@@ -7,17 +7,6 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear ...@@ -7,17 +7,6 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue| @issues.each do |issue|
xml.entry do issue_to_atom(xml, issue)
xml.id project_issue_url(@project, issue)
xml.link :href => project_issue_url(@project, issue)
xml.title truncate(issue.title, :length => 80)
xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
xml.author do |author|
xml.name issue.author_name
xml.email issue.author_email
end
xml.summary issue.title
end
end end
end end
= render "head" = render "projects/issues_nav"
.row .row
.fixed.fixed.sidebar-expand-button.hidden-lg.hidden-md.hidden-xs .fixed.fixed.sidebar-expand-button.hidden-lg.hidden-md.hidden-xs
%i.fa.fa-list.fa-2x %i.fa.fa-list.fa-2x
......
...@@ -38,6 +38,10 @@ ...@@ -38,6 +38,10 @@
- else - else
Open Open
.cross-project-ref
%i.fa.fa-link.has_tooltip{:"data-original-title" => 'Cross-project reference'}
= cross_project_reference(@project, @issue)
.creator .creator
Created by #{link_to_member(@project, @issue.author)} #{issue_timestamp(@issue)} Created by #{link_to_member(@project, @issue.author)} #{issue_timestamp(@issue)}
......
= render "projects/issues/head" = render "projects/issues_nav"
- if can? current_user, :admin_label, @project - if can? current_user, :admin_label, @project
= link_to new_project_label_path(@project), class: "pull-right btn btn-new" do = link_to new_project_label_path(@project), class: "pull-right btn btn-new" do
......
%li{ class: mr_css_classes(merge_request) } %li{ class: mr_css_classes(merge_request) }
.merge-request-title .merge-request-title
%span.light= "##{merge_request.iid}" = link_to_gfm truncate(merge_request.title, length: 80), project_merge_request_path(merge_request.target_project, merge_request), class: "row_title", dir: :auto
= link_to_gfm truncate(merge_request.title, length: 80), project_merge_request_path(merge_request.target_project, merge_request), class: "row_title", dir: "auto"
- if merge_request.merged? - if merge_request.merged?
%small.pull-right %small.pull-right
%i.fa.fa-check %i.fa.fa-check
MERGED MERGED
- else - else
%span.pull-right %span.pull-right.hidden-xs
- if merge_request.for_fork? - if merge_request.for_fork?
%span.light %span.light
#{merge_request.source_project_namespace}: #{merge_request.source_project_namespace}:
...@@ -15,6 +14,7 @@ ...@@ -15,6 +14,7 @@
%i.fa.fa-angle-right.light %i.fa.fa-angle-right.light
= merge_request.target_branch = merge_request.target_branch
.merge-request-info .merge-request-info
%span.light= "##{merge_request.iid}"
- if merge_request.author - if merge_request.author
authored by #{link_to_member(merge_request.source_project, merge_request.author)} authored by #{link_to_member(merge_request.source_project, merge_request.author)}
- if merge_request.votes_count > 0 - if merge_request.votes_count > 0
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
%span.task-status %span.task-status
= merge_request.task_status = merge_request.task_status
.pull-right .pull-right.hidden-xs
%small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')} %small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')}
.merge-request-labels .merge-request-labels
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
.form-group .form-group
.light .light
= f.label :description, "Description" = f.label :description, "Description"
= render layout: 'projects/md_preview' do
= render 'projects/zen', f: f, attr: :description, = render 'projects/zen', f: f, attr: :description,
classes: 'description form-control' classes: 'description form-control'
.clearfix.hint .clearfix.hint
......
- if can? current_user, :write_merge_request, @project = render "projects/issues_nav"
= link_to new_project_merge_request_path(@project), class: "pull-right btn btn-new", title: "New Merge Request" do
%i.fa.fa-plus
New Merge Request
%h3.page-title
Merge Requests
%hr
.row .row
.fixed.sidebar-expand-button.hidden-lg.hidden-md
%i.fa.fa-list.fa-2x
.col-md-3.responsive-side .col-md-3.responsive-side
= render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project), = render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project),
labels: true, redirect: 'merge_requests', entity: 'merge_request' labels: true, redirect: 'merge_requests', entity: 'merge_request'
.col-md-9 .col-md-9
.mr-filters.append-bottom-10 .append-bottom-10
.dropdown.inline = render 'projects/issuable_filter'
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%i.fa.fa-user
%span.light assignee:
- if @assignee.present?
%strong= @assignee.name
- elsif params[:assignee_id] == "0"
Unassigned
- else
Any
%b.caret
%ul.dropdown-menu
%li
= link_to project_filter_path(assignee_id: nil) do
Any
= link_to project_filter_path(assignee_id: 0) do
Unassigned
- @assignees.sort_by(&:name).each do |user|
%li
= link_to project_filter_path(assignee_id: user.id) do
= image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
= user.name
.dropdown.inline.prepend-left-10
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%i.fa.fa-clock-o
%span.light milestone:
- if @milestone.present?
%strong= @milestone.title
- elsif params[:milestone_id] == "0"
None (backlog)
- else
Any
%b.caret
%ul.dropdown-menu
%li
= link_to project_filter_path(milestone_id: nil) do
Any
= link_to project_filter_path(milestone_id: 0) do
None (backlog)
- project_active_milestones.each do |milestone|
%li
= link_to project_filter_path(milestone_id: milestone.id) do
%strong= milestone.title
%small.light= milestone.expires_at
.pull-right
= render 'shared/sort_dropdown'
.panel.panel-default .panel.panel-default
%ul.well-list.mr-list %ul.well-list.mr-list
= render @merge_requests = render @merge_requests
......
...@@ -8,6 +8,10 @@ ...@@ -8,6 +8,10 @@
- else - else
Open Open
.cross-project-ref
%i.fa.fa-link.has_tooltip{:"data-original-title" => 'Cross-project reference'}
= cross_project_reference(@project, @merge_request)
.creator .creator
Created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)} Created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)}
......
...@@ -11,13 +11,17 @@ ...@@ -11,13 +11,17 @@
- if @merge_request.closed? - if @merge_request.closed?
%h4 %h4
Closed by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)} Closed
- if @merge_request.closed_event
by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)}
#{time_ago_with_tooltip(@merge_request.closed_event.created_at)} #{time_ago_with_tooltip(@merge_request.closed_event.created_at)}
%p Changes were not merged into target branch %p Changes were not merged into target branch
- if @merge_request.merged? - if @merge_request.merged?
%h4 %h4
Merged by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)} Merged
- if @merge_request.merge_event
by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)}
#{time_ago_with_tooltip(@merge_request.merge_event.created_at)} #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
= render "projects/merge_requests/show/remove_source_branch" = render "projects/merge_requests/show/remove_source_branch"
...@@ -44,4 +48,3 @@ ...@@ -44,4 +48,3 @@
Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'} Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'}
= succeed '.' do = succeed '.' do
!= gfm(issues_sentence(@closes_issues)) != gfm(issues_sentence(@closes_issues))
...@@ -18,9 +18,10 @@ ...@@ -18,9 +18,10 @@
.col-sm-10 .col-sm-10
= f.text_field :title, maxlength: 255, class: "form-control", dir: :auto = f.text_field :title, maxlength: 255, class: "form-control", dir: :auto
%p.hint Required %p.hint Required
.form-group .form-group.milestone-description
= f.label :description, "Description", class: "control-label" = f.label :description, "Description", class: "control-label"
.col-sm-10 .col-sm-10
= render layout: 'projects/md_preview' do
= render 'projects/zen', f: f, attr: :description, classes: 'description form-control' = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
.hint .hint
.pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}. .pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
......
= render "projects/issues/head" = render "projects/issues_nav"
.milestones_content .milestones_content
%h3.page-title %h3.page-title
Milestones Milestones
......
= render "projects/issues/head" = render "projects/issues_nav"
%h3.page-title %h3.page-title
Milestone ##{@milestone.iid} Milestone ##{@milestone.iid}
.pull-right .pull-right
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
= render 'projects/errors' = render 'projects/errors'
.project-edit-content .project-edit-content
= form_for @project, remote: true, html: { class: 'new_project form-horizontal' } do |f| = form_for @project, html: { class: 'new_project form-horizontal' } do |f|
.form-group.project-name-holder .form-group.project-name-holder
= f.label :name, class: 'control-label' do = f.label :name, class: 'control-label' do
%strong Project name %strong Project name
......
%h2
%i.fa.fa-warning
No repository
%p.slead
The repository for this project does not exist.
%br
This means you can not push code until you create an empty repository or import existing one.
%hr
.no-repo-actions
= link_to project_repository_path(@project), method: :post, class: 'btn btn-primary' do
Create empty bare repository
%strong.prepend-left-10.append-right-10 or
= link_to new_project_import_path(@project), class: 'btn' do
Import repository
- if can? current_user, :remove_project, @project
.prepend-top-20
= link_to 'Remove project', @project, data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
...@@ -5,23 +5,13 @@ ...@@ -5,23 +5,13 @@
= f.hidden_field :noteable_id = f.hidden_field :noteable_id
= f.hidden_field :noteable_type = f.hidden_field :noteable_type
%ul.nav.nav-tabs = render layout: 'projects/md_preview' do
%li.active
= link_to '#note-write-holder', class: 'js-note-write-button' do
Write
%li
= link_to '#note-preview-holder', class: 'js-note-preview-button', data: { url: preview_project_notes_path(@project) } do
Preview
%div
.note-write-holder
= render 'projects/zen', f: f, attr: :note, = render 'projects/zen', f: f, attr: :note,
classes: 'note_text js-note-text', dir: :auto classes: 'note_text js-note-text', dir: :auto
.light.clearfix .light.clearfix
.pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }} .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }}
.pull-right Attach images (JPG, PNG, GIF) by dragging &amp; dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }. .pull-right Attach images (JPG, PNG, GIF) by dragging &amp; dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }.
.note-preview-holder.hide
.js-note-preview
.note-form-actions .note-form-actions
.buttons .buttons
...@@ -29,7 +19,7 @@ ...@@ -29,7 +19,7 @@
= yield(:note_actions) = yield(:note_actions)
%a.btn.grouped.js-close-discussion-note-form Cancel %a.btn.grouped.js-close-discussion-note-form Cancel
.note-form-option .note-form-option.hidden-xs
%a.choose-btn.btn.js-choose-note-attachment-button %a.choose-btn.btn.js-choose-note-attachment-button
%i.fa.fa-paperclip %i.fa.fa-paperclip
%span Choose File ... %span Choose File ...
......
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
%i.fa.fa-trash-o.cred %i.fa.fa-trash-o.cred
Remove Remove
= link_to_member(@project, note.author, avatar: false) = link_to_member(@project, note.author, avatar: false)
%span.author-username
= '@' + note.author.username
%span.note-last-update %span.note-last-update
= note_timestamp(note) = note_timestamp(note)
...@@ -38,7 +40,8 @@ ...@@ -38,7 +40,8 @@
.note-edit-form .note-edit-form
= form_for note, url: project_note_path(@project, note), method: :put, remote: true, authenticity_token: true do |f| = form_for note, url: project_note_path(@project, note), method: :put, remote: true, authenticity_token: true do |f|
= f.text_area :note, class: 'note_text js-note-text js-gfm-input turn-on', dir: :auto = render layout: 'projects/md_preview' do
= f.text_area :note, class: 'note_text js-note-text markdown-area js-gfm-input turn-on', dir: :auto
.form-actions.clearfix .form-actions.clearfix
= f.submit 'Save changes', class: "btn btn-primary btn-save js-comment-button" = f.submit 'Save changes', class: "btn btn-primary btn-save js-comment-button"
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
= tag.name = tag.name
- if tag.message.present? - if tag.message.present?
&nbsp; &nbsp;
= tag.message = strip_gpg_signature(tag.message)
.pull-right .pull-right
- if can? current_user, :download_code, @project - if can? current_user, :download_code, @project
= render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-small' = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-small'
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
= @search_results.notes_count = @search_results.notes_count
%li{class: ("active" if @scope == 'wiki_blobs')} %li{class: ("active" if @scope == 'wiki_blobs')}
= link_to search_filter_path(scope: 'wiki_blobs') do = link_to search_filter_path(scope: 'wiki_blobs') do
%i.fa.fa-book
Wiki Wiki
.pull-right .pull-right
= @search_results.wiki_blobs_count = @search_results.wiki_blobs_count
......
...@@ -4,4 +4,4 @@ ...@@ -4,4 +4,4 @@
&nbsp; &nbsp;
%span.file_name.js-avatar-filename File name... %span.file_name.js-avatar-filename File name...
= f.file_field :avatar, class: 'js-group-avatar-input hidden' = f.file_field :avatar, class: 'js-group-avatar-input hidden'
.light The maximum file size allowed is 100KB. .light The maximum file size allowed is 200KB.
.gitlab-promo .gitlab-promo
= link_to 'Homepage', promo_url = link_to 'Homepage', promo_url
= link_to "Blog", promo_url + '/blog/' = link_to "Blog", promo_url + '/blog/'
= link_to "@gitlabhq", "https://twitter.com/gitlabhq" = link_to "@gitlab", "https://twitter.com/gitlab"
= link_to "Requests", "http://feedback.gitlab.com/" = link_to "Requests", "http://feedback.gitlab.com/"
...@@ -9,13 +9,13 @@ ...@@ -9,13 +9,13 @@
%ul.dropdown-menu %ul.dropdown-menu
%li %li
= link_to project_filter_path(sort: 'newest') do = link_to project_filter_path(sort: 'newest') do
Newest = sort_title_recently_created
= link_to project_filter_path(sort: 'oldest') do = link_to project_filter_path(sort: 'oldest') do
Oldest = sort_title_oldest_created
= link_to project_filter_path(sort: 'recently_updated') do = link_to project_filter_path(sort: 'recently_updated') do
Recently updated = sort_title_recently_updated
= link_to project_filter_path(sort: 'last_updated') do = link_to project_filter_path(sort: 'last_updated') do
Last updated = sort_title_oldest_updated
= link_to project_filter_path(sort: 'milestone_due_soon') do = link_to project_filter_path(sort: 'milestone_due_soon') do
Milestone due soon Milestone due soon
= link_to project_filter_path(sort: 'milestone_due_later') do = link_to project_filter_path(sort: 'milestone_due_later') do
......
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
xml.title "Activity feed for #{@user.name}"
xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml"
xml.link href: user_url(@user), rel: "alternate", type: "text/html"
xml.id projects_url
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event|
event_to_atom(xml, event)
end
end
...@@ -18,7 +18,15 @@ ...@@ -18,7 +18,15 @@
%h4 Groups: %h4 Groups:
= render 'groups', groups: @groups = render 'groups', groups: @groups
%hr %hr
%h4 User Activity: %h4
User Activity:
- if current_user
%span.rss-icon.pull-right
= link_to user_path(@user, :atom, { private_token: current_user.private_token }) do
%strong
%i.fa.fa-rss
= render @events = render @events
.col-md-4 .col-md-4
= render 'profile', user: @user = render 'profile', user: @user
......
class ProjectServiceWorker
include Sidekiq::Worker
sidekiq_options queue: :project_web_hook
def perform(hook_id, data)
Service.find(hook_id).execute(data)
end
end
...@@ -96,5 +96,8 @@ module Gitlab ...@@ -96,5 +96,8 @@ module Gitlab
redis_config_hash[:namespace] = 'cache:gitlab' redis_config_hash[:namespace] = 'cache:gitlab'
config.cache_store = :redis_store, redis_config_hash config.cache_store = :redis_store, redis_config_hash
# This is needed for gitlab-shell
ENV['GITLAB_PATH_OUTSIDE_HOOK'] = ENV['PATH']
end end
end end
...@@ -35,7 +35,7 @@ production: &base ...@@ -35,7 +35,7 @@ production: &base
## Date & Time settings ## Date & Time settings
# Uncomment and customize if you want to change the default time zone of GitLab application. # Uncomment and customize if you want to change the default time zone of GitLab application.
# To see all available zones, run `bundle exec rake time:zones:all` # To see all available zones, run `bundle exec rake time:zones:all RAILS_ENV=production`
# time_zone: 'UTC' # time_zone: 'UTC'
## Email settings ## Email settings
......
...@@ -14,7 +14,8 @@ Sidekiq.configure_server do |config| ...@@ -14,7 +14,8 @@ Sidekiq.configure_server do |config|
} }
config.server_middleware do |chain| config.server_middleware do |chain|
chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS']
chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS']
end end
end end
......
...@@ -137,7 +137,8 @@ Gitlab::Application.routes.draw do ...@@ -137,7 +137,8 @@ Gitlab::Application.routes.draw do
end end
end end
match "/u/:username" => "users#show", as: :user, constraints: { username: /.*/ }, via: :get match "/u/:username" => "users#show", as: :user,
constraints: {username: /(?:[^.]|\.(?!atom$))+/, format: /atom/}, via: :get
# #
# Dashboard Area # Dashboard Area
...@@ -181,18 +182,16 @@ Gitlab::Application.routes.draw do ...@@ -181,18 +182,16 @@ Gitlab::Application.routes.draw do
resources :projects, constraints: { id: /[a-zA-Z.0-9_\-]+\/[a-zA-Z.0-9_\-]+/ }, except: [:new, :create, :index], path: "/" do resources :projects, constraints: { id: /[a-zA-Z.0-9_\-]+\/[a-zA-Z.0-9_\-]+/ }, except: [:new, :create, :index], path: "/" do
member do member do
put :transfer put :transfer
post :fork
post :archive post :archive
post :unarchive post :unarchive
post :upload_image post :upload_image
post :toggle_star post :toggle_star
get :markdown_preview
get :autocomplete_sources get :autocomplete_sources
get :import
put :retry_import
end end
scope module: :projects do scope module: :projects do
resources :blob, only: [:show, :destroy], constraints: { id: /.+/ } do resources :blob, only: [:show, :destroy], constraints: { id: /.+/, format: false } do
get :diff, on: :member get :diff, on: :member
end end
resources :raw, only: [:show], constraints: {id: /.+/} resources :raw, only: [:show], constraints: {id: /.+/}
...@@ -232,7 +231,10 @@ Gitlab::Application.routes.draw do ...@@ -232,7 +231,10 @@ Gitlab::Application.routes.draw do
end end
end end
resource :repository, only: [:show] do resource :fork, only: [:new, :create]
resource :import, only: [:new, :create, :show]
resource :repository, only: [:show, :create] do
member do member do
get "archive", constraints: { format: Gitlab::Regex.archive_formats_regex } get "archive", constraints: { format: Gitlab::Regex.archive_formats_regex }
end end
...@@ -328,10 +330,6 @@ Gitlab::Application.routes.draw do ...@@ -328,10 +330,6 @@ Gitlab::Application.routes.draw do
member do member do
delete :delete_attachment delete :delete_attachment
end end
collection do
post :preview
end
end end
end end
end end
......
...@@ -13,9 +13,9 @@ ...@@ -13,9 +13,9 @@
# #
# ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab" # ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
# Use at least one worker per core if you're on a dedicated server, # Read about unicorn workers here:
# more will usually help for _short_ waits on databases/caches. # http://doc.gitlab.com/ee/install/requirements.html#unicorn-workers
# The minimum is 2 #
worker_processes 2 worker_processes 2
# Since Unicorn is never exposed to outside clients, it does not need to # Since Unicorn is never exposed to outside clients, it does not need to
......
class AddIdentityTable < ActiveRecord::Migration
def up
create_table :identities do |t|
t.string :extern_uid
t.string :provider
t.references :user
end
add_index :identities, :user_id
execute <<eos
INSERT INTO identities (provider, extern_uid, user_id)
SELECT provider, extern_uid, id FROM users
WHERE provider IS NOT NULL
eos
if index_exists?(:users, ["extern_uid", "provider"])
remove_index :users, ["extern_uid", "provider"]
end
remove_column :users, :extern_uid
remove_column :users, :provider
end
def down
add_column :users, :extern_uid, :string
add_column :users, :provider, :string
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
execute <<eos
UPDATE users u
SET provider = i.provider, extern_uid = i.extern_uid
FROM identities i
WHERE i.user_id = u.id
eos
else
execute "UPDATE users u, identities i SET u.provider = i.provider, u.extern_uid = i.extern_uid WHERE u.id = i.user_id"
end
drop_table :identities
unless index_exists?(:users, ["extern_uid", "provider"])
add_index "users", ["extern_uid", "provider"], name: "index_users_on_extern_uid_and_provider", unique: true, using: :btree
end
end
end
class AddLockedAtToMergeRequest < ActiveRecord::Migration
def change
add_column :merge_requests, :locked_at, :datetime
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20141121133009) do ActiveRecord::Schema.define(version: 20141205134006) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -74,6 +74,14 @@ ActiveRecord::Schema.define(version: 20141121133009) do ...@@ -74,6 +74,14 @@ ActiveRecord::Schema.define(version: 20141121133009) do
add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree
create_table "identities", force: true do |t|
t.string "extern_uid"
t.string "provider"
t.integer "user_id"
end
add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree
create_table "issues", force: true do |t| create_table "issues", force: true do |t|
t.string "title" t.string "title"
t.integer "assignee_id" t.integer "assignee_id"
...@@ -173,6 +181,7 @@ ActiveRecord::Schema.define(version: 20141121133009) do ...@@ -173,6 +181,7 @@ ActiveRecord::Schema.define(version: 20141121133009) do
t.integer "iid" t.integer "iid"
t.text "description" t.text "description"
t.integer "position", default: 0 t.integer "position", default: 0
t.datetime "locked_at"
end end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
...@@ -350,8 +359,6 @@ ActiveRecord::Schema.define(version: 20141121133009) do ...@@ -350,8 +359,6 @@ ActiveRecord::Schema.define(version: 20141121133009) do
t.string "bio" t.string "bio"
t.integer "failed_attempts", default: 0 t.integer "failed_attempts", default: 0
t.datetime "locked_at" t.datetime "locked_at"
t.string "extern_uid"
t.string "provider"
t.string "username" t.string "username"
t.boolean "can_create_group", default: true, null: false t.boolean "can_create_group", default: true, null: false
t.boolean "can_create_team", default: true, null: false t.boolean "can_create_team", default: true, null: false
...@@ -375,7 +382,6 @@ ActiveRecord::Schema.define(version: 20141121133009) do ...@@ -375,7 +382,6 @@ ActiveRecord::Schema.define(version: 20141121133009) do
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["extern_uid", "provider"], name: "index_users_on_extern_uid_and_provider", unique: true, using: :btree
add_index "users", ["name"], name: "index_users_on_name", using: :btree add_index "users", ["name"], name: "index_users_on_name", using: :btree
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
add_index "users", ["username"], name: "index_users_on_username", using: :btree add_index "users", ["username"], name: "index_users_on_username", using: :btree
......
...@@ -2,27 +2,28 @@ ...@@ -2,27 +2,28 @@
## User documentation ## User documentation
- [API](api/README.md) Explore how you can access GitLab via a simple and powerful API. - [API](api/README.md) Automate GitLab via a simple and powerful API.
- [Markdown](markdown/markdown.md) Learn what you can do with GitLab's advanced formatting system. - [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
- [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do. - [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
- [Project Services](project_services/project_services.md) Explore how project services can integrate a project with external services, such as for CI. - [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to a project. - [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects. - [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
- [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project. - [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
- [Workflow](workflow/README.md) Learn how to use Git and GitLab together. - [Workflow](workflow/README.md) Learn how to get the maximum out of GitLab.
## Administrator documentation ## Administrator documentation
- [Install](install/README.md) Requirements, directory structures and manual installation. - [Install](install/README.md) Requirements, directory structures and manual installation.
- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter. - [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
- [Raketasks](raketasks/README.md) Explore what GitLab has in store for you to make administration easier. - [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects.
- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough. - [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough.
- [System hooks](system_hooks/system_hooks.md) Let GitLab notify you when certain management tasks need to be carried out. - [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
- [Security](security/README.md) Learn what you can do to further secure your GitLab instance. - [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
- [Update](update/README.md) Update guides to upgrade your installation. - [Update](update/README.md) Update guides to upgrade your installation.
- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page. - [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages. - [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars. - [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
- [Operations](operations/README.md) Keeping GitLab up and running
## Contributor documentation ## Contributor documentation
......
...@@ -260,12 +260,14 @@ GET /user/keys ...@@ -260,12 +260,14 @@ GET /user/keys
{ {
"id": 1, "id": 1,
"title": "Public key", "title": "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
"created_at": "2014-08-01T14:47:39.080Z"
}, },
{ {
"id": 3, "id": 3,
"title": "Another Public key", "title": "Another Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
"created_at": "2014-08-01T14:47:39.080Z"
} }
] ]
``` ```
...@@ -302,7 +304,8 @@ Parameters: ...@@ -302,7 +304,8 @@ Parameters:
{ {
"id": 1, "id": 1,
"title": "Public key", "title": "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
"created_at": "2014-08-01T14:47:39.080Z"
} }
``` ```
......
...@@ -4,3 +4,4 @@ ...@@ -4,3 +4,4 @@
- [Shell commands](shell_commands.md) in the GitLab codebase - [Shell commands](shell_commands.md) in the GitLab codebase
- [Rake tasks](rake_tasks.md) for development - [Rake tasks](rake_tasks.md) for development
- [CI setup](ci_setup.md) for testing GitLab - [CI setup](ci_setup.md) for testing GitLab
- [Sidekiq debugging](sidekiq_debugging.md)
...@@ -8,6 +8,38 @@ EE releases are available not long after CE releases. To obtain the GitLab EE th ...@@ -8,6 +8,38 @@ EE releases are available not long after CE releases. To obtain the GitLab EE th
Both EE and CE require an add-on component called gitlab-shell. It is obtained from the [gitlab-shell repository](https://gitlab.com/gitlab-org/gitlab-shell/tree/master). New versions are usually tags but staying on the master branch will give you the latest stable version. New releases are generally around the same time as GitLab CE releases with exception for informal security updates deemed critical. Both EE and CE require an add-on component called gitlab-shell. It is obtained from the [gitlab-shell repository](https://gitlab.com/gitlab-org/gitlab-shell/tree/master). New versions are usually tags but staying on the master branch will give you the latest stable version. New releases are generally around the same time as GitLab CE releases with exception for informal security updates deemed critical.
## Physical office analogy
You can imagine GitLab as a physical office.
**The repositories** are the goods GitLab handling.
They can be stored in a warehouse.
This can be either a hard disk, or something more complex, such as a NFS filesystem;
**NginX** acts like the front-desk.
Users come to NginX and request actions to be done by workers in the office;
**The database** is a series of metal file cabinets with information on:
- The goods in the warehouse (metadata, issues, merge requests etc);
- The users coming to the front desk (permissions)
**Redis** is a communication board with “cubby holes” that can contain tasks for office workers;
**Sidekiq** is a worker that primarily handles sending out emails.
It takes tasks from the Redis communication board;
**A Unicorn worker** is a worker that handles quick/mundane tasks.
They work with the communication board (Redis).
Their job description:
- check permissions by checking the user session stored in a Redis “cubby hole”;
- make tasks for Sidekiq;
- fetch stuff from the warehouse or move things around in there;
**Gitlab-shell** is a third kind of worker that takes orders from a fax machine (SSH) instead of the front desk (HTTP).
Gitlab-shell communicates with Sidekiq via the “communication board” (Redis), and asks quick questions of the Unicorn workers either directly or via the front desk.
**GitLab Enterprise Edition (the application)** is the collection of processes and business practices that the office is run by.
## System Layout ## System Layout
When referring to ~git in the pictures it means the home directory of the git user which is typically /home/git. When referring to ~git in the pictures it means the home directory of the git user which is typically /home/git.
......
# Rake tasks for developers # Rake tasks for developers
## Setup db with developer seeds: ## Setup db with developer seeds
Note that if your db user does not have advanced privileges you must create the db manually before running this command. Note that if your db user does not have advanced privileges you must create the db manually before running this command.
...@@ -8,6 +8,10 @@ Note that if your db user does not have advanced privileges you must create the ...@@ -8,6 +8,10 @@ Note that if your db user does not have advanced privileges you must create the
bundle exec rake setup bundle exec rake setup
``` ```
The `setup` task is a alias for `gitlab:setup`.
This tasks calls `db:setup` to create the database, calls `add_limits_mysql` that adds limits to the database schema in case of a MySQL database and fianlly it calls `db:seed_fu` to seed the database.
Note: `db:setup` calls `db:seed` but this does nothing.
## Run tests ## Run tests
This runs all test suites present in GitLab. This runs all test suites present in GitLab.
......
# Guidelines for shell commands in the GitLab codebase # Guidelines for shell commands in the GitLab codebase
This document contains guidelines for working with processes and files in the GitLab codebase.
These guidelines are meant to make your code more reliable _and_ secure.
## References ## References
- [Google Ruby Security Reviewer's Guide](https://code.google.com/p/ruby-security/wiki/Guide) - [Google Ruby Security Reviewer's Guide](https://code.google.com/p/ruby-security/wiki/Guide)
...@@ -109,3 +112,63 @@ logs = IO.popen(%W(git log), chdir: repo_dir).read ...@@ -109,3 +112,63 @@ logs = IO.popen(%W(git log), chdir: repo_dir).read
``` ```
Note that unlike `Gitlab::Popen.popen`, `IO.popen` does not capture standard error. Note that unlike `Gitlab::Popen.popen`, `IO.popen` does not capture standard error.
## Avoid user input at the start of path strings
Various methods for opening and reading files in Ruby can be used to read the
standard output of a process instead of a file. The following two commands do
roughly the same:
```
`touch /tmp/pawned-by-backticks`
File.read('|touch /tmp/pawned-by-file-read')
```
The key is to open a 'file' whose name starts with a `|`.
Affected methods include Kernel#open, File::read, File::open, IO::open and IO::read.
You can protect against this behavior of 'open' and 'read' by ensuring that an
attacker cannot control the start of the filename string you are opening. For
instance, the following is sufficient to protect against accidentally starting
a shell command with `|`:
```
# we assume repo_path is not controlled by the attacker (user)
path = File.join(repo_path, user_input)
# path cannot start with '|' now.
File.read(path)
```
## Guard against path traversal
Path traversal is a security where the program (GitLab) tries to restrict user
access to a certain directory on disk, but the user manages to open a file
outside that directory by taking advantage of the `../` path notation.
```
# Suppose the user gave us a path and they are trying to trick us
user_input = '../other-repo.git/other-file'
# We look up the repo path somewhere
repo_path = 'repositories/user-repo.git'
# The intention of the code below is to open a file under repo_path, but
# because the user used '..' she can 'break out' into
# 'repositories/other-repo.git'
full_path = File.join(repo_path, user_input)
File.open(full_path) do # Oops!
```
A good way to protect against this is to compare the full path with its
'absolute path' according to Ruby's `File.absolute_path`.
```
full_path = File.join(repo_path, user_input)
if full_path != File.absolute_path(full_path)
raise "Invalid path: #{full_path.inspect}"
end
File.open(full_path) do # Etc.
```
A check like this could have avoided CVE-2013-4583.
# Sidekiq debugging
## Log arguments to Sidekiq jobs
If you want to see what arguments are being passed to Sidekiq jobs you can set
the SIDEKIQ_LOG_ARGUMENTS environment variable.
```
SIDEKIQ_LOG_ARGUMENTS=1 bundle exec foreman start
```
It is not recommend to enable this setting in production because some Sidekiq
jobs (such as sending a password reset email) take secret arguments (for
example the password reset token).
...@@ -6,9 +6,9 @@ Since a manual installation is a lot of work and error prone we strongly recomme ...@@ -6,9 +6,9 @@ Since a manual installation is a lot of work and error prone we strongly recomme
## Select Version to Install ## Select Version to Install
Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install. In most cases this should be the highest numbered stable branch (example shown below). Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the tag (version) of GitLab you would like to install.
In most cases this should be the highest numbered production tag (without rc in it).
![Select latest branch](https://i.imgur.com/Lrdxk1k.png) You can select the tag in the version dropdown in the top left corner of GitLab (below the menu bar).
If the highest number stable branch is unclear please check the [GitLab Blog](https://about.gitlab.com/blog/) for installation guide links by version. If the highest number stable branch is unclear please check the [GitLab Blog](https://about.gitlab.com/blog/) for installation guide links by version.
...@@ -54,7 +54,7 @@ up-to-date and install it. ...@@ -54,7 +54,7 @@ up-to-date and install it.
Install the required packages (needed to compile Ruby and native extensions to Ruby gems): Install the required packages (needed to compile Ruby and native extensions to Ruby gems):
sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake libkrb5-dev
Make sure you have the right version of Git installed Make sure you have the right version of Git installed
...@@ -181,9 +181,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ...@@ -181,9 +181,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
### Clone the Source ### Clone the Source
# Clone GitLab repository # Clone GitLab repository
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-5-stable gitlab sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-6-stable gitlab
**Note:** You can change `7-5-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! **Note:** You can change `7-6-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It ### Configure It
...@@ -278,7 +278,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ...@@ -278,7 +278,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
GitLab Shell is an SSH access and repository management software developed specially for GitLab. GitLab Shell is an SSH access and repository management software developed specially for GitLab.
# Run the installation task for gitlab-shell (replace `REDIS_URL` if needed): # Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
sudo -u git -H bundle exec rake gitlab:shell:install[v2.2.0] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:shell:install[v2.4.0] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
# By default, the gitlab-shell config is generated from your main GitLab config. # By default, the gitlab-shell config is generated from your main GitLab config.
# You can review (and modify) the gitlab-shell config as follows: # You can review (and modify) the gitlab-shell config as follows:
...@@ -383,15 +383,17 @@ NOTE: Supply `SANITIZE=true` environment variable to `gitlab:check` to omit proj ...@@ -383,15 +383,17 @@ NOTE: Supply `SANITIZE=true` environment variable to `gitlab:check` to omit proj
### Initial Login ### Initial Login
Visit YOUR_SERVER in your web browser for your first GitLab login. The setup has created an admin account for you. You can use it to log in: Visit YOUR_SERVER in your web browser for your first GitLab login. The setup has created a default admin account for you. You can use it to log in:
root root
5iveL!fe 5iveL!fe
**Important Note:** Please go over to your profile page and immediately change the password, so nobody can access your GitLab by using this login information later on. **Important Note:** Please login to the server before exposing it to the public internet. On login you'll be prompted to change the password.
**Enjoy!** **Enjoy!**
You can use `sudo service gitlab start` and `sudo service gitlab stop` to start and stop GitLab.
## Advanced Setup Tips ## Advanced Setup Tips
### Using HTTPS ### Using HTTPS
......
...@@ -38,6 +38,16 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab ...@@ -38,6 +38,16 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
## Hardware requirements ## Hardware requirements
### Storage
The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least twice as much free space as all your repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo.
If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them.
Apart from a local hard drive you can also mount a volume that supports the network file system (NFS) protocol. This volume might be located on a file server, a network attached storage (NAS) device, a storage area network (SAN) or on an Amazon Web Services (AWS) Elastic Block Store (EBS) volume.
If you have enough RAM memory and a recent CPU the speed of GitLab is mainly limited by hard drive seek times. Having a fast drive (7200 RPM and up) or a solid state drive (SSD) will improve the responsiveness of GitLab.
### CPU ### CPU
- 1 core works supports up to 100 users but the application can be a bit slower due to having all workers and background jobs running on the same core - 1 core works supports up to 100 users but the application can be a bit slower due to having all workers and background jobs running on the same core
...@@ -50,6 +60,10 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab ...@@ -50,6 +60,10 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
### Memory ### Memory
You need at least 2GB of addressable memory (RAM + swap) to install and use GitLab!
With less memory GitLab will give strange errors during the reconfigure run and 500 errors during usage.
- 512MB RAM + 1.5GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advise.
- 1GB RAM + 1GB swap supports up to 100 users - 1GB RAM + 1GB swap supports up to 100 users
- **2GB RAM** is the **recommended** memory size and supports up to 500 users - **2GB RAM** is the **recommended** memory size and supports up to 500 users
- 4GB RAM supports up to 2,000 users - 4GB RAM supports up to 2,000 users
...@@ -60,15 +74,16 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab ...@@ -60,15 +74,16 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application. Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application.
### Storage ## Unicorn Workers
The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least twice as much free space as all your repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo. It's possible to increase the amount of unicorn workers and tis will usually help for to reduce the response time of the applications.
For most instances we recommend using: CPU cores + 1 = unicorn workers.
So for a machine with 2 cores, 3 unicorn workers is ideal.
If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them. For all machines that have 1GB and up we recommend a minimum of two unicorn workers.
If you have a 512MB machine with a magnetic (non-SSD) swap drive we recommend to configure only one Unicorn worker to prevent excessive swapping.
Apart from a local hard drive you can also mount a volume that supports the network file system (NFS) protocol. This volume might be located on a file server, a network attached storage (NAS) device, a storage area network (SAN) or on an Amazon Web Services (AWS) Elastic Block Store (EBS) volume. With one Unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check).
If you have a 512MB machine with a SSD drive you can use two Unicorn workers, this will allow HTTP access although it will be slow due to swapping.
If you have enough RAM memory and a recent CPU the speed of GitLab is mainly limited by hard drive seek times. Having a fast drive (7200 RPM and up) or a solid state drive (SSD) will improve the responsiveness of GitLab.
## Database ## Database
......
...@@ -9,3 +9,20 @@ If correctly setup, emails that require an action will be marked in Gmail. ...@@ -9,3 +9,20 @@ If correctly setup, emails that require an action will be marked in Gmail.
To get this functioning, you need to be registered with Google. To get this functioning, you need to be registered with Google.
[See how to register with google in this document.](https://developers.google.com/gmail/markup/registering-with-google) [See how to register with google in this document.](https://developers.google.com/gmail/markup/registering-with-google)
To aid the registering with google, GitLab offers a rake task that will send an email to google whitelisting email address from your GitLab server.
To check what would be sent to the google email address, run the rake task:
```bash
bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production
```
**This will not send the email but give you the output of how the mail will look.**
Copy the output of the rake task to [google email markup tester](https://www.google.com/webmasters/markup-tester/u/0/) and press "Validate".
If you receive "No errors detected" message from the tester you can send the email using:
```bash
bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production SEND=true
``
...@@ -13,7 +13,7 @@ To enable the Twitter OmniAuth provider you must register your application with ...@@ -13,7 +13,7 @@ To enable the Twitter OmniAuth provider you must register your application with
something else descriptive. something else descriptive.
- Description: Create a description. - Description: Create a description.
- Website: The URL to your GitLab installation. 'https://gitlab.example.com' - Website: The URL to your GitLab installation. 'https://gitlab.example.com'
- Callback URL: 'https://gitlab.example.com/users/auth/github/callback' - Callback URL: 'https://gitlab.example.com/users/auth/twitter/callback'
- Agree to the "Rules of the Road." - Agree to the "Rules of the Road."
![Twitter App Details](twitter_app_details.png) ![Twitter App Details](twitter_app_details.png)
......
# GitLab operations
- [Sidekiq MemoryKiller](sidekiq_memory_killer.md)
# Sidekiq MemoryKiller
The GitLab Rails application code suffers from memory leaks. For web requests
this problem is made manageable using
[unicorn-worker-killer](https://github.com/kzk/unicorn-worker-killer) which
restarts Unicorn worker processes in between requests when needed. The Sidekiq
MemoryKiller applies the same approach to the Sidekiq processes used by GitLab
to process background jobs.
Unlike unicorn-worker-killer, which is enabled by default for all GitLab
installations since GitLab 6.4, the Sidekiq MemoryKiller is enabled by default
_only_ for Omnibus packages. The reason for this is that the MemoryKiller
relies on Runit to restart Sidekiq after a memory-induced shutdown and GitLab
installations from source do not all use Runit or an equivalent.
With the default settings, the MemoryKiller will cause a Sidekiq restart no
more often than once every 15 minutes, with the restart causing about one
minute of delay for incoming background jobs.
## Configuring the MemoryKiller
The MemoryKiller is controlled using environment variables.
- `SIDEKIQ_MEMORY_KILLER_MAX_RSS`: if this variable is set, and its value is
greater than 0, then after each Sidekiq job, the MemoryKiller will check the
RSS of the Sidekiq process that executed the job. If the RSS of the Sidekiq
process (expressed in kilobytes) exceeds SIDEKIQ_MEMORY_KILLER_MAX_RSS, a
delayed shutdown is triggered. The default value for Omnibus packages is set
[in the omnibus-gitlab
repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb).
- `SIDEKIQ_MEMORY_KILLER_GRACE_TIME`: defaults 900 seconds (15 minutes). When
a shutdown is triggered, the Sidekiq process will keep working normally for
another 15 minutes.
- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT`: defaults to 30 seconds. When the grace
time has expired, the MemoryKiller tells Sidekiq to stop accepting new jobs.
Existing jobs get 30 seconds to finish. After that, the MemoryKiller tells
Sidekiq to shut down, and an external supervision mechanism (e.g. Runit) must
restart Sidekiq.
...@@ -19,6 +19,7 @@ If a user is a GitLab administrator they receive all permissions. ...@@ -19,6 +19,7 @@ If a user is a GitLab administrator they receive all permissions.
| Create new merge request | | | ✓ | ✓ | ✓ | | Create new merge request | | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ | | Create new branches | | | ✓ | ✓ | ✓ |
| Push to non-protected branches | | | ✓ | ✓ | ✓ | | Push to non-protected branches | | | ✓ | ✓ | ✓ |
| Force push to non-protected branches | | | ✓ | ✓ | ✓ |
| Remove non-protected branches | | | ✓ | ✓ | ✓ | | Remove non-protected branches | | | ✓ | ✓ | ✓ |
| Add tags | | | ✓ | ✓ | ✓ | | Add tags | | | ✓ | ✓ | ✓ |
| Write a wiki | | | ✓ | ✓ | ✓ | | Write a wiki | | | ✓ | ✓ | ✓ |
...@@ -35,6 +36,8 @@ If a user is a GitLab administrator they receive all permissions. ...@@ -35,6 +36,8 @@ If a user is a GitLab administrator they receive all permissions.
| Switch visibility level | | | | | ✓ | | Switch visibility level | | | | | ✓ |
| Transfer project to another namespace | | | | | ✓ | | Transfer project to another namespace | | | | | ✓ |
| Remove project | | | | | ✓ | | Remove project | | | | | ✓ |
| Force push to protected branches | | | | | |
| Remove protected branches | | | | | |
## Group ## Group
......
...@@ -14,7 +14,7 @@ You can only restore a backup to exactly the same version of GitLab that you cre ...@@ -14,7 +14,7 @@ You can only restore a backup to exactly the same version of GitLab that you cre
sudo gitlab-rake gitlab:backup:create sudo gitlab-rake gitlab:backup:create
# if you've installed GitLab from source or using the cookbook # if you've installed GitLab from source or using the cookbook
bundle exec rake gitlab:backup:create RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
``` ```
Example output: Example output:
...@@ -137,7 +137,7 @@ with the name of your bucket: ...@@ -137,7 +137,7 @@ with the name of your bucket:
Please be informed that a backup does not store your configuration files. Please be informed that a backup does not store your configuration files.
If you use an Omnibus package please see the [instructions in the readme to backup your configuration](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#backup-and-restore-omnibus-gitlab-configuration). If you use an Omnibus package please see the [instructions in the readme to backup your configuration](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#backup-and-restore-omnibus-gitlab-configuration).
If you have a cookbook installation there should be a copy of your configuration in Chef. If you have a cookbook installation there should be a copy of your configuration in Chef.
If you have a manual installation please consider backing up your gitlab.yml file and any SSL keys and certificates. If you have a manual installation please consider backing up your `gitlab.yml` file, any SSL keys and certificates, and your [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
## Restore a previously created backup ## Restore a previously created backup
...@@ -203,5 +203,31 @@ Add the following lines at the bottom: ...@@ -203,5 +203,31 @@ Add the following lines at the bottom:
``` ```
# Create a full backup of the GitLab repositories and SQL database every day at 4am # Create a full backup of the GitLab repositories and SQL database every day at 4am
0 4 * * * cd /home/git/gitlab && PATH=/usr/local/bin:/usr/bin:/bin bundle exec rake gitlab:backup:create RAILS_ENV=production 0 4 * * * cd /home/git/gitlab && PATH=/usr/local/bin:/usr/bin:/bin bundle exec rake gitlab:backup:create RAILS_ENV=production CRON=1
``` ```
The `CRON=1` environment setting tells the backup script to suppress all progress output if there are no errors.
This is recommended to reduce cron spam.
## Alternative backup strategies
If your GitLab server contains a lot of Git repository data you may find the GitLab backup script to be too slow.
In this case you can consider using filesystem snapshots as part of your backup strategy.
Example: Amazone EBS
> A GitLab server using omnibus-gitlab hosted on Amazon AWS.
> An EBS drive containing an ext4 filesystem is mounted at `/var/opt/gitlab`.
> In this case you could make an application backup by taking an EBS snapshot.
> The backup includes all repositories, uploads and Postgres data.
Example: LVM snapshots + Rsync
> A GitLab server using omnibus-gitlab, with an LVM logical volume mounted at `/var/opt/gitlab`.
> Replicating the `/var/opt/gitlab` directory usign Rsync would not be reliable because too many files would change while Rsync is running.
> Instead of rsync-ing `/var/opt/gitlab`, we create a temporary LVM snapshot, which we mount as a read-only filesystem at `/mnt/gitlab_backup`.
> Now we can have a longer running Rsync job which will create a consistent replica on the remote server.
> The replica includes all repositories, uploads and Postgres data.
If you are running GitLab on a virtualized server you can possibly also create VM snapshots of the entire GitLab server.
It is not uncommon however for a VM snapshot to require you to power down the server, so this approach is probably of limited practical use.
...@@ -122,3 +122,27 @@ sudo -u git -H mkdir -p /home/git/gitlab-satellites ...@@ -122,3 +122,27 @@ sudo -u git -H mkdir -p /home/git/gitlab-satellites
sudo -u git -H bundle exec rake gitlab:satellites:create RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:satellites:create RAILS_ENV=production
sudo chmod u+rwx,g=rx,o-rwx /home/git/gitlab-satellites sudo chmod u+rwx,g=rx,o-rwx /home/git/gitlab-satellites
``` ```
## Rebuild authorized_keys file
In some case it is necessary to rebuild the `authorized_keys` file.
For Omnibus-packages:
```
sudo gitlab-rake gitlab:shell:setup
```
For installations from source:
```
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:shell:setup RAILS_ENV=production
```
```
This will rebuild an authorized_keys file.
You will lose any data stored in authorized_keys file.
Do you want to continue (yes/no)? yes
............................
```
...@@ -8,7 +8,9 @@ NOTE: This is a guide for GitLab developers. ...@@ -8,7 +8,9 @@ NOTE: This is a guide for GitLab developers.
### **2. Release Manager** ### **2. Release Manager**
A release manager is selected that coordinates the entire release of this version. The release manager has to make sure all the steps below are done and delegated where necessary. This person should also make sure this document is kept up to date and issues are created and updated. A release manager is selected that coordinates all releases the coming month, including the patch releases for previous releases.
The release manager has to make sure all the steps below are done and delegated where necessary.
This person should also make sure this document is kept up to date and issues are created and updated.
### **3. Create an overall issue** ### **3. Create an overall issue**
...@@ -18,36 +20,40 @@ Replace the dates with actual dates based on the number of workdays before the r ...@@ -18,36 +20,40 @@ Replace the dates with actual dates based on the number of workdays before the r
``` ```
Xth: Xth:
* Update the changelog (#LINK) - [ ] Update the CE changelog (#LINK)
* Triage the omnibus-gitlab milestone - [ ] Update the EE changelog (#LINK)
- [ ] Update the CI changelog (#LINK)
- [ ] Triage the omnibus-gitlab milestone
Xth: Xth:
* Merge CE in to EE (#LINK) - [ ] Merge CE in to EE (#LINK)
* Close the omnibus-gitlab milestone - [ ] Close the omnibus-gitlab milestone
Xth: Xth:
* Create x.x.0.rc1 (#LINK) - [ ] Create x.x.0.rc1 (#LINK)
* Build package for GitLab.com (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#build-a-package) - [ ] Create x.x.0.rc1-ee (#LINK)
- [ ] Create CI y.y.0.rc1 (#LINK)
- [ ] Build package for GitLab.com (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#build-a-package)
Xth: Xth:
* Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package) - [ ] Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package)
* Regression issue and tweet about rc1 (#LINK) - [ ] Regression issues (CE, CI) and tweet about rc1 (#LINK)
* Start blog post (#LINK) - [ ] Start blog post (#LINK)
Xth: Xth:
* Do QA and fix anything coming out of it (#LINK) - [ ] Do QA and fix anything coming out of it (#LINK)
22nd: 22nd:
* Release CE and EE (#LINK) - [ ] Release CE, EE and CI (#LINK)
Xth: Xth:
* * Deploy to GitLab.com (#LINK) - [ ] Deploy to GitLab.com (#LINK)
``` ```
...@@ -55,6 +61,8 @@ Xth: ...@@ -55,6 +61,8 @@ Xth:
Any changes not yet added to the changelog are added by lead developer and in that merge request the complete team is asked if there is anything missing. Any changes not yet added to the changelog are added by lead developer and in that merge request the complete team is asked if there is anything missing.
There are three changelogs that need to be updated: CE, EE and CI.
### **5. Take weekend and vacations into account** ### **5. Take weekend and vacations into account**
Ensure that there is enough time to incorporate the findings of the release candidate, etc. Ensure that there is enough time to incorporate the findings of the release candidate, etc.
...@@ -79,6 +87,7 @@ The RC1 release comes with the task to update the installation and upgrade docs. ...@@ -79,6 +87,7 @@ The RC1 release comes with the task to update the installation and upgrade docs.
1. Create: CE update guide from previous version. Like `7.3-to-7.4.md` 1. Create: CE update guide from previous version. Like `7.3-to-7.4.md`
1. Create: CE to EE update guide in EE repository for latest version. 1. Create: CE to EE update guide in EE repository for latest version.
1. Update: `6.x-or-7.x-to-7.x.md` to latest version. 1. Update: `6.x-or-7.x-to-7.x.md` to latest version.
1. Create: CI update guide from previous version
It's best to copy paste the previous guide and make changes where necessary. It's best to copy paste the previous guide and make changes where necessary.
The typical steps are listed below with any points you should specifically look at. The typical steps are listed below with any points you should specifically look at.
...@@ -91,9 +100,9 @@ List any major changes here, so the user is aware of them before starting to upg ...@@ -91,9 +100,9 @@ List any major changes here, so the user is aware of them before starting to upg
- Web server changes - Web server changes
- File structure changes - File structure changes
#### 1. Make backup #### 1. Stop server
#### 2. Stop server #### 2. Make backup
#### 3. Do users need to update dependencies like `git`? #### 3. Do users need to update dependencies like `git`?
...@@ -171,6 +180,24 @@ Now developers can use master for merging new features. ...@@ -171,6 +180,24 @@ Now developers can use master for merging new features.
So you should use stable branch for future code chages related to release. So you should use stable branch for future code chages related to release.
### 5. Release GitLab CI RC1
Add to your local `gitlab-ci/.git/config`:
```
[remote "public"]
url = none
pushurl = git@dev.gitlab.org:gitlab/gitlab-ci.git
pushurl = git@gitlab.com:gitlab-org/gitlab-ci.git
pushurl = git@github.com:gitlabhq/gitlab-ci.git
```
* Create a stable branch `x-y-stable`
* Bump VERSION to `x.y.0.rc1`
* `git tag -a v$(cat VERSION) -m "Version $(cat VERSION)"
* `git push public x-y-stable v$(cat VERSION)`
# **4 workdays before release - Release RC1** # **4 workdays before release - Release RC1**
### **1. Determine QA person ### **1. Determine QA person
...@@ -189,6 +216,8 @@ It is important to do this as soon as possible, so we can catch any errors befor ...@@ -189,6 +216,8 @@ It is important to do this as soon as possible, so we can catch any errors befor
- Start with a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) and fill it out. - Start with a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) and fill it out.
- Check the changelog of CE and EE for important changes. - Check the changelog of CE and EE for important changes.
- Also check the CI changelog
- Add a proposed tweet text to the blog post WIP MR description.
- Create a WIP MR for the blog post - Create a WIP MR for the blog post
- Ask Dmitriy to add screenshots to the WIP MR. - Ask Dmitriy to add screenshots to the WIP MR.
- Decide with team who will be the MVP user. - Decide with team who will be the MVP user.
...@@ -236,7 +265,7 @@ Create an issue with description of a problem, if it is quick fix fix it yoursel ...@@ -236,7 +265,7 @@ Create an issue with description of a problem, if it is quick fix fix it yoursel
**NOTE** If there is a problem that cannot be fixed in a timely manner, reverting the feature is an option! If the feature is reverted, **NOTE** If there is a problem that cannot be fixed in a timely manner, reverting the feature is an option! If the feature is reverted,
create an issue about it in order to discuss the next steps after the release. create an issue about it in order to discuss the next steps after the release.
# **22nd - Release CE and EE** # **22nd - Release CE, EE and CI**
**Make sure EE `x-x-stable-ee` has latest changes from CE `x-x-stable`** **Make sure EE `x-x-stable-ee` has latest changes from CE `x-x-stable`**
...@@ -256,6 +285,11 @@ Bump version, create release tag and push to remotes: ...@@ -256,6 +285,11 @@ Bump version, create release tag and push to remotes:
bundle exec rake release["x.x.0"] bundle exec rake release["x.x.0"]
``` ```
Also perform these steps for GitLab CI:
- bump version in the stable branch
- create annotated tag
- push the stable branch and the annotated tag to the public repositories
### **2. Update installation.md** ### **2. Update installation.md**
...@@ -286,17 +320,4 @@ Proposed tweet for CE "GitLab X.X is released! It brings *** <link-to-blogpost>" ...@@ -286,17 +320,4 @@ Proposed tweet for CE "GitLab X.X is released! It brings *** <link-to-blogpost>"
# **1 workday after release - Update GitLab.com** # **1 workday after release - Update GitLab.com**
- Build a package for gitlab.com based on the official release instead of RC1 - Build a package for gitlab.com based on the official release instead of RC1
- Deploy the package - Deploy the package (should not need downtime because of the small difference with RC1)
# **25th - Release GitLab CI**
- Create the update guid `doc/x.x-to-x.x.md`.
- Update CHANGELOG
- Bump version
- Create annotated tags `git tag -a vx.x.0 -m 'Version x.x.0' xxxxx`
- Create stable branch `x-x-stable`
- Create GitHub release post
- Post to blog about release
- Post to twitter
...@@ -17,6 +17,8 @@ Otherwise include it in the monthly release and note there was a regression fix ...@@ -17,6 +17,8 @@ Otherwise include it in the monthly release and note there was a regression fix
1. Create an issue on private GitLab development server 1. Create an issue on private GitLab development server
1. Name the issue "Release X.X.X CE and X.X.X EE", this will make searching easier 1. Name the issue "Release X.X.X CE and X.X.X EE", this will make searching easier
1. Fix the issue on a feature branch, do this on the private GitLab development server 1. Fix the issue on a feature branch, do this on the private GitLab development server
1. If it is a security issue, then assign it to the release manager and apply a 'security' label
1. Build the package for GitLab.com and do a deploy
1. Consider creating and testing workarounds 1. Consider creating and testing workarounds
1. After the branch is merged into master, cherry pick the commit(s) into the current stable branch 1. After the branch is merged into master, cherry pick the commit(s) into the current stable branch
1. Make sure that the build has passed and all tests are passing 1. Make sure that the build has passed and all tests are passing
......
...@@ -14,11 +14,13 @@ Please report suspected security vulnerabilities in private to <support@gitlab.c ...@@ -14,11 +14,13 @@ Please report suspected security vulnerabilities in private to <support@gitlab.c
1. Verify that the issue can be reproduced 1. Verify that the issue can be reproduced
1. Acknowledge the issue to the researcher that disclosed it 1. Acknowledge the issue to the researcher that disclosed it
1. Inform the release manager that there needs to be a security release
1. Do the steps from [patch release document](doc/release/patch.md), starting with "Create an issue on private GitLab development server" 1. Do the steps from [patch release document](doc/release/patch.md), starting with "Create an issue on private GitLab development server"
1. The MR with the security fix should get a 'security' label and be assigned to the release manager
1. Build the package for GitLab.com and do a deploy
1. Create feature branches for the blog post on GitLab.com and link them from the code branch 1. Create feature branches for the blog post on GitLab.com and link them from the code branch
1. Merge and publish the blog posts 1. Merge and publish the blog posts
1. Send tweets about the release from `@gitlabhq` 1. Send tweets about the release from `@gitlabhq`
1. Send out an email to the 'GitLab Newsletter' mailing list on MailChimp (or the 'Subscribers' list if the security fix is for EE only)
1. Send out an email to [the community google mailing list](https://groups.google.com/forum/#!forum/gitlabhq) 1. Send out an email to [the community google mailing list](https://groups.google.com/forum/#!forum/gitlabhq)
1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number 1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number
1. Add the security researcher to the [Security Researcher Acknowledgments list](http://about.gitlab.com/vulnerability-acknowledgements/) 1. Add the security researcher to the [Security Researcher Acknowledgments list](http://about.gitlab.com/vulnerability-acknowledgements/)
......
# From 6.x or 7.x to 7.5 # From 6.x or 7.x to 7.6
This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.5. This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.6.
## Global issue numbers ## Global issue numbers
...@@ -70,7 +70,7 @@ sudo -u git -H git checkout -- db/schema.rb # local changes will be restored aut ...@@ -70,7 +70,7 @@ sudo -u git -H git checkout -- db/schema.rb # local changes will be restored aut
For GitLab Community Edition: For GitLab Community Edition:
```bash ```bash
sudo -u git -H git checkout 7-5-stable sudo -u git -H git checkout 7-6-stable
``` ```
OR OR
...@@ -78,7 +78,7 @@ OR ...@@ -78,7 +78,7 @@ OR
For GitLab Enterprise Edition: For GitLab Enterprise Edition:
```bash ```bash
sudo -u git -H git checkout 7-5-stable-ee sudo -u git -H git checkout 7-6-stable-ee
``` ```
## 4. Install additional packages ## 4. Install additional packages
...@@ -119,7 +119,7 @@ sudo apt-get install pkg-config cmake ...@@ -119,7 +119,7 @@ sudo apt-get install pkg-config cmake
```bash ```bash
cd /home/git/gitlab-shell cd /home/git/gitlab-shell
sudo -u git -H git fetch sudo -u git -H git fetch
sudo -u git -H git checkout v2.2.0 sudo -u git -H git checkout v2.4.0
``` ```
## 7. Install libs, migrations, etc. ## 7. Install libs, migrations, etc.
...@@ -154,14 +154,14 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab ...@@ -154,14 +154,14 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
TIP: to see what changed in `gitlab.yml.example` in this release use next command: TIP: to see what changed in `gitlab.yml.example` in this release use next command:
``` ```
git diff 6-0-stable:config/gitlab.yml.example 7-5-stable:config/gitlab.yml.example git diff 6-0-stable:config/gitlab.yml.example 7-6-stable:config/gitlab.yml.example
``` ```
* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/config/gitlab.yml.example but with your settings. * Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-6-stable/config/gitlab.yml.example but with your settings.
* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/config/unicorn.rb.example but with your settings. * Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-6-stable/config/unicorn.rb.example but with your settings.
* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.2.0/config.yml.example but with your settings. * Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.4.0/config.yml.example but with your settings.
* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/lib/support/nginx/gitlab but with your settings. * HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-6-stable/lib/support/nginx/gitlab but with your settings.
* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/lib/support/nginx/gitlab-ssl but with your settings. * HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-6-stable/lib/support/nginx/gitlab-ssl but with your settings.
* Copy rack attack middleware config * Copy rack attack middleware config
```bash ```bash
......
...@@ -35,8 +35,6 @@ sudo -u git -H git checkout 7-4-stable-ee ...@@ -35,8 +35,6 @@ sudo -u git -H git checkout 7-4-stable-ee
### 3. Install libs, migrations, etc. ### 3. Install libs, migrations, etc.
```bash ```bash
cd /home/git/gitlab
# MySQL installations (note: the line below states '--without ... postgres') # MySQL installations (note: the line below states '--without ... postgres')
sudo -u git -H bundle install --without development test postgres --deployment sudo -u git -H bundle install --without development test postgres --deployment
...@@ -167,6 +165,10 @@ mysql> \q ...@@ -167,6 +165,10 @@ mysql> \q
# Set production -> password: the password your replaced $password with earlier # Set production -> password: the password your replaced $password with earlier
sudo -u git -H editor /home/git/gitlab/config/database.yml sudo -u git -H editor /home/git/gitlab/config/database.yml
# Start GitLab
sudo service gitlab start
sudo service nginx restart
# Run thorough check # Run thorough check
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
``` ```
......
...@@ -32,7 +32,15 @@ For GitLab Enterprise Edition: ...@@ -32,7 +32,15 @@ For GitLab Enterprise Edition:
sudo -u git -H git checkout 7-5-stable-ee sudo -u git -H git checkout 7-5-stable-ee
``` ```
### 3. Install libs, migrations, etc. ### 3. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
sudo -u git -H git checkout v2.2.0
```
### 4. Install libs, migrations, etc.
```bash ```bash
cd /home/git/gitlab cd /home/git/gitlab
...@@ -53,7 +61,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS ...@@ -53,7 +61,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
``` ```
### 4. Update config files ### 5. Update config files
#### New configuration options for gitlab.yml #### New configuration options for gitlab.yml
...@@ -63,28 +71,17 @@ There are new configuration options available for gitlab.yml. View them with the ...@@ -63,28 +71,17 @@ There are new configuration options available for gitlab.yml. View them with the
git diff origin/7-4-stable:config/gitlab.yml.example origin/7-5-stable:config/gitlab.yml.example git diff origin/7-4-stable:config/gitlab.yml.example origin/7-5-stable:config/gitlab.yml.example
``` ```
#### Change timeout for unicorn #### Change Nginx settings
```
# set timeout to 60
sudo -u git -H editor config/unicorn.rb
```
#### Change nginx https settings
* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/lib/support/nginx/gitlab-ssl but with your setting
#### MySQL Databases: Update database.yml config file
* Add `collation: utf8_general_ci` to config/database.yml as seen in [config/database.yml.mysql](config/database.yml.mysql) * HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings
* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your setting
### 6. Start application
### 5. Start application
sudo service gitlab start sudo service gitlab start
sudo service nginx restart sudo service nginx restart
### 6. Check application status ### 7. Check application status
Check if GitLab and its environment are configured correctly: Check if GitLab and its environment are configured correctly:
...@@ -96,82 +93,6 @@ To make sure you didn't miss anything run a more thorough check with: ...@@ -96,82 +93,6 @@ To make sure you didn't miss anything run a more thorough check with:
If all items are green, then congratulations upgrade is complete! If all items are green, then congratulations upgrade is complete!
### 7. Optional optimizations for GitLab setups with MySQL databases
Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand.
```
# Stop GitLab
sudo service gitlab stop
# Secure your MySQL installation (added in GitLab 6.2)
sudo mysql_secure_installation
# Login to MySQL
mysql -u root -p
# do not type the 'mysql>', this is part of the prompt
# Convert all tables to use the InnoDB storage engine (added in GitLab 6.8)
SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' ENGINE=InnoDB;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `ENGINE` <> 'InnoDB' AND `TABLE_TYPE` = 'BASE TABLE';
# If previous query returned results, copy & run all outputed SQL statements
# Convert all tables to correct character set
SET foreign_key_checks = 0;
SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `TABLE_COLLATION` <> 'utf8_unicode_ci' AND `TABLE_TYPE` = 'BASE TABLE';
# If previous query returned results, copy & run all outputed SQL statements
# turn foreign key checks back on
SET foreign_key_checks = 1;
# Find MySQL users
mysql> SELECT user FROM mysql.user WHERE user LIKE '%git%';
# If git user exists and gitlab user does not exist
# you are done with the database cleanup tasks
mysql> \q
# If both users exist skip to Delete gitlab user
# Create new user for GitLab (changed in GitLab 6.4)
# change $password in the command below to a real password you pick
mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
# Grant the git user necessary permissions on the database
mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost';
# Delete the old gitlab user
mysql> DELETE FROM mysql.user WHERE user='gitlab';
# Quit the database session
mysql> \q
# Try connecting to the new database with the new user
sudo -u git -H mysql -u git -p -D gitlabhq_production
# Type the password you replaced $password with earlier
# You should now see a 'mysql>' prompt
# Quit the database session
mysql> \q
# Update database configuration details
# See config/database.yml.mysql for latest recommended configuration details
# Remove the reaping_frequency setting line if it exists (removed in GitLab 6.8)
# Set production -> pool: 10 (updated in GitLab 5.3)
# Set production -> username: git
# Set production -> password: the password your replaced $password with earlier
sudo -u git -H editor /home/git/gitlab/config/database.yml
# Run thorough check
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
```
## Things went south? Revert to previous version (7.4) ## Things went south? Revert to previous version (7.4)
### 1. Revert the code to the previous version ### 1. Revert the code to the previous version
......
# From 7.5 to 7.6
### 0. Stop server
sudo service gitlab stop
### 1. Backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
### 2. Get latest code
```bash
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
```
For GitLab Community Edition:
```bash
sudo -u git -H git checkout 7-6-stable
```
OR
For GitLab Enterprise Edition:
```bash
sudo -u git -H git checkout 7-6-stable-ee
```
### 3. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
sudo -u git -H git checkout v2.4.0
```
### 4. Install libs, migrations, etc.
```bash
sudo apt-get install libkrb5-dev
cd /home/git/gitlab
# MySQL installations (note: the line below states '--without ... postgres')
sudo -u git -H bundle install --without development test postgres --deployment
# PostgreSQL installations (note: the line below states '--without ... mysql')
sudo -u git -H bundle install --without development test mysql --deployment
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
# Clean up assets and cache
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
# Update init.d script
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
```
### 5. Update config files
#### New configuration options for `gitlab.yml`
There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
```
git diff origin/7-6-stable:config/gitlab.yml.example origin/7-6-stable:config/gitlab.yml.example
```
#### Change Nginx settings
* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings
* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your setting
#### Setup time zone (optional)
Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it.
### 6. Start application
sudo service gitlab start
sudo service nginx restart
### 7. Check application status
Check if GitLab and its environment are configured correctly:
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
To make sure you didn't miss anything run a more thorough check with:
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
If all items are green, then congratulations upgrade is complete!
## Things went south? Revert to previous version (7.5)
### 1. Revert the code to the previous version
Follow the [upgrade guide from 7.4 to 7.5](7.4-to-7.5.md), except for the database migration
(The backup is already migrated to the previous version)
### 2. Restore from the backup:
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
...@@ -10,6 +10,8 @@ If you have local changes to your GitLab repository the script will stash them a ...@@ -10,6 +10,8 @@ If you have local changes to your GitLab repository the script will stash them a
**GitLab Upgrader is available only for GitLab version 6.4.2 or higher.** **GitLab Upgrader is available only for GitLab version 6.4.2 or higher.**
**This script does NOT update gitlab-shell, it needs manual update. See step 5 below.**
## 0. Backup ## 0. Backup
cd /home/git/gitlab cd /home/git/gitlab
......
...@@ -54,6 +54,29 @@ Triggered when you push to the repository except when pushing tags. ...@@ -54,6 +54,29 @@ Triggered when you push to the repository except when pushing tags.
} }
``` ```
## Tag events
Triggered when you create (or delete) tags to the repository.
**Request body:**
```json
{
"ref": "refs/tags/v1.0.0",
"before": "0000000000000000000000000000000000000000",
"after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
"user_id": 1,
"user_name": "John Smith",
"project_id": 1,
"repository": {
"name": "jsmith",
"url": "ssh://git@example.com/jsmith/example.git",
"description": "",
"homepage": "http://example.com/jsmith/example"
}
}
```
## Issues events ## Issues events
Triggered when a new issue is created or an existing issue was updated/closed/reopened. Triggered when a new issue is created or an existing issue was updated/closed/reopened.
......
FROM ubuntu:14.04
# Install required packages
RUN apt-get update -q \
&& DEBIAN_FRONTEND=noninteractive apt-get install -qy \
openssh-server \
wget \
&& apt-get clean
# Download & Install GitLab
# If the Omnibus package version below is outdated please contribute a merge request to update it.
# If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
RUN TMP_FILE=$(mktemp); \
wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.5.2-omnibus.5.2.1.ci-1_amd64.deb \
&& dpkg -i $TMP_FILE \
&& rm -f $TMP_FILE
# Manage SSHD through runit
RUN mkdir -p /opt/gitlab/sv/sshd/supervise \
&& mkfifo /opt/gitlab/sv/sshd/supervise/ok \
&& printf "#!/bin/sh\nexec 2>&1\numask 077\nexec /usr/sbin/sshd -D" > /opt/gitlab/sv/sshd/run \
&& chmod a+x /opt/gitlab/sv/sshd/run \
&& ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \
&& mkdir -p /var/run/sshd
# Expose web & ssh
EXPOSE 80 22
# Volume & configuration
VOLUME ["/var/opt/gitlab", "/var/log/gitlab", "/etc/gitlab"]
ADD gitlab.rb /etc/gitlab/
# Default is to run runit & reconfigure
CMD gitlab-ctl reconfigure & /opt/gitlab/embedded/bin/runsvdir-start
What is GitLab?
===============
GitLab offers git repository management, code reviews, issue tracking, activity feeds, wikis. It has LDAP/AD integration, handles 25,000 users on a single server but can also run on a highly available active/active cluster. A subscription gives you access to our support team and to GitLab Enterprise Edition that contains extra features aimed at larger organizations.
<https://about.gitlab.com>
![GitLab Logo](https://gitlab.com/uploads/appearance/logo/1/brand_logo-c37eb221b456bb4b472cc1084480991f.png)
How to use this image
======================
At this moment GitLab doesn't have official Docker images.
Build your own based on the Omnibus packages with the following command (it assumes you're in the GitLab repo root directory):
```bash
sudo docker build --tag gitlab_image docker/
```
We assume using a data volume container, this will simplify migrations and backups.
This empty container will exist to persist as volumes the 3 directories used by GitLab, so remember not to delete it.
The directories on data container are:
- `/var/opt/gitlab` for application data
- `/var/log/gitlab` for logs
- `/etc/gitlab` for configuration
Create the data container with:
```bash
sudo docker run --name gitlab_data gitlab_image /bin/true
```
After creating this run GitLab:
```bash
sudo docker run --detach --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image
```
It might take a while before the docker container is responding to queries. You can follow the configuration process with `docker logs -f gitlab_app`.
You can then go to `http://localhost:8080/` (or `http://192.168.59.103:8080/` if you use boot2docker).
You can login with username `root` and password `5iveL!fe`.
Next time, you can just use `sudo docker start gitlab_app` and `sudo docker stop gitlab_app`.
How to configure GitLab
========================
This container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`.
To access GitLab configuration, you can start an interactive command line in a new container using the shared data volume container, you will be able to browse the 3 directories and use your favorite text editor:
```bash
docker run -ti -e TERM=linux --rm --volumes-from gitlab_data ubuntu
vi /etc/gitlab/gitlab.rb
```
**Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab.
You can find all available options in [Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration).
Troubleshooting
=========================
Please see the [troubleshooting](troubleshooting.md) file in this directory.
# External URL should be your Docker instance.
# By default, this example is the "standard" boot2docker IP.
# Always use port 80 here to force the internal nginx to bind port 80,
# even if you intend to use another port in Docker.
external_url "http://192.168.59.103/"
# Prevent Postgres from trying to allocate 25% of total memory
postgresql['shared_buffers'] = '1MB'
# Configure GitLab to redirect PostgreSQL logs to the data volume
postgresql['log_directory'] = '/var/log/gitlab/postgresql'
# Some configuration of GitLab
# You can find more at https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration
gitlab_rails['gitlab_email_from'] = 'gitlab@example.com'
gitlab_rails['gitlab_support_email'] = 'support@example.com'
gitlab_rails['time_zone'] = 'Europe/Paris'
# SMTP settings
# You must use an external server, the Docker container does not install an SMTP server
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.example.com"
gitlab_rails['smtp_port'] = 587
gitlab_rails['smtp_user_name'] = "user"
gitlab_rails['smtp_password'] = "password"
gitlab_rails['smtp_domain'] = "example.com"
gitlab_rails['smtp_authentication'] = "plain"
gitlab_rails['smtp_enable_starttls_auto'] = true
# Enable LDAP authentication
# gitlab_rails['ldap_enabled'] = true
# gitlab_rails['ldap_host'] = 'ldap.example.com'
# gitlab_rails['ldap_port'] = 389
# gitlab_rails['ldap_method'] = 'plain' # 'ssl' or 'plain'
# gitlab_rails['ldap_allow_username_or_email_login'] = false
# gitlab_rails['ldap_uid'] = 'uid'
# gitlab_rails['ldap_base'] = 'ou=users,dc=example,dc=com'
# Troubleshooting
This is to troubleshoot https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/245
But it might contain useful commands for other cases as well.
The configuration to add the postgres log in vim is:
postgresql['log_directory'] = '/var/log/gitlab/postgresql'
# Commands
```bash
sudo docker build --tag gitlab_image docker/
sudo docker rm -f gitlab_app
sudo docker rm -f gitlab_data
sudo docker run --name gitlab_data gitlab_image /bin/true
sudo docker run -ti --rm --volumes-from gitlab_data ubuntu apt-get update && sudo apt-get install -y vim && sudo vim /etc/gitlab/gitlab.rb
sudo docker run --detach --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image
sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/reconfigure.log
sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/postgresql/current
sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /etc/gitlab/gitlab.rb
```
# Interactively
```bash
# First start a GitLab container without starting GitLab
# This is almost the same as starting the GitLab container except:
# - we run interactively (-t -i)
# - we define TERM=linux because it allows to use arrow keys in vi (!!!)
# - we choose another startup command (bash)
sudo docker run -ti -e TERM=linux --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image bash
# Configure GitLab to redirect PostgreSQL logs
echo "postgresql['log_directory'] = '/var/log/gitlab/postgresql'" >> /etc/gitlab/gitlab.rb
# Prevent Postgres from allocating 25% of total memory
echo "postgresql['shared_buffers'] = '1MB'" >> /etc/gitlab/gitlab.rb
# You can now start GitLab manually from Bash (in the background)
# Maybe the command below is still missing something to run in the background
gitlab-ctl reconfigure > /var/log/gitlab/reconfigure.log & /opt/gitlab/embedded/bin/runsvdir-start &
# Inspect PostgreSQL config
cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
# And tail the logs (PostgreSQL log may not exist immediately)
tail -f /var/log/gitlab/reconfigure.log /var/log/gitlab/postgresql/current
# And get the memory
cat /proc/meminfo
head /proc/sys/kernel/shmmax /proc/sys/kernel/shmall
free -m
```
...@@ -110,7 +110,7 @@ Feature: Project Active Tab ...@@ -110,7 +110,7 @@ Feature: Project Active Tab
Scenario: On Project Issues/Browse Scenario: On Project Issues/Browse
Given I visit my project's issues page Given I visit my project's issues page
Then the active sub tab should be Browse Issues Then the active sub tab should be Issues
And no other sub tabs should be active And no other sub tabs should be active
And the active main tab should be Issues And the active main tab should be Issues
......
...@@ -16,12 +16,12 @@ Feature: Project Commits Comments ...@@ -16,12 +16,12 @@ Feature: Project Commits Comments
@javascript @javascript
Scenario: I can't preview without text Scenario: I can't preview without text
Given I haven't written any comment text Given I haven't written any comment text
Then I should not see the comment preview button Then The comment preview tab should say there is nothing to do
@javascript @javascript
Scenario: I can preview with text Scenario: I can preview with text
Given I write a comment like "Nice" Given I write a comment like ":+1: Nice"
Then I should see the comment preview button Then The comment preview tab should be display rendered Markdown
@javascript @javascript
Scenario: I preview a comment Scenario: I preview a comment
...@@ -32,7 +32,7 @@ Feature: Project Commits Comments ...@@ -32,7 +32,7 @@ Feature: Project Commits Comments
@javascript @javascript
Scenario: I can edit after preview Scenario: I can edit after preview
Given I preview a comment text like "Bug fixed :smile:" Given I preview a comment text like "Bug fixed :smile:"
Then I should see the comment edit button Then I should see the comment write tab
@javascript @javascript
Scenario: I have a reset form after posting from preview Scenario: I have a reset form after posting from preview
......
...@@ -58,13 +58,13 @@ Feature: Project Commits Diff Comments ...@@ -58,13 +58,13 @@ Feature: Project Commits Diff Comments
Scenario: I can't preview without text Scenario: I can't preview without text
Given I open a diff comment form Given I open a diff comment form
And I haven't written any diff comment text And I haven't written any diff comment text
Then I should not see the diff comment preview button Then The diff comment preview tab should say there is nothing to do
@javascript @javascript
Scenario: I can preview with text Scenario: I can preview with text
Given I open a diff comment form Given I open a diff comment form
And I write a diff comment like ":-1: I don't like this" And I write a diff comment like ":-1: I don't like this"
Then I should see the diff comment preview button Then The diff comment preview tab should display rendered Markdown
@javascript @javascript
Scenario: I preview a diff comment Scenario: I preview a diff comment
...@@ -75,7 +75,7 @@ Feature: Project Commits Diff Comments ...@@ -75,7 +75,7 @@ Feature: Project Commits Diff Comments
@javascript @javascript
Scenario: I can edit after preview Scenario: I can edit after preview
Given I preview a diff comment text like "Should fix it :smile:" Given I preview a diff comment text like "Should fix it :smile:"
Then I should see the diff comment edit button Then I should see the diff comment write tab
@javascript @javascript
Scenario: The form gets removed after posting Scenario: The form gets removed after posting
......
...@@ -6,9 +6,11 @@ Feature: Project Fork ...@@ -6,9 +6,11 @@ Feature: Project Fork
Scenario: User fork a project Scenario: User fork a project
Given I click link "Fork" Given I click link "Fork"
When I fork to my namespace
Then I should see the forked project page Then I should see the forked project page
Scenario: User already has forked the project Scenario: User already has forked the project
Given I already have a project named "Shop" in my namespace Given I already have a project named "Shop" in my namespace
And I click link "Fork" And I click link "Fork"
When I fork to my namespace
Then I should see a "Name has already been taken" warning Then I should see a "Name has already been taken" warning
...@@ -159,3 +159,37 @@ Feature: Project Issues ...@@ -159,3 +159,37 @@ Feature: Project Issues
Given project "Shop" has "Tasks-closed" closed issue with task markdown Given project "Shop" has "Tasks-closed" closed issue with task markdown
When I visit issue page "Tasks-closed" When I visit issue page "Tasks-closed"
Then Task checkboxes should be disabled Then Task checkboxes should be disabled
# Issue description preview
@javascript
Scenario: I can't preview without text
Given I click link "New Issue"
And I haven't written any description text
Then The Markdown preview tab should say there is nothing to do
@javascript
Scenario: I can preview with text
Given I click link "New Issue"
And I write a description like ":+1: Nice"
Then The Markdown preview tab should display rendered Markdown
@javascript
Scenario: I preview an issue description
Given I click link "New Issue"
And I preview a description text like "Bug fixed :smile:"
Then I should see the Markdown preview
And I should not see the Markdown text field
@javascript
Scenario: I can edit after preview
Given I click link "New Issue"
And I preview a description text like "Bug fixed :smile:"
Then I should see the Markdown write tab
@javascript
Scenario: I can preview when editing an existing issue
Given I click link "Release 0.4"
And I click link "Edit" for the issue
And I preview a description text like "Bug fixed :smile:"
Then I should see the Markdown write tab
...@@ -187,3 +187,34 @@ Feature: Project Merge Requests ...@@ -187,3 +187,34 @@ Feature: Project Merge Requests
And I visit merge request page "MR-task-open" And I visit merge request page "MR-task-open"
And I click link "Close" And I click link "Close"
Then Task checkboxes should be disabled Then Task checkboxes should be disabled
# Description preview
@javascript
Scenario: I can't preview without text
Given I visit merge request page "Bug NS-04"
And I click link "Edit" for the merge request
And I haven't written any description text
Then The Markdown preview tab should say there is nothing to do
@javascript
Scenario: I can preview with text
Given I visit merge request page "Bug NS-04"
And I click link "Edit" for the merge request
And I write a description like ":+1: Nice"
Then The Markdown preview tab should display rendered Markdown
@javascript
Scenario: I preview a merge request description
Given I visit merge request page "Bug NS-04"
And I click link "Edit" for the merge request
And I preview a description text like "Bug fixed :smile:"
Then I should see the Markdown preview
And I should not see the Markdown text field
@javascript
Scenario: I can edit after preview
Given I visit merge request page "Bug NS-04"
And I click link "Edit" for the merge request
And I preview a description text like "Bug fixed :smile:"
Then I should see the Markdown write tab
...@@ -19,6 +19,12 @@ Feature: Project Services ...@@ -19,6 +19,12 @@ Feature: Project Services
And I fill hipchat settings And I fill hipchat settings
Then I should see hipchat service settings saved Then I should see hipchat service settings saved
Scenario: Activate hipchat service with custom server
When I visit project "Shop" services page
And I click hipchat service link
And I fill hipchat settings with custom server
Then I should see hipchat service settings with custom server saved
Scenario: Activate pivotaltracker service Scenario: Activate pivotaltracker service
When I visit project "Shop" services page When I visit project "Shop" services page
And I click pivotaltracker service link And I click pivotaltracker service link
......
...@@ -170,7 +170,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps ...@@ -170,7 +170,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end end
step "I am not an ldap user" do step "I am not an ldap user" do
current_user.update_attributes(extern_uid: nil, provider: '') current_user.identities.delete
current_user.ldap_user?.should be_false current_user.ldap_user?.should be_false
end end
......
...@@ -89,8 +89,8 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps ...@@ -89,8 +89,8 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
click_link('Labels') click_link('Labels')
end end
step 'the active sub tab should be Browse Issues' do step 'the active sub tab should be Issues' do
ensure_active_sub_tab('Browse Issues') ensure_active_sub_tab('Issues')
end end
step 'the active sub tab should be Milestones' do step 'the active sub tab should be Milestones' do
......
...@@ -25,4 +25,10 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps ...@@ -25,4 +25,10 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
step 'I should see a "Name has already been taken" warning' do step 'I should see a "Name has already been taken" warning' do
page.should have_content "Name has already been taken" page.should have_content "Name has already been taken"
end end
step 'I fork to my namespace' do
within '.fork-namespaces' do
click_link current_user.name
end
end
end end
class Spinach::Features::ProjectIssues < Spinach::FeatureSteps class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedIssuable
include SharedProject include SharedProject
include SharedNote include SharedNote
include SharedPaths include SharedPaths
......
...@@ -8,7 +8,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps ...@@ -8,7 +8,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
milestone = @project.milestones.find_by(title: "v2.2") milestone = @project.milestones.find_by(title: "v2.2")
page.should have_content(milestone.title[0..10]) page.should have_content(milestone.title[0..10])
page.should have_content(milestone.expires_at) page.should have_content(milestone.expires_at)
page.should have_content("Browse Issues") page.should have_content("Issues")
end end
step 'I click link "v2.2"' do step 'I click link "v2.2"' do
...@@ -28,7 +28,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps ...@@ -28,7 +28,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
milestone = @project.milestones.find_by(title: "v2.3") milestone = @project.milestones.find_by(title: "v2.3")
page.should have_content(milestone.title[0..10]) page.should have_content(milestone.title[0..10])
page.should have_content(milestone.expires_at) page.should have_content(milestone.expires_at)
page.should have_content("Browse Issues") page.should have_content("Issues")
end end
step 'project "Shop" has milestone "v2.2"' do step 'project "Shop" has milestone "v2.2"' do
......
class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedIssuable
include SharedProject include SharedProject
include SharedNote include SharedNote
include SharedPaths include SharedPaths
......
...@@ -10,7 +10,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps ...@@ -10,7 +10,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
step 'I should see list of available services' do step 'I should see list of available services' do
page.should have_content 'Project services' page.should have_content 'Project services'
page.should have_content 'Campfire' page.should have_content 'Campfire'
page.should have_content 'Hipchat' page.should have_content 'HipChat'
page.should have_content 'GitLab CI' page.should have_content 'GitLab CI'
page.should have_content 'Assembla' page.should have_content 'Assembla'
page.should have_content 'Pushover' page.should have_content 'Pushover'
...@@ -33,7 +33,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps ...@@ -33,7 +33,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end end
step 'I click hipchat service link' do step 'I click hipchat service link' do
click_link 'Hipchat' click_link 'HipChat'
end end
step 'I fill hipchat settings' do step 'I fill hipchat settings' do
...@@ -47,6 +47,17 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps ...@@ -47,6 +47,17 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
find_field('Room').value.should == 'gitlab' find_field('Room').value.should == 'gitlab'
end end
step 'I fill hipchat settings with custom server' do
check 'Active'
fill_in 'Room', with: 'gitlab_custom'
fill_in 'Token', with: 'secretCustom'
fill_in 'Server', with: 'https://chat.example.com'
click_button 'Save'
end
step 'I should see hipchat service settings with custom server saved' do
find_field('Server').value.should == 'https://chat.example.com'
end
step 'I click pivotaltracker service link' do step 'I click pivotaltracker service link' do
click_link 'PivotalTracker' click_link 'PivotalTracker'
......
...@@ -32,7 +32,7 @@ module SharedDiffNote ...@@ -32,7 +32,7 @@ module SharedDiffNote
click_diff_line(sample_commit.line_code) click_diff_line(sample_commit.line_code)
within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "Should fix it :smile:" fill_in "note[note]", with: "Should fix it :smile:"
find(".js-note-preview-button").trigger("click") find('.js-md-preview-button').click
end end
end end
...@@ -41,7 +41,7 @@ module SharedDiffNote ...@@ -41,7 +41,7 @@ module SharedDiffNote
within("#{diff_file_selector} form[rel$='#{sample_commit.del_line_code}']") do within("#{diff_file_selector} form[rel$='#{sample_commit.del_line_code}']") do
fill_in "note[note]", with: "DRY this up" fill_in "note[note]", with: "DRY this up"
find(".js-note-preview-button").trigger("click") find('.js-md-preview-button').click
end end
end end
...@@ -71,9 +71,10 @@ module SharedDiffNote ...@@ -71,9 +71,10 @@ module SharedDiffNote
end end
end end
step 'I should not see the diff comment preview button' do step 'The diff comment preview tab should say there is nothing to do' do
within(diff_file_selector) do within(diff_file_selector) do
page.should have_css(".js-note-preview-button", visible: false) find('.js-md-preview-button').click
expect(find('.js-md-preview')).to have_content('Nothing to preview.')
end end
end end
...@@ -131,27 +132,28 @@ module SharedDiffNote ...@@ -131,27 +132,28 @@ module SharedDiffNote
step 'I should see the diff comment preview' do step 'I should see the diff comment preview' do
within("#{diff_file_selector} form") do within("#{diff_file_selector} form") do
page.should have_css(".js-note-preview", visible: false) expect(page).to have_css('.js-md-preview', visible: true)
end end
end end
step 'I should see the diff comment edit button' do step 'I should see the diff comment write tab' do
within(diff_file_selector) do within(diff_file_selector) do
page.should have_css(".js-note-write-button", visible: true) expect(page).to have_css('.js-md-write-button', visible: true)
end end
end end
step 'I should see the diff comment preview button' do step 'The diff comment preview tab should display rendered Markdown' do
within(diff_file_selector) do within(diff_file_selector) do
page.should have_css(".js-note-preview-button", visible: true) find('.js-md-preview-button').click
expect(find('.js-md-preview')).to have_css('img.emoji', visible: true)
end end
end end
step 'I should see two separate previews' do step 'I should see two separate previews' do
within(diff_file_selector) do within(diff_file_selector) do
page.should have_css(".js-note-preview", visible: true, count: 2) expect(page).to have_css('.js-md-preview', visible: true, count: 2)
page.should have_content("Should fix it") expect(page).to have_content('Should fix it')
page.should have_content("DRY this up") expect(page).to have_content('DRY this up')
end end
end end
......
module SharedIssuable
include Spinach::DSL
def edit_issuable
find('.issue-btn-group').click_link 'Edit'
end
step 'I click link "Edit" for the merge request' do
edit_issuable
end
step 'I click link "Edit" for the issue' do
edit_issuable
end
end
...@@ -54,4 +54,49 @@ EOT ...@@ -54,4 +54,49 @@ EOT
'div.description li.task-list-item input[type="checkbox"]:disabled' 'div.description li.task-list-item input[type="checkbox"]:disabled'
) )
end end
step 'I should not see the Markdown preview' do
expect(find('.gfm-form .js-md-preview')).not_to be_visible
end
step 'The Markdown preview tab should say there is nothing to do' do
within('.gfm-form') do
find('.js-md-preview-button').click
expect(find('.js-md-preview')).to have_content('Nothing to preview.')
end
end
step 'I should not see the Markdown text field' do
expect(find('.gfm-form textarea')).not_to be_visible
end
step 'I should see the Markdown write tab' do
expect(find('.gfm-form')).to have_css('.js-md-write-button', visible: true)
end
step 'I should see the Markdown preview' do
expect(find('.gfm-form')).to have_css('.js-md-preview', visible: true)
end
step 'The Markdown preview tab should display rendered Markdown' do
within('.gfm-form') do
find('.js-md-preview-button').click
expect(find('.js-md-preview')).to have_css('img.emoji', visible: true)
end
end
step 'I write a description like ":+1: Nice"' do
find('.gfm-form').fill_in 'Description', with: ':+1: Nice'
end
step 'I preview a description text like "Bug fixed :smile:"' do
within('.gfm-form') do
fill_in 'Description', with: 'Bug fixed :smile:'
find('.js-md-preview-button').click
end
end
step 'I haven\'t written any description text' do
find('.gfm-form').fill_in 'Description', with: ''
end
end end
...@@ -23,7 +23,7 @@ module SharedNote ...@@ -23,7 +23,7 @@ module SharedNote
step 'I preview a comment text like "Bug fixed :smile:"' do step 'I preview a comment text like "Bug fixed :smile:"' do
within(".js-main-target-form") do within(".js-main-target-form") do
fill_in "note[note]", with: "Bug fixed :smile:" fill_in "note[note]", with: "Bug fixed :smile:"
find(".js-note-preview-button").trigger("click") find('.js-md-preview-button').click
end end
end end
...@@ -33,9 +33,9 @@ module SharedNote ...@@ -33,9 +33,9 @@ module SharedNote
end end
end end
step 'I write a comment like "Nice"' do step 'I write a comment like ":+1: Nice"' do
within(".js-main-target-form") do within(".js-main-target-form") do
fill_in "note[note]", with: "Nice" fill_in 'note[note]', with: ':+1: Nice'
end end
end end
...@@ -51,13 +51,14 @@ module SharedNote ...@@ -51,13 +51,14 @@ module SharedNote
step 'I should not see the comment preview' do step 'I should not see the comment preview' do
within(".js-main-target-form") do within(".js-main-target-form") do
page.should have_css(".js-note-preview", visible: false) expect(find('.js-md-preview')).not_to be_visible
end end
end end
step 'I should not see the comment preview button' do step 'The comment preview tab should say there is nothing to do' do
within(".js-main-target-form") do within(".js-main-target-form") do
page.should have_css(".js-note-preview-button", visible: false) find('.js-md-preview-button').click
expect(find('.js-md-preview')).to have_content('Nothing to preview.')
end end
end end
...@@ -79,21 +80,22 @@ module SharedNote ...@@ -79,21 +80,22 @@ module SharedNote
end end
end end
step 'I should see the comment edit button' do step 'I should see the comment write tab' do
within(".js-main-target-form") do within(".js-main-target-form") do
page.should have_css(".js-note-write-button", visible: true) expect(page).to have_css('.js-md-write-button', visible: true)
end end
end end
step 'I should see the comment preview' do step 'The comment preview tab should be display rendered Markdown' do
within(".js-main-target-form") do within(".js-main-target-form") do
page.should have_css(".js-note-preview", visible: true) find('.js-md-preview-button').click
expect(find('.js-md-preview')).to have_css('img.emoji', visible: true)
end end
end end
step 'I should see the comment preview button' do step 'I should see the comment preview' do
within(".js-main-target-form") do within(".js-main-target-form") do
page.should have_css(".js-note-preview-button", visible: true) expect(page).to have_css('.js-md-preview', visible: true)
end end
end end
......
...@@ -131,7 +131,7 @@ module SharedProject ...@@ -131,7 +131,7 @@ module SharedProject
end end
step 'public empty project "Empty Public Project"' do step 'public empty project "Empty Public Project"' do
create :empty_project, :public, name: "Empty Public Project" create :project_empty_repo, :public, name: "Empty Public Project"
end end
step 'project "Community" has comments' do step 'project "Community" has comments' do
......
...@@ -14,10 +14,14 @@ module API ...@@ -14,10 +14,14 @@ module API
expose :bio, :skype, :linkedin, :twitter, :website_url expose :bio, :skype, :linkedin, :twitter, :website_url
end end
class Identity < Grape::Entity
expose :provider, :extern_uid
end
class UserFull < User class UserFull < User
expose :email expose :email
expose :theme_id, :color_scheme_id, :extern_uid, :provider, \ expose :theme_id, :color_scheme_id, :projects_limit
:projects_limit expose :identities, using: Entities::Identity
expose :can_create_group?, as: :can_create_group expose :can_create_group?, as: :can_create_group
expose :can_create_project?, as: :can_create_project expose :can_create_project?, as: :can_create_project
end end
......
...@@ -33,17 +33,22 @@ module API ...@@ -33,17 +33,22 @@ module API
end end
project = Project.find_with_namespace(project_path) project = Project.find_with_namespace(project_path)
return false unless project
unless project
return Gitlab::GitAccessStatus.new(false, 'No such project')
end
actor = if params[:key_id] actor = if params[:key_id]
Key.find(params[:key_id]) Key.find_by(id: params[:key_id])
elsif params[:user_id] elsif params[:user_id]
User.find(params[:user_id]) User.find_by(id: params[:user_id])
end end
return false unless actor unless actor
return Gitlab::GitAccessStatus.new(false, 'No such user or key')
end
access.allowed?( access.check(
actor, actor,
params[:action], params[:action],
project, project,
......
...@@ -59,10 +59,16 @@ module API ...@@ -59,10 +59,16 @@ module API
post do post do
authenticated_as_admin! authenticated_as_admin!
required_attributes! [:email, :password, :name, :username] required_attributes! [:email, :password, :name, :username]
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin] attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :can_create_group, :admin]
user = User.build_user(attrs) user = User.build_user(attrs)
admin = attrs.delete(:admin) admin = attrs.delete(:admin)
user.admin = admin unless admin.nil? user.admin = admin unless admin.nil?
identity_attrs = attributes_for_keys [:provider, :extern_uid]
if identity_attrs.any?
user.identities.build(identity_attrs)
end
if user.save if user.save
present user, with: Entities::UserFull present user, with: Entities::UserFull
else else
...@@ -89,8 +95,6 @@ module API ...@@ -89,8 +95,6 @@ module API
# twitter - Twitter account # twitter - Twitter account
# website_url - Website url # website_url - Website url
# projects_limit - Limit projects each user can create # projects_limit - Limit projects each user can create
# extern_uid - External authentication provider UID
# provider - External provider
# bio - Bio # bio - Bio
# admin - User is admin - true or false (default) # admin - User is admin - true or false (default)
# can_create_group - User can create groups - true or false # can_create_group - User can create groups - true or false
...@@ -99,7 +103,7 @@ module API ...@@ -99,7 +103,7 @@ module API
put ":id" do put ":id" do
authenticated_as_admin! authenticated_as_admin!
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin] attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :can_create_group, :admin]
user = User.find(params[:id]) user = User.find(params[:id])
not_found!('User') unless user not_found!('User') unless user
......
...@@ -13,10 +13,10 @@ module Backup ...@@ -13,10 +13,10 @@ module Backup
def dump def dump
success = case config["adapter"] success = case config["adapter"]
when /^mysql/ then when /^mysql/ then
print "Dumping MySQL database #{config['database']} ... " $progress.print "Dumping MySQL database #{config['database']} ... "
system('mysqldump', *mysql_args, config['database'], out: db_file_name) system('mysqldump', *mysql_args, config['database'], out: db_file_name)
when "postgresql" then when "postgresql" then
print "Dumping PostgreSQL database #{config['database']} ... " $progress.print "Dumping PostgreSQL database #{config['database']} ... "
pg_env pg_env
system('pg_dump', config['database'], out: db_file_name) system('pg_dump', config['database'], out: db_file_name)
end end
...@@ -27,10 +27,10 @@ module Backup ...@@ -27,10 +27,10 @@ module Backup
def restore def restore
success = case config["adapter"] success = case config["adapter"]
when /^mysql/ then when /^mysql/ then
print "Restoring MySQL database #{config['database']} ... " $progress.print "Restoring MySQL database #{config['database']} ... "
system('mysql', *mysql_args, config['database'], in: db_file_name) system('mysql', *mysql_args, config['database'], in: db_file_name)
when "postgresql" then when "postgresql" then
print "Restoring PostgreSQL database #{config['database']} ... " $progress.print "Restoring PostgreSQL database #{config['database']} ... "
# Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE # Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE
# statements like MySQL. # statements like MySQL.
Rake::Task["gitlab:db:drop_all_tables"].invoke Rake::Task["gitlab:db:drop_all_tables"].invoke
...@@ -69,9 +69,9 @@ module Backup ...@@ -69,9 +69,9 @@ module Backup
def report_success(success) def report_success(success)
if success if success
puts '[DONE]'.green $progress.puts '[DONE]'.green
else else
puts '[FAILED]'.red $progress.puts '[FAILED]'.red
end end
end end
end end
......
...@@ -18,11 +18,11 @@ module Backup ...@@ -18,11 +18,11 @@ module Backup
end end
# create archive # create archive
print "Creating backup archive: #{tar_file} ... " $progress.print "Creating backup archive: #{tar_file} ... "
if Kernel.system('tar', '-cf', tar_file, *BACKUP_CONTENTS) if Kernel.system('tar', '-cf', tar_file, *BACKUP_CONTENTS)
puts "done".green $progress.puts "done".green
else else
puts "failed".red puts "creating archive #{tar_file} failed".red
abort 'Backup failed' abort 'Backup failed'
end end
...@@ -31,37 +31,37 @@ module Backup ...@@ -31,37 +31,37 @@ module Backup
def upload(tar_file) def upload(tar_file)
remote_directory = Gitlab.config.backup.upload.remote_directory remote_directory = Gitlab.config.backup.upload.remote_directory
print "Uploading backup archive to remote storage #{remote_directory} ... " $progress.print "Uploading backup archive to remote storage #{remote_directory} ... "
connection_settings = Gitlab.config.backup.upload.connection connection_settings = Gitlab.config.backup.upload.connection
if connection_settings.blank? if connection_settings.blank?
puts "skipped".yellow $progress.puts "skipped".yellow
return return
end end
connection = ::Fog::Storage.new(connection_settings) connection = ::Fog::Storage.new(connection_settings)
directory = connection.directories.get(remote_directory) directory = connection.directories.get(remote_directory)
if directory.files.create(key: tar_file, body: File.open(tar_file), public: false) if directory.files.create(key: tar_file, body: File.open(tar_file), public: false)
puts "done".green $progress.puts "done".green
else else
puts "failed".red puts "uploading backup to #{remote_directory} failed".red
abort 'Backup failed' abort 'Backup failed'
end end
end end
def cleanup def cleanup
print "Deleting tmp directories ... " $progress.print "Deleting tmp directories ... "
if Kernel.system('rm', '-rf', *BACKUP_CONTENTS) if Kernel.system('rm', '-rf', *BACKUP_CONTENTS)
puts "done".green $progress.puts "done".green
else else
puts "failed".red puts "deleting tmp directory failed".red
abort 'Backup failed' abort 'Backup failed'
end end
end end
def remove_old def remove_old
# delete backups # delete backups
print "Deleting old backups ... " $progress.print "Deleting old backups ... "
keep_time = Gitlab.config.backup.keep_time.to_i keep_time = Gitlab.config.backup.keep_time.to_i
path = Gitlab.config.backup.path path = Gitlab.config.backup.path
...@@ -76,9 +76,9 @@ module Backup ...@@ -76,9 +76,9 @@ module Backup
end end
end end
end end
puts "done. (#{removed} removed)".green $progress.puts "done. (#{removed} removed)".green
else else
puts "skipping".yellow $progress.puts "skipping".yellow
end end
end end
...@@ -101,12 +101,12 @@ module Backup ...@@ -101,12 +101,12 @@ module Backup
exit 1 exit 1
end end
print "Unpacking backup ... " $progress.print "Unpacking backup ... "
unless Kernel.system(*%W(tar -xf #{tar_file})) unless Kernel.system(*%W(tar -xf #{tar_file}))
puts "failed".red puts "unpacking backup failed".red
exit 1 exit 1
else else
puts "done".green $progress.puts "done".green
end end
settings = YAML.load_file("backup_information.yml") settings = YAML.load_file("backup_information.yml")
......
...@@ -8,19 +8,21 @@ module Backup ...@@ -8,19 +8,21 @@ module Backup
prepare prepare
Project.find_each(batch_size: 1000) do |project| Project.find_each(batch_size: 1000) do |project|
print " * #{project.path_with_namespace} ... " $progress.print " * #{project.path_with_namespace} ... "
# Create namespace dir if missing # Create namespace dir if missing
FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.path)) if project.namespace FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.path)) if project.namespace
if project.empty_repo? if project.empty_repo?
puts "[SKIPPED]".cyan $progress.puts "[SKIPPED]".cyan
else else
output, status = Gitlab::Popen.popen(%W(git --git-dir=#{path_to_repo(project)} bundle create #{path_to_bundle(project)} --all)) cmd = %W(git --git-dir=#{path_to_repo(project)} bundle create #{path_to_bundle(project)} --all)
output, status = Gitlab::Popen.popen(cmd)
if status.zero? if status.zero?
puts "[DONE]".green $progress.puts "[DONE]".green
else else
puts "[FAILED]".red puts "[FAILED]".red
puts "failed: #{cmd.join(' ')}"
puts output puts output
abort 'Backup failed' abort 'Backup failed'
end end
...@@ -29,15 +31,17 @@ module Backup ...@@ -29,15 +31,17 @@ module Backup
wiki = ProjectWiki.new(project) wiki = ProjectWiki.new(project)
if File.exists?(path_to_repo(wiki)) if File.exists?(path_to_repo(wiki))
print " * #{wiki.path_with_namespace} ... " $progress.print " * #{wiki.path_with_namespace} ... "
if wiki.repository.empty? if wiki.repository.empty?
puts " [SKIPPED]".cyan $progress.puts " [SKIPPED]".cyan
else else
output, status = Gitlab::Popen.popen(%W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all)) cmd = %W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all)
output, status = Gitlab::Popen.popen(cmd)
if status.zero? if status.zero?
puts " [DONE]".green $progress.puts " [DONE]".green
else else
puts " [FAILED]".red puts " [FAILED]".red
puts "failed: #{cmd.join(' ')}"
abort 'Backup failed' abort 'Backup failed'
end end
end end
...@@ -55,7 +59,7 @@ module Backup ...@@ -55,7 +59,7 @@ module Backup
FileUtils.mkdir_p(repos_path) FileUtils.mkdir_p(repos_path)
Project.find_each(batch_size: 1000) do |project| Project.find_each(batch_size: 1000) do |project|
print "#{project.path_with_namespace} ... " $progress.print " * #{project.path_with_namespace} ... "
project.namespace.ensure_dir_exist if project.namespace project.namespace.ensure_dir_exist if project.namespace
...@@ -66,30 +70,41 @@ module Backup ...@@ -66,30 +70,41 @@ module Backup
end end
if system(*cmd, silent) if system(*cmd, silent)
puts "[DONE]".green $progress.puts "[DONE]".green
else else
puts "[FAILED]".red puts "[FAILED]".red
puts "failed: #{cmd.join(' ')}"
abort 'Restore failed' abort 'Restore failed'
end end
wiki = ProjectWiki.new(project) wiki = ProjectWiki.new(project)
if File.exists?(path_to_bundle(wiki)) if File.exists?(path_to_bundle(wiki))
print " * #{wiki.path_with_namespace} ... " $progress.print " * #{wiki.path_with_namespace} ... "
if system(*%W(git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)}), silent)
puts " [DONE]".green # If a wiki bundle exists, first remove the empty repo
# that was initialized with ProjectWiki.new() and then
# try to restore with 'git clone --bare'.
FileUtils.rm_rf(path_to_repo(wiki))
cmd = %W(git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)})
if system(*cmd, silent)
$progress.puts " [DONE]".green
else else
puts " [FAILED]".red puts " [FAILED]".red
puts "failed: #{cmd.join(' ')}"
abort 'Restore failed' abort 'Restore failed'
end end
end end
end end
print 'Put GitLab hooks in repositories dirs'.yellow $progress.print 'Put GitLab hooks in repositories dirs'.yellow
if system("#{Gitlab.config.gitlab_shell.path}/bin/create-hooks") cmd = "#{Gitlab.config.gitlab_shell.path}/bin/create-hooks"
puts " [DONE]".green if system(cmd)
$progress.puts " [DONE]".green
else else
puts " [FAILED]".red puts " [FAILED]".red
puts "failed: #{cmd}"
end end
end end
......
...@@ -80,7 +80,7 @@ module Grack ...@@ -80,7 +80,7 @@ module Grack
case git_cmd case git_cmd
when *Gitlab::GitAccess::DOWNLOAD_COMMANDS when *Gitlab::GitAccess::DOWNLOAD_COMMANDS
if user if user
Gitlab::GitAccess.new.download_allowed?(user, project) Gitlab::GitAccess.new.download_access_check(user, project).allowed?
elsif project.public? elsif project.public?
# Allow clone/fetch for public projects # Allow clone/fetch for public projects
true true
......
module Gitlab
class ForcePushCheck
def self.force_push?(project, oldrev, newrev)
return false if project.empty_repo?
if oldrev != Gitlab::Git::BLANK_SHA && newrev != Gitlab::Git::BLANK_SHA
missed_refs = IO.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})).read
missed_refs.split("\n").size > 0
else
false
end
end
end
end
...@@ -5,61 +5,77 @@ module Gitlab ...@@ -5,61 +5,77 @@ module Gitlab
attr_reader :params, :project, :git_cmd, :user attr_reader :params, :project, :git_cmd, :user
def allowed?(actor, cmd, project, changes = nil) def check(actor, cmd, project, changes = nil)
case cmd case cmd
when *DOWNLOAD_COMMANDS when *DOWNLOAD_COMMANDS
download_access_check(actor, project)
when *PUSH_COMMANDS
if actor.is_a? User if actor.is_a? User
download_allowed?(actor, project) push_access_check(actor, project, changes)
elsif actor.is_a? DeployKey elsif actor.is_a? DeployKey
actor.projects.include?(project) return build_status_object(false, "Deploy key not allowed to push")
elsif actor.is_a? Key elsif actor.is_a? Key
download_allowed?(actor.user, project) push_access_check(actor.user, project, changes)
else else
raise 'Wrong actor' raise 'Wrong actor'
end end
when *PUSH_COMMANDS else
if actor.is_a? User return build_status_object(false, "Wrong command")
push_allowed?(actor, project, changes) end
elsif actor.is_a? DeployKey end
# Deploy key not allowed to push
return false def download_access_check(actor, project)
if actor.is_a?(User)
user_download_access_check(actor, project)
elsif actor.is_a?(DeployKey)
if actor.projects.include?(project)
build_status_object(true)
else
build_status_object(false, "Deploy key not allowed to access this project")
end
elsif actor.is_a? Key elsif actor.is_a? Key
push_allowed?(actor.user, project, changes) user_download_access_check(actor.user, project)
else else
raise 'Wrong actor' raise 'Wrong actor'
end end
end
def user_download_access_check(user, project)
if user && user_allowed?(user) && user.can?(:download_code, project)
build_status_object(true)
else else
false build_status_object(false, "You don't have access")
end end
end end
def download_allowed?(user, project) def push_access_check(user, project, changes)
if user && user_allowed?(user) unless user && user_allowed?(user)
user.can?(:download_code, project) return build_status_object(false, "You don't have access")
else
false
end end
if changes.blank?
return build_status_object(true)
end end
def push_allowed?(user, project, changes) unless project.repository.exists?
return false unless user && user_allowed?(user) return build_status_object(false, "Repository does not exist")
return true if changes.blank? end
changes = changes.lines if changes.kind_of?(String) changes = changes.lines if changes.kind_of?(String)
# Iterate over all changes to find if user allowed all of them to be applied # Iterate over all changes to find if user allowed all of them to be applied
changes.each do |change| changes.each do |change|
unless change_allowed?(user, project, change) status = change_access_check(user, project, change)
unless status.allowed?
# If user does not have access to make at least one change - cancel all push # If user does not have access to make at least one change - cancel all push
return false return status
end end
end end
# If user has access to make all changes return build_status_object(true)
true
end end
def change_allowed?(user, project, change) def change_access_check(user, project, change)
oldrev, newrev, ref = change.split(' ') oldrev, newrev, ref = change.split(' ')
action = if project.protected_branch?(branch_name(ref)) action = if project.protected_branch?(branch_name(ref))
...@@ -72,25 +88,22 @@ module Gitlab ...@@ -72,25 +88,22 @@ module Gitlab
else else
:push_code_to_protected_branches :push_code_to_protected_branches
end end
elsif project.repository && project.repository.tag_names.include?(tag_name(ref)) elsif project.repository.tag_names.include?(tag_name(ref))
# Prevent any changes to existing git tag unless user has permissions # Prevent any changes to existing git tag unless user has permissions
:admin_project :admin_project
else else
:push_code :push_code
end end
user.can?(action, project) if user.can?(action, project)
build_status_object(true)
else
build_status_object(false, "You don't have permission")
end
end end
def forced_push?(project, oldrev, newrev) def forced_push?(project, oldrev, newrev)
return false if project.empty_repo? Gitlab::ForcePushCheck.force_push?(project, oldrev, newrev)
if oldrev != Gitlab::Git::BLANK_SHA && newrev != Gitlab::Git::BLANK_SHA
missed_refs = IO.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})).read
missed_refs.split("\n").size > 0
else
false
end
end end
private private
...@@ -116,5 +129,11 @@ module Gitlab ...@@ -116,5 +129,11 @@ module Gitlab
nil nil
end end
end end
protected
def build_status_object(status, message = '')
GitAccessStatus.new(status, message)
end
end end
end end
module Gitlab
class GitAccessStatus
attr_accessor :status, :message
alias_method :allowed?, :status
def initialize(status, message = '')
@status = status
@message = message
end
def to_json
{status: @status, message: @message}.to_json
end
end
end
\ No newline at end of file
module Gitlab module Gitlab
class GitAccessWiki < GitAccess class GitAccessWiki < GitAccess
def change_allowed?(user, project, change) def change_access_check(user, project, change)
user.can?(:write_wiki, project) if user.can?(:write_wiki, project)
build_status_object(true)
else
build_status_object(false, "You don't have access")
end
end end
end end
end end
...@@ -8,7 +8,7 @@ module Gitlab ...@@ -8,7 +8,7 @@ module Gitlab
attr_reader :adapter, :provider, :user attr_reader :adapter, :provider, :user
def self.open(user, &block) def self.open(user, &block)
Gitlab::LDAP::Adapter.open(user.provider) do |adapter| Gitlab::LDAP::Adapter.open(user.ldap_identity.provider) do |adapter|
block.call(self.new(user, adapter)) block.call(self.new(user, adapter))
end end
end end
...@@ -28,13 +28,13 @@ module Gitlab ...@@ -28,13 +28,13 @@ module Gitlab
def initialize(user, adapter=nil) def initialize(user, adapter=nil)
@adapter = adapter @adapter = adapter
@user = user @user = user
@provider = user.provider @provider = user.ldap_identity.provider
end end
def allowed? def allowed?
if Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter) if Gitlab::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter)
return true unless ldap_config.active_directory return true unless ldap_config.active_directory
!Gitlab::LDAP::Person.disabled_via_active_directory?(user.extern_uid, adapter) !Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter)
else else
false false
end end
......
...@@ -12,9 +12,10 @@ module Gitlab ...@@ -12,9 +12,10 @@ module Gitlab
class << self class << self
def find_by_uid_and_provider(uid, provider) def find_by_uid_and_provider(uid, provider)
# LDAP distinguished name is case-insensitive # LDAP distinguished name is case-insensitive
::User. identity = ::Identity.
where(provider: [provider, :ldap]). where(provider: [provider, :ldap]).
where('lower(extern_uid) = ?', uid.downcase).last where('lower(extern_uid) = ?', uid.downcase).last
identity && identity.user
end end
end end
...@@ -34,15 +35,13 @@ module Gitlab ...@@ -34,15 +35,13 @@ module Gitlab
end end
def find_by_email def find_by_email
model.find_by(email: auth_hash.email) ::User.find_by(email: auth_hash.email)
end end
def update_user_attributes def update_user_attributes
gl_user.attributes = { gl_user.email = auth_hash.email
extern_uid: auth_hash.uid, gl_user.identities.build(provider: auth_hash.provider, extern_uid: auth_hash.uid)
provider: auth_hash.provider, gl_user
email: auth_hash.email
}
end end
def changed? def changed?
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
# #
module Gitlab module Gitlab
module OAuth module OAuth
class ForbiddenAction < StandardError; end
class User class User
attr_accessor :auth_hash, :gl_user attr_accessor :auth_hash, :gl_user
...@@ -70,24 +72,24 @@ module Gitlab ...@@ -70,24 +72,24 @@ module Gitlab
end end
def find_by_uid_and_provider def find_by_uid_and_provider
model.where(provider: auth_hash.provider, extern_uid: auth_hash.uid).last identity = Identity.find_by(provider: auth_hash.provider, extern_uid: auth_hash.uid)
identity && identity.user
end end
def build_new_user def build_new_user
model.new(user_attributes).tap do |user| user = ::User.new(user_attributes)
user.skip_confirmation! user.skip_confirmation!
end user.identities.new(extern_uid: auth_hash.uid, provider: auth_hash.provider)
user
end end
def user_attributes def user_attributes
{ {
extern_uid: auth_hash.uid,
provider: auth_hash.provider,
name: auth_hash.name, name: auth_hash.name,
username: auth_hash.username, username: auth_hash.username,
email: auth_hash.email, email: auth_hash.email,
password: auth_hash.password, password: auth_hash.password,
password_confirmation: auth_hash.password, password_confirmation: auth_hash.password
} }
end end
...@@ -95,12 +97,8 @@ module Gitlab ...@@ -95,12 +97,8 @@ module Gitlab
Gitlab::AppLogger Gitlab::AppLogger
end end
def model def unauthorized_to_create
::User raise ForbiddenAction.new("Unauthorized to create user, signup disabled for #{auth_hash.provider}")
end
def raise_unauthorized_to_create
raise StandardError.new("Unauthorized to create user, signup disabled for #{auth_hash.provider}")
end end
end end
end end
......
module Gitlab
module SidekiqMiddleware
class MemoryKiller
# Default the RSS limit to 0, meaning the MemoryKiller is disabled
MAX_RSS = (ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS'] || 0).to_s.to_i
# Give Sidekiq 15 minutes of grace time after exceeding the RSS limit
GRACE_TIME = (ENV['SIDEKIQ_MEMORY_KILLER_GRACE_TIME'] || 15 * 60).to_s.to_i
# Wait 30 seconds for running jobs to finish during graceful shutdown
SHUTDOWN_WAIT = (ENV['SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT'] || 30).to_s.to_i
# Create a mutex used to ensure there will be only one thread waiting to
# shut Sidekiq down
MUTEX = Mutex.new
def call(worker, job, queue)
yield
current_rss = get_rss
return unless MAX_RSS > 0 && current_rss > MAX_RSS
Thread.new do
# Return if another thread is already waiting to shut Sidekiq down
return unless MUTEX.try_lock
Sidekiq.logger.warn "current RSS #{current_rss} exceeds maximum RSS "\
"#{MAX_RSS}"
Sidekiq.logger.warn "spawned thread that will shut down PID "\
"#{Process.pid} in #{GRACE_TIME} seconds"
sleep(GRACE_TIME)
Sidekiq.logger.warn "sending SIGUSR1 to PID #{Process.pid}"
Process.kill('SIGUSR1', Process.pid)
Sidekiq.logger.warn "waiting #{SHUTDOWN_WAIT} seconds before sending "\
"SIGTERM to PID #{Process.pid}"
sleep(SHUTDOWN_WAIT)
Sidekiq.logger.warn "sending SIGTERM to PID #{Process.pid}"
Process.kill('SIGTERM', Process.pid)
end
end
private
def get_rss
output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{Process.pid}))
return 0 unless status.zero?
output.to_i
end
end
end
end
...@@ -6,6 +6,7 @@ namespace :gitlab do ...@@ -6,6 +6,7 @@ namespace :gitlab do
desc "GITLAB | Create a backup of the GitLab system" desc "GITLAB | Create a backup of the GitLab system"
task create: :environment do task create: :environment do
warn_user_is_not_gitlab warn_user_is_not_gitlab
configure_cron_mode
Rake::Task["gitlab:backup:db:create"].invoke Rake::Task["gitlab:backup:db:create"].invoke
Rake::Task["gitlab:backup:repo:create"].invoke Rake::Task["gitlab:backup:repo:create"].invoke
...@@ -21,6 +22,7 @@ namespace :gitlab do ...@@ -21,6 +22,7 @@ namespace :gitlab do
desc "GITLAB | Restore a previously created backup" desc "GITLAB | Restore a previously created backup"
task restore: :environment do task restore: :environment do
warn_user_is_not_gitlab warn_user_is_not_gitlab
configure_cron_mode
backup = Backup::Manager.new backup = Backup::Manager.new
backup.unpack backup.unpack
...@@ -35,43 +37,54 @@ namespace :gitlab do ...@@ -35,43 +37,54 @@ namespace :gitlab do
namespace :repo do namespace :repo do
task create: :environment do task create: :environment do
puts "Dumping repositories ...".blue $progress.puts "Dumping repositories ...".blue
Backup::Repository.new.dump Backup::Repository.new.dump
puts "done".green $progress.puts "done".green
end end
task restore: :environment do task restore: :environment do
puts "Restoring repositories ...".blue $progress.puts "Restoring repositories ...".blue
Backup::Repository.new.restore Backup::Repository.new.restore
puts "done".green $progress.puts "done".green
end end
end end
namespace :db do namespace :db do
task create: :environment do task create: :environment do
puts "Dumping database ... ".blue $progress.puts "Dumping database ... ".blue
Backup::Database.new.dump Backup::Database.new.dump
puts "done".green $progress.puts "done".green
end end
task restore: :environment do task restore: :environment do
puts "Restoring database ... ".blue $progress.puts "Restoring database ... ".blue
Backup::Database.new.restore Backup::Database.new.restore
puts "done".green $progress.puts "done".green
end end
end end
namespace :uploads do namespace :uploads do
task create: :environment do task create: :environment do
puts "Dumping uploads ... ".blue $progress.puts "Dumping uploads ... ".blue
Backup::Uploads.new.dump Backup::Uploads.new.dump
puts "done".green $progress.puts "done".green
end end
task restore: :environment do task restore: :environment do
puts "Restoring uploads ... ".blue $progress.puts "Restoring uploads ... ".blue
Backup::Uploads.new.restore Backup::Uploads.new.restore
puts "done".green $progress.puts "done".green
end
end
def configure_cron_mode
if ENV['CRON']
# We need an object we can say 'puts' and 'print' to; let's use a
# StringIO.
require 'stringio'
$progress = StringIO.new
else
$progress = $stdout
end end
end end
end # namespace end: backup end # namespace end: backup
......
...@@ -790,14 +790,14 @@ namespace :gitlab do ...@@ -790,14 +790,14 @@ namespace :gitlab do
end end
def sanitized_message(project) def sanitized_message(project)
if sanitize if should_sanitize?
"#{project.namespace_id.to_s.yellow}/#{project.id.to_s.yellow} ... " "#{project.namespace_id.to_s.yellow}/#{project.id.to_s.yellow} ... "
else else
"#{project.name_with_namespace.yellow} ... " "#{project.name_with_namespace.yellow} ... "
end end
end end
def sanitize def should_sanitize?
if ENV['SANITIZE'] == "true" if ENV['SANITIZE'] == "true"
true true
else else
......
require "#{Rails.root}/app/helpers/emails_helper"
require 'action_view/helpers'
extend ActionView::Helpers
include ActionView::Context
include EmailsHelper
namespace :gitlab do
desc "Email google whitelisting email with example email for actions in inbox"
task mail_google_schema_whitelisting: :environment do
subject = "Rails | Implemented feature"
url = "#{Gitlab.config.gitlab.url}/base/rails-project/issues/#{rand(1..100)}#note_#{rand(10..1000)}"
schema = email_action(url)
body = email_template(schema, url)
mail = Notify.test_email("schema.whitelisting+sample@gmail.com", subject, body.html_safe)
if send_now
mail.deliver
else
puts "WOULD SEND:"
end
puts mail
end
def email_template(schema, url)
"<html lang='en'>
<head>
<meta content='text/html; charset=utf-8' http-equiv='Content-Type'>
<title>
GitLab
</title>
</meta>
</head>
<style>
img {
max-width: 100%;
height: auto;
}
p.details {
font-style:italic;
color:#777
}
.footer p {
font-size:small;
color:#777
}
</style>
<body>
<div class='content'>
<div>
<p>I like it :+1: </p>
</div>
</div>
<div class='footer' style='margin-top: 10px;'>
<p>
<br>
You're receiving this notification because you are a member of the Base / Rails Project project team.
<a href=\"#{url}\">View it on GitLab</a>
#{schema}
</p>
</div>
</body>
</html>"
end
def send_now
if ENV['SEND'] == "true"
true
else
false
end
end
end
...@@ -17,15 +17,19 @@ namespace :gitlab do ...@@ -17,15 +17,19 @@ namespace :gitlab do
# Clone if needed # Clone if needed
unless File.directory?(target_dir) unless File.directory?(target_dir)
sh(*%W(git clone #{args.repo} #{target_dir})) system(*%W(git clone -- #{args.repo} #{target_dir}))
end end
# Make sure we're on the right tag # Make sure we're on the right tag
Dir.chdir(target_dir) do Dir.chdir(target_dir) do
# First try to checkout without fetching # First try to checkout without fetching
# to avoid stalling tests if the Internet is down. # to avoid stalling tests if the Internet is down.
reset = "git reset --hard $(git describe #{args.tag} || git describe origin/#{args.tag})" reseted = reset_to_commit(args)
sh "#{reset} || git fetch origin && #{reset}"
unless reseted
system(*%W(git fetch origin))
reset_to_commit(args)
end
config = { config = {
user: user, user: user,
...@@ -54,7 +58,7 @@ namespace :gitlab do ...@@ -54,7 +58,7 @@ namespace :gitlab do
File.open("config.yml", "w+") {|f| f.puts config.to_yaml} File.open("config.yml", "w+") {|f| f.puts config.to_yaml}
# Launch installation process # Launch installation process
sh "bin/install" system(*%W(bin/install))
end end
# Required for debian packaging with PKGR: Setup .ssh/environment with # Required for debian packaging with PKGR: Setup .ssh/environment with
...@@ -118,5 +122,16 @@ namespace :gitlab do ...@@ -118,5 +122,16 @@ namespace :gitlab do
puts "Quitting...".red puts "Quitting...".red
exit 1 exit 1
end end
def reset_to_commit(args)
tag, status = Gitlab::Popen.popen(%W(git describe -- #{args.tag}))
unless status.zero?
tag, status = Gitlab::Popen.popen(%W(git describe -- origin/#{args.tag}))
end
tag = tag.strip
system(*%W(git reset --hard #{tag}))
end
end end
...@@ -5,10 +5,14 @@ FactoryGirl.define do ...@@ -5,10 +5,14 @@ FactoryGirl.define do
Faker::Lorem.sentence Faker::Lorem.sentence
end end
sequence :name, aliases: [:file_name] do sequence :name do
Faker::Name.name Faker::Name.name
end end
sequence :file_name do
Faker::Internet.user_name
end
sequence(:url) { Faker::Internet.uri('http') } sequence(:url) { Faker::Internet.uri('http') }
factory :user, aliases: [:author, :assignee, :owner, :creator] do factory :user, aliases: [:author, :assignee, :owner, :creator] do
...@@ -24,9 +28,18 @@ FactoryGirl.define do ...@@ -24,9 +28,18 @@ FactoryGirl.define do
admin true admin true
end end
trait :ldap do factory :omniauth_user do
ignore do
extern_uid '123456'
provider 'ldapmain' provider 'ldapmain'
extern_uid 'my-ldap-id' end
after(:create) do |user, evaluator|
user.identities << create(:identity,
provider: evaluator.provider,
extern_uid: evaluator.extern_uid
)
end
end end
factory :admin, traits: [:admin] factory :admin, traits: [:admin]
...@@ -182,4 +195,9 @@ FactoryGirl.define do ...@@ -182,4 +195,9 @@ FactoryGirl.define do
deploy_key deploy_key
project project
end end
factory :identity do
provider 'ldapmain'
extern_uid 'my-ldap-id'
end
end end
...@@ -27,6 +27,10 @@ ...@@ -27,6 +27,10 @@
# #
FactoryGirl.define do FactoryGirl.define do
# Project without repository
#
# Project does not have bare repository.
# Use this factory if you dont need repository in tests
factory :empty_project, class: 'Project' do factory :empty_project, class: 'Project' do
sequence(:name) { |n| "project#{n}" } sequence(:name) { |n| "project#{n}" }
path { name.downcase.gsub(/\s/, '_') } path { name.downcase.gsub(/\s/, '_') }
...@@ -47,6 +51,20 @@ FactoryGirl.define do ...@@ -47,6 +51,20 @@ FactoryGirl.define do
end end
end end
# Project with empty repository
#
# This is a case when you just created a project
# but not pushed any code there yet
factory :project_empty_repo, parent: :empty_project do
after :create do |project|
project.create_repository
end
end
# Project with test repository
#
# Test repository source can be found at
# https://gitlab.com/gitlab-org/gitlab-test
factory :project, parent: :empty_project do factory :project, parent: :empty_project do
path { 'gitlabhq' } path { 'gitlabhq' }
......
require 'spec_helper'
describe "User Feed", feature: true do
describe "GET /" do
let!(:user) { create(:user) }
context "user atom feed via private token" do
it "should render user atom feed" do
visit user_path(user, :atom, private_token: user.private_token)
body.should have_selector("feed title")
end
end
context 'feed content' do
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project, author: user, description: '') }
let(:note) { create(:note, noteable: issue, author: user, note: 'Bug confirmed', project: project) }
before do
project.team << [user, :master]
issue_event(issue, user)
note_event(note, user)
visit user_path(user, :atom, private_token: user.private_token)
end
it "should have issue opened event" do
body.should have_content("#{user.name} opened issue ##{issue.iid}")
end
it "should have issue comment event" do
body.should have_content("#{user.name} commented on issue ##{issue.iid}")
end
end
end
def issue_event(issue, user)
EventCreateService.new.open_issue(issue, user)
end
def note_event(note, user)
EventCreateService.new.leave_note(note, user)
end
end
...@@ -19,8 +19,9 @@ describe 'Comments' do ...@@ -19,8 +19,9 @@ describe 'Comments' do
it 'should be valid' do it 'should be valid' do
should have_css(".js-main-target-form", visible: true, count: 1) should have_css(".js-main-target-form", visible: true, count: 1)
find(".js-main-target-form input[type=submit]").value.should == "Add Comment" find(".js-main-target-form input[type=submit]").value.should == "Add Comment"
within(".js-main-target-form") { should_not have_link("Cancel") } within('.js-main-target-form') do
within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: false) } expect(page).not_to have_link('Cancel')
end
end end
describe "with text" do describe "with text" do
...@@ -31,8 +32,10 @@ describe 'Comments' do ...@@ -31,8 +32,10 @@ describe 'Comments' do
end end
it 'should have enable submit button and preview button' do it 'should have enable submit button and preview button' do
within(".js-main-target-form") { should_not have_css(".js-comment-button[disabled]") } within('.js-main-target-form') do
within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: true) } expect(page).not_to have_css('.js-comment-button[disabled]')
expect(page).to have_css('.js-md-preview-button', visible: true)
end
end end
end end
end end
...@@ -41,15 +44,17 @@ describe 'Comments' do ...@@ -41,15 +44,17 @@ describe 'Comments' do
before do before do
within(".js-main-target-form") do within(".js-main-target-form") do
fill_in "note[note]", with: "This is awsome!" fill_in "note[note]", with: "This is awsome!"
find(".js-note-preview-button").trigger("click") find('.js-md-preview-button').click
click_button "Add Comment" click_button "Add Comment"
end end
end end
it 'should be added and form reset' do it 'should be added and form reset' do
should have_content("This is awsome!") should have_content("This is awsome!")
within(".js-main-target-form") { should have_no_field("note[note]", with: "This is awesome!") } within('.js-main-target-form') do
within(".js-main-target-form") { should have_css(".js-note-preview", visible: false) } expect(page).to have_no_field('note[note]', with: 'This is awesome!')
expect(page).to have_css('.js-md-preview', visible: :hidden)
end
within(".js-main-target-form") { should have_css(".js-note-text", visible: true) } within(".js-main-target-form") { should have_css(".js-note-text", visible: true) }
end end
end end
...@@ -172,11 +177,11 @@ describe 'Comments' do ...@@ -172,11 +177,11 @@ describe 'Comments' do
# add two separate texts and trigger previews on both # add two separate texts and trigger previews on both
within("tr[id='#{line_code}'] + .js-temp-notes-holder") do within("tr[id='#{line_code}'] + .js-temp-notes-holder") do
fill_in "note[note]", with: "One comment on line 7" fill_in "note[note]", with: "One comment on line 7"
find(".js-note-preview-button").trigger("click") find('.js-md-preview-button').click
end end
within("tr[id='#{line_code_2}'] + .js-temp-notes-holder") do within("tr[id='#{line_code_2}'] + .js-temp-notes-holder") do
fill_in "note[note]", with: "Another comment on line 10" fill_in "note[note]", with: "Another comment on line 10"
find(".js-note-preview-button").trigger("click") find('.js-md-preview-button').click
end end
end end
end end
......
...@@ -5,9 +5,10 @@ describe IssuesFinder do ...@@ -5,9 +5,10 @@ describe IssuesFinder do
let(:user2) { create :user } let(:user2) { create :user }
let(:project1) { create(:project) } let(:project1) { create(:project) }
let(:project2) { create(:project) } let(:project2) { create(:project) }
let(:issue1) { create(:issue, assignee: user, project: project1) } let(:milestone) { create(:milestone, project: project1) }
let(:issue2) { create(:issue, assignee: user, project: project2) } let(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone) }
let(:issue3) { create(:issue, assignee: user2, project: project2) } let(:issue2) { create(:issue, author: user, assignee: user, project: project2) }
let(:issue3) { create(:issue, author: user2, assignee: user2, project: project2) }
before do before do
project1.team << [user, :master] project1.team << [user, :master]
...@@ -22,22 +23,29 @@ describe IssuesFinder do ...@@ -22,22 +23,29 @@ describe IssuesFinder do
issue3 issue3
end end
context 'scope: all' do
it 'should filter by all' do it 'should filter by all' do
params = { scope: "all", state: 'opened' } params = { scope: "all", state: 'opened' }
issues = IssuesFinder.new.execute(user, params) issues = IssuesFinder.new.execute(user, params)
issues.size.should == 3 issues.size.should == 3
end end
it 'should filter by assignee' do it 'should filter by assignee id' do
params = { scope: "assigned-to-me", state: 'opened' } params = { scope: "all", assignee_id: user.id, state: 'opened' }
issues = IssuesFinder.new.execute(user, params) issues = IssuesFinder.new.execute(user, params)
issues.size.should == 2 issues.size.should == 2
end end
it 'should filter by project' do it 'should filter by author id' do
params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id } params = { scope: "all", author_id: user2.id, state: 'opened' }
issues = IssuesFinder.new.execute(user, params) issues = IssuesFinder.new.execute(user, params)
issues.size.should == 1 issues.should == [issue3]
end
it 'should filter by milestone id' do
params = { scope: "all", milestone_id: milestone.id, state: 'opened' }
issues = IssuesFinder.new.execute(user, params)
issues.should == [issue1]
end end
it 'should be empty for unauthorized user' do it 'should be empty for unauthorized user' do
...@@ -55,4 +63,19 @@ describe IssuesFinder do ...@@ -55,4 +63,19 @@ describe IssuesFinder do
issues.should include(issue3) issues.should include(issue3)
end end
end end
context 'personal scope' do
it 'should filter by assignee' do
params = { scope: "assigned-to-me", state: 'opened' }
issues = IssuesFinder.new.execute(user, params)
issues.size.should == 2
end
it 'should filter by project' do
params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id }
issues = IssuesFinder.new.execute(user, params)
issues.size.should == 1
end
end
end
end end
...@@ -66,6 +66,16 @@ describe ApplicationHelper do ...@@ -66,6 +66,16 @@ describe ApplicationHelper do
avatar_icon(user.email).to_s.should match("/uploads/user/avatar/#{ user.id }/gitlab_logo.png") avatar_icon(user.email).to_s.should match("/uploads/user/avatar/#{ user.id }/gitlab_logo.png")
end end
it "should return an url for the avatar with relative url" do
Gitlab.config.gitlab.stub(relative_url_root: "/gitlab")
Gitlab.config.gitlab.stub(url: Settings.send(:build_gitlab_url))
user = create(:user)
user.avatar = File.open(avatar_file_path)
user.save!
avatar_icon(user.email).to_s.should match("/gitlab//uploads/user/avatar/#{ user.id }/gitlab_logo.png")
end
it "should call gravatar_icon when no avatar is present" do it "should call gravatar_icon when no avatar is present" do
user = create(:user, email: 'test@example.com') user = create(:user, email: 'test@example.com')
user.save! user.save!
......
require "spec_helper"
describe OauthHelper do
describe "additional_providers" do
it 'returns all enabled providers' do
allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :github] }
helper.additional_providers.should include(*[:twitter, :github])
end
it 'does not return ldap provider' do
allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :ldapmain] }
helper.additional_providers.should include(:twitter)
end
it 'returns empty array' do
allow(helper).to receive(:enabled_oauth_providers) { [] }
helper.additional_providers.should == []
end
end
end
\ No newline at end of file
...@@ -5,14 +5,14 @@ describe Gitlab::GitAccess do ...@@ -5,14 +5,14 @@ describe Gitlab::GitAccess do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:user) { create(:user) } let(:user) { create(:user) }
describe 'download_allowed?' do describe 'download_access_check' do
describe 'master permissions' do describe 'master permissions' do
before { project.team << [user, :master] } before { project.team << [user, :master] }
context 'pull code' do context 'pull code' do
subject { access.download_allowed?(user, project) } subject { access.download_access_check(user, project) }
it { should be_true } it { subject.allowed?.should be_true }
end end
end end
...@@ -20,9 +20,9 @@ describe Gitlab::GitAccess do ...@@ -20,9 +20,9 @@ describe Gitlab::GitAccess do
before { project.team << [user, :guest] } before { project.team << [user, :guest] }
context 'pull code' do context 'pull code' do
subject { access.download_allowed?(user, project) } subject { access.download_access_check(user, project) }
it { should be_false } it { subject.allowed?.should be_false }
end end
end end
...@@ -33,22 +33,41 @@ describe Gitlab::GitAccess do ...@@ -33,22 +33,41 @@ describe Gitlab::GitAccess do
end end
context 'pull code' do context 'pull code' do
subject { access.download_allowed?(user, project) } subject { access.download_access_check(user, project) }
it { should be_false } it { subject.allowed?.should be_false }
end end
end end
describe 'without acccess to project' do describe 'without acccess to project' do
context 'pull code' do context 'pull code' do
subject { access.download_allowed?(user, project) } subject { access.download_access_check(user, project) }
it { should be_false } it { subject.allowed?.should be_false }
end
end
describe 'deploy key permissions' do
let(:key) { create(:deploy_key) }
context 'pull code' do
context 'allowed' do
before { key.projects << project }
subject { access.download_access_check(key, project) }
it { subject.allowed?.should be_true }
end
context 'denied' do
subject { access.download_access_check(key, project) }
it { subject.allowed?.should be_false }
end
end end
end end
end end
describe 'push_allowed?' do describe 'push_access_check' do
def protect_feature_branch def protect_feature_branch
create(:protected_branch, name: 'feature', project: project) create(:protected_branch, name: 'feature', project: project)
end end
...@@ -117,9 +136,9 @@ describe Gitlab::GitAccess do ...@@ -117,9 +136,9 @@ describe Gitlab::GitAccess do
permissions_matrix[role].each do |action, allowed| permissions_matrix[role].each do |action, allowed|
context action do context action do
subject { access.push_allowed?(user, project, changes[action]) } subject { access.push_access_check(user, project, changes[action]) }
it { should allowed ? be_true : be_false } it { subject.allowed?.should allowed ? be_true : be_false }
end end
end end
end end
......
...@@ -11,9 +11,9 @@ describe Gitlab::GitAccessWiki do ...@@ -11,9 +11,9 @@ describe Gitlab::GitAccessWiki do
project.team << [user, :developer] project.team << [user, :developer]
end end
subject { access.push_allowed?(user, project, changes) } subject { access.push_access_check(user, project, changes) }
it { should be_true } it { subject.allowed?.should be_true }
end end
def changes def changes
......
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::LDAP::Access do describe Gitlab::LDAP::Access do
let(:access) { Gitlab::LDAP::Access.new user } let(:access) { Gitlab::LDAP::Access.new user }
let(:user) { create(:user, :ldap) } let(:user) { create(:omniauth_user) }
describe :allowed? do describe :allowed? do
subject { access.allowed? } subject { access.allowed? }
......
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::LDAP::Authentication do describe Gitlab::LDAP::Authentication do
let(:klass) { Gitlab::LDAP::Authentication } let(:klass) { Gitlab::LDAP::Authentication }
let(:user) { create(:user, :ldap, extern_uid: dn) } let(:user) { create(:omniauth_user, extern_uid: dn) }
let(:dn) { 'uid=john,ou=people,dc=example,dc=com' } let(:dn) { 'uid=john,ou=people,dc=example,dc=com' }
let(:login) { 'john' } let(:login) { 'john' }
let(:password) { 'password' } let(:password) { 'password' }
......
...@@ -15,18 +15,18 @@ describe Gitlab::LDAP::User do ...@@ -15,18 +15,18 @@ describe Gitlab::LDAP::User do
describe :find_or_create do describe :find_or_create do
it "finds the user if already existing" do it "finds the user if already existing" do
existing_user = create(:user, extern_uid: 'my-uid', provider: 'ldapmain') existing_user = create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain')
expect{ gl_user.save }.to_not change{ User.count } expect{ gl_user.save }.to_not change{ User.count }
end end
it "connects to existing non-ldap user if the email matches" do it "connects to existing non-ldap user if the email matches" do
existing_user = create(:user, email: 'john@example.com') existing_user = create(:omniauth_user, email: 'john@example.com', provider: "twitter")
expect{ gl_user.save }.to_not change{ User.count } expect{ gl_user.save }.to_not change{ User.count }
existing_user.reload existing_user.reload
expect(existing_user.extern_uid).to eql 'my-uid' expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid'
expect(existing_user.provider).to eql 'ldapmain' expect(existing_user.ldap_identity.provider).to eql 'ldapmain'
end end
it "creates a new user if not found" do it "creates a new user if not found" do
......
...@@ -15,7 +15,7 @@ describe Gitlab::OAuth::User do ...@@ -15,7 +15,7 @@ describe Gitlab::OAuth::User do
end end
describe :persisted? do describe :persisted? do
let!(:existing_user) { create(:user, extern_uid: 'my-uid', provider: 'my-provider') } let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
it "finds an existing user based on uid and provider (facebook)" do it "finds an existing user based on uid and provider (facebook)" do
auth = double(info: double(name: 'John'), uid: 'my-uid', provider: 'my-provider') auth = double(info: double(name: 'John'), uid: 'my-uid', provider: 'my-provider')
...@@ -39,8 +39,9 @@ describe Gitlab::OAuth::User do ...@@ -39,8 +39,9 @@ describe Gitlab::OAuth::User do
oauth_user.save oauth_user.save
expect(gl_user).to be_valid expect(gl_user).to be_valid
expect(gl_user.extern_uid).to eql uid identity = gl_user.identities.first
expect(gl_user.provider).to eql 'twitter' expect(identity.extern_uid).to eql uid
expect(identity.provider).to eql 'twitter'
end end
end end
......
...@@ -46,7 +46,7 @@ describe Notify do ...@@ -46,7 +46,7 @@ describe Notify do
token = 'kETLwRaayvigPq_x3SNM' token = 'kETLwRaayvigPq_x3SNM'
subject { Notify.new_user_email(new_user.id, new_user.password, token) } subject { Notify.new_user_email(new_user.id, token) }
it_behaves_like 'an email sent from GitLab' it_behaves_like 'an email sent from GitLab'
...@@ -83,7 +83,7 @@ describe Notify do ...@@ -83,7 +83,7 @@ describe Notify do
let(:example_site_path) { root_path } let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: 'newguy@example.com', password: "securePassword") } let(:new_user) { create(:user, email: 'newguy@example.com', password: "securePassword") }
subject { Notify.new_user_email(new_user.id, new_user.password) } subject { Notify.new_user_email(new_user.id) }
it_behaves_like 'an email sent from GitLab' it_behaves_like 'an email sent from GitLab'
......
...@@ -62,6 +62,7 @@ describe User do ...@@ -62,6 +62,7 @@ describe User do
it { should have_many(:assigned_issues).dependent(:destroy) } it { should have_many(:assigned_issues).dependent(:destroy) }
it { should have_many(:merge_requests).dependent(:destroy) } it { should have_many(:merge_requests).dependent(:destroy) }
it { should have_many(:assigned_merge_requests).dependent(:destroy) } it { should have_many(:assigned_merge_requests).dependent(:destroy) }
it { should have_many(:identities).dependent(:destroy) }
end end
describe "Mass assignment" do describe "Mass assignment" do
...@@ -361,24 +362,29 @@ describe User do ...@@ -361,24 +362,29 @@ describe User do
end end
describe :ldap_user? do describe :ldap_user? do
let(:user) { build(:user, :ldap) }
it "is true if provider name starts with ldap" do it "is true if provider name starts with ldap" do
user.provider = 'ldapmain' user = create(:omniauth_user, provider: 'ldapmain')
expect( user.ldap_user? ).to be_true expect( user.ldap_user? ).to be_true
end end
it "is false for other providers" do it "is false for other providers" do
user.provider = 'other-provider' user = create(:omniauth_user, provider: 'other-provider')
expect( user.ldap_user? ).to be_false expect( user.ldap_user? ).to be_false
end end
it "is false if no extern_uid is provided" do it "is false if no extern_uid is provided" do
user.extern_uid = nil user = create(:omniauth_user, extern_uid: nil)
expect( user.ldap_user? ).to be_false expect( user.ldap_user? ).to be_false
end end
end end
describe :ldap_identity do
it "returns ldap identity" do
user = create :omniauth_user
user.ldap_identity.provider.should_not be_empty
end
end
describe '#full_website_url' do describe '#full_website_url' do
let(:user) { create(:user) } let(:user) { create(:user) }
......
...@@ -26,7 +26,7 @@ describe API::API, api: true do ...@@ -26,7 +26,7 @@ describe API::API, api: true do
end end
end end
describe "GET /internal/allowed" do describe "POST /internal/allowed" do
context "access granted" do context "access granted" do
before do before do
project.team << [user, :developer] project.team << [user, :developer]
...@@ -37,7 +37,7 @@ describe API::API, api: true do ...@@ -37,7 +37,7 @@ describe API::API, api: true do
pull(key, project) pull(key, project)
response.status.should == 200 response.status.should == 200
response.body.should == 'true' JSON.parse(response.body)["status"].should be_true
end end
end end
...@@ -46,7 +46,7 @@ describe API::API, api: true do ...@@ -46,7 +46,7 @@ describe API::API, api: true do
push(key, project) push(key, project)
response.status.should == 200 response.status.should == 200
response.body.should == 'true' JSON.parse(response.body)["status"].should be_true
end end
end end
end end
...@@ -61,7 +61,7 @@ describe API::API, api: true do ...@@ -61,7 +61,7 @@ describe API::API, api: true do
pull(key, project) pull(key, project)
response.status.should == 200 response.status.should == 200
response.body.should == 'false' JSON.parse(response.body)["status"].should be_false
end end
end end
...@@ -70,7 +70,7 @@ describe API::API, api: true do ...@@ -70,7 +70,7 @@ describe API::API, api: true do
push(key, project) push(key, project)
response.status.should == 200 response.status.should == 200
response.body.should == 'false' JSON.parse(response.body)["status"].should be_false
end end
end end
end end
...@@ -87,7 +87,7 @@ describe API::API, api: true do ...@@ -87,7 +87,7 @@ describe API::API, api: true do
pull(key, personal_project) pull(key, personal_project)
response.status.should == 200 response.status.should == 200
response.body.should == 'false' JSON.parse(response.body)["status"].should be_false
end end
end end
...@@ -96,7 +96,7 @@ describe API::API, api: true do ...@@ -96,7 +96,7 @@ describe API::API, api: true do
push(key, personal_project) push(key, personal_project)
response.status.should == 200 response.status.should == 200
response.body.should == 'false' JSON.parse(response.body)["status"].should be_false
end end
end end
end end
...@@ -114,7 +114,7 @@ describe API::API, api: true do ...@@ -114,7 +114,7 @@ describe API::API, api: true do
pull(key, project) pull(key, project)
response.status.should == 200 response.status.should == 200
response.body.should == 'true' JSON.parse(response.body)["status"].should be_true
end end
end end
...@@ -123,7 +123,7 @@ describe API::API, api: true do ...@@ -123,7 +123,7 @@ describe API::API, api: true do
push(key, project) push(key, project)
response.status.should == 200 response.status.should == 200
response.body.should == 'false' JSON.parse(response.body)["status"].should be_false
end end
end end
end end
...@@ -140,7 +140,7 @@ describe API::API, api: true do ...@@ -140,7 +140,7 @@ describe API::API, api: true do
archive(key, project) archive(key, project)
response.status.should == 200 response.status.should == 200
response.body.should == 'true' JSON.parse(response.body)["status"].should be_true
end end
end end
...@@ -149,10 +149,28 @@ describe API::API, api: true do ...@@ -149,10 +149,28 @@ describe API::API, api: true do
archive(key, project) archive(key, project)
response.status.should == 200 response.status.should == 200
response.body.should == 'false' JSON.parse(response.body)["status"].should be_false
end end
end end
end end
context 'project does not exist' do
it do
pull(key, OpenStruct.new(path_with_namespace: 'gitlab/notexists'))
response.status.should == 200
JSON.parse(response.body)["status"].should be_false
end
end
context 'user does not exist' do
it do
pull(OpenStruct.new(id: 0), project)
response.status.should == 200
JSON.parse(response.body)["status"].should be_false
end
end
end end
def pull(key, project) def pull(key, project)
......
...@@ -33,7 +33,7 @@ describe API::API, api: true do ...@@ -33,7 +33,7 @@ describe API::API, api: true do
response.status.should == 200 response.status.should == 200
json_response.should be_an Array json_response.should be_an Array
json_response.first.keys.should include 'email' json_response.first.keys.should include 'email'
json_response.first.keys.should include 'extern_uid' json_response.first.keys.should include 'identities'
json_response.first.keys.should include 'can_create_project' json_response.first.keys.should include 'can_create_project'
end end
end end
......
...@@ -55,12 +55,12 @@ end ...@@ -55,12 +55,12 @@ end
# projects POST /projects(.:format) projects#create # projects POST /projects(.:format) projects#create
# new_project GET /projects/new(.:format) projects#new # new_project GET /projects/new(.:format) projects#new
# fork_project POST /:id/fork(.:format) projects#fork
# files_project GET /:id/files(.:format) projects#files # files_project GET /:id/files(.:format) projects#files
# edit_project GET /:id/edit(.:format) projects#edit # edit_project GET /:id/edit(.:format) projects#edit
# project GET /:id(.:format) projects#show # project GET /:id(.:format) projects#show
# PUT /:id(.:format) projects#update # PUT /:id(.:format) projects#update
# DELETE /:id(.:format) projects#destroy # DELETE /:id(.:format) projects#destroy
# markdown_preview_project GET /:id/markdown_preview(.:format) projects#markdown_preview
describe ProjectsController, "routing" do describe ProjectsController, "routing" do
it "to #create" do it "to #create" do
post("/projects").should route_to('projects#create') post("/projects").should route_to('projects#create')
...@@ -70,10 +70,6 @@ describe ProjectsController, "routing" do ...@@ -70,10 +70,6 @@ describe ProjectsController, "routing" do
get("/projects/new").should route_to('projects#new') get("/projects/new").should route_to('projects#new')
end end
it "to #fork" do
post("/gitlab/gitlabhq/fork").should route_to('projects#fork', id: 'gitlab/gitlabhq')
end
it "to #edit" do it "to #edit" do
get("/gitlab/gitlabhq/edit").should route_to('projects#edit', id: 'gitlab/gitlabhq') get("/gitlab/gitlabhq/edit").should route_to('projects#edit', id: 'gitlab/gitlabhq')
end end
...@@ -93,6 +89,12 @@ describe ProjectsController, "routing" do ...@@ -93,6 +89,12 @@ describe ProjectsController, "routing" do
it "to #destroy" do it "to #destroy" do
delete("/gitlab/gitlabhq").should route_to('projects#destroy', id: 'gitlab/gitlabhq') delete("/gitlab/gitlabhq").should route_to('projects#destroy', id: 'gitlab/gitlabhq')
end end
it 'to #markdown_preview' do
get('/gitlab/gitlabhq/markdown_preview').should(
route_to('projects#markdown_preview', id: 'gitlab/gitlabhq')
)
end
end end
# pages_project_wikis GET /:project_id/wikis/pages(.:format) projects/wikis#pages # pages_project_wikis GET /:project_id/wikis/pages(.:format) projects/wikis#pages
...@@ -392,15 +394,10 @@ describe Projects::IssuesController, "routing" do ...@@ -392,15 +394,10 @@ describe Projects::IssuesController, "routing" do
end end
end end
# preview_project_notes POST /:project_id/notes/preview(.:format) notes#preview
# project_notes GET /:project_id/notes(.:format) notes#index # project_notes GET /:project_id/notes(.:format) notes#index
# POST /:project_id/notes(.:format) notes#create # POST /:project_id/notes(.:format) notes#create
# project_note DELETE /:project_id/notes/:id(.:format) notes#destroy # project_note DELETE /:project_id/notes/:id(.:format) notes#destroy
describe Projects::NotesController, "routing" do describe Projects::NotesController, "routing" do
it "to #preview" do
post("/gitlab/gitlabhq/notes/preview").should route_to('projects/notes#preview', project_id: 'gitlab/gitlabhq')
end
it_behaves_like "RESTful project resources" do it_behaves_like "RESTful project resources" do
let(:actions) { [:index, :create, :destroy] } let(:actions) { [:index, :create, :destroy] }
let(:controller) { 'notes' } let(:controller) { 'notes' }
...@@ -420,6 +417,7 @@ describe Projects::BlobController, "routing" do ...@@ -420,6 +417,7 @@ describe Projects::BlobController, "routing" do
it "to #show" do it "to #show" do
get("/gitlab/gitlabhq/blob/master/app/models/project.rb").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb') get("/gitlab/gitlabhq/blob/master/app/models/project.rb").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb')
get("/gitlab/gitlabhq/blob/master/app/models/compare.rb").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/compare.rb') get("/gitlab/gitlabhq/blob/master/app/models/compare.rb").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/compare.rb')
get("/gitlab/gitlabhq/blob/master/app/models/diff.js").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/diff.js')
get("/gitlab/gitlabhq/blob/master/files.scss").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss') get("/gitlab/gitlabhq/blob/master/files.scss").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss')
end end
end end
...@@ -462,3 +460,13 @@ describe Projects::GraphsController, "routing" do ...@@ -462,3 +460,13 @@ describe Projects::GraphsController, "routing" do
get("/gitlab/gitlabhq/graphs/master").should route_to('projects/graphs#show', project_id: 'gitlab/gitlabhq', id: 'master') get("/gitlab/gitlabhq/graphs/master").should route_to('projects/graphs#show', project_id: 'gitlab/gitlabhq', id: 'master')
end end
end end
describe Projects::ForksController, "routing" do
it "to #new" do
get("/gitlab/gitlabhq/fork/new").should route_to("projects/forks#new", project_id: 'gitlab/gitlabhq')
end
it "to #create" do
post("/gitlab/gitlabhq/fork").should route_to("projects/forks#create", project_id: 'gitlab/gitlabhq')
end
end
...@@ -42,10 +42,54 @@ describe Projects::ForkService do ...@@ -42,10 +42,54 @@ describe Projects::ForkService do
end end
end end
def fork_project(from_project, user, fork_success = true) describe :fork_to_namespace do
context = Projects::ForkService.new(from_project, user) before do
shell = double("gitlab_shell") @group_owner = create(:user)
shell.stub(fork_repository: fork_success) @developer = create(:user)
@project = create(:project, creator_id: @group_owner.id,
star_count: 777,
description: 'Wow, such a cool project!')
@group = create(:group)
@group.add_user(@group_owner, GroupMember::OWNER)
@group.add_user(@developer, GroupMember::DEVELOPER)
@opts = { namespace: @group }
end
context 'fork project for group' do
it 'group owner successfully forks project into the group' do
to_project = fork_project(@project, @group_owner, true, @opts)
to_project.owner.should == @group
to_project.namespace.should == @group
to_project.name.should == @project.name
to_project.path.should == @project.path
to_project.description.should == @project.description
to_project.star_count.should be_zero
end
end
context 'fork project for group when user not owner' do
it 'group developer should fail to fork project into the group' do
to_project = fork_project(@project, @developer, true, @opts)
to_project.errors[:namespace].should == ['insufficient access rights']
end
end
context 'project already exists in group' do
it 'should fail due to validation, not transaction failure' do
existing_project = create(:project, name: @project.name,
namespace: @group)
to_project = fork_project(@project, @group_owner, true, @opts)
existing_project.persisted?.should be_true
to_project.errors[:base].should == ['Invalid fork destination']
to_project.errors[:name].should == ['has already been taken']
to_project.errors[:path].should == ['has already been taken']
end
end
end
def fork_project(from_project, user, fork_success = true, params = {})
context = Projects::ForkService.new(from_project, user, params)
shell = double('gitlab_shell').stub(fork_repository: fork_success)
context.stub(gitlab_shell: shell) context.stub(gitlab_shell: shell)
context.execute context.execute
end end
......
require 'spec_helper'
require 'rake'
describe 'gitlab:mail_google_schema_whitelisting rake task' do
before :all do
Rake.application.rake_require "tasks/gitlab/task_helpers"
Rake.application.rake_require "tasks/gitlab/mail_google_schema_whitelisting"
# empty task as env is already loaded
Rake::Task.define_task :environment
end
describe 'call' do
before do
# avoid writing task output to spec progress
$stdout.stub :write
end
let :run_rake_task do
Rake::Task["gitlab:mail_google_schema_whitelisting"].reenable
Rake.application.invoke_task "gitlab:mail_google_schema_whitelisting"
end
it 'should run the task without errors' do
expect { run_rake_task }.to_not raise_error
end
end
end
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