diff --git a/.gitignore b/.gitignore index 084edd30df7b7ae1e87c0c57ae1040c1808ac5d3..4c25c8abf8209223a2384c21086d3e422587fa72 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ doc/code/* .secret *.log public/uploads.* +public/assets/ diff --git a/.travis.yml b/.travis.yml index f0fc2fb8829b09e50fb6286fc86c23ba6f3a98c1..75b4c5c7030cd08ad09b6fa3570ddc23e7ae88ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,14 @@ language: ruby env: - - DB=mysql TRAVIS=true + global: + - DB=mysql + - TRAVIS=true + matrix: + - TASK=spinach + - TASK=spec + - TASK=jasmine:ci before_install: - sudo apt-get install libicu-dev -y - - gem install charlock_holmes -v="0.6.9" branches: only: - 'master' @@ -11,8 +16,12 @@ rvm: - 2.0.0 services: - mysql - - postgresql + - redis-server before_script: - "cp config/database.yml.$DB config/database.yml" - "cp config/gitlab.yml.example config/gitlab.yml" -script: "bundle exec rake gitlab:test --trace" + - "bundle exec rake db:setup" + - "bundle exec rake db:seed_fu" +script: "bundle exec rake $TASK --trace" +notifications: + email: false diff --git a/CHANGELOG b/CHANGELOG index c1107717fc8dd4cae1906103c8b27234f3b29b2d..57a89a4c173e3acef3dea00d48a5fd7f8eec7900 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,112 @@ +v 6.6.0 + - Permissions: Developer now can manage issue tracker (modify any issue) + - Improve Code Compare page performance + +v 6.5.1 + - Fix branch selectbox when create merge request from fork + +v 6.5.0 + - Dropdown menus on issue#show page for assignee and milestone (Jason Blanchard) + - Add color custimization and previewing to broadcast messages + - Fixed notes anchors + - Load new comments in issues dynamically + - Added sort options to Public page + - New filters (assigned/authored/all) for Dashboard#issues/merge_requests (sponsored by Say Media) + - Add project visibility icons to dashboard + - Enable secure cookies if https used + - Protect users/confirmation with rack_attack + - Default HTTP headers to protect against MIME-sniffing, force https if enabled + - Bootstrap 3 with responsive UI + - New repository download formats: tar.bz2, zip, tar (Jason Hollingsworth) + - Restyled accept widgets for MR + - SCSS refactored + - Use jquery timeago plugin + - Fix 500 error for rdoc files + - Ability to customize merge commit message (sponsored by Say Media) + - Search autocomplete via ajax + - Add website url to user profile + - Files API supports base64 encoded content (sponsored by O'Reilly Media) + - Added support for Go's repository retrieval (Bruno Albuquerque) + +v6.4.3 + - Don't use unicorn worker killer if PhusionPassenger is defined + +v6.4.2 + - Fixed wrong behaviour of script/upgrade.rb + +v6.4.1 + - Fixed bug with repository rename + - Fixed bug with project transfer + +v 6.4.0 + - Added sorting to project issues page (Jason Blanchard) + - Assembla integration (Carlos Paramio) + - Fixed another 500 error with submodules + - UI: More compact issues page + - Minimal password length increased to 8 symbols + - Side-by-side diff view (Steven Thonus) + - Internal projects (Jason Hollingsworth) + - Allow removal of avatar (Drew Blessing) + - Project web hooks now support issues and merge request events + - Visiting project page while not logged in will redirect to sign-in instead of 404 (Jason Hollingsworth) + - Expire event cache on avatar creation/removal (Drew Blessing) + - Archiving old projects (Steven Thonus) + - Rails 4 + - Add time ago tooltips to show actual date/time + - UI: Fixed UI for admin system hooks + - Ruby script for easier GitLab upgrade + - Do not remove Merge requests if fork project was removed + - Improve sign-in/signup UX + - Add resend confirmation link to sign-in page + - Set noreply@HOSTNAME for reply_to field in all emails + - Show GitLab API version on Admin#dashboard + - API Cross-origin resource sharing + - Show READMe link at project home page + - Show repo size for projects in Admin area + +v 6.3.0 + - API for adding gitlab-ci service + - Init script now waits for pids to appear after (re)starting before reporting status (Rovanion Luckey) + - Restyle project home page + - Grammar fixes + - Show branches list (which branches contains commit) on commit page (Andrew Kumanyaev) + - Security improvements + - Added support for GitLab CI 4.0 + - Fixed issue with 500 error when group did not exist + - Ability to leave project + - You can create file in repo using UI + - You can remove file from repo using UI + - API: dropped default_branch attribute from project during creation + - Project default_branch is not stored in db any more. It takes from repo now. + - Admin broadcast messages + - UI improvements + - Dont show last push widget if user removed this branch + - Fix 500 error for repos with newline in file name + - Extended html titles + - API: create/update/delete repo files + - Admin can transfer project to any namespace + - API: projects/all for admin users + - Fix recent branches order + +v 6.2.4 + - Security: Cast API private_token to string (CVE-2013-4580) + - Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583) + - Fix for Git SSH access for LDAP users + +v 6.2.3 + - Security: More protection against CVE-2013-4489 + - Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546) + - Fix sidekiq rake tasks + +v 6.2.2 + - Security: Update gitlab_git (CVE-2013-4489) + +v 6.2.1 + - Security: Fix issue with generated passwords for new users + v 6.2.0 - - Public projects are visible from the outside + - Public project pages are now visible to everyone (files, issues, wik, etc.) + THIS MEANS YOUR ISSUES AND WIKI FOR PUBLIC PROJECTS ARE PUBLICLY VISIBLE AFTER THE UPGRADE - Add group access to permissions page - Require current password to change one - Group owner or admin can remove other group owners @@ -12,6 +119,13 @@ v 6.2.0 - Update logic for validates_merge_request for tree of MR (Andrew Kumanyaev) - Rake tasks for web hooks management (Jonhnny Weslley) - Extended User API to expose admin and can_create_group for user creation/updating (Boyan Tabakov) + - API: Remove group + - API: Remove project + - Avatar upload on profile page with a maximum of 100KB (Steven Thonus) + - Store the sessions in Redis instead of the cookie store + - Fixed relative links in markdown + - User must confirm their email if signup enabled + - User must confirm changed email v 6.1.0 - Project specific IDs for issues, mr, milestones @@ -32,7 +146,7 @@ v 6.1.0 - Add links to create branch/tag from project home page - Add public-project? checkbox to new-project view - Improved compare page. Added link to proceed into Merge Request - - Send email to user when he was added to group + - Send an email to a user when they are added to group - New landing page when you have 0 projects v 6.0.0 @@ -75,6 +189,14 @@ v 6.0.0 - Improved MR comments logic - Render readme file for projects in public area +v 5.4.2 + - Security: Cast API private_token to string (CVE-2013-4580) + - Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583) + +v 5.4.1 + - Security: Fixes for CVE-2013-4489 + - Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546) + v 5.4.0 - Ability to edit own comments - Documentation improvements diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9d9be5bdc219713eabb49910e0f8e5be7428a562..0a97faaf301f6021ee7b9f542124f9a3159ecbb6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,37 +1,41 @@ # Contribute to GitLab -This guide details how to use issues and pull requests to improve GitLab. - -- [Closing policy for issues and pull requests](#closing-policy-for-issues-and-pull-requests) -- [Issue tracker](#issue-tracker) -- [Pull requests](#pull-requests) +This guide details how contribute to GitLab. If you want to know how the GitLab team handles contributions have a look at [the GitLab contributing process](PROCESS.md). -## Closing policy for issues and pull requests +## Contributor license agreement + +By submitting code as an individual you agree to the [individual contributor license agreement](doc/legal/individual_contributor_license_agreement.md). By submitting code as an entity you agree to the [corporate contributor license agreement](doc/legal/corporate_contributor_license_agreement.md). + +## Security vulnerability disclosure + +Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://www.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. + +## Closing policy for issues and merge requests -GitLab is a popular open source project and the capacity to deal with issues and pull requests is limited. Out of respect for our volunteers, issues and pull requests not in line with the guidelines listed in this document may be closed without notice. +GitLab is a popular open source project and the capacity to deal with issues and merge requests is limited. Out of respect for our volunteers, issues and merge requests not in line with the guidelines listed in this document may be closed without notice. Please treat our volunteers with courtesy and respect, it will go a long way towards getting your issue resolved. -Issues and pull requests should be in English and contain appropriate language for audiences of all ages. +Issues and merge requests should be in English and contain appropriate language for audiences of all ages. ## Issue tracker -To get support for your particular problem please use the channels as detailed in [the getting help section of the readme](https://github.com/gitlabhq/gitlabhq#getting-help). Professional [support subscriptions](http://www.gitlab.com/subscription/) and [consulting services](http://www.gitlab.com/consultancy/) are available from [GitLab.com](http://www.gitlab.com/). +To get support for your particular problem please use the channels as detailed in the getting help section of [the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md). Professional [support subscriptions](http://www.gitlab.com/subscription/) and [consulting services](http://www.gitlab.com/consultancy/) are available from [GitLab.com](http://www.gitlab.com/). -The [issue tracker](https://github.com/gitlabhq/gitlabhq/issues) is only for obvious bugs or misbehavior in the latest [stable or development release of GitLab](MAINTENANCE.md). When submitting an issue please conform to the issue submission guidelines listed below. Not all issues will be addressed and your issue is more likely to be addressed if you submit a pull request which partially or fully addresses the issue. +The [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious bugs or misbehavior in the latest [stable or development release of GitLab](MAINTENANCE.md). When submitting an issue please conform to the issue submission guidelines listed below. Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue. Do not use the issue tracker for feature requests. We have a specific [feedback and suggestions forum](http://feedback.gitlab.com) for this purpose. -Please send a pull request with a tested solution or a pull request with a failing test instead of opening an issue if you can. If you're unsure where to post, post to the [mailing list](https://groups.google.com/forum/#!forum/gitlabhq) or [Stack Overflow](http://stackoverflow.com/questions/tagged/gitlab) first. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there. +Please send a merge request with a tested solution or a merge request with a failing test instead of opening an issue if you can. If you're unsure where to post, post to the [mailing list](https://groups.google.com/forum/#!forum/gitlabhq) or [Stack Overflow](http://stackoverflow.com/questions/tagged/gitlab) first. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there. ### Issue tracker guidelines -**[Search](https://github.com/gitlabhq/gitlabhq/search?q=&ref=cmdform&type=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) -2. **Steps to reproduce:** How can we reproduce the issue, preferably on the [GitLab Vagrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm) (start with: `vagrant destroy && vagrant up && vagrant ssh`) +2. **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`) 3. **Expected behavior:** Describe your issue in detail 4. **Observed behavior** 5. **Relevant logs and/or screenshots:** Please use code blocks (\`\`\`) to format console output, logs, and code as it's very hard to read otherwise. @@ -42,34 +46,45 @@ Please send a pull request with a tested solution or a pull request with a faili * Describe your setup (use relevant parts from `sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`) 7. **Possible fixes**: If you can, link to the line of code that might be responsible for the problem -## Pull requests +## Merge requests -We welcome pull requests with fixes and improvements to GitLab code, tests, and/or documentation. The features we would really like a pull request for are listed with the [status 'accepting merge/pull requests' on our feedback forum](http://feedback.gitlab.com/forums/176466-general/status/796455) but other improvements are also welcome. +We welcome merge requests with fixes and improvements to GitLab code, tests, and/or documentation. The features we would really like a merge request for are listed with the [status 'accepting merge/merge requests' on our feedback forum](http://feedback.gitlab.com/forums/176466-general/status/796455) but other improvements are also welcome. -### Pull request guidelines +### Merge request guidelines -If you can, please submit a pull request with the fix or improvements including tests. If you don't know how to fix the issue but can write a test that exposes the issue we will accept that as well. In general bug fixes that include a regression test are merged quickly while new features without proper tests are least likely to receive timely feedback. The workflow to make a pull request is as follows: +If you can, please submit a merge request with the fix or improvements including tests. If you don't know how to fix the issue but can write a test that exposes the issue we will accept that as well. In general bug fixes that include a regression test are merged quickly while new features without proper tests are least likely to receive timely feedback. The workflow to make a merge request is as follows: -1. Fork the project on GitHub +1. Fork the project on GitLab Cloud 1. Create a feature branch 1. Write [tests](README.md#run-the-tests) and code 1. Add your changes to the [CHANGELOG](CHANGELOG) 1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) 1. Push the commit to your fork -1. Submit a pull request -2. [Search for issues](https://github.com/gitlabhq/gitlabhq/search?q=&ref=cmdform&type=Issues) related to your pull request and mention them in the pull request description - -We will accept pull requests if: - -* The code has proper tests and all tests pass (or it is a test exposing a failure in existing code) -* It can be merged without problems (if not please use: `git rebase master`) -* It does not break any existing functionality -* It's quality code that conforms to the [Ruby](https://github.com/bbatsov/ruby-style-guide) and [Rails](https://github.com/bbatsov/rails-style-guide) style guides and best practices -* The description includes a motive for your change and the method you used to achieve it -* It is not a catch all pull request but rather fixes a specific issue or implements a specific feature -* It keeps the GitLab code base clean and well structured -* We think other users will benefit from the same functionality -* If it makes changes to the UI the pull request should include screenshots -* It is a single commit (please use `git rebase -i` to squash commits) - -For examples of feedback on pull requests please look at already [closed pull requests](https://github.com/gitlabhq/gitlabhq/pulls?direction=desc&page=1&sort=created&state=closed). +1. Submit a merge request (MR) +1. The MR title should describes the change you want to make +1. The MR description should give a motive for your change and the method you used to achieve it +1. If the MR changes the UI it should include before and after screenshots +1. Link relevant [issues](https://gitlab.com/gitlab-org/gitlab-ce/issues) and/or [feedback items](http://feedback.gitlab.com/) from the merge request description and leave a comment on them with a link back to the MR +1. Be prepared to answer questions and incorporate feedback even if requests for this arrive weeks or months after your MR submittion + +Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? The smaller a MR is the more likely it is it will be merged, after that you can send more MR's to enhance it. + +The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. +The best time to submit a MR and get feedback fast. +Before this time the GitLab.com team is still dealing with work that is created by the monthly release such as assisting subscribers with upgrade issues, the release of Enterprise Edition and the upgrade of GitLab Cloud. +After the 7th it is already getting closer to the release date of the next version. +This means there is less time to fix the issues created by merging large new features. + +We will accept a merge requests if it: + +* Includes proper tests and all tests pass (unless it contains a test exposing a bug in existing code) +* Can be merged without problems (if not please use: `git rebase master`) +* Do not break any existing functionality +* Conforms to the [Ruby](https://github.com/bbatsov/ruby-style-guide) and [Rails](https://github.com/bbatsov/rails-style-guide) style guides and best practices +* Fixes one specific issue or implements one specific feature (do not combine things, send separate merge requests if needed) +* Keeps the GitLab code base clean and well structured +* Contains functionality we think other users will benefit from too +* Doesn't add avoidable configuration options since these complicate future changes +* Contains a single commit (please use `git rebase -i` to squash commits) + +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). diff --git a/Gemfile b/Gemfile index b827b59adf987c5f818ef440bb35ab6516577da2..446ee73a4b73ba3bff7be46a3422bc1127d19973 100644 --- a/Gemfile +++ b/Gemfile @@ -8,14 +8,20 @@ def linux_only(require_as) RUBY_PLATFORM.include?('linux') && require_as end -gem "rails", "3.2.13" +gem "rails", "~> 4.0.0" + +gem "protected_attributes" +gem 'rails-observers' +gem 'actionpack-page_caching' +gem 'actionpack-action_caching' # Supported DBs gem "mysql2", group: :mysql gem "pg", group: :postgres # Auth -gem "devise", '~> 2.2' +gem "devise", '3.0.4' +gem "devise-async", '0.8.0' gem 'omniauth', "~> 1.1.3" gem 'omniauth-google-oauth2' gem 'omniauth-twitter' @@ -23,26 +29,27 @@ gem 'omniauth-github' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '2.3.1' +gem "gitlab_git", '~> 5.1.0' # Ruby/Rack Git Smart-HTTP Server Handler -gem 'gitlab-grack', '~> 1.0.1', require: 'grack' +gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack' # LDAP Auth -gem 'gitlab_omniauth-ldap', '1.0.3', require: "omniauth-ldap" - -# Syntax highlighter -gem "gitlab-pygments.rb", '~> 0.3.2', require: 'pygments.rb' +gem 'gitlab_omniauth-ldap', '1.0.4', require: "omniauth-ldap" # Git Wiki -gem "gitlab-gollum-lib", "~> 1.0.1", require: 'gollum-lib' +gem "gitlab-gollum-lib", "~> 1.1.0", require: 'gollum-lib' # Language detection -gem "github-linguist", require: "linguist" +gem "gitlab-linguist", "~> 3.0.0", require: "linguist" # API -gem "grape", "~> 0.4.1" +gem "grape", "~> 0.6.1" gem "grape-entity", "~> 0.3.0" +gem 'rack-cors', require: 'rack/cors' + +# Email validation +gem "email_validator", "~> 1.4.0", :require => 'email_validator/strict' # Format dates and times # based on human-friendly examples @@ -52,7 +59,7 @@ gem "stamp" gem 'enumerize' # Pagination -gem "kaminari", "~> 0.14.1" +gem "kaminari", "~> 0.15.1" # HAML gem "haml-rails" @@ -71,13 +78,16 @@ gem "seed-fu" # Markdown to HTML gem "redcarpet", "~> 2.2.2" -gem "github-markup", "~> 0.7.4", require: 'github/markup' +gem "github-markup", "~> 0.7.4", require: 'github/markup', git: 'https://github.com/gitlabhq/markup.git', ref: '61ade389c1e1c159359338f570d18464a44ddbc4' # Asciidoc to HTML gem "asciidoctor" # Application server -gem "unicorn", '~> 4.6.3', group: :unicorn +group :unicorn do + gem "unicorn", '~> 4.6.3' + gem 'unicorn-worker-killer' +end # State machine gem "state_machine" @@ -109,7 +119,10 @@ gem "redis-rails" gem 'tinder', '~> 1.9.2' # HipChat integration -gem "hipchat", "~> 0.9.0" +gem "hipchat", "~> 0.14.0" + +# Flowdock integration +gem "gitlab-flowdock-git-hook", "~> 0.4.2" # d3 gem "d3_rails", "~> 3.1.4" @@ -123,26 +136,23 @@ gem "sanitize" # Protect against bruteforcing gem "rack-attack" -group :assets do - gem "sass-rails" - gem "coffee-rails" - gem "uglifier" - gem "therubyracer" - gem 'turbolinks' - gem 'jquery-turbolinks' - - gem 'chosen-rails', "1.0.0" - gem 'select2-rails' - gem 'jquery-atwho-rails', "0.3.0" - gem "jquery-rails", "2.1.3" - gem "jquery-ui-rails", "2.0.2" - gem "modernizr", "2.6.2" - gem "raphael-rails", "~> 2.1.2" - gem 'bootstrap-sass' - gem "font-awesome-rails" - gem "gemoji", "~> 1.2.1", require: 'emoji/railtie' - gem "gon" -end +gem "sass-rails" +gem "coffee-rails" +gem "uglifier" +gem "therubyracer" +gem 'turbolinks' +gem 'jquery-turbolinks' + +gem 'select2-rails' +gem 'jquery-atwho-rails', "~> 0.3.3" +gem "jquery-rails", "2.1.3" +gem "jquery-ui-rails", "2.0.2" +gem "modernizr", "2.6.2" +gem "raphael-rails", "~> 2.1.2" +gem 'bootstrap-sass', '~> 3.0' +gem "font-awesome-rails", '~> 3.2' +gem "gemoji", "~> 1.3.0" +gem "gon", '~> 5.0.0' group :development do gem "annotate", "~> 2.6.0.beta2" @@ -165,7 +175,7 @@ end group :development, :test do gem 'coveralls', require: false - gem 'rails-dev-tweaks' + # gem 'rails-dev-tweaks' gem 'spinach-rails' gem "rspec-rails" gem "capybara" @@ -194,7 +204,7 @@ group :development, :test do gem 'poltergeist', '~> 1.4.1' gem 'spork', '~> 1.0rc' - gem 'jasmine' + gem 'jasmine', '2.0.0.rc5' end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 7327e5c28151f1883877b0b0875b0227c080cec7..0428a9df483ff3f2e43991e68c52375c65246c69 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,135 +1,140 @@ +GIT + remote: https://github.com/gitlabhq/markup.git + revision: 61ade389c1e1c159359338f570d18464a44ddbc4 + ref: 61ade389c1e1c159359338f570d18464a44ddbc4 + specs: + github-markup (0.7.6) + GEM remote: https://rubygems.org/ specs: - actionmailer (3.2.13) - actionpack (= 3.2.13) - mail (~> 2.5.3) - actionpack (3.2.13) - activemodel (= 3.2.13) - activesupport (= 3.2.13) - builder (~> 3.0.0) + actionmailer (4.0.2) + actionpack (= 4.0.2) + mail (~> 2.5.4) + actionpack (4.0.2) + activesupport (= 4.0.2) + builder (~> 3.1.0) erubis (~> 2.7.0) - journey (~> 1.0.4) - rack (~> 1.4.5) - rack-cache (~> 1.2) - rack-test (~> 0.6.1) - sprockets (~> 2.2.1) - activemodel (3.2.13) - activesupport (= 3.2.13) - builder (~> 3.0.0) - activerecord (3.2.13) - activemodel (= 3.2.13) - activesupport (= 3.2.13) - arel (~> 3.0.2) - tzinfo (~> 0.3.29) - activeresource (3.2.13) - activemodel (= 3.2.13) - activesupport (= 3.2.13) - activesupport (3.2.13) - i18n (= 0.6.1) - multi_json (~> 1.0) + rack (~> 1.5.2) + rack-test (~> 0.6.2) + actionpack-action_caching (1.1.0) + actionpack (>= 4.0.0, < 5.0) + actionpack-page_caching (1.0.2) + actionpack (>= 4.0.0, < 5) + activemodel (4.0.2) + activesupport (= 4.0.2) + builder (~> 3.1.0) + activerecord (4.0.2) + activemodel (= 4.0.2) + activerecord-deprecated_finders (~> 1.0.2) + activesupport (= 4.0.2) + arel (~> 4.0.0) + activerecord-deprecated_finders (1.0.3) + activesupport (4.0.2) + i18n (~> 0.6, >= 0.6.4) + minitest (~> 4.2) + multi_json (~> 1.3) + thread_safe (~> 0.1) + tzinfo (~> 0.3.37) acts-as-taggable-on (2.4.1) rails (>= 3, < 5) - addressable (2.3.4) - annotate (2.6.0.beta2) + addressable (2.3.5) + annotate (2.6.0) activerecord (>= 2.3.0) rake (>= 0.8.7) - arel (3.0.2) - asciidoctor (0.1.3) + arel (4.0.1) + asciidoctor (0.1.4) + atomic (1.1.14) awesome_print (1.2.0) - backports (3.3.2) - bcrypt-ruby (3.1.1) + axiom-types (0.0.5) + descendants_tracker (~> 0.0.1) + ice_nine (~> 0.9) + bcrypt-ruby (3.1.2) better_errors (1.0.1) coderay (>= 1.0.0) erubis (>= 2.6.6) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) - bootstrap-sass (2.3.2.2) + bootstrap-sass (3.0.3.0) sass (~> 3.2) - builder (3.0.4) + builder (3.1.4) capybara (2.1.0) mime-types (>= 1.16) nokogiri (>= 1.3.3) rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) - carrierwave (0.8.0) + carrierwave (0.9.0) activemodel (>= 3.2.0) activesupport (>= 3.2.0) - celluloid (0.14.1) - timers (>= 1.0.0) + json (>= 1.7) + celluloid (0.15.2) + timers (~> 1.1.0) charlock_holmes (0.6.9.4) - childprocess (0.3.9) - ffi (~> 1.0, >= 1.0.11) - chosen-rails (1.0.0) - coffee-rails (>= 3.2) - compass-rails (>= 1.0) - railties (>= 3.0) - sass-rails (>= 3.2) - chunky_png (1.2.8) - cliver (0.2.1) + cliver (0.2.2) code_analyzer (0.4.3) sexp_processor - coderay (1.0.9) - coffee-rails (3.2.2) + coderay (1.1.0) + coercible (1.0.0) + descendants_tracker (~> 0.0.1) + coffee-rails (4.0.1) coffee-script (>= 2.2.0) - railties (~> 3.2.0) + railties (>= 4.0.0, < 5.0) coffee-script (2.2.0) coffee-script-source execjs - coffee-script-source (1.6.2) + coffee-script-source (1.6.3) colored (1.2) colorize (0.5.8) - compass (0.12.2) - chunky_png (~> 1.2) - fssm (>= 0.2.7) - sass (~> 3.1) - compass-rails (1.0.3) - compass (>= 0.12.2, < 0.14) - connection_pool (1.1.0) + connection_pool (1.2.0) coveralls (0.7.0) multi_json (~> 1.3) rest-client simplecov (>= 0.7) term-ansicolor thor - crack (0.4.0) + crack (0.4.1) safe_yaml (~> 0.9.0) d3_rails (3.1.10) railties (>= 3.1.0) daemons (1.1.9) - database_cleaner (1.1.1) + database_cleaner (1.2.0) debug_inspector (0.0.2) - descendants_tracker (0.0.1) - devise (2.2.5) + descendants_tracker (0.0.3) + devise (3.0.4) bcrypt-ruby (~> 3.0) orm_adapter (~> 0.1) - railties (~> 3.1) - warden (~> 1.2.1) - diff-lcs (1.2.4) - dotenv (0.8.0) - email_spec (1.4.0) + railties (>= 3.2.6, < 5) + warden (~> 1.2.3) + devise-async (0.8.0) + devise (>= 2.2, < 3.2) + diff-lcs (1.2.5) + docile (1.1.1) + dotenv (0.9.0) + email_spec (1.5.0) launchy (~> 2.1) mail (~> 2.2) - enumerize (0.6.1) + email_validator (1.4.0) + activemodel + enumerize (0.7.0) activesupport (>= 3.2) + equalizer (0.0.8) erubis (2.7.0) escape_utils (0.2.4) eventmachine (1.0.3) excon (0.13.4) - execjs (1.4.0) - multi_json (~> 1.0) - factory_girl (4.2.0) + execjs (2.0.2) + factory_girl (4.3.0) activesupport (>= 3.0.0) - factory_girl_rails (4.2.1) - factory_girl (~> 4.2.0) + factory_girl_rails (4.3.0) + factory_girl (~> 4.3.0) railties (>= 3.0.0) - faraday (0.8.7) - multipart-post (~> 1.1) + faraday (0.8.8) + multipart-post (~> 1.2.0) faraday_middleware (0.9.0) faraday (>= 0.7.4, < 0.9) - ffaker (1.18.0) - ffi (1.9.0) + ffaker (1.22.1) + ffi (1.9.3) fog (1.3.1) builder excon (~> 0.13.0) @@ -146,48 +151,46 @@ GEM dotenv (>= 0.7) thor (>= 0.13.6) formatador (0.2.4) - fssm (0.2.10) - gemoji (1.2.1) - gherkin-ruby (0.3.0) - github-linguist (2.3.4) - charlock_holmes (~> 0.6.6) - escape_utils (~> 0.2.3) - mime-types (~> 1.19) - pygments.rb (>= 0.2.13) - github-markdown (0.5.3) - github-markup (0.7.5) - gitlab-gollum-lib (1.0.1) + gemoji (1.3.1) + gherkin-ruby (0.3.1) + racc + github-markdown (0.5.5) + gitlab-flowdock-git-hook (0.4.2.2) + gitlab-grit (>= 2.4.1) + multi_json + gitlab-gollum-lib (1.1.0) github-markdown (~> 0.5.3) github-markup (>= 0.7.5, < 1.0.0) - gitlab-grit (>= 2.5.1) + gitlab-grit (~> 2.6.1) nokogiri (~> 1.5.9) - pygments.rb (~> 0.4.2) sanitize (~> 2.0.3) stringex (~> 1.5.1) - gitlab-grack (1.0.1) - rack (~> 1.4.1) - gitlab-grit (2.6.0) + gitlab-grack (2.0.0.pre) + rack (~> 1.5.1) + gitlab-grit (2.6.4) charlock_holmes (~> 0.6.9) diff-lcs (~> 1.1) mime-types (~> 1.15) posix-spawn (~> 0.3.6) - gitlab-pygments.rb (0.3.2) - posix-spawn (~> 0.3.6) - yajl-ruby (~> 1.1.0) - gitlab_git (2.3.1) - activesupport (~> 3.2.13) - github-linguist (~> 2.3.4) - gitlab-grit (~> 2.6.0) + gitlab-linguist (3.0.0) + charlock_holmes (~> 0.6.6) + escape_utils (~> 0.2.4) + mime-types (~> 1.19) + gitlab_git (5.1.0) + activesupport (~> 4.0.0) + gitlab-grit (~> 2.6.1) + gitlab-linguist (~> 3.0.0) + rugged (~> 0.19.0) gitlab_meta (6.0) - gitlab_omniauth-ldap (1.0.3) + gitlab_omniauth-ldap (1.0.4) net-ldap (~> 0.3.1) omniauth (~> 1.0) pyu-ruby-sasl (~> 0.0.3.1) rubyntlm (~> 0.1.1) - gon (4.1.1) + gon (5.0.1) actionpack (>= 2.3.0) json - grape (0.4.1) + grape (0.6.1) activesupport builder hashie (>= 1.2.0) @@ -196,91 +199,91 @@ GEM rack (>= 1.3.0) rack-accept rack-mount - virtus + virtus (>= 1.0.0) grape-entity (0.3.0) activesupport multi_json (>= 1.3.2) growl (1.0.3) - guard (1.8.1) + guard (2.2.4) formatador (>= 0.2.4) - listen (>= 1.0.0) - lumberjack (>= 1.0.2) - pry (>= 0.9.10) - thor (>= 0.14.6) - guard-rspec (3.0.2) - guard (>= 1.8) - rspec (~> 2.13) + listen (~> 2.1) + lumberjack (~> 1.0) + pry (>= 0.9.12) + thor (>= 0.18.1) + guard-rspec (4.2.0) + guard (>= 2.1.1) + rspec (>= 2.14, < 4.0) guard-spinach (0.0.2) guard (>= 1.1) spinach - haml (4.0.3) + haml (4.0.4) tilt - haml-rails (0.4) - actionpack (>= 3.1, < 4.1) - activesupport (>= 3.1, < 4.1) - haml (>= 3.1, < 4.1) - railties (>= 3.1, < 4.1) - hashie (1.2.0) + haml-rails (0.5.1) + actionpack (~> 4.0.0) + activesupport (~> 4.0.0) + haml (>= 3.1, < 5.0) + railties (~> 4.0.0) + hashie (2.0.5) hike (1.2.3) - hipchat (0.9.0) + hipchat (0.14.0) httparty httparty http_parser.rb (0.5.3) - httparty (0.11.0) - multi_json (~> 1.0) + httparty (0.12.0) + json (~> 1.8) multi_xml (>= 0.5.2) httpauth (0.2.0) - i18n (0.6.1) - jasmine (1.3.2) - jasmine-core (~> 1.3.1) - rack (~> 1.0) - rspec (>= 1.3.1) - selenium-webdriver (>= 0.1.3) - jasmine-core (1.3.1) - journey (1.0.4) - jquery-atwho-rails (0.3.0) + i18n (0.6.9) + ice_nine (0.10.0) + jasmine (2.0.0.rc5) + jasmine-core (~> 2.0.0.rc5) + phantomjs + rack (>= 1.2.1) + rake + jasmine-core (2.0.0.rc5) + jquery-atwho-rails (0.3.3) jquery-rails (2.1.3) railties (>= 3.1.0, < 5.0) thor (~> 0.14) - jquery-turbolinks (1.0.0) + jquery-turbolinks (2.0.1) railties (>= 3.1.0) turbolinks jquery-ui-rails (2.0.2) jquery-rails railties (>= 3.1.0) - json (1.7.7) + json (1.8.1) jwt (0.1.8) multi_json (>= 1.5) - kaminari (0.14.1) + kaminari (0.15.1) actionpack (>= 3.0.0) activesupport (>= 3.0.0) - kgio (2.8.0) - launchy (2.3.0) + kgio (2.8.1) + launchy (2.4.2) addressable (~> 2.3) - letter_opener (1.1.1) + letter_opener (1.1.2) launchy (~> 2.2) - libv8 (3.11.8.17) - listen (1.2.2) + libv8 (3.16.14.3) + listen (2.3.1) + celluloid (>= 0.15.2) rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9) - rb-kqueue (>= 0.2) - lumberjack (1.0.3) + lumberjack (1.0.4) mail (2.5.4) mime-types (~> 1.16) treetop (~> 1.4.8) - method_source (0.8.1) - mime-types (1.25) - minitest (4.7.4) + method_source (0.8.2) + mime-types (1.25.1) + minitest (4.7.5) modernizr (2.6.2) sprockets (~> 2.0) - multi_json (1.8.0) - multi_xml (0.5.4) + multi_json (1.8.4) + multi_xml (0.5.5) multipart-post (1.2.0) mysql2 (0.3.11) net-ldap (0.3.1) net-scp (1.0.4) net-ssh (>= 1.99.1) - net-ssh (2.6.8) + net-ssh (2.7.0) nokogiri (1.5.10) oauth (0.4.7) oauth2 (0.8.1) @@ -292,10 +295,10 @@ GEM omniauth (1.1.4) hashie (>= 1.2, < 3) rack - omniauth-github (1.1.0) + omniauth-github (1.1.1) omniauth (~> 1.0) omniauth-oauth2 (~> 1.1) - omniauth-google-oauth2 (0.1.19) + omniauth-google-oauth2 (0.2.1) omniauth (~> 1.0) omniauth-oauth2 omniauth-oauth (1.0.1) @@ -304,56 +307,53 @@ GEM omniauth-oauth2 (1.1.1) oauth2 (~> 0.8.0) omniauth (~> 1.0) - omniauth-twitter (0.0.17) + omniauth-twitter (1.0.1) multi_json (~> 1.3) omniauth-oauth (~> 1.0) - orm_adapter (0.4.0) + orm_adapter (0.5.0) pg (0.15.1) + phantomjs (1.9.2.0) poltergeist (1.4.1) capybara (~> 2.1.0) cliver (~> 0.2.1) multi_json (~> 1.0) websocket-driver (>= 0.2.0) polyglot (0.3.3) - posix-spawn (0.3.6) - pry (0.9.12.2) - coderay (~> 1.0.5) + posix-spawn (0.3.8) + protected_attributes (1.0.5) + activemodel (>= 4.0.1, < 5.0) + pry (0.9.12.4) + coderay (~> 1.0) method_source (~> 0.8) slop (~> 3.4) - pygments.rb (0.4.2) - posix-spawn (~> 0.3.6) - yajl-ruby (~> 1.1.0) pyu-ruby-sasl (0.0.3.3) quiet_assets (1.0.2) railties (>= 3.1, < 5.0) - rack (1.4.5) + racc (1.4.10) + rack (1.5.2) rack-accept (0.4.5) rack (>= 0.4) - rack-attack (2.2.1) + rack-attack (2.3.0) rack - rack-cache (1.2) - rack (>= 0.4) + rack-cors (0.2.9) rack-mini-profiler (0.1.31) rack (>= 1.1.3) rack-mount (0.8.3) rack (>= 1.0.0) - rack-protection (1.5.0) - rack - rack-ssl (1.3.3) + rack-protection (1.5.1) rack rack-test (0.6.2) rack (>= 1.0) - rails (3.2.13) - actionmailer (= 3.2.13) - actionpack (= 3.2.13) - activerecord (= 3.2.13) - activeresource (= 3.2.13) - activesupport (= 3.2.13) - bundler (~> 1.0) - railties (= 3.2.13) - rails-dev-tweaks (0.6.1) - actionpack (~> 3.1) - railties (~> 3.1) + rails (4.0.2) + actionmailer (= 4.0.2) + actionpack (= 4.0.2) + activerecord (= 4.0.2) + activesupport (= 4.0.2) + bundler (>= 1.3.0, < 2.0) + railties (= 4.0.2) + sprockets-rails (~> 2.0.0) + rails-observers (0.1.2) + activemodel (~> 4.0) rails_best_practices (1.14.4) activesupport awesome_print @@ -363,235 +363,237 @@ GEM i18n require_all ruby-progressbar - railties (3.2.13) - actionpack (= 3.2.13) - activesupport (= 3.2.13) - rack-ssl (~> 1.3.2) + railties (4.0.2) + actionpack (= 4.0.2) + activesupport (= 4.0.2) rake (>= 0.8.7) - rdoc (~> 3.4) - thor (>= 0.14.6, < 2.0) - raindrops (0.11.0) + thor (>= 0.18.1, < 2.0) + raindrops (0.12.0) rake (10.1.0) raphael-rails (2.1.2) rb-fsevent (0.9.3) - rb-inotify (0.9.0) - ffi (>= 0.5.0) - rb-kqueue (0.2.0) + rb-inotify (0.9.2) ffi (>= 0.5.0) rdoc (3.12.2) json (~> 1.4) redcarpet (2.2.2) - redis (3.0.4) - redis-actionpack (3.2.4) - actionpack (~> 3.2.0) - redis-rack (~> 1.4.4) - redis-store (~> 1.1.4) - redis-activesupport (3.2.4) - activesupport (~> 3.2.0) + redis (3.0.6) + redis-actionpack (4.0.0) + actionpack (~> 4) + redis-rack (~> 1.5.0) + redis-store (~> 1.1.0) + redis-activesupport (4.0.0) + activesupport (~> 4) + redis-store (~> 1.1.0) + redis-namespace (1.4.1) + redis (~> 3.0.4) + redis-rack (1.5.0) + rack (~> 1.5) + redis-store (~> 1.1.0) + redis-rails (4.0.0) + redis-actionpack (~> 4) + redis-activesupport (~> 4) redis-store (~> 1.1.0) - redis-namespace (1.3.1) - redis (~> 3.0.0) - redis-rack (1.4.4) - rack (~> 1.4.0) - redis-store (~> 1.1.4) - redis-rails (3.2.4) - redis-actionpack (~> 3.2.4) - redis-activesupport (~> 3.2.4) - redis-store (~> 1.1.4) redis-store (1.1.4) redis (>= 2.2) ref (1.0.5) - require_all (1.3.1) + require_all (1.3.2) rest-client (1.6.7) mime-types (>= 1.16) - rspec (2.13.0) - rspec-core (~> 2.13.0) - rspec-expectations (~> 2.13.0) - rspec-mocks (~> 2.13.0) - rspec-core (2.13.1) - rspec-expectations (2.13.0) + rspec (2.14.1) + rspec-core (~> 2.14.0) + rspec-expectations (~> 2.14.0) + rspec-mocks (~> 2.14.0) + rspec-core (2.14.7) + rspec-expectations (2.14.4) diff-lcs (>= 1.1.3, < 2.0) - rspec-mocks (2.13.1) - rspec-rails (2.13.2) + rspec-mocks (2.14.4) + rspec-rails (2.14.0) actionpack (>= 3.0) activesupport (>= 3.0) railties (>= 3.0) - rspec-core (~> 2.13.0) - rspec-expectations (~> 2.13.0) - rspec-mocks (~> 2.13.0) + rspec-core (~> 2.14.0) + rspec-expectations (~> 2.14.0) + rspec-mocks (~> 2.14.0) ruby-hmac (0.4.0) ruby-progressbar (1.2.0) rubyntlm (0.1.1) - rubyzip (0.9.9) - safe_yaml (0.9.3) - sanitize (2.0.3) - nokogiri (>= 1.4.4, < 1.6) - sass (3.2.11) - sass-rails (3.2.6) - railties (~> 3.2.0) + rugged (0.19.0) + safe_yaml (0.9.7) + sanitize (2.0.6) + nokogiri (>= 1.4.4) + sass (3.2.12) + sass-rails (4.0.1) + railties (>= 4.0.0, < 5.0) sass (>= 3.1.10) - tilt (~> 1.3) + sprockets-rails (~> 2.0.0) sdoc (0.3.20) json (>= 1.1.3) rdoc (~> 3.10) - seed-fu (2.2.0) - activerecord (~> 3.1) - activesupport (~> 3.1) - select2-rails (3.4.2) - sass-rails + seed-fu (2.3.0) + activerecord (>= 3.1, < 4.1) + activesupport (>= 3.1, < 4.1) + select2-rails (3.5.2) thor (~> 0.14) - selenium-webdriver (2.33.0) - childprocess (>= 0.2.5) - multi_json (~> 1.0) - rubyzip - websocket (~> 1.0.4) settingslogic (2.0.9) - sexp_processor (4.3.0) + sexp_processor (4.4.0) shoulda-matchers (2.1.0) activesupport (>= 3.0.0) - sidekiq (2.14.0) - celluloid (>= 0.14.1) + sidekiq (2.17.0) + celluloid (>= 0.15.2) connection_pool (>= 1.0.0) json redis (>= 3.0.4) - redis-namespace + redis-namespace (>= 1.3.1) simple_oauth (0.1.9) - simplecov (0.7.1) - multi_json (~> 1.0) - simplecov-html (~> 0.7.1) - simplecov-html (0.7.1) - sinatra (1.4.3) + simplecov (0.8.2) + docile (~> 1.1.0) + multi_json + simplecov-html (~> 0.8.0) + simplecov-html (0.8.0) + sinatra (1.4.4) rack (~> 1.4) rack-protection (~> 1.4) tilt (~> 1.3, >= 1.3.4) six (0.2.0) - slim (2.0.0) - temple (~> 0.6.5) - tilt (~> 1.3, >= 1.3.3) - slop (3.4.5) - spinach (0.8.3) + slim (2.0.2) + temple (~> 0.6.6) + tilt (>= 1.3.3, < 2.1) + slop (3.4.7) + spinach (0.8.7) colorize (= 0.5.8) - gherkin-ruby (~> 0.3.0) + gherkin-ruby (>= 0.3.1) spinach-rails (0.2.1) capybara (>= 2.0.0) railties (>= 3) spinach (>= 0.4) - spork (1.0.0rc2) - sprockets (2.2.2) + spork (1.0.0rc4) + sprockets (2.10.1) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) + sprockets-rails (2.0.1) + actionpack (>= 3.0) + activesupport (>= 3.0) + sprockets (~> 2.8) stamp (0.5.0) state_machine (1.2.0) stringex (1.5.1) - temple (0.6.5) + temple (0.6.7) term-ansicolor (1.2.2) tins (~> 0.8) - test_after_commit (0.2.1) - therubyracer (0.11.4) - libv8 (~> 3.11.8.12) + test_after_commit (0.2.2) + therubyracer (0.12.0) + libv8 (~> 3.16.14.0) ref - thin (1.5.1) + thin (1.6.1) daemons (>= 1.0.9) - eventmachine (>= 0.12.6) + eventmachine (>= 1.0.0) rack (>= 1.0.0) thor (0.18.1) + thread_safe (0.1.3) + atomic tilt (1.4.1) timers (1.1.0) - tinder (1.9.2) + tinder (1.9.3) eventmachine (~> 1.0) faraday (~> 0.8) faraday_middleware (~> 0.9) - hashie (~> 1.0) - json (~> 1.7.5) + hashie (>= 1.0, < 3) + json (~> 1.8.0) mime-types (~> 1.19) - multi_json (~> 1.5) + multi_json (~> 1.7) twitter-stream (~> 0.1) - tins (0.11.0) - treetop (1.4.14) + tins (0.13.1) + treetop (1.4.15) polyglot polyglot (>= 0.3.1) - turbolinks (1.2.0) + turbolinks (2.0.0) coffee-rails twitter-stream (0.1.16) eventmachine (>= 0.12.8) http_parser.rb (~> 0.5.1) simple_oauth (~> 0.1.4) - tzinfo (0.3.37) - uglifier (2.1.1) + tzinfo (0.3.38) + uglifier (2.3.2) execjs (>= 0.3.0) - multi_json (~> 1.0, >= 1.0.2) + json (>= 1.8.0) underscore-rails (1.4.4) unicorn (4.6.3) kgio (~> 2.6) rack raindrops (~> 0.7) - virtus (0.5.5) - backports (~> 3.3) + unicorn-worker-killer (0.4.2) + unicorn (~> 4) + virtus (1.0.1) + axiom-types (~> 0.0.5) + coercible (~> 1.0) descendants_tracker (~> 0.0.1) + equalizer (~> 0.0.7) warden (1.2.3) rack (>= 1.0) - webmock (1.11.0) + webmock (1.16.0) addressable (>= 2.2.7) crack (>= 0.3.2) - websocket (1.0.7) - websocket-driver (0.3.0) + websocket-driver (0.3.1) xpath (2.0.0) nokogiri (~> 1.3) - yajl-ruby (1.1.0) PLATFORMS ruby DEPENDENCIES + actionpack-action_caching + actionpack-page_caching acts-as-taggable-on annotate (~> 2.6.0.beta2) asciidoctor awesome_print better_errors binding_of_caller - bootstrap-sass + bootstrap-sass (~> 3.0) capybara carrierwave - chosen-rails (= 1.0.0) coffee-rails colored coveralls d3_rails (~> 3.1.4) database_cleaner - devise (~> 2.2) + devise (= 3.0.4) + devise-async (= 0.8.0) email_spec + email_validator (~> 1.4.0) enumerize factory_girl_rails ffaker fog (~> 1.3.1) - font-awesome-rails + font-awesome-rails (~> 3.2) foreman - gemoji (~> 1.2.1) - github-linguist - github-markup (~> 0.7.4) - gitlab-gollum-lib (~> 1.0.1) - gitlab-grack (~> 1.0.1) - gitlab-pygments.rb (~> 0.3.2) - gitlab_git (= 2.3.1) + gemoji (~> 1.3.0) + github-markup (~> 0.7.4)! + gitlab-flowdock-git-hook (~> 0.4.2) + gitlab-gollum-lib (~> 1.1.0) + gitlab-grack (~> 2.0.0.pre) + gitlab-linguist (~> 3.0.0) + gitlab_git (~> 5.1.0) gitlab_meta (= 6.0) - gitlab_omniauth-ldap (= 1.0.3) - gon - grape (~> 0.4.1) + gitlab_omniauth-ldap (= 1.0.4) + gon (~> 5.0.0) + grape (~> 0.6.1) grape-entity (~> 0.3.0) growl guard-rspec guard-spinach haml-rails - hipchat (~> 0.9.0) + hipchat (~> 0.14.0) httparty - jasmine - jquery-atwho-rails (= 0.3.0) + jasmine (= 2.0.0.rc5) + jquery-atwho-rails (~> 0.3.3) jquery-rails (= 2.1.3) jquery-turbolinks jquery-ui-rails (= 2.0.2) - kaminari (~> 0.14.1) + kaminari (~> 0.15.1) launchy letter_opener minitest (~> 4.7.0) @@ -603,12 +605,14 @@ DEPENDENCIES omniauth-twitter pg poltergeist (~> 1.4.1) + protected_attributes pry quiet_assets (~> 1.0.1) rack-attack + rack-cors rack-mini-profiler - rails (= 3.2.13) - rails-dev-tweaks + rails (~> 4.0.0) + rails-observers rails_best_practices raphael-rails (~> 2.1.2) rb-fsevent @@ -640,4 +644,5 @@ DEPENDENCIES uglifier underscore-rails (~> 1.4.4) unicorn (~> 4.6.3) + unicorn-worker-killer webmock diff --git a/PROCESS.md b/PROCESS.md index 668cacc870a3e6110d5d49c15f5aae9f2c94b5a5..bf757025c40c62e6ffa6f11d3819c769a76dbe09 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -8,25 +8,25 @@ Below we describe the contributing process to GitLab for two reasons. So that co ### Issue team - Looks for issues without workflow labels and triages issue -- Monitors pull requests -- Closes invalid issues and pull requests with a comment (duplicates, [feature requests](#feature-requests), [fixed in newer version](#issue-fixed-in-newer-version), [issue report for old version](#issue-report-for-old-version), not a problem in GitLab, etc.) +- Monitors merge requests +- Closes invalid issues and merge requests with a comment (duplicates, [feature requests](#feature-requests), [fixed in newer version](#issue-fixed-in-newer-version), [issue report for old version](#issue-report-for-old-version), not a problem in GitLab, etc.) - Assigns appropriate [labels](#how-we-handle-issues) -- Asks for feedback from issue reporter/pull request initiator ([invalid issue reports](#improperly-formatted-issue), [format code](#code-format), etc.) +- Asks for feedback from issue reporter/merge request initiator ([invalid issue reports](#improperly-formatted-issue), [format code](#code-format), etc.) - Asks for feedback from the relevant developer(s) based on the [list of members and their specialities](http://gitlab.org/team/) -- Monitors all issues/pull requests for feedback (but especially ones commented on since automatically watching them): +- Monitors all issues/merge requests for feedback (but especially ones commented on since automatically watching them): - Closes issues with no feedback from the reporter for two weeks -- Closes stale pull requests +- Closes stale merge requests ### Development team -- Responds to issues and pull requests the issue team mentions them in +- Responds to issues and merge requests the issue team mentions them in - Monitors for new issues in _Awaiting developer action/feedback_ with no developer activity (once a week) -- Monitors for new pull requests (at least once a week) -- Manages their work queue by looking at issues and pull requests assigned to them +- Monitors for new merge requests (at least once a week) +- Manages their work queue by looking at issues and merge requests assigned to them - Close fixed issues (via commit messages or manually) - Codes [new features](http://feedback.gitlab.com/forums/176466-general/filters/top)! - Response guidelines -- Be kind to people trying to contribute. Be aware that people can be a non-native or a native English speaker, they might not understand thing or they might be very sensitive to how your word things. Use emoji to express your feelings (heart, star, smile, etc.). Some good tips about giving feedback to pull requests is in the [Thoughtbot code review guide](https://github.com/thoughtbot/guides/tree/master/code-review). +- Be kind to people trying to contribute. Be aware that people can be a non-native or a native English speaker, they might not understand thing or they might be very sensitive to how your word things. Use emoji to express your feelings (heart, star, smile, etc.). Some good tips about giving feedback to merge requests is in the [Thoughtbot code review guide](https://github.com/thoughtbot/guides/tree/master/code-review). ## Priorities of the issue team @@ -45,8 +45,8 @@ Workflow labels are purposely not very detailed since that would be hard to keep - _Awaiting feedback_: Feedback pending from the reporter - _Awaiting confirmation of fix_: The issue should already be solved in **master** (generally you can avoid this workflow item and just close the issue right away) -- _Attached PR_: There is a PR attached and the discussion should happen there - - We need to let issues stay in sync with the PR's. We can do this with a "Closing #XXXX" or "Fixes #XXXX" comment in the PR. We can't close the issue when there is a pull request because sometimes a PR is not good and we just close the PR, then the issue must stay. +- _Attached MR_: There is a MR attached and the discussion should happen there + - We need to let issues stay in sync with the MR's. We can do this with a "Closing #XXXX" or "Fixes #XXXX" comment in the MR. We can't close the issue when there is a merge request because sometimes a MR is not good and we just close the MR, then the issue must stay. - _Awaiting developer action/feedback_: Issue needs to be fixed or clarified by a developer ## Functional labels @@ -59,7 +59,7 @@ If an issue is complex and needs the attention of a specific person, assignment ## Label colors - Light orange `#fef2c0`: workflow labels for issue team members (awaiting feedback, awaiting confirmation of fix) -- Bright orange `#eb6420`: workflow labels for core team members (attached PR, awaiting developer action/feedback) +- Bright orange `#eb6420`: workflow labels for core team members (attached MR, awaiting developer action/feedback) - Light blue `#82C5FF`: functional labels - Green labels `#009800`: issues that can generally be ignored. For example, issues given the following labels normally can be closed immediately: - Feature request (see copy & paste response: [Feature requests](#feature-requests)) @@ -69,19 +69,19 @@ If an issue is complex and needs the attention of a specific person, assignment ### Improperly formatted issue -Thanks for the issue report. Please reformat your issue to conform to the issue tracker guidelines found in our \[contributing guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). +Thanks for the issue report. Please reformat your issue to conform to the issue tracker guidelines found in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). ### Feature requests -Thanks for your interest in GitLab. We don't use the GitHub issue tracker for feature requests. Please use http://feedback.gitlab.com/ for this purpose or create a pull request implementing this feature. Have a look at the \[contribution guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) for more information. +Thanks for your interest in GitLab. We don't use the issue tracker for feature requests. Please use http://feedback.gitlab.com/ for this purpose or create a merge request implementing this feature. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information. ### Issue report for old version -Thanks for the issue report but we only support issues for the latest stable version of GitLab. I'm closing this issue but if you still experience this problem in the latest stable version, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our \[contributing guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). +Thanks for the issue report but we only support issues for the latest stable version of GitLab. I'm closing this issue but if you still experience this problem in the latest stable version, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). ### Support requests and configuration questions -Thanks for your interest in GitLab. We don't use the GitHub issue tracker for support requests and configuration questions. Please use the \[support forum\]\(https://groups.google.com/forum/#!forum/gitlabhq), \[Stack Overflow\]\(http://stackoverflow.com/questions/tagged/gitlab), the unofficial #gitlab IRC channel on Freenode or the http://www.gitlab.com paid services for this purpose. Have a look at the \[contribution guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) for more information. +Thanks for your interest in GitLab. We don't use the issue tracker for support requests and configuration questions. Please use the \[support forum\]\(https://groups.google.com/forum/#!forum/gitlabhq), \[Stack Overflow\]\(http://stackoverflow.com/questions/tagged/gitlab), the unofficial #gitlab IRC channel on Freenode or the http://www.gitlab.com paid services for this purpose. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information. ### Code format @@ -89,17 +89,17 @@ Please use ``` to format console output, logs, and code as it's very hard to rea ### Issue fixed in newer version -Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(http://blog.gitlab.org/). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). +Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(http://blog.gitlab.org/). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). -### Improperly formatted pull request +### Improperly formatted merge request -Thanks for your interest in improving the GitLab codebase! Please update your pull request according to the \[contributing guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#pull-request-guidelines). +Thanks for your interest in improving the GitLab codebase! Please update your merge request according to the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-request-guidelines). ### Inactivity close of an issue -It's been at least 2 weeks (and a new release) since we heard from you. I'm closing this issue but if you still experience this problem, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our \[contributing guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). +It's been at least 2 weeks (and a new release) since we heard from you. I'm closing this issue but if you still experience this problem, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). -### Inactivity close of a pull request +### Inactivity close of a merge request -This pull request has been closed because a request for more information has not been reacted to for more than 2 weeks. If you respond and conform to the pull request guidelines in our \[contributing guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#pull-requests) we will reopen this pull request. +This merge request has been closed because a request for more information has not been reacted to for more than 2 weeks. If you respond and conform to the merge request guidelines in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-requests) we will reopen this merge request. diff --git a/README.md b/README.md index ce0f4a8a1c5ec7d4fb351e266fd5a16716d1d08f..9ac064723bc1373d1d215c6c488b54bf01ce1d95 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## GitLab: self hosted Git management software -![logo](https://raw.github.com/gitlabhq/gitlabhq/master/public/gitlab_logo.png) +![logo](https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/gitlab_logo.png) ![animated-screenshots](https://gist.github.com/fnkr/2f9badd56bfe0ed04ee7/raw/4f48806fbae97f556c2f78d8c2d299c04500cb0d/compiled.gif) @@ -32,7 +32,9 @@ * GitLab.com commercial services: [Homepage](http://www.gitlab.com/) | [Subscription](http://www.gitlab.com/subscription/) | [Consultancy](http://www.gitlab.com/consultancy/) | [GitLab Cloud](http://www.gitlab.com/cloud/) | [Blog](http://blog.gitlab.com/) -* GitLab CI: [Readme](https://github.com/gitlabhq/gitlab-ci/blob/master/README.md) of the GitLab open-source continuous integration server +* [GitLab Enterprise Edition](https://www.gitlab.com/features/) offers additional features that are useful for larger organizations (100+ users). + +* [GitLab CI](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/README.md) is a continuous integration (CI) server that is easy to integrate with GitLab. ### Requirements @@ -46,40 +48,32 @@ ### Installation -#### Official production installation - -* [Installation guide for a production server](doc/install/installation.md) +#### Official installation methods +* [Manual installation guide for a production server](doc/install/installation.md) -#### Official development installation +* [GitLab Chef Cookbook](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/README.md) This cookbook can be used both for development installations and production installations. If you want to [contribute](CONTRIBUTE.md) to GitLab we suggest you follow the [development installation on a virtual machine with Vagrant](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/doc/development.md) instructions to install all testing dependencies. -If you want to contribute, please first read our [Contributing Guidelines](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) and then we suggest you to use the Vagrant virtual machine project to get an environment working with all dependencies. +#### Third party one-click installers -* [Vagrant virtual machine for development](https://github.com/gitlabhq/gitlab-vagrant-vm) +* [Digital Ocean 1-Click Application Install](https://www.digitalocean.com/blog_posts/host-your-git-repositories-in-55-seconds-with-gitlab) Have a new server up in 55 seconds. Digital Ocean uses SSD disks which is great for an IO intensive app such as GitLab. +* [BitNami one-click installers](http://bitnami.com/stack/gitlab) This package contains both GitLab and GitLab CI. It is available as installer, virtual machine or for cloud hosting providers (Amazon Web Services/Azure/etc.). -#### Unofficial production installations +#### Unofficial installation methods -* [GitLab recipes](https://github.com/gitlabhq/gitlab-recipes) repository with unofficial guides for using GitLab with different software (operating systems, webservers, etc.) than the official version. +* [GitLab recipes](https://gitlab.com/gitlab-org/gitlab-recipes/) repository with unofficial guides for using GitLab with different software (operating systems, webservers, etc.) than the official version. * [Installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) public wiki with unofficial guides to install GitLab on different operating systems. -* [BitNami one-click installers](http://bitnami.com/stack/gitlab) - -* [TurnKey Linux virtual appliance](http://www.turnkeylinux.org/gitlab) - - ### New versions and upgrading -Since 2011 GitLab is released on the 22nd of every month. Every new release includes an upgrade guide. - -* [Upgrade guides](doc/update) +Since 2011 GitLab is released on the 22nd of every month. Every new release includes an [upgrade guide](doc/update) and new features are detailed in the [Changelog](CHANGELOG). -* [Changelog](CHANGELOG) +It is recommended to follow a monthly upgrade schedule. Security releases come out when needed. For more information about the release process see the documentation for [monthly](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/release/monthly.md) and [security](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/release/security.md) releases. * Features that will be in the next releases are listed on [the feedback and suggestions 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). - ### 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: @@ -99,7 +93,7 @@ Start it with [Foreman](https://github.com/ddollar/foreman) or start each component separately bundle exec rails s - bundle exec rake sidekiq:start + script/background_jobs start ### Run the tests @@ -110,7 +104,7 @@ or start each component separately * Run all tests - bundle exec rake gitlab:test + bundle exec rake gitlab:test RAILS_ENV=test * [RSpec](http://rspec.info/) unit and functional tests @@ -127,14 +121,17 @@ or start each component separately ### GitLab interfaces -* [GitLab API](doc/api/README.md) +* [GitLab API doc](doc/api/README.md) or see the [GitLab API website](http://api.gitlab.org/) -* [Rake tasks](doc/raketasks) +* [Rake tasks](doc/raketasks) including a [backup and restore procedure](doc/raketasks/backup_restore.md) * [Directory structure](doc/install/structure.md) -* [Databases](doc/install/databases.md) +* [Database installation](doc/install/databases.md) + +* [Markdown specification](doc/markdown/markdown.md) +* [Security guide](doc/security/rack_attack.md) to throttle abusive requests ### Getting help @@ -144,23 +141,23 @@ or start each component separately * [Mailing list](https://groups.google.com/forum/#!forum/gitlabhq) and [Stack Overflow](http://stackoverflow.com/questions/tagged/gitlab) are the best places to ask questions. For example you can use it if you have questions about: permission denied errors, invisible repos, can't clone/pull/push or with web hooks that don't fire. Please search for similar issues before posting your own, there's a good chance somebody else had the same issue you have now and has resolved it. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there to a fix. -* [Unofficial #gitlab IRC on Freenode](http://www.freenode.net/) is another way to get in touch with other GitLab users who may be able to help you. - * [Feedback and suggestions forum](http://feedback.gitlab.com) is the place to propose and discuss new features for GitLab. -* [Contributing guide](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) describes how to submit pull requests and issues. Pull requests and issues not in line with the guidelines in this document will be closed. +* [Contributing guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) describes how to submit merge requests and issues. Pull requests and issues not in line with the guidelines in this document will be closed. * [Support subscription](http://www.gitlab.com/subscription/) connects you to the knowledge of GitLab experts that will resolve your issues and answer your questions. -* [Consultancy](http://www.gitlab.com/consultancy/) allows you hire GitLab experts for installations, upgrades and customizations. +* [Consultancy](http://www.gitlab.com/consultancy/) from the GitLab experts for installations, upgrades and customizations. +* [#gitlab IRC channel](http://www.freenode.net/) on Freenode to get in touch with other GitLab users and get help, it's managed by James Newton (newton), Drew Blessing (dblessing), and Sam Gleske (sag47). + +* [Book](http://www.packtpub.com/gitlab-repository-management/book) written by GitLab enthusiast Jonathan M. Hethey is unofficial but it offers a good overview. -### Getting in touch -* [Core team](https://github.com/gitlabhq?tab=members) +### Getting in touch -* [Contributors](https://github.com/gitlabhq/gitlabhq/graphs/contributors) +* [Core team](http://gitlab.org/team/) -* [Leader](https://github.com/randx) +* [Contributors](http://contributors.gitlab.org/) -* [Contact page](http://gitlab.org/contact/) +* [Community](http://gitlab.org/community/) diff --git a/VERSION b/VERSION index 79e046f49a5ed42b0fedcf1b17a2b1927015a6e0..c07edf251a3afdef78a3f323dd29135c63b49ee6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.2.0.pre +6.6.0.pre diff --git a/app/assets/images/ajax_loader.gif b/app/assets/images/ajax_loader.gif deleted file mode 100644 index 8a97c91987aa2e08dbf52a601e2cd8ee412ef617..0000000000000000000000000000000000000000 Binary files a/app/assets/images/ajax_loader.gif and /dev/null differ diff --git a/app/assets/images/ajax_loader_gray.gif b/app/assets/images/ajax_loader_gray.gif deleted file mode 100644 index af3f618bd0b735578935242a4d5a5e23906b50c8..0000000000000000000000000000000000000000 Binary files a/app/assets/images/ajax_loader_gray.gif and /dev/null differ diff --git a/app/assets/images/ajax_loader_tree.gif b/app/assets/images/ajax_loader_tree.gif deleted file mode 100644 index 99d5a0f37f3e0fda2d0564e5977e3afac9bc08eb..0000000000000000000000000000000000000000 Binary files a/app/assets/images/ajax_loader_tree.gif and /dev/null differ diff --git a/app/assets/images/bg-header.png b/app/assets/images/bg-header.png index 8759ca589fccdfcddd6ec42ba4474257da26c855..9ecdaf4e2d50de747f5249860f10d6debc33cf4e 100644 Binary files a/app/assets/images/bg-header.png and b/app/assets/images/bg-header.png differ diff --git a/app/assets/images/dark-scheme-preview.png b/app/assets/images/dark-scheme-preview.png index 055a9069b63ed88b84acceb366d3eeddd892bb9c..6dac6cd8ca138921421a697b50181abbc357a517 100644 Binary files a/app/assets/images/dark-scheme-preview.png and b/app/assets/images/dark-scheme-preview.png differ diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico index 057f74ac7ab0192514a2dc21bfc955e5700fb65d..bfb74960c480e6cb14f1d38437303af6b375ccaf 100644 Binary files a/app/assets/images/favicon.ico and b/app/assets/images/favicon.ico differ diff --git a/app/assets/images/file_txt.png b/app/assets/images/file_txt.png index f3638cb4e1e109a4989fe8b2895d8c7071ec6bf2..b3230b5add08d624539197e94100885217dffca2 100644 Binary files a/app/assets/images/file_txt.png and b/app/assets/images/file_txt.png differ diff --git a/app/assets/images/icon-search.png b/app/assets/images/icon-search.png index 7632915cacc58de452ec31885c922714a66db48f..084b89e3a7cee5787bc3fae15596dab09c547cd7 100644 Binary files a/app/assets/images/icon-search.png and b/app/assets/images/icon-search.png differ diff --git a/app/assets/images/images.png b/app/assets/images/images.png index 973d3bdd39d36e963223a1281ab44a5af453fe82..da91f6b1f4c31422a3890c3ad8a38bb418a1b81c 100644 Binary files a/app/assets/images/images.png and b/app/assets/images/images.png differ diff --git a/app/assets/images/logo-black.png b/app/assets/images/logo-black.png index 6567f2e546364c898056691dbbe0a54bb427b362..4a96572d570108366da2cf5aa8213f69b591a2a3 100644 Binary files a/app/assets/images/logo-black.png and b/app/assets/images/logo-black.png differ diff --git a/app/assets/images/logo-white.png b/app/assets/images/logo-white.png index a63fb1c9c0a9fd630b394ecd40cd45e6c7f22f6f..bc2ef601a538d69ef99d5bdafa605e63f902e8e4 100644 Binary files a/app/assets/images/logo-white.png and b/app/assets/images/logo-white.png differ diff --git a/app/assets/images/monokai-scheme-preview.png b/app/assets/images/monokai-scheme-preview.png index 9477941778e53699c7ef98cd5e149bd5f6ee3734..3aeed886a02bcdc0d38467be744887a437fb99c1 100644 Binary files a/app/assets/images/monokai-scheme-preview.png and b/app/assets/images/monokai-scheme-preview.png differ diff --git a/app/assets/images/no_avatar.png b/app/assets/images/no_avatar.png index 752d26adba7aa91b8e32685cd5ded666da5c9a69..dac3ab1bb890ee2b583ac5eaa060d972b6e35867 100644 Binary files a/app/assets/images/no_avatar.png and b/app/assets/images/no_avatar.png differ diff --git a/app/assets/images/no_group_avatar.png b/app/assets/images/no_group_avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..a97d4515982bce6cb4973a8433b9ae0363e43d46 Binary files /dev/null and b/app/assets/images/no_group_avatar.png differ diff --git a/app/assets/images/onion_skin_sprites.gif b/app/assets/images/onion_skin_sprites.gif index 85d20260edcad0f13825b8b70d930133c9e54beb..337aa1bfb632ff492b0a24ffe652ca5ab46f6ffa 100644 Binary files a/app/assets/images/onion_skin_sprites.gif and b/app/assets/images/onion_skin_sprites.gif differ diff --git a/app/assets/images/solarized-dark-scheme-preview.png b/app/assets/images/solarized-dark-scheme-preview.png index 728964bc4c8b92e2cd44ea5afda7c858b24ece42..ae092ab52139e10183efd5c175768a8e355f6678 100644 Binary files a/app/assets/images/solarized-dark-scheme-preview.png and b/app/assets/images/solarized-dark-scheme-preview.png differ diff --git a/app/assets/images/swipemode_sprites.gif b/app/assets/images/swipemode_sprites.gif index 327b3c31ffd3547bbc0e4b9fa7c81c4bcfdfc59e..b010b4e4482760d5e144d93c2b6c76fa4f981438 100644 Binary files a/app/assets/images/swipemode_sprites.gif and b/app/assets/images/swipemode_sprites.gif differ diff --git a/app/assets/images/switch_icon.png b/app/assets/images/switch_icon.png index 7c11f20659367c4f3371c27d9f1b2a37cf89dab9..6b8bde41bc95c15df2f7ce6d18330cce1181a76c 100644 Binary files a/app/assets/images/switch_icon.png and b/app/assets/images/switch_icon.png differ diff --git a/app/assets/images/trans_bg.gif b/app/assets/images/trans_bg.gif index c7e98e044f578204f9c1ebdefcb05f4b2baaea32..5f6ed04a43c97debddc5c6be5697ba74d0504adf 100644 Binary files a/app/assets/images/trans_bg.gif and b/app/assets/images/trans_bg.gif differ diff --git a/app/assets/images/white-scheme-preview.png b/app/assets/images/white-scheme-preview.png index 67eb87630447df31de8ec2bb21d77e653c63a331..d1866e0015803d0180553d350dffa322b5d9f958 100644 Binary files a/app/assets/images/white-scheme-preview.png and b/app/assets/images/white-scheme-preview.png differ diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee index 6230fe7f93f53351fdaecdf2e90f7c9051146831..6634bb6cc34c1e39eebd7732aefa7a94dee12a8a 100644 --- a/app/assets/javascripts/admin.js.coffee +++ b/app/assets/javascripts/admin.js.coffee @@ -8,6 +8,23 @@ class Admin else elems.removeAttr 'disabled' + $('body').on 'click', '.js-toggle-colors-link', (e) -> + e.preventDefault() + $('.js-toggle-colors-link').hide() + $('.js-toggle-colors-container').show() + + $('input#broadcast_message_color').on 'input', -> + previewColor = $('input#broadcast_message_color').val() + $('div.broadcast-message-preview').css('background-color', previewColor) + + $('input#broadcast_message_font').on 'input', -> + previewColor = $('input#broadcast_message_font').val() + $('div.broadcast-message-preview').css('color', previewColor) + + $('textarea#broadcast_message_message').on 'input', -> + previewMessage = $('textarea#broadcast_message_message').val() + $('div.broadcast-message-preview span').text(previewMessage) + $('.log-tabs a').click (e) -> e.preventDefault() $(this).tab('show') diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee index a36d944cbcb6f9d9f31372abe516b529a6c5a1ad..5f4a38ebbd00735d88cb1be2f680871bdf762611 100644 --- a/app/assets/javascripts/api.js.coffee +++ b/app/assets/javascripts/api.js.coffee @@ -2,6 +2,7 @@ users_path: "/api/:version/users.json" user_path: "/api/:version/users/:id.json" notes_path: "/api/:version/projects/:id/notes.json" + namespaces_path: "/api/:version/namespaces.json" # Get 20 (depends on api) recent notes # and sort the ascending from oldest to newest @@ -49,6 +50,20 @@ ).done (users) -> callback(users) + # Return namespaces list. Filtered by query + namespaces: (query, callback) -> + url = Api.buildUrl(Api.namespaces_path) + + $.ajax( + url: url + data: + private_token: gon.api_token + search: query + per_page: 20 + dataType: "json" + ).done (namespaces) -> + callback(namespaces) + buildUrl: (url) -> url = gon.relative_url_root + url if gon.relative_url_root? return url.replace(':version', gon.api_version) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 0767b82032d41562ad3aa301069716b10bf1a56e..9ff116a6644334f9ae280f2647b7eed9f2609562 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -19,12 +19,12 @@ //= require jquery.turbolinks //= require bootstrap //= require modernizr -//= require chosen-jquery //= require select2 //= require raphael //= require g.raphael-min //= require g.bar-min //= require branch-graph +//= require highlightjs.min //= require ace-src-noconflict/ace //= require_tree . //= require d3 diff --git a/app/assets/javascripts/behaviors/toggler_behavior.coffee b/app/assets/javascripts/behaviors/toggler_behavior.coffee index 7e438c51c1cd8e424065e07ae83f97e4f153846f..5afb656e696285d0bae0113b41af27f549baa4e3 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.coffee +++ b/app/assets/javascripts/behaviors/toggler_behavior.coffee @@ -3,7 +3,7 @@ $ -> container = $(@).closest(".js-toggler-container") container.toggleClass("on") - + $("body").on "click", ".js-toggle-visibility-link", (e) -> $(@).find('i'). toggleClass('icon-chevron-down'). @@ -11,7 +11,7 @@ $ -> container = $(".js-toggle-visibility-container") container.toggleClass("hide") e.preventDefault() - + $("body").on "click", ".js-toggle-button", (e) -> $(@).closest(".js-toggle-container").find(".js-toggle-content").toggle() e.preventDefault() diff --git a/app/assets/javascripts/blob.js.coffee b/app/assets/javascripts/blob.js.coffee index 03e280f89769471bac2ece86eaedf114fe9c76fb..584f6faea161c2e980a9a66327652e619b222246 100644 --- a/app/assets/javascripts/blob.js.coffee +++ b/app/assets/javascripts/blob.js.coffee @@ -1,24 +1,76 @@ class BlobView constructor: -> + # handle multi-line select + handleMultiSelect = (e) -> + [ first_line, last_line ] = parseSelectedLines() + [ line_number ] = parseSelectedLines($(this).attr("id")) + hash = "L#{line_number}" + + if e.shiftKey and not isNaN(first_line) and not isNaN(line_number) + if line_number < first_line + last_line = first_line + first_line = line_number + else + last_line = line_number + + hash = if first_line == last_line then "L#{first_line}" else "L#{first_line}-#{last_line}" + + setHash(hash) + e.preventDefault() + # See if there are lines selected # "#L12" and "#L34-56" supported - highlightBlobLines = -> - if window.location.hash isnt "" - matches = window.location.hash.match(/\#L(\d+)(\-(\d+))?/) + highlightBlobLines = (e) -> + [ first_line, last_line ] = parseSelectedLines() + + unless isNaN first_line + $("#tree-content-holder .highlight .line").removeClass("hll") + $("#LC#{line}").addClass("hll") for line in [first_line..last_line] + $("#L#{first_line}").ScrollTo() unless e? + + # parse selected lines from hash + # always return first and last line (initialized to NaN) + parseSelectedLines = (str) -> + first_line = NaN + last_line = NaN + hash = str || window.location.hash + + if hash isnt "" + matches = hash.match(/\#?L(\d+)(\-(\d+))?/) first_line = parseInt(matches?[1]) last_line = parseInt(matches?[3]) + last_line = first_line if isNaN(last_line) + + [ first_line, last_line ] + + setHash = (hash) -> + hash = hash.replace(/^\#/, "") + nodes = $("#" + hash) + # if any nodes are using this id, they must be temporarily changed + # also, add a temporary div at the top of the screen to prevent scrolling + if nodes.length > 0 + scroll_top = $(document).scrollTop() + nodes.attr("id", "") + tmp = $("<div></div>") + .css({ position: "absolute", visibility: "hidden", top: scroll_top + "px" }) + .attr("id", hash) + .appendTo(document.body) + + window.location.hash = hash + + # restore the nodes + if nodes.length > 0 + tmp.remove() + nodes.attr("id", hash) - unless isNaN first_line - last_line = first_line if isNaN(last_line) - $("#tree-content-holder .highlight .line").removeClass("hll") - $("#LC#{line}").addClass("hll") for line in [first_line..last_line] - $("#L#{first_line}").ScrollTo() + # initialize multi-line select + $("#tree-content-holder .line-numbers a[id^=L]").on("click", handleMultiSelect) # Highlight the correct lines on load highlightBlobLines() # Highlight the correct lines when the hash part of the URL changes - $(window).on 'hashchange', highlightBlobLines + $(window).on("hashchange", highlightBlobLines) @BlobView = BlobView diff --git a/app/assets/javascripts/branch-graph.js.coffee b/app/assets/javascripts/branch-graph.js.coffee index 318538509a5a087600c7e8890e8cf923566072e6..dd09ee51fe0dc8a903de4b256a54441b34c4df77 100644 --- a/app/assets/javascripts/branch-graph.js.coffee +++ b/app/assets/javascripts/branch-graph.js.coffee @@ -194,11 +194,14 @@ class BranchGraph fill: @colors[commit.space] stroke: "none" ) - r.rect(@offsetX + @unitSpace * @mspace + 10, y - 10, 20, 20).attr( - fill: "url(#{commit.author.icon})" + + avatar_box_x = @offsetX + @unitSpace * @mspace + 10 + avatar_box_y = y - 10 + r.rect(avatar_box_x, avatar_box_y, 20, 20).attr( stroke: @colors[commit.space] "stroke-width": 2 ) + r.image(commit.author.icon, avatar_box_x, avatar_box_y, 20, 20) r.text(@offsetX + @unitSpace * @mspace + 35, y, commit.message.split("\n")[0]).attr( "text-anchor": "start" font: "14px Monaco, monospace" diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee index de4c06a2728ed4d97a0ddb6049eee053a2c1c130..9c004c997edf698681516ce185b96ae16c119582 100644 --- a/app/assets/javascripts/commits.js.coffee +++ b/app/assets/javascripts/commits.js.coffee @@ -4,13 +4,13 @@ class CommitsList limit: 0 offset: 0 @disable = false - + @showProgress: -> $('.loading').show() - + @hideProgress: -> $('.loading').hide() - + @init: (ref, limit) -> $(".day-commits-table li.commit").live 'click', (event) -> if event.target.nodeName != "A" @@ -21,7 +21,7 @@ class CommitsList @data.ref = ref @data.limit = limit @data.offset = limit - + this.initLoadMore() this.showProgress() @@ -32,7 +32,9 @@ class CommitsList url: location.href data: @data complete: this.hideProgress - dataType: "script" + success: (data) -> + CommitsList.append(data.count, data.html) + dataType: "json" @append: (count, html) -> $("#commits-list").append(html) @@ -40,7 +42,7 @@ class CommitsList @data.offset += count else @disable = true - + @initLoadMore: -> $(document).unbind('scroll') $(document).endlessScroll diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index e264e281309eea1c2b01f38ef26eb0274dc968a3..9afb5974858df16ec9a0ccf46abea27c30439ad6 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -4,6 +4,7 @@ $ -> class Dispatcher constructor: () -> @initSearch() + @initHighlight() @initPageScripts() initPageScripts: -> @@ -47,5 +48,16 @@ class Dispatcher initSearch: -> - autocomplete_json = $('.search-autocomplete-json').data('autocomplete-opts') - new SearchAutocomplete(autocomplete_json) + opts = $('.search-autocomplete-opts') + path = opts.data('autocomplete-path') + project_id = opts.data('autocomplete-project-id') + project_ref = opts.data('autocomplete-project-ref') + + new SearchAutocomplete(path, project_id, project_ref) + + initHighlight: -> + $('.highlight pre code').each (i, e) -> + hljs.highlightBlock(e) + $(e).html($.map($(e).html().split("\n"), (line, i) -> + "<div class='line' id='LC" + (i + 1) + "'>" + line + "</div>" + ).join("\n")) diff --git a/app/assets/javascripts/groups.js.coffee b/app/assets/javascripts/groups.js.coffee index c0ffccd8f70cd9f119775169548edad20f31bf09..7850eb14e74b2b6418695fd80a0ebc24fe4e6c6d 100644 --- a/app/assets/javascripts/groups.js.coffee +++ b/app/assets/javascripts/groups.js.coffee @@ -4,3 +4,14 @@ class GroupMembers $(this).fadeOut() @GroupMembers = GroupMembers + +$ -> + # avatar + $('.js-choose-group-avatar-button').bind "click", -> + form = $(this).closest("form") + form.find(".js-group-avatar-input").click() + + $('.js-group-avatar-input').bind "change", -> + form = $(this).closest("form") + filename = $(this).val().replace(/^.*[\\\/]/, '') + form.find(".js-avatar-filename").text(filename) \ No newline at end of file diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee index 67d9498c50a6a7f4938599ffd7116363bbae98b2..6c239c66c0a6be8428ecef40b9f1c9f53492d651 100644 --- a/app/assets/javascripts/issues.js.coffee +++ b/app/assets/javascripts/issues.js.coffee @@ -22,19 +22,17 @@ backgroundColor: '#DDD' opacity: .4 ) - + reload: -> Issues.initSelects() Issues.initChecks() $('#filter_issue_search').val($('#issue_search').val()) initSelects: -> - $("#update_status").chosen() - $("#update_assignee_id").chosen() - $("#update_milestone_id").chosen() - $("#label_name").chosen() - $("#assignee_id").chosen() - $("#milestone_id").chosen() + $("select#update_status").select2(width: 'resolve', dropdownAutoWidth: true) + $("select#update_assignee_id").select2(width: 'resolve', dropdownAutoWidth: true) + $("select#update_milestone_id").select2(width: 'resolve', dropdownAutoWidth: true) + $("select#label_name").select2(width: 'resolve', dropdownAutoWidth: true) $("#milestone_id, #assignee_id, #label_name").on "change", -> $(this).closest("form").submit() @@ -54,7 +52,16 @@ unless terms is last_terms last_terms = terms if terms.length >= 2 or terms.length is 0 - form.submit() + $.ajax + type: "GET" + url: location.href + data: "issue_search=" + terms + complete: -> + $(".loading").hide() + success: (data) -> + $('.issues-holder').html(data.html) + Issues.reload() + dataType: "json" checkChanged: -> checked_issues = $(".selected_issue:checked") @@ -70,3 +77,9 @@ $("#update_issues_ids").val [] $(".issues_bulk_update").hide() $(".issues-filters").show() + +$ -> + $('.edit-issue.inline-update input[type="submit"]').hide(); + $("body").on "change", ".edit-issue.inline-update select", -> + $(this).submit() + diff --git a/app/assets/javascripts/main.js.coffee b/app/assets/javascripts/main.js.coffee index 011244a5868643d5c5409386d9c17c1109c5cb31..9cf4dba815bc6ac36d3b2d7268a09be08355bc63 100644 --- a/app/assets/javascripts/main.js.coffee +++ b/app/assets/javascripts/main.js.coffee @@ -1,6 +1,3 @@ -window.updatePage = (data) -> - $.ajax({type: "GET", url: location.href, data: data, dataType: "script"}) - window.slugify = (text) -> text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() @@ -56,7 +53,7 @@ window.unbindEvents = -> document.addEventListener("page:fetch", startSpinner) document.addEventListener("page:fetch", unbindEvents) -document.addEventListener("page:receive", stopSpinner) +document.addEventListener("page:change", stopSpinner) $ -> # Click a .one_click_select field, select the contents @@ -70,8 +67,8 @@ $ -> $('.appear-data').fadeIn() e.preventDefault() - # Initialize chosen selects - $('select.chosen').chosen() + # Initialize select2 selects + $('select.select2').select2(width: 'resolve', dropdownAutoWidth: true) # Initialize tooltips $('.has_tooltip').tooltip() @@ -84,6 +81,7 @@ $ -> $(@).parents('form').submit() $("abbr.timeago").timeago() + $('.js-timeago').timeago() # Flash if (flash = $(".flash-container")).length > 0 @@ -123,13 +121,11 @@ $ -> $(@).next('table').show() $(@).remove() -(($) -> - _chosen = $.fn.chosen - $.fn.extend chosen: (options) -> - default_options = search_contains: "true" - $.extend default_options, options - _chosen.apply @, [default_options] + $(".content").on "click", ".js-details-expand", -> + $(@).next('.js-details-contain').removeClass("hide") + $(@).remove() +(($) -> # Disable an element and add the 'disabled' Bootstrap class $.fn.extend disable: -> $(@).attr('disabled', 'disabled').addClass('disabled') diff --git a/app/assets/javascripts/merge_requests.js.coffee b/app/assets/javascripts/merge_requests.js.coffee index 5400bc5c1ad08199d92e0007f2ec785307593bbe..ff843c68d6859d90525d22ddda5b6535cadc8356 100644 --- a/app/assets/javascripts/merge_requests.js.coffee +++ b/app/assets/javascripts/merge_requests.js.coffee @@ -2,8 +2,8 @@ # * Filter merge requests # @merge_requestsPage = -> - $('#assignee_id').chosen() - $('#milestone_id').chosen() + $('#assignee_id').select2() + $('#milestone_id').select2() $('#milestone_id, #assignee_id').on 'change', -> $(this).closest('form').submit() @@ -21,9 +21,11 @@ class MergeRequest this.initMergeWidget() this.$('.show-all-commits').on 'click', => this.showAllCommits() - + modal = $('#modal_merge_info').modal(show: false) + disableButtonIfEmptyField '#merge_commit_message', '.accept_merge_request' + # Local jQuery finder $: (selector) -> this.$el.find(selector) @@ -83,12 +85,12 @@ class MergeRequest url: this.$('.nav-tabs .diffs-tab a').attr('href') beforeSend: => this.$('.status').addClass 'loading' - complete: => @diffs_loaded = true this.$('.status').removeClass 'loading' - - dataType: 'script' + success: (data) => + this.$(".diffs").html(data.html) + dataType: 'json' showAllCommits: -> this.$('.first-commits').remove() diff --git a/app/assets/javascripts/namespace_select.js.coffee b/app/assets/javascripts/namespace_select.js.coffee new file mode 100644 index 0000000000000000000000000000000000000000..00d135d1449d860aaa6fbefa88bc8dded9823f3d --- /dev/null +++ b/app/assets/javascripts/namespace_select.js.coffee @@ -0,0 +1,24 @@ +$ -> + namespaceFormatResult = (namespace) -> + markup = "<div class='namespace-result'>" + markup += "<span class='namespace-kind'>" + namespace.kind + "</span>" + markup += "<span class='namespace-path'>" + namespace.path + "</span>" + markup += "</div>" + markup + + formatSelection = (namespace) -> + namespace.kind + ": " + namespace.path + + $('.ajax-namespace-select').each (i, select) -> + $(select).select2 + placeholder: "Search for namespace" + multiple: $(select).hasClass('multiselect') + minimumInputLength: 0 + query: (query) -> + Api.namespaces query.term, (namespaces) -> + data = { results: namespaces } + query.callback(data) + + dropdownCssClass: "ajax-namespace-dropdown" + formatResult: namespaceFormatResult + formatSelection: formatSelection diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js deleted file mode 100644 index 5225623c1f0ce7e273a04bea18eb71a94ffe09ed..0000000000000000000000000000000000000000 --- a/app/assets/javascripts/notes.js +++ /dev/null @@ -1,580 +0,0 @@ -var NoteList = { - id: null, - notes_path: null, - target_params: null, - target_id: 0, - target_type: null, - - init: function(tid, tt, path) { - NoteList.notes_path = path + ".js"; - NoteList.target_id = tid; - NoteList.target_type = tt; - NoteList.target_params = "target_type=" + NoteList.target_type + "&target_id=" + NoteList.target_id; - - NoteList.setupMainTargetNoteForm(); - - // get initial set of notes - NoteList.getContent(); - - // Unbind events to prevent firing twice - $(document).off("click", ".js-add-diff-note-button"); - $(document).off("click", ".js-discussion-reply-button"); - $(document).off("click", ".js-note-preview-button"); - $(document).off("click", ".js-note-attachment-input"); - $(document).off("click", ".js-close-discussion-note-form"); - $(document).off("click", ".js-note-delete"); - $(document).off("click", ".js-note-edit"); - $(document).off("click", ".js-note-edit-cancel"); - $(document).off("click", ".js-note-attachment-delete"); - $(document).off("click", ".js-choose-note-attachment-button"); - $(document).off("click", ".js-show-outdated-discussion"); - - $(document).off("ajax:complete", ".js-main-target-form"); - - - // add a new diff note - $(document).on("click", - ".js-add-diff-note-button", - NoteList.addDiffNote); - - // reply to diff/discussion notes - $(document).on("click", - ".js-discussion-reply-button", - NoteList.replyToDiscussionNote); - - // setup note preview - $(document).on("click", - ".js-note-preview-button", - NoteList.previewNote); - - // update the file name when an attachment is selected - $(document).on("change", - ".js-note-attachment-input", - NoteList.updateFormAttachment); - - // hide diff note form - $(document).on("click", - ".js-close-discussion-note-form", - NoteList.removeDiscussionNoteForm); - - // remove a note (in general) - $(document).on("click", - ".js-note-delete", - NoteList.removeNote); - - // show the edit note form - $(document).on("click", - ".js-note-edit", - NoteList.showEditNoteForm); - - // cancel note editing - $(document).on("click", - ".note-edit-cancel", - NoteList.cancelNoteEdit); - - // delete note attachment - $(document).on("click", - ".js-note-attachment-delete", - NoteList.deleteNoteAttachment); - - // update the note after editing - $(document).on("ajax:complete", - "form.edit_note", - NoteList.updateNote); - - // reset main target form after submit - $(document).on("ajax:complete", - ".js-main-target-form", - NoteList.resetMainTargetForm); - - - $(document).on("click", - ".js-choose-note-attachment-button", - NoteList.chooseNoteAttachment); - - $(document).on("click", - ".js-show-outdated-discussion", - function(e) { $(this).next('.outdated-discussion').show(); e.preventDefault() }); - }, - - - /** - * When clicking on buttons - */ - - /** - * Called when clicking on the "add a comment" button on the side of a diff line. - * - * Inserts a temporary row for the form below the line. - * Sets up the form and shows it. - */ - addDiffNote: function(e) { - e.preventDefault(); - - // find the form - var form = $(".js-new-note-form"); - var row = $(this).closest("tr"); - var nextRow = row.next(); - - // does it already have notes? - if (nextRow.is(".notes_holder")) { - $.proxy(NoteList.replyToDiscussionNote, - nextRow.find(".js-discussion-reply-button") - ).call(); - } else { - // add a notes row and insert the form - row.after('<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"></td></tr>'); - form.clone().appendTo(row.next().find(".notes_content")); - - // show the form - NoteList.setupDiscussionNoteForm($(this), row.next().find("form")); - } - }, - - /** - * Called when clicking the "Choose File" button. - * - * Opens the file selection dialog. - */ - chooseNoteAttachment: function() { - var form = $(this).closest("form"); - - form.find(".js-note-attachment-input").click(); - }, - - /** - * Shows the note preview. - * - * Lets the server render GFM into Html and displays it. - * - * Note: uses the Toggler behavior to toggle preview/edit views/buttons - */ - previewNote: function(e) { - e.preventDefault(); - - var form = $(this).closest("form"); - var preview = form.find('.js-note-preview'); - var noteText = form.find('.js-note-text').val(); - - if(noteText.trim().length === 0) { - preview.text('Nothing to preview.'); - } else { - preview.text('Loading...'); - $.post($(this).data('url'), {note: noteText}) - .success(function(previewData) { - preview.html(previewData); - }); - } - }, - - /** - * Called in response to "cancel" on a diff note form. - * - * Shows the reply button again. - * Removes the form and if necessary it's temporary row. - */ - removeDiscussionNoteForm: function() { - var form = $(this).closest("form"); - var row = form.closest("tr"); - - // show the reply button (will only work for replies) - form.prev(".js-discussion-reply-button").show(); - - if (row.is(".js-temp-notes-holder")) { - // remove temporary row for diff lines - row.remove(); - } else { - // only remove the form - form.remove(); - } - }, - - /** - * Called in response to deleting a note of any kind. - * - * Removes the actual note from view. - * Removes the whole discussion if the last note is being removed. - */ - removeNote: function() { - var note = $(this).closest(".note"); - var notes = note.closest(".notes"); - - // check if this is the last note for this line - if (notes.find(".note").length === 1) { - // for discussions - notes.closest(".discussion").remove(); - - // for diff lines - notes.closest("tr").remove(); - } - - note.remove(); - NoteList.updateVotes(); - }, - - /** - * Called in response to clicking the edit note link - * - * Replaces the note text with the note edit form - * Adds a hidden div with the original content of the note to fill the edit note form with - * if the user cancels - */ - showEditNoteForm: function(e) { - e.preventDefault(); - var note = $(this).closest(".note"); - note.find(".note-text").hide(); - - // Show the attachment delete link - note.find(".js-note-attachment-delete").show(); - - GitLab.GfmAutoComplete.setup(); - - var form = note.find(".note-edit-form"); - form.show(); - - var textarea = form.find("textarea"); - var p = $("<p></p>").text(textarea.val()); - var hidden_div = $('<div class="note-original-content"></div>').append(p); - form.append(hidden_div); - hidden_div.hide(); - textarea.focus(); - }, - - /** - * Called in response to clicking the cancel button when editing a note - * - * Resets and hides the note editing form - */ - cancelNoteEdit: function(e) { - e.preventDefault(); - var note = $(this).closest(".note"); - NoteList.resetNoteEditing(note); - }, - - - /** - * Called in response to clicking the delete attachment link - * - * Removes the attachment wrapper view, including image tag if it exists - * Resets the note editing form - */ - deleteNoteAttachment: function() { - var note = $(this).closest(".note"); - note.find(".note-attachment").remove(); - NoteList.resetNoteEditing(note); - NoteList.rewriteTimestamp(note.find(".note-last-update")); - }, - - - /** - * Called when clicking on the "reply" button for a diff line. - * - * Shows the note form below the notes. - */ - replyToDiscussionNote: function() { - // find the form - var form = $(".js-new-note-form"); - - // hide reply button - $(this).hide(); - // insert the form after the button - form.clone().insertAfter($(this)); - - // show the form - NoteList.setupDiscussionNoteForm($(this), $(this).next("form")); - }, - - - /** - * Helper for inserting and setting up note forms. - */ - - - /** - * Called in response to creating a note failing validation. - * - * Adds the rendered errors to the respective form. - * If "discussionId" is null or undefined, the main target form is assumed. - */ - errorsOnForm: function(errorsHtml, discussionId) { - // find the form - if (discussionId) { - var form = $("form[rel='"+discussionId+"']"); - } else { - var form = $(".js-main-target-form"); - } - - form.find(".js-errors").remove(); - form.prepend(errorsHtml); - - form.find(".js-note-text").focus(); - }, - - - /** - * Shows the diff/discussion form and does some setup on it. - * - * Sets some hidden fields in the form. - * - * Note: dataHolder must have the "discussionId", "lineCode", "noteableType" - * and "noteableId" data attributes set. - */ - setupDiscussionNoteForm: function(dataHolder, form) { - // setup note target - form.attr("rel", dataHolder.data("discussionId")); - form.find("#note_commit_id").val(dataHolder.data("commitId")); - form.find("#note_line_code").val(dataHolder.data("lineCode")); - form.find("#note_noteable_type").val(dataHolder.data("noteableType")); - form.find("#note_noteable_id").val(dataHolder.data("noteableId")); - - NoteList.setupNoteForm(form); - - form.find(".js-note-text").focus(); - }, - - /** - * Shows the main form and does some setup on it. - * - * Sets some hidden fields in the form. - */ - setupMainTargetNoteForm: function() { - // find the form - var form = $(".js-new-note-form"); - // insert the form after the button - form.clone().replaceAll($(".js-main-target-form")); - - form = form.prev("form"); - - // show the form - NoteList.setupNoteForm(form); - - // fix classes - form.removeClass("js-new-note-form"); - form.addClass("js-main-target-form"); - - // remove unnecessary fields and buttons - form.find("#note_line_code").remove(); - form.find(".js-close-discussion-note-form").remove(); - }, - - /** - * General note form setup. - * - * * deactivates the submit button when text is empty - * * hides the preview button when text is empty - * * setup GFM auto complete - * * show the form - */ - setupNoteForm: function(form) { - disableButtonIfEmptyField(form.find(".js-note-text"), form.find(".js-comment-button")); - - form.removeClass("js-new-note-form"); - - // setup preview buttons - form.find(".js-note-edit-button, .js-note-preview-button") - .tooltip({ placement: 'left' }); - - previewButton = form.find(".js-note-preview-button"); - form.find(".js-note-text").on("input", function() { - if ($(this).val().trim() !== "") { - previewButton.removeClass("turn-off").addClass("turn-on"); - } else { - previewButton.removeClass("turn-on").addClass("turn-off"); - } - }); - - // remove notify commit author checkbox for non-commit notes - if (form.find("#note_noteable_type").val() !== "Commit") { - form.find(".js-notify-commit-author").remove(); - } - - GitLab.GfmAutoComplete.setup(); - - form.show(); - }, - - - /** - * Handle loading the initial set of notes. - * And set up loading more notes when scrolling to the bottom of the page. - */ - - - /** - * Gets an initial set of notes. - */ - getContent: function() { - $.ajax({ - url: NoteList.notes_path, - data: NoteList.target_params, - complete: function(){ $('.js-notes-busy').removeClass("loading")}, - beforeSend: function() { $('.js-notes-busy').addClass("loading") }, - dataType: "script" - }); - }, - - /** - * Called in response to getContent(). - * Replaces the content of #notes-list with the given html. - */ - setContent: function(newNoteIds, html) { - $("#notes-list").html(html); - }, - - - /** - * Adds a single common note to #notes-list. - */ - appendNewNote: function(id, html) { - $("#notes-list").append(html); - NoteList.updateVotes(); - }, - - /** - * Adds a single discussion note to #notes-list. - * - * Also removes the corresponding form. - */ - appendNewDiscussionNote: function(discussionId, diffRowHtml, noteHtml) { - var form = $("form[rel='"+discussionId+"']"); - var row = form.closest("tr"); - - // is this the first note of discussion? - if (row.is(".js-temp-notes-holder")) { - // insert the note and the reply button after the temp row - row.after(diffRowHtml); - // remove the note (will be added again below) - row.next().find(".note").remove(); - } - - // append new note to all matching discussions - $(".notes[rel='"+discussionId+"']").append(noteHtml); - - // cleanup after successfully creating a diff/discussion note - $.proxy(NoteList.removeDiscussionNoteForm, form).call(); - }, - - /** - * Called in response the main target form has been successfully submitted. - * - * Removes any errors. - * Resets text and preview. - * Resets buttons. - */ - resetMainTargetForm: function(){ - var form = $(this); - - // remove validation errors - form.find(".js-errors").remove(); - - // reset text and preview - var previewContainer = form.find(".js-toggler-container.note_text_and_preview"); - if (previewContainer.is(".on")) { - previewContainer.removeClass("on"); - } - form.find(".js-note-text").val("").trigger("input"); - }, - - /** - * Called after an attachment file has been selected. - * - * Updates the file name for the selected attachment. - */ - updateFormAttachment: function() { - var form = $(this).closest("form"); - - // get only the basename - var filename = $(this).val().replace(/^.*[\\\/]/, ''); - - form.find(".js-attachment-filename").text(filename); - }, - - /** - * Recalculates the votes and updates them (if they are displayed at all). - * - * Assumes all relevant notes are displayed (i.e. there are no more notes to - * load via getMore()). - * Might produce inaccurate results when not all notes have been loaded and a - * recalculation is triggered (e.g. when deleting a note). - */ - updateVotes: function() { - var votes = $("#votes .votes"); - var notes = $("#notes-list .note .vote"); - - // only update if there is a vote display - if (votes.size()) { - var upvotes = notes.filter(".upvote").size(); - var downvotes = notes.filter(".downvote").size(); - var votesCount = upvotes + downvotes; - var upvotesPercent = votesCount ? (100.0 / votesCount * upvotes) : 0; - var downvotesPercent = votesCount ? (100.0 - upvotesPercent) : 0; - - // change vote bar lengths - votes.find(".bar-success").css("width", upvotesPercent+"%"); - votes.find(".bar-danger").css("width", downvotesPercent+"%"); - // replace vote numbers - votes.find(".upvotes").text(votes.find(".upvotes").text().replace(/\d+/, upvotes)); - votes.find(".downvotes").text(votes.find(".downvotes").text().replace(/\d+/, downvotes)); - } - }, - - /** - * Called in response to the edit note form being submitted - * - * Updates the current note field. - * Hides the edit note form - */ - updateNote: function(e, xhr, settings) { - response = JSON.parse(xhr.responseText); - if (response.success) { - var note_li = $("#note_" + response.id); - var note_text = note_li.find(".note-text"); - note_text.html(response.note).show(); - - var note_form = note_li.find(".note-edit-form"); - note_form.hide(); - note_form.find(".btn-save").enableButton(); - - // Update the "Edited at xxx label" on the note to show it's just been updated - NoteList.rewriteTimestamp(note_li.find(".note-last-update")); - } - }, - - /** - * Called in response to the 'cancel note' link clicked, or after deleting a note attachment - * - * Hides the edit note form and shows the note - * Resets the edit note form textarea with the original content of the note - */ - resetNoteEditing: function(note) { - note.find(".note-text").show(); - - // Hide the attachment delete link - note.find(".js-note-attachment-delete").hide(); - - // Put the original content of the note back into the edit form textarea - var form = note.find(".note-edit-form"); - var original_content = form.find(".note-original-content"); - form.find("textarea").val(original_content.text()); - original_content.remove(); - - note.find(".note-edit-form").hide(); - }, - - /** - * Utility function to generate new timestamp text for a note - * - */ - rewriteTimestamp: function(element) { - // Strip all newlines from the existing timestamp - var ts = element.text().replace(/\n/g, ' ').trim(); - - // If the timestamp already has '(Edited xxx ago)' text, remove it - ts = ts.replace(new RegExp("\\(Edited [A-Za-z0-9 ]+\\)$", "gi"), ""); - - // Append "(Edited just now)" - ts = (ts + " <small>(Edited just now)</small>"); - - element.html(ts); - } -}; diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee new file mode 100644 index 0000000000000000000000000000000000000000..d200d962cae1a9939048954a285cdea817ce2915 --- /dev/null +++ b/app/assets/javascripts/notes.js.coffee @@ -0,0 +1,453 @@ +class Notes + @interval: null + + constructor: (notes_url, note_ids) -> + @notes_url = notes_url + @notes_url = gon.relative_url_root + @notes_url if gon.relative_url_root? + @note_ids = note_ids + @initRefresh() + @setupMainTargetNoteForm() + @cleanBinding() + @addBinding() + + addBinding: -> + # add note to UI after creation + $(document).on "ajax:success", ".js-main-target-form", @addNote + $(document).on "ajax:success", ".js-discussion-note-form", @addDiscussionNote + + # change note in UI after update + $(document).on "ajax:success", "form.edit_note", @updateNote + + # Edit note link + $(document).on "click", ".js-note-edit", @showEditForm + $(document).on "click", ".note-edit-cancel", @cancelEdit + + # remove a note (in general) + $(document).on "click", ".js-note-delete", @removeNote + + # delete note attachment + $(document).on "click", ".js-note-attachment-delete", @removeAttachment + + # Preview button + $(document).on "click", ".js-note-preview-button", @previewNote + + # reset main target form after submit + $(document).on "ajax:complete", ".js-main-target-form", @resetMainTargetForm + + # attachment button + $(document).on "click", ".js-choose-note-attachment-button", @chooseNoteAttachment + + # update the file name when an attachment is selected + $(document).on "change", ".js-note-attachment-input", @updateFormAttachment + + # reply to diff/discussion notes + $(document).on "click", ".js-discussion-reply-button", @replyToDiscussionNote + + # add diff note + $(document).on "click", ".js-add-diff-note-button", @addDiffNote + + # hide diff note form + $(document).on "click", ".js-close-discussion-note-form", @cancelDiscussionForm + + cleanBinding: -> + $(document).off "ajax:success", ".js-main-target-form" + $(document).off "ajax:success", ".js-discussion-note-form" + $(document).off "ajax:success", "form.edit_note" + $(document).off "click", ".js-note-edit" + $(document).off "click", ".note-edit-cancel" + $(document).off "click", ".js-note-delete" + $(document).off "click", ".js-note-attachment-delete" + $(document).off "click", ".js-note-preview-button" + $(document).off "ajax:complete", ".js-main-target-form" + $(document).off "click", ".js-choose-note-attachment-button" + $(document).off "click", ".js-discussion-reply-button" + $(document).off "click", ".js-add-diff-note-button" + + + initRefresh: -> + clearInterval(Notes.interval) + Notes.interval = setInterval => + @refresh() + , 15000 + + refresh: -> + @getContent() + + getContent: -> + $.ajax + url: @notes_url + dataType: "json" + success: (data) => + notes = data.notes + $.each notes, (i, note) => + @renderNote(note) + + + ### + Render note in main comments area. + + Note: for rendering inline notes use renderDiscussionNote + ### + renderNote: (note) -> + # render note if it not present in loaded list + # or skip if rendered + if @isNewNote(note) + @note_ids.push(note.id) + $('ul.main-notes-list').append(note.html) + code = "#note_" + note.id + " .highlight pre code" + $(code).each (i, e) -> + hljs.highlightBlock(e) + + + ### + Check if note does not exists on page + ### + isNewNote: (note) -> + $.inArray(note.id, @note_ids) == -1 + + + ### + Render note in discussion area. + + Note: for rendering inline notes use renderDiscussionNote + ### + renderDiscussionNote: (note) -> + @note_ids.push(note.id) + form = $("form[rel='" + note.discussion_id + "']") + row = form.closest("tr") + + # is this the first note of discussion? + if row.is(".js-temp-notes-holder") + # insert the note and the reply button after the temp row + row.after note.discussion_html + + # remove the note (will be added again below) + row.next().find(".note").remove() + + # append new note to all matching discussions + $(".notes[rel='" + note.discussion_id + "']").append note.html + + # cleanup after successfully creating a diff/discussion note + @removeDiscussionNoteForm(form) + + ### + Shows the note preview. + + Lets the server render GFM into Html and displays it. + + Note: uses the Toggler behavior to toggle preview/edit views/buttons + ### + previewNote: (e) -> + e.preventDefault() + form = $(this).closest("form") + 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. + + Removes any errors. + Resets text and preview. + Resets buttons. + ### + resetMainTargetForm: -> + form = $(".js-main-target-form") + + # remove validation errors + form.find(".js-errors").remove() + + # reset text and preview + previewContainer = form.find(".js-toggler-container.note_text_and_preview") + previewContainer.removeClass "on" if previewContainer.is(".on") + form.find(".js-note-text").val("").trigger "input" + + ### + Called when clicking the "Choose File" button. + + Opens the file selection dialog. + ### + chooseNoteAttachment: -> + form = $(this).closest("form") + form.find(".js-note-attachment-input").click() + + ### + Shows the main form and does some setup on it. + + Sets some hidden fields in the form. + ### + setupMainTargetNoteForm: -> + + # find the form + form = $(".js-new-note-form") + + # insert the form after the button + form.clone().replaceAll $(".js-main-target-form") + form = form.prev("form") + + # show the form + @setupNoteForm(form) + + # fix classes + form.removeClass "js-new-note-form" + form.addClass "js-main-target-form" + + # remove unnecessary fields and buttons + form.find("#note_line_code").remove() + form.find(".js-close-discussion-note-form").remove() + + ### + General note form setup. + + deactivates the submit button when text is empty + hides the preview button when text is empty + setup GFM auto complete + show the form + ### + setupNoteForm: (form) -> + disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button") + form.removeClass "js-new-note-form" + + # setup preview buttons + form.find(".js-note-edit-button, .js-note-preview-button").tooltip placement: "left" + previewButton = form.find(".js-note-preview-button") + form.find(".js-note-text").on "input", -> + if $(this).val().trim() isnt "" + previewButton.removeClass("turn-off").addClass "turn-on" + else + previewButton.removeClass("turn-on").addClass "turn-off" + + + # remove notify commit author checkbox for non-commit notes + form.find(".js-notify-commit-author").remove() if form.find("#note_noteable_type").val() isnt "Commit" + GitLab.GfmAutoComplete.setup() + form.show() + + + ### + Called in response to the new note form being submitted + + Adds new note to list. + ### + addNote: (xhr, note, status) => + @renderNote(note) + @updateVotes() + + ### + Called in response to the new note form being submitted + + Adds new note to list. + ### + addDiscussionNote: (xhr, note, status) => + @renderDiscussionNote(note) + + ### + Called in response to the edit note form being submitted + + Updates the current note field. + ### + updateNote: (xhr, note, status) => + note_li = $("#note_" + note.id) + note_li.replaceWith(note.html) + code = "#note_" + note.id + " .highlight pre code" + $(code).each (i, e) -> + hljs.highlightBlock(e) + + ### + Called in response to clicking the edit note link + + Replaces the note text with the note edit form + Adds a hidden div with the original content of the note to fill the edit note form with + if the user cancels + ### + showEditForm: (e) -> + e.preventDefault() + note = $(this).closest(".note") + note.find(".note-text").hide() + + # Show the attachment delete link + note.find(".js-note-attachment-delete").show() + GitLab.GfmAutoComplete.setup() + form = note.find(".note-edit-form") + form.show() + form.find("textarea").focus() + + ### + Called in response to clicking the edit note link + + Hides edit form + ### + cancelEdit: (e) -> + e.preventDefault() + note = $(this).closest(".note") + note.find(".note-text").show() + note.find(".js-note-attachment-delete").hide() + note.find(".note-edit-form").hide() + + ### + Called in response to deleting a note of any kind. + + Removes the actual note from view. + Removes the whole discussion if the last note is being removed. + ### + removeNote: -> + note = $(this).closest(".note") + notes = note.closest(".notes") + + # check if this is the last note for this line + if notes.find(".note").length is 1 + + # for discussions + notes.closest(".discussion").remove() + + # for diff lines + notes.closest("tr").remove() + + note.remove() + + ### + Called in response to clicking the delete attachment link + + Removes the attachment wrapper view, including image tag if it exists + Resets the note editing form + ### + removeAttachment: -> + note = $(this).closest(".note") + note.find(".note-attachment").remove() + note.find(".note-text").show() + note.find(".js-note-attachment-delete").hide() + note.find(".note-edit-form").hide() + + ### + Called when clicking on the "reply" button for a diff line. + + Shows the note form below the notes. + ### + replyToDiscussionNote: (e) => + form = $(".js-new-note-form") + replyLink = $(e.target) + replyLink.hide() + + # insert the form after the button + form.clone().insertAfter replyLink + + # show the form + @setupDiscussionNoteForm(replyLink, replyLink.next("form")) + + ### + Shows the diff or discussion form and does some setup on it. + + Sets some hidden fields in the form. + + Note: dataHolder must have the "discussionId", "lineCode", "noteableType" + and "noteableId" data attributes set. + ### + setupDiscussionNoteForm: (dataHolder, form) => + # setup note target + form.attr "rel", dataHolder.data("discussionId") + form.find("#note_commit_id").val dataHolder.data("commitId") + form.find("#note_line_code").val dataHolder.data("lineCode") + form.find("#note_noteable_type").val dataHolder.data("noteableType") + form.find("#note_noteable_id").val dataHolder.data("noteableId") + @setupNoteForm form + form.find(".js-note-text").focus() + form.addClass "js-discussion-note-form" + + ### + General note form setup. + + deactivates the submit button when text is empty + hides the preview button when text is empty + setup GFM auto complete + show the form + ### + setupNoteForm: (form) => + disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button") + form.removeClass "js-new-note-form" + form.removeClass "js-new-note-form" + GitLab.GfmAutoComplete.setup() + + # setup preview buttons + previewButton = form.find(".js-note-preview-button") + form.find(".js-note-text").on "input", -> + if $(this).val().trim() isnt "" + previewButton.removeClass("turn-off").addClass "turn-on" + else + previewButton.removeClass("turn-on").addClass "turn-off" + + form.show() + + ### + Called when clicking on the "add a comment" button on the side of a diff line. + + Inserts a temporary row for the form below the line. + Sets up the form and shows it. + ### + addDiffNote: (e) => + e.preventDefault() + link = e.target + form = $(".js-new-note-form") + row = $(link).closest("tr") + nextRow = row.next() + + # does it already have notes? + if nextRow.is(".notes_holder") + replyButton = nextRow.find(".js-discussion-reply-button") + if replyButton.length > 0 + $.proxy(@replyToDiscussionNote, replyButton).call() + else + # add a notes row and insert the form + row.after "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\" colspan=\"2\"></td><td class=\"notes_content\"></td></tr>" + form.clone().appendTo row.next().find(".notes_content") + + # show the form + @setupDiscussionNoteForm $(link), row.next().find("form") + + ### + Called in response to "cancel" on a diff note form. + + Shows the reply button again. + Removes the form and if necessary it's temporary row. + ### + removeDiscussionNoteForm: (form)-> + row = form.closest("tr") + + # show the reply button (will only work for replies) + form.prev(".js-discussion-reply-button").show() + if row.is(".js-temp-notes-holder") + # remove temporary row for diff lines + row.remove() + else + # only remove the form + form.remove() + + + cancelDiscussionForm: (e) => + e.preventDefault() + form = $(".js-new-note-form") + form = $(e.target).closest(".js-discussion-note-form") + @removeDiscussionNoteForm(form) + + updateVotes: -> + (new NotesVotes).updateVotes() + + ### + Called after an attachment file has been selected. + + Updates the file name for the selected attachment. + ### + updateFormAttachment: -> + form = $(this).closest("form") + + # get only the basename + filename = $(this).val().replace(/^.*[\\\/]/, "") + form.find(".js-attachment-filename").text filename + +@Notes = Notes diff --git a/app/assets/javascripts/notes_votes.js.coffee b/app/assets/javascripts/notes_votes.js.coffee new file mode 100644 index 0000000000000000000000000000000000000000..b31eb9ac9de7a014a719814003a696f49e042023 --- /dev/null +++ b/app/assets/javascripts/notes_votes.js.coffee @@ -0,0 +1,22 @@ +class NotesVotes + updateVotes: -> + votes = $("#votes .votes") + notes = $("#notes-list .note .vote") + + # only update if there is a vote display + if votes.size() + upvotes = notes.filter(".upvote").size() + downvotes = notes.filter(".downvote").size() + votesCount = upvotes + downvotes + upvotesPercent = (if votesCount then (100.0 / votesCount * upvotes) else 0) + downvotesPercent = (if votesCount then (100.0 - upvotesPercent) else 0) + + # change vote bar lengths + votes.find(".bar-success").css "width", upvotesPercent + "%" + votes.find(".bar-danger").css "width", downvotesPercent + "%" + + # replace vote numbers + votes.find(".upvotes").text votes.find(".upvotes").text().replace(/\d+/, upvotes) + votes.find(".downvotes").text votes.find(".downvotes").text().replace(/\d+/, downvotes) + +@NotesVotes = NotesVotes diff --git a/app/assets/javascripts/pager.js.coffee b/app/assets/javascripts/pager.js.coffee index 5bd11d273a7fed6a6f09112bbece0aacd99c1a31..1f763e8b9561f52fbf71721f6541d6685aa8fa2f 100644 --- a/app/assets/javascripts/pager.js.coffee +++ b/app/assets/javascripts/pager.js.coffee @@ -19,8 +19,9 @@ data: "limit=" + @limit + "&offset=" + @offset complete: -> $(".loading").hide() - - dataType: "script" + success: (data) -> + Pager.append(data.count, data.html) + dataType: "json" append: (count, html) -> $(".content_list").append html diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee index e7974611cbec6461c93335d56ad6848d5fbd54e8..744f3086d5516d2f6ab9b2e78566917792d9d503 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile.js.coffee @@ -16,3 +16,13 @@ $ -> $('.update-notifications').on 'ajax:complete', -> $(this).find('.btn-save').enableButton() + + + $('.js-choose-user-avatar-button').bind "click", -> + form = $(this).closest("form") + form.find(".js-user-avatar-input").click() + + $('.js-user-avatar-input').bind "change", -> + form = $(this).closest("form") + filename = $(this).val().replace(/^.*[\\\/]/, '') + form.find(".js-avatar-filename").text(filename) diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index 83236b348147a59cedccbc720d517f41c6749041..4262418fd5ec8b65f3b191ae13e6ecc5c8a95d46 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -6,10 +6,10 @@ class Project @initEvents() - + initEvents: -> disableButtonIfEmptyField '#project_name', '.project-submit' - + $('#project_issues_enabled').change -> if ($(this).is(':checked') == true) $('#project_issues_tracker').removeAttr('disabled') @@ -29,14 +29,20 @@ class Project $ -> # Git clone panel switcher - scope = $ '.project_clone_holder' + scope = $ '.git-clone-holder' if scope.length > 0 $('a, button', scope).click -> $('a, button', scope).removeClass 'active' $(@).addClass 'active' $('#project_clone', scope).val $(@).data 'clone' - $(".clone").text("").append 'git remote add origin ' + $(@).data 'clone' + $(".clone").text("").append $(@).data 'clone' # Ref switcher $('.project-refs-select').on 'change', -> $(@).parents('form').submit() + + $('.hide-no-ssh-message').on 'click', (e) -> + path = '/' + $.cookie('hide_no_ssh_message', 'false', { path: path }) + $(@).parents('.no-ssh-key-message').hide() + e.preventDefault() diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index 3418690e109f43b4c6f70807ea1bf494161a0097..e144dfa1d68d2371bd9415e08ff3700175c14813 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -1,7 +1,12 @@ class SearchAutocomplete - constructor: (json) -> + constructor: (search_autocomplete_path, project_id, project_ref) -> + project_id = '' unless project_id + project_ref = '' unless project_ref + query = "?project_id=" + project_id + "&project_ref=" + project_ref + $("#search").autocomplete - source: json + source: search_autocomplete_path + query + minLength: 1 select: (event, ui) -> location.href = ui.item.url diff --git a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee index 4844364416906dc62555def13352db20f64ac247..834c7e5dab03131e41d5b33f9a9a8d48f6a23e54 100644 --- a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee +++ b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee @@ -46,11 +46,7 @@ class window.ContributorsGraph class window.ContributorsMasterGraph extends ContributorsGraph constructor: (@data) -> - if $(window).width() > 1214 - @width = 1100 - else - @width = 870 - + @width = $('.container').width() - 70 @height = 200 @x = null @y = null @@ -88,7 +84,6 @@ class window.ContributorsMasterGraph extends ContributorsGraph x(d.date) ).y0(@height).y1((d) -> xa = d.commits = d.commits ? d.additions ? d.deletions - console.log(xa) y(xa) ).interpolate("basis") create_brush: -> @@ -124,11 +119,7 @@ class window.ContributorsMasterGraph extends ContributorsGraph class window.ContributorsAuthorGraph extends ContributorsGraph constructor: (@data) -> - if $(window).width() > 1214 - @width = 490 - else - @width = 380 - + @width = $('.container').width()/2 - 100 @height = 200 @x = null @y = null diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 8286ca2f0c11614702d55aaa088a06d124d05284..92cd3a9905b5ff0da70df7d4f2e96680d19b198f 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -1,15 +1,17 @@ $ -> userFormatResult = (user) -> - avatar = gon.gravatar_url - avatar = avatar.replace('%{hash}', md5(user.email)) - avatar = avatar.replace('%{size}', '24') + if user.avatar + avatar = user.avatar.url + else + avatar = gon.gravatar_url + avatar = avatar.replace('%{hash}', md5(user.email)) + avatar = avatar.replace('%{size}', '24') - markup = "<div class='user-result'>" - markup += "<div class='user-image'><img class='avatar s24' src='" + avatar + "'></div>" - markup += "<div class='user-name'>" + user.name + "</div>" - markup += "<div class='user-username'>" + user.username + "</div>" - markup += "</div>" - markup + "<div class='user-result'> + <div class='user-image'><img class='avatar s24' src='#{avatar}'></div> + <div class='user-name'>#{user.name}</div> + <div class='user-username'>#{user.username}</div> + </div>" userFormatSelection = (user) -> user.name diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index b1a23427addc69f6828f94cadc75e8de3aeeb271..cc5fdf614053cf857f46ce7d53e8741567249943 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -4,19 +4,46 @@ * the top of the compiled file, but it's generally better to create a new file per style scope. *= require jquery.ui.gitlab *= require jquery.atwho - *= require chosen *= require select2 + *= require highlightjs.min *= require_self */ +@import "main/variables.scss"; +@import "main/mixins.scss"; +@import "main/fonts.scss"; +@import "main/layout.scss"; + +/** + * Customized Twitter bootstrap + */ +@import 'gl_bootstrap'; + /** - * GitLab bootstrap: + * Font icons + * */ -@import "gitlab_bootstrap.scss"; +@import "font-awesome"; -@import "common.scss"; -@import "selects.scss"; +/** + * Generic css (forms, nav etc): + */ +@import "generic/avatar.scss"; +@import "generic/common.scss"; +@import "generic/typography.scss"; +@import "generic/buttons.scss"; +@import "generic/blocks.scss"; +@import "generic/ui_box.scss"; +@import "generic/issue_box.scss"; +@import "generic/files.scss"; +@import "generic/lists.scss"; +@import "generic/forms.scss"; +@import "generic/selects.scss"; +@import "generic/highlight.scss"; +/** + * Page specific styles (issues, projects etc): + */ @import "sections/header.scss"; @import "sections/nav.scss"; @import "sections/commits.scss"; @@ -39,6 +66,9 @@ @import "sections/dashboard.scss"; @import "sections/stat_graph.scss"; +/** + * Code ighlight + */ @import "highlight/white.scss"; @import "highlight/dark.scss"; @import "highlight/solarized_dark.scss"; @@ -57,4 +87,3 @@ * Styles for JS behaviors. */ @import "behaviors.scss"; - diff --git a/app/assets/stylesheets/gitlab_bootstrap/avatar.scss b/app/assets/stylesheets/generic/avatar.scss similarity index 85% rename from app/assets/stylesheets/gitlab_bootstrap/avatar.scss rename to app/assets/stylesheets/generic/avatar.scss index c23970c13eb9005e780d1b784afad3ef02259953..4f038b977e2c61f1a6c5f9eeae7db722e5a0149d 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/avatar.scss +++ b/app/assets/stylesheets/generic/avatar.scss @@ -2,8 +2,8 @@ float: left; margin-right: 12px; width: 40px; - border: 1px solid #ddd; padding: 1px; + @include border-radius(4px); &.avatar-inline { float: none; @@ -19,4 +19,5 @@ &.s32 { width: 32px; height: 32px; margin-right: 10px; } &.s60 { width: 60px; height: 60px; margin-right: 12px; } &.s90 { width: 90px; height: 90px; margin-right: 15px; } + &.s160 { width: 160px; height: 160px; margin-right: 20px; } } diff --git a/app/assets/stylesheets/generic/blocks.scss b/app/assets/stylesheets/generic/blocks.scss new file mode 100644 index 0000000000000000000000000000000000000000..1cbd743983587f71b23901df393b18e347f4e8c6 --- /dev/null +++ b/app/assets/stylesheets/generic/blocks.scss @@ -0,0 +1,4 @@ +.light-well { + background: #f9f9f9; + padding: 15px; +} diff --git a/app/assets/stylesheets/generic/buttons.scss b/app/assets/stylesheets/generic/buttons.scss new file mode 100644 index 0000000000000000000000000000000000000000..219e6ebd68b671768d9278e87cd486830ded470e --- /dev/null +++ b/app/assets/stylesheets/generic/buttons.scss @@ -0,0 +1,169 @@ +.btn { + display: inline-block; + margin-bottom: 0; + font-weight: normal; + text-align: center; + vertical-align: middle; + cursor: pointer; + background-image: none; + border: 1px solid transparent; + white-space: nowrap; + padding: 6px 12px; + font-size: 13px; + line-height: 18px; + border-radius: 4px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + color: #444444; + background-color: #fff; + border-color: #ccc; + text-shadow: none; + + &.hover, + &:hover { + color: #444444; + text-decoration: none; + background-color: #ebebeb; + border-color: #adadad; + } + + &.focus, + &:focus { + color: #444444; + text-decoration: none; + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; + } + + &.active, + &:active { + outline: 0; + background-image: none; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + } + + &.disabled, + &[disabled] { + cursor: not-allowed; + pointer-events: none; + opacity: 0.65; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; + } + + &.btn-primary { + color: #ffffff; + background-color: #429bca; + border-color: #358ebd; + + &.hover, + &:hover, + &.disabled, + &[disabled] { + color: #ffffff; + background-color: #3286b1; + border-color: #286e8e; + } + } + + &.btn-success { + color: #ffffff; + background-color: #5cb85c; + border-color: #4cae4c; + + + &.hover, + &:hover, + &.disabled, + &[disabled] { + color: #ffffff; + background-color: #47a447; + border-color: #398439; + } + } + + &.btn-danger { + color: #ffffff; + background-color: #d9534f; + border-color: #d43f3a; + + + &.hover, + &:hover, + &.disabled, + &[disabled] { + color: #ffffff; + background-color: #d2322d; + border-color: #ac2925; + } + } + + &.btn-new { + @extend .btn-success; + } + + &.btn-create { + @extend .wide; + @extend .btn-success; + } + + &.btn-save { + @extend .wide; + @extend .btn-primary; + } + + &.btn-close, + &.btn-remove { + @extend .btn-danger; + } + + &.btn-cancel { + float: right; + } + + &.wide { + padding-left: 20px; + padding-right: 20px; + } + + &.btn-small { + padding: 2px 10px; + font-size: 12px; + } + + &.btn-tiny { + font-size: 11px; + padding: 2px 6px; + line-height: 16px; + margin: 2px; + } +} + +.btn-block { + width: 100%; + margin: 0; + margin-bottom: 15px; + &.btn { + padding: 6px 0; + } +} + +.btn, +.btn-group { + &.grouped { + margin-right: 7px; + float: left; + &:last-child { + margin-right: 0px; + } + } +} + +.btn-group-small > .btn { @extend .btn.btn-small; } +.btn-group-tiny > .btn { @extend .btn.btn-tiny; } diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/generic/common.scss similarity index 54% rename from app/assets/stylesheets/common.scss rename to app/assets/stylesheets/generic/common.scss index 1572227ec3a5b7fa3af9794717c4ef34ab64d2dd..4824194ec420a75e190e58a827016fddd92c8226 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/generic/common.scss @@ -1,46 +1,132 @@ -html { - overflow-y: scroll; +/** COLORS **/ +.cgray { color: gray } +.clgray { color: #BBB } +.cred { color: #D12F19 } +.cgreen { color: #4a2 } +.cblue { color: #29A } +.cblack { color: #111 } +.cdark { color: #444 } +.camber { color: #ffc000 } +.cwhite { color: #fff!important } +.bgred { background: #F2DEDE!important } + +/** COMMON CLASSES **/ +.left { float:left } + +.prepend-top-10 { margin-top:10px } +.prepend-top-20 { margin-top:20px } +.prepend-left-10 { margin-left:10px } +.prepend-left-20 { margin-left:20px } +.append-right-10 { margin-right:10px } +.append-right-20 { margin-right:20px } +.append-bottom-10 { margin-bottom:10px } +.append-bottom-15 { margin-bottom:15px } +.append-bottom-20 { margin-bottom:20px } +.inline { display: inline-block } + +.padded { padding:20px } +.ipadded { padding:20px!important } +.lborder { border-left:1px solid #eee } +.underlined_link { text-decoration: underline; } +.hint { font-style: italic; color: #999; } +.light { color: #888 } +.tiny { font-weight: normal } +.vtop { vertical-align: top !important; } + + +/** ALERT MESSAGES **/ +.alert.alert-disabled { + background: #EEE; + color: #777; + border-color: #DDD; +} + +/** HELPERS **/ +.nothing_here_message { + text-align: center; + padding: 20px; + color: #666; + font-weight: normal; + font-size: 16px; + line-height: 36px; } -/** LAYOUT **/ - -body { - margin-bottom: 20px; +.slead { + color: #666; + font-size: 14px; + margin-bottom: 12px; + font-weight: normal; + line-height: 24px; } -.container { - padding-top: 0; - z-index: 5; + +.tab-content { + overflow: visible; } -.container .content { - margin: 0 0; +@media (max-width: 1200px) { + .only-wide { + display: none; + } } -.author_link { - color: $link_color; +pre.well-pre { + border: 1px solid #EEE; + background: #f9f9f9; + border-radius: 0; + color: #555; } -.help li { color:$style_color; } +.input-append .btn.active, .input-prepend .btn.active { + background: #CCC; + border-color: #BBB; + text-shadow: 0 1px 1px #fff; + font-weight: bold; + @include box-shadow(inset 0 2px 4px rgba(0,0,0,.15)); +} -.back-link { +/** Big Labels **/ +.state-label { font-size: 14px; + padding: 6px 25px; + text-align: center; + @include border-radius(4px); + text-shadow: none; + margin-left: 10px; + + &.state-label-green { + background: #4A4; + color: #FFF; + } + + &.state-label-red { + background: #DA4E49; + color: #FFF; + } } -table a code { - position: relative; - top: -2px; - margin-right: 3px; +.dropdown-menu > li > a { + text-shadow: none; } -.loading { - margin: 20px auto; - background: url(ajax_loader.gif) no-repeat center center; - width: 40px; - height: 40px; - &.loading-gray { - background: url(ajax_loader_gray.gif) no-repeat center center; - } +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + background: #29b; +} + +.breadcrumb > li + li:before { + content: "/"; + padding: 0; + color: #666; +} + +.str-truncated { + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: top; + white-space: nowrap; + max-width: 82%; } /** FLASH message **/ @@ -69,6 +155,29 @@ table a code { padding: 10px; } } +.author_link { + color: $link_color; +} + +.help li { color:$style_color; } + +.back-link { + font-size: 14px; +} + +table a code { + position: relative; + top: -2px; + margin-right: 3px; +} + +.loading { + margin: 20px auto; + height: 40px; + color: #555; + font-size: 32px; + text-align: center; +} span.update-author { display: block; @@ -86,30 +195,10 @@ span.update-author { font-weight: bold; } -.label { - padding: 1px 4px; - font-size: 12px; - font-style: normal; - font-weight: normal; -} - .field_with_errors { display: inline; } -ul.breadcrumb { - background: white; - border: none; - li { - display: inline; - text-shadow: 0 1px 0 white - } - - a { - font-size: 16px; - } -} - .line_holder { &:hover { td { @@ -124,43 +213,14 @@ p.time { margin: 30px 3px 3px 2px; } -.search-holder { - label, input { - height: 30px; - padding: 0; - font-size: 14px; - } - label { - line-height: 30px; - color: #666; - } +.highlight { + text-shadow: none; } .highlight_word { border-bottom: 2px solid #F90; } -.status_info { - font-size: 14px; - padding: 5px 15px; - line-height: 26px; - text-align: center; - float: right; - position: relative; - top: -5px; - @include border-radius(4px); - - &.success { - background: #4A4; - color: #FFF; - } - - &.error { - background: #DA4E49; - color: #FFF; - } -} - .thin_area{ height: 150px; } @@ -231,7 +291,7 @@ li.note { } .git_error_tips { - @extend .span6; + @extend .col-md-6; text-align: left; margin-top: 40px; pre { @@ -244,7 +304,6 @@ li.note { .error-message { padding: 10px; background: #C67; - padding-left: 20px; margin: 0; color: #FFF; @@ -252,8 +311,25 @@ li.note { color: #fff; text-decoration: underline; } - &.centered { +} + +.no-ssh-key-message { + padding: 10px 0; + background: #C67; + margin: 0; + color: #FFF; + margin-top: -1px; + text-align: center; + + a { + color: #fff; + text-decoration: underline; + } + + .links-xs { text-align: center; + font-size: 16px; + padding: 5px; } } @@ -270,27 +346,6 @@ li.note { } } -.oauth_select_holder { - padding: 20px; - img { - padding: 5px; - margin-right: 10px; - } - .active { - img { - border: 1px solid #ccc; - background: $hover; - @include border-radius(5px); - } - } -} - -.btn-build-token { - float: left; - padding: 6px 20px; - margin-right: 12px; -} - .gitlab-promo { a { color: #aaa; @@ -362,11 +417,6 @@ img.emoji { margin-bottom: 10px; } -.group-name { - font-size: 14px; - line-height: 24px; -} - table { td.permission-x { background: #D9EDF7 !important; @@ -383,7 +433,63 @@ table { min-height: 100px; } -.navbar-gitlab .navbar-inner .nav > li .btn-sign-in { - @extend .btn-new; - padding: 5px 15px; +.broadcast-message { + padding: 10px; + text-align: center; + background: #555; + color: #BBB; +} + +.broadcast-message-preview { + @extend .broadcast-message; + margin-bottom: 20px; +} + +.ajax-users-select { + width: 400px; + + &.input-large { + width: 210px; + } + + &.input-clamp { + max-width: 100%; + } +} + +.user-result { + .user-image { + float: left; + } + .user-name { + } + .user-username { + color: #999; + } +} + +.namespace-result { + .namespace-kind { + color: #AAA; + font-weight: normal; + } + .namespace-path { + margin-left: 10px; + font-weight: bolder; + } +} + +.btn-sign-in { + margin-top: 7px; + text-shadow: none; +} + +.side-filters { + fieldset { + margin-bottom: 15px; + } +} + +@media (max-width: $screen-xs-max) { + .container .content { margin-top: 20px; } } diff --git a/app/assets/stylesheets/gitlab_bootstrap/files.scss b/app/assets/stylesheets/generic/files.scss similarity index 59% rename from app/assets/stylesheets/gitlab_bootstrap/files.scss rename to app/assets/stylesheets/generic/files.scss index 8ba8c93e3d6affb0412c67ca0a4201cfb9d115e8..20877507c91c3c2fe6b592fc8279d23f8613c449 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/files.scss +++ b/app/assets/stylesheets/generic/files.scss @@ -11,8 +11,8 @@ } .file-title { - border-bottom: 1px solid #bbb; - @include bg-dark-gray-gradient; + background: #DDD; + border-bottom: 1px solid #CCC; text-shadow: 0 1px 1px #fff; margin: 0; font-weight: normal; @@ -20,7 +20,6 @@ text-align: left; color: $style_color; padding: 9px 10px; - height: 18px; .options { float: right; @@ -46,7 +45,7 @@ text-align: center; img { padding: 100px; - max-width: 300px; + max-width: 50%; } } @@ -69,6 +68,12 @@ } + &.blob-no-preview { + background: #eee; + text-shadow: 0 1px 2px #FFF; + padding: 100px 0; + } + /** * Blame file */ @@ -138,75 +143,6 @@ */ &.code { padding: 0; - - table.lines { - border: none; - box-shadow: none; - margin: 0px; - padding: 0px; - table-layout: fixed; - - pre { - border: none; - border-radius: 0; - font-family: $monospace_font; - font-size: 12px !important; - line-height: 16px !important; - margin: 0; - padding: 10px 0; - } - td { - border: none; - margin: 0; - padding: 0; - vertical-align: top; - - &:first-child { - background: #eee; - width: 50px; - } - &:last-child { - } - } - tr:hover { - background: none; - } - - pre.line_numbers { - color: #666; - padding: 10px 6px 10px 0; - text-align: right; - background: #EEE; - - a { - color: #666; - - i { - display: none; - font-size: 14px; - line-height: 14px; - } - &:hover i { - display: inherit; - } - } - } - - .highlight { - border-left: 1px solid #DEE2E3; - overflow: auto; - overflow-y: hidden; - - pre { - white-space: pre; - word-wrap: normal; - - .line { - padding: 0 10px; - } - } - } - } } } } diff --git a/app/assets/stylesheets/generic/forms.scss b/app/assets/stylesheets/generic/forms.scss new file mode 100644 index 0000000000000000000000000000000000000000..931b75a32340324a097332c3eac50eb853888eb4 --- /dev/null +++ b/app/assets/stylesheets/generic/forms.scss @@ -0,0 +1,53 @@ +input[type='search'].search-text-input { + background-image: url("icon-search.png"); + background-repeat: no-repeat; + background-position: 10px; + padding-left: 25px; +} + +input[type='text'].danger { + background: #F2DEDE!important; + border-color: #D66; + text-shadow: 0 1px 1px #fff +} + +fieldset legend { + font-size: 16px; +} + +.datetime-controls { + select { + width: 100px; + } +} + +.form-actions { + padding: 17px 20px 18px; + margin-top: 18px; + margin-bottom: 18px; + background-color: whitesmoke; + border-top: 1px solid #e5e5e5; + padding-left: 17%; +} + +label { + &.control-label { + @extend .col-sm-2; + } + + &.inline-label { + margin: 0; + } +} + +.inline-input-group { + width: 250px; +} + +.input-mx-250 { + max-width: 250px; +} + +.input-mn-300 { + min-width: 300px; +} diff --git a/app/assets/stylesheets/generic/highlight.scss b/app/assets/stylesheets/generic/highlight.scss new file mode 100644 index 0000000000000000000000000000000000000000..4110bddf4f34270e3b8730cbf8d48c5f8c874231 --- /dev/null +++ b/app/assets/stylesheets/generic/highlight.scss @@ -0,0 +1,64 @@ +.highlighted-data { + border: none; + box-shadow: none; + margin: 0px; + padding: 0px; + table-layout: fixed; + + pre { + padding: 10px; + border: none; + border-radius: 0; + font-family: $monospace_font; + font-size: 12px !important; + line-height: 16px !important; + margin: 0; + + code { + white-space: pre; + word-wrap: normal; + padding: 0; + + .line { + display: inline; + } + } + } + + .hljs { + padding: 0; + } + + .line-numbers { + padding: 10px; + text-align: right; + float: left; + + a { + font-family: $monospace_font; + display: block; + font-size: 12px !important; + line-height: 16px !important; + white-space: nowrap; + + i { + visibility: hidden; + @extend .pull-left; + } + + &:hover i { + visibility: visible; + } + } + } + + .highlight { + overflow: auto; + overflow-y: hidden; + + pre { + white-space: pre; + word-wrap: normal; + } + } +} diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss new file mode 100644 index 0000000000000000000000000000000000000000..afe9c5f8186f301eeb904c0d4b4c8128192e1405 --- /dev/null +++ b/app/assets/stylesheets/generic/issue_box.scss @@ -0,0 +1,44 @@ +/** + * Issue box: + * Huge block (one per page) for storing title, descripion and other information. + * Used for Issue#show page, MergeRequest#show page etc + * + * CLasses: + * .issue-box - Regular box + */ + +.issue-box { + color: #666; + margin:20px 0; + background: #FAFAFA; + border: 1px solid #EEE; + + .control-group { + margin-bottom: 0; + } + + .title { + font-size: 20px; + font-weight: 500; + line-height: 28px; + margin: 0; + color: #444; + } + + .context { + border: none; + border-top: 1px solid #eee; + } + + .description { + border-top: 1px solid #eee; + } + + .title, .context, .description { + padding: 15px; + + .clearfix { + margin: 0; + } + } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/lists.scss b/app/assets/stylesheets/generic/lists.scss similarity index 91% rename from app/assets/stylesheets/gitlab_bootstrap/lists.scss rename to app/assets/stylesheets/generic/lists.scss index 83066b5beecce4f9116a39b43d413c056583fc02..de70e47333faa36a67da0a7c50814b15ee35e307 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/lists.scss +++ b/app/assets/stylesheets/generic/lists.scss @@ -4,7 +4,9 @@ */ .well-list { margin: 0; + padding: 0; list-style: none; + li { padding: 10px; min-height: 20px; @@ -21,6 +23,12 @@ } } + &.warning-row { + background-color: #fcf8e3; + border-color: #faebcc; + color: #8a6d3b; + } + &.smoke { background-color: #f5f5f5; } &:hover { diff --git a/app/assets/stylesheets/generic/selects.scss b/app/assets/stylesheets/generic/selects.scss new file mode 100644 index 0000000000000000000000000000000000000000..c506bff8a74dffb752be705d549e04b92c39b12a --- /dev/null +++ b/app/assets/stylesheets/generic/selects.scss @@ -0,0 +1,80 @@ +/** Select2 selectbox style override **/ + +.select2-container, .select2-container.select2-drop-above { + .select2-choice { + background: #FFF; + border-color: #BBB; + + .select2-arrow { + background: #FFF; + } + } +} + +.select2-drop-active { + border: 1px solid #BBB; + margin-top: 4px; + + .select2-search input { + background: #fafafa; + border-color: #DDD; + } + + .select2-results { + max-height: 350px; + .select2-highlighted { + background: $bg_style_color; + } + } +} + +select { + &.select2 { + width: 100px; + } + + &.select2-sm { + width: 100px; + } +} + +@media (min-width: $screen-sm-min) { + select { + &.select2 { + width: 150px; + } + &.select2-sm { + width: 120px; + } + } +} + +/* Medium devices (desktops, 992px and up) */ +@media (min-width: $screen-md-min) { + select { + &.select2 { + width: 170px; + } + &.select2-sm { + width: 140px; + } + } +} + +/* Large devices (large desktops, 1200px and up) */ +@media (min-width: $screen-lg-min) { + select { + &.select2 { + width: 200px; + } + &.select2-sm { + width: 150px; + } + } +} + + +/** Branch/tag selector **/ +.project-refs-form .select2-container { + margin-right: 10px; +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/typography.scss b/app/assets/stylesheets/generic/typography.scss similarity index 90% rename from app/assets/stylesheets/gitlab_bootstrap/typography.scss rename to app/assets/stylesheets/generic/typography.scss index d3986556376148e7fa473959fa06d5912e0cb6be..419a63d4d3ad0c6c1a266011a204400fc344fdd5 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/typography.scss +++ b/app/assets/stylesheets/generic/typography.scss @@ -2,11 +2,6 @@ * Headers * */ -h1, h2, h3, h4, h5, h6 { - font-weight: 500; - line-height: 1.1; -} - h1.page-title { @include page-title; font-size: 28px; @@ -95,10 +90,8 @@ a:focus { font-size: 14px; line-height: 1.6; - .white .highlight pre { - background: #f5f5f5; - } ul { + padding: 0; margin: 0 0 9px 25px !important; } } diff --git a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss b/app/assets/stylesheets/generic/ui_box.scss similarity index 54% rename from app/assets/stylesheets/gitlab_bootstrap/blocks.scss rename to app/assets/stylesheets/generic/ui_box.scss index fcf1159cd33b5ff60527d9e439e7bd7cf8156a50..7a977eae70d9f01002181434da75f87ec8594824 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss +++ b/app/assets/stylesheets/generic/ui_box.scss @@ -1,104 +1,55 @@ /** - * =================================== - * Contain UI block elements: + * UI box: + * Block element for separating information on page. + * Used for storing issues lists, grouped data. + * You can have multiple ui boxes on one page + * + * Classes: * .ui-box - for any block & widgets - * =================================== - */ - -/** - * UI Block + * .ui-box.ui-box-small - same but with smaller title + * .ui-box.ui-box-danger - with red title + * + * Ex. 1: List + * .ui-box + * .title + * # title here + * %ul + * # content here + * + * Ex. 2: Block data + * .ui-box + * .title + * # title here + * .body + * # content here * */ + .ui-box { background: #FFF; margin-bottom: 20px; - border: 1px solid #CCC; + border: 1px solid #DDD; word-wrap: break-word; - &.small-box { - margin-bottom: 10px; - - .title { - font-size: 13px; - line-height: 30px; - - a { - color: #666; - &:hover { - text-decoration: underline; - } - } - } + img { + max-width: 100%; } - &.ui-box-show { - margin:20px 0; - background: #FFF; - - .control-group { - margin-bottom: 0; - } - } - - &.ui-box-danger { - .title { - @include linear-gradient(#F26E5E, #bd362f); - color: #fff; - text-shadow: 0 1px 1px #900; - font-weight: bold; - } - } - - img { max-width: 100%; } - pre { code { background: none !important; } } - .ui-box-head, - .ui-box-body, - .ui-box-bottom { - padding: 15px; - - .clearfix { - margin: 0; - } - } - - .ui-box-head { - .box-title { - color: $style_color; - font-size: 18px; - font-weight: normal; - line-height: 28px; - margin: 0; - } - h3 { - margin: 0; - } - } - - .ui-box-body { - border: none; - background-color: #f5f5f5; - border: none; - border-top: 1px solid #eee; - } - - .ui-box-bottom { - border-top: 1px solid #eee; - } - ul { margin: 0; + padding: 0; } .title { - @include bg-gray-gradient; - border-bottom: 1px solid #CCC; - color: #456; + background-color: #EEE; + border-bottom: 1px solid #DDD; + color: #666; font-size: 16px; text-shadow: 0 1px 1px #fff; padding: 0 10px; @@ -143,6 +94,10 @@ } } + .body { + padding: 10px; + } + &.padded { h5, .title { margin: -20px; @@ -152,7 +107,7 @@ } .row_title { - font-weight: bold; + font-weight: 500; color: #444; &:hover { color: #444; @@ -174,13 +129,45 @@ } } +/* + * Small box + */ +.ui-box.ui-box-small { + margin-bottom: 10px; + + .title { + font-size: 13px; + line-height: 30px; + + a { + color: #666; + &:hover { + text-decoration: underline; + } + } + } +} + +/* + * Danger box + */ +.ui-box.ui-box-danger { + background: #f7f7f7; + border: none; + + .title { + background: #D65; + color: #fff; + text-shadow: none; + font-weight: 500; + } +} + +/* + * Block under tw-bootstrap tabs + */ .tab-pane { .ui-box { margin: 3px 3px 25px 3px; } } - -.light-well { - background: #f9f9f9; - padding: 15px; -} diff --git a/app/assets/stylesheets/gitlab_bootstrap.scss b/app/assets/stylesheets/gitlab_bootstrap.scss deleted file mode 100644 index faf36b702c080ea02964966bac06d00f93da1679..0000000000000000000000000000000000000000 --- a/app/assets/stylesheets/gitlab_bootstrap.scss +++ /dev/null @@ -1,66 +0,0 @@ -/** Override bootstrap variables **/ -$baseFontSize: 13px !default; -$baseLineHeight: 18px !default; - -/** - * BOOTSTRAP - */ -@import "bootstrap/variables"; -@import "bootstrap/mixins"; -@import "bootstrap/reset"; -@import "bootstrap/scaffolding"; -@import "bootstrap/grid"; -@import "bootstrap/layouts"; -@import "bootstrap/type"; -@import "bootstrap/code"; -@import "bootstrap/forms"; -@import "bootstrap/tables"; -@import "bootstrap/sprites"; -@import "bootstrap/dropdowns"; -@import "bootstrap/wells"; -@import "bootstrap/component-animations"; -@import "bootstrap/close"; -@import "bootstrap/button-groups"; -@import "bootstrap/alerts"; -@import "bootstrap/navs"; -@import "bootstrap/navbar"; -@import "bootstrap/breadcrumbs"; -@import "bootstrap/pagination"; -@import "bootstrap/pager"; -@import "bootstrap/modals"; -@import "bootstrap/tooltip"; -@import "bootstrap/popovers"; -@import "bootstrap/thumbnails"; -@import "bootstrap/media"; -@import "bootstrap/labels-badges"; -@import "bootstrap/progress-bars"; -@import "bootstrap/accordion"; -@import "bootstrap/carousel"; -@import "bootstrap/hero-unit"; -@import "bootstrap/utilities"; -@import "bootstrap/responsive-utilities"; -@import "bootstrap/responsive-1200px-min"; - -/** - * Font icons - * - */ -@import "font-awesome"; - -/** - * GitLab bootstrap. - * Overrides some styles of twitter bootstrap. - * Also give some common classes for GitLab app - */ -@import "gitlab_bootstrap/variables.scss"; -@import "gitlab_bootstrap/fonts.scss"; -@import "gitlab_bootstrap/mixins.scss"; -@import "gitlab_bootstrap/avatar.scss"; -@import "gitlab_bootstrap/nav.scss"; -@import "gitlab_bootstrap/common.scss"; -@import "gitlab_bootstrap/typography.scss"; -@import "gitlab_bootstrap/buttons.scss"; -@import "gitlab_bootstrap/blocks.scss"; -@import "gitlab_bootstrap/files.scss"; -@import "gitlab_bootstrap/lists.scss"; -@import "gitlab_bootstrap/forms.scss"; diff --git a/app/assets/stylesheets/gitlab_bootstrap/buttons.scss b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss deleted file mode 100644 index 9eb32ca95e672e94110b4fe422034bb41d017de0..0000000000000000000000000000000000000000 --- a/app/assets/stylesheets/gitlab_bootstrap/buttons.scss +++ /dev/null @@ -1,141 +0,0 @@ -.btn { - display: inline-block; - padding: 6px 12px; - margin-bottom: 0; - font-size: 13px; - line-height: $baseLineHeight; - text-align: center; - vertical-align: middle; - cursor: pointer; - border: 1px solid #BBB; - color: $style_color; - @include border-radius($baseBorderRadius); - @include box-shadow(inset 0 1px 0 rgba(255,255,255,.2)); - @include linear-gradient(#f1f1f1, #e1e1e1); - text-shadow: 0 1px 1px #FFF; - text-decoration: none; - - &.hover, - &:hover { - color: $style_color; - background: #f1f1f1; - border-color: #AAA; - text-decoration: none; - @include linear-gradient(#fAfAfA, #f1f1f1); - } - - &.focus, - &:focus { - text-decoration: none; - @include box-shadow(inset 0 2px 4px rgba(0,0,0,.15)); - } - - &.active, - &:active { - background-image: none; - outline: 0; - text-decoration: none; - @include box-shadow(inset 0 2px 4px rgba(0,0,0,.15)); - } - - &.disabled, - &[disabled] { - cursor: default; - background-image: none; - @include opacity(65); - @include box-shadow(none); - } - - &.btn-primary { - color: #FFF; - border-color: #189; - text-shadow: 0 1px 1px #189; - @include linear-gradient(#4AC, #289); - - &.hover, - &:hover, - &.disabled, - &[disabled] { - color: #FFF; - background: #389; - } - } - - &.btn-success { - color: #FFF; - border-color: #1A1; - text-shadow: 0 1px 1px #FFF; - text-shadow: 0 1px 1px #181; - @include linear-gradient(#62C452, #51a351); - - - &.hover, - &:hover, - &.disabled, - &[disabled] { - color: #FFF; - background: #2A2; - } - } - - &.btn-danger { - color: #FFF; - text-shadow: 0 1px 1px #811; - border-color: #BD362F; - @include linear-gradient(#EE5F5B, #BD362F); - - - &.hover, - &:hover, - &.disabled, - &[disabled] { - color: #FFF; - background: #A22; - } - } - - &.btn-new { - @extend .btn-success; - } - - &.btn-create { - @extend .wide; - @extend .btn-success; - } - - &.btn-save { - @extend .wide; - @extend .btn-primary; - } - - &.btn-close, - &.btn-remove { - @extend .btn-danger; - } - - &.btn-cancel { - float: right; - } - - &.wide { - padding-left: 20px; - padding-right: 20px; - } - - &.btn-small { - padding: 2px 10px; - font-size: 12px; - } - - &.btn-tiny { - font-size: 11px; - padding: 2px 6px; - line-height: 16px; - margin: 2px; - } - - &.grouped { - margin-right: 7px; - float: left; - } -} diff --git a/app/assets/stylesheets/gitlab_bootstrap/common.scss b/app/assets/stylesheets/gitlab_bootstrap/common.scss deleted file mode 100644 index bc6c786da50c7667be2288e59e5e0b51e7b7eac9..0000000000000000000000000000000000000000 --- a/app/assets/stylesheets/gitlab_bootstrap/common.scss +++ /dev/null @@ -1,89 +0,0 @@ -/** COLORS **/ -.cgray { color: gray } -.cred { color: #D12F19 } -.cgreen { color: #4a2 } -.cblue { color: #29A } -.cblack { color: #111 } -.cdark { color: #444 } -.cwhite { color: #fff!important } -.bgred { background: #F2DEDE!important } - -/** COMMON CLASSES **/ -.left { float:left } - -.prepend-top-10 { margin-top:10px } -.prepend-top-20 { margin-top:20px } -.prepend-left-10 { margin-left:10px } -.prepend-left-20 { margin-left:20px } -.append-right-10 { margin-right:10px } -.append-right-20 { margin-right:20px } -.append-bottom-10 { margin-bottom:10px } -.append-bottom-20 { margin-bottom:20px } -.inline { display: inline-block } - -.padded { padding:20px } -.ipadded { padding:20px!important } -.lborder { border-left:1px solid #eee } -.underlined_link { text-decoration: underline; } -.hint { font-style: italic; color: #999; } -.light { color: #888 } -.tiny { font-weight: normal } -.vtop { vertical-align: top !important; } - - -/** ALERT MESSAGES **/ -.alert.alert-disabled { - background: #EEE; - color: #777; - border-color: #DDD; -} - -/** HELPERS **/ -.nothing_here_message { - text-align: center; - padding: 20px; - color: #666; - font-weight: normal; - font-size: 16px; - line-height: 36px; -} - -.slead { - color: #666; - font-size: 14px; - margin-bottom: 12px; - font-weight: normal; - line-height: 24px; -} - - -.tab-content { - overflow: visible; -} - -@media (max-width: 1200px) { - .only-wide { - display: none; - } -} - -.pagination ul > li > a, .pagination ul > li >span { - @include linear-gradient(#f1f1f1, #e1e1e1); - color: #333; - text-shadow: 0 1px 1px #FFF; -} - -pre.well-pre { - border: 1px solid #EEE; - background: #f9f9f9; - border-radius: 0; - color: #555; -} - -.input-append .btn.active, .input-prepend .btn.active { - background: #CCC; - border-color: #BBB; - text-shadow: 0 1px 1px #fff; - font-weight: bold; - @include box-shadow(inset 0 2px 4px rgba(0,0,0,.15)); -} diff --git a/app/assets/stylesheets/gitlab_bootstrap/forms.scss b/app/assets/stylesheets/gitlab_bootstrap/forms.scss deleted file mode 100644 index a2612166c7858f6feedb29af472bb794ead65edc..0000000000000000000000000000000000000000 --- a/app/assets/stylesheets/gitlab_bootstrap/forms.scss +++ /dev/null @@ -1,51 +0,0 @@ -form { - @extend .form-horizontal; - - label { - @extend .control-label; - } -} - -input.input-xpadding, -.add-on.input-xpadding { - padding: 6px 10px; -} - -.control-group { - .control-label { - padding-top: 6px; - } - .controls { - input, textarea { - padding: 6px 10px; - } - - input[type="radio"], input[type="checkbox"] { - margin-top: 6px; - } - - .add-on { - padding: 6px; - } - } -} - -input[type='search'].search-text-input { - background-image: url("icon-search.png"); - background-repeat: no-repeat; - background-position: 10px; - padding-left: 25px; - @include border-radius(4px); - border: 1px solid #ccc; -} - -input[type='text'].danger { - background: #F2DEDE!important; - border-color: #D66; - text-shadow: 0 1px 1px #fff -} - -fieldset legend { - font-size: 16px; - margin-bottom: 10px; -} diff --git a/app/assets/stylesheets/gitlab_bootstrap/nav.scss b/app/assets/stylesheets/gitlab_bootstrap/nav.scss deleted file mode 100644 index aa4cb1ed5fd12af75b634eb39165527170fe9f66..0000000000000000000000000000000000000000 --- a/app/assets/stylesheets/gitlab_bootstrap/nav.scss +++ /dev/null @@ -1,92 +0,0 @@ -/** - * nav-pills - * - */ -.nav-pills { - .active a { - background: $primary_color; - } - - > li > a { - @include border-radius(0); - } - - &.nav-stacked { - > li > a { - border-left: 4px solid #EEE; - padding: 12px; - } - > .active > a { - border-color: $primary_color; - border-radius: 0; - background: #F1F1F1; - color: $style_color; - font-weight: bold; - text-shadow: 0 1px 1px #fff; - } - - &.nav-stacked-menu { - background: #FAFAFA; - li > a { - padding: 16px; - } - } - } - - &.nav-pills-small { - > li > a { - padding: 8px 12px; - } - } -} - -.nav-pills > .active > a > i[class^="icon-"] { background: inherit; } - - - -/** - * nav-tabs - * - */ -.nav-tabs > li > a, .nav-pills > li > a { color: $style_color; } -.nav.nav-tabs { - li { - > a { - padding: 8px 20px; - margin-right: 7px; - line-height: 20px; - border-color: #EEE; - color: #888; - border-bottom: 1px solid #ddd; - .badge { - background-color: #eee; - color: #888; - text-shadow: 0 1px 1px #fff; - } - i[class^="icon-"] { - line-height: 14px; - } - } - &.active { - > a { - border-color: #CCC; - border-bottom: 1px solid #fff; - color: #333; - font-weight: bold; - } - } - } - - &.nav-small-tabs > li > a { padding: 6px 9px; } -} - - - -/** - * fix to keep tooltips position in top navigation bar - * - */ -.navbar .nav > li { - position: relative; - white-space: nowrap; -} diff --git a/app/assets/stylesheets/gl_bootstrap.scss b/app/assets/stylesheets/gl_bootstrap.scss new file mode 100644 index 0000000000000000000000000000000000000000..7f45d64fb7c0da74374fc72698c24b23e55d5d69 --- /dev/null +++ b/app/assets/stylesheets/gl_bootstrap.scss @@ -0,0 +1,220 @@ +/* + * Twitter bootstrap with GitLab customizations/additions + * + * Some unused bootstrap compontents like panels are not included. + * Other components like tabs are modified to GitLab style. + * + */ + +$font-size-base: 13px !default; +$nav-pills-active-link-hover-bg: $bg_style_color; +$pagination-active-bg: $bg_style_color; + +// Core variables and mixins +@import "bootstrap/variables"; +@import "bootstrap/mixins"; + +// Reset +@import "bootstrap/normalize"; +@import "bootstrap/print"; + +// Core CSS +@import "bootstrap/scaffolding"; +@import "bootstrap/type"; +@import "bootstrap/code"; +@import "bootstrap/grid"; +@import "bootstrap/tables"; +@import "bootstrap/forms"; + +// Components +@import "bootstrap/component-animations"; +@import "bootstrap/dropdowns"; +@import "bootstrap/button-groups"; +@import "bootstrap/input-groups"; +@import "bootstrap/navs"; +@import "bootstrap/navbar"; +@import "bootstrap/breadcrumbs"; +@import "bootstrap/pagination"; +@import "bootstrap/pager"; +@import "bootstrap/labels"; +@import "bootstrap/badges"; +@import "bootstrap/jumbotron"; +@import "bootstrap/thumbnails"; +@import "bootstrap/alerts"; +@import "bootstrap/progress-bars"; +@import "bootstrap/list-group"; +@import "bootstrap/wells"; +@import "bootstrap/close"; + +// Components w/ JavaScript +@import "bootstrap/modals"; +@import "bootstrap/tooltip"; +@import "bootstrap/popovers"; +@import "bootstrap/carousel"; + +// Utility classes +.clearfix { + @include clearfix(); +} +.center-block { + @include center-block(); +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} +.hide { + display: none; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + @include text-hide(); +} +.hidden { + display: none !important; + visibility: hidden !important; +} +.affix { + position: fixed; +} + +@import "bootstrap/responsive-utilities"; + +// Labels +.label { + padding: 2px 4px; + font-size: 12px; + font-style: normal; + font-weight: normal; + display: inline-block; + + &.label-gray { + background-color: #eee; + color: #999; + text-shadow: none; + } + + &.label-inverse { + background-color: #333333; + } +} + +// Nav tabs +.nav.nav-tabs { + li { + > a { + padding: 8px 20px; + margin-right: 7px; + line-height: 20px; + border-color: #EEE; + color: #888; + border-bottom: 1px solid #ddd; + .badge { + background-color: #eee; + color: #888; + text-shadow: 0 1px 1px #fff; + } + i[class^="icon-"] { + line-height: 14px; + } + } + &.active { + > a { + border-color: #CCC; + border-bottom: 1px solid #fff; + color: #333; + font-weight: bold; + } + } + } + + &.nav-small-tabs > li > a { + padding: 6px 9px; + } +} + +.nav-tabs > li > a, +.nav-pills > li > a { + color: #666; +} + +.nav-small > li > a { + padding: 3px 5px; + font-size: 12px; +} + + +/* + * Callouts from Bootstrap3 docs + * + * Not quite alerts, but custom and helpful notes for folks reading the docs. + * Requires a base and modifier class. + */ + +/* Common styles for all types */ +.bs-callout { + margin: 20px 0; + padding: 20px; + border-left: 3px solid #eee; + color: #666; + background: #f9f9f9; +} +.bs-callout h4 { + margin-top: 0; + margin-bottom: 5px; +} +.bs-callout p:last-child { + margin-bottom: 0; +} + +/* Variations */ +.bs-callout-danger { + background-color: #fdf7f7; + border-color: #eed3d7; + color: #b94a48; +} +.bs-callout-warning { + background-color: #faf8f0; + border-color: #faebcc; + color: #8a6d3b; +} +.bs-callout-info { + background-color: #f4f8fa; + border-color: #bce8f1; + color: #34789a; +} +.bs-callout-success { + background-color: #dff0d8; + border-color: #5cA64d; + color: #3c763d; +} + +// Breadcrumb +ul.breadcrumb { + background: white; + border: none; + li { + display: inline; + text-shadow: 0 1px 0 white + } + + a { + font-size: 16px; + } +} + +/** + * fix to keep tooltips position in top navigation bar + * + */ +.navbar .nav > li { + position: relative; + white-space: nowrap; +} diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index 129d33dcac342e3719edf971ceec0429e91e3e07..ca51da3fdd4bceb5a2cb50ca49dd4d65bac5d149 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -1,71 +1,199 @@ -.dark .highlight { +.dark { + background-color: #232323; - background-color: #333; + .line.hll { + background: #558; + } + + .highlight{ + border-left: 1px solid #444; + } + + .no-highlight { + color: #DDD; + } + + .line-numbers a { + color: #666; + } pre { - background-color: #333; - color: #eee; - } - - .hll { display: block; background-color: darken($hover, 65%) } - .c { color: #888888; font-style: italic } /* Comment */ - .err { color: #a61717; background-color: #e3d2d2 } /* Error */ - .k { color: #CDA869; font-weight: bold } /* Keyword */ - .kp { color: #CDA869; font-weight: bold } /* Keyword */ - .cm { color: #888888 } /* Comment.Multiline */ - .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ - .c1 { color: #888888 } /* Comment.Single */ - .cs { color: #cc0000; font-weight: bold; background-color: transparent } /* Comment.Special */ - .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ - .ge { font-style: italic } /* Generic.Emph */ - .gr { color: #aa0000 } /* Generic.Error */ - .gh { color: #303030 } /* Generic.Heading */ - .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ - .go { color: #888888 } /* Generic.Output */ - .gp { color: #555555 } /* Generic.Prompt */ - .gs { font-weight: bold } /* Generic.Strong */ - .gu { color: #606060 } /* Generic.Subheading */ - .gt { color: #aa0000 } /* Generic.Traceback */ - .kc{font-weight: bold;} /* Keyword.Constant */ - .kd{font-weight: bold;} /* Keyword.Declaration */ - .kn{font-weight: bold;} /* Keyword.Namespace */ - .kp{font-weight: bold;} /* Keyword.Pseudo */ - .kr{font-weight: bold;} /* Keyword.Reserved */ - .kt{color: #458;font-weight: bold;} /* Keyword.Type */ - .m { color: #0000DD; font-weight: bold } /* Literal.Number */ - .p { color: #eee; } - .s { color: #0AD; background-color: transparent } /* Literal.String */ - .na{color: #008080;} /* Name.Attribute */ - .nb{color: #0086B3;} /* Name.Builtin */ - .nc{color: #ccc;font-weight: bold;} /* Name.Class */ - .no{color: turquoise;} /* Name.Constant */ - .ni{color: #800080;} - .ne{color: #900;font-weight: bold;} /* Name.Exception */ - .nf{color: #ccc;font-weight: bold;} /* Name.Function */ - .nn{color: #79C3E0;font-weight: bold;} /* Name.Namespace */ - .nt{color: #fc5;} /* Name.Tag */ - .nv{color: #FA4;} /* Name.Variable */ - .py { color: #336699; font-weight: bold } /* Name.Property */ - .ow { color: #008800 } /* Operator.Word */ - .w { color: #bbbbbb } /* Text.Whitespace */ - .mf { color: #7AC; font-weight: bold } /* Literal.Number.Float */ - .mh { color: #7AC; font-weight: bold } /* Literal.Number.Hex */ - .mi {color: #099;} /* Literal.Number.Integer */ - .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ - .sb { color: #dd2200; background-color: transparent; } /* Literal.String.Backtick */ - .sc{color: #d14;} /* Literal.String.Char */ - .sd { color: #dd2200; background-color: transparent; } /* Literal.String.Doc */ - .s2{color: orange;} /* Literal.String.Double */ - .se{color: orange;} /* Literal.String.Escape */ - .sh{color: orange;} /* Literal.String.Heredoc */ - .si{color: orange;} /* Literal.String.Interpol */ - .sx{color: orange;} /* Literal.String.Other */ - .sr{color: orange;} /* Literal.String.Regex */ - .s1{color: orange;} /* Literal.String.Single */ - .ss{color: orange;} /* Literal.String.Symbol */ - .bp { color: #D58 } /* Name.Builtin.Pseudo */ - .vc { color: #336699 } /* Name.Variable.Class */ - .vg { color: #dd7700 } /* Name.Variable.Global */ - .vi { color: cyan } -} + background-color: #232323; + } + + .hljs { + display: block; + background: #232323; + color: #E6E1DC; + } + + .hljs-comment, + .hljs-template_comment, + .hljs-javadoc, + .hljs-shebang { + color: #BC9458; + font-style: italic; + } + + .hljs-keyword, + .ruby .hljs-function .hljs-keyword, + .hljs-request, + .hljs-status, + .nginx .hljs-title, + .method, + .hljs-list .hljs-title { + color: #C26230; + } + + .hljs-string, + .hljs-number, + .hljs-regexp, + .hljs-tag .hljs-value, + .hljs-cdata, + .hljs-filter .hljs-argument, + .hljs-attr_selector, + .apache .hljs-cbracket, + .hljs-date, + .tex .hljs-command, + .markdown .hljs-link_label { + color: #A5C261; + } + + .hljs-subst { + color: #519F50; + } + + .hljs-tag, + .hljs-tag .hljs-keyword, + .hljs-tag .hljs-title, + .hljs-doctype, + .hljs-sub .hljs-identifier, + .hljs-pi, + .input_number { + color: #E8BF6A; + } + + .hljs-identifier { + color: #D0D0FF; + } + + .hljs-class .hljs-title, + .haskell .hljs-type, + .smalltalk .hljs-class, + .hljs-javadoctag, + .hljs-yardoctag, + .hljs-phpdoc { + text-decoration: none; + } + + .hljs-constant { + color: #DA4939; + } + + + .hljs-symbol, + .hljs-built_in, + .ruby .hljs-symbol .hljs-string, + .ruby .hljs-symbol .hljs-identifier, + .markdown .hljs-link_url, + .hljs-attribute { + color: #6D9CBE; + } + + .markdown .hljs-link_url { + text-decoration: underline; + } + + + .hljs-params, + .hljs-variable, + .clojure .hljs-attribute { + color: #D0D0FF; + } + + .css .hljs-tag, + .hljs-rules .hljs-property, + .hljs-pseudo, + .tex .hljs-special { + color: #CDA869; + } + + .css .hljs-class { + color: #9B703F; + } + + .hljs-rules .hljs-keyword { + color: #C5AF75; + } + + .hljs-rules .hljs-value { + color: #CF6A4C; + } + + .css .hljs-id { + color: #8B98AB; + } + + .hljs-annotation, + .apache .hljs-sqbracket, + .nginx .hljs-built_in { + color: #9B859D; + } + + .hljs-preprocessor, + .hljs-preprocessor *, + .hljs-pragma { + color: #8996A8 !important; + } + + .hljs-hexcolor, + .css .hljs-value .hljs-number { + color: #A5C261; + } + + .hljs-title, + .hljs-decorator, + .css .hljs-function { + color: #FFC66D; + } + + .diff .hljs-header, + .hljs-chunk { + background-color: #2F33AB; + color: #E6E1DC; + display: inline-block; + width: 100%; + } + + .diff .hljs-change { + background-color: #4A410D; + color: #F8F8F8; + display: inline-block; + width: 100%; + } + + .hljs-addition { + background-color: #144212; + color: #E6E1DC; + display: inline-block; + width: 100%; + } + + .hljs-deletion { + background-color: #600; + color: #E6E1DC; + display: inline-block; + width: 100%; + } + + .coffeescript .javascript, + .javascript .xml, + .tex .hljs-formula, + .xml .javascript, + .xml .vbscript, + .xml .css, + .xml .hljs-cdata { + opacity: 0.7; + } +} diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index c9709fa7f123fa25eafac235e6127fb88a6aa841..36bc5df2f44136ff5c903a5ad4a523ffa48e5e6c 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -1,89 +1,148 @@ -$monokai-fg: #f8f8f2; -$monokai-comment: #75715e; -$monokai-pink: #f92672; -$monokai-blue: #66d9ef; -$monokai-green: #a6e22e; -$monokai-gold: #e6db74; -$monokai-dark: #3b3a32; -$monokai-purple: #ae81ff; +.monokai { + background-color: #272822; -.monokai .highlight { + .highlight{ + border-left: 1px solid #444; + } - background-color: #272822; + .line.hll { + background: #558; + } + + .no-highlight { + color: #DDD; + } + + .line-numbers a { + color: #666; + } pre { background-color: #272822; - color: $monokai-fg; + color: #f8f8f2; } - .hll { background-color: darken($hover, 65%) } - .c { color: $monokai-comment } /* Comment */ - .err { color: $monokai-fg } /* Error */ - .g { color: $monokai-fg } /* Generic */ - .k { color: $monokai-pink } /* Keyword */ - .l { color: $monokai-fg } /* Literal */ - .n { color: $monokai-blue } /* Name */ - .o { color: $monokai-fg } /* Operator */ - .x { color: $monokai-fg } /* Other */ - .p { color: $monokai-fg } /* Punctuation */ - .cm { color: $monokai-comment } /* Comment.Multiline */ - .cp { color: $monokai-comment } /* Comment.Preproc */ - .c1 { color: $monokai-comment } /* Comment.Single */ - .cs { color: $monokai-comment } /* Comment.Special */ - .gd { color: #8b0807 } /* Generic.Deleted */ - .ge { color: $monokai-fg; text-decoration: underline } /* Generic.Emph */ - .gr { color: $monokai-fg } /* Generic.Error */ - .gh { color: $monokai-fg; font-weight: bold } /* Generic.Heading */ - .gi { color: $monokai-fg; font-weight: bold; background-color: #46830c } /* Generic.Inserted */ - .go { color: $monokai-dark; background-color: #31322c } /* Generic.Output */ - .gp { color: $monokai-fg } /* Generic.Prompt */ - .gs { color: $monokai-fg } /* Generic.Strong */ - .gu { color: $monokai-fg; font-weight: bold } /* Generic.Subheading */ - .gt { color: #f8f8f0; background-color: $monokai-pink } /* Generic.Traceback */ - .kc { color: $monokai-purple } /* Keyword.Constant */ - .kd { color: $monokai-pink } /* Keyword.Declaration */ - .kn { color: $monokai-pink } /* Keyword.Namespace */ - .kp { color: $monokai-pink } /* Keyword.Pseudo */ - .kr { color: $monokai-pink } /* Keyword.Reserved */ - .kt { color: $monokai-fg } /* Keyword.Type */ - .ld { color: $monokai-fg } /* Literal.Date */ - .m { color: $monokai-purple } /* Literal.Number */ - .s { color: $monokai-gold } /* Literal.String */ - .na { color: $monokai-purple } /* Name.Attribute */ - .nb { color: $monokai-blue } /* Name.Builtin */ - .nc { color: $monokai-fg } /* Name.Class */ - .no { color: $monokai-fg } /* Name.Constant */ - .nd { color: $monokai-fg } /* Name.Decorator */ - .ni { color: $monokai-fg } /* Name.Entity */ - .ne { color: $monokai-fg } /* Name.Exception */ - .nf { color: $monokai-green } /* Name.Function */ - .nl { color: $monokai-gold } /* Name.Label */ - .nn { color: $monokai-fg } /* Name.Namespace */ - .nx { color: $monokai-fg } /* Name.Other */ - .nt { color: $monokai-pink } /* Name.Tag */ - .nv { color: $monokai-blue; font-style: italic } /* Name.Variable */ - .py { color: $monokai-fg } /* Name.Property */ - .ow { color: $monokai-pink } /* Operator.Word */ - .w { color: $monokai-fg } /* Text.Whitespace */ - .mf { color: $monokai-purple } /* Literal.Number.Float */ - .mh { color: $monokai-purple } /* Literal.Number.Hex */ - .mi { color: $monokai-purple } /* Literal.Number.Integer */ - .mo { color: $monokai-purple } /* Literal.Number.Oct */ - .sb { color: $monokai-gold } /* Literal.String.Backtick */ - .sc { color: $monokai-gold } /* Literal.String.Char */ - .sd { color: $monokai-gold } /* Literal.String.Doc */ - .s2 { color: $monokai-gold } /* Literal.String.Double */ - .se { color: $monokai-gold } /* Literal.String.Escape */ - .sh { color: $monokai-gold } /* Literal.String.Heredoc */ - .si { color: $monokai-gold } /* Literal.String.Interpol */ - .sx { color: $monokai-gold } /* Literal.String.Other */ - .sr { color: $monokai-gold } /* Literal.String.Regex */ - .s1 { color: $monokai-gold } /* Literal.String.Single */ - .ss { color: $monokai-gold } /* Literal.String.Symbol */ - .bp { color: $monokai-fg } /* Name.Builtin.Pseudo */ - .vc { color: $monokai-blue; font-style: italic } /* Name.Variable.Class */ - .vg { color: $monokai-blue; font-style: italic } /* Name.Variable.Global */ - .vi { color: $monokai-blue; font-style: italic } /* Name.Variable.Instance */ - .il { color: $monokai-purple } /* Literal.Number.Integer.Long */ -} + .hljs { + display: block; + background: #272822; + } + + .hljs-tag, + .hljs-tag .hljs-title, + .hljs-keyword, + .hljs-literal, + .hljs-strong, + .hljs-change, + .hljs-winutils, + .hljs-flow, + .lisp .hljs-title, + .clojure .hljs-built_in, + .nginx .hljs-title, + .tex .hljs-special { + color: #F92672; + } + + .hljs { + color: #DDD; + } + .hljs .hljs-constant, + .asciidoc .hljs-code { + color: #66D9EF; + } + + .hljs-code, + .hljs-class .hljs-title, + .hljs-header { + color: white; + } + + .hljs-link_label, + .hljs-attribute, + .hljs-symbol, + .hljs-symbol .hljs-string, + .hljs-value, + .hljs-regexp { + color: #BF79DB; + } + + .hljs-link_url, + .hljs-tag .hljs-value, + .hljs-string, + .hljs-bullet, + .hljs-subst, + .hljs-title, + .hljs-emphasis, + .haskell .hljs-type, + .hljs-preprocessor, + .hljs-pragma, + .ruby .hljs-class .hljs-parent, + .hljs-built_in, + .sql .hljs-aggregate, + .django .hljs-template_tag, + .django .hljs-variable, + .smalltalk .hljs-class, + .hljs-javadoc, + .django .hljs-filter .hljs-argument, + .smalltalk .hljs-localvars, + .smalltalk .hljs-array, + .hljs-attr_selector, + .hljs-pseudo, + .hljs-addition, + .hljs-stream, + .hljs-envvar, + .apache .hljs-tag, + .apache .hljs-cbracket, + .tex .hljs-command, + .hljs-prompt { + color: #A6E22E; + } + + .hljs-comment, + .java .hljs-annotation, + .smartquote, + .hljs-blockquote, + .hljs-horizontal_rule, + .python .hljs-decorator, + .hljs-template_comment, + .hljs-pi, + .hljs-doctype, + .hljs-deletion, + .hljs-shebang, + .apache .hljs-sqbracket, + .tex .hljs-formula { + color: #75715E; + } + + .hljs-keyword, + .hljs-literal, + .css .hljs-id, + .hljs-phpdoc, + .hljs-title, + .hljs-header, + .haskell .hljs-type, + .vbscript .hljs-built_in, + .sql .hljs-aggregate, + .rsl .hljs-built_in, + .smalltalk .hljs-class, + .diff .hljs-header, + .hljs-chunk, + .hljs-winutils, + .bash .hljs-variable, + .apache .hljs-tag, + .tex .hljs-special, + .hljs-request, + .hljs-status { + font-weight: bold; + } + + .coffeescript .javascript, + .javascript .xml, + .tex .hljs-formula, + .xml .javascript, + .xml .vbscript, + .xml .css, + .xml .hljs-cdata { + opacity: 0.5; + } +} diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index cc82f39ac93472ba4a79eee345b6b71e43b66c58..b9bec225188be0caf73e231b93a9cef3c55e2d8c 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -1,80 +1,125 @@ -.solarized-dark .highlight { - +.solarized-dark { background-color: #002B36; - + + .highlight{ + border-left: 1px solid #113b46; + } + + .line.hll { + background: #000; + } + + .no-highlight { + color: #DDD; + } + pre { background-color: #002B36; color: #eee; } - .hll { background-color: #073642 } - .c { color: #586E75 } /* Comment */ - .err { color: #93A1A1 } /* Error */ - .g { color: #93A1A1 } /* Generic */ - .k { color: #859900 } /* Keyword */ - .l { color: #93A1A1 } /* Literal */ - .n { color: #93A1A1 } /* Name */ - .o { color: #859900 } /* Operator */ - .x { color: #CB4B16 } /* Other */ - .p { color: #93A1A1 } /* Punctuation */ - .cm { color: #586E75 } /* Comment.Multiline */ - .cp { color: #859900 } /* Comment.Preproc */ - .c1 { color: #586E75 } /* Comment.Single */ - .cs { color: #859900 } /* Comment.Special */ - .gd { color: #2AA198 } /* Generic.Deleted */ - .ge { color: #93A1A1; font-style: italic } /* Generic.Emph */ - .gr { color: #DC322F } /* Generic.Error */ - .gh { color: #CB4B16 } /* Generic.Heading */ - .gi { color: #859900 } /* Generic.Inserted */ - .go { color: #93A1A1 } /* Generic.Output */ - .gp { color: #93A1A1 } /* Generic.Prompt */ - .gs { color: #93A1A1; font-weight: bold } /* Generic.Strong */ - .gu { color: #CB4B16 } /* Generic.Subheading */ - .gt { color: #93A1A1 } /* Generic.Traceback */ - .kc { color: #CB4B16 } /* Keyword.Constant */ - .kd { color: #268BD2 } /* Keyword.Declaration */ - .kn { color: #859900 } /* Keyword.Namespace */ - .kp { color: #859900 } /* Keyword.Pseudo */ - .kr { color: #268BD2 } /* Keyword.Reserved */ - .kt { color: #DC322F } /* Keyword.Type */ - .ld { color: #93A1A1 } /* Literal.Date */ - .m { color: #2AA198 } /* Literal.Number */ - .s { color: #2AA198 } /* Literal.String */ - .na { color: #93A1A1 } /* Name.Attribute */ - .nb { color: #B58900 } /* Name.Builtin */ - .nc { color: #268BD2 } /* Name.Class */ - .no { color: #CB4B16 } /* Name.Constant */ - .nd { color: #268BD2 } /* Name.Decorator */ - .ni { color: #CB4B16 } /* Name.Entity */ - .ne { color: #CB4B16 } /* Name.Exception */ - .nf { color: #268BD2 } /* Name.Function */ - .nl { color: #93A1A1 } /* Name.Label */ - .nn { color: #93A1A1 } /* Name.Namespace */ - .nx { color: #93A1A1 } /* Name.Other */ - .py { color: #93A1A1 } /* Name.Property */ - .nt { color: #268BD2 } /* Name.Tag */ - .nv { color: #268BD2 } /* Name.Variable */ - .ow { color: #859900 } /* Operator.Word */ - .w { color: #93A1A1 } /* Text.Whitespace */ - .mf { color: #2AA198 } /* Literal.Number.Float */ - .mh { color: #2AA198 } /* Literal.Number.Hex */ - .mi { color: #2AA198 } /* Literal.Number.Integer */ - .mo { color: #2AA198 } /* Literal.Number.Oct */ - .sb { color: #586E75 } /* Literal.String.Backtick */ - .sc { color: #2AA198 } /* Literal.String.Char */ - .sd { color: #93A1A1 } /* Literal.String.Doc */ - .s2 { color: #2AA198 } /* Literal.String.Double */ - .se { color: #CB4B16 } /* Literal.String.Escape */ - .sh { color: #93A1A1 } /* Literal.String.Heredoc */ - .si { color: #2AA198 } /* Literal.String.Interpol */ - .sx { color: #2AA198 } /* Literal.String.Other */ - .sr { color: #DC322F } /* Literal.String.Regex */ - .s1 { color: #2AA198 } /* Literal.String.Single */ - .ss { color: #2AA198 } /* Literal.String.Symbol */ - .bp { color: #268BD2 } /* Name.Builtin.Pseudo */ - .vc { color: #268BD2 } /* Name.Variable.Class */ - .vg { color: #268BD2 } /* Name.Variable.Global */ - .vi { color: #268BD2 } /* Name.Variable.Instance */ - .il { color: #2AA198 } /* Literal.Number.Integer.Long */ -} + .line-numbers a { + color: #666; + } + + .hljs { + display: block; + background: #002b36; + color: #839496; + } + + .hljs-comment, + .hljs-template_comment, + .diff .hljs-header, + .hljs-doctype, + .hljs-pi, + .lisp .hljs-string, + .hljs-javadoc { + color: #586e75; + } + + /* Solarized Green */ + .hljs-keyword, + .hljs-winutils, + .method, + .hljs-addition, + .css .hljs-tag, + .hljs-request, + .hljs-status, + .nginx .hljs-title { + color: #859900; + } + /* Solarized Cyan */ + .hljs-number, + .hljs-command, + .hljs-string, + .hljs-tag .hljs-value, + .hljs-rules .hljs-value, + .hljs-phpdoc, + .tex .hljs-formula, + .hljs-regexp, + .hljs-hexcolor, + .hljs-link_url { + color: #2aa198; + } + + /* Solarized Blue */ + .hljs-title, + .hljs-localvars, + .hljs-chunk, + .hljs-decorator, + .hljs-built_in, + .hljs-identifier, + .vhdl .hljs-literal, + .hljs-id, + .css .hljs-function { + color: #268bd2; + } + + /* Solarized Yellow */ + .hljs-attribute, + .hljs-variable, + .lisp .hljs-body, + .smalltalk .hljs-number, + .hljs-constant, + .hljs-class .hljs-title, + .hljs-parent, + .haskell .hljs-type, + .hljs-link_reference { + color: #b58900; + } + + /* Solarized Orange */ + .hljs-preprocessor, + .hljs-preprocessor .hljs-keyword, + .hljs-pragma, + .hljs-shebang, + .hljs-symbol, + .hljs-symbol .hljs-string, + .diff .hljs-change, + .hljs-special, + .hljs-attr_selector, + .hljs-subst, + .hljs-cdata, + .clojure .hljs-title, + .css .hljs-pseudo, + .hljs-header { + color: #cb4b16; + } + + /* Solarized Red */ + .hljs-deletion, + .hljs-important { + color: #dc322f; + } + + /* Solarized Violet */ + .hljs-link_label { + color: #6c71c4; + } + + .tex .hljs-formula { + background: #073642; + } +} diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index df127a7c49180ac494768fc908cb09f097ede716..880387a3483371e73da15542caf18bd8c511a720 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -1,70 +1,178 @@ -.white .highlight { - +.white { background-color: #fff; - + + .line.hll { + background: #FFA; + } + + .highlight{ + border-left: 1px solid #eee; + } + pre { background-color: #fff; color: #333; } - .hll { display: block; background-color: $hover } - .c { color: #888888; font-style: italic } /* Comment */ - .err { color: #a61717; background-color: #e3d2d2 } /* Error */ - .k { color: #000000; font-weight: bold } /* Keyword */ - .cm { color: #888888 } /* Comment.Multiline */ - .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ - .c1 { color: #888888 } /* Comment.Single */ - .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ - .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ - .ge { font-style: italic } /* Generic.Emph */ - .gr { color: #aa0000 } /* Generic.Error */ - .gh { color: #303030 } /* Generic.Heading */ - .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ - .go { color: #888888 } /* Generic.Output */ - .gp { color: #555555 } /* Generic.Prompt */ - .gs { font-weight: bold } /* Generic.Strong */ - .gu { color: #606060 } /* Generic.Subheading */ - .gt { color: #aa0000 } /* Generic.Traceback */ - .kc{font-weight: bold;} /* Keyword.Constant */ - .kd{font-weight: bold;} /* Keyword.Declaration */ - .kn{font-weight: bold;} /* Keyword.Namespace */ - .kp{font-weight: bold;} /* Keyword.Pseudo */ - .kr{font-weight: bold;} /* Keyword.Reserved */ - .kt{color: #458;font-weight: bold;} /* Keyword.Type */ - .m { color: #0000DD; font-weight: bold } /* Literal.Number */ - .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ - .na{color: #008080;} /* Name.Attribute */ - .nb{color: #0086B3;} /* Name.Builtin */ - .nc{color: #458;font-weight: bold;} /* Name.Class */ - .no{color: #008080;} /* Name.Constant */ - .ni{color: #800080;} - .ne{color: #900;font-weight: bold;} /* Name.Exception */ - .nf{color: #900;font-weight: bold;} /* Name.Function */ - .nn{color: #005;font-weight: bold;} /* Name.Namespace */ - .nt{color: #000080;} /* Name.Tag */ - .nv{color: #008080;} /* Name.Variable */ - .py { color: #336699; font-weight: bold } /* Name.Property */ - .ow { color: #008800 } /* Operator.Word */ - .w { color: #bbbbbb } /* Text.Whitespace */ - .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ - .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ - .mi {color: #099;} /* Literal.Number.Integer */ - .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ - .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ - .sc{color: #d14;} /* Literal.String.Char */ - .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ - .s2{color: #d14;} /* Literal.String.Double */ - .se{color: #d14;} /* Literal.String.Escape */ - .sh{color: #d14;} /* Literal.String.Heredoc */ - .si{color: #d14;} /* Literal.String.Interpol */ - .sx{color: #d14;} /* Literal.String.Other */ - .sr{color: #d14;} /* Literal.String.Regex */ - .s1{color: #d14;} /* Literal.String.Single */ - .ss{color: #d14;} /* Literal.String.Symbol */ - .bp { color: #003388 } /* Name.Builtin.Pseudo */ - .vc { color: #336699 } /* Name.Variable.Class */ - .vg { color: #dd7700 } /* Name.Variable.Global */ - .vi { color: #3333bb } + .hljs { + background: #FFF; + } + + .line-numbers a { + color: #999; + } + + .hljs { + display: block; + background: #fff; color: black; + } + + .hljs-comment, + .hljs-template_comment, + .hljs-javadoc, + .hljs-comment * { + color: #006a00; + } + + .hljs-keyword, + .hljs-literal, + .nginx .hljs-title { + color: #aa0d91; + } + .method, + .hljs-list .hljs-title, + .hljs-tag .hljs-title, + .setting .hljs-value, + .hljs-winutils, + .tex .hljs-command, + .http .hljs-title, + .hljs-request, + .hljs-status { + color: #008; + } + + .hljs-envvar, + .tex .hljs-special { + color: #660; + } + + .hljs-string { + color: #c41a16; + } + .hljs-tag .hljs-value, + .hljs-cdata, + .hljs-filter .hljs-argument, + .hljs-attr_selector, + .apache .hljs-cbracket, + .hljs-date, + .hljs-regexp { + color: #080; + } + + .hljs-sub .hljs-identifier, + .hljs-pi, + .hljs-tag, + .hljs-tag .hljs-keyword, + .hljs-decorator, + .ini .hljs-title, + .hljs-shebang, + .hljs-prompt, + .hljs-hexcolor, + .hljs-rules .hljs-value, + .hljs-symbol, + .hljs-symbol .hljs-string, + .hljs-number, + .css .hljs-function, + .clojure .hljs-title, + .clojure .hljs-built_in, + .hljs-function .hljs-title, + .coffeescript .hljs-attribute { + color: #1c00cf; + } + + .hljs-class .hljs-title, + .haskell .hljs-type, + .smalltalk .hljs-class, + .hljs-javadoctag, + .hljs-yardoctag, + .hljs-phpdoc, + .hljs-typename, + .hljs-tag .hljs-attribute, + .hljs-doctype, + .hljs-class .hljs-id, + .hljs-built_in, + .setting, + .hljs-params, + .clojure .hljs-attribute { + color: #5c2699; + } + + .hljs-variable { + color: #3f6e74; + } + .css .hljs-tag, + .hljs-rules .hljs-property, + .hljs-pseudo, + .hljs-subst { + color: #000; + } + + .css .hljs-class, + .css .hljs-id { + color: #9B703F; + } + + .hljs-value .hljs-important { + color: #ff7700; + font-weight: bold; + } + + .hljs-rules .hljs-keyword { + color: #C5AF75; + } + + .hljs-annotation, + .apache .hljs-sqbracket, + .nginx .hljs-built_in { + color: #9B859D; + } + + .hljs-preprocessor, + .hljs-preprocessor *, + .hljs-pragma { + color: #643820; + } + + .tex .hljs-formula { + background-color: #EEE; + font-style: italic; + } + + .diff .hljs-header, + .hljs-chunk { + color: #808080; + font-weight: bold; + } + + .diff .hljs-change { + background-color: #BCCFF9; + } + + .hljs-addition { + background-color: #BAEEBA; + } + + .hljs-deletion { + background-color: #FFC8BD; + } + + .hljs-comment .hljs-yardoctag { + font-weight: bold; + } + + .method .hljs-id { + color: #000; + } } .shadow { diff --git a/app/assets/stylesheets/gitlab_bootstrap/fonts.scss b/app/assets/stylesheets/main/fonts.scss similarity index 100% rename from app/assets/stylesheets/gitlab_bootstrap/fonts.scss rename to app/assets/stylesheets/main/fonts.scss diff --git a/app/assets/stylesheets/main/layout.scss b/app/assets/stylesheets/main/layout.scss new file mode 100644 index 0000000000000000000000000000000000000000..9e009a5e0ad568b79824dd75d653db46327b4da3 --- /dev/null +++ b/app/assets/stylesheets/main/layout.scss @@ -0,0 +1,20 @@ +html { + overflow-y: scroll; + + &.touch .tooltip { display: none !important; } +} + +body { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + margin-bottom: 20px; +} + +.container { + padding-top: 0; + z-index: 5; +} + +.container .content { + margin: 0 0; +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/mixins.scss b/app/assets/stylesheets/main/mixins.scss similarity index 79% rename from app/assets/stylesheets/gitlab_bootstrap/mixins.scss rename to app/assets/stylesheets/main/mixins.scss index 8b975a12cf75f25e990e31e7ca417ec0ec3ff24e..4afe61d756c05b5d79e96dd4ba09b89e2b57e3c5 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/mixins.scss +++ b/app/assets/stylesheets/main/mixins.scss @@ -25,6 +25,7 @@ background-image: -webkit-gradient(linear, 0 0, 0 100%, from($from), to($to)); background-image: -webkit-linear-gradient($from, $to); background-image: -moz-linear-gradient($from, $to); + background-image: -ms-linear-gradient($from, $to); background-image: -o-linear-gradient($from, $to); } @@ -45,6 +46,7 @@ background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #f5f5f5), to(#e1e1e1)); background-image: -webkit-linear-gradient(#f5f5f5 6.6%, #e1e1e1); background-image: -moz-linear-gradient(#f5f5f5 6.6%, #e1e1e1); + background-image: -ms-linear-gradient(#f5f5f5 6.6%, #e1e1e1); background-image: -o-linear-gradient(#f5f5f5 6.6%, #e1e1e1); } @@ -53,6 +55,7 @@ background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); + background-image: -ms-linear-gradient(#eee 6.6%, #dfdfdf); background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); } @@ -60,6 +63,7 @@ background: #eee; background-image: -webkit-linear-gradient(#e9e9e9, #d7d7d7); background-image: -moz-linear-gradient(#e9e9e9, #d7d7d7); + background-image: -ms-linear-gradient(#e9e9e9, #d7d7d7); background-image: -o-linear-gradient(#e9e9e9, #d7d7d7); } @@ -75,20 +79,40 @@ color: $style_color; text-shadow: 0 1px 1px #FFF; font-size: 16px; - line-height: 40px; + line-height: 44px; font-weight: normal; } @mixin md-typography { + img { + max-width: 100%; + } + *:first-child { margin-top: 0; } code { padding: 0 4px; } - h1 { margin-top: 30px;} - h2 { margin-top: 25px;} - h3 { margin-top: 20px;} - h4 { margin-top: 15px;} + + h1 { + margin-top: 45px; + font-size: 2.5em; + } + + h2 { + margin-top: 40px; + font-size: 2em; + } + + h3 { + margin-top: 35px; + font-size: 1.5em; + } + + h4 { + margin-top: 30px; + font-size: 1.2em; + } blockquote p { color: #888; @@ -103,10 +127,20 @@ background: #EEE; } } + + p > code { + font-size: inherit; + font-weight: inherit; + color: #555; + } + + li { + line-height: 1.5; + } } @mixin page-title { - color: $style_color; + color: #333; font-size: 20px; line-height: 1.5; margin-top: 0px; diff --git a/app/assets/stylesheets/gitlab_bootstrap/variables.scss b/app/assets/stylesheets/main/variables.scss similarity index 87% rename from app/assets/stylesheets/gitlab_bootstrap/variables.scss rename to app/assets/stylesheets/main/variables.scss index aeabe7ad2e83be87ee201e6971e182ff27b4162e..decc5f5046918e77967e5e3c70abe59cfe955998 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/variables.scss +++ b/app/assets/stylesheets/main/variables.scss @@ -4,6 +4,7 @@ $primary_color: #2FA0BB; $link_color: #3A89A3; $style_color: #474D57; +$bg_style_color: #2299BB; $hover: #D9EDF7; /** diff --git a/app/assets/stylesheets/sections/admin.scss b/app/assets/stylesheets/sections/admin.scss index e189fd27ac670af95b1cb2fc03a394692531dfb1..8ad9bc732b268cbc61b2d9e7b82393f282864b90 100644 --- a/app/assets/stylesheets/sections/admin.scss +++ b/app/assets/stylesheets/sections/admin.scss @@ -20,4 +20,19 @@ label { width: 110px; } .controls { margin-left: 130px; } .form-actions { padding-left: 130px; background: #fff } + .visibility-levels { + .controls { + margin-bottom: 9px; + } + + i { + color: inherit; + } + } +} + +.broadcast-messages { + .message { + line-height: 2; + } } diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss index be6fb29c817630852410d1e6a0af754e05b2c9f2..ff293bc4a0063e8d213686567d10e8e44cda4502 100644 --- a/app/assets/stylesheets/sections/commits.scss +++ b/app/assets/stylesheets/sections/commits.scss @@ -16,36 +16,29 @@ .header { @extend .clearfix; + background: #DDD; + border-bottom: 1px solid #CCC; padding: 5px 5px 5px 10px; color: #555; - border-bottom: 1px solid #CCC; - background: #eee; - // TODO Replace with linear-gradient mixin - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); - - a{ - color: $style_color; - } > span { font-family: $monospace_font; font-size: 14px; - line-height: 30px; + line-height: 2; } - a.view-file{ + .view-file { font-weight: bold; + float: right; + background-color: #EEE; } - .commit-short-id{ + .commit-short-id { font-family: $monospace_font; font-size: smaller; } - .file-mode{ + .file-mode { font-family: $monospace_font; } } @@ -55,13 +48,13 @@ background: #FFF; color: #333; font-size: 12px; - .old{ - span.idiff{ + .old { + span.idiff { background-color: #FAA; } } - .new{ - span.idiff{ + .new { + span.idiff { background-color: #AFA; } } @@ -77,7 +70,7 @@ font-size: 12px; } } - .old_line, .new_line { + .old_line, .new_line, .diff_line { margin: 0px; padding: 0px; border: none; @@ -87,7 +80,7 @@ border-right: 1px solid #ccc; text-align: right; min-width: 35px; - max-width: 35px; + max-width: 50px; width: 35px; @include user-select(none); a { @@ -99,6 +92,15 @@ text-decoration: underline; } } + &.new { + background: #CFD; + } + &.old { + background: #FDD; + } + } + .diff_line { + padding: 0; } .line_holder { &.old .old_line, @@ -129,6 +131,11 @@ color: #ccc; background: #fafafa; } + &.parallel { + display: table-cell; + overflow: hidden; + width: 50%; + } } } .image { @@ -293,6 +300,7 @@ background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); + background-image: -ms-linear-gradient(#eee 6.6%, #dfdfdf); background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); ul, li{ @@ -391,8 +399,8 @@ .commits-compare-switch{ background: url("switch_icon.png") no-repeat center center; - width: 22px; - height: 22px; + width: 32px; + height: 32px; text-indent: -9999px; float: left; margin-right: 9px; @@ -419,7 +427,6 @@ .commit-title { margin: 0; font-size: 20px; - font-weight: bold; } .commit-description { @@ -474,9 +481,13 @@ li.commit { font-family: $monospace_font; } + .str-truncated { + max-width: 70%; + } + .commit-row-message { - color: #555; - font-weight: bolder; + color: #333; + font-weight: 500; &:hover { color: #444; text-decoration: underline; @@ -485,13 +496,14 @@ li.commit { } .commit-row-info { + color: #777; + a { color: #777; } .committed_ago { float: right; - @extend .cgray; } } @@ -505,4 +517,4 @@ li.commit { @extend .cgray; } } -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/sections/dashboard.scss b/app/assets/stylesheets/sections/dashboard.scss index 3f7825d71ce43cc88cb0c2ba854e4dc78af7f477..1fd82c84fc98c612575d97b82aab69b60b5f9a05 100644 --- a/app/assets/stylesheets/sections/dashboard.scss +++ b/app/assets/stylesheets/sections/dashboard.scss @@ -1,11 +1,5 @@ .dashboard { - @extend .row; - .activities { - } - .side { - @extend .pull-right; - .ui-box { margin: 0px; box-shadow: none; @@ -20,7 +14,7 @@ .search-text-input { float:left; - @extend .span2; + @extend .col-md-2; } .btn { margin-left: 5px; @@ -32,14 +26,15 @@ .dash-filter { margin: 7px 0; padding: 4px 6px; - width: 202px; + width: 220px; float: left; + height: inherit; } } @media (max-width: 1200px) { .dashboard .dash-filter { - width: 132px; + width: 150px; } } @@ -51,7 +46,7 @@ li { &.active { a { - @include linear-gradient(#f5f5f5, #eee); + background-color: #EEE; border-bottom: 1px solid #EEE !important; &:hover { background: #eee; @@ -60,43 +55,64 @@ } a { - border-color: #CCC !important; + border-color: #DDD !important; } } } .project-row, .group-row { - padding: 10px 15px !important; + padding: 10px 12px !important; + font-size: 14px; + line-height: 24px; - .namespace-name { - color: #666; - font-weight: bold; + a { + display: block; } .project-name, .group-name { - font-size: 16px; + font-weight: 500; } .arrow { float: right; - padding: 10px 5px; + padding: 0px 5px; margin: 0; font-size: 20px; color: #666; } .last-activity { + float: right; + font-size: 12px; color: #AAA; display: block; - margin-top: 5px; .date { color: #777; } } } -.group-row { - .arrow { - padding: 2px 5px; +.project-access-icon { + margin-left: 10px; + float: left; + margin-right: 15px; + font-size: 20px; + margin-bottom: 15px; + border: 1px solid #EEE; + padding: 8px 12px; + border-radius: 50px; + background: #f5f5f5; + text-align: center; + + i { + color: #BBB; } } + +.dash-project-access-icon { + float: left; + margin-right: 3px; + color: #999; + margin-bottom: 10px; + width: 16px; +} diff --git a/app/assets/stylesheets/sections/editor.scss b/app/assets/stylesheets/sections/editor.scss index a71e5438936958f9a67ecf50d674b4b404f4dadd..057f7b7fd442c428118ed54893f1b839186a9747 100644 --- a/app/assets/stylesheets/sections/editor.scss +++ b/app/assets/stylesheets/sections/editor.scss @@ -34,15 +34,4 @@ margin: 5px 8px 0 8px; } } - .commit_message-group { - margin-top: 20px; - - label { - font-size: 16px; - line-height: 20px; - } - textarea { - @extend .span8; - } - } } diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/sections/events.scss index 39b2ad7a09cdf1deacee5b8aa1f9b701797b656d..801233519b6b291a3a2bcb46cfbfbe1d99dd04c1 100644 --- a/app/assets/stylesheets/sections/events.scss +++ b/app/assets/stylesheets/sections/events.scss @@ -47,7 +47,7 @@ .event-title { color: #333; font-weight: normal; - font-size: 15px; + font-size: 14px; .author_name { color: #333; } @@ -60,14 +60,14 @@ color: #666; } .event-note { - color: #555; + color: #666; margin-top: 5px; pre { border: none; background: #f9f9f9; border-radius: 0; - color: #555; + color: #666; margin: 0 20px; } @@ -75,6 +75,7 @@ margin-top: 4px; margin-left: 0px; max-width: 200px; + float: none; } p:last-child { @@ -112,6 +113,7 @@ &.commit { background: transparent; padding: 3px; + padding-left: 0; border: none; color: #666; .commit-row-title { @@ -121,6 +123,7 @@ &.commits-stat { display: block; padding: 3px; + padding-left: 0; &:hover { background: none; @@ -142,19 +145,19 @@ .filter_icon { a { text-align:center; - border-left: 3px solid $primary_color; - background: #f9f9f9; + background: #EEE; margin-bottom: 10px; float: left; - padding: 9px 7px; + padding: 9px 6px; font-size: 18px; - width: 26px; + width: 40px; + @include border-radius(3px); } &.inactive { a { color: #DDD; - border-left: 3px solid #EEE; + background: #f9f9f9; } } } diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss index bd72d08295a3646773f570cf8ec4f257cd860e96..96b7062ff2bfe9180b0671608b5b54a547243452 100644 --- a/app/assets/stylesheets/sections/header.scss +++ b/app/assets/stylesheets/sections/header.scss @@ -4,17 +4,24 @@ */ header { &.navbar-gitlab { + margin-bottom: 0; + min-height: 40px; + .navbar-inner { - height: 40px; - padding: 3px; background: #F1F1F1; + border-bottom: 1px solid #DDD; filter: none; .nav > li > a { color: $style_color; text-shadow: 0 1px 0 #fff; font-size: 14px; - padding: 10px; + line-height: 32px; + padding: 6px 10px; + + &:hover { + background: none; + } } /** NAV block with links and profile **/ @@ -22,6 +29,59 @@ header { float: right; margin-right: 0; } + + .navbar-toggle { + color: $style_color; + margin: 0 -15px 0 0; + padding: 10px; + border-radius: 0; + + button i { font-size: 22px; } + + &.collapsed { background-color: transparent !important;} + + &:hover { + background-color: #EEE; + } + } + } + + @media (max-width: $screen-xs-max) { + border-width: 0; + font-size: 18px; + + .app_logo { margin-left: -15px; } + .project_name { + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: top; + white-space: nowrap; + max-width: 70%; + } + + .navbar-collapse { + padding-right: 0; + padding-left: 0; + } + + .navbar-nav { + margin: 5px 0; + + .visible-xs, .visable-sm { + display: table-cell !important; + } + } + + li { + display: table-cell; + width: 1%; + + a { + text-align: center; + font-size: 18px !important; + } + } } } @@ -35,9 +95,6 @@ header { .app_logo { float: left; margin-right: 9px; - position: relative; - top: -5px; - padding-top: 5px; a { float: left; @@ -46,10 +103,10 @@ header { h1 { margin: 0; - background: url('logo-black.png') no-repeat center 1px; - background-size: 38px; + background: url('logo-black.png') no-repeat center center; + background-size: 32px; float: left; - height: 40px; + height: 46px; width: 40px; @include header-font; text-indent: -9999px; @@ -75,7 +132,7 @@ header { .profile-pic { position: relative; - top: -4px; + top: -1px; img { width: 26px; height: 26px; @@ -91,21 +148,25 @@ header { .search { margin-right: 10px; margin-left: 10px; + margin-top: 8px; + + form { + margin: 0; + padding: 0; + } .search-input { - @extend .span3; background-image: url("icon-search.png"); background-repeat: no-repeat; background-position: 10px; + height: inherit; + padding: 4px 6px; padding-left: 25px; font-size: 13px; @include border-radius(3px); border: 1px solid #c6c6c6; box-shadow: none; @include transition(all 0.15s ease-in 0s); - &:focus { - @extend .span4; - } } } @@ -120,6 +181,8 @@ header { background: #708090; border-bottom: 1px solid #AAA; + .navbar-toggle { color: #fff; } + .nav > li > a { color: #AAA; text-shadow: 0 1px 0 #444; @@ -133,6 +196,8 @@ header { .turbolink-spinner { color: #FFF; + font-size: 22px; + margin-right: 10px; } .search { @@ -152,8 +217,8 @@ header { .app_logo { a { h1 { - background: url('logo-white.png') no-repeat center 1px; - background-size: 38px; + background: url('logo-white.png') no-repeat center center; + background-size: 32px; color: #fff; text-shadow: 0 1px 1px #444; } @@ -181,12 +246,26 @@ header { .separator { float: left; height: 46px; - width: 1px; + width: 2px; background: white; border-left: 1px solid #DDD; - margin-top: -3px; margin-left: 10px; margin-right: 10px; } } +.search .search-input { + width: 300px; + &:focus { + width: 400px; + } +} + +@media (max-width: 1200px) { + .search .search-input { + width: 200px; + &:focus { + width: 300px; + } + } +} diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss index e384aebc76cb846e528543868745c5930d688d86..5afb13a86ba6dc0e63af97e4f4c77ecb9e8e2c83 100644 --- a/app/assets/stylesheets/sections/issues.scss +++ b/app/assets/stylesheets/sections/issues.scss @@ -60,7 +60,6 @@ input.check_all_issues { .btn.close_issue { color: #B94A48; font-weight: bold; - @include shade; &:hover { color: #B94A48; } @@ -68,7 +67,6 @@ input.check_all_issues { .btn.reopen_issue { color: #468847; font-weight: bold; - @include shade; &:hover { color: #468847; } @@ -77,8 +75,8 @@ input.check_all_issues { @media (min-width: 800px) { .issues_filters select { width: 160px; } } @media (min-width: 1200px) { .issues_filters select { width: 220px; } } -@media (min-width: 800px) { .issues_bulk_update select { width: 120px; } } -@media (min-width: 1200px) { .issues_bulk_update select { width: 160px; } } +@media (min-width: 800px) { .issues_bulk_update .select2-container { min-width: 120px; } } +@media (min-width: 1200px) { .issues_bulk_update .select2-container { min-width: 160px; } } .issues-holder { .issues_filters { @@ -103,3 +101,27 @@ input.check_all_issues { .participants { margin-bottom: 10px; } + +.issues_bulk_update { + .select2-container { + text-shadow: none; + } +} + +.issue-search-form { + margin: 0; + height: 24px; + + .issue_search { + border: 1px solid #DDD !important; + background-color: #f4f4f4; + } +} + +.issue-show-labels .label { + padding: 6px 10px; +} + +form.edit-issue { + margin: 0; +} diff --git a/app/assets/stylesheets/sections/login.scss b/app/assets/stylesheets/sections/login.scss index 33bef59c089d9b0a2889d26f28cb527a84e3acfc..a78a9cd487995fd005a8278d2c2085594ff7230a 100644 --- a/app/assets/stylesheets/sections/login.scss +++ b/app/assets/stylesheets/sections/login.scss @@ -1,48 +1,57 @@ /* Login Page */ -body.login-page{ - .container > .content { - padding-top: 20px; +.login-page { + h1 { + font-size: 3em; + font-weight: 200; } -} - -.login-box{ - width: 304px; - position: relative; - @include border-radius(5px); - margin: auto; - padding: 20px; - background: white; -} - -.login-box .login-logo{ - margin: 10px 0 30px 0; - display: block; -} - -.login-box input.text{background-color: #f1f1f1; font-size: 16px; @include border-radius(0); padding: 14px 10px; width: 280px} - -.login-box input.text.top{ - @include border-radius(5px 5px 0 0); - margin-bottom: 0px; -} -.login-box input.text.bottom{ - @include border-radius(0 0 5px 5px); - border-top: 0; - margin-bottom: 20px; -} + .login-box{ + max-width: 304px; + position: relative; + @include border-radius(5px); + margin: auto; + background: white; + } -.login-box input.text.middle{ - border-top: 0; - margin-bottom:0px; -} + .login-logo{ + margin: 10px 0 30px 0; + display: block; + } -.login-box a.forgot{float: right; padding-top: 6px} + .form-control { + background-color: #f1f1f1; + font-size: 16px; + padding: 14px 10px; + width: 100%; + height: auto; + + &.top { + @include border-radius(5px 5px 0 0); + margin-bottom: 0px; + } + + &.bottom { + @include border-radius(0 0 5px 5px); + border-top: 0; + margin-bottom: 20px; + } + + &.middle { + border-top: 0; + margin-bottom:0px; + @include border-radius(0); + } + } -.remember_me { - text-align: left; + .login-box a.forgot { + float: right; + padding-top: 6px + } - input { - margin: 2px; + .devise-errors { + h2 { + font-size: 14px; + color: #a00; + } } } diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss index aa61cae4b9a02d6cca0eae422aafc1a4cd17e18c..4388da007351aec1820e569df00ae5ca847ce9da 100644 --- a/app/assets/stylesheets/sections/merge_requests.scss +++ b/app/assets/stylesheets/sections/merge_requests.scss @@ -4,10 +4,6 @@ * */ .automerge_widget { - &.can_be_merged { - background: #DFF0D8; - } - form { margin-bottom: 0; .clearfix { @@ -15,32 +11,12 @@ } } - .accept_group { - float: left; - border: 1px solid #ADA; - padding: 2px; - @include border-radius(5px); - background: #CEB; - - .accept_merge_request { - font-size: 13px; - float: left; - } - .remove_branch_holder { - margin-left: 20px; - margin-right: 10px; - float: left; - } + .accept-group { label { - color: #444; - text-align: left + margin: 5px; + margin-left: 20px; } } - - - .how_to_merge_link { - @extend .primary; - } } .merge-request .nav-tabs{ @@ -53,11 +29,6 @@ } } -.merge-in-progress { - @extend .padded; - @extend .append-bottom-10; -} - .mr_source_commit, .mr_target_commit { .commit { @@ -110,9 +81,11 @@ .merge-request-angle { text-align: center; - margin: 0; + margin: 0 auto; + font-size: 2em; + line-height: 1.1; } .merge-request-form-info { - padding: 15px 0; + padding-top: 15px; } diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss index 54263523e85f439384e57f6c514b68b11a3fa6d4..372fa9669ca49e0ea8cf1cc8ec6a4251365e7e98 100644 --- a/app/assets/stylesheets/sections/nav.scss +++ b/app/assets/stylesheets/sections/nav.scss @@ -6,6 +6,7 @@ border-bottom: 1px solid #E1E1E1; ul { + padding: 0; margin: auto; height: 40px; overflow: hidden; @@ -34,7 +35,7 @@ width: 1%; &.active { a { - color: $style_color; + color: #333; font-weight: bolder; &:after { @@ -45,7 +46,7 @@ left: 50%; width: 0; height: 0; - border-color: transparent transparent #777 transparent; + border-color: transparent transparent #333 transparent; border-style: solid; border-width: 6px; margin-left: -6px; @@ -82,4 +83,38 @@ padding-top: 2px; } } + + @media (max-width: $screen-xs-max) { + font-size: 18px; + margin: 0; + + max-height: none; + + &, .container { + padding: 0; + border-top: 0; + } + + ul { + height: auto; + + li { + display: list-item; + width: auto; + padding: 5px 0; + + &.active { + background-color: $primary_color; + + a { + color: #fff; + font-weight: normal; + text-shadow: none; + + &:after { display: none; } + } + } + } + } + } } diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss index 94b9ca3b181722e8ef03fbd217d97631d6a6b8aa..9f5f1579fbde0e1b15072535b1b1bf19fa3b05b6 100644 --- a/app/assets/stylesheets/sections/notes.scss +++ b/app/assets/stylesheets/sections/notes.scss @@ -2,7 +2,7 @@ * Notes */ -@-webkit-keyframes target-note { +@-webkit-keyframes targe3-note { from { background:#fffff0; } 50% { background:#ffffd3; } to { background:#fffff0; } @@ -92,10 +92,6 @@ ul.notes { .note-body { @include md-typography; margin-left: 45px; - - .highlight { - @include border-radius(4px); - } } .note-header { padding-bottom: 5px; @@ -119,9 +115,9 @@ ul.notes { } .file .notes_holder { - font-family: $sansFontFamily; font-size: 13px; line-height: 18px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; td { border: 1px solid #ddd; @@ -130,9 +126,15 @@ ul.notes { &.notes_line { text-align: center; padding: 10px 0; + background: #eee; + } + &.notes_line2 { + text-align: center; + padding: 10px 0; + border-left: 1px solid #ddd !important; } &.notes_content { - background-color: $white; + background-color: #fff; border-width: 1px 0; padding-top: 0; @@ -251,12 +253,12 @@ ul.notes { .file, .discussion { .new_note { - margin: 8px 5px 8px 0; + margin: 0; + border: none; } } .new_note { display: none; - .buttons { float: left; margin-top: 8px; @@ -270,10 +272,9 @@ ul.notes { // preview/edit buttons > a { - font-size: 24px; - padding: 4px; position: absolute; - right: 10px; + right: 5px; + bottom: -60px; } .note_preview { background: #f5f5f5; @@ -287,7 +288,7 @@ ul.notes { box-shadow: none; font-size: 14px; height: 80px; - width: 98.6%; + width: 100%; } } } @@ -298,7 +299,7 @@ ul.notes { } .note-image-attach { - @extend .span4; + @extend .col-md-4; @extend .thumbnail; margin-left: 45px; } @@ -306,10 +307,8 @@ ul.notes { .common-note-form { margin: 0; - height: 140px; background: #F9F9F9; padding: 3px; - padding-bottom: 25px; border: 1px solid #DDD; } @@ -320,7 +319,7 @@ ul.notes { padding: 0 5px; .note-form-option { - margin-top: 10px; + margin-top: 8px; margin-left: 30px; @extend .pull-left; } @@ -338,7 +337,7 @@ ul.notes { box-shadow: none; font-size: 14px; height: 80px; - width: 98.6%; + width: 100%; } .form-actions { @@ -358,3 +357,7 @@ ul.notes { .js-note-attachment-delete { display: none; } + +.parallel-comment { + padding: 6px; +} diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss index 5c7516ce6f910651a85153a9b6e3eade2c76858c..3a3bf7cdf2aba4357a67de61c854ef4303ab4852 100644 --- a/app/assets/stylesheets/sections/profile.scss +++ b/app/assets/stylesheets/sections/profile.scss @@ -1,6 +1,107 @@ .update-notifications { - margin-bottom: 0; + .radio-inline { + margin-right: 9%; + } +} + +.account-page { + fieldset { + margin-bottom: 15px; + border-bottom: 1px dashed #ddd; + padding-bottom: 15px; + + &:last-child { + border: none; + } + + legend { + border: none; + margin-bottom: 10px; + } + } +} + +.oauth_select_holder { + img { + padding: 2px; + margin-right: 10px; + } + .active { + img { + border: 1px solid #4BD; + background: $hover; + @include border-radius(5px); + } + } +} + +.btn-build-token { + float: left; + padding: 6px 20px; + margin-right: 12px; +} + +.profile-avatar-form-option { + hr { + margin: 10px 0; + } +} + +.user-show-username { + font-weight: 200; + color: #666; +} + +/* + * Appearance settings + * + */ +.themes_opts { + label { + margin-right: 20px; + text-align: center; + + .prev { + @extend .thumbnail; + height: 30px; + width: 175px; + margin-bottom: 10px; + + &.classic { + background: #31363e; + } + + &.default { + background: #f1f1f1; + } + + &.modern { + background: #345; + } + + &.gray { + background: #373737; + } + + &.violet { + background: #547; + } + } + } +} + +.code_highlight_opts { + margin-top: 10px; + label { - margin-bottom: 0; + margin-right: 20px; + text-align: center; + + .prev { + @extend .thumbnail; + height: 151px; + width: 220px; + margin-bottom: 10px; + } } } diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss index 0491b68db57ddcdda5d34d89e7e72670abae4227..5757858a1ce28f191bdf601a3f9547d4e2b9792d 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/sections/projects.scss @@ -14,43 +14,118 @@ } } -.project_clone_panel { - @include border-radius(4px); - @include bg-gray-gradient; - padding: 4px 7px; - border: 1px solid #CCC; - margin-bottom: 20px; +.project-home-panel { + border-bottom: 1px solid #DDD; + padding-bottom: 15px; + margin-bottom: 30px; + + &.empty-project { + border-bottom: 0px; + padding-bottom: 15px; + margin-bottom: 0px; + } - .btn { - padding: 4px 12px; + .project-home-title { + font-size: 18px; + color: #444; + margin: 0; + line-height: 32px; + } + .project-home-dropdown { + margin-left: 10px; + float: right; + } + .project-home-extra { + margin-top: 15px; + + .project-home-desc { + float: left; + color: #777; + margin-bottom: 10px; + } + + .project-home-links { + float: right; + a { + margin-left: 10px; + font-weight: 500; + } + } + } + + .visibility-level-label { + font-size: 17px; + background: #f1f1f1; + border-radius: 4px; + color: #888; + position: absolute; + margin-left: -55px; + text-shadow: 0 1px 1px #FFF; + width: 40px; + text-align: center; + padding: 6px; + + i { + color: inherit; + } } } -.project_clone_holder { - input[type="text"] { - @extend .monospace; - border: 1px solid #BBB; +.git-clone-holder { + .project-home-dropdown + & { + margin-right: 45px; + } + + .btn, + .form-control { + border: 1px solid #E1E1E1; box-shadow: none; - margin-left: -1px; - background: #FFF; + padding: 6px 9px; } -} -.project-public-holder { - .help-inline { - padding-top: 7px; + .btn { + background: none; + color: #29b; + + &.active { + color: #333; + font-weight: bold; + } } -} -.save-project-loader { - img { - margin-top: 50px; - margin-bottom: 50px; + .form-control { + cursor: auto; + @extend .monospace; + background: #FAFAFA; + width: 100%; } - h3 { - @extend .page-title; +} + +.project-visibility-level-holder { + .radio { + margin-bottom: 10px; + + i { + margin: 0 3px; + font-size: 20px; + } + + .option-title { + font-weight: bold; + display: inline-block; + } + + .option-descr { + margin-left: 24px; + color: #666; + } } +} +.save-project-loader { + margin-top: 50px; + margin-bottom: 50px; + color: #555; } ul.nav.nav-projects-tabs { @@ -79,18 +154,19 @@ ul.nav.nav-projects-tabs { margin: 0px; } -.my-projects { +.my-projects, +.public-projects { li { - .project-title { - font-size: 14px; - } - .project-info { margin-bottom: 10px; } - .access-icon i { + .access-icon { color: #AAA; + margin-left: 10px; + i { + color: #AAA; + } } } } @@ -115,3 +191,44 @@ ul.nav.nav-projects-tabs { color: #777; } } + +.project-side { + .btn-block { + background-image: none; + .btn, + &.btn, + &.btn-group ul.dropdown-menu { + background-color: #F1f1f1; + border-color: #EEE; + &:hover { + background-color: #eee; + border-color: #DDD; + } + } + &.btn-group-justified { + .btn { + width: 100%; + } + .dropdown-toggle { + width: 26px; + } + } + ul { + width: 100%; + } + } + .project-fork-icon { + float: left; + font-size: 26px; + margin-right: 10px; + line-height: 1.5; + } +} + +.transfer-project .select2-container { + min-width: 200px; +} + +.deploy-project-label { + margin: 1px; +} diff --git a/app/assets/stylesheets/sections/snippets.scss b/app/assets/stylesheets/sections/snippets.scss index e67ca794f2515c49d46c33e7a16b3108ba7c9f3b..84404b6ee36ca7b0b8d1aa0fc91c13e4292232e5 100644 --- a/app/assets/stylesheets/sections/snippets.scss +++ b/app/assets/stylesheets/sections/snippets.scss @@ -1,14 +1,3 @@ -.snippet.file-holder { - .file-title { - .snippet-file-name { - padding: 4px 10px; - position: relative; - top: -4px; - left: -4px; - } - } -} - .my-snippets li:first-child { h4 { margin-top: 0; } padding-top: 0; diff --git a/app/assets/stylesheets/sections/themes.scss b/app/assets/stylesheets/sections/themes.scss index cd1aa2b011a3dd52aa010ad26ad30ff2234f58b1..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/app/assets/stylesheets/sections/themes.scss +++ b/app/assets/stylesheets/sections/themes.scss @@ -1,53 +0,0 @@ -.themes_opts { - padding-left: 20px; - - label { - width: 175px; - margin-right: 40px; - - .prev { - @extend .thumbnail; - height: 30px; - width: 175px; - margin-bottom: 10px; - - &.classic { - background: #31363e; - } - - &.default { - background: #f1f1f1; - } - - &.modern { - background: #345; - } - - &.gray { - background: #373737; - } - - &.violet { - background: #547; - } - } - } -} - -.code_highlight_opts { - padding-left: 20px; - - label { - width: 220px; - margin-right: 40px; - - .prev { - @extend .thumbnail; - height: 151px; - width: 220px; - margin-bottom: 10px; - } - } -} - - diff --git a/app/assets/stylesheets/sections/tree.scss b/app/assets/stylesheets/sections/tree.scss index 2a84741f0d61a21b66e6c8e91b0aa22a23b70ba1..dfdcbb59b6b3277d1a57f5778f9ec027ba14ba3a 100644 --- a/app/assets/stylesheets/sections/tree.scss +++ b/app/assets/stylesheets/sections/tree.scss @@ -24,10 +24,10 @@ th { font-weight: normal; font-size: 15px; - border-bottom: 1px solid #CCC; + border-bottom: 1px solid #CCC !important; } td { - border-color: #F1F1F1; + border-color: #F1F1F1 !important; } &:hover { td { @@ -49,6 +49,7 @@ .tree-item { .tree-item-file-name { + max-width: 320px; vertical-align: middle; a { &:hover { @@ -61,15 +62,18 @@ top:-1px; } } + + .tree_commit { + max-width: 320px; + } + + .tree_time_ago { + min-width: 135px; + } } .tree_author { padding-right: 8px; - - img.avatar { - margin-top: 0; - width: 16px; - } } .tree_commit { @@ -116,5 +120,16 @@ .tree-ref-holder { float: left; - margin-top: 5px; + margin-top: 8px; +} + +.readme-holder { + border-top: 1px dashed #CCC; + padding-top: 10px; + + h4 { + font-size: 14px; + margin-bottom: 20px; + color: #777; + } } diff --git a/app/assets/stylesheets/sections/votes.scss b/app/assets/stylesheets/sections/votes.scss index 49489babab71465ed7bdf842a7a2b3b5570a7d73..13f811e01a1680bf901098643bf09b94530b38b1 100644 --- a/app/assets/stylesheets/sections/votes.scss +++ b/app/assets/stylesheets/sections/votes.scss @@ -36,3 +36,8 @@ display: inline-block; margin: 0 8px; } + +.votes-holder { + float: right; + width: 250px; +} diff --git a/app/assets/stylesheets/sections/wall.scss b/app/assets/stylesheets/sections/wall.scss index d6ac08fcf6f2a2f3d7679fc20f601a36e3680d22..3705afdb87cc1e62531df6b8ecd37f327ecc87de 100644 --- a/app/assets/stylesheets/sections/wall.scss +++ b/app/assets/stylesheets/sections/wall.scss @@ -1,6 +1,6 @@ .wall-page { .wall-note-form { - @extend .span12; + @extend .col-md-12; margin: 0; height: 140px; diff --git a/app/assets/stylesheets/sections/wiki.scss b/app/assets/stylesheets/sections/wiki.scss index ed3a432ded04421159b62d1f11965f74bb639335..dfaeba41cf6603c3d86ca3bfeeb8ac19a9f97310 100644 --- a/app/assets/stylesheets/sections/wiki.scss +++ b/app/assets/stylesheets/sections/wiki.scss @@ -1,4 +1,4 @@ -h3.page-title .edit-wiki-header { +.title .edit-wiki-header { width: 780px; margin-left: auto; margin-right: auto; diff --git a/app/assets/stylesheets/selects.scss b/app/assets/stylesheets/selects.scss deleted file mode 100644 index 09ae57b66925457c3bd4afe0a3d3b7aecf388e77..0000000000000000000000000000000000000000 --- a/app/assets/stylesheets/selects.scss +++ /dev/null @@ -1,153 +0,0 @@ -/* CHZN reset few styles */ -.chosen-container-single .chosen-single { - background: #FFF; - border: 1px solid #bbb; - box-shadow: none; -} -.chosen-container-active .chosen-single { - background: #fff; -} - -.ajax-users-select { - width: 400px; - - &.input-large { - width: 210px; - } -} - -.user-result { - .user-image { - float: left; - } - .user-name { - } - .user-username { - color: #999; - } -} - -/** Branch/tag selector **/ -.project-refs-form { - margin: 0; - span { - background:none !important; - position:static !important; - width:auto !important; - height:auto !important; - } -} -.project-refs-select { - width: 120px; -} - -.project-refs-form .chosen-container { - position: relative; - top: 0; - left: 0; - margin-right: 10px; - - .chosen-drop { - min-width: 400px; - .chosen-results { - max-height: 300px; - } - .chosen-search input { - min-width: 365px; - } - } -} - -/** Fix for Search Dropdown Border **/ -.chosen-container { - min-width: 100px; - - .chosen-search { - input:focus { - @include box-shadow(none); - } - } - - .chosen-drop { - margin: 7px 0; - min-width: 200px; - border: 1px solid #bbb; - @include border-radius(0); - - .chosen-results { - margin-top: 5px; - max-height: 300px; - - .group-result { - color: $style_color; - border-bottom: 1px solid #EEE; - padding: 8px; - } - .active-result { - @include border-radius(0); - - &.highlighted { - background: $hover; - color: $style_color; - } - &.result-selected { - background: #EEE; - border-left: 4px solid #CCC; - } - } - } - - .chosen-search { - @include bg-gray-gradient; - input { - min-width: 165px; - border-color: #CCC; - } - } - } -} - -.chosen-container .chosen-single, -.chosen-container.chosen-with-drop .chosen-single { - @include bg-light-gray-gradient; - - div { - background: transparent; - border-left: none; - } - - span { - font-weight: normal; - } -} - -/** Select2 styling **/ -.select2-container .select2-choice { - background: #f1f1f1; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, whitesmoke), to(#e1e1e1)); - background-image: -webkit-linear-gradient(whitesmoke 6.6%, #e1e1e1); - background-image: -moz-linear-gradient(whitesmoke 6.6%, #e1e1e1); - background-image: -o-linear-gradient(whitesmoke 6.6%, #e1e1e1); -} - -.select2-container .select2-choice div { - border: none; - background: none; -} - -.select2-drop { - padding-top: 8px; -} - -.select2-no-results, .select2-searching { - padding: 7px; - color: #666; -} - -.chosen-container .chosen-single div b { - background-position-y: 0px !important; -} - -.chosen-container .chosen-drop .chosen-search input { - background-position-y: -24px !important; -} diff --git a/app/assets/stylesheets/themes/ui_color.scss b/app/assets/stylesheets/themes/ui_color.scss index 1fd54ff18a664df6d57acdd1217926879d5f4460..0fc72d4e0a82a94538bbd12381cdc89c74debfb9 100644 --- a/app/assets/stylesheets/themes/ui_color.scss +++ b/app/assets/stylesheets/themes/ui_color.scss @@ -18,7 +18,7 @@ .navbar-inner { background: #547; border-bottom: 1px solid #435; - .app_logo { + .app_logo, .navbar-toggle { &:hover { background-color: #435; } @@ -27,6 +27,12 @@ background: #435; border-left: 1px solid #658; } + .nav > li > a { + color: #98B; + } + .search-input { + border-color: #98B; + } } } } diff --git a/app/assets/stylesheets/themes/ui_gray.scss b/app/assets/stylesheets/themes/ui_gray.scss index 41c08c840e2c8da2b87b7cbc2fb0c0cbdc531f8f..959febad6fe7dcf25a5aca64508b428e5e9b9741 100644 --- a/app/assets/stylesheets/themes/ui_gray.scss +++ b/app/assets/stylesheets/themes/ui_gray.scss @@ -18,7 +18,7 @@ .navbar-inner { background: #373737; border-bottom: 1px solid #272727; - .app_logo { + .app_logo, .navbar-toggle { &:hover { background-color: #272727; } diff --git a/app/assets/stylesheets/themes/ui_mars.scss b/app/assets/stylesheets/themes/ui_mars.scss index a2b8c21ea11df88a8e0f97514806bd8273cf9481..9af5adbf10ac0ce61cbc4a56bfa70ba5731e1171 100644 --- a/app/assets/stylesheets/themes/ui_mars.scss +++ b/app/assets/stylesheets/themes/ui_mars.scss @@ -18,17 +18,22 @@ .navbar-inner { background: #474D57; border-bottom: 1px solid #373D47; - .app_logo { + .app_logo, .navbar-toggle { &:hover { background-color: #373D47; } } + .separator { + background: #373D47; + border-left: 1px solid #575D67; + } + .nav > li > a { + color: #979DA7; + } + .search-input { + border-color: #979DA7; + } } } - - .separator { - background: #31363E; - border-left: 1px solid #666; - } } } diff --git a/app/assets/stylesheets/themes/ui_modern.scss b/app/assets/stylesheets/themes/ui_modern.scss index 6173757082e08122cd1d7f50665b2889206d8838..b0827deb1acff5f52073b29567fa48fa404b6111 100644 --- a/app/assets/stylesheets/themes/ui_modern.scss +++ b/app/assets/stylesheets/themes/ui_modern.scss @@ -18,7 +18,7 @@ .navbar-inner { background: #345; border-bottom: 1px solid #234; - .app_logo { + .app_logo, .navbar-toggle { &:hover { background-color: #234; } @@ -27,6 +27,12 @@ background: #234; border-left: 1px solid #456; } + .nav > li > a { + color: #89A; + } + .search-input { + border-color: #89A; + } } } } diff --git a/app/contexts/commit_load_context.rb b/app/contexts/commit_load_context.rb deleted file mode 100644 index 2930c5b1668f1124f676fe4a630b08b71183b592..0000000000000000000000000000000000000000 --- a/app/contexts/commit_load_context.rb +++ /dev/null @@ -1,33 +0,0 @@ -class CommitLoadContext < BaseContext - def execute - result = { - commit: nil, - suppress_diff: false, - line_notes: [], - notes_count: 0, - note: nil, - status: :ok - } - - commit = project.repository.commit(params[:id]) - - if commit - line_notes = project.notes.for_commit_id(commit.id).inline - - result[:commit] = commit - result[:note] = project.build_commit_note(commit) - result[:line_notes] = line_notes - result[:notes_count] = project.notes.for_commit_id(commit.id).count - - begin - result[:suppress_diff] = true if commit.diff_suppress? && !params[:force_show_diff] - result[:force_suppress_diff] = commit.diff_force_suppress? - rescue Grit::Git::GitTimeout - result[:suppress_diff] = true - result[:status] = :huge_commit - end - end - - result - end -end diff --git a/app/contexts/filter_context.rb b/app/contexts/filter_context.rb deleted file mode 100644 index 2607b12b4aed0c4aa45dea05e118ae436d45873e..0000000000000000000000000000000000000000 --- a/app/contexts/filter_context.rb +++ /dev/null @@ -1,31 +0,0 @@ -class FilterContext - attr_accessor :items, :params - - def initialize(items, params) - @items = items - @params = params - end - - def execute - apply_filter(items) - end - - def apply_filter items - if params[:project_id].present? - items = items.of_projects(params[:project_id]) - end - - if params[:search].present? - items = items.search(params[:search]) - end - - case params[:status] - when 'closed' - items.closed - when 'all' - items - else - items.opened - end - end -end diff --git a/app/contexts/issues/list_context.rb b/app/contexts/issues/list_context.rb deleted file mode 100644 index da2eed0e2591a55344e30567c56a7c74c9747826..0000000000000000000000000000000000000000 --- a/app/contexts/issues/list_context.rb +++ /dev/null @@ -1,36 +0,0 @@ -module Issues - class ListContext < BaseContext - attr_accessor :issues - - def execute - @issues = @project.issues - - @issues = case params[:state] - when 'all' then @issues - when 'closed' then @issues.closed - else @issues.opened - end - - @issues = case params[:scope] - when 'assigned-to-me' then @issues.assigned_to(current_user) - when 'created-by-me' then @issues.authored(current_user) - else @issues - end - - @issues = @issues.tagged_with(params[:label_name]) if params[:label_name].present? - @issues = @issues.includes(:author, :project) - - # Filter by specific assignee_id (or lack thereof)? - if params[:assignee_id].present? - @issues = @issues.where(assignee_id: (params[:assignee_id] == '0' ? nil : params[:assignee_id])) - end - - # Filter by specific milestone_id (or lack thereof)? - if params[:milestone_id].present? - @issues = @issues.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id])) - end - - @issues - end - end -end diff --git a/app/contexts/merge_requests_load_context.rb b/app/contexts/merge_requests_load_context.rb deleted file mode 100644 index c3408db6e1138c8608a24aa28760350b19382713..0000000000000000000000000000000000000000 --- a/app/contexts/merge_requests_load_context.rb +++ /dev/null @@ -1,35 +0,0 @@ -# Build collection of Merge Requests -# based on filtering passed via params for @project -class MergeRequestsLoadContext < BaseContext - def execute - merge_requests = @project.merge_requests - - merge_requests = case params[:state] - when 'all' then merge_requests - when 'closed' then merge_requests.closed - else merge_requests.opened - end - - merge_requests = case params[:scope] - when 'assigned-to-me' then merge_requests.assigned_to(current_user) - when 'created-by-me' then merge_requests.authored(current_user) - else merge_requests - end - - - merge_requests = merge_requests.page(params[:page]).per(20) - merge_requests = merge_requests.includes(:author, :source_project, :target_project).order("created_at desc") - - # Filter by specific assignee_id (or lack thereof)? - if params[:assignee_id].present? - merge_requests = merge_requests.where(assignee_id: (params[:assignee_id] == '0' ? nil : params[:assignee_id])) - end - - # Filter by specific milestone_id (or lack thereof)? - if params[:milestone_id].present? - merge_requests = merge_requests.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id])) - end - - merge_requests - end -end diff --git a/app/contexts/projects/update_context.rb b/app/contexts/projects/update_context.rb deleted file mode 100644 index 40385fa65b09caf3fde9a6a9b9080d5ce39526e7..0000000000000000000000000000000000000000 --- a/app/contexts/projects/update_context.rb +++ /dev/null @@ -1,9 +0,0 @@ -module Projects - class UpdateContext < BaseContext - def execute(role = :default) - params[:project].delete(:namespace_id) - params[:project].delete(:public) unless can?(current_user, :change_public_mode, project) - project.update_attributes(params[:project], as: role) - end - end -end diff --git a/app/contexts/search_context.rb b/app/contexts/search_context.rb deleted file mode 100644 index 48def0784fdba884f21753e954638b18046b8482..0000000000000000000000000000000000000000 --- a/app/contexts/search_context.rb +++ /dev/null @@ -1,38 +0,0 @@ -class SearchContext - attr_accessor :project_ids, :params - - def initialize(project_ids, params) - @project_ids, @params = project_ids, params.dup - end - - def execute - query = params[:search] - - return result unless query.present? - - projects = Project.where(id: project_ids) - result[:projects] = projects.search(query).limit(20) - - # Search inside single project - project = projects.first if projects.length == 1 - - if params[:search_code].present? - result[:blobs] = project.repository.search_files(query, params[:repository_ref]) unless project.empty_repo? - else - result[:merge_requests] = MergeRequest.in_projects(project_ids).search(query).order('updated_at DESC').limit(20) - result[:issues] = Issue.where(project_id: project_ids).search(query).order('updated_at DESC').limit(20) - result[:wiki_pages] = [] - end - result - end - - def result - @result ||= { - projects: [], - merge_requests: [], - issues: [], - wiki_pages: [], - blobs: [] - } - end -end diff --git a/app/contexts/test_hook_context.rb b/app/contexts/test_hook_context.rb deleted file mode 100644 index 63eda6c7d0603a6613dc9e54dfa5d0672bf1fdd7..0000000000000000000000000000000000000000 --- a/app/contexts/test_hook_context.rb +++ /dev/null @@ -1,7 +0,0 @@ -class TestHookContext < BaseContext - def execute - hook = project.hooks.find(params[:id]) - data = GitPushService.new.sample_data(project, current_user) - hook.execute(data) - end -end diff --git a/app/controllers/admin/background_jobs_controller.rb b/app/controllers/admin/background_jobs_controller.rb index 994e707965a05f57369d94601bf76ec45156e3e4..159ab2027c26a621d1e52226478870220859fd8a 100644 --- a/app/controllers/admin/background_jobs_controller.rb +++ b/app/controllers/admin/background_jobs_controller.rb @@ -1,4 +1,5 @@ class Admin::BackgroundJobsController < Admin::ApplicationController def show + @sidekiq_processes = `ps -U #{Settings.gitlab.user} -o euser,pid,pcpu,pmem,stat,start,command | grep sidekiq | grep -v grep` end end diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..9a70ef9d199088ac7485615e4ae2e851bb071902 --- /dev/null +++ b/app/controllers/admin/broadcast_messages_controller.rb @@ -0,0 +1,32 @@ +class Admin::BroadcastMessagesController < Admin::ApplicationController + before_filter :broadcast_messages + + def index + @broadcast_message = BroadcastMessage.new + end + + def create + @broadcast_message = BroadcastMessage.new(params[:broadcast_message]) + + if @broadcast_message.save + redirect_to admin_broadcast_messages_path, notice: 'Broadcast Message was successfully created.' + else + render :index + end + end + + def destroy + BroadcastMessage.find(params[:id]).destroy + + respond_to do |format| + format.html { redirect_to :back } + format.js { render nothing: true } + end + end + + protected + + def broadcast_messages + @broadcast_messages ||= BroadcastMessage.order("starts_at DESC").page(params[:page]) + end +end diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index 3c80b6503fabbab26e979ca6264bb706e08c43d2..be19139c9b1113c19bfad3b4a4b1584ceff2b145 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -2,5 +2,6 @@ class Admin::DashboardController < Admin::ApplicationController def index @projects = Project.order("created_at DESC").limit(10) @users = User.order("created_at DESC").limit(10) + @groups = Group.order("created_at DESC").limit(10) end end diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 89b395786b316e92e4bbcdcd838e4067317ac9ec..4bb3cf07da0c78869e1130d847d69245982316f6 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -52,6 +52,6 @@ class Admin::GroupsController < Admin::ApplicationController private def group - @group = Group.find_by_path(params[:id]) + @group = Group.find_by(path: params[:id]) end end diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 088174fd3b80fe71fe1caa8d881e699a926f2acc..13a7bdcf34a7e8bfb3befb3ef3b0cbe58db15e31 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -1,12 +1,14 @@ class Admin::ProjectsController < Admin::ApplicationController - before_filter :project, only: [:edit, :show, :update, :destroy, :team_update] + before_filter :project, only: [:show, :transfer] + before_filter :group, only: [:show, :transfer] + before_filter :repository, only: [:show, :transfer] def index owner_id = params[:owner_id] - user = User.find_by_id(owner_id) + user = User.find_by(id: owner_id) - @projects = user ? user.owned_projects : Project.scoped - @projects = @projects.where(public: true) if params[:public_only].present? + @projects = user ? user.owned_projects : Project.all + @projects = @projects.where("visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present? @projects = @projects.with_push if params[:with_push].present? @projects = @projects.abandoned if params[:abandoned].present? @projects = @projects.search(params[:name]) if params[:name].present? @@ -14,8 +16,16 @@ class Admin::ProjectsController < Admin::ApplicationController end def show - @repository = @project.repository - @group = @project.group + end + + def transfer + result = ::Projects::TransferService.new(@project, current_user, project: params).execute(:admin) + + if result + redirect_to [:admin, @project] + else + render :show + end end protected @@ -26,4 +36,12 @@ class Admin::ProjectsController < Admin::ApplicationController @project = Project.find_with_namespace(id) @project || render_404 end + + def group + @group ||= project.group + end + + def repository + @repository ||= project.repository + end end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 70bbe306562e9035700a490f01736dd93cb191df..bdbb9a354b464d00d27aee023b41c09a23cc32ea 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -2,8 +2,7 @@ class Admin::UsersController < Admin::ApplicationController before_filter :user, only: [:show, :edit, :update, :destroy] def index - @users = User.scoped - @users = @users.filter(params[:filter]) + @users = User.filter(params[:filter]) @users = @users.search(params[:name]) if params[:name].present? @users = @users.alphabetically.page(params[:page]) end @@ -47,6 +46,8 @@ class Admin::UsersController < Admin::ApplicationController @user = User.build_user(params[:user].merge(opts), as: :admin) @user.admin = (admin && admin.to_i > 0) @user.created_by_id = current_user.id + @user.generate_password + @user.skip_confirmation! respond_to do |format| if @user.save @@ -71,6 +72,7 @@ class Admin::UsersController < Admin::ApplicationController respond_to do |format| if user.update_attributes(params[:user], as: :admin) + user.confirm! format.html { redirect_to [:admin, user], notice: 'User was successfully updated.' } format.json { head :ok } else @@ -98,6 +100,6 @@ class Admin::UsersController < Admin::ApplicationController protected def user - @user ||= User.find_by_username!(params[:id]) + @user ||= User.find_by!(username: params[:id]) end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d974600dcc17c66789f1f9fa90f541fada30c39c..442b1b65ce85bded110ef4f4938666268d498898 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,12 +1,15 @@ +require 'gon' + class ApplicationController < ActionController::Base before_filter :authenticate_user! before_filter :reject_blocked! before_filter :check_password_expiration - before_filter :set_current_user_for_thread + around_filter :set_current_user_for_thread before_filter :add_abilities before_filter :dev_tools if Rails.env == 'development' before_filter :default_headers before_filter :add_gon_variables + before_filter :configure_permitted_parameters, if: :devise_controller? protect_from_forgery @@ -50,6 +53,11 @@ class ApplicationController < ActionController::Base def set_current_user_for_thread Thread.current[:current_user] = current_user + begin + yield + ensure + Thread.current[:current_user] = nil + end end def abilities @@ -63,10 +71,22 @@ class ApplicationController < ActionController::Base def project id = params[:project_id] || params[:id] + # Redirect from + # localhost/group/project.git + # to + # localhost/group/project + # + if id =~ /\.git\Z/ + redirect_to request.original_url.gsub(/\.git\Z/, '') and return + end + @project = Project.find_with_namespace(id) if @project and can?(current_user, :read_project, @project) @project + elsif current_user.nil? + @project = nil + authenticate_user! else @project = nil render_404 and return @@ -88,7 +108,7 @@ class ApplicationController < ActionController::Base end def authorize_code_access! - return access_denied! unless can?(current_user, :download_code, project) or project.public? + return access_denied! unless can?(current_user, :download_code, project) end def authorize_push! @@ -140,6 +160,9 @@ class ApplicationController < ActionController::Base def default_headers headers['X-Frame-Options'] = 'DENY' headers['X-XSS-Protection'] = '1; mode=block' + headers['X-UA-Compatible'] = 'IE=edge' + headers['X-Content-Type-Options'] = 'nosniff' + headers['Strict-Transport-Security'] = 'max-age=31536000' if Gitlab.config.gitlab.https end def add_gon_variables @@ -160,4 +183,31 @@ class ApplicationController < ActionController::Base filters = cookies['event_filter'].split(',') if cookies['event_filter'].present? @event_filter ||= EventFilter.new(filters) end + + # JSON for infinite scroll via Pager object + def pager_json(partial, count) + html = render_to_string( + partial, + layout: false, + formats: [:html] + ) + + render json: { + html: html, + count: count + } + end + + def view_to_html_string(partial) + render_to_string( + partial, + layout: false, + formats: [:html] + ) + end + + def configure_permitted_parameters + devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:username, :email, :password, :login, :remember_me) } + devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :email, :name, :password, :password_confirmation) } + end end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index ac319384434e66dea478489a58818c15875f8979..656eda9dec24667bdc707e63267aec9c7e7dc60d 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -3,6 +3,8 @@ class DashboardController < ApplicationController before_filter :load_projects, except: [:projects] before_filter :event_filter, only: :show + before_filter :default_filter, only: [:issues, :merge_requests] + def show # Fetch only 30 projects. @@ -22,7 +24,7 @@ class DashboardController < ApplicationController respond_to do |format| format.html - format.js + format.json { pager_json("events/_events", @events.count) } format.atom { render layout: false } end end @@ -39,27 +41,24 @@ class DashboardController < ApplicationController current_user.authorized_projects end - @projects = @projects.where(namespace_id: Group.find_by_name(params[:group])) if params[:group].present? - @projects = @projects.includes(:namespace).sorted_by_activity + @projects = @projects.where(namespace_id: Group.find_by(name: params[:group])) if params[:group].present? + @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? + @projects = @projects.includes(:namespace) + @projects = @projects.tagged_with(params[:label]) if params[:label].present? + @projects = @projects.sort(@sort = params[:sort]) + @projects = @projects.page(params[:page]).per(30) @labels = current_user.authorized_projects.tags_on(:labels) @groups = current_user.authorized_groups - - @projects = @projects.tagged_with(params[:label]) if params[:label].present? - @projects = @projects.page(params[:page]).per(30) end - # Get authored or assigned open merge requests def merge_requests - @merge_requests = current_user.cared_merge_requests - @merge_requests = FilterContext.new(@merge_requests, params).execute + @merge_requests = FilteringService.new.execute(MergeRequest, current_user, params) @merge_requests = @merge_requests.recent.page(params[:page]).per(20) end - # Get only assigned issues def issues - @issues = current_user.assigned_issues - @issues = FilterContext.new(@issues, params).execute + @issues = FilteringService.new.execute(Issue, current_user, params) @issues = @issues.recent.page(params[:page]).per(20) @issues = @issues.includes(:author, :project) @@ -72,6 +71,11 @@ class DashboardController < ApplicationController protected def load_projects - @projects = current_user.authorized_projects.sorted_by_activity + @projects = current_user.authorized_projects.sorted_by_activity.non_archived + end + + def default_filter + params[:scope] = 'assigned-to-me' if params[:scope].blank? + params[:state] = 'opened' if params[:state].blank? end end diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb deleted file mode 100644 index a0c8a000fc70766dd2f94ba1737b30ed386b9783..0000000000000000000000000000000000000000 --- a/app/controllers/errors_controller.rb +++ /dev/null @@ -1,2 +0,0 @@ -class ErrorsController < ApplicationController -end diff --git a/app/controllers/groups/avatars_controller.rb b/app/controllers/groups/avatars_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..38071410f404a12b4b40ace7b181f6a5b12bb0fd --- /dev/null +++ b/app/controllers/groups/avatars_controller.rb @@ -0,0 +1,12 @@ +class Groups::AvatarsController < ApplicationController + layout "profile" + + def destroy + @group = Group.find_by(path: params[:group_id]) + @group.remove_avatar! + + @group.save + + redirect_to edit_group_path(@group) + end +end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index f80167da4cb5bbb5def5bed81302a0ac42738cc8..7b418ec98f55af2a902647ff917de4579d462b60 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -10,6 +10,8 @@ class GroupsController < ApplicationController # Load group projects before_filter :projects, except: [:new, :create] + before_filter :default_filter, only: [:issues, :merge_requests] + layout :determine_layout before_filter :set_title, only: [:new, :create] @@ -38,23 +40,19 @@ class GroupsController < ApplicationController respond_to do |format| format.html - format.js + format.json { pager_json("events/_events", @events.count) } format.atom { render layout: false } end end - # Get authored or assigned open merge requests def merge_requests - @merge_requests = current_user.cared_merge_requests.of_group(@group) - @merge_requests = FilterContext.new(@merge_requests, params).execute - @merge_requests = @merge_requests.recent.page(params[:page]).per(20) + @merge_requests = FilteringService.new.execute(MergeRequest, current_user, params) + @merge_requests = @merge_requests.page(params[:page]).per(20) end - # Get only assigned issues def issues - @issues = current_user.assigned_issues.of_group(@group) - @issues = FilterContext.new(@issues, params).execute - @issues = @issues.recent.page(params[:page]).per(20) + @issues = FilteringService.new.execute(Issue, current_user, params) + @issues = @issues.page(params[:page]).per(20) @issues = @issues.includes(:author, :project) respond_to do |format| @@ -89,7 +87,7 @@ class GroupsController < ApplicationController protected def group - @group ||= Group.find_by_path(params[:id]) + @group ||= Group.find_by(path: params[:id]) end def projects @@ -102,7 +100,7 @@ class GroupsController < ApplicationController # Dont allow unauthorized access to group def authorize_read_group! - unless projects.present? or can?(current_user, :read_group, @group) + unless @group and (projects.present? or can?(current_user, :read_group, @group)) return render_404 end end @@ -130,4 +128,10 @@ class GroupsController < ApplicationController 'group' end end + + def default_filter + params[:scope] = 'assigned-to-me' if params[:scope].blank? + params[:state] = 'opened' if params[:state].blank? + params[:group_id] = @group.id + end end diff --git a/app/controllers/profiles/accounts_controller.rb b/app/controllers/profiles/accounts_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..fe121691a10092034d86cf16ed1e6950d6a43607 --- /dev/null +++ b/app/controllers/profiles/accounts_controller.rb @@ -0,0 +1,7 @@ +class Profiles::AccountsController < ApplicationController + layout "profile" + + def show + @user = current_user + end +end diff --git a/app/controllers/profiles/avatars_controller.rb b/app/controllers/profiles/avatars_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..57f3bbf0627ad4d79fe4e712b2f8504ec5c3cf46 --- /dev/null +++ b/app/controllers/profiles/avatars_controller.rb @@ -0,0 +1,13 @@ +class Profiles::AvatarsController < ApplicationController + layout "profile" + + def destroy + @user = current_user + @user.remove_avatar! + + @user.save + @user.reset_events_cache + + redirect_to profile_path + end +end diff --git a/app/controllers/profiles/groups_controller.rb b/app/controllers/profiles/groups_controller.rb index 378ff6bcf34a978526c363921a52885ecaffd5e2..bdd991bec06b03f5039526f89befd856c6e43e1a 100644 --- a/app/controllers/profiles/groups_controller.rb +++ b/app/controllers/profiles/groups_controller.rb @@ -19,6 +19,6 @@ class Profiles::GroupsController < ApplicationController private def group - @group ||= Group.find_by_path(params[:id]) + @group ||= Group.find_by(path: params[:id]) end end diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index 2b991957b701af02e9741adeeb49b07d88c5d594..38424cf26c2a246e692c7941de129b2eae5201a2 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -3,7 +3,7 @@ class Profiles::KeysController < ApplicationController skip_before_filter :authenticate_user!, only: [:get_keys] def index - @keys = current_user.keys.order('id DESC').all + @keys = current_user.keys.order('id DESC') end def show diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb index 432899f857df65ce817a382b4582f17f1ec617f1..df6954554eac205789b64a0ec410ae7741b0592a 100644 --- a/app/controllers/profiles/passwords_controller.rb +++ b/app/controllers/profiles/passwords_controller.rb @@ -1,10 +1,11 @@ class Profiles::PasswordsController < ApplicationController - layout 'navless' + layout :determine_layout - skip_before_filter :check_password_expiration + skip_before_filter :check_password_expiration, only: [:new, :create] before_filter :set_user before_filter :set_title + before_filter :authorize_change_password! def new end @@ -26,6 +27,32 @@ class Profiles::PasswordsController < ApplicationController end end + def edit + end + + def update + password_attributes = params[:user].select do |key, value| + %w(password password_confirmation).include?(key.to_s) + end + + unless @user.valid_password?(params[:user][:current_password]) + redirect_to edit_profile_password_path, alert: 'You must provide a valid current password' + return + end + + if @user.update_attributes(password_attributes) + flash[:notice] = "Password was successfully updated. Please login with it" + redirect_to new_user_session_path + else + render 'edit' + end + end + + def reset + current_user.send_reset_password_instructions + redirect_to edit_profile_password_path, notice: 'We sent you an email with reset password instructions' + end + private def set_user @@ -35,4 +62,16 @@ class Profiles::PasswordsController < ApplicationController def set_title @title = "New password" end + + def determine_layout + if [:new, :create].include?(action_name.to_sym) + 'navless' + else + 'profile' + end + end + + def authorize_change_password! + return render_404 if @user.ldap_user? + end end diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 75f12f8a6af8621ebdfc193d42353fed62909860..9234cd1708fa153709ffe292dd339d2420a46e7b 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -2,7 +2,6 @@ class ProfilesController < ApplicationController include ActionView::Helpers::SanitizeHelper before_filter :user - before_filter :authorize_change_password!, only: :update_password before_filter :authorize_change_username!, only: :update_username layout 'profile' @@ -13,10 +12,9 @@ class ProfilesController < ApplicationController def design end - def account - end - def update + params[:user].delete(:email) if @user.ldap_user? + if @user.update_attributes(params[:user]) flash[:notice] = "Profile was successfully updated" else @@ -29,33 +27,12 @@ class ProfilesController < ApplicationController end end - def token - end - - def update_password - password_attributes = params[:user].select do |key, value| - %w(password password_confirmation).include?(key.to_s) - end - - unless @user.valid_password?(params[:user][:current_password]) - redirect_to account_profile_path, alert: 'You must provide a valid current password' - return - end - - if @user.update_attributes(password_attributes) - flash[:notice] = "Password was successfully updated. Please login with it" - redirect_to new_user_session_path - else - render 'account' - end - end - def reset_private_token if current_user.reset_authentication_token! flash[:notice] = "Token was successfully updated" end - redirect_to account_profile_path + redirect_to profile_account_path end def history @@ -76,10 +53,6 @@ class ProfilesController < ApplicationController @user = current_user end - def authorize_change_password! - return render_404 if @user.ldap_user? - end - def authorize_change_username! return render_404 unless @user.can_change_username? end diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 8fd4565f3677fedeafd08578ef4acb365352f62d..7e4580017dd7e6c26373a1b80186f7dd518a71b0 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -10,7 +10,7 @@ class Projects::ApplicationController < ApplicationController id = params[:project_id] || params[:id] @project = Project.find_with_namespace(id) - return if @project && @project.public + return if @project && @project.public? end super @@ -23,4 +23,10 @@ class Projects::ApplicationController < ApplicationController 'public_projects' end end + + def require_branch_head + unless @repository.branch_names.include?(@ref) + redirect_to project_tree_path(@project, @ref), notice: "This action is not allowed unless you are on top of a branch" + end + end end diff --git a/app/controllers/projects/base_tree_controller.rb b/app/controllers/projects/base_tree_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..5e30593443311ddd50db57b90433b20e56e9b2ff --- /dev/null +++ b/app/controllers/projects/base_tree_controller.rb @@ -0,0 +1,8 @@ +class Projects::BaseTreeController < Projects::ApplicationController + include ExtractsPath + + before_filter :authorize_read_project! + before_filter :authorize_code_access! + before_filter :require_non_empty_project +end + diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb index e58b450720212df28c4726230d7c461d26bfa45b..a3c41301676500b053e95472513f99264691b902 100644 --- a/app/controllers/projects/blame_controller.rb +++ b/app/controllers/projects/blame_controller.rb @@ -8,7 +8,7 @@ class Projects::BlameController < Projects::ApplicationController before_filter :require_non_empty_project def show - @blob = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, @path) + @blob = @repository.blob_at(@commit.id, @path) @blame = Gitlab::Git::Blame.new(project.repository, @commit.id, @path) end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index b1329c01ce77c83144df87b4dc6bd7546dc67540..a1a8bed09f4ae0fdbe0c06347b662b61de9c200a 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -6,8 +6,32 @@ class Projects::BlobController < Projects::ApplicationController before_filter :authorize_read_project! before_filter :authorize_code_access! before_filter :require_non_empty_project + before_filter :authorize_push!, only: [:destroy] + + before_filter :blob def show - @blob = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, @path) + end + + def destroy + result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute + + if result[:status] == :success + flash[:notice] = "Your changes have been successfully committed" + redirect_to project_tree_path(@project, @ref) + else + flash[:alert] = result[:error] + render :show + end + end + + private + + def blob + @blob ||= @repository.blob_at(@commit.id, @path) + + return not_found! unless @blob + + @blob end end diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index bdc501d73bbfeda1f93fc1c49551736f44331ec4..c56df65dcba8c4ce1b2bc25c2716bdef9fd01348 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -6,33 +6,35 @@ class Projects::CommitController < Projects::ApplicationController before_filter :authorize_read_project! before_filter :authorize_code_access! before_filter :require_non_empty_project + before_filter :commit def show - result = CommitLoadContext.new(project, current_user, params).execute + return git_not_found! unless @commit - @commit = result[:commit] + @line_notes = project.notes.for_commit_id(commit.id).inline + @branches = project.repository.branch_names_contains(commit.id) - if @commit.nil? - git_not_found! - return + begin + @suppress_diff = true if commit.diff_suppress? && !params[:force_show_diff] + @force_suppress_diff = commit.diff_force_suppress? + rescue Grit::Git::GitTimeout + @suppress_diff = true + @status = :huge_commit end - @suppress_diff = result[:suppress_diff] - @force_suppress_diff = result[:force_suppress_diff] - - @note = result[:note] - @line_notes = result[:line_notes] - @notes_count = result[:notes_count] - @target_type = :commit - @target_id = @commit.id - + @note = project.build_commit_note(commit) + @notes_count = project.notes.for_commit_id(commit.id).count + @notes = project.notes.for_commit_id(@commit.id).not_inline.fresh + @noteable = @commit @comments_allowed = @reply_allowed = true - @comments_target = { noteable_type: 'Commit', - commit_id: @commit.id } + @comments_target = { + noteable_type: 'Commit', + commit_id: @commit.id + } respond_to do |format| format.html do - if result[:status] == :huge_commit + if @status == :huge_commit render "huge_commit" and return end end @@ -41,4 +43,8 @@ class Projects::CommitController < Projects::ApplicationController format.patch { render text: @commit.to_patch } end end + + def commit + @commit ||= project.repository.commit(params[:id]) + end end diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index bdffc940ea5241ba714101d6c829f0486d531f13..12856191c2657744418efdefb741ba75bb225c5c 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -16,7 +16,7 @@ class Projects::CommitsController < Projects::ApplicationController respond_to do |format| format.html # index.html.erb - format.js + format.json { pager_json("projects/commits/_commits", @commits.size) } format.atom { render layout: false } end end diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index b7531e2cefbf082121ef751b84a021cf4252b56f..696cb7a4ba2d011e369cff0985cfc111829932c1 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -8,7 +8,7 @@ class Projects::CompareController < Projects::ApplicationController end def show - compare = Gitlab::Git::Compare.new(@repository.raw_repository, params[:from], params[:to]) + compare = Gitlab::Git::Compare.new(@repository.raw_repository, params[:from], params[:to], MergeRequestDiff::COMMITS_SAFE_SIZE) @commits = compare.commits @commit = compare.commit @@ -16,6 +16,11 @@ class Projects::CompareController < Projects::ApplicationController @refs_are_same = compare.same @line_notes = [] + if @diffs == [Gitlab::Git::Diff::BROKEN_DIFF] + @diffs = [] + @timeout = true + end + diff_line_count = Commit::diff_line_count(@diffs) @suppress_diff = Commit::diff_suppress?(@diffs, diff_line_count) && !params[:force_show_diff] @force_suppress_diff = Commit::diff_force_suppress?(@diffs, diff_line_count) diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index 0750e0a146fe409787617f511242004a5fadd8ce..6e1a76ff4177c9ed232e9fcdd4fc84617efb4ec4 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -7,7 +7,7 @@ class Projects::DeployKeysController < Projects::ApplicationController layout "project_settings" def index - @enabled_keys = @project.deploy_keys.all + @enabled_keys = @project.deploy_keys @available_keys = available_keys - @enabled_keys end diff --git a/app/controllers/projects/edit_tree_controller.rb b/app/controllers/projects/edit_tree_controller.rb index 3b945fc7126c29deff2b5a3f3b0bb8abf9c3520c..6bd1a455f323a876e6a9e35d97139bfdf1542291 100644 --- a/app/controllers/projects/edit_tree_controller.rb +++ b/app/controllers/projects/edit_tree_controller.rb @@ -1,49 +1,27 @@ -# Controller for edit a repository's file -class Projects::EditTreeController < Projects::ApplicationController - include ExtractsPath - - # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! - before_filter :require_non_empty_project - - before_filter :edit_requirements, only: [:show, :update] +class Projects::EditTreeController < Projects::BaseTreeController + before_filter :require_branch_head + before_filter :blob + before_filter :authorize_push! def show - @last_commit = Gitlab::Git::Commit.last_for_path(@project.repository, @ref, @path).sha + @last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha end def update - edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, @project, @ref, @path) - updated_successfully = edit_file_action.commit!( - params[:content], - params[:commit_message], - params[:last_commit] - ) + result = Files::UpdateService.new(@project, current_user, params, @ref, @path).execute - if updated_successfully - redirect_to project_blob_path(@project, @id), notice: "Your changes have been successfully commited" + if result[:status] == :success + flash[:notice] = "Your changes have been successfully committed" + redirect_to project_blob_path(@project, @id) else - flash[:notice] = "Your changes could not be commited, because the file has been changed" + flash[:alert] = result[:error] render :show end end private - def edit_requirements - @blob = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, @path) - - unless @blob.exists? && @blob.text? - redirect_to project_blob_path(@project, @id), notice: "You can only edit text files" - end - - allowed = if project.protected_branch? @ref - can?(current_user, :push_code_to_protected_branches, project) - else - can?(current_user, :push_code, project) - end - - return access_denied! unless allowed + def blob + @blob ||= @repository.blob_at(@commit.id, @path) end end diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index 1a94dbab5ea538fe6daa9f175e1d73c841b396d8..a863b318324856dc9663f4a64422fe783a24327e 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -7,7 +7,7 @@ class Projects::HooksController < Projects::ApplicationController layout "project_settings" def index - @hooks = @project.hooks.all + @hooks = @project.hooks @hook = ProjectHook.new end @@ -18,21 +18,26 @@ class Projects::HooksController < Projects::ApplicationController if @hook.valid? redirect_to project_hooks_path(@project) else - @hooks = @project.hooks.all + @hooks = @project.hooks render :index end end def test - TestHookContext.new(project, current_user, params).execute + TestHookService.new.execute(hook, current_user) redirect_to :back end def destroy - @hook = @project.hooks.find(params[:id]) - @hook.destroy + hook.destroy redirect_to project_hooks_path(@project) end + + private + + def hook + @hook ||= @project.hooks.find(params[:id]) + end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index e8f845b2d1710a75da654f9dae477d55b3921934..f260a2e0597d9559103785a2c9c29e21b5763376 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -9,9 +9,9 @@ class Projects::IssuesController < Projects::ApplicationController before_filter :authorize_write_issue!, only: [:new, :create] # Allow modify issue - before_filter :authorize_modify_issue!, only: [:edit, :update] + before_filter :authorize_modify_issue!, only: [:edit, :update, :bulk_update] - respond_to :js, :html + respond_to :html def index terms = params['issue_search'] @@ -23,11 +23,18 @@ class Projects::IssuesController < Projects::ApplicationController 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? + respond_to do |format| - format.html # index.html.erb - format.js + format.html format.atom { render layout: false } + format.json do + render json: { + html: view_to_html_string("projects/issues/_issues") + } + end end end @@ -42,13 +49,10 @@ class Projects::IssuesController < Projects::ApplicationController def show @note = @project.notes.new(noteable: @issue) - @target_type = :issue - @target_id = @issue.id + @notes = @issue.notes.inc_author.fresh + @noteable = @issue - respond_to do |format| - format.html - format.js - end + respond_with(@issue) end def create @@ -70,6 +74,7 @@ class Projects::IssuesController < Projects::ApplicationController def update @issue.update_attributes(params[:issue].merge(author_id_of_changes: current_user.id)) + @issue.reset_events_cache respond_to do |format| format.js @@ -84,7 +89,7 @@ class Projects::IssuesController < Projects::ApplicationController end def bulk_update - result = Issues::BulkUpdateContext.new(project, current_user, params).execute + result = Issues::BulkUpdateService.new(project, current_user, params).execute redirect_to :back, notice: "#{result[:count]} issues updated" end @@ -92,7 +97,7 @@ class Projects::IssuesController < Projects::ApplicationController def issue @issue ||= begin - @project.issues.find_by_iid!(params[:id]) + @project.issues.find_by!(iid: params[:id]) rescue ActiveRecord::RecordNotFound redirect_old end @@ -111,7 +116,9 @@ class Projects::IssuesController < Projects::ApplicationController end def issues_filtered - @issues = Issues::ListContext.new(project, current_user, params).execute + params[:scope] = 'all' if params[:scope].blank? + params[:state] = 'opened' if params[:state].blank? + @issues = FilteringService.new.execute(Issue, current_user, params.merge(project_id: @project.id)) end # Since iids are implemented only in 6.1 @@ -120,7 +127,7 @@ class Projects::IssuesController < Projects::ApplicationController # To prevent 404 errors we provide a redirect to correct iids until 7.0 release # def redirect_old - issue = @project.issues.find_by_id(params[:id]) + issue = @project.issues.find_by(id: params[:id]) if issue redirect_to project_issue_path(@project, issue) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 0cc09caf1d2c58eaf316627f3438e03b13c0f57f..de31ee12b6ad8537c98c439ec866b0d285af13e2 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -2,8 +2,8 @@ require 'gitlab/satellite/satellite' class Projects::MergeRequestsController < Projects::ApplicationController before_filter :module_enabled - before_filter :merge_request, only: [:edit, :update, :show, :commits, :diffs, :automerge, :automerge_check, :ci_status] - before_filter :closes_issues, only: [:edit, :update, :show, :commits, :diffs] + before_filter :merge_request, only: [:edit, :update, :show, :diffs, :automerge, :automerge_check, :ci_status] + before_filter :closes_issues, only: [:edit, :update, :show, :diffs] before_filter :validates_merge_request, only: [:show, :diffs] before_filter :define_show_vars, only: [:show, :diffs] @@ -17,7 +17,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort] def index - @merge_requests = MergeRequestsLoadContext.new(project, current_user, params).execute + params[:sort] ||= 'newest' + params[:scope] = 'all' if params[:scope].blank? + params[:state] = 'opened' if params[:state].blank? + + @merge_requests = FilteringService.new.execute(MergeRequest, current_user, params.merge(project_id: @project.id)) + @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? @@ -26,8 +33,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController def show respond_to do |format| format.html - format.js - format.diff { render text: @merge_request.to_diff(current_user) } format.patch { render text: @merge_request.to_patch(current_user) } end @@ -44,13 +49,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController diff_line_count = Commit::diff_line_count(@merge_request.diffs) @suppress_diff = Commit::diff_suppress?(@merge_request.diffs, diff_line_count) && !params[:force_show_diff] @force_suppress_diff = Commit::diff_force_suppress?(@merge_request.diffs, diff_line_count) + + respond_to do |format| + format.html + format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } } + end end def new @merge_request = MergeRequest.new(params[:merge_request]) @merge_request.source_project = @project unless @merge_request.source_project @merge_request.target_project = @project unless @merge_request.target_project - @target_branches = @merge_request.target_project.nil? ? [] : @merge_request.target_project.repository.branch_names @source_project = @merge_request.source_project @merge_request end @@ -66,7 +75,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.author = current_user @target_branches ||= [] if @merge_request.save - @merge_request.reload_code redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully created.' else @source_project = @merge_request.source_project @@ -76,9 +84,30 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def update + # If we close MergeRequest we want to ignore validation + # so we can close broken one (Ex. fork project removed) + if params[:merge_request] == {"state_event"=>"close"} + @merge_request.allow_broken = true + + if @merge_request.close + opts = { notice: 'Merge request was successfully closed.' } + else + opts = { alert: 'Failed to close merge request.' } + end + + redirect_to [@merge_request.target_project, @merge_request], opts + return + end + + # We dont allow change of source/target projects + # after merge request was created + params[:merge_request].delete(:source_project_id) + params[:merge_request].delete(:target_project_id) + if @merge_request.update_attributes(params[:merge_request].merge(author_id_of_changes: current_user.id)) @merge_request.reload_code @merge_request.mark_as_unchecked + @merge_request.reset_events_cache redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully updated.' else render "edit" @@ -99,7 +128,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController if @merge_request.opened? && @merge_request.can_be_merged? @merge_request.should_remove_source_branch = params[:should_remove_source_branch] - @merge_request.automerge!(current_user) + @merge_request.automerge!(current_user, params[:merge_commit_message]) @status = true else @status = false @@ -121,6 +150,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController @target_project = selected_target_project @target_branches = @target_project.repository.branch_names @target_branches + + respond_to do |format| + format.js + end end def ci_status @@ -133,11 +166,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController protected def selected_target_project - ((@project.id.to_s == params[:target_project_id]) || @project.forked_project_link.nil?) ? @project : @project.forked_project_link.forked_from_project + if @project.id.to_s == params[:target_project_id] || @project.forked_project_link.nil? + @project + else + @project.forked_project_link.forked_from_project + end end def merge_request - @merge_request ||= @project.merge_requests.find_by_iid!(params[:id]) + @merge_request ||= @project.merge_requests.find_by!(iid: params[:id]) end def closes_issues @@ -157,30 +194,34 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def validates_merge_request + # If source project was removed (Ex. mr from fork to origin) + return invalid_mr unless @merge_request.source_project + # Show git not found page # if there is no saved commits between source & target branch if @merge_request.commits.blank? - # and if source target doesn't exist - return invalid_mr unless @merge_request.target_project.repository.branch_names.include?(@merge_request.target_branch) + # and if target branch doesn't exist + return invalid_mr unless @merge_request.target_branch_exists? - # or if source branch doesn't exist - return invalid_mr unless @merge_request.source_project.repository.branch_names.include?(@merge_request.source_branch) + # or if source branch doesn't exist + return invalid_mr unless @merge_request.source_branch_exists? end end def define_show_vars # Build a note object for comment form @note = @project.notes.new(noteable: @merge_request) + @notes = @merge_request.mr_and_commit_notes.inc_author.fresh + @discussions = Note.discussions_from_notes(@notes) + @noteable = @merge_request # Get commits from repository # or from cache if already merged @commits = @merge_request.commits + @merge_request_diff = @merge_request.merge_request_diff @allowed_to_merge = allowed_to_merge? @show_merge_controls = @merge_request.opened? && @commits.any? && @allowed_to_merge - - @target_type = :merge_request - @target_id = @merge_request.id end def allowed_to_merge? diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index 39cd579cce5b75b38eaa0016e7d8712c9496118a..aea92a19f3456d29920eaeebc8d55d084ad299b3 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -14,7 +14,7 @@ class Projects::MilestonesController < Projects::ApplicationController @milestones = case params[:f] when 'all'; @project.milestones.order("state, due_date DESC") when 'closed'; @project.milestones.closed.order("due_date DESC") - else @project.milestones.active.order("due_date DESC") + else @project.milestones.active.order("due_date ASC") end @milestones = @milestones.includes(:project) @@ -34,11 +34,6 @@ class Projects::MilestonesController < Projects::ApplicationController @issues = @milestone.issues @users = @milestone.participants.uniq @merge_requests = @milestone.merge_requests - - respond_to do |format| - format.html - format.js - end end def create @@ -81,7 +76,7 @@ class Projects::MilestonesController < Projects::ApplicationController protected def milestone - @milestone ||= @project.milestones.find_by_iid!(params[:id]) + @milestone ||= @project.milestones.find_by!(iid: params[:id]) end def authorize_admin_milestone! diff --git a/app/controllers/projects/new_tree_controller.rb b/app/controllers/projects/new_tree_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..3a51a78ef6fbf484a0ead7815502edaefb373676 --- /dev/null +++ b/app/controllers/projects/new_tree_controller.rb @@ -0,0 +1,20 @@ +class Projects::NewTreeController < Projects::BaseTreeController + before_filter :require_branch_head + before_filter :authorize_push! + + def show + end + + def update + file_path = File.join(@path, File.basename(params[:file_name])) + result = Files::CreateService.new(@project, current_user, params, @ref, file_path).execute + + if result[:status] == :success + flash[:notice] = "Your changes have been successfully committed" + redirect_to project_blob_path(@project, File.join(@ref, file_path)) + else + flash[:alert] = result[:error] + render :show + end + end +end diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 8214163c3157416b63462172e14b29515d1aa457..9c9c2decc788e09d1d11755dfdc9e7c4fb03b3d9 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -2,62 +2,54 @@ class Projects::NotesController < Projects::ApplicationController # Authorize before_filter :authorize_read_note! before_filter :authorize_write_note!, only: [:create] - - respond_to :js + before_filter :authorize_admin_note!, only: [:update, :destroy] def index - @notes = Notes::LoadContext.new(project, current_user, params).execute - @target_type = params[:target_type].camelize - @target_id = params[:target_id] + @notes = Notes::LoadService.new(project, current_user, params).execute + + notes_json = { notes: [] } - if params[:target_type] == "merge_request" - @discussions = discussions_from_notes + @notes.each do |note| + notes_json[:notes] << { + id: note.id, + html: note_to_html(note) + } end - respond_with(@notes) + render json: notes_json end def create - @note = Notes::CreateContext.new(project, current_user, params).execute - @target_type = params[:target_type].camelize - @target_id = params[:target_id] + @note = Notes::CreateService.new(project, current_user, params).execute respond_to do |format| - format.html {redirect_to :back} - format.js + format.json { render_note_json(@note) } + format.html { redirect_to :back } end end - def destroy - @note = @project.notes.find(params[:id]) - return access_denied! unless can?(current_user, :admin_note, @note) - @note.destroy + def update + note.update_attributes(params[:note]) + note.reset_events_cache respond_to do |format| - format.js { render nothing: true } + format.json { render_note_json(note) } + format.html { redirect_to :back } end end - def update - @note = @project.notes.find(params[:id]) - return access_denied! unless can?(current_user, :admin_note, @note) - - @note.update_attributes(params[:note]) + def destroy + note.destroy + note.reset_events_cache respond_to do |format| - format.js do - render js: { success: @note.valid?, id: @note.id, note: view_context.markdown(@note.note) }.to_json - end - format.html do - redirect_to :back - end + format.js { render nothing: true } end end def delete_attachment - @note = @project.notes.find(params[:id]) - @note.remove_attachment! - @note.update_attribute(:attachment, nil) + note.remove_attachment! + note.update_attribute(:attachment, nil) respond_to do |format| format.js { render nothing: true } @@ -68,35 +60,40 @@ class Projects::NotesController < Projects::ApplicationController render text: view_context.markdown(params[:note]) end - protected + private - def discussion_notes_for(note) - @notes.select do |other_note| - note.discussion_id == other_note.discussion_id - end + def note + @note ||= @project.notes.find(params[:id]) end - def discussions_from_notes - discussion_ids = [] - discussions = [] + def note_to_html(note) + render_to_string( + "projects/notes/_note", + layout: false, + formats: [:html], + locals: { note: note } + ) + end - @notes.each do |note| - next if discussion_ids.include?(note.discussion_id) - - # don't group notes for the main target - if note_for_main_target?(note) - discussions << [note] - else - discussions << discussion_notes_for(note) - discussion_ids << note.discussion_id - end - end + def note_to_discussion_html(note) + render_to_string( + "projects/notes/_diff_notes_with_reply", + layout: false, + formats: [:html], + locals: { notes: [note] } + ) + end - discussions + def render_note_json(note) + render json: { + id: note.id, + discussion_id: note.discussion_id, + html: note_to_html(note), + discussion_html: note_to_discussion_html(note) + } end - # Helps to distinguish e.g. commit notes in mr notes list - def note_for_main_target?(note) - (@target_type.camelize == note.noteable_type && !note.for_diff_line?) + def authorize_admin_note! + return access_denied! unless can?(current_user, :admin_note, note) end end diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb index 81531bb0ac0a92a3b45905beaa8fd6b8609056b6..cfc1bd99a2040e8a91ff549d7c72aa648ebdd718 100644 --- a/app/controllers/projects/protected_branches_controller.rb +++ b/app/controllers/projects/protected_branches_controller.rb @@ -6,7 +6,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController before_filter :authorize_admin_project!, only: [:destroy, :create] def index - @branches = @project.protected_branches.all + @branches = @project.protected_branches.to_a @protected_branch = @project.protected_branches.new end diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb index 0c23d411f4c5040bbec86bb4ef73bcce69344e7d..18ace028b0c8dc9c5de9576b7e5e0833632d6ebc 100644 --- a/app/controllers/projects/raw_controller.rb +++ b/app/controllers/projects/raw_controller.rb @@ -8,9 +8,9 @@ class Projects::RawController < Projects::ApplicationController before_filter :require_non_empty_project def show - @blob = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, @path) + @blob = @repository.blob_at(@commit.id, @path) - if @blob.exists? + if @blob type = if @blob.mime_type =~ /html|javascript/ 'text/plain; charset=utf-8' else diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index e5c090e1f4dab63e565723a5d107d78d4518865d..16621c0371e1b7e6eaae25f05a1f136614133a84 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -24,13 +24,14 @@ class Projects::RefsController < Projects::ApplicationController format.js do @ref = params[:ref] define_tree_vars + tree render "tree" end end end def logs_tree - contents = @tree.entries + contents = tree.entries @logs = contents.map do |content| file = params[:path] ? File.join(params[:path], content.name) : content.name last_commit = @repo.commits(@commit.id, file, 1).last diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index 20e2a9311ee8dab7df8161b60ede132c31d23e7a..f686db70bd4771e8e9eecc6dd885b2463b60f130 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -16,7 +16,7 @@ class Projects::RepositoriesController < Projects::ApplicationController storage_path = Rails.root.join("tmp", "repositories") - file_path = @repository.archive_repo(params[:ref], storage_path) + file_path = @repository.archive_repo(params[:ref], storage_path, params[:format].downcase) if file_path # Send file to user diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index dd0c1a57089e51ae8f28e1b7e968ffccac14454d..f93f2d5f9bb9fa44240dac416c5839c70e32f088 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -48,8 +48,8 @@ class Projects::SnippetsController < Projects::ApplicationController def show @note = @project.notes.new(noteable: @snippet) - @target_type = :snippet - @target_id = @snippet.id + @notes = @snippet.notes.fresh + @noteable = @snippet end def destroy diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 9dbb0d818887f7161f372e66caa828f3526b3ce1..818c5d971e9784999faaa841301ccf30425a2f15 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -8,7 +8,7 @@ class Projects::TagsController < Projects::ApplicationController before_filter :authorize_admin_project!, only: [:destroy] def index - @tags = Kaminari.paginate_array(@repository.tags).page(params[:page]).per(30) + @tags = Kaminari.paginate_array(@repository.tags.reverse).page(params[:page]).per(30) end def create diff --git a/app/controllers/projects/team_members_controller.rb b/app/controllers/projects/team_members_controller.rb index b4b318fa59e95669187da58d845c3ffff26f2fbd..44068878cd1054b537ea2c2b4305f5f5c287a11d 100644 --- a/app/controllers/projects/team_members_controller.rb +++ b/app/controllers/projects/team_members_controller.rb @@ -1,6 +1,6 @@ class Projects::TeamMembersController < Projects::ApplicationController # Authorize - before_filter :authorize_admin_project! + before_filter :authorize_admin_project!, except: :leave layout "project_settings" @@ -26,7 +26,7 @@ class Projects::TeamMembersController < Projects::ApplicationController end def update - @user_project_relation = project.users_projects.find_by_user_id(member) + @user_project_relation = project.users_projects.find_by(user_id: member) @user_project_relation.update_attributes(params[:team_member]) unless @user_project_relation.valid? @@ -36,7 +36,7 @@ class Projects::TeamMembersController < Projects::ApplicationController end def destroy - @user_project_relation = project.users_projects.find_by_user_id(member) + @user_project_relation = project.users_projects.find_by(user_id: member) @user_project_relation.destroy respond_to do |format| @@ -45,6 +45,15 @@ class Projects::TeamMembersController < Projects::ApplicationController end end + def leave + project.users_projects.find_by(user_id: current_user).destroy + + respond_to do |format| + format.html { redirect_to :back } + format.js { render nothing: true } + end + end + def apply_import giver = Project.find(params[:source_project_id]) status = @project.team.import(giver) @@ -56,6 +65,6 @@ class Projects::TeamMembersController < Projects::ApplicationController protected def member - @member ||= User.find_by_username(params[:id]) + @member ||= User.find_by(username: params[:id]) end end diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index 5d543f356658059e39421267adad8a4dc69bc1de..30c94ec6da0b0bbbcdc778320c96d32711f706c6 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -1,13 +1,8 @@ # Controller for viewing a repository's file structure -class Projects::TreeController < Projects::ApplicationController - include ExtractsPath - - # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! - before_filter :require_non_empty_project - +class Projects::TreeController < Projects::BaseTreeController def show + return not_found! if tree.entries.empty? + respond_to do |format| format.html # Disable cache so browser history works diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 7264128691ee93eb3ffc0b3cee854f22e9085e58..6ec109b91452e63adacbbbd9f7c0380a08eba8d3 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -5,7 +5,7 @@ class ProjectsController < ApplicationController # Authorize before_filter :authorize_read_project!, except: [:index, :new, :create] - before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer] + before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive] before_filter :require_non_empty_project, only: [:blob, :tree, :graph] layout 'navless', only: [:new, :create, :fork] @@ -20,7 +20,7 @@ class ProjectsController < ApplicationController end def create - @project = ::Projects::CreateContext.new(current_user, params[:project]).execute + @project = ::Projects::CreateService.new(current_user, params[:project]).execute respond_to do |format| flash[:notice] = 'Project was successfully created.' if @project.saved? @@ -36,7 +36,7 @@ class ProjectsController < ApplicationController end def update - status = ::Projects::UpdateContext.new(@project, current_user, params).execute + status = ::Projects::UpdateService.new(@project, current_user, params).execute respond_to do |format| if status @@ -51,21 +51,17 @@ class ProjectsController < ApplicationController end def transfer - ::Projects::TransferContext.new(project, current_user, params).execute + ::Projects::TransferService.new(project, current_user, params).execute end def show - return authenticate_user! unless @project.public || current_user + return authenticate_user! unless @project.public? || current_user limit = (params[:limit] || 20).to_i @events = @project.events.recent @events = event_filter.apply_filter(@events) @events = @events.limit(limit).offset(params[:offset] || 0) - # Ensure project default branch is set if it possible - # Normally it defined on push or during creation - @project.discover_default_branch - respond_to do |format| format.html do if @project.empty_repo? @@ -77,7 +73,7 @@ class ProjectsController < ApplicationController render :show, layout: user_layout end end - format.js + format.json { pager_json("events/_events", @events.count) } end end @@ -93,7 +89,7 @@ class ProjectsController < ApplicationController end def fork - @forked_project = ::Projects::ForkContext.new(project, current_user).execute + @forked_project = ::Projects::ForkService.new(project, current_user).execute respond_to do |format| format.html do @@ -120,6 +116,24 @@ class ProjectsController < ApplicationController end end + def archive + return access_denied! unless can?(current_user, :archive_project, project) + project.archive! + + respond_to do |format| + format.html { redirect_to @project } + end + end + + def unarchive + return access_denied! unless can?(current_user, :archive_project, project) + project.unarchive! + + respond_to do |format| + format.html { redirect_to @project } + end + end + private def set_title diff --git a/app/controllers/public/projects_controller.rb b/app/controllers/public/projects_controller.rb index 87e903a1d2df38b327c26adc147840dc89580ebb..d7297161c22963cc67aab97c52ecd0a93d7e5231 100644 --- a/app/controllers/public/projects_controller.rb +++ b/app/controllers/public/projects_controller.rb @@ -6,8 +6,9 @@ class Public::ProjectsController < ApplicationController layout 'public' def index - @projects = Project.public_only + @projects = Project.public_or_internal_only(current_user) @projects = @projects.search(params[:search]) if params[:search].present? - @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20) + @projects = @projects.sort(@sort = params[:sort]) + @projects = @projects.includes(:namespace).page(params[:page]).per(20) end end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index f5c3bb133ed74bee9a0bc42da816c5b9bc13a4c9..c1648d6c387e6eea467b62567f71ca99a212296a 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -1,26 +1,23 @@ class SearchController < ApplicationController - def show - project_id = params[:project_id] - group_id = params[:group_id] + include SearchHelper - project_ids = current_user.authorized_projects.map(&:id) + def show + @project = Project.find_by(id: params[:project_id]) if params[:project_id].present? + @group = Group.find_by(id: params[:group_id]) if params[:group_id].present? - if group_id.present? - @group = Group.find(group_id) - group_project_ids = @group.projects.map(&:id) - project_ids.select! { |id| group_project_ids.include?(id)} - elsif project_id.present? - @project = Project.find(params[:project_id]) - project_ids.select! { |id| id == project_id.to_i} + if @project + return access_denied! unless can?(current_user, :download_code, @project) + @search_results = Search::ProjectService.new(@project, current_user, params).execute + else + @search_results = Search::GlobalService.new(current_user, params).execute end + end - result = SearchContext.new(project_ids, params).execute + def autocomplete + term = params[:term] + @project = Project.find(params[:project_id]) if params[:project_id].present? + @ref = params[:project_ref] if params[:project_ref].present? - @projects = result[:projects] - @merge_requests = result[:merge_requests] - @issues = result[:issues] - @wiki_pages = result[:wiki_pages] - @blobs = Kaminari.paginate_array(result[:blobs]).page(params[:page]).per(20) - @total_results = @projects.count + @merge_requests.count + @issues.count + @wiki_pages.count + @blobs.total_count + render json: search_autocomplete_opts(term).to_json end end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index b91f68aab5e21d9fb494473f11c1f213e7d714d7..e54a968326f8d163e849e1e8b822f8325c517aeb 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -18,7 +18,7 @@ class SnippetsController < ApplicationController end def user_index - @user = User.find_by_username(params[:username]) + @user = User.find_by(username: params[:username]) @snippets = @user.snippets.fresh.non_expired if @user == current_user diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 4947c33f959adf962e341086d425a8777af44e2e..6a5ce62909eda01575d372f9dfdfdc8027683289 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -2,8 +2,8 @@ class UsersController < ApplicationController layout 'navless' def show - @user = User.find_by_username!(params[:username]) - @projects = @user.authorized_projects.where('projects.id in (?)', current_user.authorized_projects.map(&:id)) + @user = User.find_by!(username: params[:username]) + @projects = @user.authorized_projects.where(id: current_user.authorized_projects.pluck(:id)).includes(:namespace) @events = @user.recent_events.where(project_id: @projects.map(&:id)).limit(20) @title = @user.name diff --git a/app/controllers/users_groups_controller.rb b/app/controllers/users_groups_controller.rb index 749da1e1413456a34fe7d21a95e44a01c7fa9050..bc5db445528b41223f454754b4c32bfb074e7a45 100644 --- a/app/controllers/users_groups_controller.rb +++ b/app/controllers/users_groups_controller.rb @@ -30,7 +30,7 @@ class UsersGroupsController < ApplicationController protected def group - @group ||= Group.find_by_path(params[:group_id]) + @group ||= Group.find_by(path: params[:group_id]) end def authorize_admin_group! diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 7e5c10fee05ed803db180ed75e9443fa408c0040..1550e8b7e056cbe70520900ebd97372d631fdad2 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -49,11 +49,29 @@ module ApplicationHelper args.any? { |v| v.to_s.downcase == action_name } end + def group_icon(group_path) + group = Group.find_by(path: group_path) + if group && group.avatar.present? + group.avatar.url + else + '/assets/no_group_avatar.png' + end + end + + def avatar_icon(user_email = '', size = nil) + user = User.find_by(email: user_email) + if user && user.avatar.present? + user.avatar.url + else + gravatar_icon(user_email, size) + end + end + def gravatar_icon(user_email = '', size = nil) size = 40 if size.nil? || size <= 0 if !Gitlab.config.gravatar.enabled || user_email.blank? - 'no_avatar.png' + '/assets/no_avatar.png' else gravatar_url = request.ssl? || gitlab_config.https ? Gitlab.config.gravatar.ssl_url : Gitlab.config.gravatar.plain_url user_email.strip! @@ -63,7 +81,7 @@ module ApplicationHelper def last_commit(project) if project.repo_exists? - time_ago_in_words(project.repository.commit.committed_date) + " ago" + time_ago_with_tooltip(project.repository.commit.committed_date) else "Never" end @@ -75,8 +93,8 @@ module ApplicationHelper repository = @project.repository options = [ - ["Branch", repository.branch_names ], - [ "Tag", repository.tag_names ] + ["Branches", repository.branch_names], + ["Tags", repository.tag_names] ] # If reference is commit id - @@ -89,51 +107,6 @@ module ApplicationHelper grouped_options_for_select(options, @ref || @project.default_branch) end - def search_autocomplete_source - return unless current_user - - projects = current_user.authorized_projects.map { |p| { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) } } - groups = current_user.authorized_groups.map { |group| { label: "group: #{simple_sanitize(group.name)}", url: group_path(group) } } - - default_nav = [ - { label: "My Profile", url: profile_path }, - { label: "My SSH Keys", url: profile_keys_path }, - { label: "My Dashboard", url: root_path }, - { label: "Admin Section", url: admin_root_path }, - ] - - help_nav = [ - { label: "help: API Help", url: help_api_path }, - { label: "help: Markdown Help", url: help_markdown_path }, - { label: "help: Permissions Help", url: help_permissions_path }, - { label: "help: Public Access Help", url: help_public_access_path }, - { label: "help: Rake Tasks Help", url: help_raketasks_path }, - { label: "help: SSH Keys Help", url: help_ssh_path }, - { label: "help: System Hooks Help", url: help_system_hooks_path }, - { label: "help: Web Hooks Help", url: help_web_hooks_path }, - { label: "help: Workflow Help", url: help_workflow_path }, - ] - - project_nav = [] - if @project && @project.repository.exists? && @project.repository.root_ref - project_nav = [ - { label: "#{simple_sanitize(@project.name_with_namespace)} - Files", url: project_tree_path(@project, @ref || @project.repository.root_ref) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Commits", url: project_commits_path(@project, @ref || @project.repository.root_ref) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Network", url: project_network_path(@project, @ref || @project.repository.root_ref) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Graph", url: project_graph_path(@project, @ref || @project.repository.root_ref) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Issues", url: project_issues_path(@project) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Merge Requests", url: project_merge_requests_path(@project) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Milestones", url: project_milestones_path(@project) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Snippets", url: project_snippets_path(@project) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Team", url: project_team_index_path(@project) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Wall", url: project_wall_path(@project) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Wiki", url: project_wikis_path(@project) }, - ] - end - - [groups, projects, default_nav, project_nav, help_nav].flatten.to_json - end - def emoji_autocomplete_source # should be an array of strings # so to_s can be called, because it is sufficient and to_json is too slow @@ -162,6 +135,9 @@ module ApplicationHelper # Skip if user already created appropriate MR return false if project.merge_requests.where(source_branch: event.branch_name).opened.any? + # Skip if user removed branch right after that + return false unless project.repository.branch_names.include?(event.branch_name) + true end @@ -169,21 +145,13 @@ module ApplicationHelper Digest::SHA1.hexdigest string end - def project_last_activity project - if project.last_activity_at - time_ago_in_words(project.last_activity_at) + " ago" - else - "Never" - end - end - def authbutton(provider, size = 64) file_name = "#{provider.to_s.split('_').first}_#{size}.png" image_tag("authbuttons/#{file_name}", alt: "Sign in with #{provider.to_s.titleize}") end - def simple_sanitize str + def simple_sanitize(str) sanitize(str, tags: %w(a span)) end @@ -220,14 +188,6 @@ module ApplicationHelper Gitlab.config.extra end - def public_icon - content_tag :i, nil, class: 'icon-globe cblue' - end - - def private_icon - content_tag :i, nil, class: 'icon-lock cgreen' - end - def search_placeholder if @project && @project.persisted? "Search in this project" @@ -244,4 +204,42 @@ module ApplicationHelper line += "..." if lines.size > 1 line end + + def broadcast_message + BroadcastMessage.current + end + + def highlight_js(&block) + string = capture(&block) + + content_tag :div, class: "highlighted-data #{user_color_scheme_class}" do + content_tag :div, class: 'highlight' do + content_tag :pre do + content_tag :code do + string.html_safe + end + end + end + end + end + + def time_ago_with_tooltip(date, placement = 'top', html_class = 'time_ago') + capture_haml do + haml_tag :time, date.to_s, + class: html_class, datetime: date.getutc.iso8601, title: date.stamp("Aug 21, 2011 9:23pm"), + data: { toggle: 'tooltip', placement: placement } + + haml_tag :script, "$('." + html_class + "').timeago().tooltip()" + end.html_safe + end + + def render_markup(file_name, file_content) + GitHub::Markup.render(file_name, file_content).html_safe + end + + def spinner(text = nil) + content_tag :div, class: 'loading hide' do + content_tag(:i, nil, class: 'icon-spinner icon-spin') + text + end + end end diff --git a/app/helpers/broadcast_messages_helper.rb b/app/helpers/broadcast_messages_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..29ff47663dace2ed459de012e4173fd6495d42a7 --- /dev/null +++ b/app/helpers/broadcast_messages_helper.rb @@ -0,0 +1,9 @@ +module BroadcastMessagesHelper + def broadcast_styling(broadcast_message) + if(broadcast_message.color || broadcast_message.font) + "background-color:#{broadcast_message.color};color:#{broadcast_message.font}" + else + "" + end + end +end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index f8f84ff8b62ed638ace978cd56efd4ee6ea59597..663369e45840443129ebab1d294be060043fe2a1 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -94,6 +94,21 @@ module CommitsHelper crumbs.html_safe end + # Return Project default branch, if it present in array + # Else - first branch in array (mb last actual branch) + def commit_default_branch(project, branches) + branches.include?(project.default_branch) ? branches.delete(project.default_branch) : branches.pop + end + + # Returns the sorted alphabetically links to branches, separated by a comma + def commit_branches_links(project, branches) + branches.sort.map { |branch| link_to(branch, project_tree_path(project, branch)) }.join(", ").html_safe + end + + def get_old_file(project, commit, diff) + project.repository.blob_at(commit.parent_id, diff.old_path) if commit.parent_id + end + protected # Private: Returns a link to a person. If the person has a matching user and @@ -108,13 +123,15 @@ module CommitsHelper source_name = commit.send "#{options[:source]}_name".to_sym source_email = commit.send "#{options[:source]}_email".to_sym text = if options[:avatar] - avatar = image_tag(gravatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "") + avatar = image_tag(avatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "") %Q{#{avatar} <span class="commit-#{options[:source]}-name">#{source_name}</span>} else source_name end - user = User.where('name like ? or email like ?', source_name, source_email).first + # Prefer email match over name match + user = User.where(email: source_email).first + user ||= User.where(name: source_name).first options = { class: "commit-#{options[:source]}-link has_tooltip", diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb index ea2540bf385e31511bc1c438cb5fd14391e4082a..5ff19b8829381a9e0e0c363def1cd73d8886f9b2 100644 --- a/app/helpers/compare_helper.rb +++ b/app/helpers/compare_helper.rb @@ -1,6 +1,8 @@ module CompareHelper def compare_to_mr_button? - params[:from].present? && params[:to].present? && + @project.merge_requests_enabled && + params[:from].present? && + params[:to].present? && @repository.branch_names.include?(params[:from]) && @repository.branch_names.include?(params[:to]) && params[:from] != params[:to] && diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb index 35c7bcbd2cf2c5f815d04e52992dc285a99bd568..d5712ab3374140c89ad61100164ce4f04cb2f77e 100644 --- a/app/helpers/dashboard_helper.rb +++ b/app/helpers/dashboard_helper.rb @@ -1,7 +1,8 @@ module DashboardHelper def filter_path(entity, options={}) exist_opts = { - status: params[:status], + state: params[:state], + scope: params[:scope], project_id: params[:project_id], } @@ -12,18 +13,26 @@ module DashboardHelper path end - def entities_per_project project, entity - items = project.items_for(entity) + def entities_per_project(project, entity) + case entity.to_sym + when :issue then @issues.where(project_id: project.id) + when :merge_request then @merge_requests.where(target_project_id: project.id) + else + [] + end.count + end + + def projects_dashboard_filter_path(options={}) + exist_opts = { + sort: params[:sort], + scope: params[:scope], + group: params[:group], + } - items = case params[:status] - when 'closed' - items.closed - when 'all' - items - else - items.opened - end + options = exist_opts.merge(options) - items.cared(current_user).count + path = request.path + path << "?#{options.to_param}" + path end end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index cd8761a6113f6774ec799aec57f683176cad04b4..929f9a9c38177d37e23e11da77dbdcd3956f3f7e 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -90,27 +90,23 @@ module EventsHelper if event.note? && event.note_commit? project_commit_path(event.project, event.note_target) else - url_for([event.project, event.note_target]) + polymorphic_path([event.project, event.note_target], anchor: dom_id(event.target)) end end def event_note_title_html(event) if event.note_target if event.note_commit? - link_to project_commit_path(event.project, event.note_commit_id), class: "commit_short_id" do + link_to project_commit_path(event.project, event.note_commit_id, anchor: dom_id(event.target)), class: "commit_short_id" do "#{event.note_target_type} #{event.note_short_commit_id}" end elsif event.note_project_snippet? link_to(project_snippet_path(event.project, event.note_target)) do - content_tag :strong do - "#{event.note_target_type} ##{truncate event.note_target_id}" - end + "#{event.note_target_type} ##{truncate event.note_target_id}" end else link_to event_note_target_path(event) do - content_tag :strong do - "#{event.note_target_type} ##{truncate event.note_target_iid}" - end + "#{event.note_target_type} ##{truncate event.note_target_iid}" end end elsif event.wall_note? @@ -127,4 +123,10 @@ module EventsHelper text = truncate(text, length: 150) sanitize(markdown(text), tags: %w(a img b pre p)) end + + def event_commit_title(message) + escape_once(truncate(message.split("\n").first, length: 70)) + rescue + "--broken encoding" + end end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 375f8861daea5b110c5fa6bd9915ecf24cbc2f36..315f1b805b5f7ad8a2997e63edb85616b4f6367f 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -34,7 +34,8 @@ module GitlabMarkdownHelper # see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch- filter_html: true, with_toc_data: true, - hard_wrap: true) + hard_wrap: true, + safe_links_only: true) @markdown = Redcarpet::Markdown.new(gitlab_renderer, # see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use no_intra_emphasis: true, @@ -57,4 +58,139 @@ module GitlabMarkdownHelper wiki_page.formatted_content.html_safe end end + + # text - whole text from a markdown file + # project_path_with_namespace - namespace/projectname, eg. gitlabhq/gitlabhq + # ref - name of the branch or reference, eg. stable + # requested_path - path of request, eg. doc/api/README.md, used in special case when path is pointing to the .md file were the original request is coming from + # wiki - whether the markdown is from wiki or not + def create_relative_links(text, project, ref, requested_path, wiki = false) + @path_to_satellite = project.satellite.path + project_path_with_namespace = project.path_with_namespace + paths = extract_paths(text) + paths.each do |file_path| + original_file_path = extract(file_path) + new_path = rebuild_path(project_path_with_namespace, original_file_path, requested_path, ref) + if reference_path?(file_path) + # Replacing old string with a new one that contains updated path + # eg. [some document]: document.md will be replaced with [some document] /namespace/project/master/blob/document.md + text.gsub!(file_path, file_path.gsub(original_file_path, "/#{new_path}")) + else + # Replacing old string with a new one with brackets ]() to prevent replacing occurence of a word + # e.g. If we have a markdown like [test](test) this will replace ](test) and not the word test + text.gsub!("](#{file_path})", "](/#{new_path})") + end + end + text + end + + def extract_paths(markdown_text) + all_markdown_paths = pick_out_paths(markdown_text) + paths = remove_empty(all_markdown_paths) + select_relative(paths) + end + + # Split the markdown text to each line and find all paths, this will match anything with - ]("some_text") and [some text]: file.md + def pick_out_paths(markdown_text) + inline_paths = markdown_text.split("\n").map { |text| text.scan(/\]\(([^(]+)\)/) } + reference_paths = markdown_text.split("\n").map { |text| text.scan(/\[.*\]:.*/) } + inline_paths + reference_paths + end + + # Removes any empty result produced by not matching the regexp + def remove_empty(paths) + paths.reject{|l| l.empty? }.flatten + end + + # If a path is a reference style link we need to omit ]: + def extract(path) + path.split("]: ").last + end + + # Reject any path that contains ignored protocol + # eg. reject "https://gitlab.org} but accept "doc/api/README.md" + def select_relative(paths) + paths.reject{|path| ignored_protocols.map{|protocol| path.include?(protocol)}.any?} + end + + # Check whether a path is a reference-style link + def reference_path?(path) + path.include?("]: ") + end + + def ignored_protocols + ["http://","https://", "ftp://", "mailto:"] + end + + def rebuild_path(path_with_namespace, path, requested_path, ref) + file_path = relative_file_path(path, requested_path) + [ + path_with_namespace, + path_with_ref(file_path, ref), + file_path + ].compact.join("/") + end + + # Checks if the path exists in the repo + # eg. checks if doc/README.md exists, if it doesn't then it is a wiki link + def path_with_ref(path, ref) + if file_exists?(path) + "#{local_path(path)}/#{correct_ref(ref)}" + else + "wikis" + end + end + + def relative_file_path(path, requested_path) + nested_path = build_nested_path(path, requested_path) + return nested_path if file_exists?(nested_path) + path + end + + # Covering a special case, when the link is referencing file in the same directory eg: + # If we are at doc/api/README.md and the README.md contains relative links like [Users](users.md) + # this takes the request path(doc/api/README.md), and replaces the README.md with users.md so the path looks like doc/api/users.md + # If we are at doc/api and the README.md shown in below the tree view + # this takes the rquest path(doc/api) and adds users.md so the path looks like doc/api/users.md + def build_nested_path(path, request_path) + return path unless request_path + if local_path(request_path) == "tree" + base = request_path.split("/").push(path) + base.join("/") + else + base = request_path.split("/") + base.pop + base.push(path).join("/") + end + end + + def file_exists?(path) + return false if path.nil? || path.empty? + return @repository.blob_at(current_sha, path).present? || @repository.tree(current_sha, path).entries.any? + end + + # Check if the path is pointing to a directory(tree) or a file(blob) + # eg. doc/api is directory and doc/README.md is file + def local_path(path) + return "tree" if @repository.tree(current_sha, path).entries.any? + return "raw" if @repository.blob_at(current_sha, path).image? + return "blob" + end + + def current_ref + @commit.nil? ? "master" : @commit.id + end + + def current_sha + if @commit + @commit.id + else + @repository.head_commit.sha + end + end + + # We will assume that if no ref exists we can point to master + def correct_ref(ref) + ref ? ref : "master" + end end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 8573c59dc9486e82dc98c79f2ff88330afef377f..7c09273d53edcaaa50229393834f0ef6e1549af9 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -2,4 +2,23 @@ module GroupsHelper def remove_user_from_group_message(group, user) "You are going to remove #{user.name} from #{group.name} Group. Are you sure?" end + + def group_head_title + title = @group.name + + title = if current_action?(:issues) + "Issues - " + title + elsif current_action?(:merge_requests) + "Merge requests - " + title + elsif current_action?(:members) + "Members - " + title + elsif current_action?(:edit) + "Settings - " + title + else + title + end + + title + + end end diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..53d4a8f2e6e42eff33cb0e4a66db5e149dbb160e --- /dev/null +++ b/app/helpers/icons_helper.rb @@ -0,0 +1,21 @@ +module IconsHelper + def boolean_to_icon(value) + if value.to_s == "true" + content_tag :i, nil, class: 'icon-ok cgreen' + else + content_tag :i, nil, class: 'icon-off clgray' + end + end + + def public_icon + content_tag :i, nil, class: 'icon-globe' + end + + def internal_icon + content_tag :i, nil, class: 'icon-shield' + end + + def private_icon + content_tag :i, nil, class: 'icon-lock' + end +end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 5977c9cbae21ad52393e59aaf50a2b6c772e381f..cdba6ce84dc3573e6409a972b37e0757a66997b7 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -16,7 +16,7 @@ module IssuesHelper def url_for_project_issues return "" if @project.nil? - if @project.used_default_issues_tracker? + if @project.used_default_issues_tracker? || !external_issues_tracker_enabled? project_issues_path(@project) else url = Gitlab.config.issues_tracker[@project.issues_tracker]["project_url"] @@ -28,7 +28,7 @@ module IssuesHelper def url_for_new_issue return "" if @project.nil? - if @project.used_default_issues_tracker? + if @project.used_default_issues_tracker? || !external_issues_tracker_enabled? url = new_project_issue_path project_id: @project else url = Gitlab.config.issues_tracker[@project.issues_tracker]["new_issue_url"] @@ -40,7 +40,7 @@ module IssuesHelper def url_for_issue(issue_iid) return "" if @project.nil? - if @project.used_default_issues_tracker? + if @project.used_default_issues_tracker? || !external_issues_tracker_enabled? url = project_issue_url project_id: @project, id: issue_iid else url = Gitlab.config.issues_tracker[@project.issues_tracker]["issues_url"] @@ -59,4 +59,29 @@ module IssuesHelper "" end end + + # Checks if issues_tracker setting exists in gitlab.yml + def external_issues_tracker_enabled? + if Gitlab.config.issues_tracker && Gitlab.config.issues_tracker.values.any? + true + else + false + end + end + + def bulk_update_milestone_options + options_for_select(["None (backlog)", nil]) + options_from_collection_for_select(project_active_milestones, "id", "title", params[:milestone_id]) + end + + def bulk_update_assignee_options + options_for_select(["None (unassigned)", nil]) + options_from_collection_for_select(@project.team.members, "id", "name", params[:assignee_id]) + end + + def assignee_options object + options_from_collection_for_select(@project.team.members.sort_by(&:name), 'id', 'name', object.assignee_id) + end + + def milestone_options object + options_from_collection_for_select(@project.milestones.active, 'id', 'title', object.milestone_id) + end end diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 9a6aea85ee9877dc777153e641f102bf0c0b83ad..d1eb3808f9e8f2ee6b9f01d15d81f603971b41d7 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -16,11 +16,11 @@ module LabelsHelper when *klass.warning_labels 'label-warning' when *klass.neutral_labels - 'label-inverse' + 'label-primary' when *klass.positive_labels 'label-success' when *klass.important_labels - 'label-important' + 'label-danger' else 'label-info' end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 4ba48aa4339f144bd28be55b44a48c93d47364d0..5e3f82fe9ceffdc29bc6fd96569a692f8d95cbdc 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -36,7 +36,7 @@ module MergeRequestsHelper def merge_path_description(merge_request, separator) if merge_request.for_fork? - "Project:Branches: #{@merge_request.source_project.path_with_namespace}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.path_with_namespace}:#{@merge_request.target_branch}" + "Project:Branches: #{@merge_request.source_project_path}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.path_with_namespace}:#{@merge_request.target_branch}" else "Branches: #{@merge_request.source_branch} #{separator} #{@merge_request.target_branch}" end diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index dc88e178360a52cece7d46293d15bbe1f3b02c5f..c363c7ffd7450ed12fd4434bcc3a0fc3180ddd12 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -1,7 +1,7 @@ module NamespacesHelper def namespaces_options(selected = :current_user, scope = :default) - groups = current_user.owned_groups.select {|n| n.type == 'Group'} - users = current_user.namespaces.reject {|n| n.type == 'Group'} + groups = current_user.owned_groups + users = [current_user.namespace] group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [g.human_name, g.id]} ] users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [u.human_name, u.id]} ] @@ -16,4 +16,13 @@ module NamespacesHelper grouped_options_for_select(options, selected) end + + def namespace_select_tag(id, opts = {}) + css_class = "ajax-namespace-select " + css_class << "multiselect " if opts[:multiple] + css_class << (opts[:class] || '') + value = opts[:selected] || '' + + hidden_field_tag(id, value, class: css_class) + end end diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index a3ec4cca59de85d3061ceab135c3d96dd157e174..695147cd6dbcaa31f46070e5bcadd93be7a668d6 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -1,7 +1,7 @@ module NotesHelper # Helps to distinguish e.g. commit notes in mr notes list def note_for_main_target?(note) - (@target_type.camelize == note.noteable_type && !note.for_diff_line?) + (@noteable.class.name == note.noteable_type && !note.for_diff_line?) end def note_target_fields @@ -21,18 +21,25 @@ module NotesHelper end end - def loading_more_notes? - params[:loading_more].present? - end - - def loading_new_notes? - params[:loading_new].present? - end - def note_timestamp(note) # Shows the created at time and the updated at time if different - ts = "#{time_ago_in_words(note.created_at)} ago" - ts << content_tag(:small, " (Edited #{time_ago_in_words(note.updated_at)} ago)") if note.updated_at != note.created_at + ts = "#{time_ago_with_tooltip(note.created_at, 'bottom', 'note_created_ago')}" + if note.updated_at != note.created_at + ts << capture_haml do + haml_tag :small do + haml_concat " (Edited #{time_ago_with_tooltip(note.updated_at, 'bottom', 'note_edited_ago')})" + end + end + end ts.html_safe end + + def noteable_json(noteable) + { + id: noteable.id, + class: noteable.class.name, + resources: noteable.class.table_name, + project_id: noteable.project.id, + }.to_json + end end diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb index 88d9f184d0e50f879113df5a6c9a26ca9806c1d3..dd9e03d95a8e942367fb582167ba4af751457455 100644 --- a/app/helpers/profile_helper.rb +++ b/app/helpers/profile_helper.rb @@ -14,6 +14,6 @@ module ProfileHelper end def show_profile_remove_tab? - Gitlab.config.gitlab.signup_enabled && !current_user.ldap_user? + gitlab_config.signup_enabled && !current_user.ldap_user? end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 9071c688df1328c9e37b9dfa0e484a9b67d6af43..a6a507360bdf060f7850310dab7ff2469b112aa1 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -5,10 +5,10 @@ module ProjectsHelper def link_to_project project link_to project do - title = content_tag(:strong, project.name) + title = content_tag(:span, project.name, class: 'projet-name') if project.namespace - namespace = content_tag(:span, "#{project.namespace.human_name} / ", class: 'tiny') + namespace = content_tag(:span, "#{project.namespace.human_name} / ", class: 'namespace-name') title = namespace + title end @@ -25,7 +25,7 @@ module ProjectsHelper author_html = "" # Build avatar image tag - author_html << image_tag(gravatar_icon(author.try(:email), opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar] + author_html << image_tag(avatar_icon(author.try(:email), opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar] # Build name span tag author_html << content_tag(:span, sanitize(author.name), class: 'author') if opts[:name] @@ -45,7 +45,10 @@ module ProjectsHelper link_to(simple_sanitize(project.group.name), group_path(project.group)) + " / " + project.name end else - project.name + owner = project.namespace.owner + content_tag :span do + link_to(simple_sanitize(owner.name), user_path(owner)) + " / " + project.name + end end end @@ -67,6 +70,8 @@ module ProjectsHelper scope: params[:scope], label_name: params[:label_name], milestone_id: params[:milestone_id], + assignee_id: params[:assignee_id], + sort: params[:sort], } options = exist_opts.merge(options) @@ -77,7 +82,19 @@ module ProjectsHelper end def project_active_milestones - @project.milestones.active.order("id desc").all + @project.milestones.active.order("due_date, title ASC") + end + + def project_issues_trackers(current_tracker = nil) + values = Project.issues_tracker.values.map do |tracker_key| + if tracker_key.to_sym == :gitlab + ['GitLab', tracker_key] + else + [Gitlab.config.issues_tracker[tracker_key]['title'] || tracker_key, tracker_key] + end + end + + options_for_select(values, current_tracker) end private @@ -119,4 +136,64 @@ module ProjectsHelper "your@email.com" end end + + def repository_size(project = nil) + "#{(project || @project).repository.size} MB" + rescue + # In order to prevent 500 error + # when application cannot allocate memory + # to calculate repo size - just show 'Unknown' + 'unknown' + end + + def project_head_title + title = @project.name_with_namespace + + title = if current_controller?(:tree) + "#{@project.path}\/#{@path} at #{@ref} - " + title + elsif current_controller?(:issues) + if current_action?(:show) + "Issue ##{@issue.iid} - #{@issue.title} - " + title + else + "Issues - " + title + end + elsif current_controller?(:blob) + "#{@project.path}\/#{@blob.path} at #{@ref} - " + title + elsif current_controller?(:commits) + "Commits at #{@ref} - " + title + elsif current_controller?(:merge_requests) + if current_action?(:show) + "Merge request ##{@merge_request.iid} - " + title + else + "Merge requests - " + title + end + elsif current_controller?(:wikis) + "Wiki - " + title + elsif current_controller?(:network) + "Network graph - " + title + elsif current_controller?(:graphs) + "Graphs - " + title + else + title + end + + title + end + + def default_url_to_repo(project = nil) + project = project || @project + current_user ? project.url_to_repo : project.http_url_to_repo + end + + def default_clone_protocol + current_user ? "ssh" : "http" + end + + def project_last_activity(project) + if project.last_activity_at + time_ago_with_tooltip(project.last_activity_at, 'bottom', 'last_activity_time_ago') + else + "Never" + end + end end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..470a495f03689965861340bc49efa634a203842c --- /dev/null +++ b/app/helpers/search_helper.rb @@ -0,0 +1,106 @@ +module SearchHelper + def search_autocomplete_opts(term) + return unless current_user + + resources_results = [ + groups_autocomplete(term), + projects_autocomplete(term), + public_projects_autocomplete(term), + ].flatten + + generic_results = project_autocomplete + default_autocomplete + help_autocomplete + generic_results.select! { |result| result[:label] =~ Regexp.new(term, "i") } + + [ + resources_results, + generic_results + ].flatten.uniq do |item| + item[:label] + end + end + + private + + # Autocomplete results for various settings pages + def default_autocomplete + [ + { label: "My Profile settings", url: profile_path }, + { label: "My SSH Keys", url: profile_keys_path }, + { label: "My Dashboard", url: root_path }, + { label: "Admin Section", url: admin_root_path }, + ] + end + + # Autocomplete results for internal help pages + def help_autocomplete + [ + { label: "help: API Help", url: help_api_path }, + { label: "help: Markdown Help", url: help_markdown_path }, + { label: "help: Permissions Help", url: help_permissions_path }, + { label: "help: Public Access Help", url: help_public_access_path }, + { label: "help: Rake Tasks Help", url: help_raketasks_path }, + { label: "help: SSH Keys Help", url: help_ssh_path }, + { label: "help: System Hooks Help", url: help_system_hooks_path }, + { label: "help: Web Hooks Help", url: help_web_hooks_path }, + { label: "help: Workflow Help", url: help_workflow_path }, + ] + end + + # Autocomplete results for the current project, if it's defined + def project_autocomplete + if @project && @project.repository.exists? && @project.repository.root_ref + prefix = search_result_sanitize(@project.name_with_namespace) + ref = @ref || @project.repository.root_ref + + [ + { label: "#{prefix} - Files", url: project_tree_path(@project, ref) }, + { label: "#{prefix} - Commits", url: project_commits_path(@project, ref) }, + { label: "#{prefix} - Network", url: project_network_path(@project, ref) }, + { label: "#{prefix} - Graph", url: project_graph_path(@project, ref) }, + { label: "#{prefix} - Issues", url: project_issues_path(@project) }, + { label: "#{prefix} - Merge Requests", url: project_merge_requests_path(@project) }, + { label: "#{prefix} - Milestones", url: project_milestones_path(@project) }, + { label: "#{prefix} - Snippets", url: project_snippets_path(@project) }, + { label: "#{prefix} - Team", url: project_team_index_path(@project) }, + { label: "#{prefix} - Wall", url: project_wall_path(@project) }, + { label: "#{prefix} - Wiki", url: project_wikis_path(@project) }, + ] + else + [] + end + end + + # Autocomplete results for the current user's groups + def groups_autocomplete(term, limit = 5) + current_user.authorized_groups.search(term).limit(limit).map do |group| + { + label: "group: #{search_result_sanitize(group.name)}", + url: group_path(group) + } + end + end + + # Autocomplete results for the current user's projects + def projects_autocomplete(term, limit = 5) + current_user.authorized_projects.search_by_title(term).non_archived.limit(limit).map do |p| + { + label: "project: #{search_result_sanitize(p.name_with_namespace)}", + url: project_path(p) + } + end + end + + # Autocomplete results for the current user's projects + def public_projects_autocomplete(term, limit = 5) + Project.public_or_internal_only(current_user).search_by_title(term).non_archived.limit(limit).map do |p| + { + label: "project: #{search_result_sanitize(p.name_with_namespace)}", + url: project_path(p) + } + end + end + + def search_result_sanitize(str) + Sanitize.clean(str) + end +end diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index ce675872264bae7bb11c81958ef362cb370c29d5..bd373c5f3cfa62def8429e9388cfbbb0c8c69599 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -92,7 +92,12 @@ module TabHelper def nav_tab key, value, &block o = {} o[:class] = "" - o[:class] << " active" if params[key] == value + + if value.nil? + o[:class] << " active" if params[key].blank? + else + o[:class] << " active" if params[key] == value + end if block_given? content_tag(:li, capture(&block), o) diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index 73d36d0801c75b14b83a4885bd0249b6e80141d5..2dbc1cffb16c7ecfe7120d8333408ff9cde2b706 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -67,9 +67,9 @@ module TreeHelper end def tree_breadcrumbs(tree, max_links = 2) - if tree.path + if @path.present? part_path = "" - parts = tree.path.split("\/") + parts = @path.split("\/") yield('..', nil) if parts.count > max_links @@ -78,14 +78,14 @@ module TreeHelper part_path = part if part_path.empty? next unless parts.last(2).include?(part) if parts.count > max_links - yield(part, tree_join(tree.ref, part_path)) + yield(part, tree_join(@ref, part_path)) end end end def up_dir_path tree - file = File.join(tree.path, "..") - tree_join(tree.ref, file) + file = File.join(@path, "..") + tree_join(@ref, file) end def leave_edit_message diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..81e10f3685c9783faeff9d6c8fa325d53d613b2d --- /dev/null +++ b/app/helpers/visibility_level_helper.rb @@ -0,0 +1,49 @@ +module VisibilityLevelHelper + def visibility_level_color(level) + case level + when Gitlab::VisibilityLevel::PRIVATE + 'cgreen' + when Gitlab::VisibilityLevel::INTERNAL + 'camber' + when Gitlab::VisibilityLevel::PUBLIC + 'cblue' + end + end + + def visibility_level_description(level) + capture_haml do + haml_tag :span do + case level + when Gitlab::VisibilityLevel::PRIVATE + haml_concat "Project access must be granted explicitly for each user." + when Gitlab::VisibilityLevel::INTERNAL + haml_concat "The project can be cloned by" + haml_concat "any logged in user." + when Gitlab::VisibilityLevel::PUBLIC + haml_concat "The project can be cloned" + haml_concat "without any" + haml_concat "authentication." + end + end + end + end + + def visibility_level_icon(level) + case level + when Gitlab::VisibilityLevel::PRIVATE + private_icon + when Gitlab::VisibilityLevel::INTERNAL + internal_icon + when Gitlab::VisibilityLevel::PUBLIC + public_icon + end + end + + def visibility_level_label(level) + Project.visibility_levels.key(level) + end + + def restricted_visibility_levels + current_user.is_admin? ? [] : gitlab_config.restricted_visibility_levels + end +end diff --git a/app/mailers/emails/groups.rb b/app/mailers/emails/groups.rb index 2e9d28981e37f2f987ae5144ef7c74d88ce5e081..1c8ae122c4699d04d1cc9a683ced2a57bdb5de7c 100644 --- a/app/mailers/emails/groups.rb +++ b/app/mailers/emails/groups.rb @@ -5,7 +5,7 @@ module Emails @group = @membership.group mail(to: @membership.user.email, - subject: subject("access to group was granted")) + subject: subject("Access to group was granted")) end end end diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index 6eda88c792115ac34e344529c65fe781bc33dbbe..b2b4b83d6c313e947bc6b971ca0caf6d33fb3de3 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -3,14 +3,14 @@ module Emails def new_issue_email(recipient_id, issue_id) @issue = Issue.find(issue_id) @project = @issue.project - mail(to: recipient(recipient_id), subject: subject("new issue ##{@issue.iid}", @issue.title)) + mail(to: recipient(recipient_id), subject: subject("New issue ##{@issue.iid}", @issue.title)) end def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id) @issue = Issue.find(issue_id) - @previous_assignee = User.find_by_id(previous_assignee_id) if previous_assignee_id + @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id @project = @issue.project - mail(to: recipient(recipient_id), subject: subject("changed issue ##{@issue.iid}", @issue.title)) + mail(to: recipient(recipient_id), subject: subject("Changed issue ##{@issue.iid}", @issue.title)) end def closed_issue_email(recipient_id, issue_id, updated_by_user_id) @@ -27,7 +27,7 @@ module Emails @project = @issue.project @updated_by = User.find updated_by_user_id mail(to: recipient(recipient_id), - subject: subject("changed issue ##{@issue.iid}", @issue.title)) + subject: subject("Changed issue ##{@issue.iid}", @issue.title)) end end end diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 57c1925fa47e786d45bf899300134111ff900524..e60887d525a63b57e110626ddf9439378275020d 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -2,24 +2,28 @@ module Emails module MergeRequests def new_merge_request_email(recipient_id, merge_request_id) @merge_request = MergeRequest.find(merge_request_id) - mail(to: recipient(recipient_id), subject: subject("new merge request !#{@merge_request.iid}", @merge_request.title)) + @project = @merge_request.project + mail(to: recipient(recipient_id), subject: subject("New merge request ##{@merge_request.iid}", @merge_request.title)) end def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id) @merge_request = MergeRequest.find(merge_request_id) - @previous_assignee = User.find_by_id(previous_assignee_id) if previous_assignee_id - mail(to: recipient(recipient_id), subject: subject("changed merge request !#{@merge_request.iid}", @merge_request.title)) + @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id + @project = @merge_request.project + mail(to: recipient(recipient_id), subject: subject("Changed merge request ##{@merge_request.iid}", @merge_request.title)) end def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) @merge_request = MergeRequest.find(merge_request_id) @updated_by = User.find updated_by_user_id - mail(to: recipient(recipient_id), subject: subject("Closed merge request !#{@merge_request.iid}", @merge_request.title)) + @project = @merge_request.project + mail(to: recipient(recipient_id), subject: subject("Closed merge request ##{@merge_request.iid}", @merge_request.title)) end def merged_merge_request_email(recipient_id, merge_request_id) @merge_request = MergeRequest.find(merge_request_id) - mail(to: recipient(recipient_id), subject: subject("Accepted merge request !#{@merge_request.iid}", @merge_request.title)) + @project = @merge_request.project + mail(to: recipient(recipient_id), subject: subject("Accepted merge request ##{@merge_request.iid}", @merge_request.title)) end end diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index 761b4c8161f1be03fb679676729256e74d0a2341..e967cf6dc739cb7f4257caf3bd456edb44bcaeaa 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -4,27 +4,27 @@ module Emails @note = Note.find(note_id) @commit = @note.noteable @project = @note.project - mail(to: recipient(recipient_id), subject: subject("note for commit #{@commit.short_id}", @commit.title)) + mail(to: recipient(recipient_id), subject: subject("Note for commit #{@commit.short_id}", @commit.title)) end def note_issue_email(recipient_id, note_id) @note = Note.find(note_id) @issue = @note.noteable @project = @note.project - mail(to: recipient(recipient_id), subject: subject("note for issue ##{@issue.iid}")) + mail(to: recipient(recipient_id), subject: subject("Note for issue ##{@issue.iid}")) end def note_merge_request_email(recipient_id, note_id) @note = Note.find(note_id) @merge_request = @note.noteable @project = @note.project - mail(to: recipient(recipient_id), subject: subject("note for merge request !#{@merge_request.iid}")) + mail(to: recipient(recipient_id), subject: subject("Note for merge request ##{@merge_request.iid}")) end def note_wall_email(recipient_id, note_id) @note = Note.find(note_id) @project = @note.project - mail(to: recipient(recipient_id), subject: subject("note on wall")) + mail(to: recipient(recipient_id), subject: subject("Note on wall")) end end end diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index 4d5fe9ef6142858ca51797ff56d3e9d5c7d9b05e..df21d7b5b0245150a872de299a0c7d27c381d046 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -4,14 +4,24 @@ module Emails @users_project = UsersProject.find user_project_id @project = @users_project.project mail(to: @users_project.user.email, - subject: subject("access to project was granted")) + subject: subject("Access to project was granted")) end def project_was_moved_email(project_id, user_id) @user = User.find user_id @project = Project.find project_id mail(to: @user.email, - subject: subject("project was moved")) + subject: subject("Project was moved")) + end + + def repository_push_email(project_id, recipient, author_id, branch, compare) + @project = Project.find(project_id) + @author = User.find(author_id) + @commits = Commit.decorate(compare.commits) + @diffs = compare.diffs + @branch = branch + + mail(to: recipient, subject: subject("New push to repository")) end end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 2f7be00c33ed48e37d18fffc33952ee87f81a7b2..6c1a33289604455dfdbfff0be07711bedb3c9202 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -12,10 +12,11 @@ class Notify < ActionMailer::Base default_url_options[:host] = Gitlab.config.gitlab.host default_url_options[:protocol] = Gitlab.config.gitlab.protocol - default_url_options[:port] = Gitlab.config.gitlab.port if Gitlab.config.gitlab_on_non_standard_port? + default_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port? default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root default from: Gitlab.config.gitlab.email_from + default reply_to: "noreply@#{Gitlab.config.gitlab.host}" # Just send email with 3 seconds delay def self.delay diff --git a/app/models/ability.rb b/app/models/ability.rb index 85476089145fab798b5251fb20ed174302109932..038668fccfff987cfc51c5769304097b4c7636b9 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -29,7 +29,7 @@ class Ability nil end - if project && project.public + if project && project.public? [ :read_project, :read_wiki, @@ -59,37 +59,41 @@ class Ability # Rules based on role in project if team.masters.include?(user) - rules << project_master_rules + rules += project_master_rules elsif team.developers.include?(user) - rules << project_dev_rules + rules += project_dev_rules elsif team.reporters.include?(user) - rules << project_report_rules + rules += project_report_rules elsif team.guests.include?(user) - rules << project_guest_rules + rules += project_guest_rules end - if project.public? - rules << public_project_rules + if project.public? || project.internal? + rules += public_project_rules end if project.owner == user || user.admin? - rules << project_admin_rules + rules += project_admin_rules end if project.group && project.group.has_owner?(user) - rules << project_admin_rules + rules += project_admin_rules end - rules.flatten + if project.archived? + rules -= project_archived_rules + end + + rules end def public_project_rules project_guest_rules + [ :download_code, - :fork_project, + :fork_project ] end @@ -121,10 +125,21 @@ class Ability project_report_rules + [ :write_merge_request, :write_wiki, + :modify_issue, :push_code ] end + def project_archived_rules + [ + :write_merge_request, + :push_code, + :push_code_to_protected_branches, + :modify_merge_request, + :admin_merge_request + ] + end + def project_master_rules project_dev_rules + [ :push_code_to_protected_branches, @@ -145,9 +160,10 @@ class Ability def project_admin_rules project_master_rules + [ :change_namespace, - :change_public_mode, + :change_visibility_level, :rename_project, - :remove_project + :remove_project, + :archive_project ] end @@ -160,7 +176,7 @@ class Ability # Only group owner and administrators can manage group if group.has_owner?(user) || user.admin? - rules << [ + rules += [ :manage_group, :manage_namespace ] @@ -174,7 +190,7 @@ class Ability # Only namespace owner and administrators can manage it if namespace.owner == user || user.admin? - rules << [ + rules += [ :manage_namespace ] end diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb new file mode 100644 index 0000000000000000000000000000000000000000..bebe0da9c61efe5d46b40391579e671ce9d1b93e --- /dev/null +++ b/app/models/broadcast_message.rb @@ -0,0 +1,29 @@ +# == Schema Information +# +# Table name: broadcast_messages +# +# id :integer not null, primary key +# message :text default(""), not null +# starts_at :datetime +# ends_at :datetime +# alert_type :integer +# created_at :datetime not null +# updated_at :datetime not null +# color :string(255) +# font :string(255) +# + +class BroadcastMessage < ActiveRecord::Base + attr_accessible :alert_type, :color, :ends_at, :font, :message, :starts_at + + validates :message, presence: true + validates :starts_at, presence: true + validates :ends_at, presence: true + + validates :color, format: { with: /\A\#[0-9A-Fa-f]{6}+\Z/ }, allow_blank: true + validates :font, format: { with: /\A\#[0-9A-Fa-f]{6}+\Z/ }, allow_blank: true + + def self.current + where("ends_at > :now AND starts_at < :now", now: Time.zone.now).last + end +end diff --git a/app/models/commit.rb b/app/models/commit.rb index dd1f980187852f2fc8131e57c2a33e93442b8297..bcc1bcbd96a0f14d2cec1f02bef937283d225cfa 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -16,29 +16,31 @@ class Commit DIFF_HARD_LIMIT_FILES = 500 DIFF_HARD_LIMIT_LINES = 10000 - def self.decorate(commits) - commits.map { |c| self.new(c) } - end + class << self + def decorate(commits) + commits.map { |c| self.new(c) } + end - # Calculate number of lines to render for diffs - def self.diff_line_count(diffs) - diffs.reduce(0){|sum, d| sum + d.diff.lines.count} - end + # Calculate number of lines to render for diffs + def diff_line_count(diffs) + diffs.reduce(0){|sum, d| sum + d.diff.lines.count} + end - def self.diff_suppress?(diffs, line_count = nil) - # optimize - check file count first - return true if diffs.size > DIFF_SAFE_FILES + def diff_suppress?(diffs, line_count = nil) + # optimize - check file count first + return true if diffs.size > DIFF_SAFE_FILES - line_count ||= Commit::diff_line_count(diffs) - line_count > DIFF_SAFE_LINES - end + line_count ||= Commit::diff_line_count(diffs) + line_count > DIFF_SAFE_LINES + end - def self.diff_force_suppress?(diffs, line_count = nil) - # optimize - check file count first - return true if diffs.size > DIFF_HARD_LIMIT_FILES + def diff_force_suppress?(diffs, line_count = nil) + # optimize - check file count first + return true if diffs.size > DIFF_HARD_LIMIT_FILES - line_count ||= Commit::diff_line_count(diffs) - line_count > DIFF_HARD_LIMIT_LINES + line_count ||= Commit::diff_line_count(diffs) + line_count > DIFF_HARD_LIMIT_LINES + end end attr_accessor :raw diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 7f820f950b02b02d601eca4779ad4b586009945b..0f1dad4ef16d7e44301c8852ec90bd933a768b40 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -45,6 +45,18 @@ module Issuable def search(query) where("title like :query", query: "%#{query}%") end + + def sort(method) + case method.to_s + when 'newest' then reorder('created_at DESC') + when 'oldest' then reorder('created_at ASC') + when 'recently_updated' then reorder('updated_at DESC') + when 'last_updated' then reorder('updated_at ASC') + when 'milestone_due_soon' then joins(:milestone).reorder("milestones.due_date ASC") + when 'milestone_due_later' then joins(:milestone).reorder("milestones.due_date DESC") + else reorder('created_at DESC') + end + end end def today? @@ -111,4 +123,11 @@ module Issuable end users.concat(mentions.reduce([], :|)).uniq end + + def to_hook_data + { + object_kind: self.class.name.underscore, + object_attributes: self.attributes + } + end end diff --git a/app/models/event.rb b/app/models/event.rb index 095a06c956b82dddb5279357c1d4e5562a86cdaa..ddb863c1be24ab1ca0777f90ffd4e33b336f4662 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -18,7 +18,7 @@ class Event < ActiveRecord::Base attr_accessible :project, :action, :data, :author_id, :project_id, :target_id, :target_type - default_scope where("author_id IS NOT NULL") + default_scope { where.not(author_id: nil) } CREATED = 1 UPDATED = 2 @@ -168,7 +168,7 @@ class Event < ActiveRecord::Base end def valid_push? - data[:ref] + data[:ref] && ref_name.present? rescue => ex false end @@ -223,7 +223,7 @@ class Event < ActiveRecord::Base # Max 20 commits from push DESC def commits - @commits ||= data[:commits].reverse + @commits ||= (data[:commits] || []).reverse end def commits_count diff --git a/app/models/gollum_wiki.rb b/app/models/gollum_wiki.rb index d1edbab4533afab1e846355c15f275a2560f12b4..7ebaaff61cb55ed85ac58c89527a65a1870d76f7 100644 --- a/app/models/gollum_wiki.rb +++ b/app/models/gollum_wiki.rb @@ -33,7 +33,7 @@ class GollumWiki end def http_url_to_repo - http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') + [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') end # Returns the Gollum::Wiki object. @@ -45,6 +45,10 @@ class GollumWiki end end + def empty? + pages.empty? + end + # Returns an Array of Gitlab WikiPage instances or an # empty Array if this Wiki has no pages. def pages diff --git a/app/models/group.rb b/app/models/group.rb index d6272ca46f57ff63d104c26bd4132635a3dde801..8de0c78c158c59372af75f26a616e424c34e8077 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -12,10 +12,20 @@ # description :string(255) default(""), not null # +require 'carrierwave/orm/activerecord' +require 'file_size_validator' + class Group < Namespace has_many :users_groups, dependent: :destroy has_many :users, through: :users_groups + attr_accessible :avatar + + validate :avatar_type, if: ->(user) { user.avatar_changed? } + validates :avatar, file_size: { maximum: 100.kilobytes.to_i } + + mount_uploader :avatar, AttachmentUploader + def human_name name end @@ -26,7 +36,8 @@ class Group < Namespace def add_users(user_ids, group_access) user_ids.compact.each do |user_id| - self.users_groups.create(user_id: user_id, group_access: group_access) + user = self.users_groups.find_or_initialize_by(user_id: user_id) + user.update_attributes(group_access: group_access) end end @@ -49,4 +60,10 @@ class Group < Namespace def members users_groups end + + def avatar_type + unless self.avatar.image? + self.errors.add :avatar, "only images allowed" + end + end end diff --git a/app/models/issue.rb b/app/models/issue.rb index f3ec322126facfbbac0bdd9ef6763afd82e03a46..6580c5004afc296917f526c194e9fc4de721dccf 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -21,12 +21,14 @@ class Issue < ActiveRecord::Base include Issuable include InternalId + ActsAsTaggableOn.strict_case_match = true + belongs_to :project validates :project, presence: true scope :of_group, ->(group) { where(project_id: group.project_ids) } scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) } - scope :opened, -> { with_state(:opened) } + scope :opened, -> { with_state(:opened, :reopened) } scope :closed, -> { with_state(:closed) } attr_accessible :title, :assignee_id, :position, :description, @@ -54,12 +56,23 @@ class Issue < ActiveRecord::Base state :closed end - # Both open and reopened issues should be listed as opened - scope :opened, -> { with_state(:opened, :reopened) } - # Mentionable overrides. def gfm_reference "issue ##{iid}" end + + # Reset issue events cache + # + # Since we do cache @event we need to reset cache in special cases: + # * when an issue is updated + # Events cache stored like events/23-20130109142513. + # The cache key includes updated_at timestamp. + # Thus it will automatically generate a new fragment + # when the event is updated because the key changes. + def reset_events_cache + Event.where(target_id: self.id, target_type: 'Issue'). + order('id DESC').limit(100). + update_all(updated_at: Time.now) + end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index a26190015b2cb1fdf55017b106ddc1c1e76b52b9..ca2644ec73597f3a440c00ef398a9d6dbb4f5475 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -31,30 +31,45 @@ class MergeRequest < ActiveRecord::Base belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project" belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project" + has_one :merge_request_diff, dependent: :destroy + after_create :create_merge_request_diff + + delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil + attr_accessible :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, :author_id_of_changes, :state_event, :description attr_accessor :should_remove_source_branch + # When this attribute is true some MR validation is ignored + # It allows us to close or modify broken merge requests + attr_accessor :allow_broken + state_machine :state, initial: :opened do event :close do transition [:reopened, :opened] => :closed end event :merge do - transition [:reopened, :opened] => :merged + transition [:reopened, :opened, :locked] => :merged end event :reopen do transition closed: :reopened end - state :opened + event :lock do + transition [:reopened, :opened] => :locked + end - state :reopened + event :unlock do + transition locked: :reopened + end + state :opened + state :reopened state :closed - state :merged + state :locked end state_machine :merge_status, initial: :unchecked do @@ -71,16 +86,11 @@ class MergeRequest < ActiveRecord::Base end state :unchecked - state :can_be_merged - state :cannot_be_merged end - serialize :st_commits - serialize :st_diffs - - validates :source_project, presence: true + validates :source_project, presence: true, unless: :allow_broken validates :source_branch, presence: true validates :target_project, presence: true validates :target_branch, presence: true @@ -101,7 +111,7 @@ class MergeRequest < ActiveRecord::Base scope :closed, -> { with_states(:closed, :merged) } def validate_branches - if target_project==source_project && target_branch == source_branch + if target_project == source_project && target_branch == source_branch errors.add :branch_conflict, "You can not use same project/branch for source and target" end @@ -116,8 +126,9 @@ class MergeRequest < ActiveRecord::Base end def reload_code - self.reloaded_commits - self.reloaded_diffs + if merge_request_diff && opened? + merge_request_diff.reload_content + end end def check_if_can_be_merged @@ -128,42 +139,6 @@ class MergeRequest < ActiveRecord::Base end end - def diffs - @diffs ||= (load_diffs(st_diffs) || []) - end - - def reloaded_diffs - if opened? && unmerged_diffs.any? - self.st_diffs = dump_diffs(unmerged_diffs) - self.save - end - end - - def broken_diffs? - diffs == broken_diffs - rescue - true - end - - def valid_diffs? - !broken_diffs? - end - - def unmerged_diffs - diffs = if for_fork? - Gitlab::Satellite::MergeAction.new(author, self).diffs_between_satellite - else - Gitlab::Git::Diff.between(target_project.repository, source_branch, target_branch) - end - - diffs ||= [] - diffs - end - - def last_commit - commits.first - end - def merge_event self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last end @@ -172,57 +147,20 @@ class MergeRequest < ActiveRecord::Base self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last end - def commits - load_commits(st_commits || []) - end - - def probably_merged? - unmerged_commits.empty? && - commits.any? && opened? - end - - def reloaded_commits - if opened? && unmerged_commits.any? - self.st_commits = dump_commits(unmerged_commits) - save - - end - commits - end - - def unmerged_commits - if for_fork? - commits = Gitlab::Satellite::MergeAction.new(self.author, self).commits_between - else - commits = target_project.repository.commits_between(self.target_branch, self.source_branch) - end - - if commits.present? - commits = Commit.decorate(commits). - sort_by(&:created_at). - reverse - end - commits - end - - def merge!(user_id) - self.author_id_of_changes = user_id - self.merge - end - - def automerge!(current_user) - if Gitlab::Satellite::MergeAction.new(current_user, self).merge! && self.unmerged_commits.empty? - self.merge!(current_user.id) - true - end - rescue - mark_as_unmergeable - false + def automerge!(current_user, commit_message = nil) + MergeRequests::AutoMergeService.new.execute(self, current_user, commit_message) end def mr_and_commit_notes - commit_ids = commits.map(&:id) - Note.where("(noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR (noteable_type = 'Commit' AND commit_id IN (:commit_ids))", mr_id: id, commit_ids: commit_ids) + # Fetch comments only from last 100 commits + commits_for_notes_limit = 100 + commit_ids = commits.last(commits_for_notes_limit).map(&:id) + + project.notes.where( + "(noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR (noteable_type = 'Commit' AND commit_id IN (:commit_ids))", + mr_id: id, + commit_ids: commit_ids + ) end # Returns the raw diff for this merge request @@ -239,10 +177,6 @@ class MergeRequest < ActiveRecord::Base Gitlab::Satellite::MergeAction.new(current_user, self).format_patch end - def last_commit_short_sha - @last_commit_short_sha ||= last_commit.sha[0..10] - end - def for_fork? target_project != source_project end @@ -258,7 +192,7 @@ class MergeRequest < ActiveRecord::Base # Return the set of issues that will be closed if this merge request is accepted. def closes_issues if target_branch == project.default_branch - unmerged_commits.map { |c| c.closes_issues(project) }.flatten.uniq.sort_by(&:id) + commits.map { |c| c.closes_issues(project) }.flatten.uniq.sort_by(&:id) else [] end @@ -269,33 +203,74 @@ class MergeRequest < ActiveRecord::Base "merge request !#{iid}" end - private + def target_project_path + if target_project + target_project.path_with_namespace + else + "(removed)" + end + end - def dump_commits(commits) - commits.map(&:to_hash) + def source_project_path + if source_project + source_project.path_with_namespace + else + "(removed)" + end end - def load_commits(array) - array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash)) } + def source_branch_exists? + return false unless self.source_project + + self.source_project.repository.branch_names.include?(self.source_branch) end - def dump_diffs(diffs) - if diffs == broken_diffs - broken_diffs - elsif diffs.respond_to?(:map) - diffs.map(&:to_hash) - end + def target_branch_exists? + return false unless self.target_project + + self.target_project.repository.branch_names.include?(self.target_branch) end - def load_diffs(raw) - if raw == broken_diffs - broken_diffs - elsif raw.respond_to?(:map) - raw.map { |hash| Gitlab::Git::Diff.new(hash) } + # Reset merge request events cache + # + # Since we do cache @event we need to reset cache in special cases: + # * when a merge request is updated + # Events cache stored like events/23-20130109142513. + # The cache key includes updated_at timestamp. + # Thus it will automatically generate a new fragment + # when the event is updated because the key changes. + def reset_events_cache + Event.where(target_id: self.id, target_type: 'MergeRequest'). + order('id DESC').limit(100). + update_all(updated_at: Time.now) + end + + def merge_commit_message + message = "Merge branch '#{source_branch}' into '#{target_branch}'" + message << "\n\n" + message << title.to_s + message << "\n\n" + message << description.to_s + message + end + + # Return array of possible target branches + # dependes on target project of MR + def target_branches + if target_project.nil? + [] + else + target_project.repository.branch_names end end - def broken_diffs - [Gitlab::Git::Diff::BROKEN_DIFF] + # Return array of possible source branches + # dependes on source project of MR + def source_branches + if source_project.nil? + [] + else + source_project.repository.branch_names + end end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb new file mode 100644 index 0000000000000000000000000000000000000000..3ea610197e607a5171f12b7bc656aba490a16513 --- /dev/null +++ b/app/models/merge_request_diff.rb @@ -0,0 +1,163 @@ +require Rails.root.join("app/models/commit") + +class MergeRequestDiff < ActiveRecord::Base + # Prevent store of diff + # if commits amount more then 200 + COMMITS_SAFE_SIZE = 200 + + attr_reader :commits, :diffs + + belongs_to :merge_request + + attr_accessible :state, :st_commits, :st_diffs + + delegate :target_branch, :source_branch, to: :merge_request, prefix: nil + + state_machine :state, initial: :empty do + state :collected + state :timeout + state :overflow_commits_safe_size + state :overflow_diff_files_limit + state :overflow_diff_lines_limit + end + + serialize :st_commits + serialize :st_diffs + + after_create :reload_content + + def reload_content + reload_commits + reload_diffs + end + + def diffs + @diffs ||= (load_diffs(st_diffs) || []) + end + + def commits + @commits ||= load_commits(st_commits || []) + end + + def last_commit + commits.first + end + + def last_commit_short_sha + @last_commit_short_sha ||= last_commit.sha[0..10] + end + + private + + def dump_commits(commits) + commits.map(&:to_hash) + end + + def load_commits(array) + array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash)) } + end + + def dump_diffs(diffs) + if diffs.respond_to?(:map) + diffs.map(&:to_hash) + end + end + + def load_diffs(raw) + if raw.respond_to?(:map) + raw.map { |hash| Gitlab::Git::Diff.new(hash) } + end + end + + # When Git::Diff is not able to get diff + # because of git timeout it return this value + def broken_diffs + [Gitlab::Git::Diff::BROKEN_DIFF] + end + + # Collect array of Git::Commit objects + # between target and source branches + def unmerged_commits + commits = if merge_request.for_fork? + Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).commits_between + else + repository.commits_between(target_branch, source_branch) + end + + if commits.present? + commits = Commit.decorate(commits). + sort_by(&:created_at). + reverse + end + + commits + end + + # Reload all commits related to current merge request from repo + # and save it as array of hashes in st_commits db field + def reload_commits + commit_objects = unmerged_commits + + if commit_objects.present? + self.st_commits = dump_commits(commit_objects) + end + + save + end + + # Reload diffs between branches related to current merge request from repo + # and save it as array of hashes in st_diffs db field + def reload_diffs + new_diffs = [] + + if commits.size.zero? + self.state = :empty + elsif commits.size > COMMITS_SAFE_SIZE + self.state = :overflow_commits_safe_size + else + new_diffs = unmerged_diffs + end + + if new_diffs.any? + if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES + self.state = :overflow_diff_files_limit + new_diffs = [] + end + + if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES + self.state = :overflow_diff_lines_limit + new_diffs = [] + end + end + + if new_diffs.present? + new_diffs = dump_commits(new_diffs) + self.state = :collected + end + + self.st_diffs = new_diffs + self.save + end + + # Collect array of Git::Diff objects + # between target and source branches + def unmerged_diffs + diffs = if merge_request.for_fork? + Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).diffs_between_satellite + else + Gitlab::Git::Diff.between(repository, source_branch, target_branch) + end + + if diffs == broken_diffs + self.state = :timeout + diffs = [] + end + + diffs ||= [] + diffs + end + + def repository + merge_request.target_project.repository + end +end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index fde06649c7883f75c9ee7d6cabda5e62696689b8..d5b98f588e8b0ebfb409c447adb4cfdde4554a2c 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -10,6 +10,7 @@ # updated_at :datetime not null # type :string(255) # description :string(255) default(""), not null +# avatar :string(255) # class Namespace < ActiveRecord::Base @@ -87,4 +88,8 @@ class Namespace < ActiveRecord::Base def send_update_instructions projects.each(&:send_move_instructions) end + + def kind + type == 'Group' ? 'group' : 'user' + end end diff --git a/app/models/note.rb b/app/models/note.rb index 7e7387abed6f92fb23abe9a3b99607beb25bfd96..f4c0be3307fcae893b31dec8dc5f691143ec7d1a 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -56,29 +56,64 @@ class Note < ActiveRecord::Base serialize :st_diff before_create :set_diff, if: ->(n) { n.line_code.present? } - def self.create_status_change_note(noteable, project, author, status, source) - body = "_Status changed to #{status}#{' by ' + source.gfm_reference if source}_" - - create({ - noteable: noteable, - project: project, - author: author, - note: body, - system: true - }, without_protection: true) - end - - # +noteable+ was referenced from +mentioner+, by including GFM in either +mentioner+'s description or an associated Note. - # Create a system Note associated with +noteable+ with a GFM back-reference to +mentioner+. - def self.create_cross_reference_note(noteable, mentioner, author, project) - create({ - noteable: noteable, - commit_id: (noteable.sha if noteable.respond_to? :sha), - project: project, - author: author, - note: "_mentioned in #{mentioner.gfm_reference}_", - system: true - }, without_protection: true) + class << self + def create_status_change_note(noteable, project, author, status, source) + body = "_Status changed to #{status}#{' by ' + source.gfm_reference if source}_" + + create({ + noteable: noteable, + project: project, + author: author, + note: body, + system: true + }, without_protection: true) + end + + # +noteable+ was referenced from +mentioner+, by including GFM in either +mentioner+'s description or an associated Note. + # Create a system Note associated with +noteable+ with a GFM back-reference to +mentioner+. + def create_cross_reference_note(noteable, mentioner, author, project) + create({ + noteable: noteable, + commit_id: (noteable.sha if noteable.respond_to? :sha), + project: project, + author: author, + note: "_mentioned in #{mentioner.gfm_reference}_", + system: true + }, without_protection: true) + end + + def create_assignee_change_note(noteable, project, author, assignee) + body = assignee.nil? ? '_Assignee removed_' : "_Reassigned to @#{assignee.username}_" + + create({ + noteable: noteable, + project: project, + author: author, + note: body, + system: true + }, without_protection: true) + end + + def discussions_from_notes(notes) + discussion_ids = [] + discussions = [] + + notes.each do |note| + next if discussion_ids.include?(note.discussion_id) + + # don't group notes for the main target + if !note.for_diff_line? && note.noteable_type == "MergeRequest" + discussions << [note] + else + discussions << notes.select do |other_note| + note.discussion_id == other_note.discussion_id + end + discussion_ids << note.discussion_id + end + end + + discussions + end end # Determine whether or not a cross-reference note already exists. @@ -88,8 +123,8 @@ class Note < ActiveRecord::Base def commit_author @commit_author ||= - project.users.find_by_email(noteable.author_email) || - project.users.find_by_name(noteable.author_name) + project.users.find_by(email: noteable.author_email) || + project.users.find_by(name: noteable.author_name) rescue nil end @@ -157,7 +192,8 @@ class Note < ActiveRecord::Base # otherwise false is returned def downvote? votable? && (note.start_with?('-1') || - note.start_with?(':-1:') + note.start_with?(':-1:') || + note.start_with?(':thumbsdown:') ) end @@ -206,7 +242,8 @@ class Note < ActiveRecord::Base # otherwise false is returned def upvote? votable? && (note.start_with?('+1') || - note.start_with?(':+1:') + note.start_with?(':+1:') || + note.start_with?(':thumbsup:') ) end @@ -237,4 +274,19 @@ class Note < ActiveRecord::Base def noteable_type=(sType) super(sType.to_s.classify.constantize.base_class.to_s) end + + # Reset notes events cache + # + # Since we do cache @event we need to reset cache in special cases: + # * when a note is updated + # * when a note is removed + # Events cache stored like events/23-20130109142513. + # The cache key includes updated_at timestamp. + # Thus it will automatically generate a new fragment + # when the event is updated because the key changes. + def reset_events_cache + Event.where(target_id: self.id, target_type: 'Note'). + order('id DESC').limit(100). + update_all(updated_at: Time.now) + end end diff --git a/app/models/project.rb b/app/models/project.rb index 709a6b1ea499032d27b6a5182a29b2e4bbdf1e26..d9da2c377c8a05adc843c70d9b85690e7e367192 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -9,50 +9,62 @@ # created_at :datetime not null # updated_at :datetime not null # creator_id :integer -# default_branch :string(255) # issues_enabled :boolean default(TRUE), not null # wall_enabled :boolean default(TRUE), not null # merge_requests_enabled :boolean default(TRUE), not null # wiki_enabled :boolean default(TRUE), not null # namespace_id :integer -# public :boolean default(FALSE), not null # issues_tracker :string(255) default("gitlab"), not null # issues_tracker_id :string(255) # snippets_enabled :boolean default(TRUE), not null # last_activity_at :datetime # imported :boolean default(FALSE), not null # import_url :string(255) +# visibility_level :integer default(0), not null # class Project < ActiveRecord::Base include Gitlab::ShellAdapter + include Gitlab::VisibilityLevel extend Enumerize - attr_accessible :name, :path, :description, :default_branch, :issues_tracker, :label_list, + ActsAsTaggableOn.strict_case_match = true + + attr_accessible :name, :path, :description, :issues_tracker, :label_list, :issues_enabled, :wall_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, - :wiki_enabled, :public, :import_url, :last_activity_at, as: [:default, :admin] + :wiki_enabled, :visibility_level, :import_url, :last_activity_at, as: [:default, :admin] attr_accessible :namespace_id, :creator_id, as: :admin acts_as_taggable_on :labels, :issues_default_labels + attr_accessor :new_default_branch + # Relations belongs_to :creator, foreign_key: "creator_id", class_name: "User" - belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'" + belongs_to :group, -> { where(type: Group) }, foreign_key: "namespace_id" belongs_to :namespace - has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id' + has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' has_one :gitlab_ci_service, dependent: :destroy has_one :campfire_service, dependent: :destroy + has_one :emails_on_push_service, dependent: :destroy has_one :pivotaltracker_service, dependent: :destroy has_one :hipchat_service, dependent: :destroy + has_one :flowdock_service, dependent: :destroy + has_one :assembla_service, dependent: :destroy has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" has_one :forked_from_project, through: :forked_project_link + # Merge Requests for target project should be removed with it + has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id" + + # Merge requests from source project should be kept when source project was removed + has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest + + has_many :issues, -> { order "state DESC, created_at DESC" }, dependent: :destroy has_many :services, dependent: :destroy has_many :events, dependent: :destroy - has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id" - has_many :issues, dependent: :destroy, order: "state DESC, created_at DESC" has_many :milestones, dependent: :destroy has_many :notes, dependent: :destroy has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet" @@ -69,8 +81,8 @@ class Project < ActiveRecord::Base delegate :members, to: :team, prefix: true # Validations - validates :creator, presence: true - validates :description, length: { within: 0..2000 } + validates :creator, presence: true, on: :create + validates :description, length: { maximum: 2000 }, allow_blank: true validates :name, presence: true, length: { within: 0..255 }, format: { with: Gitlab::Regex.project_name_regex, message: "only letters, digits, spaces & '_' '-' '.' allowed. Letter or digit should be first" } @@ -80,7 +92,7 @@ class Project < ActiveRecord::Base message: "only letters, digits & '_' '-' '.' allowed. Letter or digit should be first" } validates :issues_enabled, :wall_enabled, :merge_requests_enabled, :wiki_enabled, inclusion: { in: [true, false] } - validates :issues_tracker_id, length: { within: 0..255 } + validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true validates :namespace, presence: true validates_uniqueness_of :name, scope: :namespace_id @@ -102,7 +114,10 @@ class Project < ActiveRecord::Base scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") } scope :personal, ->(user) { where(namespace_id: user.namespace_id) } scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) } - scope :public_only, -> { where(public: true) } + scope :public_only, -> { where(visibility_level: PUBLIC) } + scope :public_or_internal_only, ->(user) { where("visibility_level IN (:levels)", levels: user ? [ INTERNAL, PUBLIC ] : [ PUBLIC ]) } + + scope :non_archived, -> { where(archived: false) } enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab @@ -120,20 +135,38 @@ class Project < ActiveRecord::Base end def search query - where("projects.name LIKE :query OR projects.path LIKE :query", query: "%#{query}%") + joins(:namespace).where("projects.archived = ?", false).where("projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query OR projects.description LIKE :query", query: "%#{query}%") + end + + def search_by_title query + where("projects.archived = ?", false).where("LOWER(projects.name) LIKE :query", query: "%#{query.downcase}%") end def find_with_namespace(id) if id.include?("/") id = id.split("/") - namespace = Namespace.find_by_path(id.first) + namespace = Namespace.find_by(path: id.first) return nil unless namespace - where(namespace_id: namespace.id).find_by_path(id.second) + where(namespace_id: namespace.id).find_by(path: id.second) else where(path: id, namespace_id: nil).last end end + + def visibility_levels + Gitlab::VisibilityLevel.options + end + + def sort(method) + case method.to_s + when 'newest' then reorder('projects.created_at DESC') + when 'oldest' then reorder('projects.created_at ASC') + when 'recently_updated' then reorder('projects.updated_at DESC') + when 'last_updated' then reorder('projects.updated_at ASC') + else reorder("namespaces.path, projects.name ASC") + end + end end def team @@ -141,7 +174,7 @@ class Project < ActiveRecord::Base end def repository - @repository ||= Repository.new(path_with_namespace, default_branch) + @repository ||= Repository.new(path_with_namespace) end def saved? @@ -172,6 +205,10 @@ class Project < ActiveRecord::Base [Gitlab.config.gitlab.url, path_with_namespace].join("/") end + def web_url_without_protocol + web_url.split("://")[1] + end + def build_commit_note(commit) notes.new(commit_id: commit.id, noteable_type: "Commit") end @@ -219,7 +256,7 @@ class Project < ActiveRecord::Base end def available_services_names - %w(gitlab_ci campfire hipchat pivotaltracker) + %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push) end def gitlab_ci? @@ -241,9 +278,7 @@ class Project < ActiveRecord::Base end def send_move_instructions - team.members.each do |user| - Notify.delay.project_was_moved_email(self.id, user.id) - end + NotificationService.new.project_was_moved(self) end def owner @@ -261,7 +296,7 @@ class Project < ActiveRecord::Base # Get Team Member record by user id def team_member_by_id(user_id) - users_projects.find_by_user_id(user_id) + users_projects.find_by(user_id: user_id) end def name_with_namespace @@ -286,8 +321,10 @@ class Project < ActiveRecord::Base ProjectTransferService.new.transfer(self, new_namespace) end - def execute_hooks(data) - hooks.each { |hook| hook.async_execute(data) } + def execute_hooks(data, hooks_scope = :push_hooks) + hooks.send(hooks_scope).each do |hook| + hook.async_execute(data) + end end def execute_services(data) @@ -298,27 +335,22 @@ class Project < ActiveRecord::Base end end - def discover_default_branch - # Discover the default branch, but only if it hasn't already been set to - # something else - if repository.exists? && default_branch.nil? - update_attributes(default_branch: self.repository.discover_default_branch) - end - end - def update_merge_requests(oldrev, newrev, ref, user) return true unless ref =~ /heads/ branch_name = ref.gsub("refs/heads/", "") c_ids = self.repository.commits_between(oldrev, newrev).map(&:id) - # Update code for merge requests - mrs = self.merge_requests.opened.by_branch(branch_name).all + # Update code for merge requests into project between project branches + mrs = self.merge_requests.opened.by_branch(branch_name).to_a + # Update code for merge requests between project and project fork + mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a + mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked } # Close merge requests - mrs = self.merge_requests.opened.where(target_branch: branch_name).all + mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) } - mrs.each { |merge_request| merge_request.merge!(user.id) } + mrs.each { |merge_request| MergeRequests::MergeService.new.execute(merge_request, user, nil) } true end @@ -385,7 +417,7 @@ class Project < ActiveRecord::Base end def http_url_to_repo - http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') + [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') end # Check if current branch name is marked as protected in the system @@ -442,4 +474,34 @@ class Project < ActiveRecord::Base order('id DESC').limit(100). update_all(updated_at: Time.now) end + + def project_member(user) + users_projects.where(user_id: user).first + end + + def default_branch + @default_branch ||= repository.root_ref if repository.exists? + end + + def reload_default_branch + @default_branch = nil + default_branch + end + + def visibility_level_field + visibility_level + end + + def archive! + update_attribute(:archived, true) + end + + def unarchive! + update_attribute(:archived, false) + end + + def change_head(branch) + gitlab_shell.update_repository_head(self.path_with_namespace, branch) + reload_default_branch + end end diff --git a/app/models/project_hook.rb b/app/models/project_hook.rb index 2576fc979d4b8f6798d92358c19d35315cd30396..e1c9ed01bc5c052b87660e552f3bc34b3177e0df 100644 --- a/app/models/project_hook.rb +++ b/app/models/project_hook.rb @@ -2,15 +2,24 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# type :string(255) default("ProjectHook") -# service_id :integer +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null # class ProjectHook < WebHook belongs_to :project + + attr_accessible :push_events, :issues_events, :merge_requests_events + + scope :push_hooks, -> { where(push_events: true) } + scope :issue_hooks, -> { where(issues_events: true) } + scope :merge_request_hooks, -> { where(merge_requests_events: true) } end diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..ad7eade5c7ba0a4f7a018aace0ee716af70aeaa5 --- /dev/null +++ b/app/models/project_services/assembla_service.rb @@ -0,0 +1,48 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# token :string(255) +# project_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean default(FALSE), not null +# project_url :string(255) +# subdomain :string(255) +# room :string(255) +# + +class AssemblaService < Service + attr_accessible :subdomain + + include HTTParty + + validates :token, presence: true, if: :activated? + + def title + 'Assembla' + end + + def description + 'Project Management Software (Source Commits Endpoint)' + end + + def to_param + 'assembla' + end + + def fields + [ + { type: 'text', name: 'token', placeholder: '' }, + { type: 'text', name: 'subdomain', placeholder: '' } + ] + end + + def execute(push) + url = "https://atlas.assembla.com/spaces/#{subdomain}/github_tool?secret_key=#{token}" + AssemblaService.post(url, body: { payload: push }.to_json, headers: { 'Content-Type' => 'application/json' }) + end +end diff --git a/app/models/campfire_service.rb b/app/models/project_services/campfire_service.rb similarity index 100% rename from app/models/campfire_service.rb rename to app/models/project_services/campfire_service.rb diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..2a46eff7846eddcfc7d32014464e4d7f743ff074 --- /dev/null +++ b/app/models/project_services/emails_on_push_service.rb @@ -0,0 +1,44 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# token :string(255) +# project_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean default(FALSE), not null +# project_url :string(255) +# subdomain :string(255) +# room :string(255) +# + +class EmailsOnPushService < Service + attr_accessible :recipients + + validates :recipients, presence: true, if: :activated? + + def title + 'Emails on push' + end + + def description + 'Email the commits and diff of each push to a list of recipients.' + end + + def to_param + 'emails_on_push' + end + + def execute(push_data) + EmailsOnPushWorker.perform_async(project_id, recipients, push_data) + end + + def fields + [ + { type: 'textarea', name: 'recipients', placeholder: 'Emails separated by whitespace' }, + ] + end +end diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..f72d9fa90151c5d08a6401e3db2b46522aebb86d --- /dev/null +++ b/app/models/project_services/flowdock_service.rb @@ -0,0 +1,54 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# token :string(255) +# project_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean default(FALSE), not null +# project_url :string(255) +# subdomain :string(255) +# room :string(255) +# + +require "flowdock-git-hook" + +class FlowdockService < Service + validates :token, presence: true, if: :activated? + + def title + 'Flowdock' + end + + def description + 'Flowdock is a collaboration web app for technical teams.' + end + + def to_param + 'flowdock' + end + + def fields + [ + { type: 'text', name: 'token', placeholder: '' } + ] + end + + def execute(push_data) + repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git") + Flowdock::Git.post( + push_data[:ref], + push_data[:before], + push_data[:after], + token: token, + repo: repo_path, + repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}", + commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s", + diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s", + ) + end +end diff --git a/app/models/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb similarity index 100% rename from app/models/gitlab_ci_service.rb rename to app/models/project_services/gitlab_ci_service.rb diff --git a/app/models/hipchat_service.rb b/app/models/project_services/hipchat_service.rb similarity index 93% rename from app/models/hipchat_service.rb rename to app/models/project_services/hipchat_service.rb index c3fb482633453935bc961f79ebb3377cdad53236..c0ba9f1e7f350d6674a7f4fc3dbf180b64bf3b29 100644 --- a/app/models/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -25,7 +25,7 @@ class HipchatService < Service end def description - 'Simple web-based real-time group chat' + 'Private group chat and IM' end def to_param @@ -61,7 +61,7 @@ class HipchatService < Service elsif after =~ /000000/ message << "removed branch #{ref} from <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> \n" else - message << "#pushed to branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> " + message << "pushed to branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> " message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> " message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)" for commit in push[:commits] do @@ -71,5 +71,4 @@ class HipchatService < Service message end - end diff --git a/app/models/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb similarity index 100% rename from app/models/pivotaltracker_service.rb rename to app/models/project_services/pivotaltracker_service.rb diff --git a/app/models/project_team.rb b/app/models/project_team.rb index bc35c4041ba6f2fe14dced4fc69125c33eaaa7f4..eca13e56061a267a1d075681985cbb1427a4283b 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -22,22 +22,22 @@ class ProjectTeam end def find(user_id) - user = project.users.find_by_id(user_id) + user = project.users.find_by(id: user_id) if group - user ||= group.users.find_by_id(user_id) + user ||= group.users.find_by(id: user_id) end user end def find_tm(user_id) - tm = project.users_projects.find_by_user_id(user_id) + tm = project.users_projects.find_by(user_id: user_id) # If user is not in project members # we should check for group membership if group && !tm - tm = group.users_groups.find_by_user_id(user_id) + tm = group.users_groups.find_by(user_id: user_id) end tm @@ -64,6 +64,10 @@ class ProjectTeam UsersProject.truncate_team(project) end + def users + members + end + def members @members ||= fetch_members end @@ -87,9 +91,8 @@ class ProjectTeam def import(source_project) target_project = project - source_team = source_project.users_projects.all - target_team = target_project.users_projects.all - target_user_ids = target_team.map(&:user_id) + source_team = source_project.users_projects.to_a + target_user_ids = target_project.users_projects.pluck(:user_id) source_team.reject! do |tm| # Skip if user already present in team diff --git a/app/models/repository.rb b/app/models/repository.rb index aeec48ee5cc82e40c249494621ae6bb1aeb9298b..a408fe13f80bdf734ff0357bbb3539b699f85897 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1,14 +1,19 @@ class Repository include Gitlab::ShellAdapter - attr_accessor :raw_repository + attr_accessor :raw_repository, :path_with_namespace - def initialize(path_with_namespace, default_branch) - @raw_repository = Gitlab::Git::Repository.new(path_with_namespace, default_branch) + def initialize(path_with_namespace, default_branch = nil) + @path_with_namespace = path_with_namespace + @raw_repository = Gitlab::Git::Repository.new(path_to_repo) if path_with_namespace rescue Gitlab::Git::Repository::NoRepository nil end + def path_to_repo + @path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, path_with_namespace + ".git") + end + def exists? raw_repository end @@ -52,7 +57,7 @@ class Repository def recent_branches(limit = 20) branches.sort do |a, b| - a.commit.committed_date <=> b.commit.committed_date + commit(b.target).committed_date <=> commit(a.target).committed_date end[0..limit] end @@ -128,6 +133,7 @@ class Repository Rails.cache.delete(cache_key(:tag_names)) Rails.cache.delete(cache_key(:commit_count)) Rails.cache.delete(cache_key(:graph_log)) + Rails.cache.delete(cache_key(:readme)) end def graph_log @@ -150,4 +156,36 @@ class Repository super end + + def blob_at(sha, path) + Gitlab::Git::Blob.find(self, sha, path) + end + + def readme + Rails.cache.fetch(cache_key(:readme)) do + tree(:head).readme + end + end + + def head_commit + commit(self.root_ref) + end + + def tree(sha = :head, path = nil) + if sha == :head + sha = head_commit.sha + end + + Tree.new(self, sha, path) + end + + def blob_at_branch(branch_name, path) + last_commit = commit(branch_name) + + if last_commit + blob_at(last_commit.sha, path) + else + nil + end + end end diff --git a/app/models/service_hook.rb b/app/models/service_hook.rb index 4cd2b272eece51050c3392b321b1f32900bd50b3..6f22a863d988b72dfd64d89e749c613f7f018d31 100644 --- a/app/models/service_hook.rb +++ b/app/models/service_hook.rb @@ -2,13 +2,16 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# type :string(255) default("ProjectHook") -# service_id :integer +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null # class ServiceHook < WebHook diff --git a/app/models/system_hook.rb b/app/models/system_hook.rb index 5cdf046644f184b0536f45f3c3832c84689dc977..bffcbbf00f49acbe554eadf34d81c9593947b194 100644 --- a/app/models/system_hook.rb +++ b/app/models/system_hook.rb @@ -2,13 +2,16 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# type :string(255) default("ProjectHook") -# service_id :integer +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null # class SystemHook < WebHook diff --git a/app/models/tree.rb b/app/models/tree.rb index 042050527c1df0979055cf3fa9804330005a1506..4f866f1a33d402ee5767406145f0fc609c62f7e3 100644 --- a/app/models/tree.rb +++ b/app/models/tree.rb @@ -1,17 +1,30 @@ class Tree - attr_accessor :raw + attr_accessor :entries, :readme - def initialize(repository, sha, ref = nil, path = nil) - @raw = Gitlab::Git::Tree.new(repository, sha, ref, path) + def initialize(repository, sha, path = '/') + path = '/' if path.blank? + git_repo = repository.raw_repository + @entries = Gitlab::Git::Tree.where(git_repo, sha, path) + + if readme_tree = @entries.find(&:readme?) + readme_path = path == '/' ? readme_tree.name : File.join(path, readme_tree.name) + @readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path) + end + end + + def trees + @entries.select(&:dir?) end - def method_missing(m, *args, &block) - @raw.send(m, *args, &block) + def blobs + @entries.select(&:file?) end - def respond_to?(method) - return true if @raw.respond_to?(method) + def submodules + @entries.select(&:submodule?) + end - super + def sorted_entries + trees + blobs + submodules end end diff --git a/app/models/user.rb b/app/models/user.rb index 225c97d35ffa341d569679d6c0e5be7983867f30..2a58692375d54084ffbd7c2a89fb1d1a787e3c7a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -36,15 +36,25 @@ # notification_level :integer default(1), not null # password_expires_at :datetime # created_by_id :integer +# avatar :string(255) +# confirmation_token :string(255) +# confirmed_at :datetime +# confirmation_sent_at :datetime +# unconfirmed_email :string(255) +# hide_no_ssh_key :boolean default(FALSE) +# website_url :string(255) default(""), not null # +require 'carrierwave/orm/activerecord' +require 'file_size_validator' + class User < ActiveRecord::Base - devise :database_authenticatable, :token_authenticatable, :lockable, - :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :registerable + devise :database_authenticatable, :token_authenticatable, :lockable, :async, + :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :confirmable, :registerable attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username, - :skype, :linkedin, :twitter, :color_scheme_id, :theme_id, :force_random_password, - :extern_uid, :provider, :password_expires_at, + :skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id, :force_random_password, + :extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key, as: [:default, :admin] attr_accessible :projects_limit, :can_create_group, @@ -64,44 +74,38 @@ class User < ActiveRecord::Base # # Namespace for personal projects - has_one :namespace, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace", conditions: 'type IS NULL' - - # Namespaces (owned groups and own namespace) - has_many :namespaces, foreign_key: :owner_id + has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace" # Profile has_many :keys, dependent: :destroy # Groups - has_many :own_groups, class_name: "Group", foreign_key: :owner_id - has_many :owned_groups, through: :users_groups, source: :group, conditions: { users_groups: { group_access: UsersGroup::OWNER } } - has_many :users_groups, dependent: :destroy has_many :groups, through: :users_groups - + has_many :owned_groups, -> { where users_groups: { group_access: UsersGroup::OWNER } }, through: :users_groups, source: :group # Projects + has_many :groups_projects, through: :groups, source: :projects + has_many :personal_projects, through: :namespace, source: :projects + has_many :projects, through: :users_projects + has_many :created_projects, foreign_key: :creator_id, class_name: 'Project' + has_many :snippets, dependent: :destroy, foreign_key: :author_id, class_name: "Snippet" has_many :users_projects, dependent: :destroy has_many :issues, dependent: :destroy, foreign_key: :author_id has_many :notes, dependent: :destroy, foreign_key: :author_id has_many :merge_requests, dependent: :destroy, foreign_key: :author_id has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event" - has_many :recent_events, foreign_key: :author_id, class_name: "Event", order: "id DESC" + has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id, class_name: "Event" has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue" has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest" - has_many :groups_projects, through: :groups, source: :projects - has_many :personal_projects, through: :namespace, source: :projects - has_many :projects, through: :users_projects - has_many :own_projects, foreign_key: :creator_id, class_name: 'Project' - has_many :owned_projects, through: :namespaces, source: :projects # # Validations # validates :name, presence: true - validates :email, presence: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/ } - validates :bio, length: { within: 0..255 } + validates :email, presence: true, email: {strict_mode: true}, uniqueness: 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 :username, presence: true, uniqueness: true, @@ -110,8 +114,9 @@ class User < ActiveRecord::Base message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true - validate :namespace_uniq, if: ->(user) { user.username_changed? } + validate :avatar_type, if: ->(user) { user.avatar_changed? } + validates :avatar, file_size: { maximum: 100.kilobytes.to_i } before_validation :generate_password, on: :create before_validation :sanitize_attrs @@ -150,6 +155,8 @@ class User < ActiveRecord::Base end end + mount_uploader :avatar, AttachmentUploader + # Scopes scope :admins, -> { where(admin: true) } scope :blocked, -> { with_state(:blocked) } @@ -157,7 +164,7 @@ class User < ActiveRecord::Base scope :alphabetically, -> { order('name ASC') } scope :in_team, ->(team){ where(id: 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) ) : scoped } + 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 users_projects)') } scope :ldap, -> { where(provider: 'ldap') } @@ -167,7 +174,7 @@ class User < ActiveRecord::Base # Class methods # class << self - # Devise method overridden to allow sing in with email or username + # Devise method overridden to allow sign in with email or username def find_for_database_authentication(warden_conditions) conditions = warden_conditions.dup if login = conditions.delete(:login) @@ -192,11 +199,7 @@ class User < ActiveRecord::Base end def by_username_or_id(name_or_id) - if (name_or_id.is_a?(Integer)) - User.find_by_id(name_or_id) - else - User.find_by_username(name_or_id) - end + where('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i).first end def build_user(attrs = {}, options= {}) @@ -236,15 +239,21 @@ class User < ActiveRecord::Base def namespace_uniq namespace_name = self.username - if Namespace.find_by_path(namespace_name) + if Namespace.find_by(path: namespace_name) self.errors.add :username, "already exist" end end + def avatar_type + unless self.avatar.image? + self.errors.add :avatar, "only images allowed" + end + end + # Groups user has access to def authorized_groups @authorized_groups ||= begin - group_ids = (groups.pluck(:id) + own_groups.pluck(:id) + authorized_projects.pluck(:namespace_id)) + group_ids = (groups.pluck(:id) + authorized_projects.pluck(:namespace_id)) Group.where(id: group_ids).order('namespaces.name ASC') end end @@ -253,11 +262,17 @@ class User < ActiveRecord::Base # Projects user has access to def authorized_projects @authorized_projects ||= begin - project_ids = (owned_projects.pluck(:id) + groups_projects.pluck(:id) + projects.pluck(:id)).uniq + project_ids = (personal_projects.pluck(:id) + groups_projects.pluck(:id) + projects.pluck(:id)).uniq Project.where(id: project_ids).joins(:namespace).order('namespaces.name ASC') end end + def owned_projects + @owned_projects ||= begin + Project.where(namespace_id: owned_groups.pluck(:id).push(namespace.id)).joins(:namespace) + end + end + # Team membership in authorized projects def tm_in_authorized_projects UsersProject.where(project_id: authorized_projects.map(&:id), user_id: self.id) @@ -330,7 +345,7 @@ class User < ActiveRecord::Base end def several_namespaces? - namespaces.many? || owned_groups.any? + owned_groups.any? end def namespace_id @@ -364,11 +379,11 @@ class User < ActiveRecord::Base end def accessible_deploy_keys - DeployKey.in_projects(self.authorized_projects).uniq + DeployKey.in_projects(self.authorized_projects.pluck(:id)).uniq end def created_by - User.find_by_id(created_by_id) if created_by_id + User.find_by(id: created_by_id) if created_by_id end def sanitize_attrs @@ -392,6 +407,35 @@ class User < ActiveRecord::Base self end + def can_leave_project?(project) + project.namespace != namespace && + project.project_member(self) + end + + # Reset project events cache related to this user + # + # Since we do cache @event we need to reset cache in special cases: + # * when the user changes their avatar + # Events cache stored like events/23-20130109142513. + # The cache key includes updated_at timestamp. + # Thus it will automatically generate a new fragment + # when the event is updated because the key changes. + def reset_events_cache + Event.where(author_id: self.id). + order('id DESC').limit(1000). + update_all(updated_at: Time.now) + end + + def full_website_url + return "http://#{website_url}" if website_url !~ /^https?:\/\// + + website_url + end + + def short_website_url + website_url.gsub(/https?:\/\//, '') + end + def all_ssh_keys keys.collect{|x| x.key}.join("\n") end diff --git a/app/models/web_hook.rb b/app/models/web_hook.rb index 3f22b1082fb0aaad525f8b57be062622b5501240..8a5c4b6cd47b096db0f591300f1246f4cc3c14dc 100644 --- a/app/models/web_hook.rb +++ b/app/models/web_hook.rb @@ -2,13 +2,16 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# type :string(255) default("ProjectHook") -# service_id :integer +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null # class WebHook < ActiveRecord::Base @@ -25,7 +28,7 @@ class WebHook < ActiveRecord::Base def execute(data) parsed_url = URI.parse(url) if parsed_url.userinfo.blank? - WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }) + WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }, verify: false) else post_url = url.gsub("#{parsed_url.userinfo}@", "") auth = { @@ -35,6 +38,7 @@ class WebHook < ActiveRecord::Base WebHook.post(post_url, body: data.to_json, headers: {"Content-Type" => "application/json"}, + verify: false, basic_auth: auth) end end diff --git a/app/observers/issue_observer.rb b/app/observers/issue_observer.rb index 886d8b776fb7612df313835417f9e65cbddab366..6ef13eb5d5e757674b0ff6ce5c3cb9b8c2aef756 100644 --- a/app/observers/issue_observer.rb +++ b/app/observers/issue_observer.rb @@ -1,26 +1,29 @@ class IssueObserver < BaseObserver def after_create(issue) notification.new_issue(issue, current_user) - issue.create_cross_references!(issue.project, current_user) + execute_hooks(issue) end def after_close(issue, transition) notification.close_issue(issue, current_user) - create_note(issue) + execute_hooks(issue) end def after_reopen(issue, transition) create_note(issue) + execute_hooks(issue) end def after_update(issue) if issue.is_being_reassigned? notification.reassigned_issue(issue, current_user) + create_assignee_note(issue) end issue.notice_added_references(issue.project, current_user) + execute_hooks(issue) end protected @@ -29,4 +32,12 @@ class IssueObserver < BaseObserver def create_note(issue) Note.create_status_change_note(issue, issue.project, current_user, issue.state, current_commit) end + + def create_assignee_note(issue) + Note.create_assignee_change_note(issue, issue.project, current_user, issue.assignee) + end + + def execute_hooks(issue) + issue.project.execute_hooks(issue.to_hook_data, :issue_hooks) + end end diff --git a/app/observers/merge_request_observer.rb b/app/observers/merge_request_observer.rb index d70da514cd20d47195a027603e2655df9b0e2936..ef31498e7d0517a94c62e5519e80497860c3a35b 100644 --- a/app/observers/merge_request_observer.rb +++ b/app/observers/merge_request_observer.rb @@ -7,41 +7,28 @@ class MergeRequestObserver < ActivityObserver end notification.new_merge_request(merge_request, current_user) - merge_request.create_cross_references!(merge_request.project, current_user) + execute_hooks(merge_request) end def after_close(merge_request, transition) create_event(merge_request, Event::CLOSED) - Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil) - notification.close_mr(merge_request, current_user) - end - - def after_merge(merge_request, transition) - notification.merge_mr(merge_request) - # Since MR can be merged via sidekiq - # to prevent event duplication do this check - return true if merge_request.merge_event - - Event.create( - project: merge_request.target_project, - target_id: merge_request.id, - target_type: merge_request.class.name, - action: Event::MERGED, - author_id: merge_request.author_id_of_changes - ) + create_note(merge_request) + execute_hooks(merge_request) end def after_reopen(merge_request, transition) create_event(merge_request, Event::REOPENED) - Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil) + create_note(merge_request) + execute_hooks(merge_request) end def after_update(merge_request) notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned? merge_request.notice_added_references(merge_request.project, current_user) + execute_hooks(merge_request) end def create_event(record, status) @@ -53,4 +40,17 @@ class MergeRequestObserver < ActivityObserver author_id: current_user.id ) end + + private + + # Create merge request note with service comment like 'Status changed to closed' + def create_note(merge_request) + Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil) + end + + def execute_hooks(merge_request) + if merge_request.project + merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks) + end + end end diff --git a/app/observers/project_observer.rb b/app/observers/project_observer.rb index acbb719b69a4cf939bc63b4fd539db8a874f5799..4e3deec01bf38943c33f45fd12e59734ed8381c7 100644 --- a/app/observers/project_observer.rb +++ b/app/observers/project_observer.rb @@ -14,17 +14,22 @@ class ProjectObserver < BaseObserver log_info("#{project.owner.name} created a new project \"#{project.name_with_namespace}\"") end + + if project.wiki_enabled? + begin + # force the creation of a wiki, + GollumWiki.new(project, project.owner).wiki + rescue GollumWiki::CouldNotCreateWikiError => ex + # Prevent project observer crash + # if failed to create wiki + nil + end + end end def after_update(project) project.send_move_instructions if project.namespace_id_changed? project.rename_repo if project.path_changed? - - GitlabShellWorker.perform_async( - :update_repository_head, - project.path_with_namespace, - project.default_branch - ) if project.default_branch_changed? end def before_destroy(project) diff --git a/app/observers/system_hook_observer.rb b/app/observers/system_hook_observer.rb index 3a649fd590d4051f2b653eac318e3ac6959c6b43..80de177b9a2adca5556d8c8ed27d77562a18811f 100644 --- a/app/observers/system_hook_observer.rb +++ b/app/observers/system_hook_observer.rb @@ -2,10 +2,16 @@ class SystemHookObserver < BaseObserver observe :user, :project, :users_project def after_create(model) - SystemHooksService.execute_hooks_for(model, :create) + system_hook_service.execute_hooks_for(model, :create) end def after_destroy(model) - SystemHooksService.execute_hooks_for(model, :destroy) + system_hook_service.execute_hooks_for(model, :destroy) + end + + private + + def system_hook_service + SystemHooksService.new end end diff --git a/app/observers/users_group_observer.rb b/app/observers/users_group_observer.rb index ecdbede89d96b1f29e5f1839660f552886b0cc28..42a05b5e177e55a5acfc29f57e41932eec1805d8 100644 --- a/app/observers/users_group_observer.rb +++ b/app/observers/users_group_observer.rb @@ -4,6 +4,6 @@ class UsersGroupObserver < BaseObserver end def after_update(membership) - notification.update_group_member(membership) + notification.update_group_member(membership) if membership.group_access_changed? end end diff --git a/app/observers/users_project_observer.rb b/app/observers/users_project_observer.rb index ca9649c76abc98800d02fa3f6b681abcdaddb2e9..93233898cc83e95727f1c5b8214b06c592060c64 100644 --- a/app/observers/users_project_observer.rb +++ b/app/observers/users_project_observer.rb @@ -1,8 +1,4 @@ class UsersProjectObserver < BaseObserver - def after_commit(users_project) - return if users_project.destroyed? - end - def after_create(users_project) Event.create( project_id: users_project.project.id, diff --git a/app/contexts/base_context.rb b/app/services/base_service.rb similarity index 95% rename from app/contexts/base_context.rb rename to app/services/base_service.rb index 101be50d54bd374bd5b2c60f5bc8348c6ff70466..610f04748722a7c729fba28e590531bc204f97f2 100644 --- a/app/contexts/base_context.rb +++ b/app/services/base_service.rb @@ -1,4 +1,4 @@ -class BaseContext +class BaseService attr_accessor :project, :current_user, :params def initialize(project, user, params) @@ -17,4 +17,3 @@ class BaseContext abilities.allowed?(object, action, subject) end end - diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..f1765d389767f79c9e41a5b6baf2c926f9081c72 --- /dev/null +++ b/app/services/files/base_service.rb @@ -0,0 +1,31 @@ +module Files + class BaseService < ::BaseService + attr_reader :ref, :path + + def initialize(project, user, params, ref, path = nil) + @project, @current_user, @params = project, user, params.dup + @ref = ref + @path = path + end + + private + + def error(message) + { + error: message, + status: :error + } + end + + def success + { + error: '', + status: :success + } + end + + def repository + project.repository + end + end +end diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..1998fb79e7d233b25adf4ff1c1481c1391cb32cc --- /dev/null +++ b/app/services/files/create_service.rb @@ -0,0 +1,47 @@ +require_relative "base_service" + +module Files + class CreateService < BaseService + def execute + allowed = if project.protected_branch?(ref) + can?(current_user, :push_code_to_protected_branches, project) + else + can?(current_user, :push_code, project) + end + + unless allowed + return error("You are not allowed to create file in this branch") + end + + unless repository.branch_names.include?(ref) + return error("You can only create files if you are on top of a branch") + end + + file_name = File.basename(path) + file_path = path + + unless file_name =~ Gitlab::Regex.path_regex + return error("Your changes could not be committed, because file name contains not allowed characters") + end + + blob = repository.blob_at_branch(ref, file_path) + + if blob + return error("Your changes could not be committed, because file with such name exists") + end + + new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path) + created_successfully = new_file_action.commit!( + params[:content], + params[:commit_message], + params[:encoding] + ) + + if created_successfully + success + else + error("Your changes could not be committed, because the file has been changed") + end + end + end +end diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..ff5dc6ef34c5d0c7eefb8be8b46363109c54a504 --- /dev/null +++ b/app/services/files/delete_service.rb @@ -0,0 +1,40 @@ +require_relative "base_service" + +module Files + class DeleteService < BaseService + def execute + allowed = if project.protected_branch?(ref) + can?(current_user, :push_code_to_protected_branches, project) + else + can?(current_user, :push_code, project) + end + + unless allowed + return error("You are not allowed to push into this branch") + end + + unless repository.branch_names.include?(ref) + return error("You can only create files if you are on top of a branch") + end + + blob = repository.blob_at_branch(ref, path) + + unless blob + return error("You can only edit text files") + end + + delete_file_action = Gitlab::Satellite::DeleteFileAction.new(current_user, project, ref, path) + + deleted_successfully = delete_file_action.commit!( + nil, + params[:commit_message] + ) + + if deleted_successfully + success + else + error("Your changes could not be committed, because the file has been changed") + end + end + end +end diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..c631f28749c636387eee89a1756eb70e7328ad0f --- /dev/null +++ b/app/services/files/update_service.rb @@ -0,0 +1,40 @@ +require_relative "base_service" + +module Files + class UpdateService < BaseService + def execute + allowed = if project.protected_branch?(ref) + can?(current_user, :push_code_to_protected_branches, project) + else + can?(current_user, :push_code, project) + end + + unless allowed + return error("You are not allowed to push into this branch") + end + + unless repository.branch_names.include?(ref) + return error("You can only create files if you are on top of a branch") + end + + blob = repository.blob_at_branch(ref, path) + + unless blob + return error("You can only edit text files") + end + + edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, project, ref, path) + created_successfully = edit_file_action.commit!( + params[:content], + params[:commit_message], + params[:encoding] + ) + + if created_successfully + success + else + error("Your changes could not be committed, because the file has been changed") + end + end + end +end diff --git a/app/services/filtering_service.rb b/app/services/filtering_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..ebd394ee7583bb0b929a9c73bda64d5aa6e0155c --- /dev/null +++ b/app/services/filtering_service.rb @@ -0,0 +1,138 @@ +# FilteringService class +# +# Used to filter Issues and MergeRequests collections by set of params +# +# Arguments: +# klass - actual class like Issue or MergeRequest +# current_user - which user use +# params: +# scope: 'created-by-me' or 'assigned-to-me' or 'all' +# state: 'open' or 'closed' or 'all' +# group_id: integer +# project_id: integer +# milestone_id: integer +# assignee_id: integer +# search: string +# label_name: string +# sort: string +# +class FilteringService + attr_accessor :klass, :current_user, :params + + def execute(klass, current_user, params) + @klass = klass + @current_user = current_user + @params = params + + items = init_collection + items = by_scope(items) + items = by_state(items) + items = by_group(items) + items = by_project(items) + items = by_search(items) + items = by_milestone(items) + items = by_assignee(items) + items = by_label(items) + items = sort(items) + end + + private + + def init_collection + table_name = klass.table_name + + return klass.of_projects(Project.public_only) unless current_user + + if project + if current_user.can?(:read_project, project) + project.send(table_name) + else + [] + end + else + klass.of_projects(current_user.authorized_projects) + end + end + + def by_scope(items) + case params[:scope] + when 'created-by-me', 'authored' then + klass.where(author_id: current_user.id) + when 'all' then + klass + when 'assigned-to-me' then + klass.where(assignee_id: current_user.id) + else + raise 'You must specify default scope' + end + end + + def by_state(items) + case params[:state] + when 'closed' + items.closed + when 'all' + items + when 'opened' + items.opened + else + raise 'You must specify default state' + end + end + + def by_group(items) + if params[:group_id].present? + items = items.of_group(Group.find(params[:group_id])) + end + + items + end + + def by_project(items) + if params[:project_id].present? + items = items.of_projects(params[:project_id]) + end + + items + end + + def by_search(items) + if params[:search].present? + items = items.search(params[:search]) + end + + items + end + + def sort(items) + items.sort(params[:sort]) + end + + def by_milestone(items) + if params[:milestone_id].present? + items = items.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id])) + end + + items + end + + def by_assignee(items) + if params[:assignee_id].present? + items = items.where(assignee_id: (params[:assignee_id] == '0' ? nil : params[:assignee_id])) + end + + items + end + + def by_label(items) + if params[:label_name].present? + items = items.tagged_with(params[:label_name]) + end + + items + end + + def project + Project.where(id: params[:project_id]).first if params[:project_id].present? + end +end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index f9d43e60de6729b114f6b910a1febe0572a16d5c..e54f88e42de58102dce63ac8f1cf6331317155d2 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -24,7 +24,6 @@ class GitPushService create_push_event project.ensure_satellite_exists - project.discover_default_branch project.repository.expire_cache if push_to_existing_branch?(ref, oldrev) @@ -33,7 +32,7 @@ class GitPushService end if push_to_branch?(ref) - project.execute_hooks(@push_data.dup) + project.execute_hooks(@push_data.dup, :push_hooks) project.execute_services(@push_data.dup) end @@ -112,6 +111,7 @@ class GitPushService # ref: String, # user_id: String, # user_name: String, + # project_id: String, # repository: { # name: String, # url: String, @@ -136,6 +136,7 @@ class GitPushService ref: ref, user_id: user.id, user_name: user.name, + project_id: project.id, repository: { name: project.name, url: project.url_to_repo, diff --git a/app/contexts/issues/bulk_update_context.rb b/app/services/issues/bulk_update_service.rb similarity index 91% rename from app/contexts/issues/bulk_update_context.rb rename to app/services/issues/bulk_update_service.rb index 73a3c353523ae8a92b4e9a5ea4d8643b9b3f1fc9..f72a346af6fffc8509c6ac1e7be68fafa98c0960 100644 --- a/app/contexts/issues/bulk_update_context.rb +++ b/app/services/issues/bulk_update_service.rb @@ -1,5 +1,5 @@ module Issues - class BulkUpdateContext < BaseContext + class BulkUpdateService < BaseService def execute update_data = params[:update] @@ -22,7 +22,7 @@ module Issues opts[:milestone_id] = milestone_id if milestone_id.present? opts[:assignee_id] = assignee_id if assignee_id.present? - issues = Issue.where(id: issues_ids).all + issues = Issue.where(id: issues_ids) issues = issues.select { |issue| can?(current_user, :modify_issue, issue) } issues.each do |issue| diff --git a/app/services/merge_requests/auto_merge_service.rb b/app/services/merge_requests/auto_merge_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..d60d61ed54a0f651b72cbd624270f907d5f004ef --- /dev/null +++ b/app/services/merge_requests/auto_merge_service.rb @@ -0,0 +1,30 @@ +module MergeRequests + # AutoMergeService class + # + # Do git merge in satellite and in case of success + # mark merge request as merged and execute all hooks and notifications + # Called when you do merge via GitLab UI + class AutoMergeService < BaseMergeService + def execute(merge_request, current_user, commit_message) + merge_request.lock + + if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message) + merge_request.author_id_of_changes = current_user.id + merge_request.merge + + notification.merge_mr(merge_request) + create_merge_event(merge_request) + execute_project_hooks(merge_request) + + true + else + merge_request.unlock + false + end + rescue + merge_request.unlock if merge_request.locked? + merge_request.mark_as_unmergeable + false + end + end +end diff --git a/app/services/merge_requests/base_merge_service.rb b/app/services/merge_requests/base_merge_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..dbdb00630740b9c74982a3bc78160f99a40e5e97 --- /dev/null +++ b/app/services/merge_requests/base_merge_service.rb @@ -0,0 +1,26 @@ +module MergeRequests + class BaseMergeService + + private + + def notification + NotificationService.new + end + + def create_merge_event(merge_request) + Event.create( + project: merge_request.target_project, + target_id: merge_request.id, + target_type: merge_request.class.name, + action: Event::MERGED, + author_id: merge_request.author_id_of_changes + ) + end + + def execute_project_hooks(merge_request) + if merge_request.project + merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks) + end + end + end +end diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..1d5af04cdbbb7e5dae299388df8751ad660c65fd --- /dev/null +++ b/app/services/merge_requests/merge_service.rb @@ -0,0 +1,22 @@ +module MergeRequests + # MergeService class + # + # Mark existing merge request as merged + # and execute all hooks and notifications + # Called when you do merge via command line and push code + # to target branch + class MergeService < BaseMergeService + def execute(merge_request, current_user, commit_message) + merge_request.author_id_of_changes = current_user.id + merge_request.merge + + notification.merge_mr(merge_request) + create_merge_event(merge_request) + execute_project_hooks(merge_request) + + true + rescue + false + end + end +end diff --git a/app/contexts/notes/create_context.rb b/app/services/notes/create_service.rb similarity index 80% rename from app/contexts/notes/create_context.rb rename to app/services/notes/create_service.rb index 36ea76ff9496cb83dfb3091fe5cbc82848a62796..fb87e175933c4ca321df1f332c734f7a0390cf96 100644 --- a/app/contexts/notes/create_context.rb +++ b/app/services/notes/create_service.rb @@ -1,5 +1,5 @@ module Notes - class CreateContext < BaseContext + class CreateService < BaseService def execute note = project.notes.new(params[:note]) note.author = current_user diff --git a/app/contexts/notes/load_context.rb b/app/services/notes/load_service.rb similarity index 94% rename from app/contexts/notes/load_context.rb rename to app/services/notes/load_service.rb index 234e9ac3cdd375a6a7e6ad92be49c904f643bc0e..f7ad7d60a3a7fc3ea0e7c98879a5d4aaafa11353 100644 --- a/app/contexts/notes/load_context.rb +++ b/app/services/notes/load_service.rb @@ -1,5 +1,5 @@ module Notes - class LoadContext < BaseContext + class LoadService < BaseService def execute target_type = params[:target_type] target_id = params[:target_id] diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 750a71aea6bc1f84f06f163f43ba7411cae35d34..7c02777e9141363312977c601aa792a3e03e3d71 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -19,7 +19,7 @@ class NotificationService # When create an issue we should send next emails: # - # * issue assignee if his notification level is not Disabled + # * issue assignee if their notification level is not Disabled # * project team members with notification level higher then Participating # def new_issue(issue, current_user) @@ -28,8 +28,8 @@ class NotificationService # When we close an issue we should send next emails: # - # * issue author if his notification level is not Disabled - # * issue assignee if his notification level is not Disabled + # * issue author if their notification level is not Disabled + # * issue assignee if their notification level is not Disabled # * project team members with notification level higher then Participating # def close_issue(issue, current_user) @@ -38,8 +38,8 @@ class NotificationService # When we reassign an issue we should send next emails: # - # * issue old assignee if his notification level is not Disabled - # * issue new assignee if his notification level is not Disabled + # * issue old assignee if their notification level is not Disabled + # * issue new assignee if their notification level is not Disabled # def reassigned_issue(issue, current_user) reassign_resource_email(issue, issue.project, current_user, 'reassigned_issue_email') @@ -48,7 +48,7 @@ class NotificationService # When create a merge request we should send next emails: # - # * mr assignee if his notification level is not Disabled + # * mr assignee if their notification level is not Disabled # def new_merge_request(merge_request, current_user) new_resource_email(merge_request, merge_request.target_project, 'new_merge_request_email') @@ -56,8 +56,8 @@ class NotificationService # When we reassign a merge_request we should send next emails: # - # * merge_request old assignee if his notification level is not Disabled - # * merge_request assignee if his notification level is not Disabled + # * merge_request old assignee if their notification level is not Disabled + # * merge_request assignee if their notification level is not Disabled # def reassigned_merge_request(merge_request, current_user) reassign_resource_email(merge_request, merge_request.target_project, current_user, 'reassigned_merge_request_email') @@ -65,8 +65,8 @@ class NotificationService # When we close a merge request we should send next emails: # - # * merge_request author if his notification level is not Disabled - # * merge_request assignee if his notification level is not Disabled + # * merge_request author if their notification level is not Disabled + # * merge_request assignee if their notification level is not Disabled # * project team members with notification level higher then Participating # def close_mr(merge_request, current_user) @@ -75,8 +75,8 @@ class NotificationService # When we merge a merge request we should send next emails: # - # * merge_request author if his notification level is not Disabled - # * merge_request assignee if his notification level is not Disabled + # * merge_request author if their notification level is not Disabled + # * merge_request assignee if their notification level is not Disabled # * project team members with notification level higher then Participating # def merge_mr(merge_request) @@ -157,6 +157,15 @@ class NotificationService mailer.group_access_granted_email(users_group.id) end + def project_was_moved(project) + recipients = project.team.members + recipients = reject_muted_users(recipients, project) + + recipients.each do |recipient| + mailer.project_was_moved_email(project.id, recipient.id) + end + end + protected # Get project users with WATCH notification level @@ -186,10 +195,10 @@ class NotificationService users.reject do |user| next user.notification.disabled? unless project - tm = project.users_projects.find_by_user_id(user.id) + tm = project.users_projects.find_by(user_id: user.id) if !tm && project.group - tm = project.group.users_groups.find_by_user_id(user.id) + tm = project.group.users_groups.find_by(user_id: user.id) end # reject users who globally disabled notification and has no membership diff --git a/app/services/project_transfer_service.rb b/app/services/project_transfer_service.rb index 7150c1c78c0c213b8a27ebc248f125dd032fa1f4..7055ef32aee816d0f8ceb5d10635f4ff428df9bb 100644 --- a/app/services/project_transfer_service.rb +++ b/app/services/project_transfer_service.rb @@ -18,6 +18,10 @@ class ProjectTransferService raise TransferError.new("Project with same path in target namespace already exists") end + # Remove old satellite + project.satellite.destroy + + # Apply new namespace id project.namespace = new_namespace project.save! @@ -29,8 +33,8 @@ class ProjectTransferService # Move wiki repo also if present gitlab_shell.mv_repository("#{old_path}.wiki", "#{new_path}.wiki") - # create satellite repo - project.ensure_satellite_exists + # Create a new satellite (reload project from DB) + Project.find(project.id).ensure_satellite_exists # clear project cached events project.reset_events_cache diff --git a/app/contexts/projects/create_context.rb b/app/services/projects/create_service.rb similarity index 84% rename from app/contexts/projects/create_context.rb rename to app/services/projects/create_service.rb index 1c60a5de141afa8e344b516b57cf0ca8967ab67d..ba131d8ffbe73f5b95f39bde1c38d884890d624f 100644 --- a/app/contexts/projects/create_context.rb +++ b/app/services/projects/create_service.rb @@ -1,5 +1,5 @@ module Projects - class CreateContext < BaseContext + class CreateService < BaseService def initialize(user, params) @current_user, @params = user, params.dup end @@ -8,6 +8,11 @@ module Projects # get namespace id namespace_id = params.delete(:namespace_id) + # check that user is allowed to set specified visibility_level + unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level]) + params.delete(:visibility_level) + end + # Load default feature settings default_features = Gitlab.config.gitlab.default_projects_features @@ -17,7 +22,7 @@ module Projects wall_enabled: default_features.wall, snippets_enabled: default_features.snippets, merge_requests_enabled: default_features.merge_requests, - public: default_features.public + visibility_level: default_features.visibility_level }.stringify_keys @project = Project.new(default_opts.merge(params)) @@ -47,8 +52,6 @@ module Projects @project.creator = current_user if @project.save - @project.discover_default_branch - unless @project.group @project.users_projects.create( project_access: UsersProject::MASTER, @@ -70,7 +73,7 @@ module Projects end def allowed_namespace?(user, namespace_id) - namespace = Namespace.find_by_id(namespace_id) + namespace = Namespace.find_by(id: namespace_id) current_user.can?(:manage_namespace, namespace) end end diff --git a/app/contexts/projects/fork_context.rb b/app/services/projects/fork_service.rb similarity index 97% rename from app/contexts/projects/fork_context.rb rename to app/services/projects/fork_service.rb index fbc67220d5d2533db6146e26bac65f5336182d05..2f1c7b18aa042840aa0a4746bd040750552aacfd 100644 --- a/app/contexts/projects/fork_context.rb +++ b/app/services/projects/fork_service.rb @@ -1,5 +1,5 @@ module Projects - class ForkContext < BaseContext + class ForkService < BaseService include Gitlab::ShellAdapter def initialize(project, user) diff --git a/app/contexts/projects/transfer_context.rb b/app/services/projects/transfer_service.rb similarity index 94% rename from app/contexts/projects/transfer_context.rb rename to app/services/projects/transfer_service.rb index 3011984e3f8d13f6c58bf255c31151b776301f38..f8e27f6f1c6f9d63a4921b99d9fa5b0fd6f0c7d1 100644 --- a/app/contexts/projects/transfer_context.rb +++ b/app/services/projects/transfer_service.rb @@ -1,5 +1,5 @@ module Projects - class TransferContext < BaseContext + class TransferService < BaseService def execute(role = :default) namespace_id = params[:project].delete(:namespace_id) allowed_transfer = can?(current_user, :change_namespace, project) || role == :admin diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..d9d371da5c4376e9c139a7aacdc7cfc1f719ec0f --- /dev/null +++ b/app/services/projects/update_service.rb @@ -0,0 +1,19 @@ +module Projects + class UpdateService < BaseService + def execute(role = :default) + params[:project].delete(:namespace_id) + # check that user is allowed to set specified visibility_level + unless can?(current_user, :change_visibility_level, project) && Gitlab::VisibilityLevel.allowed_for?(current_user, params[:project][:visibility_level]) + params[:project].delete(:visibility_level) + end + + new_branch = params[:project].delete(:default_branch) + + if project.repository.exists? && new_branch && new_branch != project.default_branch + project.change_head(new_branch) + end + + project.update_attributes(params[:project], as: role) + end + end +end diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..c1130401578a41846406b45edb7e191af4601f6b --- /dev/null +++ b/app/services/search/global_service.rb @@ -0,0 +1,40 @@ +module Search + class GlobalService + attr_accessor :current_user, :params + + def initialize(user, params) + @current_user, @params = user, params.dup + end + + def execute + query = params[:search] + query = Shellwords.shellescape(query) if query.present? + return result unless query.present? + + authorized_projects_ids = [] + authorized_projects_ids += current_user.authorized_projects.pluck(:id) if current_user + authorized_projects_ids += Project.public_or_internal_only(current_user).pluck(:id) + + group = Group.find_by(id: params[:group_id]) if params[:group_id].present? + projects = Project.where(id: authorized_projects_ids) + projects = projects.where(namespace_id: group.id) if group + projects = projects.search(query) + project_ids = projects.pluck(:id) + + result[:projects] = projects.limit(20) + result[:merge_requests] = MergeRequest.in_projects(project_ids).search(query).order('updated_at DESC').limit(20) + result[:issues] = Issue.where(project_id: project_ids).search(query).order('updated_at DESC').limit(20) + result[:total_results] = %w(projects issues merge_requests).sum { |items| result[items.to_sym].size } + result + end + + def result + @result ||= { + projects: [], + merge_requests: [], + issues: [], + total_results: 0, + } + end + end +end diff --git a/app/services/search/project_service.rb b/app/services/search/project_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..3ebaafc752caf2cf1bd1d7d887f513c8555fe44b --- /dev/null +++ b/app/services/search/project_service.rb @@ -0,0 +1,37 @@ +module Search + class ProjectService + attr_accessor :project, :current_user, :params + + def initialize(project, user, params) + @project, @current_user, @params = project, user, params.dup + end + + def execute + query = params[:search] + query = Shellwords.shellescape(query) if query.present? + return result unless query.present? + + if params[:search_code].present? + blobs = project.repository.search_files(query, params[:repository_ref]) unless project.empty_repo? + blobs = Kaminari.paginate_array(blobs).page(params[:page]).per(20) + result[:blobs] = blobs + result[:total_results] = blobs.total_count + else + result[:merge_requests] = project.merge_requests.search(query).order('updated_at DESC').limit(20) + result[:issues] = project.issues.search(query).order('updated_at DESC').limit(20) + result[:total_results] = %w(issues merge_requests).sum { |items| result[items.to_sym].size } + end + + result + end + + def result + @result ||= { + merge_requests: [], + issues: [], + blobs: [], + total_results: 0, + } + end + end +end diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 39aec943f75596a82c3e6f5e2a34986f31a30603..4969198b8c2b080d5633cf887cc8ea8a16472006 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -1,21 +1,21 @@ class SystemHooksService - def self.execute_hooks_for(model, event) + def execute_hooks_for(model, event) execute_hooks(build_event_data(model, event)) end private - def self.execute_hooks(data) + def execute_hooks(data) SystemHook.all.each do |sh| async_execute_hook sh, data end end - def self.async_execute_hook(hook, data) + def async_execute_hook(hook, data) Sidekiq::Client.enqueue(SystemHookWorker, hook.id, data) end - def self.build_event_data(model, event) + def build_event_data(model, event) data = { event_name: build_event_name(model, event), created_at: model.created_at @@ -36,7 +36,8 @@ class SystemHooksService when User data.merge!({ name: model.name, - email: model.email + email: model.email, + user_id: model.id }) when UsersProject data.merge!({ @@ -50,7 +51,7 @@ class SystemHooksService end end - def self.build_event_name(model, event) + def build_event_name(model, event) case model when UsersProject return "user_add_to_team" if event == :create diff --git a/app/services/test_hook_service.rb b/app/services/test_hook_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..17d86a7a274d2b4d44212e5e8c73b1f4a29d05c1 --- /dev/null +++ b/app/services/test_hook_service.rb @@ -0,0 +1,6 @@ +class TestHookService + def execute(hook, current_user) + data = GitPushService.new.sample_data(hook.project, current_user) + hook.execute(data) + end +end diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb index 98794c9470b3d9a1f3b6288785d646fd6ceef571..b122b6c8658c62cc35507fd44cef8c1c23dd582d 100644 --- a/app/uploaders/attachment_uploader.rb +++ b/app/uploaders/attachment_uploader.rb @@ -3,6 +3,8 @@ class AttachmentUploader < CarrierWave::Uploader::Base storage :file + after :store, :reset_events_cache + def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end @@ -27,4 +29,8 @@ class AttachmentUploader < CarrierWave::Uploader::Base def file_storage? self.class.storage == CarrierWave::Storage::File end + + def reset_events_cache(file) + model.reset_events_cache if model.is_a?(User) + end end diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml index 2d4ffc10d5fb7d7b9c6b303f8fe66af81a6fb117..e5af56ffc5c21a26402c2e78e90c5267e96004ad 100644 --- a/app/views/admin/background_jobs/show.html.haml +++ b/app/views/admin/background_jobs/show.html.haml @@ -1,4 +1,50 @@ %h3.page-title Background Jobs -%br +%p.light GitLab uses #{link_to "sidekiq", "http://sidekiq.org/"} library for async job processing + +%hr + +.ui-box + .title Sidekiq running processes + .body + - if @sidekiq_processes.empty? + %h4.cred + %i.icon-warning-sign + There are no running sidekiq processes. Please restart GitLab + - else + %table.table + %thead + %th USER + %th + %th PID + %th + %th CPU + %th + %th MEM + %th + %th STATE + %th + %th START + %th + %th COMMAND + %th + - @sidekiq_processes.split("\n").each do |process| + - next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/) + - data = process.gsub!(/\s+/m, '|').strip.split('|') + %tr + - 6.times do + %td= data.shift + %td + %td= data.join(" ") + + .clearfix + %p + %i.icon-exclamation-sign + If '[25 of 25 busy]' is shown, restart GitLab with 'sudo service gitlab reload'. + %p + %i.icon-exclamation-sign + If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{Settings.gitlab.user} -f sidekiq) and restart GitLab. + + + .ui-box %iframe{src: sidekiq_path, width: '100%', height: 900, style: "border: none"} diff --git a/app/views/admin/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..f28dadfb65924c9b9c9ca7f95e266a8efe564db4 --- /dev/null +++ b/app/views/admin/broadcast_messages/index.html.haml @@ -0,0 +1,61 @@ +%h3.page-title + Broadcast Messages +%p.light + Broadcast messages are displayed for every user and can be used to notify users about scheduled maintenance, recent upgrades and more. +.broadcast-message-preview + %i.icon-bullhorn + %span Your message here + += form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal'} do |f| + -if @broadcast_message.errors.any? + .alert.alert-danger + - @broadcast_message.errors.full_messages.each do |msg| + %p= msg + .form-group + = f.label :message, class: 'control-label' + .col-sm-10 + = f.text_area :message, class: "form-control", rows: 2, required: true + %div + = link_to '#', class: 'js-toggle-colors-link' do + Customize colors + .form-group.js-toggle-colors-container.hide + = f.label :color, "Background Color", class: 'control-label' + .col-sm-10 + = f.text_field :color, placeholder: "#AA33EE", class: "form-control" + .light Hex values as 3 double digit numbers, starting with a # sign. + .form-group.js-toggle-colors-container.hide + = f.label :font, "Font Color", class: 'control-label' + .col-sm-10 + = f.text_field :font, placeholder: "#224466", class: "form-control" + .light Hex values as 3 double digit numbers, starting with a # sign. + .form-group + = f.label :starts_at, class: 'control-label' + .col-sm-10.datetime-controls + = f.datetime_select :starts_at + .form-group + = f.label :ends_at, class: 'control-label' + .col-sm-10.datetime-controls + = f.datetime_select :ends_at + .form-actions + = f.submit "Add broadcast message", class: "btn btn-create" + +-if @broadcast_messages.any? + %ul.bordered-list.broadcast-messages + - @broadcast_messages.each do |broadcast_message| + %li + .pull-right + - if broadcast_message.starts_at + %strong + #{broadcast_message.starts_at.to_s(:short)} + \... + - if broadcast_message.ends_at + %strong + #{broadcast_message.ends_at.to_s(:short)} + + = link_to [:admin, broadcast_message], method: :delete, remote: true, class: 'remove-row btn btn-tiny' do + %i.icon-remove.cred + + .message= broadcast_message.message + + + = paginate @broadcast_messages diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 9ed788d0d9dc658bae06f89bc355805013db31cc..dd663945ea92f78e563ec6d7ebbf9e45123d13e9 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -1,41 +1,45 @@ +%h3.page-title + Admin area +%p.light + You can manage projects, users and other GitLab data from here. +%hr .admin_dash.row - .span4 - .ui-box - .title Projects - .data.padded + .col-sm-4 + .light-well + %h4 Projects + .data = link_to admin_projects_path do %h1= Project.count %hr - = link_to 'New Project', new_project_path, class: "btn btn-small" - .span4 - .ui-box - .title Users - .data.padded + = link_to 'New Project', new_project_path, class: "btn btn-new" + .col-sm-4 + .light-well + %h4 Users + .data = link_to admin_users_path do %h1= User.count %hr - = link_to 'New User', new_admin_user_path, class: "btn btn-small" - .span4 - .ui-box - .title Groups - .data.padded + = link_to 'New User', new_admin_user_path, class: "btn btn-new" + .col-sm-4 + .light-well + %h4 Groups + .data = link_to admin_groups_path do %h1= Group.count %hr - = link_to 'New Group', new_admin_group_path, class: "btn btn-small" + = link_to 'New Group', new_admin_group_path, class: "btn btn-new" -.row - .span4 +.row.prepend-top-10 + .col-md-4 %h4 Latest projects %hr - @projects.each do |project| %p = link_to project.name_with_namespace, [:admin, project] %span.light.pull-right - = time_ago_in_words project.created_at - ago + #{time_ago_with_tooltip(project.created_at)} - .span4 + .col-md-4 %h4 Latest users %hr - @users.each do |user| @@ -43,10 +47,21 @@ = link_to [:admin, user] do = user.name %span.light.pull-right - = time_ago_in_words user.created_at - ago + #{time_ago_with_tooltip(user.created_at)} + + .col-md-4 + %h4 Latest groups + %hr + - @groups.each do |group| + %p + = link_to [:admin, group] do + = group.name + %span.light.pull-right + #{time_ago_with_tooltip(group.created_at)} - .span4 +%br +.row + .col-md-4 %h4 Stats %hr %p @@ -77,3 +92,47 @@ Milestones %span.light.pull-right = Milestone.count + .col-md-4 + %h4 + Features + %hr + %p + Sign up + %span.light.pull-right + = boolean_to_icon gitlab_config.signup_enabled + %p + LDAP + %span.light.pull-right + = boolean_to_icon Gitlab.config.ldap.enabled + %p + Gravatar + %span.light.pull-right + = boolean_to_icon Gitlab.config.gravatar.enabled + %p + OmniAuth + %span.light.pull-right + = boolean_to_icon Gitlab.config.omniauth.enabled + .col-md-4 + %h4 Components + %hr + %p + GitLab + %span.pull-right + = Gitlab::VERSION + %p + GitLab Shell + %span.pull-right + = Gitlab::Shell.new.version + %p + GitLab API + %span.pull-right + = API::API::version + %p + Ruby + %span.pull-right + #{RUBY_VERSION}p#{RUBY_PATCHLEVEL} + + %p + Rails + %span.pull-right + #{Rails::VERSION::STRING} diff --git a/app/views/admin/groups/edit.html.haml b/app/views/admin/groups/edit.html.haml index 8ed463a819171be9ca0cc42a900d5b3e38ab8eb5..eb5c91050af9b1fb77b36bd150693f07268642c2 100644 --- a/app/views/admin/groups/edit.html.haml +++ b/app/views/admin/groups/edit.html.haml @@ -1,25 +1,25 @@ %h3.page-title Edit Group %hr -= form_for [:admin, @group] do |f| += form_for [:admin, @group], html: { class: "form-horizontal" } do |f| - if @group.errors.any? - .alert.alert-error + .alert.alert-danger %span= @group.errors.full_messages.first - .control-group.group_name_holder - = f.label :name do - Group name is - .controls - = f.text_field :name, placeholder: "Example Group", class: "input-xxlarge" + .form-group.group_name_holder + = f.label :name, class: 'control-label' do + Group name + .col-sm-10 + = f.text_field :name, placeholder: "Example Group", class: "form-control" - .control-group.group-description-holder - = f.label :description, "Details" - .controls - = f.text_area :description, maxlength: 250, class: "input-xxlarge js-gfm-input", rows: 4 + .form-group.group-description-holder + = f.label :description, "Details", class: 'control-label' + .col-sm-10 + = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4 - .control-group.group_name_holder - = f.label :path do - %span.cred Group path is - .controls - = f.text_field :path, placeholder: "example-group", class: "input-xxlarge danger" + .form-group.group_name_holder + = f.label :path, class: 'control-label' do + %span.cred Group path + .col-sm-10 + = f.text_field :path, placeholder: "example-group", class: "form-control danger" %ul.cred %li Changing group path can have unintended side effects. %li Renaming group path will rename directory for all related projects diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index c9d7c24f204f54fe18e7778c09bcf3e1af5a6727..7a373ee586ca64b03135968e9779ab6448d005c0 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -7,7 +7,8 @@ = link_to 'New Group', new_admin_group_path, class: "btn btn-new pull-right" %br = form_tag admin_groups_path, method: :get, class: 'form-inline' do - = text_field_tag :name, params[:name], class: "span6" + .form-group + = text_field_tag :name, params[:name], class: "form-control input-mn-300" = submit_tag "Search", class: "btn submit btn-primary" %hr @@ -18,7 +19,7 @@ .clearfix .pull-right.prepend-top-10 = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-small" - = link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove" + = link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: "btn btn-small btn-remove" %h4 = link_to [:admin, group] do diff --git a/app/views/admin/groups/new.html.haml b/app/views/admin/groups/new.html.haml index 0ae35eb6b43cdb244f9718d5f8fce8fac306b0ca..ae0604cf9847b1c98b971e89db5d790b3f99cc65 100644 --- a/app/views/admin/groups/new.html.haml +++ b/app/views/admin/groups/new.html.haml @@ -1,27 +1,31 @@ %h3.page-title New Group %hr -= form_for [:admin, @group] do |f| += form_for [:admin, @group], html: { class: 'group-form form-horizontal' } do |f| - if @group.errors.any? - .alert.alert-error + .alert.alert-danger %span= @group.errors.full_messages.first - .control-group - = f.label :name do - Group name is - .controls - = f.text_field :name, placeholder: "Ex. OpenSource", class: "input-xxlarge left" - .control-group.group-description-holder - = f.label :description, "Details" - .controls - = f.text_area :description, maxlength: 250, class: "input-xxlarge js-gfm-input", rows: 4 + .form-group + = f.label :name, class: 'control-label' do + Group name + .col-sm-10 + = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control" + + .form-group.group-description-holder + = f.label :description, "Details", class: 'control-label' + .col-sm-10 + = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4 + + .form-group + .col-sm-2 + .col-sm-10 + %ul + %li A group is a collection of several projects + %li Groups are private by default + %li Members of a group may only view projects they have permission to access + %li Group project URLs are prefixed with the group namespace + %li Existing projects may be moved into a group .form-actions = f.submit 'Create group', class: "btn btn-create" - %hr - .padded - %ul - %li Group is kind of directory for several projects - %li All created groups are private - %li People within a group see only projects they have access to - %li All projects of group will be stored in a group directory - %li You will be able to move existing projects into group + diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 1566c3458093822a99f6821a3cc88343a89571d7..252111875fa15cad2c4c1320ff48dd0aed72848a 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -6,7 +6,7 @@ Edit %hr .row - .span6 + .col-md-6 .ui-box .title Group info: @@ -25,7 +25,7 @@ = @group.description %li - %span.light Created at: + %span.light Created on: %strong = @group.created_at.stamp("March 1, 1999") @@ -39,14 +39,16 @@ %li %strong = link_to project.name_with_namespace, [:admin, project] + %span.label.label-gray + = repository_size(project) %span.pull-right.light %span.monospace= project.path_with_namespace + ".git" - .span6 + .col-md-6 .ui-box .title Add user(s) to the group: - .ui-box-body.form-holder + .body.form-holder %p.light Read more about project permissions %strong= link_to "here", help_permissions_path, class: "vlink" @@ -55,7 +57,7 @@ %div = users_select_tag(:user_ids, multiple: true) %div.prepend-top-10 - = select_tag :group_access, options_for_select(UsersGroup.group_access_roles), class: "project-access-select chosen" + = select_tag :group_access, options_for_select(UsersGroup.group_access_roles), class: "project-access-select select2" %hr = submit_tag 'Add users into group', class: "btn btn-create" .ui-box @@ -72,5 +74,5 @@ = link_to user.name, admin_user_path(user) %span.pull-right.light = member.human_access - = link_to group_users_group_path(@group, member), confirm: remove_user_from_group_message(@group, user), method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do + = link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do %i.icon-minus.icon-white diff --git a/app/views/admin/hooks/_data_ex.html.erb b/app/views/admin/hooks/_data_ex.html.erb deleted file mode 100644 index b69aa92716dc9444e98b0c845b487c23efce5c39..0000000000000000000000000000000000000000 --- a/app/views/admin/hooks/_data_ex.html.erb +++ /dev/null @@ -1,69 +0,0 @@ -<% data_ex_str = <<eos -1. Project created: -{ - "created_at": "2012-07-21T07:30:54Z", - "event_name": "project_create", - "name": "StoreCloud", - "owner_email": "johnsmith@gmail.com" - "owner_name": "John Smit", - "path": "stormcloud", - "path_with_namespace": "jsmith/stormcloud", - "project_id": 74, -} - -2. Project destroyed: -{ - "created_at": "2012-07-21T07:30:58Z", - "event_name": "project_destroy", - "name": "Underscore", - "owner_email": "johnsmith@gmail.com" - "owner_name": "John Smith", - "path": "underscore", - "path_with_namespace": "jsmith/underscore", - "project_id": 73, -} - -3. New Team Member: -{ - "created_at": "2012-07-21T07:30:56Z", - "event_name": "user_add_to_team", - "project_access": "Master", - "project_id": 74, - "project_name": "StoreCloud", - "project_path": "storecloud", - "user_email": "johnsmith@gmail.com", - "user_name": "John Smith", -} - -4. Team Member Removed: -{ - "created_at": "2012-07-21T07:30:56Z", - "event_name": "user_remove_from_team", - "project_access": "Master", - "project_id": 74, - "project_name": "StoreCloud", - "project_path": "storecloud", - "user_email": "johnsmith@gmail.com", - "user_name": "John Smith", -} - -5. User created: -{ - "created_at": "2012-07-21T07:44:07Z", - "email": "js@gitlabhq.com", - "event_name": "user_create", - "name": "John Smith" -} - -6. User removed: -{ - "created_at": "2012-07-21T07:44:07Z", - "email": "js@gitlabhq.com", - "event_name": "user_destroy", - "name": "John Smith" -} - -eos -%> -<% js_lexer = Pygments::Lexer[:js] %> -<%= raw js_lexer.highlight(data_ex_str) %> diff --git a/app/views/admin/hooks/_data_ex.html.haml b/app/views/admin/hooks/_data_ex.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..9861e372efccab6a16049e2523a833a3b3017658 --- /dev/null +++ b/app/views/admin/hooks/_data_ex.html.haml @@ -0,0 +1,67 @@ += highlight_js do + :erb + 1. Project created: + { + "created_at": "2012-07-21T07:30:54Z", + "event_name": "project_create", + "name": "StoreCloud", + "owner_email": "johnsmith@gmail.com" + "owner_name": "John Smit", + "path": "stormcloud", + "path_with_namespace": "jsmith/stormcloud", + "project_id": 74, + } + + 2. Project destroyed: + { + "created_at": "2012-07-21T07:30:58Z", + "event_name": "project_destroy", + "name": "Underscore", + "owner_email": "johnsmith@gmail.com" + "owner_name": "John Smith", + "path": "underscore", + "path_with_namespace": "jsmith/underscore", + "project_id": 73, + } + + 3. New Team Member: + { + "created_at": "2012-07-21T07:30:56Z", + "event_name": "user_add_to_team", + "project_access": "Master", + "project_id": 74, + "project_name": "StoreCloud", + "project_path": "storecloud", + "user_email": "johnsmith@gmail.com", + "user_name": "John Smith", + } + + 4. Team Member Removed: + { + "created_at": "2012-07-21T07:30:56Z", + "event_name": "user_remove_from_team", + "project_access": "Master", + "project_id": 74, + "project_name": "StoreCloud", + "project_path": "storecloud", + "user_email": "johnsmith@gmail.com", + "user_name": "John Smith", + } + + 5. User created: + { + "created_at": "2012-07-21T07:44:07Z", + "email": "js@gitlabhq.com", + "event_name": "user_create", + "name": "John Smith", + "user_id": 41 + } + + 6. User removed: + { + "created_at": "2012-07-21T07:44:07Z", + "email": "js@gitlabhq.com", + "event_name": "user_destroy", + "name": "John Smith", + "user_id": 41 + } diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index eb6570af30e2a271341b89f0d3ae94b668813f0c..ff90d513ca118aa855ddc19bde6a36c6915ac7ce 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -1,39 +1,35 @@ -.alert.alert-info - %span - Post-receive hooks for binding events. - %br - Read more about system hooks - %strong #{link_to "here", help_system_hooks_path, class: "vlink"} +%h3.page-title + System hooks -= form_for @hook, as: :hook, url: admin_hooks_path, html: { class: 'form-inline' } do |f| +%p.light + #{link_to "System hooks ", help_system_hooks_path, class: "vlink"} can be + used for binding events when GitLab creates a User or Project. + +%hr + + += form_for @hook, as: :hook, url: admin_hooks_path, html: { class: 'form-horizontal' } do |f| -if @hook.errors.any? - .alert.alert-error + .alert.alert-danger - @hook.errors.full_messages.each do |msg| %p= msg - .control-group - = f.label :url, "URL:" - .controls - = f.text_field :url, class: "text_field input-xxlarge input-xpadding" - - = f.submit "Add System Hook", class: "btn btn-create" + .form-group + = f.label :url, "URL:", class: 'control-label' + .col-sm-10 + = f.text_field :url, class: "form-control" + .form-actions + = f.submit "Add System Hook", class: "btn btn-create" %hr -if @hooks.any? - %h3 - Hooks - %small (#{@hooks.count}) - %br - %table - %tr - %th URL - %th Method - %th - - @hooks.each do |hook| - %tr - %td + .ui-box + .title + System hooks (#{@hooks.count}) + %ul.well-list + - @hooks.each do |hook| + %li + .pull-right + = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-small" + = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-small" = link_to admin_hook_path(hook) do %strong= hook.url - = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-small pull-right" - %td POST - %td - = link_to 'Remove', admin_hook_path(hook), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove btn-small pull-right" diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index bc297209ae5d4537dfb65699b20ca1349c9abdac..940a539d5e14b5dab7a5077a3ce1cc77dea88855 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -1,39 +1,41 @@ .row - .span4 + .col-md-4 .admin-filter - = form_tag admin_projects_path, method: :get, class: 'form-inline' do - .control-group - = label_tag :name, 'Name:', class: 'control-label' - .controls - = text_field_tag :name, params[:name], class: "span2" - - .control-group - = label_tag :owner_id, 'Owner:', class: 'control-label' - .controls - = users_select_tag :owner_id, selected: params[:owner_id], class: 'input-large' - .control-group - = label_tag :public_only, 'Public Only', class: 'control-label' - .controls - = check_box_tag :public_only, 1, params[:public_only] - .control-group - = label_tag :with_push, 'Not empty', class: 'control-label' - .controls - = check_box_tag :with_push, 1, params[:with_push] - - %span.light Projects with push events - .control-group - = label_tag :abandoned, 'Abandoned', class: 'control-label' - .controls - = check_box_tag :abandoned, 1, params[:abandoned] - - %span.light No activity over 6 month - + = form_tag admin_projects_path, method: :get, class: '' do + .form-group + = label_tag :name, 'Name:' + = text_field_tag :name, params[:name], class: "form-control" + .form-group + = label_tag :owner_id, 'Owner:' + %div + = users_select_tag :owner_id, selected: params[:owner_id], class: 'input-large input-clamp' + .checkbox + = label_tag :with_push, 'Not empty' + = check_box_tag :with_push, 1, params[:with_push] + + %span.light Projects with push events + .checkbox + = label_tag :abandoned, 'Abandoned' + = check_box_tag :abandoned, 1, params[:abandoned] + + %span.light No activity over 6 month + %fieldset + %strong Visibility level: + .visibility-levels + - Project.visibility_levels.each do |label, level| + .checkbox + %label + = check_box_tag 'visibility_levels[]', level, params[:visibility_levels].present? && params[:visibility_levels].include?(level.to_s) + %span.descr + = visibility_level_icon(level) + = label .form-actions = submit_tag "Search", class: "btn submit btn-primary" = link_to "Reset", admin_projects_path, class: "btn" - .span8 + + .col-md-8 .ui-box .title Projects (#{@projects.total_count}) @@ -42,14 +44,14 @@ %ul.well-list - @projects.each do |project| %li - - if project.public - = public_icon - - else - = private_icon + %span{ class: visibility_level_color(project.visibility_level) } + = visibility_level_icon(project.visibility_level) = link_to project.name_with_namespace, [:admin, project] .pull-right + %span.label.label-gray + = repository_size(project) = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" - = link_to 'Destroy', [project], confirm: remove_project_message(project), method: :delete, class: "btn btn-small btn-remove" + = link_to 'Destroy', [project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-small btn-remove" - if @projects.blank? %p.nothing_here_message 0 projects matches = paginate @projects, theme: "gitlab" diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index c8a87328207d389326718c6e3d482c31d1a4f003..34a91cce163162b5c0b1113c956cfed7d528c93b 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -5,7 +5,7 @@ Edit %hr .row - .span6 + .col-md-6 .ui-box .title Project info: @@ -35,7 +35,7 @@ = @project.creator.try(:name) || '(deleted)' %li - %span.light Created at: + %span.light Created on: %strong = @project.created_at.stamp("March 1, 1999") @@ -53,6 +53,11 @@ %strong = @repository.path_to_repo + %li + %span.light Size + %strong + = repository_size(@project) + %li %span.light last commit: %strong @@ -66,15 +71,26 @@ %li %span.light access: %strong - - if @project.public - %span.cblue - %i.icon-share - Public - - else - %span.cgreen - %i.icon-lock - Private - .span6 + %span{ class: visibility_level_color(@project.visibility_level) } + = visibility_level_icon(@project.visibility_level) + = visibility_level_label(@project.visibility_level) + + .ui-box + .title + Transfer project + .body + = form_for @project, url: transfer_admin_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f| + .form-group + = f.label :namespace_id, "Namespace", class: 'control-label' + .col-sm-10 + = namespace_select_tag :namespace_id, selected: params[:namespace_id], class: 'input-large' + + .form-group + .col-sm-2 + .col-sm-10 + = f.submit 'Transfer', class: 'btn btn-primary' + + .col-md-6 - if @group .ui-box .title @@ -107,5 +123,5 @@ %span.light Owner - else %span.light= users_project.human_access - = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, remote: true, class: "btn btn-small btn-remove" do + = link_to project_team_member_path(@project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, remote: true, class: "btn btn-small btn-remove" do %i.icon-remove diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index 3f930c45fa61647a7db27d9e3fd718cf9cf3054b..881a043f36f41993b62c69d98b32ccf8113f6b06 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -1,35 +1,35 @@ .user_new - = form_for [:admin, @user] do |f| + = form_for [:admin, @user], html: { class: 'form-horizontal' } do |f| -if @user.errors.any? #error_explanation - %ul.unstyled.alert.alert-error + %ul.unstyled.alert.alert-danger - @user.errors.full_messages.each do |msg| %li= msg %fieldset %legend Account - .control-group - = f.label :name - .controls - = f.text_field :name, required: true, autocomplete: "off" + .form-group + = f.label :name, class: 'control-label' + .col-sm-10 + = f.text_field :name, required: true, autocomplete: "off", class: 'form-control' %span.help-inline * required - .control-group - = f.label :username - .controls - = f.text_field :username, required: true, autocomplete: "off" + .form-group + = f.label :username, class: 'control-label' + .col-sm-10 + = f.text_field :username, required: true, autocomplete: "off", class: 'form-control' %span.help-inline * required - .control-group - = f.label :email - .controls - = f.text_field :email, required: true, autocomplete: "off" + .form-group + = f.label :email, class: 'control-label' + .col-sm-10 + = f.text_field :email, required: true, autocomplete: "off", class: 'form-control' %span.help-inline * required - if @user.new_record? %fieldset %legend Password - .control-group - = f.label :password - .controls + .form-group + = f.label :password, class: 'control-label' + .col-sm-10 %strong A temporary password will be generated and sent to user. %br @@ -37,49 +37,52 @@ - else %fieldset %legend Password - .control-group - = f.label :password - .controls= f.password_field :password, disabled: f.object.force_random_password - .control-group - = f.label :password_confirmation - .controls= f.password_field :password_confirmation, disabled: f.object.force_random_password + .form-group + = f.label :password, class: 'control-label' + .col-sm-10= f.password_field :password, disabled: f.object.force_random_password, class: 'form-control' + .form-group + = f.label :password_confirmation, class: 'control-label' + .col-sm-10= f.password_field :password_confirmation, disabled: f.object.force_random_password, class: 'form-control' %fieldset %legend Access - .row - .span8 - .control-group - = f.label :projects_limit - .controls= f.number_field :projects_limit + .form-group + = f.label :projects_limit, class: 'control-label' + .col-sm-10= f.number_field :projects_limit, class: 'form-control' - .control-group - = f.label :can_create_group - .controls= f.check_box :can_create_group + .form-group + = f.label :can_create_group, class: 'control-label' + .col-sm-10= f.check_box :can_create_group - .control-group - = f.label :admin do - %strong.cred Administrator - .controls= f.check_box :admin - .span4 - - unless @user.new_record? - .alert.alert-error - - if @user.blocked? - %p This user is blocked and is not able to login to GitLab - = link_to 'Unblock User', unblock_admin_user_path(@user), method: :put, class: "btn btn-small" - - else - %p Blocked users will be removed from all projects & will not be able to login to GitLab. - = link_to 'Block User', block_admin_user_path(@user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-small btn-remove" + .form-group + = f.label :admin, class: 'control-label' + - if current_user == @user + .col-sm-10= f.check_box :admin, disabled: true + .col-sm-10 You cannot remove your own admin rights + - else + .col-sm-10= f.check_box :admin + - unless @user.new_record? || current_user == @user + .alert.alert-danger + - if @user.blocked? + %p This user is blocked and is not able to login to GitLab + = link_to 'Unblock User', unblock_admin_user_path(@user), method: :put, class: "btn btn-small" + - else + %p Blocked users will be removed from all projects & will not be able to login to GitLab. + = link_to 'Block User', block_admin_user_path(@user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-small btn-remove" %fieldset %legend Profile - .control-group - = f.label :skype - .controls= f.text_field :skype - .control-group - = f.label :linkedin - .controls= f.text_field :linkedin - .control-group - = f.label :twitter - .controls= f.text_field :twitter + .form-group + = f.label :skype, class: 'control-label' + .col-sm-10= f.text_field :skype, class: 'form-control' + .form-group + = f.label :linkedin, class: 'control-label' + .col-sm-10= f.text_field :linkedin, class: 'form-control' + .form-group + = f.label :twitter, class: 'control-label' + .col-sm-10= f.text_field :twitter, class: 'form-control' + .form-group + = f.label :website_url, 'Website', class: 'control-label' + .col-sm-10= f.text_field :website_url, class: 'form-control' .form-actions - if @user.new_record? diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index b32f0ae87ccf09449e155ba5e2865806cdc99f67..1fa6fdfaff1fe295a839be9a6f5136f428b1d28b 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -1,10 +1,12 @@ .row - .span3 + .col-md-3 .admin-filter = form_tag admin_users_path, method: :get, class: 'form-inline' do - = search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'search-text-input span2' - = button_tag type: 'submit', class: 'btn' do - %i.icon-search + .append-bottom-10 + .form-group + = search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'form-control' + = button_tag type: 'submit', class: 'btn btn-primary' do + %i.icon-search %ul.nav.nav-pills.nav-stacked %li{class: "#{'active' unless params[:filter]}"} = link_to admin_users_path do @@ -25,7 +27,7 @@ %hr = link_to 'Reset', admin_users_path, class: "btn btn-cancel" - .span9 + .col-md-9 .ui-box .title Users (#{@users.total_count}) @@ -53,6 +55,6 @@ - if user.blocked? = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn btn-small success" - else - = link_to 'Block', block_admin_user_path(user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-small btn-remove" - = link_to 'Destroy', [:admin, user], confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn btn-small btn-remove" + = link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-small btn-remove" + = link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All tickets linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: "btn btn-small btn-remove" = paginate @users, theme: "gitlab" diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 3df9903e40905d96a17f1cfb6e74be0b6cbfe9fa..e888358dc63932e6551a85e6f56d35623e7c69d6 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -1,5 +1,5 @@ %h3.page-title - User: + %span.cgray User: = @user.name - if @user.blocked? %span.cred (Blocked) @@ -10,17 +10,15 @@ = link_to edit_admin_user_path(@user), class: "btn grouped" do %i.icon-edit Edit - - if @user.blocked? - = link_to 'Unblock', unblock_admin_user_path(@user), method: :put, class: "btn grouped success" %hr .row - .span6 + .col-md-6 .ui-box .title Account: .pull-right - = image_tag gravatar_icon(@user.email, 32), class: "avatar s32" + = image_tag avatar_icon(@user.email, 32), class: "avatar s32" %ul.well-list %li %span.light Name: @@ -45,6 +43,16 @@ %span.light Member since: %strong = @user.created_at.stamp("Nov 12, 2031") + - if @user.confirmed_at + %li + %span.light Confirmed at: + %strong + = @user.confirmed_at.stamp("Nov 12, 2031") + - else + %li + %span.light Confirmed: + %strong.cred + No %li %span.light Last sign-in at: @@ -67,22 +75,34 @@ = link_to @user.created_by.name, [:admin, @user.created_by] - unless @user == current_user - .alert - %h4 Block user - %br - %p Blocking user has the following effects: - %ul - %li User will not be able to login - %li User will not be able to access git repositories - %li User will be removed from joined projects and groups - %li Personal projects will be left - %li Owned groups will be left - = link_to 'Block user', block_admin_user_path(@user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-remove" + - if @user.blocked? + .alert.alert-info + %h4 This user is blocked + %p Blocking user has the following effects: + %ul + %li User will not be able to login + %li User will not be able to access git repositories + %li User will be removed from joined projects and groups + %li Personal projects will be left + %li Owned groups will be left + %br + = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-new", data: { confirm: 'Are you sure?' } + - else + .alert.alert-warning + %h4 Block this user + %p Blocking user has the following effects: + %ul + %li User will not be able to login + %li User will not be able to access git repositories + %li User will be removed from joined projects and groups + %li Personal projects will be left + %li Owned groups will be left + %br + = link_to 'Block user', block_admin_user_path(@user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put, class: "btn btn-remove" - .alert.alert-error + .alert.alert-danger %h4 Remove user - %br %p Deleting a user has the following effects: %ul %li All user content like authored issues, snippets, comments will be removed @@ -93,9 +113,10 @@ %li Next groups with all content will be removed: %strong #{@user.solo_owned_groups.map(&:name).join(', ')} - = link_to 'Remove user', [:admin, @user], confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn btn-remove" + %br + = link_to 'Remove user', [:admin, @user], data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove" - .span6 + .col-md-6 - if @user.users_groups.present? .ui-box .title Groups: @@ -107,7 +128,7 @@ .pull-right %span.light= user_group.human_access - unless user_group.owner? - = link_to group_users_group_path(group, user_group), confirm: remove_user_from_group_message(group, @user), method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do + = link_to group_users_group_path(group, user_group), data: { confirm: remove_user_from_group_message(group, @user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do %i.icon-remove.icon-white .ui-box @@ -117,11 +138,7 @@ - tm = project.team.find_tm(@user.id) %li.users_project = link_to admin_project_path(project), class: dom_class(project) do - - if project.namespace - = project.namespace.human_name - \/ - %strong.well-title - = truncate(project.name, length: 45) + = project.name_with_namespace - if tm .pull-right @@ -131,7 +148,5 @@ %span.light= tm.human_access - if tm.respond_to? :project - = link_to project_team_member_path(project, @user), confirm: remove_from_project_team_message(project, @user), remote: true, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from project' do + = link_to project_team_member_path(project, @user), data: { confirm: remove_from_project_team_message(project, @user) }, remote: true, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from project' do %i.icon-remove - - diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml index 891177263173f01ac8e38a598084d0998c8714df..39dd600dba3168b9e594fe208ccdbb0b5e712ab2 100644 --- a/app/views/dashboard/_activities.html.haml +++ b/app/views/dashboard/_activities.html.haml @@ -5,4 +5,5 @@ .content_list - else %p.nothing_here_message Projects activity will be displayed here -.loading.hide + += spinner diff --git a/app/views/dashboard/_groups.html.haml b/app/views/dashboard/_groups.html.haml index b4f3866228deced6e1e55142035cdd48c4a4e91a..2ff1c33c53affc7010f38bf186a387660dafa6ce 100644 --- a/app/views/dashboard/_groups.html.haml +++ b/app/views/dashboard/_groups.html.haml @@ -1,6 +1,6 @@ .ui-box .title.clearfix - = search_field_tag :filter_group, nil, placeholder: 'Filter by name', class: 'dash-filter' + = search_field_tag :filter_group, nil, placeholder: 'Filter by name', class: 'dash-filter form-control' - if current_user.can_create_group? %span.pull-right = link_to new_group_path, class: "btn btn-new" do @@ -10,6 +10,7 @@ - groups.each do |group| %li.group-row = link_to group_path(id: group.path), class: dom_class(group) do + = image_tag group_icon(group.path), class: "avatar s24" %span.group-name.filter-title = truncate(group.name, length: 35) %span.arrow diff --git a/app/views/dashboard/_project.html.haml b/app/views/dashboard/_project.html.haml index 50b833f3d82239f5ac6469ba14096cdf8b5addce..e326bee53abd2dcc193e79dc96efaca5ef1b4b37 100644 --- a/app/views/dashboard/_project.html.haml +++ b/app/views/dashboard/_project.html.haml @@ -1,12 +1,12 @@ = link_to project_path(project), class: dom_class(project) do - %span.namespace-name - - if project.namespace - = project.namespace.human_name - \/ - %span.project-name.filter-title - = truncate(project.name, length: 25) + .dash-project-access-icon + = visibility_level_icon(project.visibility_level) + %span.str-truncated + %span.namespace-name + - if project.namespace + = project.namespace.human_name + \/ + %span.project-name.filter-title + = project.name %span.arrow %i.icon-angle-right - %span.last-activity - %span Last activity: - %span.date= project_last_activity(project) diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml index b79b27fc95add93c413c1b77c839d3085e095cb9..8313cc07b5e47505e4eb70f973453809f008c28a 100644 --- a/app/views/dashboard/_projects.html.haml +++ b/app/views/dashboard/_projects.html.haml @@ -1,6 +1,6 @@ .ui-box .title.clearfix - = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'dash-filter' + = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'dash-filter form-control' - if current_user.can_create_project? %span.pull-right = link_to new_project_path, class: "btn btn-new" do diff --git a/app/views/dashboard/_projects_filter.html.haml b/app/views/dashboard/_projects_filter.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..8c9893ba84f33dfa37011c932dc5031468d44bbe --- /dev/null +++ b/app/views/dashboard/_projects_filter.html.haml @@ -0,0 +1,55 @@ +%fieldset + %ul.nav.nav-pills.nav-stacked + = nav_tab :scope, nil do + = link_to projects_dashboard_filter_path(scope: nil) do + All + %span.pull-right + = current_user.authorized_projects.count + = nav_tab :scope, 'personal' do + = link_to projects_dashboard_filter_path(scope: 'personal') do + Personal + %span.pull-right + = current_user.personal_projects.count + = nav_tab :scope, 'joined' do + = link_to projects_dashboard_filter_path(scope: 'joined') do + Joined + %span.pull-right + = current_user.authorized_projects.joined(current_user).count + = nav_tab :scope, 'owned' do + = link_to projects_dashboard_filter_path(scope: 'owned') do + Owned + %span.pull-right + = current_user.owned_projects.count + +%fieldset + %legend Visibility + %ul.nav.nav-pills.nav-stacked.nav-small.visibility-filter + - Gitlab::VisibilityLevel.values.each do |level| + %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' } + = link_to projects_dashboard_filter_path(visibility_level: level) do + = visibility_level_icon(level) + = visibility_level_label(level) + +- if @groups.present? + %fieldset + %legend Groups + %ul.nav.nav-pills.nav-stacked.nav-small + - @groups.each do |group| + %li{ class: (group.name == params[:group]) ? 'active' : 'light' } + = link_to projects_dashboard_filter_path(group: group.name) do + %i.icon-folder-close-alt + = group.name + %small.pull-right + = group.projects.count + + + +- if @labels.present? + %fieldset + %legend Labels + %ul.nav.nav-pills.nav-stacked.nav-small + - @labels.each do |label| + %li{ class: (label.name == params[:label]) ? 'active' : 'light' } + = link_to projects_dashboard_filter_path(scope: params[:scope], label: label.name) do + %i.icon-tag + = label.name diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder index 0f0f3466e92dce54addf87855d1a39f993673f1c..f54135577839221bd66dbfc10e914e4853556296 100644 --- a/app/views/dashboard/issues.atom.builder +++ b/app/views/dashboard/issues.atom.builder @@ -12,7 +12,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear 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 => gravatar_icon(issue.author_email) + 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 diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index 82880d5ef63e4c96ec166638bc4f771847c6ea3a..19bd4e7bd545ab91cdeb5702d39138328134cdfd 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -1,13 +1,13 @@ %h3.page-title - Issues assigned to me + Issues %span.pull-right #{@issues.total_count} issues %p.light - For all issues you should visit the project's issues page, or use the search panel to find a specific issue. + List all issues from all project's you have access to. %hr .row - .span3 + .col-md-3 = render 'shared/filter', entity: 'issue' - .span9 + .col-md-9 = render 'shared/issues' diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index 9c96edeefd588a058ce62759ffce18d1f9b627f0..b487a4d6666048a3637ddb0d39c50b3e5c3241d3 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -4,10 +4,10 @@ %p.light - Only merge requests created by you or assigned to you are listed here. + List all merge requests from all project's you have access to. %hr .row - .span3 + .col-md-3 = render 'shared/filter', entity: 'merge_request' - .span9 + .col-md-9 = render 'shared/merge_requests' diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml index 0dcb1a87e9aaadc3a12b66302136b824c15f41f0..8feef97c7328c9680195190d3c0d8ac2d1c7fb47 100644 --- a/app/views/dashboard/projects.html.haml +++ b/app/views/dashboard/projects.html.haml @@ -1,68 +1,47 @@ -%h3.page-title My Projects +%h3.page-title + My Projects +.pull-right + .dropdown.inline + %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"} + %span.light sort: + - if @sort.present? + = @sort.humanize + - else + Name + %b.caret + %ul.dropdown-menu + %li + = link_to projects_dashboard_filter_path(sort: nil) do + Name + = link_to projects_dashboard_filter_path(sort: 'newest') do + Newest + = link_to projects_dashboard_filter_path(sort: 'oldest') do + Oldest + = link_to projects_dashboard_filter_path(sort: 'recently_updated') do + Recently updated + = link_to projects_dashboard_filter_path(sort: 'last_updated') do + Last updated %p.light All projects you have access to are listed here. Public projects are not included here unless you are a member %hr .row - .span3 - %ul.nav.nav-pills.nav-stacked - = nav_tab :scope, nil do - = link_to projects_dashboard_path do - All - %span.pull-right - = current_user.authorized_projects.count - = nav_tab :scope, 'personal' do - = link_to projects_dashboard_path(scope: 'personal') do - Personal - %span.pull-right - = current_user.personal_projects.count - = nav_tab :scope, 'joined' do - = link_to projects_dashboard_path(scope: 'joined') do - Joined - %span.pull-right - = current_user.authorized_projects.joined(current_user).count - = nav_tab :scope, 'owned' do - = link_to projects_dashboard_path(scope: 'owned') do - Owned - %span.pull-right - = current_user.owned_projects.count - - - - if @groups.present? - %fieldset - %legend Groups - %ul.bordered-list - - @groups.each do |group| - %li{ class: (group.name == params[:group]) ? 'active' : 'light' } - = link_to projects_dashboard_path(group: group.name) do - %i.icon-folder-close-alt - = group.name - %small.pull-right - = group.projects.count - - - - - if @labels.present? - %fieldset - %legend Labels - %ul.bordered-list - - @labels.each do |label| - %li{ class: (label.name == params[:label]) ? 'active' : 'light' } - = link_to projects_dashboard_path(scope: params[:scope], label: label.name) do - %i.icon-tag - = label.name - - .span9 + .col-md-3.hidden-sm.hidden-xs.side-filters + = render "projects_filter" + .col-md-9 %ul.bordered-list.my-projects.top-list - @projects.each do |project| - %li + %li.my-project-row %h4.project-title - %span.access-icon - - if project.public - = public_icon - - else - = private_icon + .project-access-icon + = visibility_level_icon(project.visibility_level) = link_to project_path(project), class: dom_class(project) do - %strong= project.name_with_namespace + = project.name_with_namespace + + - if current_user.can_leave_project?(project) + .pull-right + = link_to leave_project_team_members_path(project), data: { confirm: "Leave project?"}, method: :delete, remote: true, class: "btn-tiny btn remove-row", title: 'Leave project' do + %i.icon-signout + Leave - if project.forked_from_project %small.pull-right @@ -71,6 +50,10 @@ = link_to project.forked_from_project.name_with_namespace, project_path(project.forked_from_project) .project-info .pull-right + - if project.archived? + %span.label + %i.icon-archive + Archived - project.labels.each do |label| %span.label.label-info %i.icon-tag @@ -81,6 +64,7 @@ %span.light Last activity: %span.date= project_last_activity(project) + - if @projects.blank? %li %h3.nothing_here_message There are no projects here. diff --git a/app/views/dashboard/show.atom.builder b/app/views/dashboard/show.atom.builder index a913df92299b6fc4781f3c3332ff7cfda94254f8..f4cf24ccd9982521073598156c5499711900bb4b 100644 --- a/app/views/dashboard/show.atom.builder +++ b/app/views/dashboard/show.atom.builder @@ -17,7 +17,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear 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 => gravatar_icon(event.author_email) + 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 diff --git a/app/views/dashboard/show.html.haml b/app/views/dashboard/show.html.haml index 2305eae1f71901a9bc928380fc6b0df4bd59495d..e5b7fbf097e287b31e189d97575189c6fc7ce44a 100644 --- a/app/views/dashboard/show.html.haml +++ b/app/views/dashboard/show.html.haml @@ -1,8 +1,8 @@ - if @has_authorized_projects - .dashboard - .activities.span8 + .dashboard.row + .activities.col-md-8 = render 'activities' - .side.span4 + .side.col-md-4.hidden-sm.hidden-xs = render 'sidebar' - else diff --git a/app/views/dashboard/show.js.haml b/app/views/dashboard/show.js.haml deleted file mode 100644 index 7e5a148e5ef2d56f87c72403ee7525cffc8cfc14..0000000000000000000000000000000000000000 --- a/app/views/dashboard/show.js.haml +++ /dev/null @@ -1,2 +0,0 @@ -:plain - Pager.append(#{@events.count}, "#{escape_javascript(render(@events))}"); diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb deleted file mode 100644 index adc9b6720923e921b9acc94427020f7038b586c9..0000000000000000000000000000000000000000 --- a/app/views/devise/confirmations/new.html.erb +++ /dev/null @@ -1,12 +0,0 @@ -<h2>Resend confirmation instructions</h2> - -<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> - <%= devise_error_messages! %> - - <div><%= f.label :email %><br /> - <%= f.email_field :email %></div> - - <div><%= f.submit "Resend confirmation instructions" %></div> -<% end %> - -<%= render partial: "devise/shared/links" %> diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..dd63a232fe2ea85c83d2587adfa299e805ef2c41 --- /dev/null +++ b/app/views/devise/confirmations/new.html.haml @@ -0,0 +1,14 @@ +.login-box + %h3.page-title Resend confirmation instructions + = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| + .devise-errors + = devise_error_messages! + = f.email_field :email, placeholder: 'Email', class: "form-control", required: true + .clearfix.append-bottom-10 + = f.submit "Resend confirmation instructions", class: 'btn btn-success' + %hr + %p + %span.light + Already have login and password? + %strong + = link_to "Sign in", new_session_path(resource_name) diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb index 7b4fd5269647f128b3e9b8bf02396b3d52eee574..553d08369e9ff2f160a477474b7761d88816fe56 100644 --- a/app/views/devise/mailer/confirmation_instructions.html.erb +++ b/app/views/devise/mailer/confirmation_instructions.html.erb @@ -1,5 +1,9 @@ -<p>Welcome <%= @resource.email %>!</p> +<p>Welcome <%= @resource.name %>!</p> -<p>You can confirm your account through the link below:</p> +<% if @resource.unconfirmed_email.present? %> + <p>You can confirm your email (<%= @resource.unconfirmed_email %>) through the link below:</p> +<% else %> + <p>You can confirm your account through the link below:</p> +<% end %> <p><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @resource.confirmation_token) %></p> diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index d85b4ab08b212eaa092b77d151ba982a82bf825d..95c52608e1f29b225fa713a52cc46ce80479cd5d 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -1,11 +1,15 @@ = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: "login-box" }) do |f| %h3 Change your password - = devise_error_messages! + .devise-errors + = devise_error_messages! = f.hidden_field :reset_password_token %div - = f.password_field :password, class: "text top", placeholder: "New password" + = f.password_field :password, class: "form-control top", placeholder: "New password", required: true %div - = f.password_field :password_confirmation, class: "text bottom", placeholder: "Confirm new password" + = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true %div + .clearfix.append-bottom-10 = f.submit "Change my password", class: "btn btn-primary" - .pull-right= render partial: "devise/shared/links" + = link_to "Sign in", new_session_path(resource_name), class: "btn pull-right" + %div + = link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml index 3df65d037af594bdc8c3a09db74b85af90aeab5e..a14ef2995c870351f7c57a8603661d15e4322661 100644 --- a/app/views/devise/passwords/new.html.haml +++ b/app/views/devise/passwords/new.html.haml @@ -1,10 +1,13 @@ = form_for(resource, as: resource_name, url: password_path(resource_name), html: { class: "login-box", method: :post }) do |f| %h3.page-title Reset password - = devise_error_messages! - = f.email_field :email, placeholder: "Email", class: "text" - %br/ - %br/ - = f.submit "Reset password", class: "btn-primary btn" - .pull-right - = link_to "Sign in", new_session_path(resource_name), class: "btn" - %br/ + .devise-errors + = devise_error_messages! + = f.email_field :email, placeholder: "Email", class: "form-control", required: true + .clearfix.append-bottom-10 + = f.submit "Reset password", class: "btn-primary btn" + %hr + %p + %span.light + Already have login and password? + %strong + = link_to "Sign in", new_session_path(resource_name) diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb index 139acf28a9f59ec282cd79f9eacb010e72dc60ea..b11817af95dbf3e7bf70460e8b485a8b4c4d6b4d 100644 --- a/app/views/devise/registrations/edit.html.erb +++ b/app/views/devise/registrations/edit.html.erb @@ -23,6 +23,6 @@ <h3>Cancel my account</h3> -<p>Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), confirm: "Are you sure?", method: :delete %>.</p> +<p>Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>.</p> <%= link_to "Back", :back %> diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index d749d7bac093ce5526af9a9a3eac0ec9580aff46..24bc0406544541ca57cf5e449d3074757d5a7a62 100644 --- a/app/views/devise/registrations/new.html.haml +++ b/app/views/devise/registrations/new.html.haml @@ -1,19 +1,24 @@ = form_for(resource, as: resource_name, url: registration_path(resource_name), html: { class: "login-box" }) do |f| %h3.page-title Sign Up - %br - = devise_error_messages! + .devise-errors + = devise_error_messages! %div - = f.text_field :name, class: "text top", placeholder: "Name", required: true + = f.text_field :name, class: "form-control top", placeholder: "Name", required: true %div - = f.text_field :username, class: "text middle", placeholder: "Username", required: true + = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true %div - = f.email_field :email, class: "text middle", placeholder: "Email", required: true + = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true %div - = f.password_field :password, class: "text middle", placeholder: "Password", required: true + = f.password_field :password, class: "form-control middle", placeholder: "Password", required: true %div - = f.password_field :password_confirmation, class: "text bottom", placeholder: "Confirm password", required: true + = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm password", required: true %div = f.submit "Sign up", class: "btn-create btn" %hr - = link_to "Sign in", new_session_path(resource_name) - = link_to "Forgot your password?", new_password_path(resource_name), class: "pull-right" + %p + %span.light + Have an account? + %strong + = link_to "Sign in", new_session_path(resource_name) + %p + = link_to "Forgot your password?", new_password_path(resource_name) diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index 0c8be9d5c48c0b7ce04bcb4935cc60516070c65f..a2f85fa3fe27093bb28cb3fc50e026b869e0af40 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -1,6 +1,6 @@ = form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| - = f.text_field :login, class: "text top", placeholder: "Username or Email", autofocus: "autofocus" - = f.password_field :password, class: "text bottom", placeholder: "Password" + = f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus" + = f.password_field :password, class: "form-control bottom", placeholder: "Password" - if devise_mapping.rememberable? .clearfix.append-bottom-10 %label.checkbox.remember_me{for: "user_remember_me"} diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index 575d33949b68ce50f5f64cecbc520362eabbbd95..bb1d0a4001f0e217b9581594a4e30a7102ce2460 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -1,5 +1,5 @@ = form_tag(user_omniauth_callback_path(:ldap), id: 'new_ldap_user' ) do - = text_field_tag :username, nil, {class: "text top", placeholder: "LDAP Login", autofocus: "autofocus"} - = password_field_tag :password, nil, {class: "text bottom", placeholder: "Password"} + = text_field_tag :username, nil, {class: "form-control top", placeholder: "LDAP Login", autofocus: "autofocus"} + = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} %br/ = submit_tag "LDAP Sign in", class: "btn-create btn" diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index c6e1d8db577c077963d9617a2f7a28c322ab91fb..938f61d2093ce477848d0a5cc609d798a33ef5c2 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -1,7 +1,7 @@ .login-box %h3.page-title Sign in - if ldap_enabled? - %ul.nav.nav-tabs + %ul.nav.nav-tabs.append-bottom-20 %li.active = link_to 'LDAP', '#tab-ldap', 'data-toggle' => 'tab' %li @@ -17,14 +17,20 @@ = render 'devise/sessions/oauth_providers' if devise_mapping.omniauthable? + %hr - - if Gitlab.config.gitlab.signup_enabled - %hr - %div - Don't have an account? + - if gitlab_config.signup_enabled + %p + %span.light + Don't have an account? %strong = link_to "Sign up", new_registration_path(resource_name) + %p + %span.light Did not receive confirmation email? + = link_to "Send again", new_confirmation_path(resource_name) + + - if extra_config.has_key?('sign_in_text') %hr = markdown(extra_config.sign_in_text) diff --git a/app/views/devise/shared/_links.erb b/app/views/devise/shared/_links.erb index db931b86288effeb3cf6b65ce8bb49b90384598a..49e99e25c1d27a78895700b76f6bd34e2ce329c3 100644 --- a/app/views/devise/shared/_links.erb +++ b/app/views/devise/shared/_links.erb @@ -2,7 +2,7 @@ <%= link_to "Sign in", new_session_path(resource_name), class: "btn" %><br /> <% end -%> -<%- if devise_mapping.registerable? && controller_name != 'registrations' && Gitlab.config.gitlab.signup_enabled %> +<%- if devise_mapping.registerable? && controller_name != 'registrations' && gitlab_config.signup_enabled %> <%= link_to "Sign up", new_registration_path(resource_name) %><br /> <% end -%> diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml index 4d4b24009f43cd9923cb0b878eebe1d7cfb14b4a..135320da57ea7b17d6a9424f7d996a69de2daabe 100644 --- a/app/views/events/_commit.html.haml +++ b/app/views/events/_commit.html.haml @@ -2,4 +2,4 @@ .commit-row-title = link_to commit[:id][0..8], project_commit_path(project, commit[:id]), class: "commit_short_id", alt: '' - = gfm escape_once(truncate(commit[:message], length: 70)) rescue "--broken encoding" + = gfm event_commit_title(commit[:message]) diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index b3543460d65dfabd2f6808021e8853fb54a03c65..8cf26671e3bf945237bf1eafdaa61b66857b26bc 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -1,10 +1,10 @@ - if event.proper? .event-item{class: "#{event.body? ? "event-block" : "event-inline" }"} %span.cgray.pull-right - #{time_ago_in_words(event.created_at)} ago. + #{time_ago_with_tooltip(event.created_at)} = cache event do - = image_tag gravatar_icon(event.author_email), class: "avatar s24", alt:'' + = image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:'' - if event.push? = render "events/event/push", event: event diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml index de5634d3c55ac7f686fec452675313355273f1d0..6db05a1a5a6ef5a208d810be6f0924f4a5442c15 100644 --- a/app/views/events/_event_last_push.html.haml +++ b/app/views/events/_event_last_push.html.haml @@ -5,9 +5,8 @@ %strong= truncate(event.ref_name, length: 28) at %strong= link_to_project event.project - %span - = time_ago_in_words(event.created_at) - ago. + #{time_ago_with_tooltip(event.created_at)} + .pull-right = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-create btn-small" do Create Merge Request diff --git a/app/views/events/_events.html.haml b/app/views/events/_events.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..3d62d4788690528c4d2c3f06019d73605e466423 --- /dev/null +++ b/app/views/events/_events.html.haml @@ -0,0 +1 @@ += render @events diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml index adba9a5f61975994590eb9ac704d9e3cd5c520a6..f181df23eb4c6fd80201d4a87115bc6aa1f868aa 100644 --- a/app/views/events/event/_push.html.haml +++ b/app/views/events/event/_push.html.haml @@ -7,7 +7,7 @@ = link_to project_commits_path(event.project, event.ref_name) do %strong= truncate(event.ref_name, length: 30) at - %strong= link_to_project event.project + = link_to_project event.project - if event.push_with_commits? - project = event.project diff --git a/app/views/groups/_new_group_member.html.haml b/app/views/groups/_new_group_member.html.haml index 234392c03e1dc9588745b85a364b653234d7cec5..5801139a9f2c43feb3329bb16f70ae93802d419f 100644 --- a/app/views/groups/_new_group_member.html.haml +++ b/app/views/groups/_new_group_member.html.haml @@ -1,20 +1,18 @@ -= form_for @users_group, url: group_users_groups_path(@group) do |f| - %fieldset - %legend - New member(s) for - %strong #{@group.name} - group += form_for @users_group, url: group_users_groups_path(@group), html: { class: 'form-horizontal users-group-form' } do |f| + %h4.append-bottom-20 + New member(s) for + %strong #{@group.name} + group - %p 1. Choose users you want in the group - .control-group - = f.label :user_ids, "People" - .controls= users_select_tag(:user_ids, multiple: true, class: 'input-large') + %p 1. Choose users you want in the group + .form-group + = f.label :user_ids, "People", class: 'control-label' + .col-sm-10= users_select_tag(:user_ids, multiple: true, class: 'input-large') - %p 2. Set access level for them - .control-group - = f.label :group_access, "Group Access" - .controls= select_tag :group_access, options_for_select(UsersGroup.group_access_roles, @users_group.group_access), class: "project-access-select chosen" - - .form-actions - = f.submit 'Add users into group', class: "btn btn-create" + %p 2. Set access level for them + .form-group + = f.label :group_access, "Group Access", class: 'control-label' + .col-sm-10= select_tag :group_access, options_for_select(UsersGroup.group_access_roles, @users_group.group_access), class: "project-access-select select2" + .form-actions + = f.submit 'Add users into group', class: "btn btn-create" diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml index 16a3c60f660a451377765e6a41e67c616652b427..029d6cb0efa98fa89483e3d5cfdaeec9aedb80b1 100644 --- a/app/views/groups/_projects.html.haml +++ b/app/views/groups/_projects.html.haml @@ -12,10 +12,10 @@ - projects.each do |project| %li.project-row = link_to project_path(project), class: dom_class(project) do - %span.project-name - = truncate(project.name, length: 25) + .dash-project-access-icon + = visibility_level_icon(project.visibility_level) + %span.str-truncated + %span.project-name + = truncate(project.name, length: 25) %span.arrow %i.icon-angle-right - %span.last-activity - %span Last activity: - %span.date= project_last_activity(project) diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 2682d31ff470600641734b1673d608dc4c06379e..e274a79967415a5314a87ec0715e5786908e043b 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -1,5 +1,5 @@ .row - .span2 + .col-md-2 %ul.nav.nav-pills.nav-stacked.nav-stacked-menu %li.active = link_to '#tab-edit', 'data-toggle' => 'tab' do @@ -12,7 +12,7 @@ %li = link_to 'Remove', '#tab-remove', 'data-toggle' => 'tab' - .span10 + .col-md-10 .tab-content .tab-pane.active#tab-edit .ui-box @@ -20,20 +20,40 @@ %strong= @group.name group settings: %div.form-holder - = form_for @group do |f| + = form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f| - if @group.errors.any? - .alert.alert-error + .alert.alert-danger %span= @group.errors.full_messages.first - .control-group - = f.label :name do - Group name is - .controls - = f.text_field :name, placeholder: "Ex. OpenSource", class: "input-xxlarge left" + .form-group + = f.label :name, class: 'control-label' do + Group name + .col-sm-10 + = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control left" - .control-group.group-description-holder - = f.label :description, "Details" - .controls - = f.text_area :description, maxlength: 250, class: "input-xxlarge js-gfm-input", rows: 4 + .form-group.group-description-holder + = f.label :description, "Details", class: 'control-label' + .col-sm-10 + = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4 + + .form-group + .col-sm-2 + .col-sm-10 + = image_tag group_icon(@group.to_param), alt: '', class: 'avatar s160' + %p.light + - if @group.avatar? + You can change your group avatar here + - else + You can upload an group avatar here + %a.choose-btn.btn.btn-small.js-choose-group-avatar-button + %i.icon-paper-clip + %span Choose File ... + + %span.file_name.js-avatar-filename File name... + = f.file_field :avatar, class: "js-group-avatar-input hidden" + .light The maximum file size allowed is 100KB. + - if @group.avatar? + %hr + = link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar" .form-actions = f.submit 'Save group', class: "btn btn-save" @@ -51,25 +71,22 @@ %ul.well-list - @group.projects.each do |project| %li - - if project.public - = public_icon - - else - = private_icon + = visibility_level_icon(project.visibility_level) = link_to project.name_with_namespace, project .pull-right = link_to 'Members', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" - = link_to 'Remove', project, confirm: remove_project_message(project), method: :delete, class: "btn btn-small btn-remove" + = link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-small btn-remove" - if @group.projects.blank? %p.nothing_here_message This group has no projects yet .tab-pane#tab-remove .ui-box.ui-box-danger .title Remove group - .ui-box-body + .body %p - Remove of group will cause removing all child projects and resources. + Removing group will cause all child projects and resources to be removed. %p %strong Removed group can not be restored! - = link_to 'Remove Group', @group, confirm: 'Removed group can not be restored! Are you sure?', method: :delete, class: "btn btn-remove" + = link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove" diff --git a/app/views/groups/issues.atom.builder b/app/views/groups/issues.atom.builder index 701747bdbc3242cfd894e95ed4666dc4b0331c16..f2005193f830181987058910753be4115113473b 100644 --- a/app/views/groups/issues.atom.builder +++ b/app/views/groups/issues.atom.builder @@ -12,7 +12,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear 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 => gravatar_icon(issue.author_email) + 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 diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 482613f172dcedbb141449bb3ead2c45f0d5c4aa..e24df310910d990b7f1286a1c44661bbcb7ebde7 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -1,5 +1,5 @@ %h3.page-title - Issues assigned to me + Issues %span.pull-right #{@issues.total_count} issues %p.light @@ -9,7 +9,7 @@ %hr .row - .span3 + .col-md-3 = render 'shared/filter', entity: 'issue' - .span9 + .col-md-9 = render 'shared/issues' diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index 8a9b03535bc2e2383881b5318c76d012b2f0a190..eaf85bbdbc8ee5b4e721f919767fdf8e9330322f 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -3,12 +3,12 @@ %span.pull-right #{@merge_requests.total_count} merge requests %p.light - Authored or assigned to you from + Only merge requests from %strong #{@group.name} - group. To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. + group are listed here. To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. %hr .row - .span3 + .col-md-3 = render 'shared/filter', entity: 'merge_request' - .span9 + .col-md-9 = render 'shared/merge_requests' diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index 02049bb2ee671c7bbf17cfce62b598d972c8b17f..955a107e542701d4fb2f81cbc8e1fb156628d79e 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -1,26 +1,27 @@ -= form_for @group do |f| += form_for @group, html: { class: 'group-form form-horizontal' } do |f| - if @group.errors.any? - .alert.alert-error + .alert.alert-danger %span= @group.errors.full_messages.first - .control-group - = f.label :name do - Group name is - .controls - = f.text_field :name, placeholder: "Ex. OpenSource", class: "input-xxlarge left" + .form-group + = f.label :name, class: 'control-label' do + Group name + .col-sm-10 + = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control" - .control-group.group-description-holder - = f.label :description, "Details" - .controls - = f.text_area :description, maxlength: 250, class: "input-xxlarge js-gfm-input", rows: 4 + .form-group.group-description-holder + = f.label :description, "Details", class: 'control-label' + .col-sm-10 + = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4 - .control-group - .controls + .form-group + .col-sm-2 + .col-sm-10 %ul - %li Group is kind of directory for several projects - %li All created groups are private - %li People within a group see only projects they have access to - %li All projects of group will be stored in a group directory - %li You will be able to move existing projects into group + %li A group is a collection of several projects + %li Groups are private by default + %li Members of a group may only view projects they have permission to access + %li Group project URLs are prefixed with the group namespace + %li Existing projects may be moved into a group .form-actions = f.submit 'Create group', class: "btn btn-create" diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder index edf03642d82d4e63ceea57aa2c9d23980cc36fcd..e07bb7d2fb7145b863b9d6cf9cf950971f446765 100644 --- a/app/views/groups/show.atom.builder +++ b/app/views/groups/show.atom.builder @@ -16,7 +16,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear 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 => gravatar_icon(event.author_email) + 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 diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index e613ed3eaa3531b7bc70fa05d8f76da30a8e8d80..6256c047929b53704ab5eba6777ca014024d3d64 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,5 +1,5 @@ .dashboard - .activities.span8 + .activities.col-md-8.hidden-sm = render "events/event_last_push", event: @last_push = link_to dashboard_path, class: 'btn btn-tiny' do ← To dashboard @@ -11,11 +11,15 @@ .content_list - else %p.nothing_here_message Project activity will be displayed here - .loading.hide - .side.span4 - - if @group.description.present? - .description-block - = @group.description + = spinner + .side.col-md-4 + .light-well.append-bottom-20 + = image_tag group_icon(@group.path), class: "avatar s90" + .clearfix.light + %h3.page-title + = @group.name + - if @group.description.present? + %p= @group.description = render "projects", projects: @projects .prepend-top-20 = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed" do diff --git a/app/views/groups/show.js.haml b/app/views/groups/show.js.haml deleted file mode 100644 index 7e5a148e5ef2d56f87c72403ee7525cffc8cfc14..0000000000000000000000000000000000000000 --- a/app/views/groups/show.js.haml +++ /dev/null @@ -1,2 +0,0 @@ -:plain - Pager.append(#{@events.count}, "#{escape_javascript(render(@events))}"); diff --git a/app/views/help/_api_layout.html.haml b/app/views/help/_api_layout.html.haml index 502cc31a80cfd16ff9d3ddaf08c54ad463cf0176..c211b658410ae724185b291b5ce3e91097bc7e8c 100644 --- a/app/views/help/_api_layout.html.haml +++ b/app/views/help/_api_layout.html.haml @@ -1,5 +1,5 @@ .row - .span3 + .col-md-3 .append-bottom-20 = link_to help_path, class: 'btn btn-small' do %i.icon-angle-left @@ -9,5 +9,5 @@ %li{class: file == @category ? 'active' : nil} = link_to file.titleize, help_api_file_path(file) - .span9.pull-right + .col-md-9.pull-right = yield diff --git a/app/views/help/_layout.html.haml b/app/views/help/_layout.html.haml index da917888eeece82f46355f29d4f224fbb3ef3577..a413616bad0dc09802f444a067f000999bf09a11 100644 --- a/app/views/help/_layout.html.haml +++ b/app/views/help/_layout.html.haml @@ -1,37 +1,11 @@ .row - .span3{:"data-spy" => 'affix'} - .ui-box - .title - Help - %ul.well-list - %li - %strong= link_to "Workflow", help_workflow_path - %li - %strong= link_to "SSH keys", help_ssh_path - - %li - %strong= link_to "GitLab Markdown", help_markdown_path - - %li - %strong= link_to "Permissions", help_permissions_path - - %li - %strong= link_to "API", help_api_path - - %li - %strong= link_to "Web Hooks", help_web_hooks_path - - %li - %strong= link_to "Rake Tasks", help_raketasks_path - - %li - %strong= link_to "System Hooks", help_system_hooks_path - - %li - %strong= link_to "Public Access", help_public_access_path - - %li - %strong= link_to "Security", help_security_path - - .span9.pull-right + .col-md-3 + %h3.page-title Help + %ul.nav.nav-pills.nav-stacked + - links = {:"Workflow" => help_workflow_path, :"SSH Keys" => help_ssh_path, :"GitLab Markdown" => help_markdown_path, :"Permissions" => help_permissions_path, :"API" => help_api_path, :"Web Hooks" => help_web_hooks_path, :"Rake Tasks" => help_raketasks_path, :"System Hooks" => help_system_hooks_path, :"Public Access" => help_public_access_path, :"Security" => help_security_path} + - links.each do |title,path| + %li{class: current_page?(path) ? 'active' : nil} + = link_to title, path + + .col-md-9 = yield diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index e979e7c0d07c3f0f235aa02e296551d8ad57e8ae..500e5dc65e116e8a0152b739ca5e73467f6c742f 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -1,30 +1,32 @@ -#modal-shortcuts.modal.hide - .modal-header - %a.close{href: "#", "data-dismiss" => "modal"} × - %h3 Keyboard Shortcuts - .modal-body - %h5 Global Shortcuts - %p - %span.label.label-inverse s - – - Focus Search - %p - %span.label.label-inverse ? - – - Show this dialog +#modal-shortcuts.modal.hide{tabindex: -1} + .modal-dialog + .modal-content + .modal-header + %a.close{href: "#", "data-dismiss" => "modal"} × + %h3 Keyboard Shortcuts + .modal-body + %h5 Global Shortcuts + %p + %span.label.label-inverse s + – + Focus Search + %p + %span.label.label-inverse ? + – + Show this dialog - %h5 Project Files browsing - %p - %span.label.label-inverse - %i.icon-arrow-up - – - Move selection up - %p - %span.label.label-inverse - %i.icon-arrow-down - – - Move selection down - %p - %span.label.label-inverse Enter - – - Open selection + %h5 Project Files browsing + %p + %span.label.label-inverse + %i.icon-arrow-up + – + Move selection up + %p + %span.label.label-inverse + %i.icon-arrow-down + – + Move selection down + %p + %span.label.label-inverse Enter + – + Open selection diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index fadc2dc21cbc5f5f23df8f488ac249535153eb15..f1cb723ebac79e62555ad27ffccf42ba07ce5b79 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -1,15 +1,17 @@ -%h2.page-title - GitLab - .pull-right +.jumbotron + %h2 + GitLab %span= Gitlab::VERSION %small= Gitlab::REVISION -%p.slead - Self Hosted Git Management - %br - Fast, secure and stable solution based on Ruby on Rails. + %p.slead + GitLab is open source software to collaborate on code. + %br + Create projects and repositories, manage access and do code reviews. + %br + Read more about GitLab at #{link_to "gitlab.org", "http://gitlab.org/", target: "_blank"}. .row - .span4 + .col-md-4 .ui-box .title Quick help @@ -31,9 +33,9 @@ = link_to "Stack Overflow", "http://stackoverflow.com/questions/tagged/gitlab" %li Browse our - = link_to "issue tracker", "https://github.com/gitlabhq/gitlabhq/issues" + = link_to "issue tracker", "https://gitlab.com/gitlab-org/gitlab-ce/issues" - .span4 + .col-md-4 .ui-box .title User documentation @@ -55,14 +57,14 @@ %p Get familiar with GitLab's permission levels. %li - %strong= link_to "API", help_api_path + %strong= link_to "API", help_api_file_path(category: 'README') %p Explore how you can access GitLab via a simple and powerful API. %li %strong= link_to "Web Hooks", help_web_hooks_path %p Let GitLab notify you when new code has been pushed to your project. - .span4 + .col-md-4 .ui-box .title Admin documentation diff --git a/app/views/help/permissions.html.haml b/app/views/help/permissions.html.haml index bab1e7c0a41efc12b279b2ced1608f9a847b924a..6505609022aa110c735998acb0e30110ecb6312c 100644 --- a/app/views/help/permissions.html.haml +++ b/app/views/help/permissions.html.haml @@ -1,6 +1,8 @@ = render layout: 'help/layout' do %h3.page-title Permissions - %p.light User has different abilities depends on access level he has in particular group or project + %p.light Users have different abilities depending on the access level they have in particular group or project. + %p.light If a user is both in a project group and in the project itself the highest permission level is used. + %p.light If a user is a GitLab administrator they receive all permissions. %hr %h4 Project: @@ -98,6 +100,13 @@ %td.permission-x ✓ %td.permission-x ✓ %td.permission-x ✓ + %tr + %td Manage issue tracker + %td + %td + %td.permission-x ✓ + %td.permission-x ✓ + %td.permission-x ✓ %tr %td Add new team members %td @@ -141,7 +150,7 @@ %td.permission-x ✓ %td.permission-x ✓ %tr - %td Switch public mode + %td Switch visibility level %td %td %td diff --git a/app/views/help/public_access.html.haml b/app/views/help/public_access.html.haml index c67402ee319610a1cc54f13fbe474ca1275037d0..ba2df8c355393bca832ddca1dc4940c2c28a0d95 100644 --- a/app/views/help/public_access.html.haml +++ b/app/views/help/public_access.html.haml @@ -1,15 +1,46 @@ = render layout: 'help/layout' do %h3.page-title Public Access - %p - GitLab allows you to open selected projects to be accessed publicly. - These projects will be cloneable - %em without any - authentication. - Also they will be listed on the #{link_to "public access directory", public_root_path}. + %p.slead + GitLab allows you to open selected projects to be accessed + %strong publicly + or + %strong internally + \. + %br + Projects with either of these visibility levels will be listed in the #{link_to "public access directory", public_root_path}. + %br + Internal projects will only be available to authenticated users. + .clearfix + .dashboard-intro-icon + = public_icon + %h4 + Public projects + %p + Public project can be cloned + %strong without any + authentication. + %br + It will also be listed on the #{link_to "public access directory", public_root_path}. + %br + %strong Any logged in user + will have #{link_to "Guest", help_permissions_path} permissions on the repository. + + .clearfix + .dashboard-intro-icon + = internal_icon + %h4 + Internal projects + %p + Internal project can be cloned by any logged in user. + %br + It will also be listed on the #{link_to "public access directory", public_root_path} for logged in users. + %br + Any logged in user will have #{link_to "Guest", help_permissions_path} permissions on the repository. + + %h4 How to change project visibility %ol %li Go to your project dashboard %li Click on the "Edit" tab - %li Select "Public clone access" - + %li Change "Visibility Level" diff --git a/app/views/help/ssh.html.haml b/app/views/help/ssh.html.haml index 72a21b89cabb09850ea22dd1408e16bba45a18de..1c7b08aa7cdf4f0d2cd1a894934e16ab1e289367 100644 --- a/app/views/help/ssh.html.haml +++ b/app/views/help/ssh.html.haml @@ -5,18 +5,24 @@ SSH key allows you to establish a secure connection between your computer and GitLab %p.slead - To generate a new SSH key just open your terminal and use code below. + Before generating an SSH key, check if your system already has one by running cat ~/.ssh/id_rsa.pub + If your see a long string starting with 'ssh-rsa' or 'ssh-dsa', you can skip the ssh-keygen step. + + %p.slead + To generate a new SSH key just open your terminal and use code below. The ssh-keygen command prompts you for a location and filename to store the key pair and for a password. + When prompted for the location and filename you can press enter to use the default. + It is a best practice to use a password for an SSH key but it is not required and you can skip creating a password by pressing enter. + Note that the password you choose here can't be altered or retrieved. %pre.dark ssh-keygen -t rsa -C "#{current_user.email}" - \# Creates a new ssh key using the provided email - \# Generating public/private rsa key pair... - %p.slead - Next just use code below to dump your public key and add to GitLab SSH Keys + Use code below to show your public key. %pre.dark cat ~/.ssh/id_rsa.pub - \# ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6eNtGpNGwstc.... + %p.slead + Copy-paste the key to the 'My SSH Keys' section under the 'SSH' tab in your user profile. + Please copy the complete key starting with 'ssh-' and ending with your username and host. diff --git a/app/views/help/web_hooks.html.haml b/app/views/help/web_hooks.html.haml index 25865251de35127cca9a536373fb21f0edf06f4c..7bde7fcc3d0aa799f3bf2f487b6368c1e7d17460 100644 --- a/app/views/help/web_hooks.html.haml +++ b/app/views/help/web_hooks.html.haml @@ -1,12 +1,115 @@ = render layout: 'help/layout' do - %h3.page-title Web hooks + %h3.page-title Project web hooks + %p.light + Project web hooks allow you to trigger url if new code is pushed or new issue is created + %hr %p.slead - Every GitLab project can trigger a web server whenever the repo is pushed to. + You can configure web hook to listen for specific events like pushes, issues, merge requests. + %br + GitLab will send POST request with data to web hook url. %br Web Hooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server. + %hr + + %h4 Push events + %p.light + Triggered when you push to the repository except pushing tags. %br - GitLab will send POST request with commits information on every push. - %h5 Hooks request example: - = render "projects/hooks/data_ex" + Request body: + = highlight_js do + :erb + { + "before": "95790bf891e76fee5e1747ab589903a6a1f80f22", + "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "ref": "refs/heads/master", + "user_id": 4, + "user_name": "John Smith", + "project_id": 15, + "repository": { + "name": "Diaspora", + "url": "git@localhost:diaspora.git", + "description": "", + "homepage": "http://localhost/diaspora", + }, + "commits": [ + { + "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", + "message": "Update Catalan translation to e38cb41.", + "timestamp": "2011-12-12T14:27:31+02:00", + "url": "http://localhost/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", + "author": { + "name": "Jordi Mallach", + "email": "jordi@softcatala.org", + } + }, + // ... + { + "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "message": "fixed readme", + "timestamp": "2012-01-03T23:36:29+02:00", + "url": "http://localhost/diaspora/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "author": { + "name": "GitLab dev user", + "email": "gitlabdev@dv6700.(none)", + }, + }, + ], + "total_commits_count": 4, + }; + + %h4.prepend-top-20 Issues events + %p.light + Triggered when new issue created or existing issue was updated/closed/reopened. + %br + Request body: + = highlight_js do + :erb + { + "object_kind":"issue", + "object_attributes":{ + "id":301, + "title":"New API: create/update/delete file", + "assignee_id":51, + "author_id":51, + "project_id":14, + "created_at":"2013-12-03T17:15:43Z", + "updated_at":"2013-12-03T17:15:43Z", + "position":0, + "branch_name":null, + "description":"Create new API for manipulations with repository", + "milestone_id":null, + "state":"opened", + "iid":23 + } + } + %h4.prepend-top-20 Merge request events + %p.light + Triggered when new merge request created or existing merge request was updated/merged/closed. + %br + Request body: + = highlight_js do + :erb + { + "object_kind":"merge_request", + "object_attributes":{ + "id":99, + "target_branch":"master", + "source_branch":"ms-viewport", + "source_project_id":14, + "author_id":51, + "assignee_id":6, + "title":"MS-Viewport", + "created_at":"2013-12-03T17:23:34Z", + "updated_at":"2013-12-03T17:23:34Z", + "st_commits":null, + "st_diffs":null, + "milestone_id":null, + "state":"opened", + "merge_status":"unchecked", + "target_project_id":14, + "iid":1, + "description":"" + } + } diff --git a/app/views/kaminari/gitlab/_paginator.html.haml b/app/views/kaminari/gitlab/_paginator.html.haml index 6f9fb332261558322437102b24bb0cef3d95558d..4f7996e49961cd2468f0a8feb2e134b43c4f602a 100644 --- a/app/views/kaminari/gitlab/_paginator.html.haml +++ b/app/views/kaminari/gitlab/_paginator.html.haml @@ -6,8 +6,8 @@ -# remote: data-remote -# paginator: the paginator that renders the pagination tags inside = paginator.render do - %div.pagination - %ul + %div.gl-pagination + %ul.pagination = prev_page_tag unless current_page.first? - each_page do |page| - if page.left_outer? || page.right_outer? || page.inside_window? diff --git a/app/views/layouts/_broadcast.html.haml b/app/views/layouts/_broadcast.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..5794e3de33863057e28857fcfc53bbc6610b520b --- /dev/null +++ b/app/views/layouts/_broadcast.html.haml @@ -0,0 +1,4 @@ +- if broadcast_message.present? + .broadcast-message{ style: broadcast_styling(broadcast_message) } + %i.icon-bullhorn + = broadcast_message.message diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 0775abea3dda3dbeff677e902dc2b24e1df5e599..5723250151a605a243dc0a08641d6ec41b6e908b 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -8,6 +8,8 @@ = javascript_include_tag "application" = csrf_meta_tags = include_gon + :erb + <meta name="viewport" content="width=device-width, initial-scale=1.0"> = render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id') @@ -20,3 +22,8 @@ = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, format: :atom, private_token: current_user.private_token), title: "Recent commits to #{@project.name}:#{@ref}") - if current_controller?(:issues) = auto_discovery_link_tag(:atom, project_issues_url(@project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues") + + -# Go repository retrieval support. + - if controller_name == 'projects' && action_name == 'show' + %meta{name: "go-import", content: "#{@project.web_url_without_protocol} git #{@project.web_url}.git"} + diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml index 6492c122ba04c97505a426b719a5bf0ec357d349..1d181d3519c9f31504c23798702df42b7029d098 100644 --- a/app/views/layouts/_head_panel.html.haml +++ b/app/views/layouts/_head_panel.html.haml @@ -7,34 +7,42 @@ %h1 GITLAB %span.separator %h1.project_name= title - %ul.nav - %li - %a - %div.hide.turbolink-spinner - %i.icon-refresh.icon-spin - Loading... - %li - = render "layouts/search" - %li - = link_to public_root_path, title: "Public area", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do - %i.icon-globe - %li - = link_to user_snippets_path(current_user), title: "My snippets", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do - %i.icon-paste - - if current_user.is_admin? + + %button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"} + %span.sr-only Toggle navigation + %i.icon-reorder + + .navbar-collapse.collapse + %ul.nav.navbar-nav + %li.hidden-sm.hidden-xs + %a + %div.hide.turbolink-spinner + %i.icon-refresh.icon-spin + %li.hidden-sm.hidden-xs + = render "layouts/search" + %li.visible-sm.visible-xs + = link_to search_path, title: "Search", class: 'has_bottom_tooltip', 'data-original-title' => 'Search area' do + %i.icon-search %li - = link_to admin_root_path, title: "Admin area", class: 'has_bottom_tooltip', 'data-original-title' => 'Admin area' do - %i.icon-cogs - - if current_user.can_create_project? + = link_to public_root_path, title: "Public area", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do + %i.icon-globe %li - = link_to new_project_path, title: "New project", class: 'has_bottom_tooltip', 'data-original-title' => 'New project' do - %i.icon-plus - %li - = link_to profile_path, title: "My profile", class: 'has_bottom_tooltip', 'data-original-title' => 'My profile' do - %i.icon-user - %li - = link_to destroy_user_session_path, class: "logout", method: :delete, title: "Logout", class: 'has_bottom_tooltip', 'data-original-title' => 'Logout' do - %i.icon-signout - %li - = link_to current_user, class: "profile-pic", id: 'profile-pic' do - = image_tag gravatar_icon(current_user.email, 26), alt: '' + = link_to user_snippets_path(current_user), title: "My snippets", class: 'has_bottom_tooltip', 'data-original-title' => 'My snippets' do + %i.icon-paste + - if current_user.is_admin? + %li + = link_to admin_root_path, title: "Admin area", class: 'has_bottom_tooltip', 'data-original-title' => 'Admin area' do + %i.icon-cogs + - if current_user.can_create_project? + %li + = link_to new_project_path, title: "New project", class: 'has_bottom_tooltip', 'data-original-title' => 'New project' do + %i.icon-plus + %li + = link_to profile_path, title: "Profile settings", class: 'has_bottom_tooltip', 'data-original-title' => 'Profile settings"' do + %i.icon-user + %li + = link_to destroy_user_session_path, class: "logout", method: :delete, title: "Logout", class: 'has_bottom_tooltip', 'data-original-title' => 'Logout' do + %i.icon-signout + %li + = link_to current_user, class: "profile-pic", id: 'profile-pic' do + = image_tag avatar_icon(current_user.email, 26), alt: 'User activity' diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml index 7d16a75af6eceb3562e2d26c343601fd7dea480c..6a20dedf62f76ad99ee54ba02e9145518bd2567a 100644 --- a/app/views/layouts/_init_auto_complete.html.haml +++ b/app/views/layouts/_init_auto_complete.html.haml @@ -1,4 +1,4 @@ :javascript GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_project_path(@project)}" - GitLab.GfmAutoComplete.Emoji.assetBase = '#{image_path("emoji")}' + GitLab.GfmAutoComplete.Emoji.assetBase = "#{Gitlab.config.gitlab.relative_url_root + '/assets/emoji'}" GitLab.GfmAutoComplete.setup(); diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml index 3c4bd857c22055e523d6206713149c5708c71c8a..65c806a915fd46e9a0c77bc2cd1e17e47df67894 100644 --- a/app/views/layouts/_public_head_panel.html.haml +++ b/app/views/layouts/_public_head_panel.html.haml @@ -12,11 +12,11 @@ - else Public Projects - %ul.nav + .pull-right + = link_to "Sign in", new_session_path(:user), class: 'btn btn-sign-in btn-new' + + %ul.nav.navbar-nav %li %a %div.hide.turbolink-spinner %i.icon-refresh.icon-spin - Loading... - %li - = link_to "Sign in", new_session_path(:user), class: 'btn btn-sign-in' diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 9a0db99332a726a7803c5f44a1e81028483ecf8e..a0e55b21c321be0bceb046b983309ed0fef4996f 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -7,4 +7,4 @@ = hidden_field_tag :search_code, true = hidden_field_tag :repository_ref, @ref = submit_tag 'Go' if ENV['RAILS_ENV'] == 'test' - .search-autocomplete-json.hide{:'data-autocomplete-opts' => search_autocomplete_source } + .search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref } diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index 3a23cbdb376a5081086116121d0a9b9f14e08a8b..439cb978a76ceab6536aa46bc51da35bfb6cf9ec 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -4,7 +4,7 @@ %body{class: "#{app_theme} admin", :'data-page' => body_data_page} = render "layouts/head_panel", title: "Admin area" = render "layouts/flash" - %nav.main-nav + %nav.main-nav.navbar-collapse.collapse .container= render 'layouts/nav/admin' .container diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 792fe5e4a284e615a307e68d8fcf098e4fb96e8f..511db389e0f740465f52d22540dc56c589cf59b6 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -2,9 +2,10 @@ %html{ lang: "en"} = render "layouts/head", title: "Dashboard" %body{class: "#{app_theme} application", :'data-page' => body_data_page } + = render "layouts/broadcast" = render "layouts/head_panel", title: "Dashboard" = render "layouts/flash" - %nav.main-nav + %nav.main-nav.navbar-collapse.collapse .container= render 'layouts/nav/dashboard' .container diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index c4729836faac292d1a6ed494d124ab6282bdf2f0..c5041dd71b80e34700414847434af7700aa1ae76 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -11,5 +11,7 @@ GitLab is open source software to collaborate on code. %br #{link_to "Sign in", new_user_session_path} or browse for #{link_to "public projects", public_projects_path}. - %hr + %hr + .container + .content = yield diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index 0e955d59ff8fc1b5c525220dc3622037ea25ed8f..fb4a3a3ba951e8301fbf2ff3bfd7c5d873c9942c 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -1,10 +1,11 @@ !!! 5 %html{ lang: "en"} - = render "layouts/head", title: "#{@group.name}" + = render "layouts/head", title: group_head_title %body{class: "#{app_theme} application", :'data-page' => body_data_page} + = render "layouts/broadcast" = render "layouts/head_panel", title: "group: #{@group.name}" = render "layouts/flash" - %nav.main-nav + %nav.main-nav.navbar-collapse.collapse .container= render 'layouts/nav/group' .container diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index 73946f9988925039c83a26e36a6a2fb19eea6941..48c569f868400046269b94a6ba4b65847d65355f 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -10,6 +10,8 @@ = link_to "Users", admin_users_path = nav_link(controller: :logs) do = link_to "Logs", admin_logs_path + = nav_link(controller: :broadcast_messages) do + = link_to "Messages", admin_broadcast_messages_path = nav_link(controller: :hooks) do = link_to "Hooks", admin_hooks_path = nav_link(controller: :background_jobs) do diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index cae24c3170da70e77e4204db119756b36a15d135..12fd49e609f5438842ff18b087790116fc69edbc 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -12,7 +12,7 @@ = nav_link(path: 'dashboard#merge_requests') do = link_to merge_requests_dashboard_path do Merge Requests - %span.count= current_user.cared_merge_requests.opened.count + %span.count= current_user.assigned_merge_requests.opened.count = nav_link(controller: :help) do = link_to "Help", help_path diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index 7c3acfc398ad954e2033639ada9afb55bd9670bb..d44cb975ea59adea34d1a735f252876d931534a9 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -2,8 +2,11 @@ = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to profile_path, title: "Profile" do %i.icon-home - = nav_link(path: 'profiles#account') do - = link_to "Account", account_profile_path + = nav_link(controller: :accounts) do + = link_to "Account", profile_account_path + - unless current_user.ldap_user? + = nav_link(controller: :passwords) do + = link_to "Password", edit_profile_password_path = nav_link(controller: :notifications) do = link_to "Notifications", profile_notifications_path = nav_link(controller: :keys) do diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index e9d535f6972fd69ec411edfdc18fd70a467a96bc..1f70cf17987217bb4a55f9ec43380833874af640 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -4,7 +4,7 @@ %i.icon-home - if project_nav_tab? :files - = nav_link(controller: %w(tree blob blame edit_tree)) do + = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do = link_to 'Files', project_tree_path(@project, @ref || @repository.root_ref) - if project_nav_tab? :commits diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index 30a0532bc2b1e065368b588a23b84c17713e4f66..2d869a6cdcb633f4f7ba8ebd6a611446902e2e5a 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -2,9 +2,10 @@ %html{ lang: "en"} = render "layouts/head", title: "Profile" %body{class: "#{app_theme} profile", :'data-page' => body_data_page} + = render "layouts/broadcast" = render "layouts/head_panel", title: "Profile" = render "layouts/flash" - %nav.main-nav + %nav.main-nav.navbar-collapse.collapse .container= render 'layouts/nav/profile' .container diff --git a/app/views/layouts/project_settings.html.haml b/app/views/layouts/project_settings.html.haml index ea739da73d861b3548be95aa76516300eeb46a25..5659cfab31dffc7017fe31b159ca282ea885ee46 100644 --- a/app/views/layouts/project_settings.html.haml +++ b/app/views/layouts/project_settings.html.haml @@ -2,19 +2,20 @@ %html{ lang: "en"} = render "layouts/head", title: @project.name_with_namespace %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id } + = render "layouts/broadcast" = render "layouts/head_panel", title: project_title(@project) = render "layouts/init_auto_complete" = render "layouts/flash" - if can?(current_user, :download_code, @project) = render 'shared/no_ssh' - %nav.main-nav + %nav.main-nav.navbar-collapse.collapse .container= render 'layouts/nav/project' .container .content .row - .span2 + .col-md-2 = render "projects/settings_nav" - .span10 + .col-md-10 = yield diff --git a/app/views/layouts/projects.html.haml b/app/views/layouts/projects.html.haml index 6d8bf9b710b0fdca72c27e7500c9718fdf781f10..3ae4961b13752527eefa068117000819a17d0379 100644 --- a/app/views/layouts/projects.html.haml +++ b/app/views/layouts/projects.html.haml @@ -1,14 +1,15 @@ !!! 5 %html{ lang: "en"} - = render "layouts/head", title: @project.name_with_namespace + = render "layouts/head", title: project_head_title %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id } + = render "layouts/broadcast" = render "layouts/head_panel", title: project_title(@project) = render "layouts/init_auto_complete" = render "layouts/flash" - if can?(current_user, :download_code, @project) = render 'shared/no_ssh' - %nav.main-nav + %nav.main-nav.navbar-collapse.collapse .container= render 'layouts/nav/project' .container diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml index f922dcc420378f0fbca72a14c5933b0adedd5f93..8f490f61a9cf6bdbbebdfcc01ba062ddb67399d8 100644 --- a/app/views/layouts/public.html.haml +++ b/app/views/layouts/public.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "Public Projects" - %body{class: "ui_mars application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} - if current_user = render "layouts/head_panel", title: "Public Projects" - else diff --git a/app/views/layouts/public_projects.html.haml b/app/views/layouts/public_projects.html.haml index cfe6a63055adde65f7b4bb944df87f12fa107fb7..a8e3236d865676148a501e7046c4c85e32bb1227 100644 --- a/app/views/layouts/public_projects.html.haml +++ b/app/views/layouts/public_projects.html.haml @@ -1,9 +1,9 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: @project.name_with_namespace - %body{class: "ui_mars application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/public_head_panel" - %nav.main-nav + %nav.main-nav.navbar-collapse.collapse .container= render 'layouts/nav/project' .container .content= yield diff --git a/app/views/layouts/user_team.html.haml b/app/views/layouts/user_team.html.haml index e64e68d24460a7ad74be90474a9c87b60975464b..191ad406c3cf6e5065da07073a591863a33146c3 100644 --- a/app/views/layouts/user_team.html.haml +++ b/app/views/layouts/user_team.html.haml @@ -4,7 +4,7 @@ %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/head_panel", title: "team: #{@team.name}" = render "layouts/flash" - %nav.main-nav + %nav.main-nav.navbar-collapse.collapse .container= render 'layouts/nav/team' .container diff --git a/app/views/notify/_note_message.html.haml b/app/views/notify/_note_message.html.haml index 88c4df55b7fdf715a90fcc8a445620e468f6733c..9e329af2d478918aa60ffd867450951917d2fe72 100644 --- a/app/views/notify/_note_message.html.haml +++ b/app/views/notify/_note_message.html.haml @@ -1,6 +1,6 @@ %p %strong #{@note.author_name} - left next message: + wrote: %cite{style: 'color: #666'} = markdown(@note.note) diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml index 6b2782ebb0b9b7a03477336c607632837fe68c1d..321f9418ded9c3e1648b2c42cc8096fee03b91a7 100644 --- a/app/views/notify/new_merge_request_email.html.haml +++ b/app/views/notify/new_merge_request_email.html.haml @@ -1,5 +1,5 @@ %p - = "New Merge Request !#{@merge_request.iid}" + = "New Merge Request ##{@merge_request.iid}" %p = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.target_project, @merge_request) %p diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb index 2d27350486e4f0e36a1393ab890ec50157ff300e..16be4bb619f04a68b749777f2e3e2c276a225222 100644 --- a/app/views/notify/new_merge_request_email.text.erb +++ b/app/views/notify/new_merge_request_email.text.erb @@ -1,4 +1,4 @@ -New Merge Request <%= @merge_request.iid %> +New Merge Request #<%= @merge_request.iid %> <%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %> diff --git a/app/views/notify/reassigned_merge_request_email.html.haml b/app/views/notify/reassigned_merge_request_email.html.haml index 3a615f86e69edce9ffc5dba7bc6e29f042bb2ecb..d2d82d36c4831da94bbea0b88ba349b6d251331f 100644 --- a/app/views/notify/reassigned_merge_request_email.html.haml +++ b/app/views/notify/reassigned_merge_request_email.html.haml @@ -1,5 +1,5 @@ %p - = "Reassigned Merge Request !#{@merge_request.iid}" + = "Reassigned Merge Request ##{@merge_request.iid}" = link_to_gfm truncate(@merge_request.title, length: 30), project_merge_request_url(@merge_request.target_project, @merge_request) %p Assignee changed diff --git a/app/views/notify/reassigned_merge_request_email.text.erb b/app/views/notify/reassigned_merge_request_email.text.erb index eecf055ff6f800821168e293d9ec1088a9136f62..87a7847e06d19f0b1f95cf4147464b64683fee05 100644 --- a/app/views/notify/reassigned_merge_request_email.text.erb +++ b/app/views/notify/reassigned_merge_request_email.text.erb @@ -1,4 +1,4 @@ -Reassigned Merge Request <%= @merge_request.iid %> +Reassigned Merge Request #<%= @merge_request.iid %> <%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %> diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..d0b30c08338d43ee2047ee82b6323fcd8d42015f --- /dev/null +++ b/app/views/notify/repository_push_email.html.haml @@ -0,0 +1,23 @@ +%h3 #{@author.name} pushed to #{@branch} at #{@project.name_with_namespace} + +%h4 Commits: + +%ul + - @commits.each do |commit| + %li + #{commit.short_id} - #{commit.title} + +%h4 Diff: +- @diffs.each do |diff| + %li + %strong + - if diff.old_path == diff.new_path + = diff.new_path + - elsif diff.new_path && diff.old_path + #{diff.old_path} → #{diff.new_path} + - else + = diff.new_path || diff.old_path + %hr + %pre + = diff.diff + %br diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml new file mode 100644 index 0000000000000000000000000000000000000000..6718ca683598c104efa846bc1ab60429ca7b7f65 --- /dev/null +++ b/app/views/notify/repository_push_email.text.haml @@ -0,0 +1,20 @@ +#{@author.name} pushed to #{@branch} at #{@project.name_with_namespace} + +\ +Commits: +- @commits.each do |commit| + #{commit.short_id} - #{truncate(commit.title, length: 40)} +\ +\ +Diff: +- @diffs.each do |diff| + \ + \===================================== + - if diff.old_path == diff.new_path + = diff.new_path + - elsif diff.new_path && diff.old_path + #{diff.old_path} → #{diff.new_path} + - else + = diff.new_path || diff.old_path + \===================================== + = diff.diff diff --git a/app/views/profiles/account.html.haml b/app/views/profiles/account.html.haml deleted file mode 100644 index 42c7ec051cbbdd05ad216027483c2efefe9ad1c6..0000000000000000000000000000000000000000 --- a/app/views/profiles/account.html.haml +++ /dev/null @@ -1,141 +0,0 @@ -%h3.page-title - Account settings -%p.light - You can change your password, username and private token here. - - if current_user.ldap_user? - Some options are unavailable for LDAP accounts -%hr - - -.row - .span2 - %ul.nav.nav-pills.nav-stacked.nav-stacked-menu - %li.active - = link_to '#tab-token', 'data-toggle' => 'tab' do - Private Token - %li - = link_to '#tab-password', 'data-toggle' => 'tab' do - Password - - - if show_profile_social_tab? - %li - = link_to '#tab-social', 'data-toggle' => 'tab' do - Social Accounts - - - if show_profile_username_tab? - %li - = link_to '#tab-username', 'data-toggle' => 'tab' do - Change Username - - - if show_profile_remove_tab? - %li - = link_to '#tab-remove', 'data-toggle' => 'tab' do - Remove Account - .span10 - .tab-content - .tab-pane.active#tab-token - %fieldset.update-token - %legend - Private token - %span.cred.pull-right - keep it secret! - %div - = form_for @user, url: reset_private_token_profile_path, method: :put do |f| - .data - %p.slead - Your private token is used to access application resources without authentication. - %br - It can be used for atom feeds or the API. - %p.cgray - - if current_user.private_token - = text_field_tag "token", current_user.private_token, class: "input-xxlarge large_text input-xpadding" - = f.submit 'Reset', confirm: "Are you sure?", class: "btn btn-primary btn-build-token" - - else - %span You don`t have one yet. Click generate to fix it. - = f.submit 'Generate', class: "btn success btn-build-token" - - .tab-pane#tab-password - %fieldset.update-password - %legend Password - - if current_user.ldap_user? - %h3.nothing_here_message Not available for LDAP user - - else - = form_for @user, url: update_password_profile_path, method: :put do |f| - %div - %p.slead - You must provide current password in order to change it. - %br - After a successful password update you will be redirected to login page where you should login with your new password - -if @user.errors.any? - .alert.alert-error - %ul - - @user.errors.full_messages.each do |msg| - %li= msg - .control-group - = f.label :current_password, class: 'cgreen' - .controls= f.password_field :current_password, required: true - .control-group - = f.label :password, 'New password' - .controls= f.password_field :password, required: true - .control-group - = f.label :password_confirmation - .controls - = f.password_field :password_confirmation, required: true - .control-group - .controls - = f.submit 'Save password', class: "btn btn-save" - - - if show_profile_social_tab? - .tab-pane#tab-social - %fieldset - %legend Social Accounts - .oauth_select_holder - %p.hint Tip: Click on icon to activate signin with one of the following services - - enabled_social_providers.each do |provider| - %span{class: oauth_active_class(provider) } - = link_to authbutton(provider, 32), omniauth_authorize_path(User, provider) - - - if show_profile_username_tab? - .tab-pane#tab-username - %fieldset.update-username - %legend - Username - %small.cred.pull-right - Changing your username can have unintended side effects! - = form_for @user, url: update_username_profile_path, method: :put, remote: true do |f| - %div - .control-group - = f.label :username - .controls - = f.text_field :username, required: true - - %span.loading-gif.hide= image_tag "ajax_loader.gif" - %span.update-success.cgreen.hide - %i.icon-ok - Saved - %span.update-failed.cred.hide - %i.icon-remove - Failed - %ul.cred - %li This will change the web URL for personal projects. - %li This will change the git path to repositories for personal projects. - .controls - = f.submit 'Save username', class: "btn btn-save" - - - if show_profile_remove_tab? - .tab-pane#tab-remove - %fieldset.remove-account - %legend - Remove account - %div - %p Deleting an account has the following effects: - %ul - %li All user content like authored issues, snippets, comments will be removed - - rp = current_user.personal_projects.count - - unless rp.zero? - %li #{pluralize rp, 'personal project'} will be removed and cannot be restored - - if current_user.solo_owned_groups.present? - %li - Next groups will be abandoned. You should transfer or remove them: - %strong #{current_user.solo_owned_groups.map(&:name).join(', ')} - = link_to 'Delete account', user_registration_path, confirm: "REMOVE #{current_user.name}? Are you sure?", method: :delete, class: "btn btn-remove" diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..c60e1ca9297463bc9b00141dd40aa1b5307cd2cb --- /dev/null +++ b/app/views/profiles/accounts/show.html.haml @@ -0,0 +1,77 @@ +%h3.page-title + Account settings +%p.light + You can change your username and private token here. + - if current_user.ldap_user? + Some options are unavailable for LDAP accounts +%hr + + +.account-page + %fieldset.update-token + %legend + Private token + %div + = form_for @user, url: reset_private_token_profile_path, method: :put do |f| + .data + %p + Your private token is used to access application resources without authentication. + %br + It can be used for atom feeds or the API. + %span.cred + Keep it secret! + + %p.cgray + - if current_user.private_token + = text_field_tag "token", current_user.private_token, class: "form-control" + %div + = f.submit 'Reset', data: { confirm: "Are you sure?" }, class: "btn btn-primary btn-build-token" + - else + %span You don`t have one yet. Click generate to fix it. + = f.submit 'Generate', class: "btn success btn-build-token" + + + - if show_profile_social_tab? + %fieldset + %legend Social Accounts + .oauth_select_holder.append-bottom-10 + %p Click on icon to activate signin with one of the following services + - enabled_social_providers.each do |provider| + %span{class: oauth_active_class(provider) } + = link_to authbutton(provider, 32), omniauth_authorize_path(User, provider) + + - if show_profile_username_tab? + %fieldset.update-username + %legend + Username + = form_for @user, url: update_username_profile_path, method: :put, remote: true do |f| + %p + Changing your username will change path to all personal projects! + %div + = f.text_field :username, required: true, class: 'form-control' + + .loading-gif.hide + %p + %i.icon-spinner.icon-spin + Saving new username + %p.light + = user_url(@user) + %div + = f.submit 'Save username', class: "btn btn-save" + + - if show_profile_remove_tab? + %fieldset.remove-account + %legend + Remove account + %div + %p Deleting an account has the following effects: + %ul + %li All user content like authored issues, snippets, comments will be removed + - rp = current_user.personal_projects.count + - unless rp.zero? + %li #{pluralize rp, 'personal project'} will be removed and cannot be restored + - if current_user.solo_owned_groups.present? + %li + The following groups will be abandoned. You should transfer or remove them: + %strong #{current_user.solo_owned_groups.map(&:name).join(', ')} + = link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove" diff --git a/app/views/profiles/groups/index.html.haml b/app/views/profiles/groups/index.html.haml index 6fc27be5db4399d051869fc1100371d79f113b3b..1d24e636bf41d3338d461ab210d1045c068f3d9e 100644 --- a/app/views/profiles/groups/index.html.haml +++ b/app/views/profiles/groups/index.html.haml @@ -22,7 +22,7 @@ %i.icon-cogs Settings - = link_to leave_profile_group_path(group), confirm: "Are you sure you want to leave #{group.name} group?", method: :delete, class: "btn-small btn grouped", title: 'Remove user from group' do + = link_to leave_profile_group_path(group), data: { confirm: "Are you sure you want to leave #{group.name} group?"}, method: :delete, class: "btn-small btn grouped", title: 'Remove user from group' do %i.icon-signout Leave diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml index 158979c0ee5eae93e425bc9a7e113379961e0360..f905417f0e2bf905705b939f19fe3235decc51a6 100644 --- a/app/views/profiles/keys/_form.html.haml +++ b/app/views/profiles/keys/_form.html.haml @@ -1,20 +1,18 @@ %div - = form_for [:profile, @key] do |f| + = form_for [:profile, @key], html: { class: 'form-horizontal' } do |f| - if @key.errors.any? - .alert.alert-error + .alert.alert-danger %ul - @key.errors.full_messages.each do |msg| %li= msg - .control-group - = f.label :title - .controls= f.text_field :title, class: "input-xlarge" - .control-group - = f.label :key - .controls - %p.light - Paste your public key here. Read more about how to generate a key on #{link_to "the SSH help page", help_ssh_path}. - = f.text_area :key, class: "input-xxlarge thin_area" + .form-group + = f.label :title, class: 'control-label' + .col-sm-10= f.text_field :title, class: "form-control" + .form-group + = f.label :key, class: 'control-label' + .col-sm-10 + = f.text_area :key, class: "form-control", rows: 8 .form-actions diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml index d0a3fe32c35847e63c4b5a79d20e8c456852b9fb..81411a7565e1fe042fb2b5e199dbeae835c45e0c 100644 --- a/app/views/profiles/keys/_key.html.haml +++ b/app/views/profiles/keys/_key.html.haml @@ -4,8 +4,6 @@ %span (#{key.fingerprint}) %span.cgray - added - = time_ago_in_words(key.created_at) - ago + added #{time_ago_with_tooltip(key.created_at)} - = link_to 'Remove', profile_key_path(key), confirm: 'Are you sure?', method: :delete, class: "btn btn-small btn-remove delete-key pull-right" + = link_to 'Remove', profile_key_path(key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-small btn-remove delete-key pull-right" diff --git a/app/views/profiles/keys/new.html.haml b/app/views/profiles/keys/new.html.haml index f1b8fe08d5c42d55b4782c0bf173fc87175bac69..3d5a947948d5ff3628a20add24561ce7b48e111f 100644 --- a/app/views/profiles/keys/new.html.haml +++ b/app/views/profiles/keys/new.html.haml @@ -1,4 +1,6 @@ %h3.page-title Add an SSH Key +%p.light + Paste your public key here. Read more about how to generate a key on #{link_to "the SSH help page", help_ssh_path}. %hr = render 'form' diff --git a/app/views/profiles/keys/show.html.haml b/app/views/profiles/keys/show.html.haml index b736ab17087a9712cb70ee7be7c7114f27f7cd11..b6724a7cb5d50ef17bd2919e255906d00f2a4957 100644 --- a/app/views/profiles/keys/show.html.haml +++ b/app/views/profiles/keys/show.html.haml @@ -1,5 +1,5 @@ .row - .span4 + .col-md-4 .ui-box .title SSH Key @@ -8,10 +8,10 @@ %span.light Title: %strong= @key.title %li - %span.light Created at: + %span.light Created on: %strong= @key.created_at.stamp("Aug 21, 2011") - .span8 + .col-md-8 %p %span.light Fingerprint: %strong= @key.fingerprint @@ -19,4 +19,4 @@ = @key.key .pull-right - = link_to 'Remove', profile_key_path(@key), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove delete-key" + = link_to 'Remove', profile_key_path(@key), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key" diff --git a/app/views/profiles/notifications/_settings.html.haml b/app/views/profiles/notifications/_settings.html.haml index 5f62c8099d0fc6e1e758d17e4c245c3ef7ad6d2e..d123b8f9407a98aeb496d5c2f3d3bf96881365a5 100644 --- a/app/views/profiles/notifications/_settings.html.haml +++ b/app/views/profiles/notifications/_settings.html.haml @@ -1,6 +1,6 @@ %li .row - .span4 + .col-sm-4 %span = notification_icon(notification) @@ -8,24 +8,24 @@ = link_to membership.group.name, membership.group - else = link_to_project(membership.project) - .span7 + .col-sm-8 = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do = hidden_field_tag :notification_type, type, id: dom_id(membership, 'notification_type') = hidden_field_tag :notification_id, membership.id, id: dom_id(membership, 'notification_id') - = label_tag do + = label_tag nil, class: 'radio-inline' do = radio_button_tag :notification_level, Notification::N_GLOBAL, notification.global?, id: dom_id(membership, 'notification_level'), class: 'trigger-submit' %span Use global setting - = label_tag do + = label_tag nil, class: 'radio-inline' do = radio_button_tag :notification_level, Notification::N_DISABLED, notification.disabled?, id: dom_id(membership, 'notification_level'), class: 'trigger-submit' %span Disabled - = label_tag do + = label_tag nil, class: 'radio-inline' do = radio_button_tag :notification_level, Notification::N_PARTICIPATING, notification.participating?, id: dom_id(membership, 'notification_level'), class: 'trigger-submit' %span Participating - = label_tag do + = label_tag nil, class: 'radio-inline' do = radio_button_tag :notification_level, Notification::N_WATCH, notification.watch?, id: dom_id(membership, 'notification_level'), class: 'trigger-submit' %span Watch diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 8353b2f5f2306448c821f3a2761e2a6650415c89..878d7f774303ed6f8511334f6648fec9fbec7038 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -18,23 +18,23 @@ – You will receive all notifications from projects in which you participate .row - .span4 + .col-sm-4 %h4 = notification_icon(@notification) Global setting - .span7 + .col-sm-8 = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do = hidden_field_tag :notification_type, 'global' - = label_tag do + = label_tag nil, class: 'radio-inline' do = radio_button_tag :notification_level, Notification::N_DISABLED, @notification.disabled?, class: 'trigger-submit' %span Disabled - = label_tag do + = label_tag nil, class: 'radio-inline' do = radio_button_tag :notification_level, Notification::N_PARTICIPATING, @notification.participating?, class: 'trigger-submit' %span Participating - = label_tag do + = label_tag nil, class: 'radio-inline' do = radio_button_tag :notification_level, Notification::N_WATCH, @notification.watch?, class: 'trigger-submit' %span Watch diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..2a7d317aa3e00eb787b4bde7d00a9fbb3838b413 --- /dev/null +++ b/app/views/profiles/passwords/edit.html.haml @@ -0,0 +1,33 @@ +%h3.page-title Password +%p.light + Change your password or recover your current one. +%hr +.update-password + = form_for @user, url: profile_password_path, method: :put, html: { class: 'form-horizontal' } do |f| + %div + %p.slead + You must provide current password in order to change it. + %br + After a successful password update you will be redirected to login page where you should login with your new password + -if @user.errors.any? + .alert.alert-danger + %ul + - @user.errors.full_messages.each do |msg| + %li= msg + .form-group + = f.label :current_password, class: 'control-label' + .col-sm-10 + = f.password_field :current_password, required: true, class: 'form-control' + %div + = link_to "Forgot your password?", reset_profile_password_path, method: :put + + .form-group + = f.label :password, 'New password', class: 'control-label' + .col-sm-10 + = f.password_field :password, required: true, class: 'form-control' + .form-group + = f.label :password_confirmation, class: 'control-label' + .col-sm-10 + = f.password_field :password_confirmation, required: true, class: 'form-control' + .form-actions + = f.submit 'Save password', class: "btn btn-save" diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml index a4e7dadd16a5ef3bef9881109e0b839db18e9aab..f333879cf6ce88da69bba3a5fd9d0aa9e4e4cf82 100644 --- a/app/views/profiles/passwords/new.html.haml +++ b/app/views/profiles/passwords/new.html.haml @@ -5,18 +5,18 @@ %br After successful password update you will be redirected to login screen -if @user.errors.any? - .alert.alert-error + .alert.alert-danger %ul - @user.errors.full_messages.each do |msg| %li= msg - .control-group + .form-group = f.label :password - .controls= f.password_field :password, required: true - .control-group + .col-sm-10= f.password_field :password, required: true + .form-group = f.label :password_confirmation - .controls + .col-sm-10 = f.password_field :password_confirmation, required: true - .control-group - .controls + .form-group + .col-sm-10 = f.submit 'Set new password', class: "btn btn-create" diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 25bf7912f1eb8d1f4e2ffd1013ecd588a4a29361..523a07db4001a8fe0e933c4f855f4be80dd79d73 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -1,90 +1,86 @@ -= image_tag gravatar_icon(@user.email, 60), alt: '', class: 'avatar s60' %h3.page-title - = @user.name - %br - %small - = @user.email - - .pull-right - = link_to destroy_user_session_path, class: "logout", method: :delete do - %small - %i.icon-signout - Logout + Profile settings +%p.light + This information appears on your profile. + - if current_user.ldap_user? + Some options are unavailable for LDAP accounts %hr -= form_for @user, url: profile_path, method: :put, html: { class: "edit_user form-horizontal" } do |f| + + += form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit_user form-horizontal" }, authenticity_token: true do |f| -if @user.errors.any? - %div.alert.alert-error + %div.alert.alert-danger %ul - @user.errors.full_messages.each do |msg| %li= msg .row - .span7 - .control-group + .col-md-7 + .form-group = f.label :name, class: "control-label" - .controls - = f.text_field :name, class: "input-xlarge", required: true + .col-sm-10 + = f.text_field :name, class: "form-control", required: true %span.help-block Enter your name, so people you know can recognize you. - .control-group + + .form-group = f.label :email, class: "control-label" - .controls - = f.text_field :email, class: "input-xlarge", required: true - %span.help-block We also use email for avatar detection. - .control-group + .col-sm-10 + - if @user.ldap_user? + = f.text_field :email, class: "form-control", required: true, readonly: true + %span.help-block.light + Email is read-only for LDAP user + - else + = f.text_field :email, class: "form-control", required: true + - if @user.unconfirmed_email.present? + %span.help-block + We sent confirmation email to + %strong #{@user.unconfirmed_email} + - else + %span.help-block We also use email for avatar detection if no avatar is uploaded. + .form-group = f.label :skype, class: "control-label" - .controls= f.text_field :skype, class: "input-xlarge" - .control-group + .col-sm-10= f.text_field :skype, class: "form-control" + .form-group = f.label :linkedin, class: "control-label" - .controls= f.text_field :linkedin, class: "input-xlarge" - .control-group + .col-sm-10= f.text_field :linkedin, class: "form-control" + .form-group = f.label :twitter, class: "control-label" - .controls= f.text_field :twitter, class: "input-xlarge" - .control-group + .col-sm-10= f.text_field :twitter, class: "form-control" + .form-group + = f.label :website_url, 'Website', class: "control-label" + .col-sm-10= f.text_field :website_url, class: "form-control" + .form-group = f.label :bio, class: "control-label" - .controls - = f.text_area :bio, rows: 6, class: "input-xlarge", maxlength: 250 + .col-sm-10 + = f.text_area :bio, rows: 6, class: "form-control", maxlength: 250 %span.help-block Tell us about yourself in fewer than 250 characters. - .span5.pull-right - %fieldset.tips - %legend Tips: - %ul - %li - %p You can change your password on the Account page - - if Gitlab.config.gravatar.enabled - %li - %p You can change your avatar at #{link_to "gravatar.com", "http://gravatar.com"} - - - if Gitlab.config.omniauth.enabled && @user.provider? - %li - %p - You can login through #{@user.provider.titleize}! - = link_to "click here to change", account_profile_path - - if current_user.can_create_group? - %li - %p - Need a group for several dependent projects? - = link_to new_group_path, class: "btn btn-tiny" do - Create a group - - unless current_user.projects_limit_left > 100 - %fieldset - %legend - Personal projects: - %small.pull-right - %span= current_user.personal_projects.count - of - %span= current_user.projects_limit - .padded - .progress - .bar{style: "width: #{current_user.projects_limit_percent}%;"} + .col-md-5 + .light-well + = image_tag avatar_icon(@user.email, 160), alt: '', class: 'avatar s160' - %fieldset - %legend - SSH public keys: - %span.pull-right - = link_to pluralize(current_user.keys.count, 'key'), profile_keys_path - .padded - = link_to "Add Public Key", new_profile_key_path, class: "btn btn-small" + .clearfix + .profile-avatar-form-option + %p.light + - if @user.avatar? + You can change your avatar here + %br + or remove the current avatar to revert to #{link_to "gravatar.com", "http://gravatar.com"} + - else + You can upload an avatar here + %br + or change it at #{link_to "gravatar.com", "http://gravatar.com"} + %hr + %a.choose-btn.btn.btn-small.js-choose-user-avatar-button + %i.icon-paper-clip + %span Choose File ... + + %span.file_name.js-avatar-filename File name... + = f.file_field :avatar, class: "js-user-avatar-input hidden" + .light The maximum file size allowed is 100KB. + - if @user.avatar? + %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" .form-actions = f.submit 'Save changes', class: "btn btn-save" diff --git a/app/views/profiles/update_username.js.haml b/app/views/profiles/update_username.js.haml index abd90269c93832e71bc0d79ccf2ab0eef56c6e42..249680bcab6634f7dfd9d7806058053a1a91c9c9 100644 --- a/app/views/profiles/update_username.js.haml +++ b/app/views/profiles/update_username.js.haml @@ -1,6 +1,6 @@ - if @user.valid? :plain - $('.update-username .update-success').show(); + new Flash("Username sucessfully changed", "notice") - else :plain - $('.update-username .update-failed').show(); + new Flash("Username change failed - #{@user.errors.full_messages.first}", "alert") diff --git a/app/views/projects/_clone_panel.html.haml b/app/views/projects/_clone_panel.html.haml deleted file mode 100644 index c2f85e8ebe8329229a9872fcd847b17f4e0cd256..0000000000000000000000000000000000000000 --- a/app/views/projects/_clone_panel.html.haml +++ /dev/null @@ -1,56 +0,0 @@ -.project_clone_panel - .row - .span8 - .form-horizontal= render "shared/clone_panel" - .span3.pull-right - .pull-right - - unless @project.empty_repo? - - if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace - - if current_user.already_forked?(@project) - = link_to project_path(current_user.fork_of(@project)), class: 'btn grouped disabled' do - %i.icon-code-fork - Forked - - else - = link_to fork_project_path(@project), title: "Fork", class: "btn grouped", method: "POST" do - %i.icon-code-fork - Fork - - if can? current_user, :download_code, @project - = link_to archive_project_repository_path(@project), class: "btn grouped" do - %i.icon-download-alt - %span.only-wide Download - - - if current_user - .dropdown.pull-right - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %i.icon-plus-sign-alt - %span.only-wide New - %b.caret - %ul.dropdown-menu - - if @project.issues_enabled && can?(current_user, :write_issue, @project) - %li - = link_to url_for_new_issue, title: "New Issue" do - Issue - - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project) - %li - = link_to new_project_merge_request_path(@project), title: "New Merge Request" do - Merge Request - - if @project.snippets_enabled && can?(current_user, :write_snippet, @project) - %li - = link_to new_project_snippet_path(@project), title: "New Snippet" do - Snippet - - if can? current_user, :push_code, @project - %li.divider - %li - = link_to new_project_branch_path(@project) do - %i.icon-code-fork - Git branch - %li - = link_to new_project_tag_path(@project) do - %i.icon-tag - Git tag - - - if can?(current_user, :admin_team_member, @project) - %li.divider - %li - = link_to new_project_team_member_path(@project), title: "New project member" do - Project member diff --git a/app/views/projects/_dropdown.html.haml b/app/views/projects/_dropdown.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..e283bd2bf1d951858dcd9976a27d0e972b78e2e6 --- /dev/null +++ b/app/views/projects/_dropdown.html.haml @@ -0,0 +1,33 @@ +- if current_user + .dropdown.pull-right + %a.dropdown-toggle.btn.btn-new{href: '#', "data-toggle" => "dropdown"} + %i.icon-reorder + %ul.dropdown-menu + - if @project.issues_enabled && can?(current_user, :write_issue, @project) + %li + = link_to url_for_new_issue, title: "New Issue" do + New issue + - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project) + %li + = link_to new_project_merge_request_path(@project), title: "New Merge Request" do + New merge request + - if @project.snippets_enabled && can?(current_user, :write_snippet, @project) + %li + = link_to new_project_snippet_path(@project), title: "New Snippet" do + New snippet + - if can?(current_user, :admin_team_member, @project) + %li + = link_to new_project_team_member_path(@project), title: "New project member" do + New project member + - if can? current_user, :push_code, @project + %li.divider + %li + = link_to new_project_branch_path(@project) do + %i.icon-code-fork + Git branch + %li + = link_to new_project_tag_path(@project) do + %i.icon-tag + Git tag + + diff --git a/app/views/projects/_errors.html.haml b/app/views/projects/_errors.html.haml index bb9759353a3a36c09f4064f93fd4e716f34bde7f..7c8bb33ed7edc4f3d868742ec68f1a16005d3aab 100644 --- a/app/views/projects/_errors.html.haml +++ b/app/views/projects/_errors.html.haml @@ -1,4 +1,4 @@ - if @project.errors.any? - .alert.alert-error + .alert.alert-danger %button{ type: "button", class: "close", "data-dismiss" => "alert"} × = @project.errors.full_messages.first diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..48e6ea91d949ba2df90583bf8ca17f790db26a87 --- /dev/null +++ b/app/views/projects/_home_panel.html.haml @@ -0,0 +1,31 @@ +- empty_repo = @project.empty_repo? +.project-home-panel{:class => ("empty-project" if empty_repo)} + .visibility-level-label.has_tooltip{'data-title' => "#{visibility_level_label(@project.visibility_level)} project" } + = visibility_level_icon(@project.visibility_level) + .row + .col-sm-6 + %h4.project-home-title + = @project.name_with_namespace + + .col-sm-6 + - unless empty_repo + .project-home-dropdown + = render "dropdown" + = render "shared/clone_panel" + + .project-home-extra.row + .col-md-8 + .project-home-desc + - if @project.description.present? + = @project.description + - if can?(current_user, :admin_project, @project) + – + %strong= link_to 'Edit', edit_project_path + + - unless empty_repo + .col-md-4 + .project-home-links + = link_to pluralize(@repository.round_commit_count, 'commit'), project_commits_path(@project, @ref || @repository.root_ref) + = link_to pluralize(@repository.branch_names.count, 'branch'), project_branches_path(@project) + = link_to pluralize(@repository.tag_names.count, 'tag'), project_tags_path(@project) + %span.light.prepend-left-20= repository_size diff --git a/app/views/projects/_settings_nav.html.haml b/app/views/projects/_settings_nav.html.haml index f59e2871aa3a8d54ac3cbd431d223b5d359e9ce5..e4cfabc310024472df210180bc197b85011b0fdb 100644 --- a/app/views/projects/_settings_nav.html.haml +++ b/app/views/projects/_settings_nav.html.haml @@ -1,11 +1,9 @@ -%ul.nav.nav-pills.nav-stacked.nav-stacked-menu +%ul.nav.nav-pills.nav-stacked.nav-stacked-menu.append-bottom-20 = nav_link(path: 'projects#edit') do = link_to edit_project_path(@project), class: "stat-tab tab " do - %i.icon-edit Edit Project = nav_link(controller: [:team_members, :teams]) do = link_to project_team_index_path(@project), class: "team-tab tab" do - %i.icon-group Members = nav_link(controller: :deploy_keys) do = link_to project_deploy_keys_path(@project) do diff --git a/app/views/projects/_visibility_level.html.haml b/app/views/projects/_visibility_level.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..eb38fce0ecf0dee042a49ca13757e6345415992e --- /dev/null +++ b/app/views/projects/_visibility_level.html.haml @@ -0,0 +1,27 @@ +.form-group.project-visibility-level-holder + = f.label :visibility_level, class: 'control-label' do + Visibility Level + = link_to "(?)", help_public_access_path + .col-sm-10 + - if can_change_visibility_level + - Gitlab::VisibilityLevel.values.each do |level| + .radio + - restricted = restricted_visibility_levels.include?(level) + = f.radio_button :visibility_level, level, checked: (visibility_level == level), disabled: restricted + = label :project_visibility_level, level do + = visibility_level_icon(level) + .option-title + = visibility_level_label(level) + .option-descr + = visibility_level_description(level) + - unless restricted_visibility_levels.empty? + .col-sm-10 + %span.info + Some visibility level settings have been restricted by the administrator. + - else + .col-sm-10 + %span.info + = visibility_level_icon(visibility_level) + %strong + = visibility_level_label(visibility_level) + .light= visibility_level_description(visibility_level) diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml index 5641c528a4fc7b56d47fdda294d465832058806a..2f82bfe52f32122bba85be41760e395f1319bb36 100644 --- a/app/views/projects/blob/_actions.html.haml +++ b/app/views/projects/blob/_actions.html.haml @@ -1,7 +1,10 @@ .btn-group.tree-btn-group -# only show edit link for text files - if @blob.text? - = link_to "edit", project_edit_tree_path(@project, @id), class: "btn btn-small", disabled: !allowed_tree_edit? + - if allowed_tree_edit? + = link_to "edit", project_edit_tree_path(@project, @id), class: "btn btn-small" + - else + %span.btn.btn-small.disabled edit = link_to "raw", project_raw_path(@project, @id), class: "btn btn-small", target: "_blank" -# only show normal/blame view links for text files - if @blob.text? @@ -10,3 +13,7 @@ - else = link_to "blame", project_blame_path(@project, @id), class: "btn btn-small" unless @blob.empty? = link_to "history", project_commits_path(@project, @id), class: "btn btn-small" + + - if allowed_tree_edit? + = link_to '#modal-remove-blob', class: "remove-blob btn btn-small btn-remove", "data-toggle" => "modal" do + remove diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml index 6524aaeef4c418c8449c4da4d14fe83fe6b0d2b0..32ea967105ac2ba9e2a8a509a6e3158d45cbf758 100644 --- a/app/views/projects/blob/_blob.html.haml +++ b/app/views/projects/blob/_blob.html.haml @@ -4,7 +4,6 @@ = link_to project_tree_path(@project, @ref) do = @project.path - tree_breadcrumbs(@tree, 6) do |title, path| - \/ %li - if path - if path.end_with?(@path) diff --git a/app/views/projects/blob/_download.html.haml b/app/views/projects/blob/_download.html.haml index f3da1a2a2196609e2f0f9f406b50f89b83259334..ff317f9020967e356bb81d4e773d411220b2c3ef 100644 --- a/app/views/projects/blob/_download.html.haml +++ b/app/views/projects/blob/_download.html.haml @@ -1,8 +1,7 @@ -.file-content.blob_file +.file-content.blob_file.blob-no-preview %center = link_to project_raw_path(@project, @id) do - %div.padded - %h4 - %i.icon-download-alt - %br - Download (#{number_to_human_size blob.size}) + %h1.light + %i.icon-download-alt + %h4 + Download (#{number_to_human_size blob.size}) diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..6384703671aee38e3b1ce0758d84b691cb96b2d2 --- /dev/null +++ b/app/views/projects/blob/_remove.html.haml @@ -0,0 +1,22 @@ +#modal-remove-blob.modal.hide + .modal-dialog + .modal-content + .modal-header + %a.close{href: "#", "data-dismiss" => "modal"} × + %h3.page-title Remove #{@blob.name} + %p.light + From branch + %strong= @ref + + .modal-body + = form_tag project_blob_path(@project, @id), method: :delete, class: 'form-horizontal' do + .form-group.commit_message-group + = label_tag 'commit_message', class: "control-label" do + Commit message + .col-sm-10 + = text_area_tag 'commit_message', params[:commit_message], placeholder: "Removed this file because...", required: true, rows: 3, class: 'form-control' + .form-group + .col-sm-2 + .col-sm-10 + = submit_tag 'Remove file', class: 'btn btn-remove' + = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml index bed493d6d8c8e882a892efdd58a57481cb11d191..080a39ab944787ded2f9e6e976b98e9bbb2e3842 100644 --- a/app/views/projects/blob/_text.html.haml +++ b/app/views/projects/blob/_text.html.haml @@ -4,11 +4,10 @@ = markdown(blob.data) - elsif markup?(blob.name) .file-content.wiki - = raw GitHub::Markup.render(blob.name, blob.data) + = render_markup(blob.name, blob.data) - else .file-content.code - unless blob.empty? - %div{class: user_color_scheme_class} - = raw blob.colorize(formatter: :gitlab) + = render 'shared/file_hljs', blob: blob - else %p.nothing_here_message Empty file diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index d96595bc7f08b8ef479340a50a0b969a0fec7d10..56220e520f3b376d679ebd6419e67e28131151de 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -2,3 +2,6 @@ = render 'shared/ref_switcher', destination: 'blob', path: @path %div#tree-holder.tree-holder = render 'blob', blob: @blob + +- if allowed_tree_edit? + = render 'projects/blob/remove' diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index b83925257918ee631f1d464824941a701c16e8f0..40b6fc5d72ec3b297fbbe0bbe038c4e2f95035ca 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -1,4 +1,4 @@ -- commit = Commit.new(Gitlab::Git::Commit.new(branch.commit)) +- commit = @repository.commit(branch.target) %li %h4 = link_to project_commits_path(@project, branch.name) do @@ -10,23 +10,23 @@ %i.icon-lock .pull-right - if can?(current_user, :download_code, @project) - = link_to archive_project_repository_path(@project, ref: branch.name), class: 'btn grouped btn-small' do - %i.icon-download-alt - Download + = render 'projects/repositories/download_archive', ref: branch.name, btn_class: 'grouped btn-group-small' = link_to project_compare_index_path(@project, from: branch.name, to: branch.name), class: 'btn grouped btn-small', title: "Compare" do %i.icon-copy Compare - if can?(current_user, :admin_project, @project) && branch.name != @repository.root_ref - = link_to project_branch_path(@project, branch.name), class: 'btn grouped btn-small remove-row', method: :delete, confirm: 'Removed branch cannot be restored. Are you sure?', remote: true do + = link_to project_branch_path(@project, branch.name), class: 'btn grouped btn-small remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do %i.icon-trash - %p - = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do - = commit.short_id - = image_tag gravatar_icon(commit.author_email), class: "avatar s16", alt: '' - %span.light - = gfm escape_once(truncate(commit.title, length: 40)) - %span - = time_ago_in_words(commit.committed_date) - ago + - if commit + %p + = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do + = commit.short_id + = image_tag avatar_icon(commit.author_email), class: "avatar s16", alt: '' + %span.light + = gfm escape_once(truncate(commit.title, length: 40)) + #{time_ago_with_tooltip(commit.committed_date)} + - else + %p + Cant find HEAD commit for this branch diff --git a/app/views/projects/branches/_filter.html.haml b/app/views/projects/branches/_filter.html.haml index 7ea11a74a2b8ca6f1ea61f2593d03ab7c221410c..7e1478292e01b3deb8c3aaed00d311ded225cbbd 100644 --- a/app/views/projects/branches/_filter.html.haml +++ b/app/views/projects/branches/_filter.html.haml @@ -1,12 +1,22 @@ %ul.nav.nav-pills.nav-stacked = nav_link(path: 'branches#recent') do - = link_to 'Recent', recent_project_branches_path(@project) + = link_to recent_project_branches_path(@project) do + Recent + .pull-right + = @repository.recent_branches.count + = nav_link(path: 'protected_branches#index') do = link_to project_protected_branches_path(@project) do Protected %i.icon-lock + .pull-right + = @project.protected_branches.count + = nav_link(path: 'branches#index') do - = link_to 'All branches', project_branches_path(@project) + = link_to project_branches_path(@project) do + All branches + .pull-right + = @repository.branch_names.count %hr diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 45b9c6c85217926725f179acd92b36445a953708..690df98a2abc116e9d7448979fd1d2c361eb5974 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -1,10 +1,10 @@ = render "projects/commits/head" .row - .span3 + .col-md-3 = render "filter" - .span9 + .col-md-9 - unless @branches.empty? %ul.bordered-list.top-list - @branches.each do |branch| = render "projects/branches/branch", branch: branch - = paginate @branches, theme: 'gitlab' \ No newline at end of file + = paginate @branches, theme: 'gitlab' diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml index 37612d3fd7bc40485d9a65362173fb9fb2413b13..5da2ede2937b44f979c47b7cc686b1dd051c9b91 100644 --- a/app/views/projects/branches/new.html.haml +++ b/app/views/projects/branches/new.html.haml @@ -1,16 +1,15 @@ %h3.page-title %i.icon-code-fork New branch -= form_tag project_branches_path, method: :post do - .control-group += form_tag project_branches_path, method: :post, class: "form-horizontal" do + .form-group = label_tag :branch_name, 'Name for new branch', class: 'control-label' - .controls - = text_field_tag :branch_name, nil, placeholder: 'feature/dashboard', required: true, tabindex: 1 - .control-group + .col-sm-10 + = text_field_tag :branch_name, nil, placeholder: 'enter new branch name', required: true, tabindex: 1, class: 'form-control' + .form-group = label_tag :ref, 'Create from', class: 'control-label' - .controls - = text_field_tag :ref, nil, placeholder: 'master', required: true, tabindex: 2 - .light branch name or commit SHA + .col-sm-10 + = text_field_tag :ref, nil, placeholder: 'existing branch name, tag or commit SHA', required: true, tabindex: 2, class: 'form-control' .form-actions = submit_tag 'Create branch', class: 'btn btn-create', tabindex: 3 = link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel' diff --git a/app/views/projects/branches/recent.html.haml b/app/views/projects/branches/recent.html.haml index 25f416c78f213c80201643aa177a5bc66acaf3c5..37d7919121e7d3132e841e122a476eff53dca2eb 100644 --- a/app/views/projects/branches/recent.html.haml +++ b/app/views/projects/branches/recent.html.haml @@ -1,8 +1,8 @@ = render "projects/commits/head" .row - .span3 + .col-md-3 = render "filter" - .span9 + .col-md-9 %ul.bordered-list.top-list - @branches.each do |branch| - = render "projects/branches/branch", branch: branch \ No newline at end of file + = render "projects/branches/branch", branch: branch diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 1f4934520648586e03e9ce3c7fbfe7c6f2d75428..3d666807cf995f00af2ccaca5fae0ab6ea3fbd93 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -23,22 +23,33 @@ %span.light Authored by %strong = commit_author_link(@commit, avatar: true, size: 24) - %time{title: @commit.authored_date.stamp("Aug 21, 2011 9:23pm")} - #{time_ago_in_words(@commit.authored_date)} ago + #{time_ago_with_tooltip(@commit.authored_date)} - if @commit.different_committer? .commit-info-row %span.light Committed by %strong = commit_committer_link(@commit, avatar: true, size: 24) - %time{title: @commit.committed_date.stamp("Aug 21, 2011 9:23pm")} - #{time_ago_in_words(@commit.committed_date)} ago + #{time_ago_with_tooltip(@commit.committed_date)} .commit-info-row %span.cgray= pluralize(@commit.parents.count, "parent") - @commit.parents.each do |parent| = link_to parent.id[0...10], project_commit_path(@project, parent) +- if @branches.any? + .commit-info-row + %span.cgray + Exists in + %span + - branch = commit_default_branch(@project, @branches) + = link_to(branch, project_tree_path(@project, branch)) + - if @branches.any? + and in + = link_to("#{pluralize(@branches.count, "other branch")}", "#", class: "js-details-expand") + %span.js-details-contain.hide + = commit_branches_links(@project, @branches) + .commit-box %h3.commit-title = gfm escape_once(@commit.title) diff --git a/app/views/projects/commit/huge_commit.html.haml b/app/views/projects/commit/huge_commit.html.haml index 210d8d9e2073aafc5e3d7d78aa077bc5789f4f28..398ce77142695dc1097e79c8de9f5f62be95c6df 100644 --- a/app/views/projects/commit/huge_commit.html.haml +++ b/app/views/projects/commit/huge_commit.html.haml @@ -1,3 +1,3 @@ = render "projects/commit/commit_box" -.alert.alert-error +.alert.alert-danger %h4 Commit diffs are too big to be displayed diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index d70fd96accdf22d8f5872b5b9073e45a02fa91fb..9772d3ef2ef823a8ed018d8c0054a3c53ba3ef86 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -2,18 +2,17 @@ .commit-row-title = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id" - = link_to_gfm truncate(commit.title, length: 70), project_commit_path(project, commit.id), class: "commit-row-message" + %span.str-truncated + = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message" = link_to "Browse Code »", project_tree_path(project, commit), class: "pull-right" .notes_count - notes = project.notes.for_commit_id(commit.id) - if notes.any? - %span.badge.badge-info + %span.label.label-gray %i.icon-comment = notes.count .commit-row-info = commit_author_link(commit, avatar: true, size: 16) - %time.committed_ago{ datetime: commit.committed_date, title: commit.committed_date.stamp("Aug 21, 2011 9:23pm") } - = time_ago_in_words(commit.committed_date) - ago - + .committed_ago + #{time_ago_with_tooltip(commit.committed_date)} diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml index b6404778073e5bb3ef3dd8d9318ac8d4f314c83b..e3411b62eb64e1388d66ca60ad3d90e7c65456a2 100644 --- a/app/views/projects/commits/_commits.html.haml +++ b/app/views/projects/commits/_commits.html.haml @@ -1,11 +1,11 @@ - @commits.group_by { |c| c.committed_date.to_date }.sort.reverse.each do |day, commits| .row.commits-row - .span2 + .col-md-2 %h4 %i.icon-calendar %span= day.stamp("28 Aug, 2010") %p= pluralize(commits.count, 'commit') - .span10 + .col-md-10 %ul.well-list = render commits, project: @project %hr.lists-separator diff --git a/app/views/projects/commits/_diffs.html.haml b/app/views/projects/commits/_diffs.html.haml index c51f1b6eff50504cb5acd897eb3caa12e6628ebe..a41a89bb9724fc501489c18eacfa3191796cb894 100644 --- a/app/views/projects/commits/_diffs.html.haml +++ b/app/views/projects/commits/_diffs.html.haml @@ -1,6 +1,6 @@ - @suppress_diff ||= @suppress_diff || @force_suppress_diff - if @suppress_diff - .alert.alert-block + .alert.alert-warning %p %strong Warning! This is a large diff. %p @@ -30,6 +30,10 @@ %strong.cgreen #{@commit.stats.additions} additions and %strong.cred #{@commit.stats.deletions} deletions + - if params[:view] == 'parallel' + = link_to "Inline Diff", url_for(view: 'inline'), {id: "commit-diff-viewtype", class: 'btn btn-tiny pull-right'} + - else + = link_to "Side-by-side Diff", url_for(view: 'parallel'), {id: "commit-diff-viewtype", class: 'btn btn-tiny pull-right'} .file-stats = render "projects/commits/diff_head", diffs: diffs @@ -37,16 +41,16 @@ - unless @suppress_diff - diffs.each_with_index do |diff, i| - next if diff.diff.empty? - - file = Gitlab::Git::Blob.new(project.repository, @commit.id, @ref, diff.new_path) - - file = Gitlab::Git::Blob.new(project.repository, @commit.parent_id, @ref, diff.old_path) unless file.exists? - - next unless file.exists? + - file = project.repository.blob_at(@commit.id, diff.new_path) + - file = project.repository.blob_at(@commit.parent_id, diff.old_path) unless file + - next unless file .file{id: "diff-#{i}"} .header - if diff.deleted_file %span= diff.old_path - if @commit.parent_ids.present? - = link_to project_blob_path(project, tree_join(@commit.parent_id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do + = link_to project_blob_path(project, tree_join(@commit.parent_id, diff.new_path)), { class: 'btn btn-small view-file' } do View file @ %span.commit-short-id= @commit.short_id(6) - else @@ -54,7 +58,7 @@ - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}" - = link_to project_blob_path(project, tree_join(@commit.id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do + = link_to project_blob_path(project, tree_join(@commit.id, diff.new_path)), { class: 'btn btn-small view-file' } do View file @ %span.commit-short-id= @commit.short_id(6) @@ -62,9 +66,12 @@ -# Skipp all non non-supported blobs - next unless file.respond_to?('text?') - if file.text? - = render "projects/commits/text_file", diff: diff, index: i + - if params[:view] == 'parallel' + = render "projects/commits/parallel_view", diff: diff, project: project, file: file, index: i + - else + = render "projects/commits/text_file", diff: diff, index: i - elsif file.image? - - old_file = Gitlab::Git::Blob.new(project.repository, @commit.parent_id, @ref, diff.old_path) if @commit.parent_id + - old_file = project.repository.blob_at(@commit.parent_id, diff.old_path) if @commit.parent_id = render "projects/commits/image", diff: diff, old_file: old_file, file: file, index: i - else %p.nothing_here_message No preview for this file type diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index c2da9f273b3ed835986c5d7e4c3eb0dade26b4b8..b9ab27d212cc31b763716f56029cc23ffcff5c7f 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -1,4 +1,4 @@ -%ul.nav.nav-tabs +%ul.nav.nav-tabs.append-bottom-15 %li= render partial: 'shared/ref_switcher', locals: {destination: 'commits'} = nav_link(controller: [:commit, :commits]) do @@ -22,6 +22,6 @@ - if current_user && current_controller?(:commits) && current_user.private_token - %li.pull-right + %li.pull-right.hidden-sm = link_to project_commits_path(@project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Feed" do %i.icon-rss diff --git a/app/views/projects/commits/_image.html.haml b/app/views/projects/commits/_image.html.haml index 73f87289d3db1486de190791dbb764709fd09d21..9a8b7c857e59b71636e4b1fe0edaab3127d2a3d0 100644 --- a/app/views/projects/commits/_image.html.haml +++ b/app/views/projects/commits/_image.html.haml @@ -49,7 +49,7 @@ %img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"} .frame.added %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} - .controls + .col-sm-10 .transparent .drag-track .dragger{:style => "left: 0px;"} @@ -60,4 +60,4 @@ %ul.view-modes-menu %li.two-up{data: {mode: 'two-up'}} 2-up %li.swipe{data: {mode: 'swipe'}} Swipe - %li.onion-skin{data: {mode: 'onion-skin'}} Onion skin \ No newline at end of file + %li.onion-skin{data: {mode: 'onion-skin'}} Onion skin diff --git a/app/views/projects/commits/_inline_commit.html.haml b/app/views/projects/commits/_inline_commit.html.haml index 5be8460e06130ed38abf51f7d1f07e442f99f577..b36369b428584a1fb126ad848bac63c758355db6 100644 --- a/app/views/projects/commits/_inline_commit.html.haml +++ b/app/views/projects/commits/_inline_commit.html.haml @@ -2,8 +2,7 @@ .commit-row-title = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id" - = link_to_gfm truncate(commit.title, length: 40), project_commit_path(project, commit.id), class: "commit-row-message" - %time.committed_ago{ datetime: commit.committed_date, title: commit.committed_date.stamp("Aug 21, 2011 9:23pm") } - = time_ago_in_words(commit.committed_date) - ago - + %span.str-truncated + = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message" + .pull-right + #{time_ago_with_tooltip(commit.committed_date)} diff --git a/app/views/projects/commits/_parallel_view.html.haml b/app/views/projects/commits/_parallel_view.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..3234e9da0ac88fa923ee22fb156ea49cdc5e56db --- /dev/null +++ b/app/views/projects/commits/_parallel_view.html.haml @@ -0,0 +1,75 @@ +/ Side-by-side diff view +- old_file = get_old_file(project, @commit, diff) +- deleted_lines = {} +- added_lines = {} +- each_diff_line(diff, index) do |line, type, line_code, line_new, line_old, raw_line| + - if type == "old" + - deleted_lines[line_old] = { line_code: line_code, type: type, line: line } + - elsif type == "new" + - added_lines[line_new] = { line_code: line_code, type: type, line: line } + +- max_length = old_file.sloc + added_lines.length if old_file +- max_length ||= file.sloc +- offset1 = 0 +- offset2 = 0 + +%div.text-file-parallel + %table{ style: "table-layout: fixed;" } + - max_length.times do |line_index| + - line_index1 = line_index - offset1 + - line_index2 = line_index - offset2 + - deleted_line = deleted_lines[line_index1 + 1] + - added_line = added_lines[line_index2 + 1] + - old_line = old_file.lines[line_index1] if old_file + - new_line = file.lines[line_index2] + + - if deleted_line && added_line + - elsif deleted_line + - new_line = nil + - offset2 += 1 + - elsif added_line + - old_line = nil + - offset1 += 1 + + %tr.line_holder.parallel + - if line_index == 0 && diff.new_file + %td.line_content.parallel= "File was created" + %td.old_line= "" + - elsif deleted_line + %td.line_content{class: "parallel noteable_line old #{deleted_line[:line_code]}", "line_code" => deleted_line[:line_code] }= old_line + %td.old_line.old + = line_index1 + 1 + - if @comments_allowed + =# render "projects/notes/diff_note_link", line_code: deleted_line[:line_code] + - elsif old_line + %td.line_content.parallel= old_line + %td.old_line= line_index1 + 1 + - else + %td.line_content.parallel= "" + %td.old_line= "" + + %td.diff_line= "" + + - if diff.deleted_file && line_index == 0 + %td.new_line= "" + %td.line_content.parallel= "File was deleted" + - elsif added_line + %td.new_line.new + = line_index2 + 1 + - if @comments_allowed + =# render "projects/notes/diff_note_link", line_code: added_line[:line_code] + %td.line_content{class: "parallel noteable_line new #{added_line[:line_code]}", "line_code" => added_line[:line_code] }= new_line + - elsif new_line + %td.new_line= line_index2 + 1 + %td.line_content.parallel= new_line + - else + %td.new_line= "" + %td.line_content.parallel= "" + + - if @reply_allowed + - comments1 = [] + - comments2 = [] + - comments1 = @line_notes.select { |n| n.line_code == deleted_line[:line_code] }.sort_by(&:created_at) if deleted_line + - comments2 = @line_notes.select { |n| n.line_code == added_line[:line_code] }.sort_by(&:created_at) if added_line + - unless comments1.empty? && comments2.empty? + = render "projects/notes/diff_notes_with_reply_parallel", notes1: comments1, notes2: comments2, line1: deleted_line, line2: added_line \ No newline at end of file diff --git a/app/views/projects/commits/_text_file.html.haml b/app/views/projects/commits/_text_file.html.haml index c724213878ac5d949fb87bd2820a36413876a2a9..c827d96d855847b8738c65a1546a5121f421c2a6 100644 --- a/app/views/projects/commits/_text_file.html.haml +++ b/app/views/projects/commits/_text_file.html.haml @@ -21,3 +21,4 @@ - comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at) - unless comments.empty? = render "projects/notes/diff_notes_with_reply", notes: comments, line: line + diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder index 46f9838e84ae8edad8be05a568401b761c1061ac..32c82edb2489bc47dd721c1051c4a9dc8a2d951f 100644 --- a/app/views/projects/commits/show.atom.builder +++ b/app/views/projects/commits/show.atom.builder @@ -3,7 +3,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.title "Recent commits to #{@project.name}:#{@ref}" xml.link :href => project_commits_url(@project, @ref, format: :atom), :rel => "self", :type => "application/atom+xml" xml.link :href => project_commits_url(@project, @ref), :rel => "alternate", :type => "text/html" - xml.id project_commits_url(@project) + xml.id project_commits_url(@project, @ref) xml.updated @commits.first.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ") if @commits.any? @commits.each do |commit| @@ -12,7 +12,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.link :href => project_commit_url(@project, :id => commit.id) xml.title truncate(commit.title, :length => 80) xml.updated commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(commit.author_email) + xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(commit.author_email) xml.author do |author| xml.name commit.author_name xml.email commit.author_email diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 723c5a1c34077d2e889ad21414da7257c91c5a0a..3a4f304a2554a62470487c2dd447c93e5620204c 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -9,7 +9,7 @@ %div{id: dom_id(@project)} #commits-list= render "commits" .clear -.loading{ style: "display:none;"} += spinner - if @commits.count == @limit :javascript diff --git a/app/views/projects/commits/show.js.haml b/app/views/projects/commits/show.js.haml deleted file mode 100644 index 045c9dd83d705dc839e10e3f10bbb9ebda99f364..0000000000000000000000000000000000000000 --- a/app/views/projects/commits/show.js.haml +++ /dev/null @@ -1,3 +0,0 @@ -:plain - CommitsList.append(#{@commits.count}, "#{escape_javascript(render('projects/commits/commits'))}"); - diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index ca1ea4073e6ea4b51fc453d1241a93cb27d20651..da6157cf1b615789e28868b91856a774c0b8c0ef 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -1,21 +1,21 @@ -= form_tag project_compare_index_path(@project), method: :post do - .clearfix - .pull-left - - if params[:to] && params[:from] - = link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has_tooltip', title: 'Switch base of comparison'} - .input-prepend - %span.add-on.input-xpadding from - = text_field_tag :from, params[:from], class: "span3 input-xpadding" - = "..." - .input-prepend - %span.add-on.input-xpadding to - = text_field_tag :to, params[:to], class: "span3 input-xpadding" - .pull-left - - = submit_tag "Compare", class: "btn btn-create commits-compare-btn" - - if compare_to_mr_button? - = link_to compare_mr_path, class: 'prepend-left-10' do - %strong Make a merge request += form_tag project_compare_index_path(@project), method: :post, class: 'form-inline' do + .clearfix.append-bottom-20 + - if params[:to] && params[:from] + = link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has_tooltip', title: 'Switch base of comparison'} + .form-group + .input-group.inline-input-group + %span.input-group-addon from + = text_field_tag :from, params[:from], class: "form-control" + = "..." + .form-group + .input-group.inline-input-group + %span.input-group-addon to + = text_field_tag :to, params[:to], class: "form-control" + + = submit_tag "Compare", class: "btn btn-create commits-compare-btn" + - if compare_to_mr_button? + = link_to compare_mr_path, class: 'prepend-left-10 btn' do + %strong Make a merge request :javascript diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index 4be62d3691742dcfad2c00f6f616b99117d6b4fa..9bd498553697169a56881c5a7dc3b224f022ad48 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -5,21 +5,32 @@ = render "form" -- if @commits.size > 100 - .alert.alert-block - %p - %strong Warning! This comparison includes more than 100 commits. - %p To preserve performance the line diff is not shown. - - if @commits.present? %div.ui-box .title Commits (#{@commits.count}) - %ul.well-list= render Commit.decorate(@commits), project: @project + - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE + %ul.well-list + - Commit.decorate(@commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE)).each do |commit| + = render "projects/commits/inline_commit", commit: commit, project: @project + %li.warning-row.unstyled + other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues. + - else + %ul.well-list= render Commit.decorate(@commits), project: @project - - unless @diffs.empty? - %h4 Diff + %h4 Diff + - if @diffs.present? = render "projects/commits/diffs", diffs: @diffs, project: @project + - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE + .bs-callout.bs-callout-danger + %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits. + %p To preserve performance the line diff is not shown. + - elsif @timeout + .bs-callout.bs-callout-danger + %h4 Diff for this comparison is extremely large. + %p Use command line to browse diff for this comparison. + + - else .light-well %center diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml index 45f80ecd5566779148ad604829e265f57b130a1e..2b4f36fb4b89c64b5f8dd3dcbfd917081458a75d 100644 --- a/app/views/projects/deploy_keys/_deploy_key.html.haml +++ b/app/views/projects/deploy_keys/_deploy_key.html.haml @@ -10,7 +10,7 @@ %i.icon-off Disable - else - = link_to 'Remove', project_deploy_key_path(@project, deploy_key), confirm: 'You are going to remove deploy key. Are you sure?', method: :delete, class: "btn btn-remove delete-key btn-small pull-right" + = link_to 'Remove', project_deploy_key_path(@project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :delete, class: "btn btn-remove delete-key btn-small pull-right" = link_to project_deploy_key_path(deploy_key.projects.include?(@project) ? @project : deploy_key.projects.first, deploy_key) do @@ -19,7 +19,6 @@ %p.light.prepend-top-10 - deploy_key.projects.map(&:name_with_namespace).each do |project_name| - %span.label= project_name + %span.label.label-gray.deploy-project-label= project_name %small.pull-right - Created #{time_ago_in_words(deploy_key.created_at)} ago - + Created #{time_ago_with_tooltip(deploy_key.created_at)} diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml index d49b2204537cbcf13719b8fe46916bbfc54fb9bc..ebb92b36b47d791e3bddf327bd258da912e4fc1f 100644 --- a/app/views/projects/deploy_keys/_form.html.haml +++ b/app/views/projects/deploy_keys/_form.html.haml @@ -1,21 +1,21 @@ %div - = form_for [@project, @key], url: project_deploy_keys_path do |f| + = form_for [@project, @key], url: project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal' } do |f| -if @key.errors.any? - .alert.alert-error + .alert.alert-danger %ul - @key.errors.full_messages.each do |msg| %li= msg - .control-group - = f.label :title - .controls= f.text_field :title, class: 'input-xlarge' - .control-group - = f.label :key - .controls + .form-group + = f.label :title, class: "control-label" + .col-sm-10= f.text_field :title, class: 'form-control' + .form-group + = f.label :key, class: "control-label" + .col-sm-10 %p.light Paste a machine public key here. Read more about how to generate it = link_to "here", help_ssh_path - = f.text_area :key, class: "input-xxlarge thin_area" + = f.text_area :key, class: "form-control thin_area", rows: 5 .form-actions = f.submit 'Create', class: "btn-create btn" diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml index 53d6e36c62c333378a71e38e2960cef98e125d17..90d86102acac37626453e473187d7e68280bafdc 100644 --- a/app/views/projects/deploy_keys/index.html.haml +++ b/app/views/projects/deploy_keys/index.html.haml @@ -12,7 +12,7 @@ %hr.clearfix .row - .span5.enabled-keys + .col-md-6.enabled-keys %h5 %strong.cgreen Enabled deploy keys for this project @@ -21,7 +21,7 @@ - if @enabled_keys.blank? .light-well %p.nothing_here_message Create a #{link_to 'new deploy key', new_project_deploy_key_path(@project)} or add an existing one - .span5.available-keys + .col-md-6.available-keys %h5 %strong Deploy keys from projects available to you diff --git a/app/views/projects/deploy_keys/show.html.haml b/app/views/projects/deploy_keys/show.html.haml index 49566f83d20620dcd35d3fc340348f107da87446..67615e1781fd0dc4acccc2dac5ca5530f41da25f 100644 --- a/app/views/projects/deploy_keys/show.html.haml +++ b/app/views/projects/deploy_keys/show.html.haml @@ -10,4 +10,4 @@ %hr %pre= @key.key .pull-right - = link_to 'Remove', project_deploy_key_path(@project, @key), confirm: 'Are you sure?', method: :delete, class: "btn-remove btn delete-key" + = link_to 'Remove', project_deploy_key_path(@project, @key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn-remove btn delete-key" diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 117ee13140e30668d0fadd42091d3b8c2fd8aff7..b9cd5a20d50e9b444bd62468eae71779a78f3d9d 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -7,95 +7,85 @@ %p.light Some settings, such as "Transfer Project", are hidden inside the danger area below %hr .form-holder - = form_for(@project, remote: true) do |f| + = form_for @project, remote: true, html: { class: "edit_project form-horizontal" } do |f| %fieldset - .control-group.project_name_holder - = f.label :name do - Project name is - .controls - = f.text_field :name, placeholder: "Example Project", class: "span5" + .form-group.project_name_holder + = f.label :name, class: 'control-label' do + Project name + .col-sm-10 + = f.text_field :name, placeholder: "Example Project", class: "form-control" - .control-group - = f.label :description do + .form-group + = f.label :description, class: 'control-label' do Project description %span.light (optional) - .controls - = f.text_area :description, placeholder: "awesome project", class: "span5", rows: 3, maxlength: 250 + .col-sm-10 + = f.text_area :description, placeholder: "Awesome project", class: "form-control", rows: 3, maxlength: 250 - if @project.repository.exists? && @project.repository.branch_names.any? - .control-group - = f.label :default_branch, "Default Branch" - .controls= f.select(:default_branch, @repository.branch_names, {}, {class: 'chosen'}) - - - - if can?(current_user, :change_public_mode, @project) - %fieldset.public-mode - %legend - Public mode: - .control-group - = f.label :public, class: 'control-label' do - %span Public access - .controls - = f.check_box :public - %span.descr - If checked, this project can be cloned - %em without any - authentication. - It will also be listed on the #{link_to "public access directory", public_root_path}. - %em Any - user will have #{link_to "Guest", help_permissions_path} permissions on the repository. + .form-group + = f.label :default_branch, "Default Branch", class: 'control-label' + .col-sm-10= f.select(:default_branch, @repository.branch_names, {}, {class: 'select2 select-wide'}) + + + = render "visibility_level", f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can?(current_user, :change_visibility_level, @project) %fieldset.features %legend Labels: - .control-group + .form-group = f.label :label_list, "Labels", class: 'control-label' - .controls - = f.text_field :label_list, maxlength: 2000, class: "span5" + .col-sm-10 + = f.text_field :label_list, maxlength: 2000, class: "form-control" %p.hint Separate labels with commas. %fieldset.features %legend Features: - .control-group + .form-group = f.label :issues_enabled, "Issues", class: 'control-label' - .controls - = f.check_box :issues_enabled - %span.descr Lightweight issue tracking system for this project + .col-sm-10 + .checkbox + = f.check_box :issues_enabled + %span.descr Lightweight issue tracking system for this project - if Project.issues_tracker.values.count > 1 - .control-group + .form-group = f.label :issues_tracker, "Issues tracker", class: 'control-label' - .controls= f.select(:issues_tracker, Project.issues_tracker.values, {}, { disabled: !@project.issues_enabled }) + .col-sm-10= f.select(:issues_tracker, project_issues_trackers(@project.issues_tracker), {}, { disabled: !@project.issues_enabled }) - .control-group + .form-group = f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label' - .controls= f.text_field :issues_tracker_id, disabled: !@project.can_have_issues_tracker_id? + .col-sm-10= f.text_field :issues_tracker_id, disabled: !@project.can_have_issues_tracker_id?, class: 'form-control' - .control-group + .form-group = f.label :merge_requests_enabled, "Merge Requests", class: 'control-label' - .controls - = f.check_box :merge_requests_enabled - %span.descr Submit changes to be merged upstream. + .col-sm-10 + .checkbox + = f.check_box :merge_requests_enabled + %span.descr Submit changes to be merged upstream. - .control-group + .form-group = f.label :wiki_enabled, "Wiki", class: 'control-label' - .controls - = f.check_box :wiki_enabled - %span.descr Pages for project documentation + .col-sm-10 + .checkbox + = f.check_box :wiki_enabled + %span.descr Pages for project documentation - .control-group + .form-group = f.label :wall_enabled, "Wall", class: 'control-label' - .controls - = f.check_box :wall_enabled - %span.descr Simple chat system for broadcasting inside project + .col-sm-10 + .checkbox + = f.check_box :wall_enabled + %span.descr Simple chat system for broadcasting inside project - .control-group + .form-group = f.label :snippets_enabled, "Snippets", class: 'control-label' - .controls - = f.check_box :snippets_enabled - %span.descr Share code pastes with others out of git repository + .col-sm-10 + .checkbox + = f.check_box :snippets_enabled + %span.descr Share code pastes with others out of git repository .form-actions @@ -113,18 +103,49 @@ %i.icon-chevron-down .js-toggle-visibility-container.hide + - if can? current_user, :archive_project, @project + .ui-box.ui-box-danger + .title + - if @project.archived? + Unarchive project + - else + Archive project + .body + - if @project.archived? + %p + Unarchiving the project will mark its repository as active. + %br + The project can be committed to. + %br + %strong Once active this project shows up in the search and on the dashboard. + = link_to 'Unarchive', unarchive_project_path(@project), + data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be comitted to again." }, + method: :post, class: "btn btn-remove" + - else + %p + Archiving the project will mark its repository as read-only. + %br + It is hidden from the dashboard and doesn't show up in searches. + %br + %strong Archived projects cannot be committed to! + = link_to 'Archive', archive_project_path(@project), + data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." }, + method: :post, class: "btn btn-remove" + - else + %p.nothing_here_message Only the project owner can archive a project + - if can?(current_user, :change_namespace, @project) .ui-box.ui-box-danger .title Transfer project .errors-holder .form-holder - = form_for(@project, url: transfer_project_path(@project), remote: true, html: { class: 'transfer-project' }) do |f| - .control-group - = f.label :namespace_id do + = form_for(@project, url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f| + .form-group + = f.label :namespace_id, class: 'control-label' do %span Namespace - .controls - .control-group - = f.select :namespace_id, namespaces_options(@project.namespace_id), {prompt: 'Choose a project namespace'}, {class: 'chosen'} + .col-sm-10 + .form-group + = f.select :namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace' }, { class: 'select2' } %ul %li Be careful. Changing the project's namespace can have unintended side effects. %li You can only transfer the project to namespaces you manage. @@ -138,13 +159,15 @@ .title Rename repository .errors-holder .form-holder - = form_for(@project) do |f| - .control-group - = f.label :path do + = form_for(@project, html: { class: 'form-horizontal' }) do |f| + .form-group + = f.label :path, class: 'control-label' do %span Path - .controls - .control-group - = f.text_field :path + .col-sm-9 + .form-group + .input-group + = f.text_field :path, class: 'form-control' + %span.input-group-addon .git %ul %li Be careful. Renaming a project's repository can have unintended side effects. %li You will need to update your local repositories to point to the new location. @@ -154,18 +177,19 @@ - if can?(current_user, :remove_project, @project) .ui-box.ui-box-danger .title Remove project - .ui-box-body + .body %p Removing the project will delete its repository and all related resources including issues, merge requests etc. %br %strong Removed projects cannot be restored! - = link_to 'Remove project', @project, confirm: remove_project_message(@project), method: :delete, class: "btn btn-remove" + = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project) }, method: :delete, class: "btn btn-remove" - else %p.nothing_here_message Only project owner can remove a project .save-project-loader.hide %center - = image_tag "ajax_loader.gif" - %h3 Saving project. + %h2 + %i.icon-spinner.icon-spin + Saving project. %p Please wait a moment, this page will automatically refresh when ready. diff --git a/app/views/projects/edit_tree/show.html.haml b/app/views/projects/edit_tree/show.html.haml index b1fb4f8637ddd1f53095cef60492bda37b5846fd..10dae3c8ff0624a73e00e6b8a857b4c0c1e6c92e 100644 --- a/app/views/projects/edit_tree/show.html.haml +++ b/app/views/projects/edit_tree/show.html.haml @@ -11,15 +11,15 @@ %strong= @ref %span.options .btn-group.tree-btn-group - = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-tiny btn-cancel", confirm: leave_edit_message + = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-tiny btn-cancel", data: { confirm: leave_edit_message } .file-content.code %pre#editor= @blob.data - .control-group.commit_message-group + .form-group.commit_message-group = label_tag 'commit_message', class: "control-label" do Commit message - .controls - = text_area_tag 'commit_message', '', placeholder: "Update #{@blob.name}", required: true, rows: 3 + .col-sm-10 + = text_area_tag 'commit_message', '', placeholder: "Update #{@blob.name}", required: true, rows: 3, class: 'form-control' .form-actions = hidden_field_tag 'last_commit', @last_commit = hidden_field_tag 'content', '', id: "file-content" @@ -28,7 +28,7 @@ .message to branch %strong= @ref - = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-cancel", confirm: leave_edit_message + = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-cancel", data: { confirm: leave_edit_message} :javascript ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict") diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 9f3502e90de9b0fef2f7c88f9f4ffba18b6eb0f3..489b9b0e951b5c8715e507e62db1533582f46ef7 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -1,12 +1,13 @@ -= render 'clone_panel' += render "home_panel" - if @project.import? && !@project.imported .save-project-loader %center - = image_tag "ajax_loader.gif" - %h3 Importing repository. + %h2 + %i.icon-spinner.icon-spin + Importing repository. %p.monospace git clone --bare #{@project.import_url} - %p Please wait until we import repository for you. Refresh at will. + %p Please wait while we import the repository for you. Refresh at will. :javascript new ProjectImport(); @@ -29,8 +30,7 @@ touch README git add README git commit -m 'first commit' - %span.clone= "git remote add origin #{@project.url_to_repo}" - :preserve + git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')} git push -u origin master %fieldset @@ -38,10 +38,9 @@ %pre.dark :preserve cd existing_git_repo - %span.clone= "git remote add origin #{@project.url_to_repo}" - :preserve + git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')} git push -u origin master - if can? current_user, :remove_project, @project .prepend-top-20 - = link_to 'Remove project', @project, confirm: remove_project_message(@project), method: :delete, class: "btn btn-remove pull-right" + = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right" diff --git a/app/views/projects/fork.html.haml b/app/views/projects/fork.html.haml index a1c109e5d624da6c6f08e771cecc56bc402d7497..bdd75de447a97ac0ccbd8e14ef6ac8c04dae32dd 100644 --- a/app/views/projects/fork.html.haml +++ b/app/views/projects/fork.html.haml @@ -1,11 +1,11 @@ -.alert.alert-error.alert-block +.alert.alert-danger.alert-block %h4 %i.icon-code-fork Fork Error! %p - You are trying to fork + You tried to fork = link_to_project @project - but it fails due to next reason: + but it failed for the following reason: - if @forked_project && @forked_project.errors.any? diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index a21cb9e7861a2607d1681b49aa5bc60a4cbe4f28..27348232ba23f5f0df5afd94aefebc77b7744770 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -1,7 +1,8 @@ .loading-graph %center - .loading - %h3.page-title Building repository graph. + %h3.page-title + %i.icon-spinner.icon-spin + Building repository graph. %p Please wait a moment, this page will automatically refresh when ready. .stat-graph diff --git a/app/views/projects/graphs/show.js.haml b/app/views/projects/graphs/show.js.haml index 43e776e5330dd2ad19d12977ede75e0e7cca891a..dcf6cacdcebf520b019b7a6f6b3cb4d3c79440c2 100644 --- a/app/views/projects/graphs/show.js.haml +++ b/app/views/projects/graphs/show.js.haml @@ -16,4 +16,4 @@ }) - else :plain - $('.stat-graph').replaceWith('<div class="alert alert-error">Failed to load graph</div>') + $('.stat-graph').replaceWith('<div class="alert alert-danger">Failed to load graph</div>') diff --git a/app/views/projects/hooks/_data_ex.html.erb b/app/views/projects/hooks/_data_ex.html.erb deleted file mode 100644 index b4281fa18c7c36bf56ffdcbaa35e16e9a6c7c653..0000000000000000000000000000000000000000 --- a/app/views/projects/hooks/_data_ex.html.erb +++ /dev/null @@ -1,43 +0,0 @@ -<% data_ex_str = <<eos -{ - "before": "95790bf891e76fee5e1747ab589903a6a1f80f22", - "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", - "ref": "refs/heads/master", - "user_id": 4, - "user_name": "John Smith", - "repository": { - "name": "Diaspora", - "url": "git@localhost:diaspora.git", - "description": "", - "homepage": "http://localhost/diaspora", - }, - "commits": [ - { - "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", - "message": "Update Catalan translation to e38cb41.", - "timestamp": "2011-12-12T14:27:31+02:00", - "url": "http://localhost/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", - "author": { - "name": "Jordi Mallach", - "email": "jordi@softcatala.org", - } - }, - // ... - { - "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", - "message": "fixed readme", - "timestamp": "2012-01-03T23:36:29+02:00", - "url": "http://localhost/diaspora/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7", - "author": { - "name": "GitLab dev user", - "email": "gitlabdev@dv6700.(none)", - }, - }, - ], - "total_commits_count": 4, -}; -eos -%> -<div class="<%= user_color_scheme_class%>"> - <%= raw Pygments::Lexer[:js].highlight(data_ex_str) %> -</div> diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml index f748eb29294294b19ea61d55a26a57136df8fab2..a095fd06d2f326d368ed12708e63a3a35b6d9740 100644 --- a/app/views/projects/hooks/index.html.haml +++ b/app/views/projects/hooks/index.html.haml @@ -1,35 +1,61 @@ %h3.page-title - Post-receive hooks + Web hooks %p.light - #{link_to "Post-receive hooks ", help_web_hooks_path, class: "vlink"} can be - used for binding events when someone pushes to the repository. + #{link_to "Web hooks ", help_web_hooks_path, class: "vlink"} can be + used for binding events when something is happening within the project. %hr.clearfix -= form_for [@project, @hook], as: :hook, url: project_hooks_path(@project), html: { class: 'form-inline' } do |f| += form_for [@project, @hook], as: :hook, url: project_hooks_path(@project), html: { class: 'form-horizontal' } do |f| -if @hook.errors.any? - .alert.alert-error + .alert.alert-danger - @hook.errors.full_messages.each do |msg| %p= msg - .control-group - = f.label :url, "URL:" - .controls - = f.text_field :url, class: "text_field input-xxlarge input-xpadding", placeholder: 'http://example.com/trigger-ci.json' - - = f.submit "Add Web Hook", class: "btn btn-create" -%hr + .form-group + = f.label :url, "URL", class: 'control-label' + .col-sm-10 + = f.text_field :url, class: "form-control", placeholder: 'http://example.com/trigger-ci.json' + .form-group + = f.label :url, "Trigger", class: 'control-label' + .col-sm-10 + %div + = f.check_box :push_events, class: 'pull-left' + .prepend-left-20 + = f.label :push_events, class: 'list-label' do + %strong Push events + %p.light + This url will be triggered by a push to the repository + %div + = f.check_box :issues_events, class: 'pull-left' + .prepend-left-20 + = f.label :issues_events, class: 'list-label' do + %strong Issues events + %p.light + This url will be triggered when an issue is created + %div + = f.check_box :merge_requests_events, class: 'pull-left' + .prepend-left-20 + = f.label :merge_requests_events, class: 'list-label' do + %strong Merge Request events + %p.light + This url will be triggered when a merge request is created + .form-actions + = f.submit "Add Web Hook", class: "btn btn-create" -if @hooks.any? .ui-box .title - Hooks (#{@hooks.count}) + Web hooks (#{@hooks.count}) %ul.well-list - @hooks.each do |hook| %li - %span.badge.badge-info POST - → - %span.monospace= hook.url .pull-right = link_to 'Test Hook', test_project_hook_path(@project, hook), class: "btn btn-small grouped" - = link_to 'Remove', project_hook_path(@project, hook), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove btn-small grouped" + = link_to 'Remove', project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-small grouped" + .clearfix + %span.monospace= hook.url + %p + - %w(push_events issues_events merge_requests_events).each do |trigger| + - if hook.send(trigger) + %span.label.label-gray= trigger.titleize diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index 6acad9134d1e67eead4fba2bf79d76fb119834d4..c95e81785943eeb5691c25b79d3e624a345a8c71 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -1,50 +1,47 @@ %div.issue-form-holder - %h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.id}" - = form_for [@project, @issue] do |f| + %h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.iid}" + %hr + = form_for [@project, @issue], html: { class: 'form-horizontal issue-form' } do |f| -if @issue.errors.any? - .alert.alert-error + .alert.alert-danger - @issue.errors.full_messages.each do |msg| %span= msg %br - .ui-box.ui-box-show - .ui-box-head - .control-group - = f.label :title do - %strong= "Subject *" - .controls - = f.text_field :title, maxlength: 255, class: "input-xxlarge js-gfm-input", autofocus: true, required: true - .ui-box-body - .control-group - .issue_assignee.pull-left - = f.label :assignee_id do - %i.icon-user - Assign to - .controls - .pull-left - = f.select(:assignee_id, @project.team.members.sort_by(&:name).map {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }, {class: 'chosen'}) - .pull-right - - = link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link' - .issue_milestone.pull-left - = f.label :milestone_id do - %i.icon-time - Milestone - .controls= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'}) + .form-group + = f.label :title, class: 'control-label' do + %strong= "Subject *" + .col-sm-10 + = f.text_field :title, maxlength: 255, class: "form-control js-gfm-input", autofocus: true, required: true + .form-group + = f.label :description, "Details", class: 'control-label' + .col-sm-10 + = f.text_area :description, class: "form-control js-gfm-input", rows: 14 + %p.hint Issues are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. + %hr + .form-group + .issue-assignee + = f.label :assignee_id, class: 'control-label' do + %i.icon-user + Assign to + .col-sm-10 + = f.select(:assignee_id, assignee_options(@issue), { include_blank: "Select a user" }, {class: 'select2'}) + + = link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link' + .form-group + .issue-milestone + = f.label :milestone_id, class: 'control-label' do + %i.icon-time + Milestone + .col-sm-10= f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone" }, {class: 'select2'}) - .ui-box-bottom - .control-group - = f.label :label_list do - %i.icon-tag - Labels - .controls - = f.text_field :label_list, maxlength: 2000, class: "input-xxlarge" - %p.hint Separate labels with commas. + .form-group + = f.label :label_list, class: 'control-label' do + %i.icon-tag + Labels + .col-sm-10 + = f.text_field :label_list, maxlength: 2000, class: "form-control" + %p.hint Separate labels with commas. - .control-group - = f.label :description, "Details" - .controls - = f.text_area :description, class: "input-xxlarge js-gfm-input", rows: 14 - %p.hint Issues are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. .form-actions @@ -90,6 +87,6 @@ }); $('.assign-to-me-link').on('click', function(e){ - $('#issue_assignee_id').val("#{current_user.id}").trigger("chosen:updated"); + $('#issue_assignee_id').val("#{current_user.id}").trigger("change"); e.preventDefault(); }); diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml index 438cc02b47719c29ac5e36bb925cf0194f2d2b4d..61213e752f82c047d472e86e09bd2ab86a4c70f2 100644 --- a/app/views/projects/issues/_head.html.haml +++ b/app/views/projects/issues/_head.html.haml @@ -1,11 +1,26 @@ -%ul.nav.nav-tabs +%ul.nav.nav-tabs.append-bottom-15 = nav_link(controller: :issues) do - = link_to 'Browse Issues', project_issues_path(@project), class: "tab" + = link_to project_issues_path(@project), class: "tab" do + Browse Issues + - if current_controller?(:issues) + %span.badge.issue_counter #{@issues.total_count} = nav_link(controller: :milestones) do = link_to 'Milestones', project_milestones_path(@project), class: "tab" = nav_link(controller: :labels) do = link_to 'Labels', project_labels_path(@project), class: "tab" - - if current_user + + - if current_controller?(:issues) + - if current_user + %li + = link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do + %i.icon-rss + %li.pull-right - = link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do - %i.icon-rss + .pull-right + = form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'inline issue-search-form' do + .append-right-10.hidden-xs.hidden-sm + = search_field_tag :issue_search, nil, { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' } + - 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", title: "New Issue", id: "new_issue_link" do + %i.icon-plus + New Issue diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index b9a2c18efdc78bf99a02e08384bc5e8e1277fcf7..1bd93b774f1353899d5f477a4b8118d31a5a11b3 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -5,7 +5,11 @@ .issue-title %span.light= "##{issue.iid}" - = link_to_gfm truncate(issue.title, length: 100), project_issue_path(issue.project, issue), class: "row_title" + %span.str-truncated + = link_to_gfm issue.title, project_issue_path(issue.project, issue), class: "row_title" + - if issue.closed? + %small.pull-right + = "CLOSED" .issue-info - if issue.assignee @@ -23,7 +27,7 @@ %i.icon-time = issue.milestone.title .pull-right - %small updated #{time_ago_in_words(issue.updated_at)} ago + %small updated #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_update_ago')} .issue-labels - issue.labels.each do |label| diff --git a/app/views/projects/issues/_issue_context.html.haml b/app/views/projects/issues/_issue_context.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..7ddf470b6a0b566b3f9b5dd2762e21ae47672273 --- /dev/null +++ b/app/views/projects/issues/_issue_context.html.haml @@ -0,0 +1,25 @@ += form_for [@project, @issue], remote: true, html: {class: 'edit-issue inline-update'} do |f| + Created by #{link_to_member(@project, issue.author)} + - if issue.assignee + \ and currently assigned to + + - if can?(current_user, :modify_issue, @issue) + = link_to profile_path(issue.assignee) do + = image_tag(avatar_icon(issue.assignee.email), class: 'avatar avatar-inline s16 assignee') if issue.assignee + = f.select(:assignee_id, assignee_options(@issue), { include_blank: "Assign to user (none):" }, {class: 'select2'}) + - elsif issue.assignee + = link_to_member(@project, @issue.assignee) + + + .pull-right.hidden-sm + - if issue.milestone + - milestone = issue.milestone + %cite.cgray Attached to milestone + + - if can?(current_user, :modify_issue, @issue) + = f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone (none):" }, {class: 'select2 select2-compact'}) + + = hidden_field_tag :issue_context + = f.submit class: 'btn' + - elsif issue.milestone + = link_to issue.milestone.title, project_milestone_path diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index 539c45edd947b11bf48cc90f8a5101f9fd8a32dc..87d30a4a16379b98f1d4d35fb209a85394f33b8f 100644 --- a/app/views/projects/issues/_issues.html.haml +++ b/app/views/projects/issues/_issues.html.haml @@ -6,8 +6,8 @@ = form_tag bulk_update_project_issues_path(@project), method: :post do %span Update selected issues with = select_tag('update[status]', options_for_select(['open', 'closed']), prompt: "Status") - = select_tag('update[assignee_id]', options_from_collection_for_select(@project.team.members, "id", "name", params[:assignee_id]), prompt: "Assignee") - = select_tag('update[milestone_id]', options_from_collection_for_select(project_active_milestones, "id", "title", params[:milestone_id]), prompt: "Milestone") + = select_tag('update[assignee_id]', bulk_update_assignee_options, prompt: "Assignee") + = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone") = hidden_field_tag 'update[issues_ids]', [] = hidden_field_tag :status, params[:status] = button_tag "Save", class: "btn update_selected_issues btn-small btn-save" @@ -52,7 +52,7 @@ - @project.team.members.sort_by(&:name).each do |user| %li = link_to project_filter_path(assignee_id: user.id) do - = image_tag gravatar_icon(user.email), class: "avatar s16", alt: '' + = image_tag avatar_icon(user.email), class: "avatar s16", alt: '' = user.name .dropdown.inline.prepend-left-10 @@ -78,6 +78,9 @@ %strong= milestone.title %small.light= milestone.expires_at + .pull-right + = render 'shared/sort_dropdown' + %ul.well-list.issues-list = render @issues @@ -90,4 +93,4 @@ %span.issue_counter #{@issues.total_count} issues for this filter - = paginate @issues, remote: true, theme: "gitlab" + = paginate @issues, theme: "gitlab" diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder index 00ddd4bf7024dfe6c4d1eff801e875f9b7051ba0..012ba235951b0d43a1b2779703864c254a36e1bc 100644 --- a/app/views/projects/issues/index.atom.builder +++ b/app/views/projects/issues/index.atom.builder @@ -12,7 +12,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear 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 => gravatar_icon(issue.author_email) + 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 diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 81594b4972e3161d2d0b655f9ec8c994f2f322b3..71a89af61a2e1e800bafbbc1f76f9a95f4f6b9a0 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -1,23 +1,6 @@ = render "head" -.issues_content - %h3.page-title - Issues - %span (<span class=issue_counter>#{@issues.total_count}</span>) - .pull-right - .span6 - - 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-right", title: "New Issue", id: "new_issue_link" do - %i.icon-plus - New Issue - = form_tag project_issues_path(@project), method: :get, remote: true, id: "issue_search_form", class: 'pull-right' do - = hidden_field_tag :status, params[:status], id: 'search_status' - = hidden_field_tag :assignee_id, params[:assignee_id], id: 'search_assignee_id' - = hidden_field_tag :milestone_id, params[:milestone_id], id: 'search_milestone_id' - = hidden_field_tag :label_name, params[:label_name], id: 'search_label_name' - = search_field_tag :issue_search, nil, { placeholder: 'Filter by title or description', class: 'input-xpadding issue_search input-xlarge append-right-10 search-text-input' } - .row - .span3 + .col-md-3 = render 'shared/project_filter', project_entities_path: project_issues_path(@project) - .span9.issues-holder + .col-md-9.issues-holder = render "issues" diff --git a/app/views/projects/issues/index.js.haml b/app/views/projects/issues/index.js.haml deleted file mode 100644 index 1be6a64f535792057aefc30822dede6475e053ce..0000000000000000000000000000000000000000 --- a/app/views/projects/issues/index.js.haml +++ /dev/null @@ -1,4 +0,0 @@ -:plain - $('.issues-holder').html("#{escape_javascript(render('issues'))}"); - History.replaceState({path: "#{request.url}"}, document.title, "#{request.url}"); - Issues.reload(); diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 6d1a088721ccb33eec75953847904f9a45df0d4d..cd4a158e4279b94d015ea2a38036882b2fb6eac3 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -2,8 +2,12 @@ Issue ##{@issue.iid} %small - created at - = @issue.created_at.stamp("Aug 21, 2011") + created #{time_ago_with_tooltip(@issue.created_at)} + + - if @issue.closed? + %span.state-label.state-label-red Closed + - else + %span.state-label.state-label-green Open %span.pull-right - if can?(current_user, :write_issue, @project) @@ -20,41 +24,29 @@ %i.icon-edit Edit -.pull-right - .span3#votes= render 'votes/votes_block', votable: @issue +.votes-holder + #votes= render 'votes/votes_block', votable: @issue .back-link = link_to project_issues_path(@project) do ← To issues list + %span.milestone-nav-link + - if @issue.milestone + | + %span.light Milestone + = link_to project_milestone_path(@project, @issue.milestone) do + = @issue.milestone.title +.issue-box + %h4.title + = gfm escape_once(@issue.title) -.ui-box.ui-box-show - .ui-box-head - %h4.box-title - - if @issue.closed? - .error.status_info Closed - = gfm escape_once(@issue.title) - - .ui-box-body + .context %cite.cgray - Created by #{link_to_member(@project, @issue.author)} - - if @issue.assignee - \ and currently assigned to #{link_to_member(@project, @issue.assignee)} - - - if @issue.milestone - - milestone = @issue.milestone - %cite.cgray and attached to milestone - %strong= link_to_gfm truncate(milestone.title, length: 20), project_milestone_path(milestone.project, milestone) - - .pull-right - - @issue.labels.each do |label| - %span{class: "label #{label_css_class(label.name)}"} - %i.icon-tag - = label.name - + = render partial: 'issue_context', locals: { issue: @issue } - if @issue.description.present? - .ui-box-bottom + .description .wiki = preserve do = markdown @issue.description @@ -71,4 +63,11 @@ - @issue.participants.each do |participant| = link_to_member(@project, participant, name: false, size: 24) + .issue-show-labels.pull-right + - @issue.labels.each do |label| + %span{class: "label #{label_css_class(label.name)}"} + %i.icon-tag + = label.name + + .voting_notes#notes= render "projects/notes/notes_with_form" diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml index 7f66022a2de34d851856dd0ba4ad9e1ff5f9f549..59524e0f224653dc5fdda814159299315d1db26a 100644 --- a/app/views/projects/issues/update.js.haml +++ b/app/views/projects/issues/update.js.haml @@ -2,3 +2,12 @@ - if @issue.valid? :plain $("##{dom_id(@issue)}").fadeOut(); +- elsif params[:issue_context] + $('.issue-box .context').html("#{escape_javascript(render partial: 'issue_context', locals: { issue: @issue })}"); + $('.issue-box .context').effect('highlight'); + $('.select2').select2(); + $('.edit-issue.inline-update input[type="submit"]').hide(); + - if @issue.milestone + $('.milestone-nav-link').replaceWith("<span class='milestone-nav-link'>| <span class='light'>Milestone</span> #{escape_javascript(link_to @issue.milestone.title, project_milestone_path(@issue.project, @issue.milestone))}</span>") + - else + $('.milestone-nav-link').html('') diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml index 2b1aafc546b8e131603406dee7c74c7ec0c1251d..6e1ca0d8f2fe6e608e96a374d4b5197a1a170384 100644 --- a/app/views/projects/labels/_label.html.haml +++ b/app/views/projects/labels/_label.html.haml @@ -1,15 +1,13 @@ - frequency = @project.issues.tagged_with(label.name).count %li - %strong - %span{class: "label #{label_css_class(label.name)}"} - %i.icon-tag - - if frequency.zero? - %span.light= label.name - - else - = label.name + %span{class: "label #{label_css_class(label.name)}"} + %i.icon-tag + - if frequency.zero? + %span.light= label.name + - else + = label.name .pull-right - unless frequency.zero? = link_to project_issues_path(label_name: label.name) do - %strong - = pluralize(frequency, 'issue') - = "»" + = pluralize(frequency, 'issue') + = "»" diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml index ce72756303e76ce190f710720ac6fc65a482c67b..b4ba127da25d92759d44a0783c3a73dab0383a49 100644 --- a/app/views/projects/merge_requests/_form.html.haml +++ b/app/views/projects/merge_requests/_form.html.haml @@ -1,57 +1,61 @@ -= form_for [@project, @merge_request], html: { class: "#{controller.action_name}-merge-request form-horizontal" } do |f| += form_for [@project, @merge_request], html: { class: "merge-request-form form-horizontal" } do |f| -if @merge_request.errors.any? - .alert.alert-error + .alert.alert-danger %ul - @merge_request.errors.full_messages.each do |msg| %li= msg .merge-request-branches .row - .span5 + .col-md-5 .clearfix .pull-left - = f.select(:source_project_id,[[@merge_request.source_project.path_with_namespace,@merge_request.source_project.id]] , {}, {class: 'source_project chosen span3'}) + = f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted? }) .pull-left - %i.icon-code-fork - = f.select(:source_branch, @merge_request.source_project.repository.branch_names, { include_blank: "Select branch" }, {class: 'source_branch chosen span2'}) + = f.select(:source_branch, @merge_request.source_branches, { include_blank: "Select branch" }, {class: 'source_branch select2 span2'}) .mr_source_commit.prepend-top-10 - .span2 - %h2.merge-request-angle.light + .col-md-2 + .merge-request-angle %i.icon-long-arrow-right - .span5 + .col-md-5 .clearfix .pull-left - projects = @project.forked_from_project.nil? ? [@project] : [ @project,@project.forked_from_project] - = f.select(:target_project_id, projects.map { |proj| [proj.path_with_namespace,proj.id] }, {include_blank: "Select Target Project" }, {class: 'target_project chosen span3'}) + = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace'), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted? }) .pull-left - %i.icon-code-fork - = f.select(:target_branch, @target_branches, { include_blank: "Select branch" }, {class: 'target_branch chosen span2'}) + = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, {class: 'target_branch select2 span2'}) .mr_target_commit.prepend-top-10 %hr .merge-request-form-info - .control-group - = f.label :title do + .form-group + = f.label :title, class: 'control-label' do %strong= "Title *" - .controls= f.text_field :title, class: "input-xxlarge pad js-gfm-input", maxlength: 255, rows: 5, required: true - .control-group - .left - = f.label :assignee_id do + .col-sm-10= f.text_field :title, class: "form-control pad js-gfm-input", maxlength: 255, rows: 5, required: true + .form-group + = f.label :description, "Description", class: 'control-label' + .col-sm-10 + = f.text_area :description, class: "form-control js-gfm-input", rows: 14 + %p.hint Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. + + %hr + .form-group + .merge-request-assignee + = f.label :assignee_id, class: 'control-label' do %i.icon-user Assign to - .controls= f.select(:assignee_id, @project.team.members.sort_by(&:name).map {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, {class: 'chosen span3'}) - .left - = f.label :milestone_id do + .col-sm-10 + = f.select(:assignee_id, assignee_options(@merge_request), { include_blank: "Select a user" }, {class: 'select2'}) + + = link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link' + .form-group + .merge-request-milestone + = f.label :milestone_id, class: 'control-label' do %i.icon-time Milestone - .controls= f.select(:milestone_id, @project.milestones.active.all.map {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'}) - .control-group - = f.label :description, "Description" - .controls - = f.text_area :description, class: "input-xxlarge js-gfm-input", rows: 14 - %p.hint Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. + .col-sm-10= f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2'}) .form-actions @@ -85,3 +89,7 @@ target_branch.on("change", function() { $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: $(this).val() }); }); + $('.assign-to-me-link').on('click', function(e){ + $('#merge_request_assignee_id').val("#{current_user.id}").trigger("change"); + e.preventDefault(); + }); diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 933d78bcbfbd5e12fe3b70e6e85470130d7017e5..ff763bca3070609fb4b2ab089b341b9da504a375 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -10,7 +10,7 @@ %span.pull-right - if merge_request.for_fork? %span.light - = "#{merge_request.source_project.path_with_namespace}" + = "#{merge_request.source_project_path}" = "#{merge_request.source_branch}" %i.icon-angle-right.light = "#{merge_request.target_branch}" @@ -34,4 +34,4 @@ .pull-right - %small updated #{time_ago_in_words(merge_request.updated_at)} ago + %small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')} diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 47c14abdedd146a68f16245dde57baebc1917d57..42641765c5cce4df60a5769121e622485dc6beda 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -2,13 +2,17 @@ = render "projects/merge_requests/show/mr_title" = render "projects/merge_requests/show/how_to_merge" = render "projects/merge_requests/show/mr_box" - = render "projects/merge_requests/show/mr_accept" + - if @merge_request.opened? + - if @merge_request.source_branch_exists? && @merge_request.target_branch_exists? + = render "projects/merge_requests/show/mr_accept" + - else + = render "projects/merge_requests/show/no_accept" - if @merge_request.source_project.gitlab_ci? = render "projects/merge_requests/show/mr_ci" = render "projects/merge_requests/show/commits" - if @commits.present? - %ul.nav.nav-tabs + %ul.nav.nav-tabs.append-bottom-10 %li.notes-tab{data: {action: 'notes'}} = link_to project_merge_request_path(@project, @merge_request) do %i.icon-comment diff --git a/app/views/projects/merge_requests/commits.js.haml b/app/views/projects/merge_requests/commits.js.haml deleted file mode 100644 index 923b1ea032f544166c91638f3b5dd520a7d55235..0000000000000000000000000000000000000000 --- a/app/views/projects/merge_requests/commits.js.haml +++ /dev/null @@ -1,4 +0,0 @@ -:plain - merge_request.$(".commits").html("#{escape_javascript(render(partial: "commits"))}"); - - diff --git a/app/views/projects/merge_requests/diffs.js.haml b/app/views/projects/merge_requests/diffs.js.haml deleted file mode 100644 index 2964f0ec462a02c2be298ed8cbd19cb80de16bd7..0000000000000000000000000000000000000000 --- a/app/views/projects/merge_requests/diffs.js.haml +++ /dev/null @@ -1,2 +0,0 @@ -:plain - merge_request.$(".diffs").html("#{escape_javascript(render(partial: "projects/merge_requests/show/diffs"))}"); diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml index 67a1541d9bf23c6ce460e3278494df0a29b2c048..839c63986ab36a155125e67a5739f0702dd8ef6e 100644 --- a/app/views/projects/merge_requests/edit.html.haml +++ b/app/views/projects/merge_requests/edit.html.haml @@ -1,4 +1,4 @@ %h3.page-title - = "Edit merge request ##{@merge_request.id}" + = "Edit merge request ##{@merge_request.iid}" %hr = render 'form' diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 2bd5a027a0289634bf0ab0d16c45ef34e212970a..a525a49015f7269b19c47b2cba21bae6f4fde9df 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -5,12 +5,11 @@ %h3.page-title Merge Requests %span (#{@merge_requests.total_count}) - - +%hr .row - .span3 + .col-md-3 = render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project) - .span9 + .col-md-9 .ui-box .title .mr-filters @@ -35,7 +34,7 @@ - @project.team.members.sort_by(&:name).each do |user| %li = link_to project_filter_path(assignee_id: user.id) do - = image_tag gravatar_icon(user.email), class: "avatar s16", alt: '' + = image_tag avatar_icon(user.email), class: "avatar s16", alt: '' = user.name .dropdown.inline.prepend-left-10 @@ -61,6 +60,9 @@ %strong= milestone.title %small.light= milestone.expires_at + .pull-right + = render 'shared/sort_dropdown' + %ul.well-list.mr-list = render @merge_requests - if @merge_requests.blank? diff --git a/app/views/projects/merge_requests/invalid.html.haml b/app/views/projects/merge_requests/invalid.html.haml index c962811a3e4308d572dbdde355e9fc9b207b119d..b9c466657de4f01fe66edf0f9f530e8664dfc9db 100644 --- a/app/views/projects/merge_requests/invalid.html.haml +++ b/app/views/projects/merge_requests/invalid.html.haml @@ -2,16 +2,22 @@ = render "projects/merge_requests/show/mr_title" = render "projects/merge_requests/show/mr_box" - .alert.alert-error - %h5 - %i.icon-exclamation-sign - We cannot find - %span.label-branch= @merge_request.source_branch - or - %span.label-branch= @merge_request.target_branch - branches in the repository. - %p - Maybe it was removed or never pushed. + .alert.alert-danger %p + We cannot render this merge request properly because + - if @merge_request.for_fork? && !@merge_request.source_project + fork project was removed + - elsif !@merge_request.source_branch_exists? + %span.label.label-inverse= @merge_request.source_branch + does not exist in + %span.label.label-info= @merge_request.source_project_path + - elsif !@merge_request.target_branch_exists? + %span.label.label-inverse= @merge_request.target_branch + does not exist in + %span.label.label-info= @merge_request.target_project_path + - else + of internal error + + %strong Please close Merge Request or change branches with existing one diff --git a/app/views/projects/merge_requests/show.js.haml b/app/views/projects/merge_requests/show.js.haml deleted file mode 100644 index 2ce6eb6329015cf4e3fa16cc30f7a02d597147c3..0000000000000000000000000000000000000000 --- a/app/views/projects/merge_requests/show.js.haml +++ /dev/null @@ -1,2 +0,0 @@ -:plain - merge_request.$(".notes").html("#{escape_javascript(render "notes/notes_with_form")}"); diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml index 7b0e67053a534cc10bf2232f537a02f9e51fd707..8ca1326c96ab66e88c3f4cc7b913798e335476e7 100644 --- a/app/views/projects/merge_requests/show/_commits.html.haml +++ b/app/views/projects/merge_requests/show/_commits.html.haml @@ -12,9 +12,16 @@ 8 of #{@commits.count} commits displayed. %strong %a.show-all-commits Click here to show all - %ul.all-commits.hide.well-list - - @commits.each do |commit| - = render "projects/commits/commit", commit: commit, project: @merge_request.source_project + - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE + %ul.all-commits.hide.well-list + - @commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE).each do |commit| + = render "projects/commits/inline_commit", commit: commit, project: @merge_request.source_project + %li + other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden top prevent performance issues. + - else + %ul.all-commits.hide.well-list + - @commits.each do |commit| + = render "projects/commits/inline_commit", commit: commit, project: @merge_request.source_project - else %ul.well-list diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml index 25f638048589726c9f3201f741d769bf3529b507..2c7507cfb8b2ec09c8a07a6c0cc09c1a10698506 100644 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ b/app/views/projects/merge_requests/show/_diffs.html.haml @@ -1,10 +1,10 @@ -- if @merge_request.valid_diffs? +- if @merge_request_diff.collected? = render "projects/commits/diffs", diffs: @merge_request.diffs, project: @merge_request.source_project -- elsif @merge_request.broken_diffs? +- elsif @merge_request_diff.empty? + %h4.nothing_here_message Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} +- else %h4.nothing_here_message Can't load diff. You can = link_to "download it", project_merge_request_path(@merge_request.source_project, @merge_request), format: :diff, class: "vlink" instead. -- else - %h4.nothing_here_message Nothing to merge diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml index 030ac285f2a9e5f73afc117720e956232452631b..9540453ce3ebca1ca885c7989d13255b8dffa0de 100644 --- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml +++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml @@ -1,42 +1,44 @@ %div#modal_merge_info.modal.hide - .modal-header - %a.close{href: "#", "data-dismiss" => "modal"} × - %h3 How to merge - .modal-body - - if @merge_request.for_fork? - - source_remote = @merge_request.source_project.namespace.nil? ? "source" :@merge_request.source_project.namespace.path - - target_remote = @merge_request.target_project.namespace.nil? ? "target" :@merge_request.target_project.namespace.path - %p - %strong Step 1. - Checkout target branch and get recent objects from GitLab - Assuming remote for #{@merge_request.target_project.path_with_namespace} is called #{target_remote} - remote for #{@merge_request.source_project.path_with_namespace} is called #{source_remote} - %pre.dark - :preserve - git checkout #{target_remote} #{@merge_request.target_branch} - git fetch #{source_remote} - %p - %strong Step 2. - Merge source branch into target branch and push changes to GitLab - %pre.dark - :preserve - git merge #{source_remote}/#{@merge_request.source_branch} - git push #{target_remote} #{@merge_request.target_branch} - - else - %p - %strong Step 1. - Checkout target branch and get recent objects from GitLab - %pre.dark - :preserve - git checkout #{@merge_request.target_branch} - git fetch origin - %p - %strong Step 2. - Merge source branch into target branch and push changes to GitLab - %pre.dark - :preserve - git merge origin/#{@merge_request.source_branch} - git push origin #{@merge_request.target_branch} + .modal-dialog + .modal-content + .modal-header + %a.close{href: "#", "data-dismiss" => "modal"} × + %h3 How to merge + .modal-body + - if @merge_request.for_fork? + - source_remote = @merge_request.source_project.namespace.nil? ? "source" :@merge_request.source_project.namespace.path + - target_remote = @merge_request.target_project.namespace.nil? ? "target" :@merge_request.target_project.namespace.path + %p + %strong Step 1. + Checkout the branch we are going to merge and pull in the code + %pre.dark + :preserve + git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} #{@merge_request.target_branch} + git pull #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch} + %p + %strong Step 2. + Merge the branch and push the changes to GitLab + %pre.dark + :preserve + git checkout #{@merge_request.target_branch} + git merge --no-ff #{@merge_request.source_project_path}-#{@merge_request.source_branch} + git push origin #{@merge_request.target_branch} + - else + %p + %strong Step 1. + Update the repo and checkout the branch we are going to merge + %pre.dark + :preserve + git fetch origin + git checkout -b #{@merge_request.source_branch} origin/#{@merge_request.source_branch} + %p + %strong Step 2. + Merge the branch and push the changes to GitLab + %pre.dark + :preserve + git checkout #{@merge_request.target_branch} + git merge --no-ff #{@merge_request.source_branch} + git push origin #{@merge_request.target_branch} :javascript diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml index 299e1bc1e08715feec50df76593a6b15abf8ba83..83f2db03b185b531515d751832b6cc16cbd7994e 100644 --- a/app/views/projects/merge_requests/show/_mr_accept.html.haml +++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml @@ -1,53 +1,69 @@ - unless @allowed_to_merge - .alert + .bs-callout %strong You don't have permission to merge this MR - if @show_merge_controls - .automerge_widget.can_be_merged{style: "display:none"} - .alert.alert-success - %span - = form_for [:automerge, @project, @merge_request], remote: true, method: :get do |f| - %p - You can accept this request automatically. - If you still want to do it manually - - %strong - = link_to "click here", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" - for instructions - .accept_group + .automerge_widget.can_be_merged.hide + .bs-callout.bs-callout-success.clearfix + = form_for [:automerge, @project, @merge_request], remote: true, method: :get do |f| + %h4 + You can accept this request automatically. + %p + If you still want to do it manually - + %strong + = link_to "click here", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" + for instructions. + + %br + If you want to modify merge commit message - + %strong + = link_to "click here", "#", class: "modify-merge-commit-link js-toggle-visibility-link", title: "Modify merge commit message" + + .js-toggle-visibility-container.hide + .form-group + = label_tag :merge_commit_message, "Commit message", class: 'control-label' + .col-sm-10 + = text_area_tag :merge_commit_message, @merge_request.merge_commit_message, class: "form-control js-gfm-input", rows: 14, required: true + %p.hint + The recommended maximum line length is 52 characters for the first line and 72 characters for all following lines. + + .accept-group + .pull-left = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request" - - unless @merge_request.disallow_source_branch_removal? - .remove_branch_holder - = label_tag :should_remove_source_branch, class: "checkbox" do - = check_box_tag :should_remove_source_branch - Remove source-branch - .clearfix + - unless @merge_request.disallow_source_branch_removal? + .remove_branch_holder.pull-left + = label_tag :should_remove_source_branch, class: "checkbox" do + = check_box_tag :should_remove_source_branch + Remove source-branch - .automerge_widget.no_satellite{style: "display:none"} - .alert.alert-error + .automerge_widget.no_satellite.hide + .bs-callout.bs-callout-danger %span %strong This repository does not have satellite. Ask an administrator to fix this issue - .automerge_widget.cannot_be_merged{style: "display:none"} - .alert.alert-disabled + .automerge_widget.cannot_be_merged.hide + .bs-callout.bs-callout-disabled + %h4 + This request can't be merged with GitLab. %span - = link_to "Show how to merge", "#modal_merge_info", class: "how_to_merge_link btn padded", title: "How To Merge", "data-toggle" => "modal" - - %strong This request can't be merged with GitLab. You should do it manually + You should do it manually with + %strong + = link_to "command line", "#modal_merge_info", class: "how_to_merge_link", title: "How To Merge", "data-toggle" => "modal" .automerge_widget.unchecked - .alert + .bs-callout.bs-callout-warning %strong - %i.icon-refresh + %i.icon-spinner.icon-spin Checking for ability to automatically merge… - .automerge_widget.already_cannot_be_merged{style: "display:none"} - .alert.alert-info + .automerge_widget.already_cannot_be_merged.hide + .bs-callout.bs-callout-info %strong This merge request already can not be merged. Try to reload page. .merge-in-progress.hide - %span.cgray - %i.icon-refresh.icon-spin + .bs-callout.bs-callout-success + %i.icon-spinner.icon-spin Merge is in progress. Please wait. Page will be automatically reloaded. diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml index 1f750e22c6571439914c2f3e336309f7ab5033dd..b4f648ab197fdc75f4c79dcc6c73a2370bbeb00c 100644 --- a/app/views/projects/merge_requests/show/_mr_box.html.haml +++ b/app/views/projects/merge_requests/show/_mr_box.html.haml @@ -1,46 +1,39 @@ -.ui-box.ui-box-show - .ui-box-head - %h4.box-title - = gfm escape_once(@merge_request.title) - - if @merge_request.merged? - .success.status_info - %i.icon-ok - Merged - - elsif @merge_request.closed? - .error.status_info Closed +.issue-box + %h4.title + = gfm escape_once(@merge_request.title) - .ui-box-body - %div - %cite.cgray - Created at #{@merge_request.created_at.stamp("Aug 21, 2011")} by #{link_to_member(@project, @merge_request.author)} - - if @merge_request.assignee - \, currently assigned to #{link_to_member(@project, @merge_request.assignee)} - - if @merge_request.milestone + .context + %cite.cgray + Created by #{link_to_member(@project, @merge_request.author)}. + - if @merge_request.assignee + Currently assigned to #{link_to_member(@project, @merge_request.assignee)}. + - if @merge_request.milestone + .pull-right - milestone = @merge_request.milestone - %cite.cgray and attached to milestone + %cite.cgray Attached to milestone %strong= link_to_gfm truncate(milestone.title, length: 20), project_milestone_path(milestone.project, milestone) - if @merge_request.description.present? - .ui-box-bottom + .description .wiki = preserve do = markdown @merge_request.description - if @merge_request.closed? - .ui-box-bottom.alert-error + .description.alert-danger %span %i.icon-remove Closed by #{link_to_member(@project, @merge_request.closed_event.author)} - %small #{time_ago_in_words(@merge_request.closed_event.created_at)} ago. + #{time_ago_with_tooltip(@merge_request.closed_event.created_at)}. - if @merge_request.merged? - .ui-box-bottom.alert-success + .description.alert-success %span %i.icon-ok Merged by #{link_to_member(@project, @merge_request.merge_event.author)} - #{time_ago_in_words(@merge_request.merge_event.created_at)} ago. + #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}. - if !@closes_issues.empty? && @merge_request.opened? - .ui-box-bottom.alert-info + .description.alert-info %span %i.icon-ok Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'} diff --git a/app/views/projects/merge_requests/show/_mr_ci.html.haml b/app/views/projects/merge_requests/show/_mr_ci.html.haml index 9b15c4526e943ef49abc54b66daf37ae34877aed..915d24dd021a32203ce8066c3c52e86faed9124c 100644 --- a/app/views/projects/merge_requests/show/_mr_ci.html.haml +++ b/app/views/projects/merge_requests/show/_mr_ci.html.haml @@ -8,7 +8,7 @@ .ci_widget.ci-failed{style: "display:none"} - .alert.alert-error + .alert.alert-danger %i.icon-remove %strong CI build failed for #{@merge_request.last_commit_short_sha}. @@ -16,20 +16,20 @@ - [:running, :pending].each do |status| .ci_widget{class: "ci-#{status}", style: "display:none"} - .alert + .alert.alert-warning %i.icon-time %strong CI build #{status} for #{@merge_request.last_commit_short_sha}. = link_to "Build page", ci_build_details_path(@merge_request) .ci_widget - .alert + .alert.alert-warning %strong - %i.icon-refresh + %i.icon-spinner Checking for CI status for #{@merge_request.last_commit_short_sha} .ci_widget.ci-error{style: "display:none"} - .alert.alert-error + .alert.alert-danger %i.icon-remove %strong Cannot connect to CI server. Please check your setting diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index 096a91676455682750feb3d7984fadf7ff28ff44..08a3fdf869a6b742991034cb58143efb6d718082 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -1,16 +1,20 @@ %h3.page-title - = "Merge Request ##{@merge_request.iid}:" - - -if @merge_request.for_fork? - %span.label-branch - %span.label-project= truncate(@merge_request.source_project.path_with_namespace, length: 25) - #{@merge_request.source_branch} - → - %span.label-branch= @merge_request.target_branch + = "Merge Request ##{@merge_request.iid}" + %small + created #{time_ago_with_tooltip(@merge_request.created_at)} + + - if @merge_request.merged? + %span.state-label.state-label-green + %i.icon-ok + Merged + - elsif @merge_request.closed? + %span.state-label.state-label-red + Closed - else - %span.label-branch= @merge_request.source_branch - → - %span.label-branch= @merge_request.target_branch + %span.state-label.state-label-green + Open + + %span.pull-right - if can?(current_user, :modify_merge_request, @merge_request) @@ -24,15 +28,28 @@ %li= link_to "Email Patches", project_merge_request_path(@project, @merge_request, format: :patch) %li= link_to "Plain Diff", project_merge_request_path(@project, @merge_request, format: :diff) - = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn grouped btn-close", title: "Close merge request" + = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: { state_event: :close }), method: :put, class: "btn grouped btn-close", title: "Close merge request" = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn grouped", id:"edit_merge_request" do %i.icon-edit Edit -.pull-right - .span3#votes= render 'votes/votes_block', votable: @merge_request +.votes-holder + #votes= render 'votes/votes_block', votable: @merge_request .back-link = link_to project_merge_requests_path(@project) do ← To merge requests + + %span.prepend-left-20.monospace + -if @merge_request.for_fork? + %span + %strong + #{truncate(@merge_request.source_project_path, length: 25)}: + #{@merge_request.source_branch} + → + %span= @merge_request.target_branch + - else + %span= @merge_request.source_branch + → + %spanh= @merge_request.target_branch diff --git a/app/views/projects/merge_requests/show/_no_accept.html.haml b/app/views/projects/merge_requests/show/_no_accept.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..203e2612de476750cfd59c3adfc6a0fd02641f94 --- /dev/null +++ b/app/views/projects/merge_requests/show/_no_accept.html.haml @@ -0,0 +1,12 @@ +.alert.alert-danger + %p + This merge request can not be accepted because branch + - unless @merge_request.source_branch_exists? + %span.label.label-inverse= @merge_request.source_branch + does not exist in + %span.label.label-info= @merge_request.source_project_path + - else + %span.label.label-inverse= @merge_request.target_branch + does not exist in + %span.label.label-info= @merge_request.target_project_path + %strong Please close this merge request or change branches with existing one diff --git a/app/views/projects/merge_requests/update_branches.js.haml b/app/views/projects/merge_requests/update_branches.js.haml index dfccb586ec7afbd0cd83e3362a50144f88deb9a8..ca21b3bc0deb365c4db2f3ecc72576c80cd5e4e4 100644 --- a/app/views/projects/merge_requests/update_branches.js.haml +++ b/app/views/projects/merge_requests/update_branches.js.haml @@ -1,5 +1,9 @@ :plain $(".target_branch").html("#{escape_javascript(options_for_select(@target_branches))}"); - $(".target_branch").trigger("chosen:updated"); + + $('select.target_branch').select2({ + width: 'resolve', + dropdownAutoWidth: true + }); + $(".mr_target_commit").html(""); - $(".target_branch").trigger("change"); diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index 78e4cd2243e0f7897aa643a563a66d144f73a351..d770bb5b3710b52958034f8a9aaa556a4a401b3d 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -1,4 +1,4 @@ -%h3.page-title= @milestone.new_record? ? "New Milestone" : "Edit Milestone ##{@milestone.id}" +%h3.page-title= @milestone.new_record? ? "New Milestone" : "Edit Milestone ##{@milestone.iid}" .back-link = link_to project_milestones_path(@project) do ← To milestones @@ -7,27 +7,27 @@ = form_for [@project, @milestone], html: {class: "new_milestone form-horizontal"} do |f| -if @milestone.errors.any? - .alert.alert-error + .alert.alert-danger %ul - @milestone.errors.full_messages.each do |msg| %li= msg .row - .span6 - .control-group + .col-md-6 + .form-group = f.label :title, "Title", class: "control-label" - .controls - = f.text_field :title, maxlength: 255, class: "input-xlarge" + .col-sm-10 + = f.text_field :title, maxlength: 255, class: "form-control" %p.hint Required - .control-group + .form-group = f.label :description, "Description", class: "control-label" - .controls - = f.text_area :description, maxlength: 2000, class: "input-xlarge", rows: 10 + .col-sm-10 + = f.text_area :description, maxlength: 2000, class: "form-control", rows: 10 %p.hint Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. - .span6 - .control-group + .col-md-6 + .form-group = f.label :due_date, "Due Date", class: "control-label" - .controls= f.hidden_field :due_date - .controls + .col-sm-10= f.hidden_field :due_date + .col-sm-10 .datepicker .form-actions diff --git a/app/views/projects/milestones/_issues.html.haml b/app/views/projects/milestones/_issues.html.haml index bf81cfda45fdf12b1b8fabdb0f5e6392ea0b8ecc..21939ad0132289add1d9ad19a43b7be3586deee9 100644 --- a/app/views/projects/milestones/_issues.html.haml +++ b/app/views/projects/milestones/_issues.html.haml @@ -8,4 +8,4 @@ = link_to_gfm truncate(issue.title, length: 40), [@project, issue] - if issue.assignee .pull-right - = image_tag gravatar_icon(issue.assignee.email, 16), class: "avatar s16" + = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16" diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml index 7f81589406952c181971d55c0e97481782c8c452..8e30a42a608033c9bcbd87b53926183e0ebc1554 100644 --- a/app/views/projects/milestones/_merge_request.html.haml +++ b/app/views/projects/milestones/_merge_request.html.haml @@ -1,5 +1,5 @@ %li = link_to [@project, merge_request] do - %span.badge.badge-info ##{merge_request.id} + %span.badge.badge-info ##{merge_request.iid} – = link_to_gfm truncate(merge_request.title, length: 60), [@project, merge_request] diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml index bc3368b765cf3ff1b41df40e22e08bc0553b494a..4dec3838d97bef90048f27e16ebe39dcb83878aa 100644 --- a/app/views/projects/milestones/_milestone.html.haml +++ b/app/views/projects/milestones/_milestone.html.haml @@ -24,4 +24,4 @@ %span.light #{milestone.percent_complete}% complete .progress.progress-info - .bar{style: "width: #{milestone.percent_complete}%;"} + .progress-bar{style: "width: #{milestone.percent_complete}%;"} diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index ddb46bcb5cb511660c16540df1f8e1ac289de95c..6cfe4d287783181be69db2cc360b877a70758d42 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -8,7 +8,7 @@ New Milestone .row - .span3 + .col-md-3.hidden-sm %ul.nav.nav-pills.nav-stacked %li{class: ("active" if (params[:f] == "active" || !params[:f]))} = link_to project_milestones_path(@project, f: "active") do @@ -19,7 +19,7 @@ %li{class: ("active" if params[:f] == "all")} = link_to project_milestones_path(@project, f: "all") do All - .span9 + .col-md-9 .ui-box %ul.well-list = render @milestones diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index b755a8134191c23e27085346a340664c5f5a0806..e7c3785c0565bee54ec46473f4adab4a0068ba33 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -1,17 +1,23 @@ = render "projects/issues/head" %h3.page-title - Milestone ##{@milestone.id} + Milestone ##{@milestone.iid} %small = @milestone.expires_at + - if @milestone.closed? + %span.state-label.state-label-red Closed + - elsif @milestone.expired? + %span.state-label.state-label-red Expired + - else + %span.state-label.state-label-green Open .pull-right - if can?(current_user, :admin_milestone, @project) = link_to edit_project_milestone_path(@project, @milestone), class: "btn grouped" do %i.icon-edit Edit - if @milestone.active? - = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-remove" + = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-remove grouped" - else - = link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn" + = link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn grouped" - if @milestone.issues.any? && @milestone.can_be_closed? .alert.alert-success @@ -22,17 +28,11 @@ ← To milestones list -.ui-box.ui-box-show - .ui-box-head - %h4.box-title - - if @milestone.closed? - .error.status_info Closed - - elsif @milestone.expired? - .error.status_info Expired +.issue-box + %h4.title + = gfm escape_once(@milestone.title) - = gfm escape_once(@milestone.title) - - .ui-box-body + .context %p Progress: #{@milestone.closed_items_count} closed @@ -40,16 +40,16 @@ #{@milestone.open_items_count} open %span.pull-right= @milestone.expires_at .progress.progress-info - .bar{style: "width: #{@milestone.percent_complete}%;"} + .progress-bar{style: "width: #{@milestone.percent_complete}%;"} - if @milestone.description.present? - .ui-box-bottom + .description = preserve do = markdown @milestone.description -%ul.nav.nav-tabs +%ul.nav.nav-tabs.append-bottom-10 %li.active = link_to '#tab-issues', 'data-toggle' => 'tab' do Issues @@ -72,22 +72,22 @@ .tab-content .tab-pane.active#tab-issues .row - .span4 + .col-md-4 = render('issues', title: 'Unstarted Issues (open and unassigned)', issues: @issues.opened.unassigned) - .span4 + .col-md-4 = render('issues', title: 'Ongoing Issues (open and assigned)', issues: @issues.opened.assigned) - .span4 + .col-md-4 = render('issues', title: 'Completed Issues (closed)', issues: @issues.closed) .tab-pane#tab-merge-requests .row - .span6 + .col-md-6 .ui-box .title Open %ul.well-list - @merge_requests.opened.each do |merge_request| = render 'merge_request', merge_request: merge_request - .span6 + .col-md-6 .ui-box .title Closed %ul.well-list @@ -99,7 +99,7 @@ - @users.each do |user| %li = link_to user, title: user.name, class: "dark" do - = image_tag gravatar_icon(user.email, 32), class: "avatar s32" + = image_tag avatar_icon(user.email, 32), class: "avatar s32" %strong= truncate(user.name, lenght: 40) %br %small.cgray= user.username diff --git a/app/views/projects/network/_head.html.haml b/app/views/projects/network/_head.html.haml index 2790ed6f59483725687039d95ef88a537ba4fcbb..d61ff789b6530e2d509534127f461767e2e187af 100644 --- a/app/views/projects/network/_head.html.haml +++ b/app/views/projects/network/_head.html.haml @@ -1,23 +1,15 @@ -.clearfix - .pull-left +.row.append-bottom-20 + .col-md-2.append-bottom-10 = render partial: 'shared/ref_switcher', locals: {destination: 'graph'} - .pull-left - = form_tag project_network_path(@project, @id), method: :get do |f| - .control-group - = label_tag :filter_ref, "Begin with the selected commit", class: 'control-label light' - .controls - = check_box_tag :filter_ref, 1, @options[:filter_ref] - - @options.each do |key, value| - = hidden_field_tag(key, value, id: nil) unless key == "filter_ref" - - .search.pull-right - = form_tag project_network_path(@project, @id), method: :get do |f| - .control-group - = label_tag :search , "Looking for commit:", class: 'control-label light' - .controls - = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: "search-input input-xlarge" - = button_tag type: 'submit', class: 'btn vtop' do - %i.icon-search - - @options.each do |key, value| - = hidden_field_tag(key, value, id: nil) unless key == "extended_sha1" + .col-md-10 + = form_tag project_network_path(@project, @id), method: :get, class: 'form-inline network-form' do |f| + = label_tag :extended_sha1 , "Looking for", class: 'light inline-label' + = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: "search-input form-control input-mx-250" + = button_tag type: 'submit', class: 'btn btn-success' do + %i.icon-search + .inline.prepend-left-20 + .checkbox.light + = label_tag :filter_ref do + = check_box_tag :filter_ref, 1, @options[:filter_ref] + %span Begin with the selected commit diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index 492f77341f7592abd490e369ea4d536260f9a222..da0cfa84c2d2f31c7d3bfd10ae3dc5fd55b02f64 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -3,7 +3,7 @@ .tip You can move around the graph by using the arrow keys. .network-graph - .loading.loading-gray + = spinner :javascript new Network({ diff --git a/app/views/projects/network/show.json.erb b/app/views/projects/network/show.json.erb index f0bedcf2d352918e471d6e128286866314e43597..dc82adcb2c640fd6c9da4208cfead0926eb9b302 100644 --- a/app/views/projects/network/show.json.erb +++ b/app/views/projects/network/show.json.erb @@ -9,7 +9,7 @@ author: { name: c.author_name, email: c.author_email, - icon: gravatar_icon(c.author_email, 20) + icon: avatar_icon(c.author_email, 20) }, time: c.time, space: c.spaces.first, diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 0213576927bc5710bb66b835cb8d8ff084a48442..9ee54fef062a69816a88d4cede7c6bb4861e49ba 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -3,69 +3,66 @@ = render 'projects/errors' .project-edit-content - = form_for @project, remote: true do |f| - .control-group.project-name-holder - = f.label :name do - %strong Project name is - .controls - = f.text_field :name, placeholder: "Example Project", class: "input-xlarge", tabindex: 1, autofocus: true - %span.help-inline + = form_for @project, remote: true, html: { class: 'new_project form-horizontal' } do |f| + .form-group.project-name-holder + = f.label :name, class: 'control-label' do + %strong Project name + .col-sm-10 + = f.text_field :name, placeholder: "Example Project", class: "form-control", tabindex: 1, autofocus: true + .help-inline = link_to "#", class: 'js-toggle-visibility-link' do %span Customize repository name? - .control-group.js-toggle-visibility-container.hide - = f.label :path do + .form-group.js-toggle-visibility-container.hide + = f.label :path, class: 'control-label' do %span Repository name - .controls - .input-append - = f.text_field :path - %span.add-on .git + .col-sm-10 + .input-group + = f.text_field :path, class: 'form-control' + %span.input-group-addon .git - if current_user.can_select_namespace? - .control-group - = f.label :namespace_id do + .form-group + = f.label :namespace_id, class: 'control-label' do %span Namespace - .controls - = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'chosen', tabindex: 2} + .col-sm-10 + = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'select2', tabindex: 2} - .control-group - .controls + .form-group + .col-sm-2 + .col-sm-10 = link_to "#", class: 'appear-link' do %i.icon-upload-alt %span Import existing repository? - .control-group.appear-data.import-url-data - = f.label :import_url do + .form-group.appear-data.import-url-data + = f.label :import_url, class: 'control-label' do %span Import existing repo - .controls - = f.text_field :import_url, class: 'input-xlarge', placeholder: 'https://github.com/randx/six.git' + .col-sm-10 + = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git' .light URL must be cloneable - .control-group - = f.label :description do + .form-group + = f.label :description, class: 'control-label' do Description %span.light (optional) - .controls - = f.text_area :description, placeholder: "awesome project", class: "input-xlarge", rows: 3, maxlength: 250, tabindex: 3 - .control-group.project-public-holder - = f.label :public do - %span Public project - .controls - = f.check_box :public, { checked: Gitlab.config.gitlab.default_projects_features.public }, true, false - %span.help-inline Make project visible to everyone + .col-sm-10 + = f.text_area :description, placeholder: "Awesome project", class: "form-control", rows: 3, maxlength: 250, tabindex: 3 + = render "visibility_level", f: f, visibility_level: gitlab_config.default_projects_features.visibility_level, can_change_visibility_level: true .form-actions = f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4 - if current_user.can_create_group? .pull-right - .controls.light + .light Need a group for several dependent projects? = link_to new_group_path, class: "btn btn-tiny" do Create a group .save-project-loader.hide %center - = image_tag "ajax_loader.gif" - %h3 Creating project & repository. + %h2 + %i.icon-spinner.icon-spin + Creating project & repository. %p Please wait a moment, this page will automatically refresh when ready. diff --git a/app/views/projects/new_tree/show.html.haml b/app/views/projects/new_tree/show.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..9d7c7afbeac2c4cba10624998a3054eb851a4efc --- /dev/null +++ b/app/views/projects/new_tree/show.html.haml @@ -0,0 +1,53 @@ +%h3.page-title New file +%hr +.file-editor + = form_tag(project_new_tree_path(@project, @id), method: :put, class: "form-horizontal") do + .form-group.commit_message-group + = label_tag 'file_name', class: "control-label" do + File name + .col-sm-10 + .input-group + %span.input-group-addon + = @path[-1] == "/" ? @path : @path + "/" + = text_field_tag 'file_name', params[:file_name], placeholder: "sample.rb", required: true, class: 'form-control' + %span.input-group-addon + on + %span= @ref + + .form-group.commit_message-group + = label_tag :encoding, class: "control-label" do + Encoding + .col-sm-10 + = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control' + + .form-group.commit_message-group + = label_tag 'commit_message', class: "control-label" do + Commit message + .col-sm-10 + = text_area_tag 'commit_message', params[:commit_message], placeholder: "Added new file", required: true, rows: 3, class: 'form-control' + + .file-holder + .file-title + %i.icon-file + .file-content.code + %pre#editor= params[:content] + + .form-actions + = hidden_field_tag 'content', '', id: "file-content" + .commit-button-annotation + = button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-create' + .message + to branch + %strong= @ref + = link_to "Cancel", project_tree_path(@project, @id), class: "btn btn-cancel", data: { confirm: leave_edit_message} + +:javascript + ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict") + var editor = ace.edit("editor"); + + disableButtonIfEmptyField("#commit_message", ".js-commit-button"); + + $(".js-commit-button").click(function(){ + $("#file-content").val(editor.getValue()); + $(".file-editor form").submit(); + }); diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..936dbb354cd501c11c345e4c02b0e01eb4a2fef9 --- /dev/null +++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml @@ -0,0 +1,34 @@ +- note1 = notes1.first # example note +- note2 = notes2.first # example note +%tr.notes_holder + -# Check if line want not changed since comment was left + /- if !defined?(line1) || line1 == note1.diff_line + - if note1 + %td.notes_content + %ul.notes{ rel: note1.discussion_id } + = render notes1 + = render "projects/notes/discussion_reply_button", note: note1 + %td.notes_line2 + %span.btn.disabled.parallel-comment + %i.icon-comment + = notes1.count + - else + %td= "" + %td= "" + + %td= "" + + -# Check if line want not changed since comment was left + /- if !defined?(line2) || line2 == note2.diff_line + - if note2 + %td.notes_line + %span.btn.disabled.parallel-comment + %i.icon-comment + = notes2.count + %td.notes_content + %ul.notes{ rel: note2.discussion_id } + = render notes2 + = render "projects/notes/discussion_reply_button", note: note2 + - else + %td= "" + %td= "" diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml index a964d86a8dc31a85ab4b2237150564589ebbc3bd..ee65ae1e2f5522c63e512e01665f7a58ee5255b2 100644 --- a/app/views/projects/notes/_discussion.html.haml +++ b/app/views/projects/notes/_discussion.html.haml @@ -8,7 +8,7 @@ = link_to "javascript:;", class: "js-details-target turn-off js-toggler-target" do %i.icon-eye-open Show discussion - = image_tag gravatar_icon(note.author_email), class: "avatar s32" + = image_tag avatar_icon(note.author_email), class: "avatar s32" %div = link_to_member(@project, note.author, avatar: false) - if note.for_merge_request? @@ -32,8 +32,7 @@ last updated by = link_to_member(@project, last_note.author, avatar: false) %span.discussion-last-update - = time_ago_in_words(last_note.updated_at) - ago + #{time_ago_with_tooltip(last_note.updated_at, 'bottom', 'discussion_updated_ago')} .discussion-body - if note.for_diff_line? - if note.active? diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index 7add2921830fd24916657b4742556f01576a7459..bcaedc8bd7ceac0269af462211a477ef7cf798e3 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -1,5 +1,4 @@ -= form_for [@project, @note], remote: true, html: { multipart: true, id: nil, class: "new_note js-new-note-form common-note-form" } do |f| - += form_for [@project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form" }, authenticity_token: true do |f| = note_target_fields = f.hidden_field :commit_id = f.hidden_field :line_code @@ -7,10 +6,12 @@ = f.hidden_field :noteable_type .note_text_and_preview.js-toggler-container - %a.js-note-preview-button.js-toggler-target.turn-off{ href: "javascript:;", title: "Preview", data: {url: preview_project_notes_path(@project)} } + %a.btn.js-note-preview-button.js-toggler-target.turn-off{ href: "javascript:;", data: {url: preview_project_notes_path(@project)} } %i.icon-eye-open - %a.js-note-edit-button.js-toggler-target.turn-off{ href: "javascript:;", title: "Edit" } + Preview + %a.btn.btn-primary.js-note-edit-button.js-toggler-target.turn-off{ href: "javascript:;" } %i.icon-edit + Write = f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input turn-on' .note_preview.js-note-preview.turn-off @@ -27,11 +28,11 @@ %a.btn.grouped.js-close-discussion-note-form Cancel .note-form-option - %a.choose-btn.btn.btn-small.js-choose-note-attachment-button + %a.choose-btn.btn.js-choose-note-attachment-button %i.icon-paper-clip %span Choose File ... %span.file_name.js-attachment-filename File name... - = f.file_field :attachment, class: "js-note-attachment-input hide" + = f.file_field :attachment, class: "js-note-attachment-input hidden" .clearfix diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 324b698f3b581cd4accfcf3b4ccfb8d5e545bb55..fd2a3f4367475e050db5461ac2fb360da8104db1 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -10,10 +10,10 @@ %i.icon-edit Edit - = link_to project_note_path(@project, note), title: "Remove comment", method: :delete, confirm: 'Are you sure you want to remove this comment?', remote: true, class: "danger js-note-delete" do + = link_to project_note_path(@project, note), title: "Remove comment", method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: "danger js-note-delete" do %i.icon-trash.cred Remove - = image_tag gravatar_icon(note.author_email), class: "avatar s32" + = image_tag avatar_icon(note.author_email), class: "avatar s32" = link_to_member(@project, note.author, avatar: false) %span.note-last-update = note_timestamp(note) @@ -34,10 +34,10 @@ = markdown(note.note) .note-edit-form - = form_for note, url: project_note_path(@project, note), method: :put, remote: 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' - .form-actions + .form-actions.clearfix = f.submit 'Save changes', class: "btn btn-primary btn-save" .note-form-option @@ -46,7 +46,7 @@ %span Choose File ... %span.file_name.js-attachment-filename File name... - = f.file_field :attachment, class: "js-note-attachment-input hide" + = f.file_field :attachment, class: "js-note-attachment-input hidden" = link_to 'Cancel', "#", class: "btn btn-cancel note-edit-cancel" @@ -61,6 +61,6 @@ %i.icon-paper-clip = note.attachment_identifier = link_to delete_attachment_project_note_path(@project, note), - title: "Delete this attachment", method: :delete, remote: true, confirm: 'Are you sure you want to remove the attachment?', class: "danger js-note-attachment-delete" do + title: "Delete this attachment", method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: "danger js-note-attachment-delete" do %i.icon-trash.cred .clear diff --git a/app/views/projects/notes/_notes.html.haml b/app/views/projects/notes/_notes.html.haml index ac8901fe70458bcdbf3bd1cb5203792a05873de1..ca60dd239b234033e6106d5466ce86689ba83d52 100644 --- a/app/views/projects/notes/_notes.html.haml +++ b/app/views/projects/notes/_notes.html.haml @@ -4,7 +4,7 @@ - if note_for_main_target?(note) = render discussion_notes - else - = render 'discussion', discussion_notes: discussion_notes + = render 'projects/notes/discussion', discussion_notes: discussion_notes - else - @notes.each do |note| - next unless note.author diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml index ac28c7432ef98eda09ad0b7971a268c448bd1e59..3bd592e398290ddeabe6ee2a21066f2ddf900e6c 100644 --- a/app/views/projects/notes/_notes_with_form.html.haml +++ b/app/views/projects/notes/_notes_with_form.html.haml @@ -1,4 +1,5 @@ -%ul#notes-list.notes +%ul#notes-list.notes.main-notes-list + = render "projects/notes/notes" .js-notes-busy .js-main-target-form @@ -6,4 +7,4 @@ = render "projects/notes/form" :javascript - NoteList.init("#{@target_id}", "#{@target_type}", "#{project_notes_path(@project)}"); + new Notes("#{project_notes_path(target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}) diff --git a/app/views/projects/notes/create.js.haml b/app/views/projects/notes/create.js.haml deleted file mode 100644 index c113b3482ecf04b26a83d75fbf556860f99b8d65..0000000000000000000000000000000000000000 --- a/app/views/projects/notes/create.js.haml +++ /dev/null @@ -1,18 +0,0 @@ -- if @note.valid? - var noteHtml = "#{escape_javascript(render @note)}"; - - - if note_for_main_target?(@note) - NoteList.appendNewNote(#{@note.id}, noteHtml); - - else - :plain - var firstDiscussionNoteHtml = "#{escape_javascript(render "projects/notes/diff_notes_with_reply", notes: [@note])}"; - NoteList.appendNewDiscussionNote("#{@note.discussion_id}", - firstDiscussionNoteHtml, - noteHtml); - -- else - var errorsHtml = "#{escape_javascript(render 'projects/notes/form_errors', note: @note)}"; - - if note_for_main_target?(@note) - NoteList.errorsOnForm(errorsHtml); - - else - NoteList.errorsOnForm(errorsHtml, "#{@note.discussion_id}"); diff --git a/app/views/projects/notes/index.js.haml b/app/views/projects/notes/index.js.haml deleted file mode 100644 index 6c4ed203497228da165b276c8c166b090808c8c1..0000000000000000000000000000000000000000 --- a/app/views/projects/notes/index.js.haml +++ /dev/null @@ -1,4 +0,0 @@ -- unless @notes.blank? - var notesHtml = "#{escape_javascript(render 'projects/notes/notes')}"; - - new_note_ids = @notes.map(&:id) - NoteList.setContent(#{new_note_ids}, notesHtml); diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index 8930ec4b30a8022784a22a6348214a494857dfb5..a32679a18db6cc8d62b43e94823725b4e481e74f 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -1,9 +1,9 @@ = render "projects/commits/head" .row - .span3 + .col-md-3 = render "projects/branches/filter" - .span9 - .alert.alert-info + .col-md-9 + .bs-callout.bs-callout-info %p Protected branches designed to prevent push for all except #{link_to "masters", help_permissions_path, class: "vlink"}. %p This ability allows: %ul @@ -12,18 +12,18 @@ %p Read more about project permissions #{link_to "here", help_permissions_path, class: "underlined_link"} - if can? current_user, :admin_project, @project - = form_for [@project, @protected_branch] do |f| + = form_for [@project, @protected_branch], html: { class: 'form-horizontal' } do |f| -if @protected_branch.errors.any? - .alert.alert-error + .alert.alert-danger %ul - @protected_branch.errors.full_messages.each do |msg| %li= msg - .entry.clearfix - = f.label :name, "Branch" - .span3 - = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "chosen span3"}) - + .form-group + = f.label :name, "Branch", class: 'control-label' + .col-sm-10 + = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "select2"}) + .form-actions = f.submit 'Protect', class: "btn-create btn" - unless @branches.empty? %h5 Already Protected: @@ -39,15 +39,13 @@ %i.icon-lock .pull-right - if can? current_user, :admin_project, @project - = link_to 'Unprotect', [@project, branch], confirm: 'Branch will be writable for developers. Are you sure?', method: :delete, class: "btn btn-remove btn-small" + = link_to 'Unprotect', [@project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-small" - if commit = branch.commit = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do = commit.short_id %span.light = gfm escape_once(truncate(commit.title, length: 40)) - %span - = time_ago_in_words(commit.committed_date) - ago + #{time_ago_with_tooltip(commit.committed_date)} - else (branch was removed from repository) diff --git a/app/views/projects/refs/logs_tree.js.haml b/app/views/projects/refs/logs_tree.js.haml index 213c54f5f75519cbed915febcd83184b4897da81..e7343e0997f7df339ea9efdd59caf200663a3ded 100644 --- a/app/views/projects/refs/logs_tree.js.haml +++ b/app/views/projects/refs/logs_tree.js.haml @@ -5,5 +5,5 @@ :plain var row = $("table.table_#{@hex_path} tr.file_#{hexdigest(file_name)}"); - row.find("td.tree_time_ago").html('#{escape_javascript time_ago_in_words(commit.committed_date)} ago'); + row.find("td.tree_time_ago").html('#{escape_javascript time_ago_with_tooltip(commit.committed_date)}'); row.find("td.tree_commit").html('#{escape_javascript render("projects/tree/tree_commit_column", commit: commit)}'); diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..b03feded0a7ac5869cbcf6719a49f0784b60a3e2 --- /dev/null +++ b/app/views/projects/repositories/_download_archive.html.haml @@ -0,0 +1,37 @@ +- ref = ref || nil +- btn_class = btn_class || '' +- split_button = split_button || false +- if split_button == true + %span.btn-group{class: btn_class} + = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn' do + %i.icon-download-alt + %span Download zip + %a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } + %span.caret + %span.sr-only + Select Archive Format + %ul.dropdown-menu{ role: 'menu' } + %li + = link_to archive_project_repository_path(@project, ref: ref, format: 'zip') do + %i.icon-download-alt + %span Download zip + %li + = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz') do + %i.icon-download-alt + %span Download tar.gz + %li + = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.bz2') do + %i.icon-download-alt + %span Download tar.bz2 + %li + = link_to archive_project_repository_path(@project, ref: ref, format: 'tar') do + %i.icon-download-alt + %span Download tar +- else + %span.btn-group{class: btn_class} + = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn' do + %i.icon-download-alt + %span zip + = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), class: 'btn' do + %i.icon-download-alt + %span tar.gz \ No newline at end of file diff --git a/app/views/projects/repositories/_feed.html.haml b/app/views/projects/repositories/_feed.html.haml index faa3ed1746c1cd1f910756a98ee43ffbc5721953..c77ffff43fe02df137a9b432996b9538cfa4dc86 100644 --- a/app/views/projects/repositories/_feed.html.haml +++ b/app/views/projects/repositories/_feed.html.haml @@ -11,9 +11,8 @@ %div = link_to project_commits_path(@project, commit.id) do %code= commit.short_id - = image_tag gravatar_icon(commit.author_email), class: "", width: 16, alt: '' + = image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: '' = gfm escape_once(truncate(commit.title, length: 40)) %td %span.pull-right.cgray - = time_ago_in_words(commit.committed_date) - ago + = time_ago_with_tooltip(commit.committed_date) diff --git a/app/views/projects/repositories/stats.html.haml b/app/views/projects/repositories/stats.html.haml index 454296e82fd2ac6dd31e4e1f9e5a941e03792519..fd353978572c5b8dbfba9b655197b80b976c3949 100644 --- a/app/views/projects/repositories/stats.html.haml +++ b/app/views/projects/repositories/stats.html.haml @@ -1,6 +1,6 @@ = render "projects/commits/head" .row - .span6 + .col-md-6 %div#activity-chart.chart %hr %p @@ -14,12 +14,12 @@ %span= @stats.authors_count - .span6 + .col-md-6 %h4 Top 50 Committers: %ol.styled - @stats.authors[0...50].each do |author| %li - = image_tag gravatar_icon(author.email, 16), class: 'avatar s16', alt: '' + = image_tag avatar_icon(author.email, 16), class: 'avatar s16', alt: '' = author.name %small.light= author.email .pull-right diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index a099193cb6e762983ffb374afe97a90208acf2da..a3b5296ed1e3cd90b1ea2913971e757643a4bebd 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -1,11 +1,6 @@ %h3.page-title - - if @service.activated? - %span.cgreen - %i.icon-circle - - else - %span.cgray - %i.icon-circle-blank = @service.title + = boolean_to_icon @service.activated? %p= @service.description @@ -15,17 +10,17 @@ %hr -= form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put) do |f| += form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |f| - if @service.errors.any? - .alert.alert-error + .alert.alert-danger %ul - @service.errors.full_messages.each do |msg| %li= msg - .control-group + .form-group = f.label :active, "Active", class: "control-label" - .controls + .col-sm-10 = f.check_box :active - @service.fields.each do |field| @@ -33,11 +28,13 @@ - type = field[:type] - placeholder = field[:placeholder] - .control-group + .form-group = f.label name, class: "control-label" - .controls + .col-sm-10 - if type == 'text' - = f.text_field name, class: "input-xlarge", placeholder: placeholder + = f.text_field name, class: "form-control", placeholder: placeholder + - elsif type == 'textarea' + = f.text_area name, rows: 5, class: "form-control", placeholder: placeholder - elsif type == 'checkbox' = f.check_box name diff --git a/app/views/projects/services/index.html.haml b/app/views/projects/services/index.html.haml index 82b85a18acdf44765d677a73735ec2b50ae1ef72..7271dd830ca76f2e9b73cce73dbd10ff5170c31c 100644 --- a/app/views/projects/services/index.html.haml +++ b/app/views/projects/services/index.html.haml @@ -1,17 +1,13 @@ -%h3.page-title Services -%p.light Services allow you to integrate GitLab with other applications +%h3.page-title Project services +%p.light Project services allow you to integrate GitLab with other applications %hr %ul.bordered-list - - @services.each do |service| + - @services.sort_by(&:title).each do |service| %li %h4 - - if service.activated? - %span.cgreen - %i.icon-circle - - else - %span.cgray - %i.icon-circle-blank = link_to edit_project_service_path(@project, service.to_param) do = service.title + .pull-right + = boolean_to_icon service.activated? %p= service.description diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 06ca5169dff0e7a682eb9ea52f42f10756fde061..32a42916bd652d3c3ad4da47b82ded502a788712 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,25 +1,52 @@ -= render 'clone_panel' += render "home_panel" .row - .span9 + .col-md-9 = render "events/event_last_push", event: @last_push = render 'shared/event_filter' .content_list - .loading.hide - .span3 - .light-well - %h3.page-title - = @project.name - - if @project.description.present? - %p.light= @project.description - - %hr - %p - %p - %span.light Repo size is - #{@project.repository.size} MB + = spinner + .col-md-3.project-side.hidden-sm + .clearfix + - if @project.archived? + .alert + %h5 + %i.icon-warning-sign + Archived project! + %p Repository is read-only + + + - if @project.forked_from_project + .alert.alert-success + %i.icon-code-fork.project-fork-icon + Forked from: + %br + = link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project) + - unless @project.empty_repo? + - if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace + - if current_user.already_forked?(@project) + = link_to project_path(current_user.fork_of(@project)), class: 'btn btn-block' do + %i.icon-compass + Go to fork + - else + = link_to fork_project_path(@project), title: "Fork", class: "btn btn-block", method: "POST" do + %i.icon-code-fork + Fork repository + + - if can? current_user, :download_code, @project + = render 'projects/repositories/download_archive', btn_class: 'btn-block btn-group-justified', split_button: true + + = link_to project_compare_index_path(@project, from: @repository.root_ref, to: @ref || @repository.root_ref), class: 'btn btn-block' do + Compare code + + - if @repository.readme + - readme = @repository.readme + = link_to project_blob_path(@project, tree_join(@repository.root_ref, readme.name)), class: 'btn btn-block' do + = readme.name + + .prepend-top-10 %p - %span.light Created at + %span.light Created on #{@project.created_at.stamp('Aug 22, 2013')} %p %span.light Owned by @@ -27,19 +54,7 @@ #{link_to @project.group.name, @project.group} Group - else #{link_to @project.owner_name, @project.owner} - - if @project.forked_from_project - %p - %i.icon-code-fork - Forked from: - = link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project) - %hr - %p - = link_to pluralize(@repository.round_commit_count, 'commit'), project_commits_path(@project, @ref || @repository.root_ref) - %p - = link_to pluralize(@repository.branch_names.count, 'branch'), project_branches_path(@project) - %p - = link_to pluralize(@repository.tag_names.count, 'tag'), project_tags_path(@project) - if @project.gitlab_ci? %hr diff --git a/app/views/projects/show.js.haml b/app/views/projects/show.js.haml deleted file mode 100644 index 511f278929e3fa818793bd14a8e199b8c803b983..0000000000000000000000000000000000000000 --- a/app/views/projects/show.js.haml +++ /dev/null @@ -1,2 +0,0 @@ -:plain - Pager.append(#{@events.count}, "#{escape_javascript(render(@events))}"); diff --git a/app/views/projects/snippets/_blob.html.haml b/app/views/projects/snippets/_blob.html.haml index f14a2bd4ec004b034f9193dae47016a4135f90ab..af326a1a99affcefafb5cb75753eae8e9a04fee0 100644 --- a/app/views/projects/snippets/_blob.html.haml +++ b/app/views/projects/snippets/_blob.html.haml @@ -6,10 +6,5 @@ .btn-group.tree-btn-group.pull-right - if can?(current_user, :admin_project_snippet, @project) || @snippet.author == current_user = link_to "Edit", edit_project_snippet_path(@project, @snippet), class: "btn btn-tiny", title: 'Edit Snippet' - = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn btn-tiny", target: "_blank" - .file-content.code - - unless @snippet.content.empty? - %div{class: user_color_scheme_class} - = raw @snippet.colorize(formatter: :gitlab) - - else - %p.nothing_here_message Empty file + = link_to "Raw", raw_project_snippet_path(@project, @snippet), class: "btn btn-tiny", target: "_blank" + = render 'snippets/blob_content' diff --git a/app/views/projects/snippets/_form.html.haml b/app/views/projects/snippets/_form.html.haml index d414ee2d1ec70a33dd079652b38779c2e91ae0fe..866346990d3d65ff9430f5058149c59e7b127588 100644 --- a/app/views/projects/snippets/_form.html.haml +++ b/app/views/projects/snippets/_form.html.haml @@ -2,26 +2,23 @@ = @snippet.new_record? ? "New Snippet" : "Edit Snippet ##{@snippet.id}" %hr .snippet-form-holder - = form_for [@project, @snippet], as: :project_snippet, url: url do |f| + = form_for [@project, @snippet], as: :project_snippet, url: url, html: {class: "form-horizontal snippet-form"} do |f| -if @snippet.errors.any? - .alert.alert-error + .alert.alert-danger %ul - @snippet.errors.full_messages.each do |msg| %li= msg - .control-group - = f.label :title - .controls= f.text_field :title, placeholder: "Example Snippet", class: 'input-xlarge', required: true - .control-group - = f.label "Lifetime" - .controls= f.select :expires_at, lifetime_select_options, {}, {class: 'chosen span2'} - .control-group + .form-group + = f.label :title, class: 'control-label' + .col-sm-10= f.text_field :title, placeholder: "Example Snippet", class: 'form-control', required: true + .form-group .file-editor - = f.label :file_name, "File" - .controls + = f.label :file_name, "File", class: 'control-label' + .col-sm-10 .file-holder.snippet .file-title - = f.text_field :file_name, placeholder: "example.rb", class: 'snippet-file-name', required: true + = f.text_field :file_name, placeholder: "example.rb", class: 'form-control snippet-file-name', required: true .file-content.code %pre#editor= @snippet.content = f.hidden_field :content, class: 'snippet-file-content' @@ -31,10 +28,11 @@ = f.submit 'Create snippet', class: "btn-create btn" - else = f.submit 'Save', class: "btn-save btn" - = link_to "Cancel", project_snippets_path(@project), class: "btn btn-cancel" - - unless @snippet.new_record? - = link_to 'Remove snippet', project_snippet_path(@project, @snippet), confirm: 'Are you sure?', method: :delete, class: "btn pull-right btn-remove delete-snippet prepend-left-10", id: "destroy_snippet_#{@snippet.id}" + - unless @snippet.new_record? + .pull-right.prepend-left-20 + = link_to 'Remove snippet', project_snippet_path(@project, @snippet), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn pull-right btn-remove delete-snippet prepend-left-10", id: "destroy_snippet_#{@snippet.id}" + = link_to "Cancel", project_snippets_path(@project), class: "btn btn-cancel" :javascript var editor = ace.edit("editor"); diff --git a/app/views/projects/snippets/_snippet.html.haml b/app/views/projects/snippets/_snippet.html.haml index fc1c0893b083a5ab7848fffe28ee24431ac927d0..b2c35edc44c653e011712dbbb54e26040affeb9a 100644 --- a/app/views/projects/snippets/_snippet.html.haml +++ b/app/views/projects/snippets/_snippet.html.haml @@ -5,17 +5,11 @@ %span.cgray.monospace.tiny.pull-right = snippet.file_name - %small.pull-right.cgray - Expires: - - if snippet.expires_at - = snippet.expires_at.to_date.to_s(:short) - - else - Never - .snippet-info = "##{snippet.id}" %span by - = image_tag gravatar_icon(snippet.author_email), class: "avatar avatar-inline s16" + = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16" = snippet.author_name - %span.light #{time_ago_in_words(snippet.created_at)} ago + %span.light + #{time_ago_with_tooltip(snippet.created_at)} diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml index 4a07ebf7fd9aaac2c348638ae54e176bc7aa3530..ac32f4866b6cc4792689b8f233d4e7c82f02da58 100644 --- a/app/views/projects/snippets/show.html.haml +++ b/app/views/projects/snippets/show.html.haml @@ -5,7 +5,7 @@ = "##{@snippet.id}" %span.light by - = image_tag gravatar_icon(@snippet.author_email), class: "avatar avatar-inline s16" + = image_tag avatar_icon(@snippet.author_email), class: "avatar avatar-inline s16" = @snippet.author_name %div= render 'projects/snippets/blob' %div#notes= render "projects/notes/notes_with_form" diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..70dedcf91551d4e0a43d9bd7cf9863ceac70b794 --- /dev/null +++ b/app/views/projects/tags/_tag.html.haml @@ -0,0 +1,22 @@ +- commit = @repository.commit(tag.target) +%li + %h4 + = link_to project_commits_path(@project, tag.name), class: "" do + %i.icon-tag + = tag.name + .pull-right + %small.cdark + %i.icon-calendar + #{time_ago_with_tooltip(commit.committed_date)} + %p.prepend-left-20 + = link_to commit.short_id(8), project_commit_path(@project, commit), class: "monospace" + – + = link_to_gfm truncate(commit.title, length: 70), project_commit_path(@project, commit.id), class: "cdark" + + %span.pull-right + - if can? current_user, :download_code, @project + = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'grouped btn-group-small' + - if can?(current_user, :admin_project, @project) + = link_to project_tag_path(@project, tag.name), class: 'btn btn-small remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do + %i.icon-trash + diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 5361517b2fcc69ed1552771edf8deeb0e4106e19..2d53a5dd66a3ed6efeaa2034168438f405c518b7 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -13,32 +13,7 @@ - unless @tags.empty? %ul.bordered-list - @tags.each do |tag| - - commit = Commit.new(Gitlab::Git::Commit.new(tag.commit)) - %li - %h4 - = link_to project_commits_path(@project, tag.name), class: "" do - %i.icon-tag - = tag.name - %small - = truncate(tag.message || '', length: 70) - .pull-right - %small.cdark - %i.icon-calendar - = time_ago_in_words(commit.committed_date) - ago - %p.prepend-left-20 - = link_to commit.short_id(8), project_commit_path(@project, commit), class: "monospace" - – - = link_to_gfm truncate(commit.title, length: 70), project_commit_path(@project, commit.id), class: "cdark" - - %span.pull-right - - if can? current_user, :download_code, @project - = link_to archive_project_repository_path(@project, ref: tag.name), class: 'btn grouped btn-small' do - %i.icon-download-alt - Download - - if can?(current_user, :admin_project, @project) - = link_to project_tag_path(@project, tag.name), class: 'btn btn-small remove-row', method: :delete, confirm: 'Removed tag cannot be restored. Are you sure?', remote: true do - %i.icon-trash + = render 'tag', tag: tag = paginate @tags, theme: 'gitlab' diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index 67030785e066b74e0aaef42ce853da8a5c1f417b..a9fd97f8915e11284806d55fc0760ebfd604b591 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -1,15 +1,15 @@ %h3.page-title %i.icon-code-fork New tag -= form_tag project_tags_path, method: :post do - .control-group += form_tag project_tags_path, method: :post, class: "form-horizontal" do + .form-group = label_tag :tag_name, 'Name for new tag', class: 'control-label' - .controls - = text_field_tag :tag_name, nil, placeholder: 'v3.0.1', required: true, tabindex: 1 - .control-group + .col-sm-10 + = text_field_tag :tag_name, nil, placeholder: 'v3.0.1', required: true, tabindex: 1, class: 'form-control' + .form-group = label_tag :ref, 'Create from', class: 'control-label' - .controls - = text_field_tag :ref, nil, placeholder: 'master', required: true, tabindex: 2 + .col-sm-10 + = text_field_tag :ref, nil, placeholder: 'master', required: true, tabindex: 2, class: 'form-control' .light Branch name or commit SHA .form-actions = submit_tag 'Create tag', class: 'btn btn-create', tabindex: 3 diff --git a/app/views/projects/team_members/_form.html.haml b/app/views/projects/team_members/_form.html.haml index 5214a54e9095c7f10c1c6529840b5dc1244bea37..dd059fb99d32c8cd25c3a858051c9a7d6a4d63e6 100644 --- a/app/views/projects/team_members/_form.html.haml +++ b/app/views/projects/team_members/_form.html.haml @@ -1,23 +1,23 @@ %h3.page-title = "New project member(s)" -= form_for @user_project_relation, as: :team_member, url: project_team_members_path(@project) do |f| += form_for @user_project_relation, as: :team_member, url: project_team_members_path(@project), html: { class: "form-horizontal users-project-form" } do |f| -if @user_project_relation.errors.any? - .alert.alert-error + .alert.alert-danger %ul - @user_project_relation.errors.full_messages.each do |msg| %li= msg %p 1. Choose people you want in the project - .control-group - = f.label :user_ids, "People" - .controls + .form-group + = f.label :user_ids, "People", class: 'control-label' + .col-sm-10 = users_select_tag(:user_ids, multiple: true) %p 2. Set access level for them - .control-group - = f.label :project_access, "Project Access" - .controls= select_tag :project_access, options_for_select(Gitlab::Access.options, @user_project_relation.project_access), class: "project-access-select chosen" + .form-group + = f.label :project_access, "Project Access", class: 'control-label' + .col-sm-10= select_tag :project_access, options_for_select(Gitlab::Access.options, @user_project_relation.project_access), class: "project-access-select select2" .form-actions = f.submit 'Add users', class: "btn btn-create" diff --git a/app/views/projects/team_members/_team_member.html.haml b/app/views/projects/team_members/_team_member.html.haml index 916cf2e7a878aa8e1e1ef13277874cf4619d3162..d93bb44ab963159dda376be1365559cc91d90aa6 100644 --- a/app/views/projects/team_members/_team_member.html.haml +++ b/app/views/projects/team_members/_team_member.html.haml @@ -7,9 +7,9 @@ = form_for(member, as: :team_member, url: project_team_member_path(@project, member.user)) do |f| = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2 trigger-submit" - = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do + = link_to project_team_member_path(@project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do %i.icon-minus.icon-white - = image_tag gravatar_icon(user.email, 32), class: "avatar s32" + = image_tag avatar_icon(user.email, 32), class: "avatar s32" %p %strong= user.name %span.cgray= user.username diff --git a/app/views/projects/team_members/import.html.haml b/app/views/projects/team_members/import.html.haml index 1d98b9862105b58204df61ead712ed7828163c36..d3e4a7620180bc3597fc44ef3ac35655007f9434 100644 --- a/app/views/projects/team_members/import.html.haml +++ b/app/views/projects/team_members/import.html.haml @@ -3,10 +3,10 @@ %p.light Only project members will be imported. Group members will be skipped. %hr -= form_tag apply_import_project_team_members_path(@project), method: 'post' do - .padded - = label_tag :source_project_id, "Project" - .controls= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "chosen xxlarge", required: true) += form_tag apply_import_project_team_members_path(@project), method: 'post', class: 'form-horizontal' do + .form-group + = label_tag :source_project_id, "Project", class: 'control-label' + .col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true) .form-actions = submit_tag 'Import project members', class: "btn btn-create" diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml index b179ad7d245f051a95db77c2909996a179375c68..6fee604b554d576042d3a98c9e53e5123339b4cf 100644 --- a/app/views/projects/tree/_blob_item.html.haml +++ b/app/views/projects/tree/_blob_item.html.haml @@ -1,9 +1,8 @@ %tr{ class: "tree-item #{tree_hex_class(blob_item)}" } %td.tree-item-file-name = tree_icon(type) - %span= link_to truncate(blob_item.name, length: 40), project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)) + %span.str-truncated + = link_to blob_item.name, project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)) %td.tree_time_ago.cgray - %span.log_loading.hide - Loading commit data... - = image_tag "ajax_loader_tree.gif", width: 14 + = render 'spinner' %td.tree_commit{ colspan: 2 } diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml index 98bacb495628429388842d90c79ee4d335b92c8b..ab572f2e97b1fa2671aca786f9d17583f028b345 100644 --- a/app/views/projects/tree/_readme.html.haml +++ b/app/views/projects/tree/_readme.html.haml @@ -1,13 +1,13 @@ -.file-holder#README - .file-title +.readme-holder#README + %h4 %i.icon-file - = readme.name - .file-content.wiki + = readme.name + .wiki - if gitlab_markdown?(readme.name) = preserve do = markdown(readme.data) - elsif plain_text_readme?(readme.name) %pre.clean = readme.data - - else - = raw GitHub::Markup.render(readme.name, readme.data) + - elsif markup?(readme.name) + = render_markup(readme.name, readme.data) diff --git a/app/views/projects/tree/_spinner.html.haml b/app/views/projects/tree/_spinner.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..5a9e77b63df05e7cb7012de90773e0b69d1122fa --- /dev/null +++ b/app/views/projects/tree/_spinner.html.haml @@ -0,0 +1,3 @@ +%span.log_loading.hide + %i.icon-spinner.icon-spin + Loading commit data... diff --git a/app/views/projects/tree/_submodule_item.html.haml b/app/views/projects/tree/_submodule_item.html.haml index 26aad16e2c00cfcda5917f7af5e6c955b34ff678..badc7d992bd48628e159776163f0800ed53ae558 100644 --- a/app/views/projects/tree/_submodule_item.html.haml +++ b/app/views/projects/tree/_submodule_item.html.haml @@ -1,11 +1,10 @@ -- url = submodule_item.url(@ref) rescue '' -- name = submodule_item.basename -- return '' unless url -%tr{ class: "tree-item", url: url } +%tr{ class: "tree-item" } %td.tree-item-file-name = image_tag "submodule.png" - %span= truncate(name, length: 40) + %span + = link_to truncate(submodule_item.name, length: 40), submodule_item.submodule_url + @ + %span.monospace #{submodule_item.id[0..10]} + %td + %td %td - %code= submodule_item.id[0..10] - %td{ colspan: 2 } - = link_to truncate(url, length: 40), url diff --git a/app/views/projects/tree/_tree.html.haml b/app/views/projects/tree/_tree.html.haml index ae5f30c00048616dead7c05049bbf4e71b7904c3..ee850e2bc1b84f9081c0022add252d42277f6429 100644 --- a/app/views/projects/tree/_tree.html.haml +++ b/app/views/projects/tree/_tree.html.haml @@ -4,12 +4,16 @@ = link_to project_tree_path(@project, @ref) do = @project.path - tree_breadcrumbs(tree, 6) do |title, path| - \/ %li - if path = link_to truncate(title, length: 40), project_tree_path(@project, path) - else = link_to title, '#' + - if current_user && @repository.branch_names.include?(@ref) && current_user.can?(:push_code, @project) + %li + = link_to project_new_tree_path(@project, @id), title: 'New file', id: 'new-file-link' do + %small + %i.icon-plus.light %div#tree-content-holder.tree-content-holder %table#tree-slider{class: "table_#{@hex_path} tree-table" } @@ -17,18 +21,19 @@ %tr %th Name %th Last Update - %th + %th.hidden-sm Last Commit - - %i.icon-angle-right - - %small.light - = link_to @commit.short_id, project_commit_path(@project, @commit) - – - = truncate(@commit.title, length: 50) + %span.last-commit + + %i.icon-angle-right + + %small.light + = link_to @commit.short_id, project_commit_path(@project, @commit) + – + = truncate(@commit.title, length: 50) %th= link_to "history", project_commits_path(@project, @id), class: "pull-right" - - if tree.up_dir? + - if @path.present? %tr.tree-item %td.tree-item-file-name = image_tag "file_empty.png", size: '16x16' diff --git a/app/views/projects/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml index 7ae2582c130af4537208212f692f8708452860d0..bd50dd4d9a2d8e6681b3799dd5f353718d407da2 100644 --- a/app/views/projects/tree/_tree_commit_column.html.haml +++ b/app/views/projects/tree/_tree_commit_column.html.haml @@ -1,2 +1,3 @@ -%span.tree_author= commit_author_link(commit, avatar: true) -= link_to_gfm truncate(commit.title, length: 80), project_commit_path(@project, commit.id), class: "tree-commit-link" +%span.str-truncated + %span.tree_author= commit_author_link(commit, avatar: true, size: 16) + = link_to_gfm commit.title, project_commit_path(@project, commit.id), class: "tree-commit-link" diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml index f8856afc8664b31e719832df14979c63618a94f8..1b3900bcbae8b7e03c82c074b598b9906425c885 100644 --- a/app/views/projects/tree/_tree_item.html.haml +++ b/app/views/projects/tree/_tree_item.html.haml @@ -1,9 +1,8 @@ %tr{ class: "tree-item #{tree_hex_class(tree_item)}" } %td.tree-item-file-name = tree_icon(type) - %span= link_to truncate(tree_item.name, length: 40), project_tree_path(@project, tree_join(@id || @commit.id, tree_item.name)) + %span.str-truncated + = link_to tree_item.name, project_tree_path(@project, tree_join(@id || @commit.id, tree_item.name)) %td.tree_time_ago.cgray - %span.log_loading.hide - Loading commit data... - = image_tag "ajax_loader_tree.gif", width: 14 + = render 'spinner' %td.tree_commit{ colspan: 2 } diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index 0f7692aba7f5f69d121079286eebebbdde58d1f0..6b33493b7d299e2e9b0b2a68cbd248c430a6f658 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -1,4 +1,6 @@ %div.tree-ref-holder = render 'shared/ref_switcher', destination: 'tree', path: @path +- if can? current_user, :download_code, @project + = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group-small tree-ref-holder pull-right', split_button: true %div#tree-holder.tree-holder = render "tree", tree: @tree diff --git a/app/views/projects/walls/show.html.haml b/app/views/projects/walls/show.html.haml index 88aecee0815d393fb2977dc5ec9defd7fdee37e8..c6afec443f444059c3b9bddcfcb44300bedb9d85 100644 --- a/app/views/projects/walls/show.html.haml +++ b/app/views/projects/walls/show.html.haml @@ -3,7 +3,7 @@ - if can? current_user, :write_note, @project .note-form-holder - = form_for [@project, @note], remote: true, html: { multipart: true, id: nil, class: "new_note wall-note-form" } do |f| + = form_for [@project, @note], remote: true, html: { multipart: true, id: nil, class: "new_note wall-note-form" }, authenticity_token: true do |f| = note_target_fields .note_text_and_preview = f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input turn-on' @@ -17,7 +17,7 @@ %span Choose File ... %span.file_name.js-attachment-filename File name... - = f.file_field :attachment, class: "js-note-attachment-input hide" + = f.file_field :attachment, class: "js-note-attachment-input hidden" .hint.pull-right CTRL + Enter to send message .clearfix diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 16061c9dcbbbcc160d828612fdcd02c829f94207..06d8660630dd4a89d1513a61569e1042ee252e2c 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project, @wiki] do |f| += form_for [@project, @wiki], method: @wiki.persisted? ? :put : :post, html: { class: 'form-horizontal' } do |f| -if @wiki.errors.any? #error_explanation %h2= "#{pluralize(@wiki.errors.count, "error")} prohibited this wiki from being saved:" @@ -6,30 +6,30 @@ - @wiki.errors.full_messages.each do |msg| %li= msg - .ui-box.ui-box-show - .ui-box-head - %h3.page-title - .edit-wiki-header - = @wiki.title.titleize - = f.hidden_field :title, value: @wiki.title - = f.select :format, options_for_select(GollumWiki::MARKUPS, {selected: @wiki.format}), {}, class: "pull-right input-medium" - = f.label :format, class: "pull-right", style: "padding-right: 20px;" - .ui-box-body - .controls - %span.cgray - Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. - To link to a (new) page you can just type - %code [Link Title](page-slug) - \. + = f.hidden_field :title, value: @wiki.title + .form-group + = f.label :format, class: 'control-label' + .col-sm-10 + = f.select :format, options_for_select(GollumWiki::MARKUPS, {selected: @wiki.format}), {}, class: "form-control" + + .row + .col-sm-2 + .col-sm-10 + %p.cgray + Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. + To link to a (new) page you can just type + %code [Link Title](page-slug) + \. + + .form-group + = f.label :content, class: 'control-label' + .col-sm-10 + = f.text_area :content, class: 'form-control js-gfm-input', rows: 18 + + .form-group + = f.label :commit_message, class: 'control-label' + .col-sm-10= f.text_field :message, class: 'form-control', rows: 18 - .ui-box-bottom - .control-group - = f.label :content - .controls= f.text_area :content, class: 'span8 js-gfm-input' - .ui-box-bottom - .control-group - = f.label :commit_message - .controls= f.text_field :message, class: 'span8' .form-actions - if @wiki && @wiki.persisted? = f.submit 'Save changes', class: "btn-save btn" diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml index 0a7e51e974c25b94906eeaf09340f5229b4686fa..5e5aa5170d6b8ff35115c374367c2767bb3a5b99 100644 --- a/app/views/projects/wikis/_nav.html.haml +++ b/app/views/projects/wikis/_nav.html.haml @@ -1,4 +1,4 @@ -%ul.nav.nav-tabs +%ul.nav.nav-tabs.append-bottom-20 = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do = link_to 'Home', project_wiki_path(@project, :home) diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml index f64772b20014847fa1a5a3564dfdd52265b3c576..8cb7fa8aa0b196ff1c39ac928dfc7a483024498b 100644 --- a/app/views/projects/wikis/_new.html.haml +++ b/app/views/projects/wikis/_new.html.haml @@ -1,12 +1,14 @@ %div#modal-new-wiki.modal.hide - .modal-header - %a.close{href: "#", "data-dismiss" => "modal"} × - %h3.page-title New Wiki Page - .modal-body - = label_tag :new_wiki_path do - %span Page slug - = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'input-xlarge', required: true, :'data-wikis-path' => project_wikis_path(@project) - %p.hint - Please don't use spaces and slashes - .modal-footer - = link_to 'Build', '#', class: 'build-new-wiki btn btn-create' + .modal-dialog + .modal-content + .modal-header + %a.close{href: "#", "data-dismiss" => "modal"} × + %h3.page-title New Wiki Page + .modal-body + = label_tag :new_wiki_path do + %span Page slug + = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => project_wikis_path(@project) + %p.hint + Please don't use spaces and slashes + .modal-footer + = link_to 'Build', '#', class: 'build-new-wiki btn btn-create' diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml index 66be82777c9b832e535cd0c117a2009a65b0161a..47b236083b36fcf7ff649a17ae9193f416e76087 100644 --- a/app/views/projects/wikis/edit.html.haml +++ b/app/views/projects/wikis/edit.html.haml @@ -1,10 +1,13 @@ = render 'nav' -%h3.page-title - Editing page +.pull-right = render 'main_links' +%h3.page-title + Editing - + %span.light #{@wiki.title.titleize} +%hr = render 'form' .pull-right - if @wiki.persisted? && can?(current_user, :admin_wiki, @project) - = link_to project_wiki_path(@project, @wiki), confirm: "Are you sure you want to delete this page?", method: :delete, class: "btn btn-small btn-remove" do + = link_to project_wiki_path(@project, @wiki), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-small btn-remove" do Delete this page diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml index dd01bb990415000c5d2e5d6fb9531d10cefc9cd3..b62c4975416468b078de7da16298cfdad5a20ede 100644 --- a/app/views/projects/wikis/git_access.html.haml +++ b/app/views/projects/wikis/git_access.html.haml @@ -1,36 +1,31 @@ = render 'nav' -%h3.page-title - Git access for - %strong= @gollum_wiki.path_with_namespace - = render 'main_links' +.row + .col-sm-6 + %h3.page-title + Git access for + %strong= @gollum_wiki.path_with_namespace -.content - .project_clone_panel - .row - .span7 - .form-horizontal - .input-prepend.project_clone_holder - %button{class: "btn active", :"data-clone" => @gollum_wiki.ssh_url_to_repo} SSH - %button{class: "btn", :"data-clone" => @gollum_wiki.http_url_to_repo}= Gitlab.config.gitlab.protocol.upcase - = text_field_tag :project_clone, @gollum_wiki.url_to_repo, class: "one_click_select input-xxlarge", readonly: true - .git-empty - %fieldset - %legend Install Gollum: - %pre.dark - :preserve - gem install gollum + .col-sm-6 + = render "shared/clone_panel", project: @gollum_wiki - %legend Clone Your Wiki: - %pre.dark - :preserve - git clone #{@gollum_wiki.ssh_url_to_repo} - cd #{@gollum_wiki.path} +.git-empty + %fieldset + %legend Install Gollum: + %pre.dark + :preserve + gem install gollum - %legend Start Gollum And Edit Locally: - %pre.dark - :preserve - gollum - == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin - >> Thin web server (v1.5.0 codename Knife) - >> Maximum connections set to 1024 - >> Listening on 0.0.0.0:4567, CTRL+C to stop + %legend Clone Your Wiki: + %pre.dark + :preserve + git clone #{ content_tag(:span, default_url_to_repo(@gollum_wiki), class: 'clone')} + cd #{@gollum_wiki.path} + + %legend Start Gollum And Edit Locally: + %pre.dark + :preserve + gollum + == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin + >> Thin web server (v1.5.0 codename Knife) + >> Maximum connections set to 1024 + >> Listening on 0.0.0.0:4567, CTRL+C to stop diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml index 344a5e88e6e93e831977089e8766e213d8e4bd41..55efb624e236cd3f8ae96bdcde64949d5e6c8f00 100644 --- a/app/views/projects/wikis/history.html.haml +++ b/app/views/projects/wikis/history.html.haml @@ -3,7 +3,7 @@ %span.light History for = link_to @wiki.title.titleize, project_wiki_path(@project, @wiki) -%table +%table.table %thead %tr %th Page version @@ -23,8 +23,7 @@ %td = commit.title %td - = time_ago_in_words(version.date) - ago + #{time_ago_with_tooltip(version.date)} %td %strong = @wiki.page.wiki.page(@wiki.page.name, commit.id).try(:format) diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml index 59d104a985bd65562ae5b341ed3f9ef72e64ba3f..7a890816568523a35b2258c09a7918e8e995ac11 100644 --- a/app/views/projects/wikis/pages.html.haml +++ b/app/views/projects/wikis/pages.html.haml @@ -8,4 +8,4 @@ = link_to wiki_page.title.titleize, project_wiki_path(@project, wiki_page) %small (#{wiki_page.format}) .pull-right - %small Last edited #{time_ago_in_words(wiki_page.commit.created_at)} ago + %small Last edited #{time_ago_with_tooltip(wiki_page.commit.created_at)} diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index 63e7f12b7fe9b04c92f35bace6d2322f9bd67395..f7d4fe42b5799973563f0754db55cdabd99dbef2 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -7,9 +7,14 @@ This is an old version of this page. You can view the #{link_to "most recent version", project_wiki_path(@project, @wiki)} or browse the #{link_to "history", history_project_wiki_path(@project, @wiki)}. -.file-holder - .file-content.wiki +%hr + +.wiki-holder + .wiki = preserve do = render_wiki_content(@wiki) -%p.time Last edited by #{commit_author_link(@wiki.commit, avatar: true, size: 16)} #{time_ago_in_words @wiki.commit.created_at} ago +%hr + +.wiki-last-edit-by + Last edited by #{commit_author_link(@wiki.commit, avatar: true, size: 16)} #{time_ago_with_tooltip(@wiki.commit.created_at)} diff --git a/app/views/public/projects/index.html.haml b/app/views/public/projects/index.html.haml index 21aee6445795df849988cbf5d7cb24784c55c9bd..2fc93c5b74297ef1fde376b5c2fd134ed6cd97b5 100644 --- a/app/views/public/projects/index.html.haml +++ b/app/views/public/projects/index.html.haml @@ -1,16 +1,38 @@ -.row - .span6 - %h3.page-title - Projects (#{@projects.total_count}) - .light - You can browse public projects in read-only mode until signed in. +%h3.page-title + Projects (#{@projects.total_count}) +.light + You can browse public projects in read-only mode until signed in. +%hr +.clearfix + .pull-left + = form_tag public_projects_path, method: :get, class: 'form-inline form-tiny' do |f| + .form-group + = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "projects_search" + .form-group + = submit_tag 'Search', class: "btn btn-primary wide" + + .pull-right + .dropdown.inline + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %span.light sort: + - if @sort.present? + = @sort.humanize + - else + Name + %b.caret + %ul.dropdown-menu + %li + = link_to public_projects_path(sort: nil) do + Name + = link_to public_projects_path(sort: 'newest') do + Newest + = link_to public_projects_path(sort: 'oldest') do + Oldest + = link_to public_projects_path(sort: 'recently_updated') do + Recently updated + = link_to public_projects_path(sort: 'last_updated') do + Last updated - .span6 - .pull-right - = form_tag public_projects_path, method: :get, class: 'form-inline' do |f| - .search-holder - = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "span3 search-text-input", id: "projects_search" - = submit_tag 'Search', class: "btn btn-primary wide" %hr .public-projects %ul.bordered-list.top-list @@ -19,6 +41,10 @@ %h4 = link_to project_path(project) do = project.name_with_namespace + - if project.internal? + %small.access-icon + = internal_icon + Internal .pull-right %pre.public-clone git clone #{project.http_url_to_repo} diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml index f7a00b234803bf98a1310a0e1a9a1a30af11216e..979b18e38563421ae662e32f836cda7fccad4b5d 100644 --- a/app/views/search/_filter.html.haml +++ b/app/views/search/_filter.html.haml @@ -9,7 +9,7 @@ %b.caret %ul.dropdown-menu %li - = link_to search_path(group_id: nil) do + = link_to search_path(group_id: nil, search: params[:search]) do Any - current_user.authorized_groups.sort_by(&:name).each do |group| %li @@ -27,7 +27,7 @@ %b.caret %ul.dropdown-menu %li - = link_to search_path(project_id: nil) do + = link_to search_path(project_id: nil, search: params[:search]) do Any - current_user.authorized_projects.sort_by(&:name_with_namespace).each do |project| %li diff --git a/app/views/search/_global_results.html.haml b/app/views/search/_global_results.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..7f4f0e5e0007f9d92c6803a2550ee5752bcd7b35 --- /dev/null +++ b/app/views/search/_global_results.html.haml @@ -0,0 +1,5 @@ +.search_results + %ul.bordered-list + = render partial: "search/results/project", collection: @search_results[:projects] + = render partial: "search/results/merge_request", collection: @search_results[:merge_requests] + = render partial: "search/results/issue", collection: @search_results[:issues] diff --git a/app/views/search/_project_results.html.haml b/app/views/search/_project_results.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..ea324b3a9aa911ca9b843f0b134b14494023156b --- /dev/null +++ b/app/views/search/_project_results.html.haml @@ -0,0 +1,17 @@ +%ul.nav.nav-tabs.append-bottom-10 + %li{class: ("active" if params[:search_code].present?)} + = link_to search_path(params.merge(search_code: true)) do + Repository Code + %li{class: ("active" if params[:search_code].blank?)} + = link_to search_path(params.merge(search_code: nil)) do + Issues and Merge requests + +.search_results + - if params[:search_code].present? + .blob-results + = render partial: "search/results/blob", collection: @search_results[:blobs] + = paginate @search_results[:blobs], theme: 'gitlab' + - else + %ul.bordered-list + = render partial: "search/results/merge_request", collection: @search_results[:merge_requests] + = render partial: "search/results/issue", collection: @search_results[:issues] diff --git a/app/views/search/_result.html.haml b/app/views/search/_result.html.haml deleted file mode 100644 index 5f7540d1b16c61bfed6f061ebfddeecd8019aa38..0000000000000000000000000000000000000000 --- a/app/views/search/_result.html.haml +++ /dev/null @@ -1,62 +0,0 @@ -%fieldset - %legend - Search results - %span.cgray (#{@total_results}) - -- if @project - %ul.nav.nav-pills - %li{class: ("active" if params[:search_code].present?)} - = link_to search_path(params.merge(search_code: true)) do - Repository Code - %li{class: ("active" if params[:search_code].blank?)} - = link_to search_path(params.merge(search_code: nil)) do - Everything else - -.search_results - %ul.bordered-list - - @projects.each do |project| - %li - project: - = link_to project do - %strong.term= project.name_with_namespace - - @merge_requests.each do |merge_request| - %li - merge request: - = link_to [merge_request.target_project, merge_request] do - %span ##{merge_request.iid} - %strong.term - = truncate merge_request.title, length: 50 - - if merge_request.for_fork? - %span.light (#{merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} → #{merge_request.target_project.name_with_namespace}:#{merge_request.target_branch}) - - else - %span.light (#{merge_request.source_branch} → #{merge_request.target_branch}) - - if merge_request.closed? - %span.label Closed - - - @issues.each do |issue| - %li - issue: - = link_to [issue.project, issue] do - %span ##{issue.iid} - %strong.term - = truncate issue.title, length: 50 - %span.light (#{issue.project.name_with_namespace}) - - if issue.closed? - %span.label Closed - - - @wiki_pages.each do |wiki_page| - %li - wiki: - = link_to project_wiki_path(wiki_page.project, wiki_page) do - %strong.term - = truncate wiki_page.title, length: 50 - %span.light (#{wiki_page.project.name_with_namespace}) - - - @blobs.each do |blob| - = render 'blob', blob: blob - - = paginate @blobs, theme: 'gitlab' - -:javascript - $(".search_results .term").highlight("#{escape_javascript(params[:search])}"); - diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..2336d0f71d56c04b770fb9c87190b96d64a000c8 --- /dev/null +++ b/app/views/search/_results.html.haml @@ -0,0 +1,17 @@ +%h4 + #{@search_results[:total_results]} results found + - if @project + for #{link_to @project.name_with_namespace, @project} + - elsif @group + for #{link_to @group.name, @group} + +%hr + +- if @project + = render "project_results" +- else + = render "global_results" + +:javascript + $(".search_results .term").highlight("#{escape_javascript(params[:search])}"); + diff --git a/app/views/search/_blob.html.haml b/app/views/search/results/_blob.html.haml similarity index 62% rename from app/views/search/_blob.html.haml rename to app/views/search/results/_blob.html.haml index 559fdd794fc9b8ec2d7be5942c7781f8764c56e9..f9d217e8408b1fd1598e71162176f303bb488153 100644 --- a/app/views/search/_blob.html.haml +++ b/app/views/search/results/_blob.html.haml @@ -1,4 +1,4 @@ -%li +.blob-result .file-holder .file-title = link_to project_blob_path(@project, tree_join(blob.ref, blob.filename), :anchor => "L" + blob.startline.to_s) do @@ -6,5 +6,4 @@ %strong = blob.filename .file-content.code.term - %div{class: user_color_scheme_class} - = raw blob.colorize( formatter: :gitlab, options: { first_line_number: blob.startline } ) + = render 'shared/file_hljs', blob: blob, first_line_number: blob.startline diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..7a24b76bced1b04dcdbb7a7efa354640252e9efc --- /dev/null +++ b/app/views/search/results/_issue.html.haml @@ -0,0 +1,9 @@ +%li + issue: + = link_to [issue.project, issue] do + %span ##{issue.iid} + %strong.term + = truncate issue.title, length: 50 + %span.light (#{issue.project.name_with_namespace}) + - if issue.closed? + %span.label Closed diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..22d7587f6c1d645e659cccdd6cd8d3f4f4d1d35d --- /dev/null +++ b/app/views/search/results/_merge_request.html.haml @@ -0,0 +1,12 @@ +%li + merge request: + = link_to [merge_request.target_project, merge_request] do + %span ##{merge_request.iid} + %strong.term + = truncate merge_request.title, length: 50 + - if merge_request.for_fork? + %span.light (#{merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} → #{merge_request.target_project.name_with_namespace}:#{merge_request.target_branch}) + - else + %span.light (#{merge_request.source_branch} → #{merge_request.target_branch}) + - if merge_request.closed? + %span.label Closed diff --git a/app/views/search/results/_project.html.haml b/app/views/search/results/_project.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..abc86c72bef1fb53cf5b0d9f91f90d57a1c26f7e --- /dev/null +++ b/app/views/search/results/_project.html.haml @@ -0,0 +1,7 @@ +%li + project: + = link_to project do + %strong.term= project.name_with_namespace + - if project.description.present? + – + %span.light.term= project.description diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index f1f65981b37f8e6f2ed4cfb16cc016d1e254966b..3b6f10d4d9ccff717e59c5629e916b13fbcf82b5 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -1,16 +1,20 @@ -= form_tag search_path, method: :get, class: 'form-inline' do |f| - .search-holder - = label_tag :search do - %span Looking for - .controls - = search_field_tag :search, params[:search], placeholder: "issue 143", class: "input-xxlarge search-text-input", id: "dashboard_search" += form_tag search_path, method: :get, class: 'form-horizontal' do |f| + .search-holder.clearfix + .form-group + = label_tag :search, class: 'control-label' do + %span Looking for + .col-sm-6 + = search_field_tag :search, params[:search], placeholder: "issue 143", class: "form-control search-text-input", id: "dashboard_search" + .col-sm-4 + = submit_tag 'Search', class: "btn btn-create" + .form-group + .col-sm-2 + .col-sm-10 + = render 'filter', f: f = hidden_field_tag :project_id, params[:project_id] = hidden_field_tag :group_id, params[:group_id] = hidden_field_tag :search_code, params[:search_code] - = submit_tag 'Search', class: "btn btn-create" - .prepend-top-10 - = render 'filter', f: f .results.prepend-top-10 - if params[:search].present? - = render 'search/result' + = render 'search/results' diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 5120902fa0e467b6b7a506ca333155f674c2ea6d..8cd426c71e693a3d782b78eb8697422b83469d54 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -1,11 +1,6 @@ -.input-prepend.input-append.project_clone_holder - %button{class: "btn active", :"data-clone" => @project.ssh_url_to_repo} SSH - %button{class: "btn", :"data-clone" => @project.http_url_to_repo}= gitlab_config.protocol.upcase - = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span7", readonly: true - %span.add-on - - if @project.public - = public_icon - %span.cblue public - - else - = private_icon - %span.cgreen private +- project = project || @project +.git-clone-holder.input-group + .input-group-btn + %button{class: "btn #{ 'active' if default_clone_protocol == 'ssh' }", :"data-clone" => project.ssh_url_to_repo} SSH + %button{class: "btn #{ 'active' if default_clone_protocol == 'http' }", :"data-clone" => project.http_url_to_repo}= gitlab_config.protocol.upcase + = text_field_tag :project_clone, default_url_to_repo(project), class: "one_click_select form-control", readonly: true diff --git a/app/views/shared/_file_hljs.html.haml b/app/views/shared/_file_hljs.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..166a13bd76c02553e50bde266fb323b69212948d --- /dev/null +++ b/app/views/shared/_file_hljs.html.haml @@ -0,0 +1,12 @@ +%div.highlighted-data{class: user_color_scheme_class} + .line-numbers + - blob.data.lines.size.times do |index| + - offset = defined?(first_line_number) ? first_line_number : 1 + - i = index + offset + = link_to "#L#{i}", id: "L#{i}", rel: "#L#{i}" do + %i.icon-link + = i + .highlight + %pre + %code + = blob.data diff --git a/app/views/shared/_filter.html.haml b/app/views/shared/_filter.html.haml index 2f051cea48b7d08381979bc82dea57b419ee0352..6063b4a07323ee184efa9b4f5f370add089f97a6 100644 --- a/app/views/shared/_filter.html.haml +++ b/app/views/shared/_filter.html.haml @@ -1,29 +1,43 @@ -= form_tag filter_path(entity), method: 'get' do - %fieldset - %ul.nav.nav-pills.nav-stacked - %li{class: ("active" if params[:status].blank?)} - = link_to filter_path(entity, status: nil) do - Open - %li{class: ("active" if params[:status] == 'closed')} - = link_to filter_path(entity, status: 'closed') do - Closed - %li{class: ("active" if params[:status] == 'all')} - = link_to filter_path(entity, status: 'all') do - All +.side-filters.hidden-xs.hidden-sm + = form_tag filter_path(entity), method: 'get' do + %fieldset.scope-filter + %ul.nav.nav-pills.nav-stacked + %li{class: ("active" if params[:scope] == 'assigned-to-me')} + = link_to filter_path(entity, scope: 'assigned-to-me') do + Assigned to me + %li{class: ("active" if params[:scope] == 'authored')} + = link_to filter_path(entity, scope: 'authored') do + Created by me + %li{class: ("active" if params[:scope] == 'all')} + = link_to filter_path(entity, scope: 'all') do + Everyone's - %fieldset - %legend Projects - %ul.nav.nav-pills.nav-pills-small.nav-stacked - - @projects.each do |project| - - unless entities_per_project(project, entity).zero? - %li{class: ("active" if params[:project_id] == project.id.to_s)} - = link_to filter_path(entity, project_id: project.id) do - = project.name_with_namespace - %small.pull-right= entities_per_project(project, entity) + %fieldset.status-filter + %legend State + %ul.nav.nav-pills + %li{class: ("active" if params[:state] == 'opened')} + = link_to filter_path(entity, state: 'opened') do + Open + %li{class: ("active" if params[:state] == 'closed')} + = link_to filter_path(entity, state: 'closed') do + Closed + %li{class: ("active" if params[:state] == 'all')} + = link_to filter_path(entity, state: 'all') do + All - %fieldset - - if params[:status].present? || params[:project_id].present? - = link_to filter_path(entity, status: nil, project_id: nil), class: 'pull-right cgray' do - %i.icon-remove - %strong Clear filter + %fieldset + %legend Projects + %ul.nav.nav-pills.nav-stacked.nav-small + - @projects.each do |project| + - unless entities_per_project(project, entity).zero? + %li{class: ("active" if params[:project_id] == project.id.to_s)} + = link_to filter_path(entity, project_id: project.id) do + = project.name_with_namespace + %small.pull-right= entities_per_project(project, entity) + + %fieldset + - if params[:state].present? || params[:project_id].present? + = link_to filter_path(entity, state: nil, project_id: nil), class: 'pull-right cgray' do + %i.icon-remove + %strong Clear filter diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml index 3b3888a50e9dcfc07b03bed2d4af792efc57e72e..199000656fed6a6d89a9a689a943e072fc52a741 100644 --- a/app/views/shared/_issues.html.haml +++ b/app/views/shared/_issues.html.haml @@ -1,6 +1,6 @@ - if @issues.any? - @issues.group_by(&:project).each do |group| - .ui-box.small-box + .ui-box.ui-box-small - project = group[0] .title = link_to_project project diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml index b7a7ca8fcc8874c9414f1898d6081c54ff1c5df4..ddad28339c8f7f5f15553373a703102b2c0722e8 100644 --- a/app/views/shared/_merge_requests.html.haml +++ b/app/views/shared/_merge_requests.html.haml @@ -1,9 +1,10 @@ - if @merge_requests.any? - @merge_requests.group_by(&:target_project).each do |group| - .ui-box.small-box + .ui-box.ui-box-small - project = group[0] .title = link_to_project project + = link_to 'show all', project_merge_requests_path(project), class: 'pull-right' %ul.well-list.mr-list - group[1].each do |merge_request| = render 'projects/merge_requests/merge_request', merge_request: merge_request diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml index 6d363807d62541a05b2a70c4688e0d7c718283c3..e70eb4d01b99e7d6b6fb6dfa2194ba9432ea9af0 100644 --- a/app/views/shared/_no_ssh.html.haml +++ b/app/views/shared/_no_ssh.html.haml @@ -1,3 +1,14 @@ -- if current_user.require_ssh_key? && alert.blank? && notice.blank? - %p.error-message.centered - You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path} to your profile +- if cookies[:hide_no_ssh_message].blank? && current_user.require_ssh_key? && !current_user.hide_no_ssh_key + .no-ssh-key-message + .container + You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path} to your profile + .pull-right.hidden-xs + = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'hide-no-ssh-message', remote: true + | + = link_to 'Remind later', '#', class: 'hide-no-ssh-message' + .links-xs.visible-xs + = link_to "Add key", new_profile_key_path + | + = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'hide-no-ssh-message', remote: true + | + = link_to 'Later', '#', class: 'hide-no-ssh-message' diff --git a/app/views/shared/_project_filter.html.haml b/app/views/shared/_project_filter.html.haml index f3d032ef98661914c7fc323d39593aea7cc84595..9b89c5c8007ec4e47e780cf5e1062bb8692e8f5e 100644 --- a/app/views/shared/_project_filter.html.haml +++ b/app/views/shared/_project_filter.html.haml @@ -1,32 +1,35 @@ -= form_tag project_entities_path, method: 'get' do - %fieldset +.side-filters.hidden-xs.hidden-sm + = form_tag project_entities_path, method: 'get' do - if current_user - %ul.nav.nav-pills.nav-stacked - %li{class: ("active" if params[:scope].blank?)} - = link_to project_filter_path(scope: nil) do - Everyone's - %li{class: ("active" if params[:scope] == 'assigned-to-me')} - = link_to project_filter_path(scope: 'assigned-to-me') do - Assigned to me - %li{class: ("active" if params[:scope] == 'created-by-me')} - = link_to project_filter_path(scope: 'created-by-me') do - Created by me + %fieldset + %ul.nav.nav-pills.nav-stacked + %li{class: ("active" if params[:scope] == 'all')} + = link_to project_filter_path(scope: 'all') do + Everyone's + %li{class: ("active" if params[:scope] == 'assigned-to-me')} + = link_to project_filter_path(scope: 'assigned-to-me') do + Assigned to me + %li{class: ("active" if params[:scope] == 'created-by-me')} + = link_to project_filter_path(scope: 'created-by-me') do + Created by me - %ul.nav.nav-pills.nav-stacked - %li{class: ("active" if params[:state].blank?)} - = link_to project_filter_path(state: nil) do - Open - %li{class: ("active" if params[:state] == 'closed')} - = link_to project_filter_path(state: 'closed') do - Closed - %li{class: ("active" if params[:state] == 'all')} - = link_to project_filter_path(state: 'all') do - All + %fieldset + %legend State + %ul.nav.nav-pills + %li{class: ("active" if params[:state] == 'opened')} + = link_to project_filter_path(state: 'opened') do + Open + %li{class: ("active" if params[:state] == 'closed')} + = link_to project_filter_path(state: 'closed') do + Closed + %li{class: ("active" if params[:state] == 'all')} + = link_to project_filter_path(state: 'all') do + All - %fieldset - - if %w(state scope milestone_id assignee_id label_name).select { |k| params[k].present? }.any? - = link_to project_entities_path, class: 'cgray pull-right' do - %i.icon-remove - %strong Clear filter + %fieldset + - if %w(state scope milestone_id assignee_id label_name).select { |k| params[k].present? }.any? + = link_to project_entities_path, class: 'cgray pull-right' do + %i.icon-remove + %strong Clear filter diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml index dc8c656e12e571b9024c572c377623dcaa982ca1..4d9534f49b1e3b2599d58007a5c07dfbf1656a17 100644 --- a/app/views/shared/_ref_switcher.html.haml +++ b/app/views/shared/_ref_switcher.html.haml @@ -1,5 +1,5 @@ = form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do - = select_tag "ref", grouped_options_refs, class: "project-refs-select chosen" + = select_tag "ref", grouped_options_refs, class: "project-refs-select select2 select2-sm" = hidden_field_tag :destination, destination - if defined?(path) = hidden_field_tag :path, path diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..2e87566996755c7d46f9916eb66be71985227a50 --- /dev/null +++ b/app/views/shared/_sort_dropdown.html.haml @@ -0,0 +1,22 @@ +.dropdown.inline.prepend-left-10 + %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"} + %span.light sort: + - if @sort.present? + = @sort + - else + Newest + %b.caret + %ul.dropdown-menu + %li + = link_to project_filter_path(sort: 'newest') do + Newest + = link_to project_filter_path(sort: 'oldest') do + Oldest + = link_to project_filter_path(sort: 'recently_updated') do + Recently updated + = link_to project_filter_path(sort: 'last_updated') do + Last updated + = link_to project_filter_path(sort: 'milestone_due_soon') do + Milestone due soon + = link_to project_filter_path(sort: 'milestone_due_later') do + Milestone due later diff --git a/app/views/snippets/_blob.html.haml b/app/views/snippets/_blob.html.haml index c2e0d97a1179171accdb47929741891e53d8de2e..15867f071ef487a732014d9f7fdfee98d6d7c40d 100644 --- a/app/views/snippets/_blob.html.haml +++ b/app/views/snippets/_blob.html.haml @@ -6,11 +6,6 @@ .btn-group.tree-btn-group.pull-right - if @snippet.author == current_user = link_to "Edit", edit_snippet_path(@snippet), class: "btn btn-tiny", title: 'Edit Snippet' - = link_to "Delete", snippet_path(@snippet), method: :delete, confirm: "Are you sure?", class: "btn btn-tiny", title: 'Delete Snippet' + = link_to "Delete", snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-tiny", title: 'Delete Snippet' = link_to "Raw", raw_snippet_path(@snippet), class: "btn btn-tiny", target: "_blank" - .file-content.code - - unless @snippet.content.empty? - %div{class: user_color_scheme_class} - = raw @snippet.colorize(formatter: :gitlab) - - else - %p.nothing_here_message Empty file + = render 'snippets/blob_content' diff --git a/app/views/snippets/_blob_content.html.haml b/app/views/snippets/_blob_content.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..81055451b66ec7d6a796285af9e5601618ee8ba9 --- /dev/null +++ b/app/views/snippets/_blob_content.html.haml @@ -0,0 +1,14 @@ +- unless @snippet.content.empty? + - if gitlab_markdown?(@snippet.file_name) + .file-content.wiki + = preserve do + = markdown(@snippet.data) + - elsif markup?(@snippet.file_name) + .file-content.wiki + = render_markup(@snippet.file_name, @snippet.data) + - else + .file-content.code + = render 'shared/file_hljs', blob: @snippet +- else + .file-content.code + %p.nothing_here_message Empty file diff --git a/app/views/snippets/_form.html.haml b/app/views/snippets/_form.html.haml index e77550e7be3921bb771dd8eda9bb7d3b4cd07284..d466dc1af14036fe6f6c88d657fb4b1dbe632f10 100644 --- a/app/views/snippets/_form.html.haml +++ b/app/views/snippets/_form.html.haml @@ -2,27 +2,38 @@ = @snippet.new_record? ? "New Snippet" : "Edit Snippet ##{@snippet.id}" %hr .snippet-form-holder - = form_for @snippet, as: :personal_snippet, url: url do |f| + = form_for @snippet, as: :personal_snippet, url: url, html: { class: "form-horizontal snippet-form" } do |f| -if @snippet.errors.any? - .alert.alert-error + .alert.alert-danger %ul - @snippet.errors.full_messages.each do |msg| %li= msg - .control-group - = f.label :title - .controls= f.text_field :title, placeholder: "Example Snippet", class: 'input-xlarge', required: true - .control-group - = f.label "Private?" - .controls - = f.check_box :private, {class: ''} - .control-group + .form-group + = f.label :title, class: 'control-label' + .col-sm-10= f.text_field :title, placeholder: "Example Snippet", class: 'form-control', required: true + .form-group + = f.label "Access", class: 'control-label' + .col-sm-10 + = f.label :private_true, class: 'radio-label' do + = f.radio_button :private, true + %span + %strong Private + (only you can see this snippet) + %br + = f.label :private_false, class: 'radio-label' do + = f.radio_button :private, false + %span + %strong Public + (GitLab users can see this snippet) + + .form-group .file-editor - = f.label :file_name, "File" - .controls + = f.label :file_name, "File", class: 'control-label' + .col-sm-10 .file-holder.snippet .file-title - = f.text_field :file_name, placeholder: "example.rb", class: 'snippet-file-name', required: true + = f.text_field :file_name, placeholder: "example.rb", class: 'form-control snippet-file-name', required: true .file-content.code %pre#editor= @snippet.content = f.hidden_field :content, class: 'snippet-file-content' @@ -33,9 +44,10 @@ - else = f.submit 'Save', class: "btn-save btn" - = link_to "Cancel", snippets_path(@project), class: "btn btn-cancel" - unless @snippet.new_record? - .pull-right= link_to 'Destroy', snippet_path(@snippet), confirm: 'Removed snippet cannot be restored! Are you sure?', method: :delete, class: "btn pull-right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}" + .pull-right.prepend-left-20 + = link_to 'Remove', snippet_path(@snippet), data: { confirm: 'Removed snippet cannot be restored! Are you sure?'}, method: :delete, class: "btn btn-remove delete-snippet", id: "destroy_snippet_#{@snippet.id}" + = link_to "Cancel", snippets_path(@project), class: "btn btn-cancel" :javascript diff --git a/app/views/snippets/_snippet.html.haml b/app/views/snippets/_snippet.html.haml index 9689c9c4d38a9a532a908f5df58190cde909a238..e6f83167330fc5b291e7b25d4c3b13af3a7b1feb 100644 --- a/app/views/snippets/_snippet.html.haml +++ b/app/views/snippets/_snippet.html.haml @@ -3,7 +3,7 @@ = link_to reliable_snippet_path(snippet) do = truncate(snippet.title, length: 60) - if snippet.private? - %span.label.label-success + %span.label.label-gray %i.icon-lock private %span.cgray.monospace.tiny.pull-right @@ -18,6 +18,6 @@ %span by = link_to user_snippets_path(snippet.author) do - = image_tag gravatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: '' + = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: '' = snippet.author_name - %span.light #{time_ago_in_words(snippet.created_at)} ago + %span.light #{time_ago_with_tooltip(snippet.created_at)} diff --git a/app/views/snippets/current_user_index.html.haml b/app/views/snippets/current_user_index.html.haml index 51030f965a184ab29b5012b9046455054a002caa..bf712b2c7e7e3f52cc80ba2d391d9014bf0f53cc 100644 --- a/app/views/snippets/current_user_index.html.haml +++ b/app/views/snippets/current_user_index.html.haml @@ -11,7 +11,7 @@ %hr .row - .span3 + .col-md-3 %ul.nav.nav-pills.nav-stacked = nav_tab :scope, nil do = link_to user_snippets_path(@user) do @@ -29,6 +29,6 @@ %span.pull-right = @user.snippets.public.count - .span9.my-snippets + .col-md-9.my-snippets = render 'snippets' diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index 37f9e7576f5bf0dfa9f89160b8c1772842a3bcf3..a680e5eb5b79df96bb56e72d5431212ffa061058 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -17,7 +17,7 @@ %span.light by = link_to user_snippets_path(@snippet.author) do - = image_tag gravatar_icon(@snippet.author_email), class: "avatar avatar-inline s16" + = image_tag avatar_icon(@snippet.author_email), class: "avatar avatar-inline s16" = @snippet.author_name .back-link diff --git a/app/views/snippets/user_index.html.haml b/app/views/snippets/user_index.html.haml index 49636c3f5f0a7ddd52e496fd492c4d71dd67334a..1cb53ec6a254869c625fba45e1719cd4bb4c1190 100644 --- a/app/views/snippets/user_index.html.haml +++ b/app/views/snippets/user_index.html.haml @@ -1,5 +1,5 @@ %h3.page-title - = image_tag gravatar_icon(@user.email), class: "avatar s24" + = image_tag avatar_icon(@user.email), class: "avatar s24" = @user.name %span \/ diff --git a/app/views/users/_profile.html.haml b/app/views/users/_profile.html.haml index 4cd1eebdf91e67121e5565e1c3420212e7216dc1..7ffd43e837d5544c9dda78191475fb45d30d578a 100644 --- a/app/views/users/_profile.html.haml +++ b/app/views/users/_profile.html.haml @@ -8,7 +8,7 @@ - unless user.skype.blank? %li %span.light Skype: - %strong= user.skype + %strong= link_to user.skype, "skype:#{user.skype}" - unless user.linkedin.blank? %li %span.light LinkedIn: @@ -16,7 +16,11 @@ - unless user.twitter.blank? %li %span.light Twitter: - %strong= user.twitter + %strong= link_to user.twitter, "http://www.twitter.com/#{user.twitter}" + - unless user.website_url.blank? + %li + %span.light Website: + %strong= link_to user.short_website_url, user.full_website_url - unless user.bio.blank? %li %span.light Bio: diff --git a/app/views/users/_projects.html.haml b/app/views/users/_projects.html.haml index f1b2c8dd7f76a3f367e51e62ece95a3a67ac906d..a61c6ba5b8666bf75767dbc70bf3272d92814eb9 100644 --- a/app/views/users/_projects.html.haml +++ b/app/views/users/_projects.html.haml @@ -3,9 +3,4 @@ %ul.well-list - @projects.each do |project| %li - = link_to project_path(project), class: dom_class(project) do - - if project.namespace - = project.namespace.human_name - \/ - %strong.well-title - = truncate(project.name, length: 45) + = link_to_project project diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 743ab0949a1557f44064b4103fb40742dd6dc0fb..65f46500a8900c2e5a1321925f199bf18b805cdf 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -1,21 +1,21 @@ .row - .span8 + .col-md-8 %h3.page-title - = image_tag gravatar_icon(@user.email, 90), class: "avatar s90", alt: '' + = image_tag avatar_icon(@user.email, 90), class: "avatar s90", alt: '' = @user.name - if @user == current_user .pull-right = link_to profile_path, class: 'btn' do %i.icon-edit - Edit Profile + Edit Profile settings %br - %small #{@user.username} + %span.user-show-username #{@user.username} %br %small member since #{@user.created_at.stamp("Nov 12, 2031")} .clearfix %hr %h4 User Activity: = render @events - .span4 + .col-md-4 = render 'profile', user: @user = render 'projects', user: @user diff --git a/app/views/users_groups/_users_group.html.haml b/app/views/users_groups/_users_group.html.haml index 5cdb5bb8c4031064a0a431669bc0ed325715e251..5f477f3c976720244a6b0198761a8a9cd21fdc8c 100644 --- a/app/views/users_groups/_users_group.html.haml +++ b/app/views/users_groups/_users_group.html.haml @@ -1,7 +1,7 @@ - user = member.user - return unless user %li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)} - = image_tag gravatar_icon(user.email, 16), class: "avatar s16" + = image_tag avatar_icon(user.email, 16), class: "avatar s16" %strong= user.name %span.cgray= user.username - if user == current_user @@ -13,7 +13,7 @@ - if show_controls && can?(current_user, :manage_group, @group) && current_user != user = link_to '#', class: "btn-tiny btn js-toggle-button", title: 'Edit access level' do %i.icon-edit - = link_to group_users_group_path(@group, member), confirm: remove_user_from_group_message(@group, user), method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do + = link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do %i.icon-minus.icon-white .edit-member.hide.js-toggle-content diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index bded53b2f21e1bef0a6a5db9ff5604892f74da4d..788d9065a7b43c26062b408a4aba9b65884395ee 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -1,6 +1,6 @@ .votes.votes-block .progress - .bar.bar-success{style: "width: #{votable.upvotes_in_percent}%;"} - .bar.bar-danger{style: "width: #{votable.downvotes_in_percent}%;"} + .progress-bar.progress-bar-success{style: "width: #{votable.upvotes_in_percent}%;"} + .progress-bar.progress-bar-danger{style: "width: #{votable.downvotes_in_percent}%;"} .upvotes= "#{votable.upvotes} up" .downvotes= "#{votable.downvotes} down" diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb new file mode 100644 index 0000000000000000000000000000000000000000..9982b362a1043a4b9af17503ae5fad99703c9fa1 --- /dev/null +++ b/app/workers/emails_on_push_worker.rb @@ -0,0 +1,25 @@ +class EmailsOnPushWorker + include Sidekiq::Worker + + def perform(project_id, recipients, push_data) + project = Project.find(project_id) + before_sha = push_data["before"] + after_sha = push_data["after"] + branch = push_data["ref"] + author_id = push_data["user_id"] + + if before_sha =~ /^000000/ || after_sha =~ /^000000/ + # skip if new branch was pushed or branch was removed + return true + end + + compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha) + + # Do not send emails if git compare failed + return false unless compare && compare.commits.present? + + recipients.split(" ").each do |recipient| + Notify.delay.repository_push_email(project_id, recipient, author_id, branch, compare) + end + end +end diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index a3b4bd0c9b58f1af30707178521d0fd4371abb81..95b80bca7c0d3839815561d17b83d2141de86da2 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -14,7 +14,6 @@ class RepositoryImportWorker project.imported = true project.save project.satellite.create unless project.satellite.exists? - project.discover_default_branch else project.imported = false end diff --git a/config.ru b/config.ru index dfd2d862237323aa599be31b473d70a8a817943b..e90863a5c21baf365868b4ad325c88cd52338949 100644 --- a/config.ru +++ b/config.ru @@ -1,5 +1,14 @@ # This file is used by Rack-based servers to start the application. +if defined?(Unicorn) + require 'unicorn' + # Unicorn self-process killer + require 'unicorn/worker_killer' + + # Max memory size (RSS) per worker + use Unicorn::WorkerKiller::Oom, (200 * (1 << 20)), (250 * (1 << 20)) +end + require ::File.expand_path('../config/environment', __FILE__) map ENV['RAILS_RELATIVE_URL_ROOT'] || "/" do diff --git a/config/application.rb b/config/application.rb index d85bcab7885b62bc6f656ea5486b2c672be5269d..88759ce7cf31c3a4eeff6e989d2950272a4cbe47 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,13 +1,9 @@ require File.expand_path('../boot', __FILE__) require 'rails/all' +require 'devise' -if defined?(Bundler) - # If you precompile assets before deploying to production, use this line - # Bundler.require(*Rails.groups(assets: %w(development test))) - # If you want your assets lazily compiled in production, use this line - Bundler.require(:default, :assets, Rails.env) -end +Bundler.require(:default, Rails.env) module Gitlab class Application < Rails::Application @@ -16,7 +12,7 @@ module Gitlab # -- all .rb files in that directory are automatically loaded. # Custom directories with classes and modules you want to be autoloadable. - config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/models/concerns) + config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/models/concerns #{config.root}/app/models/project_services) # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named. @@ -42,6 +38,7 @@ module Gitlab # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de + config.i18n.enforce_available_locales = false # Configure the default encoding used in templates for Ruby 1.9. config.encoding = "utf-8" @@ -70,15 +67,24 @@ module Gitlab config.assets.version = '1.0' # Uncomment and customize the last line to run in a non-root path - # WARNING: This feature is known to work, but unsupported - # Note that three settings need to be changed for this to work. + # WARNING: We recommend creating a FQDN to host GitLab in a root path instead of this. + # Note that four settings need to be changed for this to work. # 1) In your application.rb file: config.relative_url_root = "/gitlab" # 2) In your gitlab.yml file: relative_url_root: /gitlab # 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab" + # 4) In ../gitlab-shell/config.yml: gitlab_url: "http://127.0.0.1/gitlab" + # To update the path, run: sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production # # config.relative_url_root = "/gitlab" - # Uncomment to enable rack attack middleware - # config.middleware.use Rack::Attack + config.middleware.use Rack::Attack + + # Allow access to GitLab API from other domains + config.middleware.use Rack::Cors do + allow do + origins '*' + resource '/api/*', headers: :any, methods: [:get, :post, :options, :put, :delete] + end + end end end diff --git a/config/database.yml.mysql b/config/database.yml.mysql index a3eff1a74f8a5b4fa8acc57994371b3f6bcac0e8..55ac088bc1df920a4c11fc3741f3b3cecb410b97 100644 --- a/config/database.yml.mysql +++ b/config/database.yml.mysql @@ -7,7 +7,7 @@ production: reconnect: false database: gitlabhq_production pool: 10 - username: root + username: git password: "secure password" # host: localhost # socket: /tmp/mysql.sock diff --git a/config/database.yml.postgresql b/config/database.yml.postgresql index 4b74f3348f8a3a54baeb9df9c53d34926c3e7a9a..66960551cfde14278d8f5e166ed9d0e3ba7431ab 100644 --- a/config/database.yml.postgresql +++ b/config/database.yml.postgresql @@ -6,8 +6,8 @@ production: encoding: unicode database: gitlabhq_production pool: 10 - username: git - password: + # username: git + # password: # host: localhost # port: 5432 # socket: /tmp/postgresql.sock diff --git a/config/environments/development.rb b/config/environments/development.rb index 6cba17f6ea2152f3a371ca3d63c4b24d6961824c..e4c7649fda044bfb022f774f45549198f3f9902e 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -6,9 +6,6 @@ Gitlab::Application.configure do # since you don't have to restart the web server when you make code changes. config.cache_classes = false - # Log error messages when you accidentally call methods on nil. - config.whiny_nils = true - # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false @@ -25,10 +22,6 @@ Gitlab::Application.configure do # Raise exception on mass assignment protection for Active Record models config.active_record.mass_assignment_sanitizer = :strict - # Log the query plan for queries taking more than this (works - # with SQLite, MySQL, and PostgreSQL) - config.active_record.auto_explain_threshold_in_seconds = 0.5 - # Do not compress assets config.assets.compress = false @@ -39,4 +32,6 @@ Gitlab::Application.configure do config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } # Open sent mails in browser config.action_mailer.delivery_method = :letter_opener + + config.eager_load = false end diff --git a/config/environments/production.rb b/config/environments/production.rb index e3476be8fba969eef62419e25dbf3ba0a8dca61b..9ac4622abc28a158b78ab8c6e2b92383a9a749a7 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -80,4 +80,9 @@ Gitlab::Application.configure do # # } config.action_mailer.perform_deliveries = true config.action_mailer.raise_delivery_errors = true + + config.eager_load = true + config.assets.js_compressor = :uglifier + + config.allow_concurrency = false end diff --git a/config/environments/test.rb b/config/environments/test.rb index b626986299ba6830068ef83eb9f001bfcb049bae..3860dc5c74c588a42fc70056b5d9e5f968746585 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -11,9 +11,6 @@ Gitlab::Application.configure do config.serve_static_assets = true config.static_cache_control = "public, max-age=3600" - # Log error messages when you accidentally call methods on nil - config.whiny_nils = true - # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false @@ -34,4 +31,6 @@ Gitlab::Application.configure do # Print deprecation notices to the stderr config.active_support.deprecation = :stderr + + config.eager_load = false end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 0b1560ac587957e38ac7fcb1781dc84d0d884385..ce57465d687daa4f34dcd2b399e4a13ff9a8da70 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -14,17 +14,19 @@ production: &base ## GitLab settings gitlab: - ## Web server settings + ## Web server settings (note: host is the FQDN, do not include http://) host: localhost port: 80 https: false # Uncomment and customize the last line to run in a non-root path - # WARNING: This feature is known to work, but unsupported - # Note that three settings need to be changed for this to work. + # WARNING: We recommend creating a FQDN to host GitLab in a root path instead of this. + # Note that four settings need to be changed for this to work. # 1) In your application.rb file: config.relative_url_root = "/gitlab" # 2) In your gitlab.yml file: relative_url_root: /gitlab # 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab" + # 4) In ../gitlab-shell/config.yml: gitlab_url: "http://127.0.0.1/gitlab" + # To update the path, run: sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production # # relative_url_root: /gitlab @@ -55,10 +57,15 @@ production: &base # default: false - Account passwords are not sent via the email if signup is enabled. # signup_enabled: true + # Restrict setting visibility levels for non-admin users. + # The default is to allow all levels. + #restricted_visibility_levels: [ "public" ] + ## Automatic issue closing - # If a commit message matches this regular express, all issues referenced from the matched text will be closed - # if it's pushed to a project's default branch. - # issue_closing_pattern: ^([Cc]loses|[Ff]ixes) +#\d+ + # If a commit message matches this regular expression, all issues referenced from the matched text will be closed. + # This happens when the commit is pushed or merged into the default branch of a project. + # When not specified the default issue_closing_pattern as specified below will be used. + # issue_closing_pattern: '([Cc]lose[sd]|[Ff]ixe[sd]) +#\d+' ## Default project features settings default_projects_features: @@ -67,11 +74,12 @@ production: &base wiki: true wall: false snippets: false - public: false + visibility_level: "private" # can be "private" | "internal" | "public" ## External issues trackers issues_tracker: # redmine: + # title: "Redmine" # ## If not nil, link 'Issues' on project page will be replaced with this # ## Use placeholders: # ## :project_id - GitLab project identifier @@ -92,6 +100,7 @@ production: &base # new_issue_url: "http://redmine.sample/projects/:issues_tracker_id/issues/new" # # jira: + # title: "Atlassian Jira" # project_url: "http://jira.sample/issues/?jql=project=:issues_tracker_id" # issues_url: "http://jira.sample/browse/:id" # new_issue_url: "http://jira.sample/secure/CreateIssue.jspa" @@ -107,15 +116,26 @@ production: &base # ========================== ## LDAP settings + # You can inspect a sample of the LDAP users with login access by running: + # bundle exec rake gitlab:ldap:check RAILS_ENV=production ldap: enabled: false host: '_your_ldap_server' base: '_the_base_where_you_search_for_users' port: 636 uid: 'sAMAccountName' - method: 'ssl' # "ssl" or "plain" + method: 'ssl' # "tls" or "ssl" or "plain" bind_dn: '_the_full_dn_of_the_user_you_will_bind_with' password: '_the_password_of_the_bind_user' + # If allow_username_or_email_login is enabled, GitLab will ignore everything + # after the first '@' in the LDAP username submitted by the user on login. + # + # Example: + # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials; + # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'. + # + # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to + # disable this setting, because the userPrincipalName contains an '@'. allow_username_or_email_login: true ## OmniAuth settings @@ -133,7 +153,7 @@ production: &base ## Auth providers # Uncomment the following lines and fill in the data of the auth provider you want to use # If your favorite auth provider is not listed you can use others: - # see https://github.com/gitlabhq/gitlabhq/wiki/Using-Custom-Omniauth-Providers + # see https://github.com/gitlabhq/gitlab-public-wiki/wiki/Working-custom-omniauth-provider-configurations # The 'app_id' and 'app_secret' parameters are always passed as the first two # arguments, followed by optional 'args' which can be either a hash or an array. providers: @@ -143,7 +163,8 @@ production: &base # - { name: 'twitter', app_id: 'YOUR APP ID', # app_secret: 'YOUR APP SECRET'} # - { name: 'github', app_id: 'YOUR APP ID', - # app_secret: 'YOUR APP SECRET' } + # app_secret: 'YOUR APP SECRET', + # args: { scope: 'user:email' } } @@ -163,6 +184,8 @@ production: &base ## GitLab Shell settings gitlab_shell: + path: /home/git/gitlab-shell/ + # REPOS_PATH MUST NOT BE A SYMLINK!!! repos_path: /home/git/repositories/ hooks_path: /home/git/gitlab-shell/hooks/ @@ -179,7 +202,8 @@ production: &base # Use the default values unless you really know what you are doing git: bin_path: /usr/bin/git - # Max size of a git object (e.g. a commit), in bytes + # The next value is the maximum memory size grit can use + # Given in number of bytes per git object (e.g. a commit) # This value can be increased if you have very large commits max_size: 5242880 # 5.megabytes # Git timeout to read a commit, in seconds @@ -205,6 +229,7 @@ test: <<: *base issues_tracker: redmine: + title: "Redmine" project_url: "http://redmine/projects/:issues_tracker_id" issues_url: "http://redmine/:project_id/:issues_tracker_id/:id" new_issue_url: "http://redmine/projects/:issues_tracker_id/issues/new" diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 1c8758d942032c7280a17dd0d97c1593d2362cbb..cf6c79bb50e39735c046c96af5061c5db5cd5d90 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -3,8 +3,8 @@ class Settings < Settingslogic namespace Rails.env class << self - def gitlab_on_non_standard_port? - ![443, 80].include?(gitlab.port.to_i) + def gitlab_on_standard_port? + gitlab.port.to_i == (gitlab.https ? 443 : 80) end private @@ -18,11 +18,7 @@ class Settings < Settingslogic end def build_gitlab_url - if gitlab_on_non_standard_port? - custom_port = ":#{gitlab.port}" - else - custom_port = nil - end + custom_port = gitlab_on_standard_port? ? nil : ":#{gitlab.port}" [ gitlab.protocol, "://", gitlab.host, @@ -30,6 +26,29 @@ class Settings < Settingslogic gitlab.relative_url_root ].join('') end + + # check that values in `current` (string or integer) is a contant in `modul`. + def verify_constant_array(modul, current, default) + values = default || [] + if !current.nil? + values = [] + current.each do |constant| + values.push(verify_constant(modul, constant, nil)) + end + values.delete_if { |value| value.nil? } + end + values + end + + # check that `current` (string or integer) is a contant in `modul`. + def verify_constant(modul, current, default) + constant = modul.constants.find{ |name| modul.const_get(name) == current } + value = constant.nil? ? default : modul.const_get(constant) + if current.is_a? String + value = modul.const_get(current.upcase) rescue default + end + value + end end end @@ -68,15 +87,16 @@ rescue ArgumentError # no user configured '/home/' + Settings.gitlab['user'] end Settings.gitlab['signup_enabled'] ||= false +Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil? -Settings.gitlab['issue_closing_pattern'] = '^([Cc]loses|[Ff]ixes) #(\d+)' if Settings.gitlab['issue_closing_pattern'].nil? +Settings.gitlab['issue_closing_pattern'] = '([Cc]loses|[Ff]ixes) #(\d+)' if Settings.gitlab['issue_closing_pattern'].nil? Settings.gitlab['default_projects_features'] ||= {} Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil? Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.gitlab.default_projects_features['merge_requests'].nil? Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil? Settings.gitlab.default_projects_features['wall'] = false if Settings.gitlab.default_projects_features['wall'].nil? Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil? -Settings.gitlab.default_projects_features['public'] = false if Settings.gitlab.default_projects_features['public'].nil? +Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE) # # Gravatar @@ -90,6 +110,7 @@ Settings.gravatar['ssl_url'] ||= 'https://secure.gravatar.com/avatar/%{hash}? # GitLab Shell # Settings['gitlab_shell'] ||= Settingslogic.new({}) +Settings.gitlab_shell['path'] ||= Settings.gitlab['user_home'] + '/gitlab-shell/' Settings.gitlab_shell['hooks_path'] ||= Settings.gitlab['user_home'] + '/gitlab-shell/hooks/' Settings.gitlab_shell['receive_pack'] = true if Settings.gitlab_shell['receive_pack'].nil? Settings.gitlab_shell['upload_pack'] = true if Settings.gitlab_shell['upload_pack'].nil? diff --git a/config/initializers/3_grit_ext.rb b/config/initializers/3_grit_ext.rb index 8b298e821e795e766f3e849b83911b3060fd51d2..6540ac839cb1e5242678ee25b35b129c4bee8237 100644 --- a/config/initializers/3_grit_ext.rb +++ b/config/initializers/3_grit_ext.rb @@ -1,5 +1,4 @@ require 'grit' -require 'pygments' Grit::Git.git_binary = Gitlab.config.git.bin_path Grit::Git.git_timeout = Gitlab.config.git.timeout diff --git a/config/initializers/5_backend.rb b/config/initializers/5_backend.rb index e60d9559c9462852abd8f1ae9a7c0b6426fff7a2..7c2e7f3900053c1b1b9b893faa0f1dc9c2c244ba 100644 --- a/config/initializers/5_backend.rb +++ b/config/initializers/5_backend.rb @@ -6,6 +6,3 @@ require Rails.root.join("lib", "gitlab", "backend", "shell") # GitLab shell adapter require Rails.root.join("lib", "gitlab", "backend", "shell_adapter") - -# Gitlab Git repos path -Gitlab::Git::Repository.repos_path = Gitlab.config.gitlab_shell.repos_path diff --git a/config/initializers/carrierwave.rb b/config/initializers/carrierwave.rb index 45bc68f32206d5d52b4a356c5153a84b77281efc..6875fa74eddeb3afe53c8cd29b6cb0129994605c 100644 --- a/config/initializers/carrierwave.rb +++ b/config/initializers/carrierwave.rb @@ -15,5 +15,7 @@ if File.exists?(aws_file) config.fog_directory = AWS_CONFIG['bucket'] # required config.fog_public = false # optional, defaults to true config.fog_attributes = {'Cache-Control'=>'max-age=315576000'} # optional, defaults to {} + config.fog_authenticated_url_expiration = 1 << 29 # optional time (in seconds) that authenticated urls will be valid. + # when fog_public is false and provider is AWS or Google, defaults to 600 end end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 39c1b7c235b44394d994549a154f9ddda3de5d14..a02bf9d4aec9e9d7e8b78176d255da767a9bf2fd 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -6,6 +6,7 @@ Devise.setup do |config| # note that it will be overwritten if you use your own mailer class with default "from" parameter. config.mailer_sender = Gitlab.config.gitlab.email_from + # Configure the class responsible to send e-mails. # config.mailer = "Devise::Mailer" @@ -54,6 +55,8 @@ Devise.setup do |config| # The realm used in Http Basic Authentication. "Application" by default. # config.http_authentication_realm = "Application" + config.reconfirmable = true + # It will change confirmation, password recovery and other workflows # to behave the same regardless if the e-mail provided was right or wrong. # Does not affect registerable. @@ -72,13 +75,13 @@ Devise.setup do |config| # config.pepper = "2ef62d549c4ff98a5d3e0ba211e72cff592060247e3bbbb9f499af1222f876f53d39b39b823132affb32858168c79c1d7741d26499901b63c6030a42129924ef" # ==> Configuration for :confirmable - # The time you want to give your user to confirm his account. During this time - # he will be able to access your application without confirming. Default is 0.days - # When confirm_within is zero, the user won't be able to sign in without confirming. + # The time you want to give a user to confirm their account. During this time + # they will be able to access your application without confirming. Default is 0.days + # When allow_unconfirmed_access_for is zero, the user won't be able to sign in without confirming. # You can use this to let your user access some features of your application # without confirming the account, but blocking it after a certain period # (ie 2 days). - # config.confirm_within = 2.days + # config.allow_unconfirmed_access_for = 2.days # Defines which key will be used when confirming an account # config.confirmation_keys = [ :email ] @@ -99,7 +102,7 @@ Devise.setup do |config| # ==> Configuration for :validatable # Range for password length. Default is 6..128. - config.password_length = 6..128 + config.password_length = 8..128 # Email regex used to validate email formats. It simply asserts that # an one (and only one) @ exists in the given string. This is mainly @@ -224,15 +227,21 @@ Devise.setup do |config| end Gitlab.config.omniauth.providers.each do |provider| + provider_arguments = [] + + %w[app_id app_secret].each do |argument| + provider_arguments << provider[argument] if provider[argument] + end + case provider['args'] when Array # An Array from the configuration will be expanded. - config.omniauth provider['name'].to_sym, provider['app_id'], provider['app_secret'], *provider['args'] + provider_arguments.concat provider['args'] when Hash # A Hash from the configuration will be passed as is. - config.omniauth provider['name'].to_sym, provider['app_id'], provider['app_secret'], provider['args'] - else - config.omniauth provider['name'].to_sym, provider['app_id'], provider['app_secret'] + provider_arguments << provider['args'] end + + config.omniauth provider['name'].to_sym, *provider_arguments end end diff --git a/config/initializers/devise_async.rb b/config/initializers/devise_async.rb new file mode 100644 index 0000000000000000000000000000000000000000..05a1852cdbd9d141a757755983adc59ad467f3ca --- /dev/null +++ b/config/initializers/devise_async.rb @@ -0,0 +1 @@ +Devise::Async.backend = :sidekiq diff --git a/config/initializers/devise_password_length.rb.example b/config/initializers/devise_password_length.rb.example new file mode 100644 index 0000000000000000000000000000000000000000..97305825e07ccf9c7fd29e60c227168fd548ee64 --- /dev/null +++ b/config/initializers/devise_password_length.rb.example @@ -0,0 +1,6 @@ +Devise.setup do |config| + # The following line changes the password length limits for new users. In the + # example below the minimum length is 12 characters, and the maximum length + # is 128 characters. + config.password_length = 12..128 +end diff --git a/config/initializers/gemoji.rb b/config/initializers/gemoji.rb index 8c85aad5d3ba4ba4a4b9a962aedb9e086aa687c6..6cc33aced77900ec0ec71f958837800e94b7ede8 100644 --- a/config/initializers/gemoji.rb +++ b/config/initializers/gemoji.rb @@ -1,2 +1,3 @@ # Workaround for https://github.com/github/gemoji/pull/18 +require 'gemoji' Gitlab::Application.config.assets.paths << Emoji.images_path diff --git a/config/initializers/rack_attack.rb.example b/config/initializers/rack_attack.rb.example index 76fa7ad282e9350fa1b14b0f882493e42deee3b3..bc3234bf0b68d05f500d6149c6bb4efc19c2357f 100644 --- a/config/initializers/rack_attack.rb.example +++ b/config/initializers/rack_attack.rb.example @@ -1,16 +1,18 @@ -# To enable rack-attack for your GitLab instance do the following: -# 1. In config/application.rb find and uncomment the following line: -# config.middleware.use Rack::Attack -# 2. Rename this file to rack_attack.rb -# 3. Review the paths_to_be_protected and add any other path you need protecting -# 4. Restart GitLab instance +# 1. Rename this file to rack_attack.rb +# 2. Review the paths_to_be_protected and add any other path you need protecting # paths_to_be_protected = [ "#{Rails.application.config.relative_url_root}/users/password", "#{Rails.application.config.relative_url_root}/users/sign_in", - "#{Rails.application.config.relative_url_root}/users" + "#{Rails.application.config.relative_url_root}/api/#{API::API.version}/session.json", + "#{Rails.application.config.relative_url_root}/api/#{API::API.version}/session", + "#{Rails.application.config.relative_url_root}/users", + "#{Rails.application.config.relative_url_root}/users/confirmation" ] -Rack::Attack.throttle('protected paths', limit: 6, period: 60.seconds) do |req| - req.ip if paths_to_be_protected.include?(req.path) && req.post? + +unless Rails.env.test? + Rack::Attack.throttle('protected paths', limit: 10, period: 60.seconds) do |req| + req.ip if paths_to_be_protected.include?(req.path) && req.post? + end end diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb index 16d1d4a9fdd0a41e16a4009c0e43eeb7dd0a14d7..98400290113acb77ee820b20098c702d6724796e 100644 --- a/config/initializers/secret_token.rb +++ b/config/initializers/secret_token.rb @@ -21,3 +21,4 @@ def find_secure_token end Gitlab::Application.config.secret_token = find_secure_token +Gitlab::Application.config.secret_key_base = find_secure_token diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index 52a099c3e169705eeabbabd2f719e88207b2ce21..f80b67a554bdc65730a47d88c7cfda89ca05a9ff 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -1,11 +1,10 @@ # Be sure to restart your server when you modify this file. -Gitlab::Application.config.session_store :cookie_store, key: '_gitlab_session', - secure: Gitlab::Application.config.force_ssl, - httponly: true, - path: (Rails.application.config.relative_url_root.nil?) ? '/' : Rails.application.config.relative_url_root - -# Use the database for sessions instead of the cookie-based default, -# which shouldn't be used to store highly confidential information -# (create the session table with "rails generate session_migration") -# Gitlab::Application.config.session_store :active_record_store +Gitlab::Application.config.session_store( + :redis_store, # Using the cookie_store would enable session replay attacks. + servers: Gitlab::Application.config.cache_store.last, # re-use the Redis config from the Rails cache store + key: '_gitlab_session', + secure: Gitlab.config.gitlab.https, + httponly: true, + path: (Rails.application.config.relative_url_root.nil?) ? '/' : Rails.application.config.relative_url_root +) diff --git a/config/initializers/smtp_settings.rb.sample b/config/initializers/smtp_settings.rb.sample index e62ad0f4b7140786303f2496461b4064dfcd1ca8..3711b03796e797c5dc76544b6cd9bf611b7af594 100644 --- a/config/initializers/smtp_settings.rb.sample +++ b/config/initializers/smtp_settings.rb.sample @@ -1,11 +1,11 @@ # To enable smtp email delivery for your GitLab instance do next: -# 1. Change config/environments/production.rb to use smtp -# config.action_mailer.delivery_method = :smtp -# 2. Rename this file to smtp_settings.rb -# 3. Edit settings inside this file -# 4. Restart GitLab instance +# 1. Rename this file to smtp_settings.rb +# 2. Edit settings inside this file +# 3. Restart GitLab instance # -if Gitlab::Application.config.action_mailer.delivery_method == :smtp +if Rails.env.production? + Gitlab::Application.config.action_mailer.delivery_method = :smtp + ActionMailer::Base.smtp_settings = { address: "email.server.com", port: 456, diff --git a/config/routes.rb b/config/routes.rb index 1d2b4d737363a7aea853764ca3c6f320dab30f09..1cc6242c623b68ae54db36bcc20ad0e37428d631 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,6 +6,7 @@ Gitlab::Application.routes.draw do # Search # get 'search' => "search#show" + get 'search/autocomplete' => "search#autocomplete", as: :search_autocomplete # API API::API.logger Rails.logger @@ -25,7 +26,7 @@ Gitlab::Application.routes.draw do project_root: Gitlab.config.gitlab_shell.repos_path, upload_pack: Gitlab.config.gitlab_shell.upload_pack, receive_pack: Gitlab.config.gitlab_shell.receive_pack - }), at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) } + }), at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post] # # Help @@ -89,42 +90,50 @@ Gitlab::Application.routes.draw do get :test end + resources :broadcast_messages, only: [:index, :create, :destroy] resource :logs, only: [:show] resource :background_jobs, controller: 'background_jobs', only: [:show] - resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, only: [:index, :show] + + resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, only: [:index, :show] do + member do + put :transfer + end + end + root to: "dashboard#index" end - get "errors/githost" - # # Profile Area # resource :profile, only: [:show, :update] do member do - get :account get :history - get :token get :design - put :update_password put :reset_private_token put :update_username end scope module: :profiles do + resource :account, only: [:show, :update] resource :notifications, only: [:show, :update] - resource :password, only: [:new, :create] + resource :password, only: [:new, :create, :edit, :update] do + member do + put :reset + end + end resources :keys resources :groups, only: [:index] do member do delete :leave end end + resource :avatar, only: [:destroy] end end - match "/u/:username" => "users#show", as: :user, constraints: { username: /.*/ } + match "/u/:username" => "users#show", as: :user, constraints: { username: /.*/ }, via: :get @@ -150,6 +159,9 @@ Gitlab::Application.routes.draw do end resources :users_groups, only: [:create, :update, :destroy] + scope module: :groups do + resource :avatar, only: [:destroy] + end end resources :projects, constraints: { id: /[^\/]+/ }, only: [:new, :create] @@ -163,20 +175,24 @@ Gitlab::Application.routes.draw do member do put :transfer post :fork + post :archive + post :unarchive get :autocomplete_sources end scope module: :projects do - resources :blob, only: [:show], constraints: {id: /.+/} - resources :raw, only: [:show], constraints: {id: /.+/} - resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ } - resources :edit_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'edit' - resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/} - resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/} - resources :compare, only: [:index, :create] - resources :blame, only: [:show], constraints: {id: /.+/} + resources :blob, only: [:show, :destroy], constraints: {id: /.+/} + resources :raw, only: [:show], constraints: {id: /.+/} + resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ } + resources :edit_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'edit' + resources :new_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'new' + resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/} + resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/} + resources :compare, only: [:index, :create] + resources :blame, only: [:show], constraints: {id: /.+/} resources :network, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/} - resources :graphs, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/} + resources :graphs, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/} + match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/} resources :snippets, constraints: {id: /\d+/} do @@ -206,7 +222,7 @@ Gitlab::Application.routes.draw do resource :repository, only: [:show] do member do get "stats" - get "archive" + get "archive", constraints: { format: Gitlab::Regex.archive_formats_regex } end end @@ -223,14 +239,14 @@ Gitlab::Application.routes.draw do end end - resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ } do + resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } do collection do - get :recent + get :recent, constraints: { id: Gitlab::Regex.git_reference_regex } end end - resources :tags, only: [:index, :new, :create, :destroy], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ } - resources :protected_branches, only: [:index, :create, :destroy], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ } + resources :tags, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } + resources :protected_branches, only: [:index, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } resources :refs, only: [] do collection do @@ -239,11 +255,11 @@ Gitlab::Application.routes.draw do member do # tree viewer logs - get "logs_tree", constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ } + get "logs_tree", constraints: { id: Gitlab::Regex.git_reference_regex } get "logs_tree/:path" => "refs#logs_tree", as: :logs_file, constraints: { - id: /[a-zA-Z.0-9\/_\-#%+]+/, + id: Gitlab::Regex.git_reference_regex, path: /.*/ } end @@ -287,6 +303,7 @@ Gitlab::Application.routes.draw do resources :team_members, except: [:index, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ } do collection do + delete :leave # Used for import team # from another project diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example index e4e1342683187a6f2a2b30b56aa7d50a0cb2c304..ba5e5cdde0b14776b080ff7c3ceb3f129faf07be 100644 --- a/config/unicorn.rb.example +++ b/config/unicorn.rb.example @@ -9,11 +9,13 @@ # documentation. # Uncomment and customize the last line to run in a non-root path -# WARNING: This feature is known to work, but unsupported -# Note that three settings need to be changed for this to work. +# WARNING: We recommend creating a FQDN to host GitLab in a root path instead of this. +# Note that four settings need to be changed for this to work. # 1) In your application.rb file: config.relative_url_root = "/gitlab" # 2) In your gitlab.yml file: relative_url_root: /gitlab # 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab" +# 4) In ../gitlab-shell/config.yml: gitlab_url: "http://127.0.0.1/gitlab" +# To update the path, run: sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production # # ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab" diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb index ecea8211393c86371728db405b8482f75b52984a..3e76d76e8383b7b5af9f5434531dab0e4956df26 100644 --- a/db/fixtures/development/01_admin.rb +++ b/db/fixtures/development/01_admin.rb @@ -11,3 +11,5 @@ User.seed(:id, [ theme_id: Gitlab::Theme::MARS } ]) + +User.find(1).confirm! diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb index 43178dee25d6798369ef9d173e7523cf627efd81..50726a5a51ee513e5f86012d1da80a8fbe5687ec 100644 --- a/db/fixtures/development/04_project.rb +++ b/db/fixtures/development/04_project.rb @@ -19,13 +19,14 @@ project_urls = [ project_urls.each_with_index do |url, i| group_path, project_path = url.split('/')[-2..-1] - group = Group.find_by_path(group_path) + group = Group.find_by(path: group_path) unless group group = Group.new( name: group_path.titleize, path: group_path ) + group.description = Faker::Lorem.sentence group.owner = User.first group.save end @@ -35,10 +36,11 @@ project_urls.each_with_index do |url, i| params = { import_url: url, namespace_id: group.id, - name: project_path.titleize + name: project_path.titleize, + description: Faker::Lorem.sentence } - project = Projects::CreateContext.new(User.first, params).execute + project = Projects::CreateService.new(User.first, params).execute if project.valid? print '.' diff --git a/db/fixtures/development/05_users.rb b/db/fixtures/development/05_users.rb index cbb3e636accfe38e3fc4784dc10def26dda2c4bb..3440a645408385293008c833ce3ec1c3d411c087 100644 --- a/db/fixtures/development/05_users.rb +++ b/db/fixtures/development/05_users.rb @@ -1,5 +1,5 @@ Gitlab::Seeder.quiet do - (2..50).each do |i| + (2..10).each do |i| begin User.seed(:id, [{ id: i, diff --git a/db/fixtures/development/09_issues.rb b/db/fixtures/development/09_issues.rb index 31ba77254a399ca08f841c73d0c987d855bef99e..2b81d7a25971154c936df55265942aab2e946139 100644 --- a/db/fixtures/development/09_issues.rb +++ b/db/fixtures/development/09_issues.rb @@ -6,22 +6,28 @@ Gitlab::Seeder.quiet do project = Project.all.sample # Random user - user = project.users.sample + user = project.team.users.sample next unless user user_id = user.id - Thread.current[:current_user] = user - Issue.seed(:id, [{ - id: i, - project_id: project.id, - author_id: user_id, - assignee_id: user_id, - state: ['opened', 'closed'].sample, - milestone: project.milestones.sample, - title: Faker::Lorem.sentence(6) - }]) + begin + Thread.current[:current_user] = user + + Issue.seed(:id, [{ + id: i, + project_id: project.id, + author_id: user_id, + assignee_id: user_id, + state: ['opened', 'closed'].sample, + milestone: project.milestones.sample, + title: Faker::Lorem.sentence(6), + description: Faker::Lorem.sentence + }]) + ensure + Thread.current[:current_user] = nil + end print('.') end diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb index 1e61ea28636e7b44df374ad62e2516c23d63e0af..2b1f41602114f89af441a06fc22ea5d987f86fca 100644 --- a/db/fixtures/development/10_merge_requests.rb +++ b/db/fixtures/development/10_merge_requests.rb @@ -6,7 +6,7 @@ Gitlab::Seeder.quiet do project = Project.all.sample # Random user - user = project.users.sample + user = project.team.users.sample next unless user @@ -17,19 +17,23 @@ Gitlab::Seeder.quiet do next if branches.uniq.size < 2 user_id = user.id - Thread.current[:current_user] = user - - MergeRequest.seed(:id, [{ - id: i, - source_branch: branches.first, - target_branch: branches.last, - source_project_id: project.id, - target_project_id: project.id, - author_id: user_id, - assignee_id: user_id, - milestone: project.milestones.sample, - title: Faker::Lorem.sentence(6) - }]) + begin + Thread.current[:current_user] = user + + MergeRequest.seed(:id, [{ + id: i, + source_branch: branches.first, + target_branch: branches.last, + source_project_id: project.id, + target_project_id: project.id, + author_id: user_id, + assignee_id: user_id, + milestone: project.milestones.sample, + title: Faker::Lorem.sentence(6) + }]) + ensure + Thread.current[:current_user] = nil + end print('.') end end diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb index 1b77d94905d7f6dad2ba7e4c277074fdb7577705..a919fad70400ea1ac5c1d8f359db38d8ad07d39f 100644 --- a/db/fixtures/production/001_admin.rb +++ b/db/fixtures/production/001_admin.rb @@ -12,6 +12,7 @@ admin = User.create( admin.projects_limit = 10000 admin.admin = true admin.save! +admin.confirm! if admin.valid? puts %q[ diff --git a/db/migrate/20110913200833_devise_create_users.rb b/db/migrate/20110913200833_devise_create_users.rb deleted file mode 100644 index e00f275c55d272e9d4445e486fd3cc99e76a1273..0000000000000000000000000000000000000000 --- a/db/migrate/20110913200833_devise_create_users.rb +++ /dev/null @@ -1,55 +0,0 @@ -class DeviseCreateUsers < ActiveRecord::Migration - def self.up - create_table(:users) do |t| - ## Database authenticatable - t.string :email, :null => false, :default => "" - t.string :encrypted_password, :null => false, :default => "" - - ## Recoverable - t.string :reset_password_token - t.datetime :reset_password_sent_at - - ## Rememberable - t.datetime :remember_created_at - - ## Trackable - t.integer :sign_in_count, :default => 0 - t.datetime :current_sign_in_at - t.datetime :last_sign_in_at - t.string :current_sign_in_ip - t.string :last_sign_in_ip - - ## Encryptable - # t.string :password_salt - - ## Confirmable - # t.string :confirmation_token - # t.datetime :confirmed_at - # t.datetime :confirmation_sent_at - # t.string :unconfirmed_email # Only if using reconfirmable - - ## Lockable - # t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts - # t.string :unlock_token # Only if unlock strategy is :email or :both - # t.datetime :locked_at - - # Token authenticatable - # t.string :authentication_token - - ## Invitable - # t.string :invitation_token - - t.timestamps - end - - add_index :users, :email, :unique => true - add_index :users, :reset_password_token, :unique => true - # add_index :users, :confirmation_token, :unique => true - # add_index :users, :unlock_token, :unique => true - # add_index :users, :authentication_token, :unique => true - end - - def self.down - drop_table :users - end -end diff --git a/db/migrate/20110913204141_create_projects.rb b/db/migrate/20110913204141_create_projects.rb deleted file mode 100644 index 45b76f955637fb1519dd54a981461869c692463d..0000000000000000000000000000000000000000 --- a/db/migrate/20110913204141_create_projects.rb +++ /dev/null @@ -1,11 +0,0 @@ -class CreateProjects < ActiveRecord::Migration - def change - create_table :projects do |t| - t.string :name - t.string :path - t.text :description - - t.timestamps - end - end -end diff --git a/db/migrate/20110914221600_create_users_projects.rb b/db/migrate/20110914221600_create_users_projects.rb deleted file mode 100644 index a89798ae86f6382a661901d1caaa8613e419ae89..0000000000000000000000000000000000000000 --- a/db/migrate/20110914221600_create_users_projects.rb +++ /dev/null @@ -1,13 +0,0 @@ -class CreateUsersProjects < ActiveRecord::Migration - def change - create_table :users_projects do |t| - t.integer :user_id, :null => false - t.integer :project_id, :null => false - t.boolean :read, :default => false - t.boolean :write, :default => false - t.boolean :admin, :default => false - - t.timestamps - end - end -end diff --git a/db/migrate/20110915205627_add_private_flag_to_project.rb b/db/migrate/20110915205627_add_private_flag_to_project.rb deleted file mode 100644 index 73c0b9df8348de4eb53c81aba2509acb88d6b478..0000000000000000000000000000000000000000 --- a/db/migrate/20110915205627_add_private_flag_to_project.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddPrivateFlagToProject < ActiveRecord::Migration - def change - add_column :projects, :private_flag, :boolean, :default => true, :null => false - end -end diff --git a/db/migrate/20110915213352_create_keys.rb b/db/migrate/20110915213352_create_keys.rb deleted file mode 100644 index d4615b4babf32501217acde33632cde9633cfd44..0000000000000000000000000000000000000000 --- a/db/migrate/20110915213352_create_keys.rb +++ /dev/null @@ -1,9 +0,0 @@ -class CreateKeys < ActiveRecord::Migration - def change - create_table :keys do |t| - t.integer :user_id, :null => false - t.text :project_id, :null => false - t.timestamps - end - end -end diff --git a/db/migrate/20110916123731_add_name_to_user.rb b/db/migrate/20110916123731_add_name_to_user.rb deleted file mode 100644 index 74142b05cb5fec1365bae83108b498b522b1d208..0000000000000000000000000000000000000000 --- a/db/migrate/20110916123731_add_name_to_user.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddNameToUser < ActiveRecord::Migration - def change - add_column :users, :name, :string - end -end diff --git a/db/migrate/20110916162511_add_key_title_to_key.rb b/db/migrate/20110916162511_add_key_title_to_key.rb deleted file mode 100644 index b2eaa51cccf6799589ab01707e9364e46f62f6e0..0000000000000000000000000000000000000000 --- a/db/migrate/20110916162511_add_key_title_to_key.rb +++ /dev/null @@ -1,7 +0,0 @@ -class AddKeyTitleToKey < ActiveRecord::Migration - def change - add_column :keys, :key, :text - add_column :keys, :title, :string - remove_column :keys, :project_id - end -end diff --git a/db/migrate/20110917212932_add_identifier_to_key.rb b/db/migrate/20110917212932_add_identifier_to_key.rb deleted file mode 100644 index e572793952aa43621c7dd331891f9cf70061b530..0000000000000000000000000000000000000000 --- a/db/migrate/20110917212932_add_identifier_to_key.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddIdentifierToKey < ActiveRecord::Migration - def change - add_column :keys, :identifier, :string - end -end diff --git a/db/migrate/20110921192501_create_issues.rb b/db/migrate/20110921192501_create_issues.rb deleted file mode 100644 index 63b42ad998eb500652f79af58925b564f73f9b79..0000000000000000000000000000000000000000 --- a/db/migrate/20110921192501_create_issues.rb +++ /dev/null @@ -1,13 +0,0 @@ -class CreateIssues < ActiveRecord::Migration - def change - create_table :issues do |t| - t.string :title - t.text :content - t.integer :assignee_id - t.integer :author_id - t.integer :project_id - - t.timestamps - end - end -end diff --git a/db/migrate/20110922110156_add_code_to_project.rb b/db/migrate/20110922110156_add_code_to_project.rb deleted file mode 100644 index f54a02bd70a61234a00707a36fdbb2eb636a512d..0000000000000000000000000000000000000000 --- a/db/migrate/20110922110156_add_code_to_project.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddCodeToProject < ActiveRecord::Migration - def change - add_column :projects, :code, :string - end -end diff --git a/db/migrate/20110923211333_add_status_to_issue.rb b/db/migrate/20110923211333_add_status_to_issue.rb deleted file mode 100644 index c53bf46ef7296a312776f39b92358419d749dd43..0000000000000000000000000000000000000000 --- a/db/migrate/20110923211333_add_status_to_issue.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddStatusToIssue < ActiveRecord::Migration - def change - add_column :issues, :closed, :boolean, :default => false, :null => false - end -end diff --git a/db/migrate/20110924214549_create_rails_admin_histories_table.rb b/db/migrate/20110924214549_create_rails_admin_histories_table.rb deleted file mode 100644 index 3c743aa2874433b051f8d8f3ea83dd1935bea492..0000000000000000000000000000000000000000 --- a/db/migrate/20110924214549_create_rails_admin_histories_table.rb +++ /dev/null @@ -1,18 +0,0 @@ -class CreateRailsAdminHistoriesTable < ActiveRecord::Migration - def self.up - create_table :rails_admin_histories do |t| - t.text :message # title, name, or object_id - t.string :username - t.integer :item - t.string :table - t.integer :month, :limit => 2 - t.integer :year, :limit => 5 - t.timestamps - end - add_index(:rails_admin_histories, [:item, :table, :month, :year], :name => 'index_rails_admin_histories' ) - end - - def self.down - drop_table :rails_admin_histories - end -end diff --git a/db/migrate/20110924215658_add_admin_field_to_user.rb b/db/migrate/20110924215658_add_admin_field_to_user.rb deleted file mode 100644 index 321587e6957626d8bef79658c1fef358fd0d1d38..0000000000000000000000000000000000000000 --- a/db/migrate/20110924215658_add_admin_field_to_user.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddAdminFieldToUser < ActiveRecord::Migration - def change - add_column :users, :admin, :boolean, :default => false, :null => false - end -end diff --git a/db/migrate/20110926082616_remove_admin.rb b/db/migrate/20110926082616_remove_admin.rb deleted file mode 100644 index aac9ff7888ce7b27e7caacff2560c199a504788c..0000000000000000000000000000000000000000 --- a/db/migrate/20110926082616_remove_admin.rb +++ /dev/null @@ -1,9 +0,0 @@ -class RemoveAdmin < ActiveRecord::Migration - def up - drop_table :rails_admin_histories - end - - def down - raise "No rollback" - end -end diff --git a/db/migrate/20110927130352_create_notes.rb b/db/migrate/20110927130352_create_notes.rb deleted file mode 100644 index 72a0e817617065ad35d2fd9a806a6ec5468cba7e..0000000000000000000000000000000000000000 --- a/db/migrate/20110927130352_create_notes.rb +++ /dev/null @@ -1,12 +0,0 @@ -class CreateNotes < ActiveRecord::Migration - def change - create_table :notes do |t| - t.string :note - t.integer :noteable_id - t.string :noteable_type - t.integer :author_id - - t.timestamps - end - end -end diff --git a/db/migrate/20110928140106_add_project_id_for_note.rb b/db/migrate/20110928140106_add_project_id_for_note.rb deleted file mode 100644 index 3e641089799e2fcc69d90cd764de8b31170ae085..0000000000000000000000000000000000000000 --- a/db/migrate/20110928140106_add_project_id_for_note.rb +++ /dev/null @@ -1,9 +0,0 @@ -class AddProjectIdForNote < ActiveRecord::Migration - def up - add_column :notes, :project_id, :integer - end - - def down - remove_column :notes, :project_id, :integer - end -end diff --git a/db/migrate/20110928142747_change_noteable_id_for_note.rb b/db/migrate/20110928142747_change_noteable_id_for_note.rb deleted file mode 100644 index dc9d1f0171a229a3baf16cc4b219a8c050f58925..0000000000000000000000000000000000000000 --- a/db/migrate/20110928142747_change_noteable_id_for_note.rb +++ /dev/null @@ -1,9 +0,0 @@ -class ChangeNoteableIdForNote < ActiveRecord::Migration - def up - change_column :notes, :noteable_id, :string - end - - def down - change_column :notes, :noteable_id, :integer - end -end diff --git a/db/migrate/20110928161328_add_attachment_to_note.rb b/db/migrate/20110928161328_add_attachment_to_note.rb deleted file mode 100644 index 37d9cf10258f8abeb2efbb25fd384cb786ced669..0000000000000000000000000000000000000000 --- a/db/migrate/20110928161328_add_attachment_to_note.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddAttachmentToNote < ActiveRecord::Migration - def change - add_column :notes, :attachment, :string - end -end diff --git a/db/migrate/20111005193700_add_allow_repo_creation_for_user.rb b/db/migrate/20111005193700_add_allow_repo_creation_for_user.rb deleted file mode 100644 index 82bd94b80897358a8d7e57704397bd3f87f593ef..0000000000000000000000000000000000000000 --- a/db/migrate/20111005193700_add_allow_repo_creation_for_user.rb +++ /dev/null @@ -1,9 +0,0 @@ -class AddAllowRepoCreationForUser < ActiveRecord::Migration - def up - add_column :users, :allowed_create_repo, :boolean, :default => true, :null => false - end - - def down - remove_column :users, :allowed_create_repo - end -end diff --git a/db/migrate/20111009101738_add_ownerto_project.rb b/db/migrate/20111009101738_add_ownerto_project.rb deleted file mode 100644 index 8d265533add849bd479a0f044da7e7e2d6310171..0000000000000000000000000000000000000000 --- a/db/migrate/20111009101738_add_ownerto_project.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddOwnertoProject < ActiveRecord::Migration - def change - add_column :projects, :owner_id, :integer - end -end diff --git a/db/migrate/20111009110913_add_projects_limit_to_user.rb b/db/migrate/20111009110913_add_projects_limit_to_user.rb deleted file mode 100644 index 8eb8382b51558225eda7b3914658684d76330a1b..0000000000000000000000000000000000000000 --- a/db/migrate/20111009110913_add_projects_limit_to_user.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddProjectsLimitToUser < ActiveRecord::Migration - def change - add_column :users, :projects_limit, :integer, :default => 10 - end -end diff --git a/db/migrate/20111009111204_remove_allow_create_repo_from_user.rb b/db/migrate/20111009111204_remove_allow_create_repo_from_user.rb deleted file mode 100644 index 614520063970cde301736802952cf77e8ac6752a..0000000000000000000000000000000000000000 --- a/db/migrate/20111009111204_remove_allow_create_repo_from_user.rb +++ /dev/null @@ -1,9 +0,0 @@ -class RemoveAllowCreateRepoFromUser < ActiveRecord::Migration - def up - remove_column :users, :allowed_create_repo - end - - def down - add_column :users, :allowed_create_repo, :boolean, :default => true, :null => false - end -end diff --git a/db/migrate/20111015154310_add_position_to_issues.rb b/db/migrate/20111015154310_add_position_to_issues.rb deleted file mode 100644 index 41451a0cabb971d6d43f9cf35ca02abf85e58508..0000000000000000000000000000000000000000 --- a/db/migrate/20111015154310_add_position_to_issues.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddPositionToIssues < ActiveRecord::Migration - def change - add_column :issues, :position, :integer, :default => 0 - end -end diff --git a/db/migrate/20111016183422_create_snippets.rb b/db/migrate/20111016183422_create_snippets.rb deleted file mode 100644 index 9b0bf201cbaf4ae8dde65b5f8ceba62e718990a2..0000000000000000000000000000000000000000 --- a/db/migrate/20111016183422_create_snippets.rb +++ /dev/null @@ -1,12 +0,0 @@ -class CreateSnippets < ActiveRecord::Migration - def change - create_table :snippets do |t| - t.string :title - t.text :content - t.integer :author_id, :null => false - t.integer :project_id, :null => false - - t.timestamps - end - end -end diff --git a/db/migrate/20111016193417_add_content_type_to_snippets.rb b/db/migrate/20111016193417_add_content_type_to_snippets.rb deleted file mode 100644 index 511a6793f4ff978ca874f0ada79a250b8b67212d..0000000000000000000000000000000000000000 --- a/db/migrate/20111016193417_add_content_type_to_snippets.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddContentTypeToSnippets < ActiveRecord::Migration - def change - add_column :snippets, :content_type, :string, :null => false, :default => "txt" - end -end diff --git a/db/migrate/20111016195506_add_file_name_to_snippets.rb b/db/migrate/20111016195506_add_file_name_to_snippets.rb deleted file mode 100644 index d378d225ec1b580b2a0fe964d8a704642a8f1d17..0000000000000000000000000000000000000000 --- a/db/migrate/20111016195506_add_file_name_to_snippets.rb +++ /dev/null @@ -1,6 +0,0 @@ -class AddFileNameToSnippets < ActiveRecord::Migration - def change - add_column :snippets, :file_name, :string - remove_column :snippets, :content_type - end -end diff --git a/db/migrate/20111019212429_add_social_to_user.rb b/db/migrate/20111019212429_add_social_to_user.rb deleted file mode 100644 index b0ffe5366a40333b1b72698a6d36f0f5459e5bf4..0000000000000000000000000000000000000000 --- a/db/migrate/20111019212429_add_social_to_user.rb +++ /dev/null @@ -1,7 +0,0 @@ -class AddSocialToUser < ActiveRecord::Migration - def change - add_column :users, :skype, :string - add_column :users, :linkedin, :string - add_column :users, :twitter, :string - end -end diff --git a/db/migrate/20111021101550_change_social_fields_in_users.rb b/db/migrate/20111021101550_change_social_fields_in_users.rb deleted file mode 100644 index 6e506c1cf9d3c7488d22e432e3dddf55e893af22..0000000000000000000000000000000000000000 --- a/db/migrate/20111021101550_change_social_fields_in_users.rb +++ /dev/null @@ -1,14 +0,0 @@ -class ChangeSocialFieldsInUsers < ActiveRecord::Migration - def up - remove_column :users, :skype - remove_column :users, :linkedin - remove_column :users, :twitter - - add_column :users, :skype, :string, {:null => false, :default => ''} - add_column :users, :linkedin, :string, {:null => false, :default => ''} - add_column :users, :twitter, :string, {:null => false, :default => ''} - end - - def down - end -end diff --git a/db/migrate/20111025134235_add_high_label_to_issue.rb b/db/migrate/20111025134235_add_high_label_to_issue.rb deleted file mode 100644 index 676eaaf8c1ba1de5dfa273dc3229da7fae5d2152..0000000000000000000000000000000000000000 --- a/db/migrate/20111025134235_add_high_label_to_issue.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddHighLabelToIssue < ActiveRecord::Migration - def change - add_column :issues, :critical, :boolean, :default => false, :null => false - end -end diff --git a/db/migrate/20111027051828_add_expires_at_to_snippets.rb b/db/migrate/20111027051828_add_expires_at_to_snippets.rb deleted file mode 100644 index 0d94b33376b2c0de4a99080f40535825581478f5..0000000000000000000000000000000000000000 --- a/db/migrate/20111027051828_add_expires_at_to_snippets.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddExpiresAtToSnippets < ActiveRecord::Migration - def change - add_column :snippets, :expires_at, :datetime - end -end diff --git a/db/migrate/20111027142641_change_note_note_to_text.rb b/db/migrate/20111027142641_change_note_note_to_text.rb deleted file mode 100644 index d762d361aecf81462249bd466846330994127c74..0000000000000000000000000000000000000000 --- a/db/migrate/20111027142641_change_note_note_to_text.rb +++ /dev/null @@ -1,8 +0,0 @@ -class ChangeNoteNoteToText < ActiveRecord::Migration - def up - change_column :notes, :note, :text - end - - def down - end -end diff --git a/db/migrate/20111027152724_issue_conten_to_note.rb b/db/migrate/20111027152724_issue_conten_to_note.rb deleted file mode 100644 index 0677fee6b9736b9a09c9947b21436c755db08e5a..0000000000000000000000000000000000000000 --- a/db/migrate/20111027152724_issue_conten_to_note.rb +++ /dev/null @@ -1,34 +0,0 @@ -class IssueContenToNote < ActiveRecord::Migration - def up - puts "Issue content is deprecated -> move to notes" - Issue.find_each(:batch_size => 100) do |issue| - next if issue.content.blank? - note = Note.new( - :note => issue.content, - :project_id => issue.project_id, - :noteable => issue, - :created_at => issue.created_at, - :updated_at => issue.created_at - ) - note.author_id = issue.author_id - - if note.save - issue.update_attributes(:content => nil) - print "." - else - print "F" - end - end - - total = Issue.where("content is not null").count - - if total > 0 - puts "content of #{total} issues were not migrated" - else - puts "Done" - end - end - - def down - end -end diff --git a/db/migrate/20111101222453_acts_as_taggable_on_migration.rb b/db/migrate/20111101222453_acts_as_taggable_on_migration.rb deleted file mode 100644 index 16610615f9d6b9bc5ed5b5c78375391f7eb448a8..0000000000000000000000000000000000000000 --- a/db/migrate/20111101222453_acts_as_taggable_on_migration.rb +++ /dev/null @@ -1,28 +0,0 @@ -class ActsAsTaggableOnMigration < ActiveRecord::Migration - def self.up - create_table :tags do |t| - t.string :name - end - - create_table :taggings do |t| - t.references :tag - - # You should make sure that the column created is - # long enough to store the required class names. - t.references :taggable, :polymorphic => true - t.references :tagger, :polymorphic => true - - t.string :context - - t.datetime :created_at - end - - add_index :taggings, :tag_id - add_index :taggings, [:taggable_id, :taggable_type, :context] - end - - def self.down - drop_table :taggings - drop_table :tags - end -end diff --git a/db/migrate/20111111093150_remove_content_from_issues.rb b/db/migrate/20111111093150_remove_content_from_issues.rb deleted file mode 100644 index 30bcdfb543ba15a72d6537722064848e70929ddf..0000000000000000000000000000000000000000 --- a/db/migrate/20111111093150_remove_content_from_issues.rb +++ /dev/null @@ -1,9 +0,0 @@ -class RemoveContentFromIssues < ActiveRecord::Migration - def up - remove_column :issues, :content - end - - def down - add_column :issues, :content, :text - end -end diff --git a/db/migrate/20111115063954_add_authentication_token_to_users.rb b/db/migrate/20111115063954_add_authentication_token_to_users.rb deleted file mode 100644 index 84433656d6c8d9b058a070f26cd6800a0f5227e7..0000000000000000000000000000000000000000 --- a/db/migrate/20111115063954_add_authentication_token_to_users.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddAuthenticationTokenToUsers < ActiveRecord::Migration - def change - add_column :users, :authentication_token, :string - end -end diff --git a/db/migrate/20111124115339_add_extra_field_to_issue.rb b/db/migrate/20111124115339_add_extra_field_to_issue.rb deleted file mode 100644 index 4946c6fb0bcddbab32c84151719945cbeb4a9dcc..0000000000000000000000000000000000000000 --- a/db/migrate/20111124115339_add_extra_field_to_issue.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddExtraFieldToIssue < ActiveRecord::Migration - def change - add_column :issues, :branch_name, :string, :null => true - end -end diff --git a/db/migrate/20111127155345_create_merge_requests.rb b/db/migrate/20111127155345_create_merge_requests.rb deleted file mode 100644 index 1555ae84041cd9f7f69803e30d9e437fc74cb455..0000000000000000000000000000000000000000 --- a/db/migrate/20111127155345_create_merge_requests.rb +++ /dev/null @@ -1,15 +0,0 @@ -class CreateMergeRequests < ActiveRecord::Migration - def change - create_table :merge_requests do |t| - t.string :target_branch, :null => false - t.string :source_branch, :null => false - t.integer :project_id, :null => false - t.integer :author_id - t.integer :assignee_id - t.string :title - t.boolean :closed, :default => false, :null => false - - t.timestamps - end - end -end diff --git a/db/migrate/20111206213842_add_advanced_rights_to_team_member.rb b/db/migrate/20111206213842_add_advanced_rights_to_team_member.rb deleted file mode 100644 index e2695fdafa75e72e7e97a44773300983e9245660..0000000000000000000000000000000000000000 --- a/db/migrate/20111206213842_add_advanced_rights_to_team_member.rb +++ /dev/null @@ -1,6 +0,0 @@ -class AddAdvancedRightsToTeamMember < ActiveRecord::Migration - def change - add_column :users_projects, :repo_access, :integer, :default => 0, :null => false - add_column :users_projects, :project_access, :integer, :default => 0, :null => false - end -end diff --git a/db/migrate/20111206222316_migrate_to_new_rights.rb b/db/migrate/20111206222316_migrate_to_new_rights.rb deleted file mode 100644 index 22e0c1ce994731e9cfa0058295b7f3a338408d0b..0000000000000000000000000000000000000000 --- a/db/migrate/20111206222316_migrate_to_new_rights.rb +++ /dev/null @@ -1,20 +0,0 @@ -class MigrateToNewRights < ActiveRecord::Migration - def up - # Repository access - UsersProject.update_all("repo_access = 2", :write => true) - UsersProject.update_all("repo_access = 1", :read => true, :write => false) - - # Project access - UsersProject.update_all("project_access = 1", :read => true, :write => false, :admin => false) - UsersProject.update_all("project_access = 2", :read => true, :write => true, :admin => false) - UsersProject.update_all("project_access = 3", :read => true, :write => true, :admin => true) - - # Remove old fields - remove_column :users_projects, :read - remove_column :users_projects, :write - remove_column :users_projects, :admin - end - - def down - end -end diff --git a/db/migrate/20111207211728_add_default_branch_to_project.rb b/db/migrate/20111207211728_add_default_branch_to_project.rb deleted file mode 100644 index 5c2107f7aa57ae5f8d7fb236784f54e77aeb1538..0000000000000000000000000000000000000000 --- a/db/migrate/20111207211728_add_default_branch_to_project.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddDefaultBranchToProject < ActiveRecord::Migration - def change - add_column :projects, :default_branch, :string, :null => false, :default => "master" - end -end diff --git a/db/migrate/20111214091851_create_web_hooks.rb b/db/migrate/20111214091851_create_web_hooks.rb deleted file mode 100644 index c6ba89c10e1da9214be3ac38ab6f84afb7f88fc3..0000000000000000000000000000000000000000 --- a/db/migrate/20111214091851_create_web_hooks.rb +++ /dev/null @@ -1,9 +0,0 @@ -class CreateWebHooks < ActiveRecord::Migration - def change - create_table :web_hooks do |t| - t.string :url - t.integer :project_id - t.timestamps - end - end -end diff --git a/db/migrate/20111220190817_add_coloscheme_option_to_user.rb b/db/migrate/20111220190817_add_coloscheme_option_to_user.rb deleted file mode 100644 index fe06c3aa34a6307b96b7e2830ab64b3f6fc48529..0000000000000000000000000000000000000000 --- a/db/migrate/20111220190817_add_coloscheme_option_to_user.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddColoschemeOptionToUser < ActiveRecord::Migration - def change - add_column :users, :dark_scheme, :boolean, :default => false, :null => false - end -end diff --git a/db/migrate/20111231111825_add_project_id_to_key.rb b/db/migrate/20111231111825_add_project_id_to_key.rb deleted file mode 100644 index dc80cbdb71ff5a6c09e8dfeac9fb517c103cdb72..0000000000000000000000000000000000000000 --- a/db/migrate/20111231111825_add_project_id_to_key.rb +++ /dev/null @@ -1,6 +0,0 @@ -class AddProjectIdToKey < ActiveRecord::Migration - def change - add_column :keys, :project_id, :integer, :null => true - change_column :keys, :user_id, :integer, :null => true - end -end diff --git a/db/migrate/20120110180749_add_line_number_to_note.rb b/db/migrate/20120110180749_add_line_number_to_note.rb deleted file mode 100644 index 9bdcce3c47df4308efbd2784a1616db43513d35a..0000000000000000000000000000000000000000 --- a/db/migrate/20120110180749_add_line_number_to_note.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddLineNumberToNote < ActiveRecord::Migration - def change - add_column :notes, :line_code, :string, :null => true - end -end diff --git a/db/migrate/20120119202326_add_indexes.rb b/db/migrate/20120119202326_add_indexes.rb deleted file mode 100644 index ee07176a26752ee0f018ebb8f14d039551362a08..0000000000000000000000000000000000000000 --- a/db/migrate/20120119202326_add_indexes.rb +++ /dev/null @@ -1,9 +0,0 @@ -class AddIndexes < ActiveRecord::Migration - def change - add_index :issues, :project_id - add_index :merge_requests, :project_id - add_index :notes, :noteable_id - add_index :notes, :noteable_type - end - -end diff --git a/db/migrate/20120121122616_fix_noteable_id.rb b/db/migrate/20120121122616_fix_noteable_id.rb deleted file mode 100644 index d110e8cd63b7d80583c4a87930cea18861c41603..0000000000000000000000000000000000000000 --- a/db/migrate/20120121122616_fix_noteable_id.rb +++ /dev/null @@ -1,8 +0,0 @@ -class FixNoteableId < ActiveRecord::Migration - def up - change_column :notes, :noteable_id, :string, :limit => 255 - end - - def down - end -end diff --git a/db/migrate/20120206170141_add_modularity_fields_to_project.rb b/db/migrate/20120206170141_add_modularity_fields_to_project.rb deleted file mode 100644 index d63de0c2be3578fb83bc87485c9115321e968d4a..0000000000000000000000000000000000000000 --- a/db/migrate/20120206170141_add_modularity_fields_to_project.rb +++ /dev/null @@ -1,7 +0,0 @@ -class AddModularityFieldsToProject < ActiveRecord::Migration - def change - add_column :projects, :issues_enabled, :boolean, :null => false, :default => true - add_column :projects, :wall_enabled, :boolean, :null => false, :default => true - add_column :projects, :merge_requests_enabled, :boolean, :null => false, :default => true - end -end diff --git a/db/migrate/20120215182305_create_protected_branches.rb b/db/migrate/20120215182305_create_protected_branches.rb deleted file mode 100644 index 841d08c33d5f135b0463541909a85e3d11e162e3..0000000000000000000000000000000000000000 --- a/db/migrate/20120215182305_create_protected_branches.rb +++ /dev/null @@ -1,10 +0,0 @@ -class CreateProtectedBranches < ActiveRecord::Migration - def change - create_table :protected_branches do |t| - t.integer :project_id, :null => false - t.string :name, :null => false - - t.timestamps - end - end -end diff --git a/db/migrate/20120216085842_move_to_roles_permissions.rb b/db/migrate/20120216085842_move_to_roles_permissions.rb deleted file mode 100644 index 36d02cf972a7381fadab3d4f891c3778850dc26d..0000000000000000000000000000000000000000 --- a/db/migrate/20120216085842_move_to_roles_permissions.rb +++ /dev/null @@ -1,22 +0,0 @@ -class MoveToRolesPermissions < ActiveRecord::Migration - def up - repo_n = 0 - repo_r = 1 - repo_rw = 2 - project_rwa = 3 - - - # Build masters and reset repo_access - UsersProject.update_all({:project_access => UsersProject::MASTER, :repo_access => 99 }, ["project_access = ?", project_rwa]) - - # Build other roles based on repo access - UsersProject.update_all ["project_access = ?", UsersProject::DEVELOPER], ["repo_access = ?", repo_rw] - UsersProject.update_all ["project_access = ?", UsersProject::REPORTER], ["repo_access = ?", repo_r] - UsersProject.update_all ["project_access = ?", UsersProject::GUEST], ["repo_access = ?", repo_n] - - remove_column :users_projects, :repo_access - end - - def down - end -end diff --git a/db/migrate/20120216215008_create_wikis.rb b/db/migrate/20120216215008_create_wikis.rb deleted file mode 100644 index 38947df389cca5bcbc0bb8e21376fe46428b6c10..0000000000000000000000000000000000000000 --- a/db/migrate/20120216215008_create_wikis.rb +++ /dev/null @@ -1,11 +0,0 @@ -class CreateWikis < ActiveRecord::Migration - def change - create_table :wikis do |t| - t.string :title - t.text :content - t.integer :project_id - - t.timestamps - end - end -end diff --git a/db/migrate/20120219130957_add_slug_to_wiki.rb b/db/migrate/20120219130957_add_slug_to_wiki.rb deleted file mode 100644 index 5f2d5970a89053a2ed49e66b30806ff7db1f6226..0000000000000000000000000000000000000000 --- a/db/migrate/20120219130957_add_slug_to_wiki.rb +++ /dev/null @@ -1,6 +0,0 @@ -class AddSlugToWiki < ActiveRecord::Migration - def change - add_column :wikis, :slug, :string - - end -end diff --git a/db/migrate/20120219140810_add_wiki_enabled_to_project.rb b/db/migrate/20120219140810_add_wiki_enabled_to_project.rb deleted file mode 100644 index ebd71bea84d26a6b5485c8e0c7de4a0d3a327008..0000000000000000000000000000000000000000 --- a/db/migrate/20120219140810_add_wiki_enabled_to_project.rb +++ /dev/null @@ -1,6 +0,0 @@ -class AddWikiEnabledToProject < ActiveRecord::Migration - def change - add_column :projects, :wiki_enabled, :boolean, :default => true, :null => false - - end -end diff --git a/db/migrate/20120219193300_add_user_to_wiki.rb b/db/migrate/20120219193300_add_user_to_wiki.rb deleted file mode 100644 index 8a6c0a06ef00d3b870805f78524219b8576538aa..0000000000000000000000000000000000000000 --- a/db/migrate/20120219193300_add_user_to_wiki.rb +++ /dev/null @@ -1,6 +0,0 @@ -class AddUserToWiki < ActiveRecord::Migration - def change - add_column :wikis, :user_id, :integer - - end -end diff --git a/db/migrate/20120228130210_create_events.rb b/db/migrate/20120228130210_create_events.rb deleted file mode 100644 index c01f557aaaa28838ce5654581db77ead22632807..0000000000000000000000000000000000000000 --- a/db/migrate/20120228130210_create_events.rb +++ /dev/null @@ -1,14 +0,0 @@ -class CreateEvents < ActiveRecord::Migration - def change - create_table :events do |t| - t.string :target_type, :null => true - t.integer :target_id, :null => true - - t.string :title, :null => true - t.text :data, :null => true - t.integer :project_id, :null => true - - t.timestamps - end - end -end diff --git a/db/migrate/20120228134252_add_action_to_event.rb b/db/migrate/20120228134252_add_action_to_event.rb deleted file mode 100644 index aade3d90a8012fcc4d2ebee2364438ba610f05a9..0000000000000000000000000000000000000000 --- a/db/migrate/20120228134252_add_action_to_event.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddActionToEvent < ActiveRecord::Migration - def change - add_column :events, :action, :integer, :null => true - end -end diff --git a/db/migrate/20120301185805_add_theme_to_user.rb b/db/migrate/20120301185805_add_theme_to_user.rb deleted file mode 100644 index 7c2e55692b0bfe555e968d95d882ef0ddf00352f..0000000000000000000000000000000000000000 --- a/db/migrate/20120301185805_add_theme_to_user.rb +++ /dev/null @@ -1,6 +0,0 @@ -class AddThemeToUser < ActiveRecord::Migration - def change - add_column :users, :theme_id, :integer, :null => false, :default => 1 - - end -end diff --git a/db/migrate/20120307095918_add_author_id_to_event.rb b/db/migrate/20120307095918_add_author_id_to_event.rb deleted file mode 100644 index 1d24d41e424c550abd934e90d584fa4704fcd509..0000000000000000000000000000000000000000 --- a/db/migrate/20120307095918_add_author_id_to_event.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddAuthorIdToEvent < ActiveRecord::Migration - def change - add_column :events, :author_id, :integer, :null => true - end -end diff --git a/db/migrate/20120315111711_add_commits_diff_store_to_merge_request.rb b/db/migrate/20120315111711_add_commits_diff_store_to_merge_request.rb deleted file mode 100644 index 2dc1dfb4b6ea23dde2bc2d95bdbfc9209d9b1285..0000000000000000000000000000000000000000 --- a/db/migrate/20120315111711_add_commits_diff_store_to_merge_request.rb +++ /dev/null @@ -1,6 +0,0 @@ -class AddCommitsDiffStoreToMergeRequest < ActiveRecord::Migration - def change - add_column :merge_requests, :st_commits, :text, :null => true - add_column :merge_requests, :st_diffs, :text, :null => true - end -end diff --git a/db/migrate/20120315132931_add_merged_to_merge_request.rb b/db/migrate/20120315132931_add_merged_to_merge_request.rb deleted file mode 100644 index 2deb59e4420763acdd535d95d8f7501b9f526286..0000000000000000000000000000000000000000 --- a/db/migrate/20120315132931_add_merged_to_merge_request.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddMergedToMergeRequest < ActiveRecord::Migration - def change - add_column :merge_requests, :merged, :boolean, :null => false, :default => false - end -end diff --git a/db/migrate/20120317095543_add_description_to_issues.rb b/db/migrate/20120317095543_add_description_to_issues.rb deleted file mode 100644 index e47e3f8e898cc642ce71bc3d34bad2a387e4d270..0000000000000000000000000000000000000000 --- a/db/migrate/20120317095543_add_description_to_issues.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddDescriptionToIssues < ActiveRecord::Migration - def change - add_column :issues, :description, :text - end -end diff --git a/db/migrate/20120323221339_add_bio_field_to_user.rb b/db/migrate/20120323221339_add_bio_field_to_user.rb deleted file mode 100644 index 80a4dec5971c1718180758f3a566e8f14ced04a8..0000000000000000000000000000000000000000 --- a/db/migrate/20120323221339_add_bio_field_to_user.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddBioFieldToUser < ActiveRecord::Migration - def change - add_column :users, :bio, :string, :null => true - end -end diff --git a/db/migrate/20120329170745_add_automerge_to_merge_request.rb b/db/migrate/20120329170745_add_automerge_to_merge_request.rb deleted file mode 100644 index de7c68ee1cbd868ca54b7da0217ab9681da4c13b..0000000000000000000000000000000000000000 --- a/db/migrate/20120329170745_add_automerge_to_merge_request.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddAutomergeToMergeRequest < ActiveRecord::Migration - def change - add_column :merge_requests, :state, :integer, :null => false, :default => 1 - end -end diff --git a/db/migrate/20120405211750_increase_mr_text_column_size.rb b/db/migrate/20120405211750_increase_mr_text_column_size.rb deleted file mode 100644 index 4fbef622da4db7f6a69300d7c193470c7363dcfd..0000000000000000000000000000000000000000 --- a/db/migrate/20120405211750_increase_mr_text_column_size.rb +++ /dev/null @@ -1,10 +0,0 @@ -class IncreaseMrTextColumnSize < ActiveRecord::Migration - def up - # MYSQL LARGETEXT for merge request - change_column :merge_requests, :st_diffs, :text, :limit => 4294967295 - change_column :merge_requests, :st_commits, :text, :limit => 4294967295 - end - - def down - end -end diff --git a/db/migrate/20120408180246_create_milestones.rb b/db/migrate/20120408180246_create_milestones.rb deleted file mode 100644 index ed3a510d660b4225af1bfd5e1a0427ae5b665725..0000000000000000000000000000000000000000 --- a/db/migrate/20120408180246_create_milestones.rb +++ /dev/null @@ -1,12 +0,0 @@ -class CreateMilestones < ActiveRecord::Migration - def change - create_table :milestones do |t| - t.string :title, :null => false - t.integer :project_id, :null => false - t.text :description - t.date :due_date - t.boolean :closed, :default => false, :null => false - t.timestamps - end - end -end diff --git a/db/migrate/20120408181910_add_milestone_id_to_issue.rb b/db/migrate/20120408181910_add_milestone_id_to_issue.rb deleted file mode 100644 index a6b44090c1c7433ca8bd84ca11a9a60dfb9c5d85..0000000000000000000000000000000000000000 --- a/db/migrate/20120408181910_add_milestone_id_to_issue.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddMilestoneIdToIssue < ActiveRecord::Migration - def change - add_column :issues, :milestone_id, :integer, :null => true - end -end diff --git a/db/migrate/20120413135904_add_blocked_field_to_user.rb b/db/migrate/20120413135904_add_blocked_field_to_user.rb deleted file mode 100644 index 050450bf034d2c855cafb31ce253703c7e726d91..0000000000000000000000000000000000000000 --- a/db/migrate/20120413135904_add_blocked_field_to_user.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddBlockedFieldToUser < ActiveRecord::Migration - def change - add_column :users, :blocked, :boolean, :null => false, :default => false - end -end diff --git a/db/migrate/20120627145613_remove_critical_from_issue.rb b/db/migrate/20120627145613_remove_critical_from_issue.rb deleted file mode 100644 index f8d0797197b745c12eb1ab2dbedc3e120d6b4de3..0000000000000000000000000000000000000000 --- a/db/migrate/20120627145613_remove_critical_from_issue.rb +++ /dev/null @@ -1,9 +0,0 @@ -class RemoveCriticalFromIssue < ActiveRecord::Migration - def up - remove_column :issues, :critical - end - - def down - add_column :issues, :critical, :boolean, :null => true, :default => false - end -end diff --git a/db/migrate/20120706065612_add_lockable_to_users.rb b/db/migrate/20120706065612_add_lockable_to_users.rb deleted file mode 100644 index cf86e66087624b1d8fcf75dc01c0519bbbdc1783..0000000000000000000000000000000000000000 --- a/db/migrate/20120706065612_add_lockable_to_users.rb +++ /dev/null @@ -1,6 +0,0 @@ -class AddLockableToUsers < ActiveRecord::Migration - def change - add_column :users, :failed_attempts, :integer, :default => 0 - add_column :users, :locked_at, :datetime - end -end diff --git a/db/migrate/20120712080407_add_type_to_web_hook.rb b/db/migrate/20120712080407_add_type_to_web_hook.rb deleted file mode 100644 index 18ab024c817d438a9354cc7436eac77ee89fd176..0000000000000000000000000000000000000000 --- a/db/migrate/20120712080407_add_type_to_web_hook.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddTypeToWebHook < ActiveRecord::Migration - def change - add_column :web_hooks, :type, :string, :default => "ProjectHook" - end -end diff --git a/db/migrate/20120729131232_add_extern_auth_provider_to_users.rb b/db/migrate/20120729131232_add_extern_auth_provider_to_users.rb deleted file mode 100644 index d5e66ba4d3bc83d5c7d8d9f92dd1983b4e0e6dde..0000000000000000000000000000000000000000 --- a/db/migrate/20120729131232_add_extern_auth_provider_to_users.rb +++ /dev/null @@ -1,8 +0,0 @@ -class AddExternAuthProviderToUsers < ActiveRecord::Migration - def change - add_column :users, :extern_uid, :string - add_column :users, :provider, :string - - add_index :users, [:extern_uid, :provider], :unique => true - end -end diff --git a/db/migrate/20120905043334_set_default_branch_default_to_nil.rb b/db/migrate/20120905043334_set_default_branch_default_to_nil.rb deleted file mode 100644 index f5956fe875184473481fe8673e57a9bab967c95e..0000000000000000000000000000000000000000 --- a/db/migrate/20120905043334_set_default_branch_default_to_nil.rb +++ /dev/null @@ -1,12 +0,0 @@ -class SetDefaultBranchDefaultToNil < ActiveRecord::Migration - def up - # Set the default_branch to allow nil, and default it to nil - change_column_null(:projects, :default_branch, true) - change_column_default(:projects, :default_branch, nil) - end - - def down - change_column_null(:projects, :default_branch, false) - change_column_default(:projects, :default_branch, 'master') - end -end diff --git a/db/migrate/20121002150926_create_groups.rb b/db/migrate/20121002150926_create_groups.rb deleted file mode 100644 index ac178294c0279c2bc106f5cacb587e06b2a89acb..0000000000000000000000000000000000000000 --- a/db/migrate/20121002150926_create_groups.rb +++ /dev/null @@ -1,11 +0,0 @@ -class CreateGroups < ActiveRecord::Migration - def change - create_table :groups do |t| - t.string :name, null: false - t.string :code, null: false - t.integer :owner_id, null: false - - t.timestamps - end - end -end diff --git a/db/migrate/20121002151033_add_group_id_to_project.rb b/db/migrate/20121002151033_add_group_id_to_project.rb deleted file mode 100644 index 683fbfec5137edf7ff84e38c0b0aee315d19dcb7..0000000000000000000000000000000000000000 --- a/db/migrate/20121002151033_add_group_id_to_project.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddGroupIdToProject < ActiveRecord::Migration - def change - add_column :projects, :group_id, :integer - end -end diff --git a/db/migrate/20121009205010_postgres_create_integer_cast.rb b/db/migrate/20121009205010_postgres_create_integer_cast.rb deleted file mode 100644 index b9a971387d1c8e5d33194af38d924e7dc0552265..0000000000000000000000000000000000000000 --- a/db/migrate/20121009205010_postgres_create_integer_cast.rb +++ /dev/null @@ -1,15 +0,0 @@ -class PostgresCreateIntegerCast < ActiveRecord::Migration - def up - execute <<-SQL - CREATE CAST (integer AS text) WITH INOUT AS IMPLICIT; - SQL - rescue ActiveRecord::StatementInvalid - end - - def down - execute <<-SQL - DROP CAST (integer AS text); - SQL - rescue ActiveRecord::StatementInvalid - end -end diff --git a/db/migrate/20121026114600_add_milestone_id_to_merge_requests.rb b/db/migrate/20121026114600_add_milestone_id_to_merge_requests.rb deleted file mode 100644 index b516772434870c85dc76134146188afe4d0a0f8c..0000000000000000000000000000000000000000 --- a/db/migrate/20121026114600_add_milestone_id_to_merge_requests.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddMilestoneIdToMergeRequests < ActiveRecord::Migration - def change - add_column :merge_requests, :milestone_id, :integer, :null => true - end -end diff --git a/db/migrate/20121119170638_create_services.rb b/db/migrate/20121119170638_create_services.rb deleted file mode 100644 index f7cac1445a78adba9728fe0946bb51799f54b804..0000000000000000000000000000000000000000 --- a/db/migrate/20121119170638_create_services.rb +++ /dev/null @@ -1,12 +0,0 @@ -class CreateServices < ActiveRecord::Migration - def change - create_table :services do |t| - t.string :type - t.string :title - t.string :token - t.integer :project_id, null: false - - t.timestamps - end - end -end diff --git a/db/migrate/20121120051432_add_service_id_to_web_hook.rb b/db/migrate/20121120051432_add_service_id_to_web_hook.rb deleted file mode 100644 index 77adf92578ff0829e85eba7a46ef169330aa1940..0000000000000000000000000000000000000000 --- a/db/migrate/20121120051432_add_service_id_to_web_hook.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddServiceIdToWebHook < ActiveRecord::Migration - def change - add_column :web_hooks, :service_id, :integer, null: true - end -end diff --git a/db/migrate/20121120103700_add_active_to_service.rb b/db/migrate/20121120103700_add_active_to_service.rb deleted file mode 100644 index f45ef52e6f06b59a188af02de0099e01c100e8e8..0000000000000000000000000000000000000000 --- a/db/migrate/20121120103700_add_active_to_service.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddActiveToService < ActiveRecord::Migration - def change - add_column :services, :active, :boolean, default: false, null: false - end -end diff --git a/db/migrate/20121120113838_add_project_url_to_service.rb b/db/migrate/20121120113838_add_project_url_to_service.rb deleted file mode 100644 index 13ffbdb192f4152898e8827e473eba7b9ac4590d..0000000000000000000000000000000000000000 --- a/db/migrate/20121120113838_add_project_url_to_service.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddProjectUrlToService < ActiveRecord::Migration - def change - add_column :services, :project_url, :string, null: true - end -end diff --git a/db/migrate/20121122145155_convert_group_to_namespace.rb b/db/migrate/20121122145155_convert_group_to_namespace.rb deleted file mode 100644 index fc8b023d814f3694329d36d06190d94706be77d5..0000000000000000000000000000000000000000 --- a/db/migrate/20121122145155_convert_group_to_namespace.rb +++ /dev/null @@ -1,13 +0,0 @@ -class ConvertGroupToNamespace < ActiveRecord::Migration - def up - rename_table 'groups', 'namespaces' - add_column :namespaces, :type, :string, null: true - - # Migrate old groups - Namespace.update_all(type: 'Group') - end - - def down - raise 'Rollback is not allowed' - end -end diff --git a/db/migrate/20121122150932_add_namespace_id_to_project.rb b/db/migrate/20121122150932_add_namespace_id_to_project.rb deleted file mode 100644 index 904f3aa32be7ba5a6edfa0201ba5cf12cd13f9f0..0000000000000000000000000000000000000000 --- a/db/migrate/20121122150932_add_namespace_id_to_project.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddNamespaceIdToProject < ActiveRecord::Migration - def change - rename_column :projects, :group_id, :namespace_id - end -end diff --git a/db/migrate/20121123104937_add_username_to_user.rb b/db/migrate/20121123104937_add_username_to_user.rb deleted file mode 100644 index 04232a119d92b6e651753df4f06aefd6aa51018f..0000000000000000000000000000000000000000 --- a/db/migrate/20121123104937_add_username_to_user.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddUsernameToUser < ActiveRecord::Migration - def change - add_column :users, :username, :string, null: true - end -end diff --git a/db/migrate/20121123164910_rename_code_to_path.rb b/db/migrate/20121123164910_rename_code_to_path.rb deleted file mode 100644 index fb10baf58cf4660fdbd18a81d9882ff142892a1b..0000000000000000000000000000000000000000 --- a/db/migrate/20121123164910_rename_code_to_path.rb +++ /dev/null @@ -1,11 +0,0 @@ -class RenameCodeToPath < ActiveRecord::Migration - def up - remove_column :projects, :code - rename_column :namespaces, :code, :path - end - - def down - add_column :projects, :code, :string - rename_column :namespaces, :path, :code - end -end diff --git a/db/migrate/20121203154450_add_events_indices.rb b/db/migrate/20121203154450_add_events_indices.rb deleted file mode 100644 index 502a2ccbcdab0bba3d23b38873d18ae7071e9be9..0000000000000000000000000000000000000000 --- a/db/migrate/20121203154450_add_events_indices.rb +++ /dev/null @@ -1,8 +0,0 @@ -class AddEventsIndices < ActiveRecord::Migration - def change - add_index :events, :project_id - add_index :events, :author_id - add_index :events, :action - add_index :events, :target_type - end -end diff --git a/db/migrate/20121203160507_more_indices.rb b/db/migrate/20121203160507_more_indices.rb deleted file mode 100644 index 52170a7c597dd8bc7c87e52d09ab5cd0909b9df1..0000000000000000000000000000000000000000 --- a/db/migrate/20121203160507_more_indices.rb +++ /dev/null @@ -1,26 +0,0 @@ -class MoreIndices < ActiveRecord::Migration - def change - add_index :notes, :project_id - add_index :namespaces, :owner_id - add_index :keys, :user_id - - add_index :projects, :namespace_id - add_index :projects, :owner_id - - add_index :services, :project_id - add_index :snippets, :project_id - - add_index :users_projects, :project_id - - # Issues - add_index :issues, :assignee_id - add_index :issues, :milestone_id - add_index :issues, :author_id - - # Merge Requests - add_index :merge_requests, :assignee_id - add_index :merge_requests, :milestone_id - add_index :merge_requests, :author_id - - end -end diff --git a/db/migrate/20121205201726_add_more_indexes.rb b/db/migrate/20121205201726_add_more_indexes.rb deleted file mode 100644 index a2b36f7fdf9a4bac17ebf5b64e1181f577a58cc0..0000000000000000000000000000000000000000 --- a/db/migrate/20121205201726_add_more_indexes.rb +++ /dev/null @@ -1,44 +0,0 @@ -class AddMoreIndexes < ActiveRecord::Migration - def change - add_index :events, :created_at - add_index :events, :target_id - - add_index :issues, :closed - add_index :issues, :created_at - add_index :issues, :title - - add_index :keys, :identifier - # FIXME: MySQL can't index text columns - #add_index :keys, :key - add_index :keys, :project_id - - add_index :merge_requests, :closed - add_index :merge_requests, :created_at - add_index :merge_requests, :source_branch - add_index :merge_requests, :target_branch - add_index :merge_requests, :title - - add_index :milestones, :due_date - add_index :milestones, :project_id - - add_index :namespaces, :name - add_index :namespaces, :path - add_index :namespaces, :type - - add_index :notes, :created_at - - add_index :snippets, :created_at - add_index :snippets, :expires_at - - add_index :users, :admin - add_index :users, :blocked - add_index :users, :name - add_index :users, :username - - add_index :users_projects, :project_access - add_index :users_projects, :user_id - - add_index :wikis, :project_id - add_index :wikis, :slug - end -end diff --git a/db/migrate/20121218164840_move_noteable_commit_to_own_field.rb b/db/migrate/20121218164840_move_noteable_commit_to_own_field.rb deleted file mode 100644 index 6f2da4136a3cad9623e68ce7c6021abbe3358ad2..0000000000000000000000000000000000000000 --- a/db/migrate/20121218164840_move_noteable_commit_to_own_field.rb +++ /dev/null @@ -1,20 +0,0 @@ -class MoveNoteableCommitToOwnField < ActiveRecord::Migration - def up - add_column :notes, :commit_id, :string, null: true - add_column :notes, :new_noteable_id, :integer, null: true - Note.where(noteable_type: 'Commit').update_all('commit_id = noteable_id') - - if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' - Note.where("noteable_type != 'Commit'").update_all('new_noteable_id = CAST (noteable_id AS INTEGER)') - else - Note.where("noteable_type != 'Commit'").update_all('new_noteable_id = noteable_id') - end - - remove_column :notes, :noteable_id - rename_column :notes, :new_noteable_id, :noteable_id - end - - def down - raise ActiveRecord::IrreversibleMigration - end -end diff --git a/db/migrate/20121219095402_indices_for_notes.rb b/db/migrate/20121219095402_indices_for_notes.rb deleted file mode 100644 index 4c5d041ce815eada2db44b572a5eb75973ce58a7..0000000000000000000000000000000000000000 --- a/db/migrate/20121219095402_indices_for_notes.rb +++ /dev/null @@ -1,6 +0,0 @@ -class IndicesForNotes < ActiveRecord::Migration - def change - add_index :notes, :commit_id - add_index :notes, [:project_id, :noteable_type] - end -end diff --git a/db/migrate/20121219183753_create_user_teams.rb b/db/migrate/20121219183753_create_user_teams.rb deleted file mode 100644 index 65c4d053982cadce45061357bc18b95c89dbf541..0000000000000000000000000000000000000000 --- a/db/migrate/20121219183753_create_user_teams.rb +++ /dev/null @@ -1,11 +0,0 @@ -class CreateUserTeams < ActiveRecord::Migration - def change - create_table :user_teams do |t| - t.string :name - t.string :path - t.integer :owner_id - - t.timestamps - end - end -end diff --git a/db/migrate/20121220064104_create_user_team_project_relationships.rb b/db/migrate/20121220064104_create_user_team_project_relationships.rb deleted file mode 100644 index 8eb654c8728ba6d1a06308f2b3253d8b4df7a6c8..0000000000000000000000000000000000000000 --- a/db/migrate/20121220064104_create_user_team_project_relationships.rb +++ /dev/null @@ -1,11 +0,0 @@ -class CreateUserTeamProjectRelationships < ActiveRecord::Migration - def change - create_table :user_team_project_relationships do |t| - t.integer :project_id - t.integer :user_team_id - t.integer :greatest_access - - t.timestamps - end - end -end diff --git a/db/migrate/20121220064453_create_user_team_user_relationships.rb b/db/migrate/20121220064453_create_user_team_user_relationships.rb deleted file mode 100644 index 7783b0ae4328ff8d1b1d7958f3708d0b6749a29b..0000000000000000000000000000000000000000 --- a/db/migrate/20121220064453_create_user_team_user_relationships.rb +++ /dev/null @@ -1,12 +0,0 @@ -class CreateUserTeamUserRelationships < ActiveRecord::Migration - def change - create_table :user_team_user_relationships do |t| - t.integer :user_id - t.integer :user_team_id - t.boolean :group_admin - t.integer :permission - - t.timestamps - end - end -end diff --git a/db/migrate/20121220064453_init_schema.rb b/db/migrate/20121220064453_init_schema.rb new file mode 100644 index 0000000000000000000000000000000000000000..90f5eb08e8cdaf539a99f3c469e27977c6ae1908 --- /dev/null +++ b/db/migrate/20121220064453_init_schema.rb @@ -0,0 +1,306 @@ +class InitSchema < ActiveRecord::Migration + def up + + create_table "events", force: true do |t| + t.string "target_type" + t.integer "target_id" + t.string "title" + t.text "data" + t.integer "project_id" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "action" + t.integer "author_id" + end + + add_index "events", ["action"], name: "index_events_on_action", using: :btree + add_index "events", ["author_id"], name: "index_events_on_author_id", using: :btree + add_index "events", ["created_at"], name: "index_events_on_created_at", using: :btree + add_index "events", ["project_id"], name: "index_events_on_project_id", using: :btree + add_index "events", ["target_id"], name: "index_events_on_target_id", using: :btree + add_index "events", ["target_type"], name: "index_events_on_target_type", using: :btree + + create_table "issues", force: true do |t| + t.string "title" + t.integer "assignee_id" + t.integer "author_id" + t.integer "project_id" + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "closed", default: false, null: false + t.integer "position", default: 0 + t.string "branch_name" + t.text "description" + t.integer "milestone_id" + end + + add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree + add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree + add_index "issues", ["closed"], name: "index_issues_on_closed", using: :btree + add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree + add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree + add_index "issues", ["project_id"], name: "index_issues_on_project_id", using: :btree + add_index "issues", ["title"], name: "index_issues_on_title", using: :btree + + create_table "keys", force: true do |t| + t.integer "user_id" + t.datetime "created_at" + t.datetime "updated_at" + t.text "key" + t.string "title" + t.string "identifier" + t.integer "project_id" + end + + add_index "keys", ["identifier"], name: "index_keys_on_identifier", using: :btree + add_index "keys", ["project_id"], name: "index_keys_on_project_id", using: :btree + add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree + + create_table "merge_requests", force: true do |t| + t.string "target_branch", null: false + t.string "source_branch", null: false + t.integer "project_id", null: false + t.integer "author_id" + t.integer "assignee_id" + t.string "title" + t.boolean "closed", default: false, null: false + t.datetime "created_at" + t.datetime "updated_at" + t.text "st_commits", limit: 2147483647 + t.text "st_diffs", limit: 2147483647 + t.boolean "merged", default: false, null: false + t.integer "state", default: 1, null: false + t.integer "milestone_id" + end + + add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree + add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree + add_index "merge_requests", ["closed"], name: "index_merge_requests_on_closed", using: :btree + add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree + add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree + add_index "merge_requests", ["project_id"], name: "index_merge_requests_on_project_id", using: :btree + add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree + add_index "merge_requests", ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree + add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree + + create_table "milestones", force: true do |t| + t.string "title", null: false + t.integer "project_id", null: false + t.text "description" + t.date "due_date" + t.boolean "closed", default: false, null: false + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree + add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree + + create_table "namespaces", force: true do |t| + t.string "name", null: false + t.string "path", null: false + t.integer "owner_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.string "type" + end + + add_index "namespaces", ["name"], name: "index_namespaces_on_name", using: :btree + add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree + add_index "namespaces", ["path"], name: "index_namespaces_on_path", using: :btree + add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree + + create_table "notes", force: true do |t| + t.text "note" + t.string "noteable_type" + t.integer "author_id" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "project_id" + t.string "attachment" + t.string "line_code" + t.string "commit_id" + t.integer "noteable_id" + end + + add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree + add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree + add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree + add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree + add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree + + create_table "projects", force: true do |t| + t.string "name" + t.string "path" + t.text "description" + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "private_flag", default: true, null: false + t.integer "owner_id" + t.string "default_branch" + t.boolean "issues_enabled", default: true, null: false + t.boolean "wall_enabled", default: true, null: false + t.boolean "merge_requests_enabled", default: true, null: false + t.boolean "wiki_enabled", default: true, null: false + t.integer "namespace_id" + end + + add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree + add_index "projects", ["owner_id"], name: "index_projects_on_owner_id", using: :btree + + create_table "protected_branches", force: true do |t| + t.integer "project_id", null: false + t.string "name", null: false + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "services", force: true do |t| + t.string "type" + t.string "title" + t.string "token" + t.integer "project_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "active", default: false, null: false + t.string "project_url" + end + + add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree + + create_table "snippets", force: true do |t| + t.string "title" + t.text "content" + t.integer "author_id", null: false + t.integer "project_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.string "file_name" + t.datetime "expires_at" + end + + add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree + add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree + add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree + + create_table "taggings", force: true do |t| + t.integer "tag_id" + t.integer "taggable_id" + t.string "taggable_type" + t.integer "tagger_id" + t.string "tagger_type" + t.string "context" + t.datetime "created_at" + end + + add_index "taggings", ["tag_id"], name: "index_taggings_on_tag_id", using: :btree + add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree + + create_table "tags", force: true do |t| + t.string "name" + end + + create_table "user_team_project_relationships", force: true do |t| + t.integer "project_id" + t.integer "user_team_id" + t.integer "greatest_access" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "user_team_user_relationships", force: true do |t| + t.integer "user_id" + t.integer "user_team_id" + t.boolean "group_admin" + t.integer "permission" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "user_teams", force: true do |t| + t.string "name" + t.string "path" + t.integer "owner_id" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "users", force: true do |t| + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" + t.datetime "reset_password_sent_at" + t.datetime "remember_created_at" + t.integer "sign_in_count", default: 0 + t.datetime "current_sign_in_at" + t.datetime "last_sign_in_at" + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" + t.datetime "created_at" + t.datetime "updated_at" + t.string "name" + t.boolean "admin", default: false, null: false + t.integer "projects_limit", default: 10 + t.string "skype", default: "", null: false + t.string "linkedin", default: "", null: false + t.string "twitter", default: "", null: false + t.string "authentication_token" + t.boolean "dark_scheme", default: false, null: false + t.integer "theme_id", default: 1, null: false + t.string "bio" + t.boolean "blocked", default: false, null: false + t.integer "failed_attempts", default: 0 + t.datetime "locked_at" + t.string "extern_uid" + t.string "provider" + t.string "username" + end + + add_index "users", ["admin"], name: "index_users_on_admin", using: :btree + add_index "users", ["blocked"], name: "index_users_on_blocked", 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", ["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 + + create_table "users_projects", force: true do |t| + t.integer "user_id", null: false + t.integer "project_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.integer "project_access", default: 0, null: false + end + + add_index "users_projects", ["project_access"], name: "index_users_projects_on_project_access", using: :btree + add_index "users_projects", ["project_id"], name: "index_users_projects_on_project_id", using: :btree + add_index "users_projects", ["user_id"], name: "index_users_projects_on_user_id", using: :btree + + create_table "web_hooks", force: true do |t| + t.string "url" + t.integer "project_id" + t.datetime "created_at" + t.datetime "updated_at" + t.string "type", default: "ProjectHook" + t.integer "service_id" + end + + create_table "wikis", force: true do |t| + t.string "title" + t.text "content" + t.integer "project_id" + t.datetime "created_at" + t.datetime "updated_at" + t.string "slug" + t.integer "user_id" + end + + add_index "wikis", ["project_id"], name: "index_wikis_on_project_id", using: :btree + add_index "wikis", ["slug"], name: "index_wikis_on_slug", using: :btree + + end + + def down + raise "Can not revert initial migration" + end +end diff --git a/db/migrate/20130506095501_remove_project_id_from_key.rb b/db/migrate/20130506095501_remove_project_id_from_key.rb index 4214fd45d141b44c496ccc335b430d7bda964a8a..6b794cfb5c133e7d717824c3c592a95ca9130bed 100644 --- a/db/migrate/20130506095501_remove_project_id_from_key.rb +++ b/db/migrate/20130506095501_remove_project_id_from_key.rb @@ -4,7 +4,7 @@ class RemoveProjectIdFromKey < ActiveRecord::Migration Key.where('project_id IS NOT NULL').update_all(type: 'DeployKey') DeployKey.all.each do |key| - project = Project.find_by_id(key.project_id) + project = Project.find_by(id: key.project_id) if project project.deploy_keys << key print '.' diff --git a/db/migrate/20131005191208_add_avatar_to_users.rb b/db/migrate/20131005191208_add_avatar_to_users.rb new file mode 100644 index 0000000000000000000000000000000000000000..7b4de37ad72369481793ec72a1ce99ec867f21ba --- /dev/null +++ b/db/migrate/20131005191208_add_avatar_to_users.rb @@ -0,0 +1,5 @@ +class AddAvatarToUsers < ActiveRecord::Migration + def change + add_column :users, :avatar, :string + end +end diff --git a/db/migrate/20131009115346_add_confirmable_to_users.rb b/db/migrate/20131009115346_add_confirmable_to_users.rb new file mode 100644 index 0000000000000000000000000000000000000000..249cbe704ed678596bee82a744218c97ee4253dd --- /dev/null +++ b/db/migrate/20131009115346_add_confirmable_to_users.rb @@ -0,0 +1,15 @@ +class AddConfirmableToUsers < ActiveRecord::Migration + def self.up + add_column :users, :confirmation_token, :string + add_column :users, :confirmed_at, :datetime + add_column :users, :confirmation_sent_at, :datetime + add_column :users, :unconfirmed_email, :string + add_index :users, :confirmation_token, unique: true + User.update_all(confirmed_at: Time.now) + end + + def self.down + remove_column :users, :confirmation_token, :confirmed_at, :confirmation_sent_at + remove_column :users, :unconfirmed_email + end +end diff --git a/db/migrate/20131106151520_remove_default_branch.rb b/db/migrate/20131106151520_remove_default_branch.rb new file mode 100644 index 0000000000000000000000000000000000000000..88a890eb3eb7e759ced674d594f3218d57a91efe --- /dev/null +++ b/db/migrate/20131106151520_remove_default_branch.rb @@ -0,0 +1,9 @@ +class RemoveDefaultBranch < ActiveRecord::Migration + def up + remove_column :projects, :default_branch + end + + def down + add_column :projects, :default_branch, :string + end +end diff --git a/db/migrate/20131112114325_create_broadcast_messages.rb b/db/migrate/20131112114325_create_broadcast_messages.rb new file mode 100644 index 0000000000000000000000000000000000000000..147178e9dcf2dc7d490f82507aa6fe144f449cf3 --- /dev/null +++ b/db/migrate/20131112114325_create_broadcast_messages.rb @@ -0,0 +1,12 @@ +class CreateBroadcastMessages < ActiveRecord::Migration + def change + create_table :broadcast_messages do |t| + t.text :message, null: false + t.datetime :starts_at + t.datetime :ends_at + t.integer :alert_type + + t.timestamps + end + end +end diff --git a/db/migrate/20131112220935_add_visibility_level_to_projects.rb b/db/migrate/20131112220935_add_visibility_level_to_projects.rb new file mode 100644 index 0000000000000000000000000000000000000000..cf1e9f912a04957ee92486d189ae1edf74ddd83f --- /dev/null +++ b/db/migrate/20131112220935_add_visibility_level_to_projects.rb @@ -0,0 +1,13 @@ +class AddVisibilityLevelToProjects < ActiveRecord::Migration + def self.up + add_column :projects, :visibility_level, :integer, :default => 0, :null => false + Project.where(public: true).update_all(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + remove_column :projects, :public + end + + def self.down + add_column :projects, :public, :boolean, :default => false, :null => false + Project.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).update_all(public: true) + remove_column :projects, :visibility_level + end +end diff --git a/db/migrate/20131129154016_add_archived_to_projects.rb b/db/migrate/20131129154016_add_archived_to_projects.rb new file mode 100644 index 0000000000000000000000000000000000000000..917e690ba477d552aa6cbbc79ffdd490441c87bd --- /dev/null +++ b/db/migrate/20131129154016_add_archived_to_projects.rb @@ -0,0 +1,5 @@ +class AddArchivedToProjects < ActiveRecord::Migration + def change + add_column :projects, :archived, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20131130165425_add_color_and_font_to_broadcast_messages.rb b/db/migrate/20131130165425_add_color_and_font_to_broadcast_messages.rb new file mode 100644 index 0000000000000000000000000000000000000000..473f355eceb0566f4caf97ce575770d95dc5e690 --- /dev/null +++ b/db/migrate/20131130165425_add_color_and_font_to_broadcast_messages.rb @@ -0,0 +1,6 @@ +class AddColorAndFontToBroadcastMessages < ActiveRecord::Migration + def change + add_column :broadcast_messages, :color, :string + add_column :broadcast_messages, :font, :string + end +end diff --git a/db/migrate/20131202192556_add_event_fields_for_web_hook.rb b/db/migrate/20131202192556_add_event_fields_for_web_hook.rb new file mode 100644 index 0000000000000000000000000000000000000000..d29e996852ec3e9a1f8e8f215a9ecfd74ec421fa --- /dev/null +++ b/db/migrate/20131202192556_add_event_fields_for_web_hook.rb @@ -0,0 +1,7 @@ +class AddEventFieldsForWebHook < ActiveRecord::Migration + def change + add_column :web_hooks, :push_events, :boolean, default: true, null: false + add_column :web_hooks, :issues_events, :boolean, default: false, null: false + add_column :web_hooks, :merge_requests_events, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20131214224427_add_hide_no_ssh_key_to_users.rb b/db/migrate/20131214224427_add_hide_no_ssh_key_to_users.rb new file mode 100644 index 0000000000000000000000000000000000000000..7cec79e7ee823547e9b6c026198b0c97581e3d84 --- /dev/null +++ b/db/migrate/20131214224427_add_hide_no_ssh_key_to_users.rb @@ -0,0 +1,5 @@ +class AddHideNoSshKeyToUsers < ActiveRecord::Migration + def change + add_column :users, :hide_no_ssh_key, :boolean, :default => false + end +end diff --git a/db/migrate/20131217102743_add_recipients_to_service.rb b/db/migrate/20131217102743_add_recipients_to_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..9695c25135202edacea014e237179a4fb1ee62fa --- /dev/null +++ b/db/migrate/20131217102743_add_recipients_to_service.rb @@ -0,0 +1,5 @@ +class AddRecipientsToService < ActiveRecord::Migration + def change + add_column :services, :recipients, :text + end +end diff --git a/db/migrate/20140116231608_add_website_url_to_users.rb b/db/migrate/20140116231608_add_website_url_to_users.rb new file mode 100644 index 0000000000000000000000000000000000000000..0996fdcad73257dc31b728a2da9564b4715626c2 --- /dev/null +++ b/db/migrate/20140116231608_add_website_url_to_users.rb @@ -0,0 +1,5 @@ +class AddWebsiteUrlToUsers < ActiveRecord::Migration + def change + add_column :users, :website_url, :string, {:null => false, :default => ''} + end +end diff --git a/db/migrate/20140122112253_create_merge_request_diffs.rb b/db/migrate/20140122112253_create_merge_request_diffs.rb new file mode 100644 index 0000000000000000000000000000000000000000..ef592305a23a9488f666f8a98d8297cd28d8a815 --- /dev/null +++ b/db/migrate/20140122112253_create_merge_request_diffs.rb @@ -0,0 +1,12 @@ +class CreateMergeRequestDiffs < ActiveRecord::Migration + def change + create_table :merge_request_diffs do |t| + t.string :state, null: false, default: 'collected' + t.text :st_commits, null: true, limit: 2147483647 + t.text :st_diffs, null: true, limit: 2147483647 + t.integer :merge_request_id, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20140122114406_migrate_mr_diffs.rb b/db/migrate/20140122114406_migrate_mr_diffs.rb new file mode 100644 index 0000000000000000000000000000000000000000..1595e2b64725923ee02c0f65a65d25144f573f47 --- /dev/null +++ b/db/migrate/20140122114406_migrate_mr_diffs.rb @@ -0,0 +1,9 @@ +class MigrateMrDiffs < ActiveRecord::Migration + def self.up + execute "INSERT INTO merge_request_diffs ( merge_request_id, st_commits, st_diffs ) SELECT id, st_commits, st_diffs FROM merge_requests" + end + + def self.down + MergeRequestDiff.delete_all + end +end diff --git a/db/migrate/20140122122549_remove_m_rdiff_fields.rb b/db/migrate/20140122122549_remove_m_rdiff_fields.rb new file mode 100644 index 0000000000000000000000000000000000000000..8f863d85a684909d886a7ee67685b28aca190749 --- /dev/null +++ b/db/migrate/20140122122549_remove_m_rdiff_fields.rb @@ -0,0 +1,21 @@ +class RemoveMRdiffFields < ActiveRecord::Migration + def up + remove_column :merge_requests, :st_commits + remove_column :merge_requests, :st_diffs + end + + def down + add_column :merge_requests, :st_commits, :text, null: true, limit: 2147483647 + add_column :merge_requests, :st_diffs, :text, null: true, limit: 2147483647 + + if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' + execute "UPDATE merge_requests mr + SET (st_commits, st_diffs) = (md.st_commits, md.st_diffs) + FROM merge_request_diffs md + WHERE md.merge_request_id = mr.id" + else + execute "UPDATE merge_requests mr, merge_request_diffs md SET mr.st_commits = md.st_commits WHERE md.merge_request_id = mr.id" + execute "UPDATE merge_requests mr, merge_request_diffs md SET mr.st_diffs = md.st_diffs WHERE md.merge_request_id = mr.id" + end + end +end diff --git a/db/migrate/20140127170938_add_group_avatars.rb b/db/migrate/20140127170938_add_group_avatars.rb new file mode 100644 index 0000000000000000000000000000000000000000..2911096dd5d300268d1efeedb3510a368d5bc8a3 --- /dev/null +++ b/db/migrate/20140127170938_add_group_avatars.rb @@ -0,0 +1,5 @@ +class AddGroupAvatars < ActiveRecord::Migration + def change + add_column :namespaces, :avatar, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 713d9f733d6b50f3c8e660088aab4ae850bb31eb..acbb793bbe8daab9522909eb299826ddbff6f836 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -9,55 +9,66 @@ # from scratch. The latter is a flawed and unsustainable approach (the more migrations # you'll amass, the slower it'll run and the greater likelihood for issues). # -# It's strongly recommended to check this file into your version control system. - -ActiveRecord::Schema.define(:version => 20130926081215) do +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 20140127170938) do + + create_table "broadcast_messages", force: true do |t| + t.text "message", null: false + t.datetime "starts_at" + t.datetime "ends_at" + t.integer "alert_type" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "color" + t.string "font" + end - create_table "deploy_keys_projects", :force => true do |t| - t.integer "deploy_key_id", :null => false - t.integer "project_id", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + create_table "deploy_keys_projects", force: true do |t| + t.integer "deploy_key_id", null: false + t.integer "project_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "deploy_keys_projects", ["project_id"], :name => "index_deploy_keys_projects_on_project_id" + add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree - create_table "events", :force => true do |t| + create_table "events", force: true do |t| t.string "target_type" t.integer "target_id" t.string "title" t.text "data" t.integer "project_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "action" t.integer "author_id" end - add_index "events", ["action"], :name => "index_events_on_action" - add_index "events", ["author_id"], :name => "index_events_on_author_id" - add_index "events", ["created_at"], :name => "index_events_on_created_at" - add_index "events", ["project_id"], :name => "index_events_on_project_id" - add_index "events", ["target_id"], :name => "index_events_on_target_id" - add_index "events", ["target_type"], :name => "index_events_on_target_type" - - create_table "forked_project_links", :force => true do |t| - t.integer "forked_to_project_id", :null => false - t.integer "forked_from_project_id", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + add_index "events", ["action"], name: "index_events_on_action", using: :btree + add_index "events", ["author_id"], name: "index_events_on_author_id", using: :btree + add_index "events", ["created_at"], name: "index_events_on_created_at", using: :btree + add_index "events", ["project_id"], name: "index_events_on_project_id", using: :btree + add_index "events", ["target_id"], name: "index_events_on_target_id", using: :btree + add_index "events", ["target_type"], name: "index_events_on_target_type", using: :btree + + create_table "forked_project_links", force: true do |t| + t.integer "forked_to_project_id", null: false + t.integer "forked_from_project_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "forked_project_links", ["forked_to_project_id"], :name => "index_forked_project_links_on_forked_to_project_id", :unique => true + 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 "issues", :force => true do |t| + create_table "issues", force: true do |t| t.string "title" t.integer "assignee_id" t.integer "author_id" t.integer "project_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "position", :default => 0 + t.datetime "created_at" + t.datetime "updated_at" + t.integer "position", default: 0 t.string "branch_name" t.text "description" t.integer "milestone_id" @@ -65,174 +76,183 @@ ActiveRecord::Schema.define(:version => 20130926081215) do t.integer "iid" end - add_index "issues", ["assignee_id"], :name => "index_issues_on_assignee_id" - add_index "issues", ["author_id"], :name => "index_issues_on_author_id" - add_index "issues", ["created_at"], :name => "index_issues_on_created_at" - add_index "issues", ["milestone_id"], :name => "index_issues_on_milestone_id" - add_index "issues", ["project_id"], :name => "index_issues_on_project_id" - add_index "issues", ["title"], :name => "index_issues_on_title" + add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree + add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree + add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree + add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree + add_index "issues", ["project_id"], name: "index_issues_on_project_id", using: :btree + add_index "issues", ["title"], name: "index_issues_on_title", using: :btree - create_table "keys", :force => true do |t| + create_table "keys", force: true do |t| t.integer "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at" + t.datetime "updated_at" t.text "key" t.string "title" t.string "type" t.string "fingerprint" end - add_index "keys", ["user_id"], :name => "index_keys_on_user_id" + add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree - create_table "merge_requests", :force => true do |t| - t.string "target_branch", :null => false - t.string "source_branch", :null => false - t.integer "source_project_id", :null => false + create_table "merge_request_diffs", force: true do |t| + t.string "state", default: "collected", null: false + t.text "st_commits", limit: 2147483647 + t.text "st_diffs", limit: 2147483647 + t.integer "merge_request_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "merge_requests", force: true do |t| + t.string "target_branch", null: false + t.string "source_branch", null: false + t.integer "source_project_id", null: false t.integer "author_id" t.integer "assignee_id" t.string "title" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.text "st_commits", :limit => 2147483647 - t.text "st_diffs", :limit => 2147483647 + t.datetime "created_at" + t.datetime "updated_at" t.integer "milestone_id" t.string "state" t.string "merge_status" - t.integer "target_project_id", :null => false + t.integer "target_project_id", null: false t.integer "iid" t.text "description" end - add_index "merge_requests", ["assignee_id"], :name => "index_merge_requests_on_assignee_id" - add_index "merge_requests", ["author_id"], :name => "index_merge_requests_on_author_id" - add_index "merge_requests", ["created_at"], :name => "index_merge_requests_on_created_at" - add_index "merge_requests", ["milestone_id"], :name => "index_merge_requests_on_milestone_id" - add_index "merge_requests", ["source_branch"], :name => "index_merge_requests_on_source_branch" - add_index "merge_requests", ["source_project_id"], :name => "index_merge_requests_on_project_id" - add_index "merge_requests", ["target_branch"], :name => "index_merge_requests_on_target_branch" - add_index "merge_requests", ["title"], :name => "index_merge_requests_on_title" - - create_table "milestones", :force => true do |t| - t.string "title", :null => false - t.integer "project_id", :null => false + add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree + add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree + add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree + add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree + add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree + add_index "merge_requests", ["source_project_id"], name: "index_merge_requests_on_project_id", using: :btree + add_index "merge_requests", ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree + add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree + + create_table "milestones", force: true do |t| + t.string "title", null: false + t.integer "project_id", null: false t.text "description" t.date "due_date" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "state" t.integer "iid" end - add_index "milestones", ["due_date"], :name => "index_milestones_on_due_date" - add_index "milestones", ["project_id"], :name => "index_milestones_on_project_id" + add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree + add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree - create_table "namespaces", :force => true do |t| - t.string "name", :null => false - t.string "path", :null => false + create_table "namespaces", force: true do |t| + t.string "name", null: false + t.string "path", null: false t.integer "owner_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "type" - t.string "description", :default => "", :null => false + t.string "description", default: "", null: false + t.string "avatar" end - add_index "namespaces", ["name"], :name => "index_namespaces_on_name" - add_index "namespaces", ["owner_id"], :name => "index_namespaces_on_owner_id" - add_index "namespaces", ["path"], :name => "index_namespaces_on_path" - add_index "namespaces", ["type"], :name => "index_namespaces_on_type" + add_index "namespaces", ["name"], name: "index_namespaces_on_name", using: :btree + add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree + add_index "namespaces", ["path"], name: "index_namespaces_on_path", using: :btree + add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree - create_table "notes", :force => true do |t| + create_table "notes", force: true do |t| t.text "note" t.string "noteable_type" t.integer "author_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at" + t.datetime "updated_at" t.integer "project_id" t.string "attachment" t.string "line_code" t.string "commit_id" t.integer "noteable_id" t.text "st_diff" - t.boolean "system", :default => false, :null => false + t.boolean "system", default: false, null: false end - add_index "notes", ["author_id"], :name => "index_notes_on_author_id" - add_index "notes", ["commit_id"], :name => "index_notes_on_commit_id" - add_index "notes", ["created_at"], :name => "index_notes_on_created_at" - add_index "notes", ["noteable_id", "noteable_type"], :name => "index_notes_on_noteable_id_and_noteable_type" - add_index "notes", ["noteable_type"], :name => "index_notes_on_noteable_type" - add_index "notes", ["project_id", "noteable_type"], :name => "index_notes_on_project_id_and_noteable_type" - add_index "notes", ["project_id"], :name => "index_notes_on_project_id" + add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree + add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree + add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree + add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree + add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree + add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree + add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree - create_table "projects", :force => true do |t| + create_table "projects", force: true do |t| t.string "name" t.string "path" t.text "description" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at" + t.datetime "updated_at" t.integer "creator_id" - t.string "default_branch" - t.boolean "issues_enabled", :default => true, :null => false - t.boolean "wall_enabled", :default => true, :null => false - t.boolean "merge_requests_enabled", :default => true, :null => false - t.boolean "wiki_enabled", :default => true, :null => false + t.boolean "issues_enabled", default: true, null: false + t.boolean "wall_enabled", default: true, null: false + t.boolean "merge_requests_enabled", default: true, null: false + t.boolean "wiki_enabled", default: true, null: false t.integer "namespace_id" - t.boolean "public", :default => false, :null => false - t.string "issues_tracker", :default => "gitlab", :null => false + t.string "issues_tracker", default: "gitlab", null: false t.string "issues_tracker_id" - t.boolean "snippets_enabled", :default => true, :null => false + t.boolean "snippets_enabled", default: true, null: false t.datetime "last_activity_at" - t.boolean "imported", :default => false, :null => false + t.boolean "imported", default: false, null: false t.string "import_url" + t.integer "visibility_level", default: 0, null: false + t.boolean "archived", default: false, null: false end - add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id" - add_index "projects", ["last_activity_at"], :name => "index_projects_on_last_activity_at" - add_index "projects", ["namespace_id"], :name => "index_projects_on_namespace_id" + add_index "projects", ["creator_id"], name: "index_projects_on_owner_id", using: :btree + add_index "projects", ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree + add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree - create_table "protected_branches", :force => true do |t| - t.integer "project_id", :null => false - t.string "name", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + create_table "protected_branches", force: true do |t| + t.integer "project_id", null: false + t.string "name", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "protected_branches", ["project_id"], :name => "index_protected_branches_on_project_id" + add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree - create_table "services", :force => true do |t| + create_table "services", force: true do |t| t.string "type" t.string "title" t.string "token" - t.integer "project_id", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.boolean "active", :default => false, :null => false + t.integer "project_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "active", default: false, null: false t.string "project_url" t.string "subdomain" t.string "room" + t.text "recipients" end - add_index "services", ["project_id"], :name => "index_services_on_project_id" + add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree - create_table "snippets", :force => true do |t| + create_table "snippets", force: true do |t| t.string "title" - t.text "content", :limit => 2147483647 - t.integer "author_id", :null => false + t.text "content", limit: 2147483647 + t.integer "author_id", null: false t.integer "project_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at" + t.datetime "updated_at" t.string "file_name" t.datetime "expires_at" - t.boolean "private", :default => true, :null => false + t.boolean "private", default: true, null: false t.string "type" end - add_index "snippets", ["author_id"], :name => "index_snippets_on_author_id" - add_index "snippets", ["created_at"], :name => "index_snippets_on_created_at" - add_index "snippets", ["expires_at"], :name => "index_snippets_on_expires_at" - add_index "snippets", ["project_id"], :name => "index_snippets_on_project_id" + add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree + add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree + add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree + add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree - create_table "taggings", :force => true do |t| + create_table "taggings", force: true do |t| t.integer "tag_id" t.integer "taggable_id" t.string "taggable_type" @@ -242,90 +262,97 @@ ActiveRecord::Schema.define(:version => 20130926081215) do t.datetime "created_at" end - add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id" - add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_context" - - create_table "tags", :force => true do |t| + create_table "tags", force: true do |t| t.string "name" end - create_table "users", :force => true do |t| - t.string "email", :default => "", :null => false - t.string "encrypted_password", :default => "", :null => false + create_table "users", force: true do |t| + t.string "email", default: "", null: false + t.string "encrypted_password", limit: 128, default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", :default => 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" t.string "last_sign_in_ip" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at" + t.datetime "updated_at" t.string "name" - t.boolean "admin", :default => false, :null => false - t.integer "projects_limit", :default => 10 - t.string "skype", :default => "", :null => false - t.string "linkedin", :default => "", :null => false - t.string "twitter", :default => "", :null => false + t.boolean "admin", default: false, null: false + t.integer "projects_limit", default: 10 + t.string "skype", default: "", null: false + t.string "linkedin", default: "", null: false + t.string "twitter", default: "", null: false t.string "authentication_token" - t.integer "theme_id", :default => 1, :null => false + t.integer "theme_id", default: 1, null: false t.string "bio" - t.integer "failed_attempts", :default => 0 + t.integer "failed_attempts", default: 0 t.datetime "locked_at" t.string "extern_uid" t.string "provider" t.string "username" - t.boolean "can_create_group", :default => true, :null => false - t.boolean "can_create_team", :default => true, :null => false + t.boolean "can_create_group", default: true, null: false + t.boolean "can_create_team", default: true, null: false t.string "state" - t.integer "color_scheme_id", :default => 1, :null => false - t.integer "notification_level", :default => 1, :null => false + t.integer "color_scheme_id", default: 1, null: false + t.integer "notification_level", default: 1, null: false t.datetime "password_expires_at" t.integer "created_by_id" + t.string "avatar" + t.string "confirmation_token" + t.datetime "confirmed_at" + t.datetime "confirmation_sent_at" + t.string "unconfirmed_email" + t.boolean "hide_no_ssh_key", default: false + t.string "website_url", default: "", null: false end - add_index "users", ["admin"], :name => "index_users_on_admin" - add_index "users", ["authentication_token"], :name => "index_users_on_authentication_token", :unique => true - add_index "users", ["email"], :name => "index_users_on_email", :unique => true - add_index "users", ["extern_uid", "provider"], :name => "index_users_on_extern_uid_and_provider", :unique => true - add_index "users", ["name"], :name => "index_users_on_name" - add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true - add_index "users", ["username"], :name => "index_users_on_username" - - create_table "users_groups", :force => true do |t| - t.integer "group_access", :null => false - t.integer "group_id", :null => false - t.integer "user_id", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "notification_level", :default => 3, :null => false + add_index "users", ["admin"], name: "index_users_on_admin", using: :btree + add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree + add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree + add_index "users", ["email"], name: "index_users_on_email", unique: true, 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", ["username"], name: "index_users_on_username", using: :btree + + create_table "users_groups", force: true do |t| + t.integer "group_access", null: false + t.integer "group_id", null: false + t.integer "user_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "notification_level", default: 3, null: false end - add_index "users_groups", ["user_id"], :name => "index_users_groups_on_user_id" + add_index "users_groups", ["user_id"], name: "index_users_groups_on_user_id", using: :btree - create_table "users_projects", :force => true do |t| - t.integer "user_id", :null => false - t.integer "project_id", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "project_access", :default => 0, :null => false - t.integer "notification_level", :default => 3, :null => false + create_table "users_projects", force: true do |t| + t.integer "user_id", null: false + t.integer "project_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.integer "project_access", default: 0, null: false + t.integer "notification_level", default: 3, null: false end - add_index "users_projects", ["project_access"], :name => "index_users_projects_on_project_access" - add_index "users_projects", ["project_id"], :name => "index_users_projects_on_project_id" - add_index "users_projects", ["user_id"], :name => "index_users_projects_on_user_id" + add_index "users_projects", ["project_access"], name: "index_users_projects_on_project_access", using: :btree + add_index "users_projects", ["project_id"], name: "index_users_projects_on_project_id", using: :btree + add_index "users_projects", ["user_id"], name: "index_users_projects_on_user_id", using: :btree - create_table "web_hooks", :force => true do |t| + create_table "web_hooks", force: true do |t| t.string "url" t.integer "project_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "type", :default => "ProjectHook" + t.datetime "created_at" + t.datetime "updated_at" + t.string "type", default: "ProjectHook" t.integer "service_id" + t.boolean "push_events", default: true, null: false + t.boolean "issues_events", default: false, null: false + t.boolean "merge_requests_events", default: false, null: false end - add_index "web_hooks", ["project_id"], :name => "index_web_hooks_on_project_id" + add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree end diff --git a/doc/api/README.md b/doc/api/README.md index 6971e08f010418207bb2bff312365466e51b656e..517a9fae6f60054985f7e0d73c4bfcfb58ae3aa7 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -96,13 +96,30 @@ curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" --header "SUDO: username" "h curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" --header "SUDO: 23" "http://example.com/api/v3/projects" ``` -#### Pagination +## Pagination When listing resources you can pass the following parameters: + `page` (default: `1`) - page number + `per_page` (default: `20`, max: `100`) - number of items to list per page +## id vs iid + +When you work with API you may notice two similar fields in api entites: id and iid. +The main difference between them is scope. Example: + +Issue + id: 46 + iid: 5 + +* id - is uniq across all Issues table. It used for any api calls. +* iid - is uniq only in scope of single project. When you browse issues or merge requests with Web UI - you see iid. + +So if you want to get issue with api you use `http://host/api/v3/.../issues/:id.json` +But when you want to create a link to web page - use `http:://host/project/issues/:iid.json` + + + ## Contents + [Users](users.md) @@ -117,7 +134,7 @@ When listing resources you can pass the following parameters: + [Deploy Keys](deploy_keys.md) + [System Hooks](system_hooks.md) + [Groups](groups.md) -+ [User Teams](user_teams.md) + ## Clients diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md index fbb1e45bccdac13d62be809e603acbac7bd05fe7..50d2b82c36f4143ab7a1f1714b600e8aaa83b499 100644 --- a/doc/api/deploy_keys.md +++ b/doc/api/deploy_keys.md @@ -20,13 +20,15 @@ Parameters: "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4 soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "created_at":"2013-10-02T10:12:29Z" }, { "id": 3, "title" : "Another Public key", "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4 - soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" + soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "created_at":"2013-10-02T11:12:29Z" } ] ``` @@ -51,7 +53,8 @@ Parameters: "title" : "Public key", "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4 - soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" + soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "created_at":"2013-10-02T10:12:29Z" } ``` diff --git a/doc/api/groups.md b/doc/api/groups.md index fb77901c65be5c44e9c05af6ff11b40d4b71bfde..f5f5d7690509cccd3184caefb53284d4c32222b2 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -8,12 +8,12 @@ GET /groups ```json [ - { - "id": 1, - "name": "Foobar Group", - "path": "foo-bar", - "owner_id": 18 - } + { + "id": 1, + "name": "Foobar Group", + "path": "foo-bar", + "owner_id": 18 + } ] ``` @@ -57,8 +57,34 @@ Parameters: + `project_id` (required) - The ID of a project +## Remove group + +Removes group with all projects inside. + +``` +DELETE /groups/:id +``` + +Parameters: + ++ `id` (required) - The ID of a user group + + ## Group members + +**Group access levels** + +The group access levels are defined in the `Gitlab::Access` module. Currently, these levels are recognized: + +``` + GUEST = 10 + REPORTER = 20 + DEVELOPER = 30 + MASTER = 40 + OWNER = 50 +``` + ### List group members Get a list of group members viewable by the authenticated user. @@ -70,22 +96,22 @@ GET /groups/:id/members ```json [ { - id: 1, - username: "raymond_smith", - email: "ray@smith.org", - name: "Raymond Smith", - state: "active", - created_at: "2012-10-22T14:13:35Z", - access_level: 30 + "id": 1, + "username": "raymond_smith", + "email": "ray@smith.org", + "name": "Raymond Smith", + "state": "active", + "created_at": "2012-10-22T14:13:35Z", + "access_level": 30 }, { - id: 2, - username: "john_doe", - email: "joh@doe.org", - name: "John Doe", - state: "active", - created_at: "2012-10-22T14:13:35Z", - access_level: 30 + "id": 2, + "username": "john_doe", + "email": "joh@doe.org", + "name": "John Doe", + "state": "active", + "created_at": "2012-10-22T14:13:35Z", + "access_level": 30 } ] ``` diff --git a/doc/api/issues.md b/doc/api/issues.md index 723c8acf381604c104b1465c00c7099de04584ed..ef37940faf014df5a72db693ce8870b74eb30fe5 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -11,6 +11,7 @@ GET /issues [ { "id": 43, + "iid": 3, "project_id": 8, "title": "4xx/5xx pages", "description": "", @@ -22,15 +23,16 @@ GET /issues "username": "john_smith", "email": "john@example.com", "name": "John Smith", - "blocked": false, + "state": "active", "created_at": "2012-05-23T08:00:58Z" }, - "state": 'closed', + "state": "closed", "updated_at": "2012-07-02T17:53:12Z", "created_at": "2012-07-02T17:53:12Z" }, { "id": 42, + "iid": 4, "project_id": 8, "title": "Add user settings", "description": "", @@ -42,7 +44,7 @@ GET /issues "title": "v1.0", "description": "", "due_date": "2012-07-20", - "state": 'reopenend', + "state": "reopenend", "updated_at": "2012-07-04T13:42:48Z", "created_at": "2012-07-04T13:42:48Z" }, @@ -51,7 +53,7 @@ GET /issues "username": "jack_smith", "email": "jack@example.com", "name": "Jack Smith", - "blocked": false, + "state": "active", "created_at": "2012-05-23T08:01:01Z" }, "author": { @@ -59,10 +61,10 @@ GET /issues "username": "john_smith", "email": "john@example.com", "name": "John Smith", - "blocked": false, + "state": "active", "created_at": "2012-05-23T08:00:58Z" }, - "state": 'opened', + "state": "opened", "updated_at": "2012-07-12T13:43:19Z", "created_at": "2012-06-28T12:58:06Z" } @@ -100,6 +102,7 @@ Parameters: ```json { "id": 42, + "iid": 3, "project_id": 8, "title": "Add user settings", "description": "", @@ -111,7 +114,7 @@ Parameters: "title": "v1.0", "description": "", "due_date": "2012-07-20", - "state": 'closed', + "state": "closed", "updated_at": "2012-07-04T13:42:48Z", "created_at": "2012-07-04T13:42:48Z" }, @@ -120,7 +123,7 @@ Parameters: "username": "jack_smith", "email": "jack@example.com", "name": "Jack Smith", - "blocked": false, + "state": "active", "created_at": "2012-05-23T08:01:01Z" }, "author": { @@ -128,10 +131,10 @@ Parameters: "username": "john_smith", "email": "john@example.com", "name": "John Smith", - "blocked": false, + "state": "active", "created_at": "2012-05-23T08:00:58Z" }, - "state": 'opened', + "state": "opened", "updated_at": "2012-07-12T13:43:19Z", "created_at": "2012-06-28T12:58:06Z" } @@ -190,4 +193,3 @@ Parameters: + `id` (required) - The project ID + `issue_id` (required) - The ID of the issue - diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 111c52112ebc806c8a70a1adc1e336160176a930..2ddaea5a584b04db72f5cf0bf86dde22c2ce2433 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -15,18 +15,20 @@ Parameters: [ { "id":1, + "iid":1, "target_branch":"master", "source_branch":"test1", "project_id":3, "title":"test1", - "closed":true, - "merged":false, + "state":"opened", + "upvotes":0, + "downvotes":0, "author":{ "id":1, "username": "admin", "email":"admin@local.host", "name":"Administrator", - "blocked":false, + "state":"active", "created_at":"2012-04-29T08:46:00Z" }, "assignee":{ @@ -34,7 +36,7 @@ Parameters: "username": "admin", "email":"admin@local.host", "name":"Administrator", - "blocked":false, + "state":"active", "created_at":"2012-04-29T08:46:00Z" } } @@ -58,18 +60,20 @@ Parameters: ```json { "id":1, + "iid":1, "target_branch":"master", "source_branch":"test1", "project_id":3, "title":"test1", - "closed":true, - "merged":false, + "state":"merged", + "upvotes":0, + "downvotes":0, "author":{ "id":1, "username": "admin", "email":"admin@local.host", "name":"Administrator", - "blocked":false, + "state":"active", "created_at":"2012-04-29T08:46:00Z" }, "assignee":{ @@ -77,7 +81,7 @@ Parameters: "username": "admin", "email":"admin@local.host", "name":"Administrator", - "blocked":false, + "state":"active", "created_at":"2012-04-29T08:46:00Z" } } @@ -97,7 +101,7 @@ Parameters: + `id` (required) - The ID of a project + `source_branch` (required) - The source branch + `target_branch` (required) - The target branch -+ `assignee_id` - Assignee user ID ++ `assignee_id` (optional) - Assignee user ID + `title` (required) - Title of MR ```json @@ -107,14 +111,15 @@ Parameters: "source_branch":"test1", "project_id":3, "title":"test1", - "closed":true, - "merged":false, + "state":"opened", + "upvotes":0, + "downvotes":0, "author":{ "id":1, "username": "admin", "email":"admin@local.host", "name":"Administrator", - "blocked":false, + "state":"active", "created_at":"2012-04-29T08:46:00Z" }, "assignee":{ @@ -122,7 +127,7 @@ Parameters: "username": "admin", "email":"admin@local.host", "name":"Administrator", - "blocked":false, + "state":"active", "created_at":"2012-04-29T08:46:00Z" } } @@ -145,24 +150,24 @@ Parameters: + `target_branch` - The target branch + `assignee_id` - Assignee user ID + `title` - Title of MR -+ `closed` - Status of MR. true - closed - ```json + { "id":1, "target_branch":"master", "source_branch":"test1", "project_id":3, "title":"test1", - "closed":true, - "merged":false, + "state":"opened", + "upvotes":0, + "downvotes":0, "author":{ "id":1, "username": "admin", "email":"admin@local.host", "name":"Administrator", - "blocked":false, + "state":"active", "created_at":"2012-04-29T08:46:00Z" }, "assignee":{ @@ -170,7 +175,7 @@ Parameters: "username": "admin", "email":"admin@local.host", "name":"Administrator", - "blocked":false, + "state":"active", "created_at":"2012-04-29T08:46:00Z" } } diff --git a/doc/api/milestones.md b/doc/api/milestones.md index aa8f1bf5e02b846e588583324a96b336bef8d4ab..e899e28d219591cf51ca55b6685af9c4196327a8 100644 --- a/doc/api/milestones.md +++ b/doc/api/milestones.md @@ -6,6 +6,22 @@ Returns a list of project milestones. GET /projects/:id/milestones ``` +```json +[ + { + "id":12, + "iid":3, + "project_id":16, + "title":"10.0", + "description":"Version", + "due_date":"2013-11-29", + "state":"active", + "updated_at":"2013-10-02T09:24:18Z", + "created_at":"2013-10-02T09:24:18Z" + } +] +``` + Parameters: + `id` (required) - The ID of a project diff --git a/doc/api/notes.md b/doc/api/notes.md index 4b57f636a0143e6075b2bb761f58a31ffa939d1f..397aa87aadff10aa72e6a74098e77a8ad0622ef7 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -8,17 +8,22 @@ Get a list of project wall notes. GET /projects/:id/notes ``` +Parameters: + ++ `id` (required) - The ID of a project + ```json [ { "id": 522, "body": "The solution is rather tricky", + "attachment":null, "author": { "id": 1, "username": "john_smith", "email": "john@example.com", "name": "John Smith", - "blocked": false, + "state": "active", "created_at": "2012-05-23T08:00:58Z" }, "created_at": "2012-11-27T19:16:44Z" @@ -26,11 +31,6 @@ GET /projects/:id/notes ] ``` -Parameters: - -+ `id` (required) - The ID of a project - - ### Get single wall note Returns a single wall note. @@ -74,6 +74,38 @@ Parameters: + `id` (required) - The ID of a project + `issue_id` (required) - The ID of an issue +```json +[ + { + "id":302, + "body":"_Status changed to closed_", + "attachment":null, + "author":{ + "id":1, + "username":"pipin", + "email":"admin@example.com", + "name":"Pip", + "state":"active", + "created_at":"2013-09-30T13:46:01Z" + }, + "created_at":"2013-10-02T09:22:45Z" + }, + { + "id":305, + "body":"Text of the comment\r\n", + "attachment":null, + "author":{ + "id":1, + "username":"pipin", + "email":"admin@example.com", + "name":"Pip", + "state":"active", + "created_at":"2013-09-30T13:46:01Z" + }, + "created_at":"2013-10-02T09:56:03Z" + } +] +``` ### Get single issue note @@ -135,6 +167,24 @@ Parameters: + `snippet_id` (required) - The ID of a project snippet + `note_id` (required) - The ID of an snippet note +```json +{ + "id":52, + "title":"Snippet", + "file_name":"snippet.rb", + "author":{ + "id":1, + "username":"pipin", + "email":"admin@example.com", + "name":"Pip", + "state":"active", + "created_at":"2013-09-30T13:46:01Z" + }, + "expires_at":null, + "updated_at":"2013-10-02T07:34:20Z", + "created_at":"2013-10-02T07:34:20Z" +} +``` ### Create new snippet note @@ -181,6 +231,22 @@ Parameters: + `merge_request_id` (required) - The ID of a project merge request + `note_id` (required) - The ID of a merge request note +```json +{ + "id":301, + "body":"Comment for MR", + "attachment":null, + "author":{ + "id":1, + "username":"pipin", + "email":"admin@example.com", + "name":"Pip", + "state":"active", + "created_at":"2013-09-30T13:46:01Z" + }, + "created_at":"2013-10-02T08:57:14Z" +} +``` ### Create new merge request note diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md index 04ea367d51807cf1033f230e5f9a0a8ed041df49..e16e1e845963ea298b3b58c078f66e9f822eeda2 100644 --- a/doc/api/project_snippets.md +++ b/doc/api/project_snippets.md @@ -34,7 +34,7 @@ Parameters: "username": "john_smith", "email": "john@example.com", "name": "John Smith", - "blocked": false, + "state": "active", "created_at": "2012-05-23T08:00:58Z" }, "expires_at": null, @@ -57,7 +57,6 @@ Parameters: + `id` (required) - The ID of a project + `title` (required) - The title of a snippet + `file_name` (required) - The name of a snippet file -+ `lifetime` (optional) - The expiration date of a snippet + `code` (required) - The content of a snippet @@ -75,7 +74,6 @@ Parameters: + `snippet_id` (required) - The ID of a project's snippet + `title` (optional) - The title of a snippet + `file_name` (optional) - The name of a snippet file -+ `lifetime` (optional) - The expiration date of a snippet + `code` (optional) - The content of a snippet diff --git a/doc/api/projects.md b/doc/api/projects.md index 5150331e7d7face41f2ca53bd234531f4bc3b536..559553a1108657ddb02449e474cd3d92dd3176b7 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -2,7 +2,7 @@ ### List projects -Get a list of projects owned by the authenticated user. +Get a list of projects accessible by the authenticated user. ``` GET /projects @@ -11,60 +11,99 @@ GET /projects ```json [ { - "id": 3, - "name": "rails", + "id": 4, "description": null, "default_branch": "master", + "public": false, + "visibility_level": 0, + "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git", + "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git", + "web_url": "http://example.com/diaspora/diaspora-client", "owner": { - "id": 1, - "username": "john_smith", - "email": "john@example.com", - "name": "John Smith", - "blocked": false, - "created_at": "2012-05-23T08:00:58Z" + "id": 3, + "name": "Diaspora", + "created_at": "2013-09-30T13: 46: 02Z" }, - "public": true, - "path": "rails", - "path_with_namespace": "rails/rails", - "issues_enabled": false, - "merge_requests_enabled": false, - "wall_enabled": true, + "name": "Diaspora Client", + "name_with_namespace": "Diaspora / Diaspora Client", + "path": "diaspora-client", + "path_with_namespace": "diaspora/diaspora-client", + "issues_enabled": true, + "merge_requests_enabled": true, + "wall_enabled": false, "wiki_enabled": true, - "created_at": "2012-05-23T08:05:02Z", - "last_activity_at": "2012-05-23T08:05:02Z" + "snippets_enabled": false, + "created_at": "2013-09-30T13: 46: 02Z", + "last_activity_at": "2013-09-30T13: 46: 02Z", + "namespace": { + "created_at": "2013-09-30T13: 46: 02Z", + "description": "", + "id": 3, + "name": "Diaspora", + "owner_id": 1, + "path": "diaspora", + "updated_at": "2013-09-30T13: 46: 02Z" + } }, { - "id": 5, - "name": "gitlab", + "id": 6, "description": null, - "default_branch": "api", - "owner": { - "id": 1, - "username": "john_smith", - "email": "john@example.com", - "name": "John Smith", - "blocked": false, - "created_at": "2012-05-23T08:00:58Z" + "default_branch": "master", + "public": false, + "visibility_level": 0, + "ssh_url_to_repo": "git@example.com:brightbox/puppet.git", + "http_url_to_repo": "http://example.com/brightbox/puppet.git", + "web_url": "http://example.com/brightbox/puppet", + "owner": { + "id": 4, + "name": "Brightbox", + "created_at": "2013-09-30T13:46:02Z" }, - "public": true, - "path": "gitlab", - "path_with_namespace": "randx/gitlab", + "name": "Puppet", + "name_with_namespace": "Brightbox / Puppet", + "path": "puppet", + "path_with_namespace": "brightbox/puppet", "issues_enabled": true, "merge_requests_enabled": true, - "wall_enabled": true, + "wall_enabled": false, "wiki_enabled": true, - "snippets_enabled": true, - "created_at": "2012-05-30T12:49:20Z", - "last_activity_at": "2012-05-23T08:05:02Z" + "snippets_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", + "namespace": { + "created_at": "2013-09-30T13:46:02Z", + "description": "", + "id": 4, + "name": "Brightbox", + "owner_id": 1, + "path": "brightbox", + "updated_at": "2013-09-30T13:46:02Z" + } } ] ``` +#### List owned projects + +Get a list of projects owned by the authenticated user. + +``` +GET /projects/owned +``` + +#### List ALL projects + +Get a list of all GitLab projects (admin only). + +``` +GET /projects/all +``` + ### Get single project -Get a specific project, identified by project ID or NAME, which is owned by the authentication user. -Currently namespaced projects cannot retrieved by name. +Get a specific project, identified by project ID or NAMESPACE/PROJECT_NAME , which is owned by the authentication user. +If using namespaced projects call make sure that the NAMESPACE/PROJECT_NAME is URL-encoded, eg. `/api/v3/projects/diaspora%2Fdiaspora` (where `/` is represented by `%2F`). ``` GET /projects/:id @@ -72,33 +111,43 @@ GET /projects/:id Parameters: -+ `id` (required) - The ID or NAME of a project ++ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project ```json { - "id": 5, - "name": "gitlab", - "name_with_namespace": "GitLab / gitlabhq", + "id": 3, "description": null, - "default_branch": "api", + "default_branch": "master", + "public": false, + "visibility_level": 0, + "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", + "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", + "web_url": "http://example.com/diaspora/diaspora-project-site", "owner": { - "id": 1, - "username": "john_smith", - "email": "john@example.com", - "name": "John Smith", - "blocked": false, - "created_at": "2012-05-23T08:00:58Z" + "id": 3, + "name": "Diaspora", + "created_at": "2013-09-30T13: 46: 02Z" }, - "public": true, - "path": "gitlab", - "path_with_namespace": "randx/gitlab", + "name": "Diaspora Project Site", + "name_with_namespace": "Diaspora / Diaspora Project Site", + "path": "diaspora-project-site", + "path_with_namespace": "diaspora/diaspora-project-site", "issues_enabled": true, "merge_requests_enabled": true, - "wall_enabled": true, + "wall_enabled": false, "wiki_enabled": true, - "snippets_enabled": true, - "created_at": "2012-05-30T12:49:20Z", - "last_activity_at": "2012-05-23T08:05:02Z" + "snippets_enabled": false, + "created_at": "2013-09-30T13: 46: 02Z", + "last_activity_at": "2013-09-30T13: 46: 02Z", + "namespace": { + "created_at": "2013-09-30T13: 46: 02Z", + "description": "", + "id": 3, + "name": "Diaspora", + "owner_id": 1, + "path": "diaspora", + "updated_at": "2013-09-30T13: 46: 02Z" + } } ``` @@ -183,24 +232,14 @@ Parameters: + `name` (required) - new project name + `description` (optional) - short project description -+ `default_branch` (optional) - 'master' by default + `issues_enabled` (optional) + `wall_enabled` (optional) + `merge_requests_enabled` (optional) + `wiki_enabled` (optional) + `snippets_enabled` (optional) -+ `public` (optional) - -**Project access levels** - -The project access levels are defined in the `user_project.rb` class. Currently, these levels are recognized: - -``` - GUEST = 10 - REPORTER = 20 - DEVELOPER = 30 - MASTER = 40 -``` ++ `public` (optional) - if `true` same as setting visibility_level = 20 ++ `visibility_level` (optional) +* `import_url` (optional) ### Create project for user @@ -222,8 +261,21 @@ Parameters: + `merge_requests_enabled` (optional) + `wiki_enabled` (optional) + `snippets_enabled` (optional) -+ `public` (optional) ++ `public` (optional) - if `true` same as setting visibility_level = 20 ++ `visibility_level` (optional) + + +## Remove project + +Removes project with all resources(issues, merge requests etc) +``` +DELETE /projects/:id +``` + +Parameters: + ++ `id` (required) - The ID of a project ## Team members @@ -261,7 +313,7 @@ Parameters: "username": "john_smith", "email": "john@example.com", "name": "John Smith", - "blocked": false, + "state": "active", "created_at": "2012-05-23T08:00:58Z", "access_level": 40 } @@ -351,6 +403,10 @@ Parameters: { "id": 1, "url": "http://example.com/hook", + "project_id": 3, + "push_events": "true", + "issues_events": "true", + "merge_requests_events": "true", "created_at": "2012-10-12T17:04:47Z" } ``` @@ -368,6 +424,9 @@ Parameters: + `id` (required) - The ID or NAME of a project + `url` (required) - The hook URL ++ `push_events` - Trigger hook on push events ++ `issues_events` - Trigger hook on issues events ++ `merge_requests_events` - Trigger hook on merge_requests events ### Edit project hook @@ -383,6 +442,9 @@ Parameters: + `id` (required) - The ID or NAME of a project + `hook_id` (required) - The ID of a project hook + `url` (required) - The hook URL ++ `push_events` - Trigger hook on push events ++ `issues_events` - Trigger hook on issues events ++ `merge_requests_events` - Trigger hook on merge_requests events ### Delete project hook @@ -417,6 +479,55 @@ Parameters: + `id` (required) - The ID of the project +```json +[ + { + "name":"async", + "commit": { + "id":"a2b702edecdf41f07b42653eb1abe30ce98b9fca", + "parents": [{ + "id":"3f94fc7c85061973edc9906ae170cc269b07ca55" + }], + "tree": "c68537c6534a02cc2b176ca1549f4ffa190b58ee", + "message":"give caolan credit where it's due (up top)", + "author": { + "name":"Jeremy Ashkenas", + "email":"jashkenas@example.com" + }, + "committer": { + "name":"Jeremy Ashkenas", + "email":"jashkenas@example.com" + }, + "authored_date":"2010-12-08T21:28:50+00:00", + "committed_date":"2010-12-08T21:28:50+00:00" + }, + "protected":false + }, + { + "name": "gh-pages", + "commit": { + "id": "101c10a60019fe870d21868835f65c25d64968fc", + "parents": [{ + "id": "9c15d2e26945a665131af5d7b6d30a06ba338aaa" + }], + "tree": "fb5cc9d45da3014b17a876ad539976a0fb9b352a", + "message": "Underscore.js 1.5.2", + "author": { + "name": "Jeremy Ashkenas", + "email": "jashkenas@example.com" + }, + "committer": { + "name": "Jeremy Ashkenas", + "email": "jashkenas@example.com" + }, + "authored_date": "2013-09-07T12: 58: 21+00: 00", + "committed_date": "2013-09-07T12: 58: 21+00: 00" + }, + "protected": false + } +] + +``` ### List single branch diff --git a/doc/api/repositories.md b/doc/api/repositories.md index cb0626972e5a20720a3b53c3a638de54a42030b9..0160726300893a179c723586b73265c0f9502932 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -259,7 +259,12 @@ Parameters: "title": "Sanitize for network graph", "author_name": "randx", "author_email": "dmitriy.zaporozhets@gmail.com", - "created_at": "2012-09-20T09:06:12+03:00" + "created_at": "2012-09-20T09:06:12+03:00", + "committed_date": "2012-09-20T09:06:12+03:00", + "authored_date": "2012-09-20T09:06:12+03:00", + "parent_ids" : [ + "ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba" + ] } ``` @@ -343,9 +348,9 @@ Parameters: ``` -## Raw blob content +## Raw file content -Get the raw file contents for a file. +Get the raw file contents for a file by commit sha and path. ``` GET /projects/:id/repository/blobs/:sha @@ -356,3 +361,71 @@ Parameters: + `id` (required) - The ID of a project + `sha` (required) - The commit or branch name + `filepath` (required) - The path the file + + +## Raw blob content + +Get the raw file contents for a blob by blob sha. + +``` +GET /projects/:id/repository/raw_blobs/:sha +``` + +Parameters: + ++ `id` (required) - The ID of a project ++ `sha` (required) - The blob sha + + +## Get file archive + +Get a an archive of the repository + +``` +GET /projects/:id/repository/archive +``` + +Parameters: ++ `id` (required) - The ID of a project ++ `sha` (optional) - The commit sha to download defaults to the tip of the default branch + + +## Create new file in repository + +``` +POST /projects/:id/repository/files +``` + +Parameters: + ++ `file_path` (optional) - Full path to new file. Ex. lib/class.rb ++ `branch_name` (required) - The name of branch ++ `encoding` (optional) - 'text' or 'base64'. Text is default. ++ `content` (required) - File content ++ `commit_message` (required) - Commit message + +## Update existing file in repository + +``` +PUT /projects/:id/repository/files +``` + +Parameters: + ++ `file_path` (required) - Full path to file. Ex. lib/class.rb ++ `branch_name` (required) - The name of branch ++ `encoding` (optional) - 'text' or 'base64'. Text is default. ++ `content` (required) - New file content ++ `commit_message` (required) - Commit message + +## Delete existing file in repository + +``` +DELETE /projects/:id/repository/files +``` + +Parameters: + ++ `file_path` (required) - Full path to file. Ex. lib/class.rb ++ `branch_name` (required) - The name of branch ++ `commit_message` (required) - Commit message diff --git a/doc/api/session.md b/doc/api/session.md index 162d4c8bf78282c2b6003fe17bfd20f764289cd5..fd18d5c5e974da8f2d280549a96c725eca2b549f 100644 --- a/doc/api/session.md +++ b/doc/api/session.md @@ -26,6 +26,7 @@ __You can login with both GitLab and LDAP credentials now__ "skype": "", "linkedin": "", "twitter": "", + "website_url": "", "dark_scheme": false, "theme_id": 1, "is_admin": false, diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md index dca22c43f833fc0a903ce3a71873c1ba755adfff..355ce31c1263d66cb580ea7d36472738e9ce6374 100644 --- a/doc/api/system_hooks.md +++ b/doc/api/system_hooks.md @@ -1,5 +1,7 @@ All methods require admin authorization. +The url endpoint of the system hooks can be configured in [the admin area under hooks](/admin/hooks). + ## List system hooks Get list of system hooks @@ -12,6 +14,15 @@ Parameters: + **none** +```json +[ + { + "id":3, + "url":"http://example.com/hook", + "created_at":"2013-10-02T10:15:31Z" + } +] +``` ## Add new system hook hook @@ -34,6 +45,16 @@ Parameters: + `id` (required) - The ID of hook +```json +{ + "event_name":"project_create", + "name":"Ruby", + "path":"ruby", + "project_id":1, + "owner_name":"Someone", + "owner_email":"example@gitlabhq.com" +} +``` ## Delete system hook diff --git a/doc/api/user_teams.md b/doc/api/user_teams.md deleted file mode 100644 index cf467a54667d942331332b5079cbd6e3acfd51fb..0000000000000000000000000000000000000000 --- a/doc/api/user_teams.md +++ /dev/null @@ -1,209 +0,0 @@ -## User teams - -### List user teams - -Get a list of user teams viewable by the authenticated user. - -``` -GET /user_teams -``` - -```json -[ - { - id: 1, - name: "User team 1", - path: "user_team1", - owner_id: 1 - }, - { - id: 2, - name: "User team 2", - path: "user_team2", - owner_id: 1 - } -] -``` - - -### Get single user team - -Get a specific user team, identified by user team ID, which is viewable by the authenticated user. - -``` -GET /user_teams/:id -``` - -Parameters: - -+ `id` (required) - The ID of a user_team - -```json -{ - id: 1, - name: "User team 1", - path: "user_team1", - owner_id: 1 -} -``` - - -### Create user team - -Creates new user team owned by user. Available only for admins. - -``` -POST /user_teams -``` - -Parameters: - -+ `name` (required) - new user team name -+ `path` (required) - new user team internal name - - - -## User team members - -### List user team members - -Get a list of project team members. - -``` -GET /user_teams/:id/members -``` - -Parameters: - -+ `id` (required) - The ID of a user_team - - -### Get user team member - -Gets a user team member. - -``` -GET /user_teams/:id/members/:user_id -``` - -Parameters: - -+ `id` (required) - The ID of a user_team -+ `user_id` (required) - The ID of a user - -```json -{ - id: 2, - username: "john_doe", - email: "joh@doe.org", - name: "John Doe", - state: "active", - created_at: "2012-10-22T14:13:35Z", - access_level: 30 -} -``` - - -### Add user team member - -Adds a user to a user team. - -``` -POST /user_teams/:id/members -``` - -Parameters: - -+ `id` (required) - The ID of a user team -+ `user_id` (required) - The ID of a user to add -+ `access_level` (required) - Project access level - - -### Remove user team member - -Removes user from user team. - -``` -DELETE /user_teams/:id/members/:user_id -``` - -Parameters: - -+ `id` (required) - The ID of a user team -+ `user_id` (required) - The ID of a team member - -## User team projects - -### List user team projects - -Get a list of project team projects. - -``` -GET /user_teams/:id/projects -``` - -Parameters: - -+ `id` (required) - The ID of a user_team - - -### Get user team project - -Gets a user team project. - -``` -GET /user_teams/:id/projects/:project_id -``` - -Parameters: - -+ `id` (required) - The ID of a user_team -+ `project_id` (required) - The ID of a user - -```json -{ - id: 12, - name: "project1", - description: null, - default_branch: "develop", - public: false, - path: "project1", - path_with_namespace: "group1/project1", - issues_enabled: false, - merge_requests_enabled: true, - wall_enabled: true, - wiki_enabled: false, - created_at: "2013-03-11T12:59:08Z", - greatest_access_level: 30 -} -``` - - -### Add user team project - -Adds a project to a user team. - -``` -POST /user_teams/:id/projects -``` - -Parameters: - -+ `id` (required) - The ID of a user team -+ `project_id` (required) - The ID of a project to add -+ `greatest_access_level` (required) - Maximum project access level - - -### Remove user team project - -Removes project from user team. - -``` -DELETE /user_teams/:id/projects/:project_id -``` - -Parameters: - -+ `id` (required) - The ID of a user team -+ `project_id` (required) - The ID of a team project - diff --git a/doc/api/users.md b/doc/api/users.md index 50c0f560d870644aad2807bb959a6a5536c9616a..4098da72b3018d380f075290ca808a187a493976 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -20,6 +20,7 @@ GET /users "skype": "", "linkedin": "", "twitter": "", + "website_url": "", "extern_uid": "john.smith", "provider": "provider_name", "theme_id": 1, @@ -38,12 +39,14 @@ GET /users "skype": "", "linkedin": "", "twitter": "", + "website_url": "", "extern_uid": "jack.smith", "provider": "provider_name", "theme_id": 1, "color_scheme_id": 3, "is_admin": false, - "can_create_group": true + "can_create_group": true, + "can_create_project": true } ] ``` @@ -73,12 +76,14 @@ Parameters: "skype": "", "linkedin": "", "twitter": "", + "website_url": "", "extern_uid": "john.smith", "provider": "provider_name", "theme_id": 1, "color_scheme_id": 2, "is_admin": false, - "can_create_group": true + "can_create_group": true, + "can_create_project": true } ``` @@ -100,6 +105,7 @@ Parameters: + `skype` (optional) - Skype ID + `linkedin` (optional) - Linkedin + `twitter` (optional) - Twitter account ++ `website_url` (optional) - Website url + `projects_limit` (optional) - Number of projects user can create + `extern_uid` (optional) - External UID + `provider` (optional) - External provider name @@ -125,6 +131,7 @@ Parameters: + `skype` - Skype ID + `linkedin` - Linkedin + `twitter` - Twitter account ++ `website_url` - Website url + `projects_limit` - Limit projects each user can create + `extern_uid` - External UID + `provider` - External provider name @@ -172,6 +179,7 @@ GET /user "skype": "", "linkedin": "", "twitter": "", + "website_url": "", "theme_id": 1, "color_scheme_id": 2, "is_admin": false, diff --git a/doc/development/architecture.md b/doc/development/architecture.md new file mode 100644 index 0000000000000000000000000000000000000000..bf7e356cad1ea689ab3cb385e4361a4c9d155ab0 --- /dev/null +++ b/doc/development/architecture.md @@ -0,0 +1,149 @@ +# GitLab Architecture Overview +--- + +# Software delivery + +There are two editions of GitLab: [Enterprise Edition](https://www.gitlab.com/features/) (EE) and [Community Edition](http://gitlab.org/gitlab-ce/) (CE). GitLab CE is delivered via git from the [gitlabhq repository](https://gitlab.com/gitlab-org/gitlab-ce/tree/master). New versions of GitLab are released in stable branches and the master branch is for bleeding edge development. + +EE releases are available not long after CE releases. To obtain the GitLab EE there is a [repository at gitlab.com](https://gitlab.com/subscribers/gitlab-ee). For more information about the release process see the section 'New versions and upgrading' in the readme. + +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. + +# System Layout + +When referring to ~git in the picures it means the home directory of the git user which is typically /home/git. + +GitLab is primarily installed within the `/home/git` user home directory as `git` user. Within the home directory is where the gitlabhq server software resides as well as the repositories (though the repository location is configurable). The bare repositories are located in `/home/git/repositories`. GitLab is a ruby on rails application so the particulars of the inner workings can be learned by studying how a ruby on rails application works. To serve repositories over SSH there's an add-on application called gitlab-shell which is installed in `/home/git/gitlab-shell`. + +## Components + +![GitLab Diagram Overview](resources/gitlab_diagram_overview.png "GitLab Diagram Overview") + +A typical install of GitLab will be on Ubuntu Linux or RHEL/CentOS. It uses Nginx or Apache as a web front end to proxypass the Unicorn web server. Communication between Unicorn and the front end is usually HTTP but access via socket is also supported. The web front end accesses `/home/git/gitlab/public` bypassing the Unicorn server to serve static pages, uploads (e.g. avatar images or attachments), and precompiled assets. GitLab serves web pages and a [GitLab API](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/api) using the Unicorn web server. It uses Sidekiq as a job queue which, in turn, uses redis as a non-persistent database backend for job information, meta data, and incomming jobs. The GitLab web app uses MySQL or PostgreSQL for persistent database information (e.g. users, permissions, issues, other meta data). GitLab stores the bare git repositories it serves in `/home/git/repositories` by default. It also keeps default branch and hook information with the bare repository. `/home/git/gitlab-satellites` keeps checked out repositories when performing actions such as a merge request, editing files in the web interface, etc. The satellite repository is used by the web interface for editing repositories and the wiki which is also a git repository. When serving repositories over HTTP/HTTPS GitLab utilizes the GitLab API to resolve authorization and access as well as serving git objects. + +The add-on component gitlab-shell serves repositories over SSH. It manages the SSH keys within `/home/git/.ssh/authorized_keys` which should not be manually edited. gitlab-shell accesses the bare repositories directly to serve git objects and communicates with redis to submit jobs to Sidekiq for GitLab to process. gitlab-shell queries the GitLab API to determine authorization and access. + +## Installation Folder Summary + +To summarize here's the [directory structure of the `git` user home directory](../install/structure.md). + + +## Processes + + ps aux | grep '^git' + +GitLab has several components to operate. As a system user (i.e. any user that is not the `git` user) it requires a persistent database (MySQL/PostreSQL) and redis database. It also uses Apache httpd or nginx to proxypass Unicorn. As the `git` user it starts Sidekiq and Unicorn (a simple ruby HTTP server running on port `8080` by default). Under the gitlab user there are normally 4 processes: `unicorn_rails master` (1 process), `unicorn_rails worker` (2 processes), `sidekiq` (1 process). + +## Repository access + +Repositories get accessed via HTTP or SSH. HTTP cloning/push/pull utilizes the GitLab API and SSH cloning is handled by gitlab-shell (previously explained). + +# Troubleshooting + +See the README for more information. + +## Init scripts of the services + +The GitLab init script starts and stops Unicorn and Sidekiq. + +``` +/etc/init.d/gitlab +Usage: service gitlab {start|stop|restart|reload|status} +``` + +Redis (key-value store/non-persistent database) + +``` +/etc/init.d/redis +Usage: /etc/init.d/redis {start|stop|status|restart|condrestart|try-restart} +``` + +SSH daemon + +``` +/etc/init.d/sshd +Usage: /etc/init.d/sshd {start|stop|restart|reload|force-reload|condrestart|try-restart|status} +``` + +Web server (one of the following) + +``` +/etc/init.d/httpd +Usage: httpd {start|stop|restart|condrestart|try-restart|force-reload|reload|status|fullstatus|graceful|help|configtest} + +$ /etc/init.d/nginx +Usage: nginx {start|stop|restart|reload|force-reload|status|configtest} +``` + +Persistent database (one of the following) + +``` +/etc/init.d/mysqld +Usage: /etc/init.d/mysqld {start|stop|status|restart|condrestart|try-restart|reload|force-reload} + +$ /etc/init.d/postgresql +Usage: /etc/init.d/postgresql {start|stop|restart|reload|force-reload|status} [version ..] +``` + +## Log locations of the services + +Note: `/home/git/` is shorthand for `/home/git`. + +gitlabhq (includes Unicorn and Sidekiq logs) + +* `/home/git/gitlab/log/` contains `application.log`, `production.log`, `sidekiq.log`, `unicorn.stdout.log`, `githost.log`, `satellites.log`, and `unicorn.stderr.log` normally. + +gitlab-shell + +* `/home/git/gitlab-shell/gitlab-shell.log` + +ssh + +* `/var/log/auth.log` auth log (on Ubuntu). +* `/var/log/secure` auth log (on RHEL). + +nginx + +* `/var/log/nginx/` contains error and access logs. + +Apache httpd + +* [Explanation of apache logs](http://httpd.apache.org/docs/2.2/logs.html). +* `/var/log/apache2/` contains error and output logs (on Ubuntu). +* `/var/log/httpd/` contains error and output logs (on RHEL). + +redis + +* `/var/log/redis/redis.log` there are also logrotated logs there. + +PostgreSQL + +* `/var/log/postgresql/*` + +MySQL + +* `/var/log/mysql/*` +* `/var/log/mysql.*` + +## GitLab specific config files + +GitLab has configuration files located in `/home/git/gitlab/config/*`. Commonly referenced config files include: + +* `gitlab.yml` - GitLab configuration. +* `unicorn.rb` - Unicorn web server settings. +* `database.yml` - Database connection settings. + +gitlab-shell has a configuration file at `/home/git/gitlab-shell/config.yml`. + +## Maintenance Tasks + +[GitLab](https://gitlab.com/gitlab-org/gitlab-ce/tree/master) provides rake tasks with which you see version information and run a quick check on your configuration to ensure it is configured properly within the application. See [maintenance rake tasks](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/raketasks/maintenance.md). In a nutshell, do the following: + +``` +sudo -i -u git +cd gitlab +bundle exec rake gitlab:env:info RAILS_ENV=production +bundle exec rake gitlab:check RAILS_ENV=production +``` + +Note: It is recommended to log into the `git` user using `sudo -i -u git` or `sudo su - git`. While the sudo commands provided by gitlabhq work in Ubuntu they do not always work in RHEL. diff --git a/doc/development/resources/gitlab_diagram_overview.odg b/doc/development/resources/gitlab_diagram_overview.odg new file mode 100644 index 0000000000000000000000000000000000000000..b7e02f8fa7836794460e648c71f8cc50a144ada3 Binary files /dev/null and b/doc/development/resources/gitlab_diagram_overview.odg differ diff --git a/doc/development/resources/gitlab_diagram_overview.png b/doc/development/resources/gitlab_diagram_overview.png new file mode 100644 index 0000000000000000000000000000000000000000..b5831cf0a4c2678afef2654855210be2036492df Binary files /dev/null and b/doc/development/resources/gitlab_diagram_overview.png differ diff --git a/doc/install/databases.md b/doc/install/databases.md index 6477e1c967c1531485e94fa3fe88f2e14122ddf8..481a698a8c8b22d734392a237cfb078a9d2a756f 100644 --- a/doc/install/databases.md +++ b/doc/install/databases.md @@ -16,7 +16,7 @@ GitLab supports the following databases: # Secure your installation. sudo mysql_secure_installation - + # Login to MySQL mysql -u root -p @@ -25,19 +25,19 @@ GitLab supports the following databases: # Create a user for GitLab # do not type the 'mysql>', this is part of the prompt # change $password in the command below to a real password you pick - mysql> CREATE USER 'gitlab'@'localhost' IDENTIFIED BY '$password'; + mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password'; # Create the GitLab production database mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`; # Grant the GitLab user necessary permissions on the table. - mysql> GRANT SELECT, LOCK TABLES, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON `gitlabhq_production`.* TO 'gitlab'@'localhost'; + mysql> GRANT SELECT, LOCK TABLES, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON `gitlabhq_production`.* TO 'git'@'localhost'; # Quit the database session mysql> \q # Try connecting to the new database with the new user - sudo -u git -H mysql -u gitlab -p -D gitlabhq_production + sudo -u git -H mysql -u git -p -D gitlabhq_production # Type the password you replaced $password with earlier @@ -52,13 +52,13 @@ GitLab supports the following databases: ## PostgreSQL # Install the database packages - sudo apt-get install -y postgresql-9.1 libpq-dev + sudo apt-get install -y postgresql-9.1 postgresql-client libpq-dev # Login to PostgreSQL sudo -u postgres psql -d template1 - # Create a user for GitLab. (change $password to a real password) - template1=# CREATE USER git WITH PASSWORD '$password'; + # Create a user for GitLab. + template1=# CREATE USER git; # Create the GitLab production database & grant all privileges on database template1=# CREATE DATABASE gitlabhq_production OWNER git; diff --git a/doc/install/installation.md b/doc/install/installation.md index 03ea5fbb28f6ac248078c1a9255843ac523a73c0..cc2641f6f37cf21214d6e3c9737c514768de03be 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -1,20 +1,22 @@ # Select Version to Install Make sure you view this installation guide 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). +this should be the highest numbered stable branch (example shown below). -![capture](https://f.cloud.github.com/assets/1192780/564911/2f9f3e1e-c5b7-11e2-9f89-98e527d1adec.png) +![capture](http://i.imgur.com/d2AlIVj.png) If this is unclear check the [GitLab Blog](http://blog.gitlab.org/) for installation guide links by version. # Important notes -This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [`doc/install/requirements.md`](./requirements.md) for hardware and operating system requirements. +This guide is long because it covers many cases and includes all commands you need, this is [one of the few installation scripts that actually works out of the box](https://twitter.com/robinvdvleuten/status/424163226532986880). -This is the official installation guide to set up a production server. To set up a **development installation** or for many other installation options please consult [the installation section in the readme](https://github.com/gitlabhq/gitlabhq#installation). +This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [doc/install/requirements.md](./requirements.md) for hardware and operating system requirements. An unofficial guide for RHEL/CentOS can be found in the [GitLab recipes repository](https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/install/centos). + +This is the official installation guide to set up a production server. To set up a **development installation** or for many other installation options please see [the installation section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#installation). The following steps have been known to work. Please **use caution when you deviate** from this guide. Make sure you don't violate any assumptions GitLab makes about its environment. For example many people run into permission problems because they changed the location of directories or run services as the wrong user. -If you find a bug/error in this guide please **submit a pull request** following the [contributing guide](../../CONTRIBUTING.md). +If you find a bug/error in this guide please **submit a merge request** following the [contributing guide](../../CONTRIBUTING.md). - - - @@ -52,38 +54,50 @@ If you are not familiar with vim please skip this and keep using the default edi Install the required packages: - sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl git-core openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev + 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 -Make sure you have the right version of Python installed. + # For reStructuredText markup language support install required package: + sudo apt-get install -y python-docutils - # Install Python - sudo apt-get install -y python +Make sure you have the right version of Git installed - # Make sure that Python is 2.5+ (3.x is not supported at the moment) - python --version + # Install Git + sudo apt-get install -y git-core - # If it's Python 3 you might need to install Python 2 separately - sudo apt-get install python2.7 + # Make sure Git is version 1.7.10 or higher, for example 1.7.12 or 1.8.4 + git --version - # Make sure you can access Python via python2 - python2 --version +Is the system packaged Git too old? Remove it and compile from source. - # If you get a "command not found" error create a link to the python binary - sudo ln -s /usr/bin/python /usr/bin/python2 + # Remove packaged Git + sudo apt-get remove git-core - # For reStructuredText markup language support install required package: - sudo apt-get install python-docutils + # Install dependencies + sudo apt-get install -y libcurl4-openssl-dev libexpat1-dev gettext libz-dev libssl-dev build-essential + + # Download and compile from source + cd /tmp + curl --progress https://git-core.googlecode.com/files/git-1.8.5.2.tar.gz | tar xz + cd git-1.8.5.2/ + make prefix=/usr/local all + + # Install into /usr/local/bin + sudo make prefix=/usr/local install + + # When editing config/gitlab.yml (Step 6), change the git bin_path to /usr/local/bin/git **Note:** In order to receive mail notifications, make sure to install a mail server. By default, Debian is shipped with exim4 whereas Ubuntu does not ship with one. The recommended mail server is postfix and you can install it with: - sudo apt-get install -y postfix + sudo apt-get install -y postfix Then select 'Internet Site' and press enter to confirm the hostname. # 2. Ruby +The use of ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. Version managers are not supported and we stronly advise everyone to follow the instructions below to use a system ruby. + Remove the old Ruby 1.8 if present sudo apt-get remove ruby1.8 @@ -91,9 +105,9 @@ Remove the old Ruby 1.8 if present Download Ruby and compile it: mkdir /tmp/ruby && cd /tmp/ruby - curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.0/ruby-2.0.0-p247.tar.gz | tar xz - cd ruby-2.0.0-p247 - ./configure + curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.0/ruby-2.0.0-p353.tar.gz | tar xz + cd ruby-2.0.0-p353 + ./configure --disable-install-rdoc make sudo make install @@ -117,13 +131,10 @@ GitLab Shell is an ssh access and repository management software developed speci cd /home/git # Clone gitlab shell - sudo -u git -H git clone https://github.com/gitlabhq/gitlab-shell.git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-shell.git -b v1.8.0 cd gitlab-shell - # switch to right version - sudo -u git -H git checkout v1.7.1 - sudo -u git -H cp config.yml.example config.yml # Edit config and replace gitlab_url @@ -147,16 +158,13 @@ To setup the MySQL/PostgreSQL database and dependencies please see [`doc/install ## Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://github.com/gitlabhq/gitlabhq.git gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 6-5-stable gitlab # Go to gitlab dir cd /home/git/gitlab - # Checkout to stable release - sudo -u git -H git checkout 6-1-stable - **Note:** -You can change `6-1-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +You can change `6-5-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ## Configure it @@ -167,6 +175,8 @@ You can change `6-1-stable` to `master` if you want the *bleeding edge* version, # Make sure to change "localhost" to the fully-qualified domain name of your # host serving GitLab where necessary + # + # If you installed Git from source, change the git bin_path to /usr/local/bin/git sudo -u git -H editor config/gitlab.yml # Make sure GitLab can write to the log/ and tmp/ directories @@ -198,10 +208,6 @@ You can change `6-1-stable` to `master` if you want the *bleeding edge* version, # Copy the example Rack attack config sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb - # Enable rack attack middleware - # Find and uncomment the line 'config.middleware.use Rack::Attack' - sudo -u git -H editor config/application.rb - # Configure Git global settings for git user, useful when editing via web # Edit user.email according to what is set in gitlab.yml sudo -u git -H git config --global user.name "GitLab" @@ -216,19 +222,19 @@ Make sure to edit both `gitlab.yml` and `unicorn.rb` to match your setup. # Mysql sudo -u git cp config/database.yml.mysql config/database.yml - or - - # PostgreSQL - sudo -u git cp config/database.yml.postgresql config/database.yml - # Make sure to update username/password in config/database.yml. # You only need to adapt the production settings (first part). # If you followed the database guide then please do as follows: - # Change 'root' to 'gitlab' # Change 'secure password' with the value you have given to $password # You can keep the double quotes around the password sudo -u git -H editor config/database.yml - + + or + + # PostgreSQL + sudo -u git cp config/database.yml.postgresql config/database.yml + + # Make config/database.yml readable to git only sudo -u git -H chmod o-rwx config/database.yml @@ -236,8 +242,6 @@ Make sure to edit both `gitlab.yml` and `unicorn.rb` to match your setup. cd /home/git/gitlab - sudo gem install charlock_holmes --version '0.6.9.4' - # For MySQL (note, the option says "without ... postgres") sudo -u git -H bundle install --deployment --without development test postgres aws @@ -249,7 +253,7 @@ Make sure to edit both `gitlab.yml` and `unicorn.rb` to match your setup. sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production - # Type 'yes' to create the database. + # Type 'yes' to create the database tables. # When done you see 'Administrator account created:' @@ -259,12 +263,20 @@ Make sure to edit both `gitlab.yml` and `unicorn.rb` to match your setup. Download the init script (will be /etc/init.d/gitlab): sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab - sudo chmod +x /etc/init.d/gitlab + +And if you are installing with a non-default folder or user copy and edit the defaults file: + + sudo cp lib/support/init.d/gitlab.default.example /etc/default/gitlab + +If you installed gitlab in another directory or as a user other than the default you should change these settings in /etc/default/gitlab. Do not edit /etc/init.d/gitlab as it will be changed on upgrade. Make GitLab start on boot: sudo update-rc.d gitlab defaults 21 +## Set up logrotate + + sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab ## Check Application Status @@ -278,21 +290,17 @@ Check if GitLab and its environment are configured correctly: # or sudo /etc/init.d/gitlab restart -## Double-check Application Status -To make sure you didn't miss anything run a more thorough check with: +## Compile assets - sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production - -If all items are green, then congratulations on successfully installing GitLab! -However there are still a few steps left. + sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production # 7. Nginx **Note:** Nginx is the officially supported web server for GitLab. If you cannot or do not want to use Nginx as your web server, have a look at the -[GitLab recipes](https://github.com/gitlabhq/gitlab-recipes). +[GitLab recipes](https://gitlab.com/gitlab-org/gitlab-recipes/). ## Installation sudo apt-get install -y nginx @@ -317,7 +325,17 @@ Make sure to edit the config file to match your setup: # Done! -Visit YOUR_SERVER for your first GitLab login. +## Double-check Application Status + +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 on successfully installing GitLab! + +## 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: admin@local.host @@ -344,6 +362,12 @@ a different host, you can configure its connection string via the # example production: redis://redis.example.tld:6379 +If you want to connect the Redis server via socket, then use the "unix:" URL scheme +and the path to the Redis socket file in the `config/resque.yml` file. + + # example + production: unix:/path/to/redis/socket + ## Custom SSH Connection If you are running SSH on a non-standard port, you must change the gitlab user's SSH config. @@ -371,10 +395,10 @@ These steps are fairly general and you will need to figure out the exact details * Stop GitLab `sudo service gitlab stop` -* Add provider specific configuration options to your `config/gitlab.yml` (you can use the [auth providers section of the example config](https://github.com/gitlabhq/gitlabhq/blob/master/config/gitlab.yml.example) as a reference) +* Add provider specific configuration options to your `config/gitlab.yml` (you can use the [auth providers section of the example config](https://gitlab.com/gitlab-org/gitlab-ce/blob/masterconfig/gitlab.yml.example) as a reference) -* Add the gem to your [Gemfile](https://github.com/gitlabhq/gitlabhq/blob/master/Gemfile) - `gem "omniauth-your-auth-provider"` +* Add the gem to your [Gemfile](https://gitlab.com/gitlab-org/gitlab-ce/blob/masterGemfile) + `gem "omniauth-your-auth-provider"` * If you're using MySQL, install the new Omniauth provider gem by running the following command: `sudo -u git -H bundle install --without development test postgres --path vendor/bundle --no-deployment` @@ -390,5 +414,5 @@ These steps are fairly general and you will need to figure out the exact details ### Examples If you have successfully set up a provider that is not shipped with GitLab itself, please let us know. -You can help others by reporting successful configurations and probably share a few insights or provide warnings for common errors or pitfalls by sharing your experience [in the public Wiki](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Working-Custom-Omniauth-Provider-Configurations). +You can help others by reporting successful configurations and probably share a few insights or provide warnings for common errors or pitfalls by sharing your experience [in the public Wiki](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Custom-omniauth-provider-configurations). While we can't officially support every possible auth mechanism out there, we'd like to at least help those with special needs. diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 1dba04f4237033c498406e2d05d5a8f7c75ee4c7..ea172733b112035d49aecc8cdbeb94a504bf8f7a 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -1,66 +1,80 @@ # Operating Systems -## Linux +GitLab is developed for the Linux operating system. For the installations options and instructions please see [the installation section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#installation). -GitLab is developed for the Linux operating system. - -GitLab officially supports (recent versions of) these Linux distributions: +## GitLab officially supports - Ubuntu Linux - Debian/GNU Linux -It should also work on (though they are not officially supported): +## GitLab.com offers paid support for -- Arch +- Red Hat Enterprise Linux (RHEL) - CentOS +- Oracle Linux + +## Not officially supported are + +- Arch Linux - Fedora - Gentoo -- RedHat -## Other Unix Systems +But on the above distributions it is pretty easy to install GitLab yourself. -There is nothing that prevents GitLab from running on other Unix operating -systems. This means you may get it to work on systems running FreeBSD or OS X. -**If you want to try, please proceed with caution!** +## Unsupported Unix Systems -## Windows +There is nothing that prevents GitLab from running on other Unix operating systems. +This means you may get it to work on systems running FreeBSD or OS X. +If you want to do this, please be aware it could be a lot of work. +Please consider using a virtual machine to run GitLab. -GitLab does **not** run on Windows and we have no plans of supporting it in the -near future. Please consider using a virtual machine to run GitLab. +## Other operating systems such as Windows +GitLab does **not** run on Windows and we have no plans of supporting it in the near future. +Please consider using a virtual machine to run GitLab. -# Rubies -GitLab requires Ruby (MRI) 1.9.3 and several Gems with native components. -While it is generally possible to use other Rubies (like -[JRuby](http://jruby.org/) or [Rubinius](http://rubini.us/)) it might require -some work on your part. +# Ruby versions + +GitLab requires Ruby (MRI) 1.9.3 or 2.0+. +You will have to use the standard MRI implementation of Ruby. +We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/)) but GitLab needs several Gems that have native extensions. # Hardware requirements ## CPU -We recommend a processor with **4 cores**. At a minimum you need a processor with 2 cores to responsively run an unmodified installation. +- 1 core works for under 100 users but the responsiveness might suffer +- **2 cores** is the **recommended** number of cores and supports up to 100 users +- 4 cores supports up to 1,000 users +- 8 cores supports up to 10,000 users ## Memory - 512MB is too little memory, GitLab will be very slow and you will need 250MB of swap -- 768MB is the minimal memory size and supports up to 100 users -- **1GB** is the **recommended** memory size and supports up to 1,000 users -- 1.5GB supports up to 10,000 users +- 768MB is the minimal memory size but we advise against this +- 1GB supports up to 100 users (with individual repositories under 250MB, otherwise git memory usage necessitates using swap space) +- **2GB** is the **recommended** memory size and supports up to 1,000 users +- 4GB supports up to 10,000 users ## 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 your all repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo. 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. +free space as your all 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. -# Installation troubles and reporting success or failure +# Supported webbrowsers -If you have troubles installing GitLab following the [official installation guide](installation.md) -or want to share your experience installing GitLab on a not officially supported -platform, please follow the the [contribution guide](/CONTRIBUTING.md). +- Chrome (Latest stable version) +- Firefox (Latest released version) +- Safari 7+ (Know problem: required fields in html5 do not work) +- Opera (Latest released version) +- IE 10+ diff --git a/doc/install/structure.md b/doc/install/structure.md index f580ea159a2a461d5b7b852229caee786215c1ea..67ca18953748711aa05d06046a2fbd5ce41e64e6 100644 --- a/doc/install/structure.md +++ b/doc/install/structure.md @@ -10,18 +10,12 @@ This is the directory structure you will end up with following the instructions | |-- gitlab-shell | |-- repositories +* `/home/git/.ssh` - contains openssh settings. Specifically the `authorized_keys` file managed by gitlab-shell. +* `/home/git/gitlab` - GitLab core software. +* `/home/git/gitlab-satellites` - checked out repositories for merge requests and file editing from web UI. This can be treated as a temporary files directory. +* `/home/git/gitlab-shell` - Core add-on component of gitlab. Maintains SSH cloning and other functionality. +* `/home/git/repositories` - bare repositories for all projects organized by namespace. This is where the git repositories which are pushed/pulled are maintained for all projects. **This area is critical data for projects. [Keep a backup](../raketasks/backup_restore.md)** -**/home/git/.ssh** +*Note: the default locations for gitlab-satellites and repositories can be configured in `config/gitlab.yml` of gitlab and `config.yml` of gitlab-shell.* -**/home/git/gitlab** - This is where GitLab lives. - -**/home/git/gitlab-satellites** - Contains a copy of all repositories with a working tree. - It's used for merge requests, editing files, etc. - -**/home/git/repositories** - Holds all your repositories in bare format. - This is the place Git uses when you pull/push to your projects. - -You can change them in your `config/gitlab.yml` file. +To see a more in-depth overview see the [GitLab architecture doc](../development/architecture.md). diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md new file mode 100644 index 0000000000000000000000000000000000000000..02eadfd410acd18242fbc80cf9baebeebca547b5 --- /dev/null +++ b/doc/integration/external-issue-tracker.md @@ -0,0 +1,7 @@ +GitLab has a great issue tracker but you can also use an external issue tracker such as JIRA or Redmine. This is something that you can turn on per GitLab project. If for example you configure JIRA it provides the following functionality: + +- the 'Issues' link on the GitLab project pages takes you to the appropriate JIRA issue index; +- clicking 'New issue' on the project dashboard creates a new JIRA issue; +- textual references to PROJECT-1234 in comments, commit messages get turned into HTML links to the corresponding JIRA issue. + +![jira screenshot](jira-intergration-points.png) diff --git a/doc/integration/jira-integration-points.png b/doc/integration/jira-integration-points.png new file mode 100644 index 0000000000000000000000000000000000000000..0692a7b458a3eef7486c0c665b16eb4222cfd11a Binary files /dev/null and b/doc/integration/jira-integration-points.png differ diff --git a/doc/legal/corporate_contributor_license_agreement.md b/doc/legal/corporate_contributor_license_agreement.md new file mode 100644 index 0000000000000000000000000000000000000000..bbc274f3b0cc76cdeafce2449889a9c067e1c6cf --- /dev/null +++ b/doc/legal/corporate_contributor_license_agreement.md @@ -0,0 +1,25 @@ +You accept and agree to the following terms and conditions for Your present and future Contributions submitted to GitLab.com. Except for the license granted herein to GitLab.com and recipients of software distributed by GitLab.com, You reserve all right, title, and interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with GitLab.com. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean the code, documentation or other original works of authorship expressly identified in Schedule B, as well as any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to GitLab.com for inclusion in, or documentation of, any of the products owned or managed by GitLab.com (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab.com or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab.com for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab.com and to recipients of software distributed by GitLab.com a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab.com and to recipients of software distributed by GitLab.com a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. + +4. You represent that You are legally entitled to grant the above license. You represent further that each employee of the Corporation designated on Schedule A below (or in a subsequent written modification to that Schedule) is authorized to submit Contributions on behalf of the Corporation. + +5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). + +6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, You may submit it to GitLab.com separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]". + +8. It is your responsibility to notify GitLab.com when any change is required to the list of designated employees authorized to submit Contributions on behalf of the Corporation, or to the Corporation's Point of Contact with GitLab.com. + +--------------------------------------- + +This text is licensed under the [Creative Commons Attribution 3.0 License](http://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office. diff --git a/doc/legal/individual_contributor_license_agreement.md b/doc/legal/individual_contributor_license_agreement.md new file mode 100644 index 0000000000000000000000000000000000000000..eaf5812ca4c5a2cba421df41b946f757bc9ec6df --- /dev/null +++ b/doc/legal/individual_contributor_license_agreement.md @@ -0,0 +1,25 @@ +You accept and agree to the following terms and conditions for Your present and future Contributions submitted to GitLab.com. Except for the license granted herein to GitLab.com and recipients of software distributed by GitLab.com, You reserve all right, title, and interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with GitLab.com. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to GitLab.com for inclusion in, or documentation of, any of the products owned or managed by GitLab.com (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab.com or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab.com for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab.com and to recipients of software distributed by GitLab.com a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab.com and to recipients of software distributed by GitLab.com a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. + +4. You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to GitLab.com, or that your employer has executed a separate Corporate CLA with GitLab.com. + +5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions. + +6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, You may submit it to GitLab.com separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [[]named here]". + +8. You agree to notify GitLab.com of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect. + +--------------------------------------- + +This text is licensed under the [Creative Commons Attribution 3.0 License](http://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office. diff --git a/doc/make_release.md b/doc/make_release.md deleted file mode 100644 index 5be0d5980a175c7468a964d3fa50cdb2b75a7a8b..0000000000000000000000000000000000000000 --- a/doc/make_release.md +++ /dev/null @@ -1,56 +0,0 @@ -# Things to do when creating new release -NOTE: This is a developer guide. If you are trying to install GitLab see the latest stable [installation guide](install/installation.md) and if you are trying to upgrade, see the [upgrade guides](update). -## Install guide up to date? - -* References correct GitLab branch `x-x-stable` and correct GitLab shell tag? - -## Make upgrade guide - -### From x.x to x.x - -#### 0. Any major changes? Database updates? Web server change? File structure changes? - -#### 1. Make backup - -#### 2. Stop server - -#### 3. Do users need to update dependencies like `git`? - -#### 4. Get latest code - -#### 5. Does GitLab shell need to be updated? - -#### 6. Install libs, migrations, etc. - -#### 7. Any config files updated since last release? - -Check if any of these changed since last release (~22nd of last month depending on when last release branch was created): - -* https://github.com/gitlabhq/gitlabhq/commits/master/lib/support/nginx/gitlab -* https://github.com/gitlabhq/gitlab-shell/commits/master/config.yml.example -* https://github.com/gitlabhq/gitlabhq/commits/master/config/gitlab.yml.example -* https://github.com/gitlabhq/gitlabhq/commits/master/config/unicorn.rb.example -* https://github.com/gitlabhq/gitlabhq/commits/master/config/database.yml.mysql -* https://github.com/gitlabhq/gitlabhq/commits/master/config/database.yml.postgresql - -#### 8. Need to update init script? - -Check if changed since last release (~22nd of last month depending on when last release branch was created): https://github.com/gitlabhq/gitlabhq/commits/master/lib/support/init.d/gitlab - -#### 9. Start application - -#### 10. Check application status - -## Make sure code status is good - -* [![build status](http://ci.gitlab.org/projects/1/status.png?ref=master)](http://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch) - -* [![build status](https://secure.travis-ci.org/gitlabhq/gitlabhq.png)](https://travis-ci.org/gitlabhq/gitlabhq) on travis-ci.org (master branch) - -* [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.png)](https://codeclimate.com/github/gitlabhq/gitlabhq) - -* [![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.png)](https://gemnasium.com/gitlabhq/gitlabhq) this button can be yellow (small updates are available) but must not be red (a security fix or an important update is available) - -* [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq) - -## Make release branch diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index a84222f9fc23106ea33944dd8b1aa1a0b4365183..bfb93a4701c02f574ccfb9b630f6139024b4f9e2 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -187,7 +187,7 @@ GFM will recognize the following: * !123 : for merge requests * $123 : for snippets * 1234567 : for commits -* [file](path/to/file) : for file references +* \[file\](path/to/file) : for file references <a name="standard"/> @@ -326,12 +326,12 @@ Some text to show that the reference links can follow later. Here's our logo (hover to see the title text): Inline-style: - ![alt text](/assets/logo-white.png "Logo Title Text 1") + ![alt text](assets/logo-white.png) Reference-style: - ![alt text][logo] + ![alt text1][logo] - [logo]: /assets/logo-white.png "Logo Title Text 2" + [logo]: assets/logo-white.png Here's our logo (hover to see the title text): diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index d2da64f3d3c070531d68c0f88c98c7812cb963c7..bdff6ad5da8f07d4a4436f4237592d18bbbd11cf 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -78,3 +78,18 @@ Restoring repositories: - Restoring repository abcd... [DONE] Deleting tmp directories...[DONE] ``` + +### Configure cron to make daily backups + +``` +cd /home/git/gitlab +sudo -u git -H editor config/gitlab.yml # Enable keep_time in the backup section to automatically delete old backups +sudo -u git crontab -e # Edit the crontab for the git user +``` + +Add the following lines at the bottom: + +``` +# Create a full backup of the GitLab repositories and SQL database every day at 2am +0 2 * * * cd /home/git/gitlab && PATH=/usr/local/bin:/usr/bin:/bin bundle exec rake gitlab:backup:create RAILS_ENV=production +``` diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md index 8fa2ed1311cded768031c0dd76aa0bce7855c5fb..9884c9c0fe3215e06b7912a88a99be0c87aa7c5e 100644 --- a/doc/raketasks/user_management.md +++ b/doc/raketasks/user_management.md @@ -1,6 +1,6 @@ ### Add user as a developer to all projects -``` +```bash bundle exec rake gitlab:import:user_to_projects[username@domain.tld] ``` @@ -11,6 +11,6 @@ Notes: * admin users are added as masters -``` +```bash bundle exec rake gitlab:import:all_users_to_all_projects ``` diff --git a/doc/release/monthly.md b/doc/release/monthly.md new file mode 100644 index 0000000000000000000000000000000000000000..62bb0f7b40a3cd9cddfb793ab3140fa06f4f1c58 --- /dev/null +++ b/doc/release/monthly.md @@ -0,0 +1,76 @@ +# Things to do when creating new monthly minor or major release +NOTE: This is a guide for GitLab developers. If you are trying to install GitLab see the latest stable [installation guide](install/installation.md) and if you are trying to upgrade, see the [upgrade guides](update). + +## Install guide up to date? + +* References correct GitLab branch `x-x-stable` and correct GitLab shell tag? + +## Make upgrade guide + +### From x.x to x.x + +#### 0. Any major changes? Database updates? Web server change? File structure changes? + +#### 1. Make backup + +#### 2. Stop server + +#### 3. Do users need to update dependencies like `git`? + +#### 4. Get latest code + +#### 5. Does GitLab shell need to be updated? + +#### 6. Install libs, migrations, etc. + +#### 7. Any config files updated since last release? + +Check if any of these changed since last release (~22nd of last month depending on when last release branch was created): + +* https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/nginx/gitlab +* https://gitlab.com/gitlab-org/gitlab-shell/commits/master/config.yml.example +* https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/gitlab.yml.example +* https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/unicorn.rb.example +* https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/database.yml.mysql +* https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/database.yml.postgresql + +#### 8. Need to update init script? + +Check if changed since last release (~22nd of last month depending on when last release branch was created): https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/init.d/gitlab + +#### 9. Start application + +#### 10. Check application status + +## Make sure the code quality indicatiors are good + +* [![build status](http://ci.gitlab.org/projects/1/status.png?ref=master)](http://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch) + +* [![build status](https://secure.travis-ci.org/gitlabhq/gitlabhq.png)](https://travis-ci.org/gitlabhq/gitlabhq) on travis-ci.org (master branch) + +* [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.png)](https://codeclimate.com/github/gitlabhq/gitlabhq) + +* [![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.png)](https://gemnasium.com/gitlabhq/gitlabhq) this button can be yellow (small updates are available) but must not be red (a security fix or an important update is available) + +* [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq) + +## Make a release branch + +After making the release branch new commits are cherry-picked from master. When the release gets closer we get more selective what is cherry-picked. The days of the month are approximately as follows: + +* 17th: feature freeze (stop merging new features in master) +* 18th: UI freeze (stop merging changes to the user interface) +* 19th: code freeze (stop merging non-essential code improvements) +* 20th: release candidate 1 (VERSION x.x.0.pre, tag and tweet about x.x.0.rc1) +* 21st: optional release candidate 2 (x.x.0.rc2, only if rc1 had problems) +* 22nd: release (VERSION x.x.0, create x-x-stable branch, tag, blog and tweet) +* 23nd: optional patch releases (x.x.1, x.x.2, etc., only if there are serious problems) +* 24-end of month: release Enterprise Edition and upgrade GitLab Cloud +* 1-7th: official merge window (see contributing guide) +* 8-16th: bugfixes and sponsored features + +# Write a blog post + +* Mention what GitLab is on the second line: GitLab is open source software to collaborate on code. +* Select and thank the the Most Valuable Person (MVP) of this release. +* Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible. diff --git a/doc/release/security.md b/doc/release/security.md new file mode 100644 index 0000000000000000000000000000000000000000..7ec3991de859680d953caf07a6c3779d8a3b0895 --- /dev/null +++ b/doc/release/security.md @@ -0,0 +1,76 @@ +# Things to do when doing an out-of-bound security release +NOTE: This is a guide for GitLab developers. If you are trying to install GitLab see the latest stable [installation guide](install/installation.md) and if you are trying to upgrade, see the [upgrade guides](update). + +## When to do a security release + +Do a security release when there is a critical issue that needs to be adresses before the next monthly release. Otherwise include it in the monthly release and note there was a security fix in the release announcement. + +## Security vulnerability disclosure + +Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://www.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. + +## Release Procedure + +1. Verify that the issue can be repoduced +1. Acknowledge the issue to the researcher that disclosed it +1. Fix the issue on a feature branch, do this on the private GitLab development server and update the VERSION and CHANGELOG in this branch +1. Consider creating and testing workarounds +1. Create feature branches for the blog posts on GitLab.org and GitLab.com and link them from the code branch +1. Merge the code feature branch into master +1. Cherry-pick the code into the latest stable branch +1. Create a git tag vX.X.X for CE and another patch release for EE +1. Push the code and the tags to all the CE and EE repositories +1. Apply the patch to GitLab Cloud and the private GitLab development server +1. Merge and publish the blog posts +1. Send tweets about the release from @gitlabhq +1. Send out an email to the subscribers mailing list on MailChimp +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 GitLab newsletter list](http://gitlab.us5.list-manage.com/subscribe?u=498dccd07cf3e9482bee33ba4&id=98a9a4992c) +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://www.gitlab.com/vulnerability-acknowledgements/) +1. Thank the security researcher in an email for their cooperation +1. Update the blogpost and the CHANGELOG when we receive the CVE number + +The timing of the code merge into master should be coordinated in advance. +After the merge we strive to publish the announcements within 60 minutes. + +## Blog post template + +XXX Security Advisory for GitLab + +A recently discovered critical vulnerability in GitLab allows [unauthenticated API access|remote code execution|unauthorized access to repositories|XXX|PICKSOMETHING]. All users should update GitLab and gitlab-shell immediately. +We [have|haven't|XXX|PICKSOMETHING|] heard of this vulnerability being actively exploited. + +### Version affected + +GitLab Community Edition XXX and lower +GitLab Enterprise Edition XXX and lower + +### Fixed versions + +GitLab Community Edition XXX and up +GitLab Enterprise Edition XXX and up + +### Impact + +On GitLab installations which use MySQL as their database backend it is possible for an attacker to assume the identity of any existing GitLab user in certain API calls. This attack can be performed by [unauthenticated|authenticated|XXX|PICKSOMETHING] users. + +### Workarounds + +If you are unable to upgrade you should apply the following patch and restart GitLab. + +XXX + +### Credit + +We want to thank XXX of XXX for the reponsible disclosure of this vulnerability. + +## Email template + +We just announced a security advisory for GitLab at XXX + +Please contact us at support@gitlab.com if you have any questions. + +## Tweet template + +We just announced a security advisory for GitLab at XXX diff --git a/doc/security/password_length_limits.md b/doc/security/password_length_limits.md new file mode 100644 index 0000000000000000000000000000000000000000..c8d66e9636c0b97a72f1296ed86dda57bbd54df6 --- /dev/null +++ b/doc/security/password_length_limits.md @@ -0,0 +1,10 @@ +# Custom password length limits + +If you want to enforce longer user passwords you can create an extra Devise initializer with the steps below. +If you do not use the `devise_password_length.rb` initializer the password length is set to a minimum of 8 characters in `config/initializers/devise.rb`. + +```bash +cd /home/git/gitlab +sudo -u git -H cp config/initializers/devise_password_length.rb.example config/initializers/devise_password_length.rb +sudo -u git -H editor config/initializers/devise_password_length.rb # inspect and edit the new password length limits +``` diff --git a/doc/update/4.1-to-4.2.md b/doc/update/4.1-to-4.2.md index 536f22415e229b1f215e05357145f801ad31cd2a..3aaf17b7727bf2f33880d71f23f88bd10ec82db9 100644 --- a/doc/update/4.1-to-4.2.md +++ b/doc/update/4.1-to-4.2.md @@ -12,15 +12,15 @@ cd /home/gitlab/gitlab/ # Get latest code -sudo -u gitlab git fetch +sudo -u gitlab -H git fetch -sudo -u gitlab git checkout 4-2-stable +sudo -u gitlab -H git checkout 4-2-stable # Install libs -sudo -u gitlab bundle install --without development test postgres --deployment +sudo -u gitlab -H bundle install --without development test postgres --deployment # update db -sudo -u gitlab bundle exec rake db:migrate RAILS_ENV=production +sudo -u gitlab -H bundle exec rake db:migrate RAILS_ENV=production ``` diff --git a/doc/update/4.2-to-5.0.md b/doc/update/4.2-to-5.0.md index 053a50ebc88405008e2e78fe8a2794355daebbae..5bf8c36773465879a7c155b77460e482ec82df7f 100644 --- a/doc/update/4.2-to-5.0.md +++ b/doc/update/4.2-to-5.0.md @@ -1,5 +1,8 @@ # From 4.2 to 5.0 +## Warning +GitLab 5.0 is affected by critical security vulnerability CVE-2013-4490. Please update to GitLab 5.4 immediately. + ## Important changes * We don't use `gitlab` user any more. Everything will be moved to `git` user @@ -32,6 +35,7 @@ sudo chown git:git -R /home/git/repositories/ # login as git sudo su git cd /home/git/gitlab-shell +git checkout v1.1.0 # copy config cp config.yml.example config.yml @@ -85,7 +89,7 @@ sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:shell:setup RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:shell:build_missing_projects RAILS_ENV=production -sudo -u git -H mkdir /home/git/gitlab-satellites +sudo -u git -H mkdir -p /home/git/gitlab-satellites sudo -u git -H bundle exec rake gitlab:satellites:create RAILS_ENV=production # migrate wiki to git @@ -101,7 +105,7 @@ sudo chown -R git /home/git/gitlab/log/ sudo chown -R git /home/git/gitlab/tmp/ sudo chmod -R u+rwX /home/git/gitlab/log/ sudo chmod -R u+rwX /home/git/gitlab/tmp/ -sudo -u git -H mkdir /home/git/gitlab/tmp/pids/ +sudo -u git -H mkdir -p /home/git/gitlab/tmp/pids/ sudo chmod -R u+rwX /home/git/gitlab/tmp/pids ``` diff --git a/doc/update/5.0-to-5.1.md b/doc/update/5.0-to-5.1.md index 45fc3436ebeb9ea376998b87cd41636e7160141c..24d96e43bade42d6f42fca7ffe1d24a0daa2c009 100644 --- a/doc/update/5.0-to-5.1.md +++ b/doc/update/5.0-to-5.1.md @@ -1,5 +1,8 @@ # From 5.0 to 5.1 +## Warning +GitLab 5.1 is affected by critical security vulnerability CVE-2013-4490. Please [update to GitLab 5.4 immediately](5.1-to-5.4.md). + ## Release notes: * `unicorn` replaced with `puma` diff --git a/doc/update/5.1-to-5.2.md b/doc/update/5.1-to-5.2.md index 8599c4323eae0a41cc07c17db40add1f29cf089d..e4eaee91b8e02d88a4e147609292332e5d21b54e 100644 --- a/doc/update/5.1-to-5.2.md +++ b/doc/update/5.1-to-5.2.md @@ -1,5 +1,8 @@ # From 5.1 to 5.2 +## Warning +GitLab 5.2 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489. Please [update to GitLab 5.4 directly](5.1-to-5.4.md). + ### 0. Backup It's useful to make a backup just in case things go south: @@ -7,7 +10,7 @@ It's useful to make a backup just in case things go south: ```bash cd /home/git/gitlab -sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` ### 1. Stop server @@ -48,8 +51,8 @@ sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production ### 5. Update config files -* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/5-2-stable/config/gitlab.yml.example but with your settings. -* Make `/home/git/gitlab/config/puma.rb` same as https://github.com/gitlabhq/gitlabhq/blob/5-2-stable/config/puma.rb.example but with your settings. +* Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-2-stable/config/gitlab.yml.example but with your settings. +* Make `/home/git/gitlab/config/puma.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-2-stable/config/puma.rb.example but with your settings. ### 6. Update Init script @@ -95,5 +98,5 @@ Follow the [`upgrade guide from 5.0 to 5.1`](5.0-to-5.1.md), except for the data ```bash cd /home/git/gitlab -sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:restore +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production ``` diff --git a/doc/update/5.1-to-5.4.md b/doc/update/5.1-to-5.4.md new file mode 100644 index 0000000000000000000000000000000000000000..39cacd381a3e36c950a09a0fb324727176bf14a4 --- /dev/null +++ b/doc/update/5.1-to-5.4.md @@ -0,0 +1,103 @@ +# From 5.1 to 5.4 +Also works starting from 5.2. + +## Notice +Security vulnerabilities CVE-2013-4490 and CVE-2013-4489 have been patched in the latest version of GitLab 5.4. + +### 0. Backup + +It's useful to make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get latest code + +```bash +cd /home/git/gitlab +sudo -u git -H git fetch +sudo -u git -H git checkout 5-4-stable # Latest version of 5-4-stable addresses CVE-2013-4489 +``` + +### 3. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulnerabilities +``` + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production +``` + +### 5. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-4-stable/config/gitlab.yml.example but with your settings. +* Make `/home/git/gitlab/config/puma.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-4-stable/config/puma.rb.example but with your settings. + +### 6. Update Init script + +```bash +sudo rm /etc/init.d/gitlab +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +sudo chmod +x /etc/init.d/gitlab +``` + +### 7. Create uploads directory + +```bash +cd /home/git/gitlab +sudo -u git -H mkdir public/uploads +sudo chmod -R u+rwX public/uploads +``` + + +### 8. Start application + + sudo service gitlab start + sudo service nginx restart + +### 9. 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 complete! + +## Things went south? Revert to previous version (5.3) + +### 1. Revert the code to the previous version +Follow the [`upgrade guide from 5.2 to 5.3`](5.2-to-5.3.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 +``` diff --git a/doc/update/5.1-to-6.0.md b/doc/update/5.1-to-6.0.md new file mode 100644 index 0000000000000000000000000000000000000000..fa0f9ce54b626dc61b765672a4619402924a3b48 --- /dev/null +++ b/doc/update/5.1-to-6.0.md @@ -0,0 +1,156 @@ +# From 5.1 to 6.0 + +## Warning +GitLab 6.0 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489. Please [update to GitLab 6.2 immediately](6.0-to-6.2.md). + +### Deprecations + +#### Global projects + +The root (global) namespace for projects is deprecated. +So you need to move all your global projects under groups or users manually before update or they will be automatically moved to the project owner namespace during the update. When a project is moved all its members will receive an email with instructions how to update their git remote url. Please make sure you disable sending email when you do a test of the upgrade. + +#### Teams + +We introduce group membership in 6.0 as a replacement for teams. +The old combination of groups and teams was confusing for a lot of people. +And when the members of a team where changed this wasn't reflected in the project permissions. +In GitLab 6.0 you will be able to add members to a group with a permission level for each member. +These group members will have access to the projects in that group. +Any changes to group members will immediately be reflected in the project permissions. +You can even have multiple owners for a group, greatly simplifying administration. + +### 0. Backup + +It's useful to make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get latest code + +```bash +cd /home/git/gitlab +sudo -u git -H git fetch +sudo -u git -H git checkout 6-0-stable +``` + +### 3. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout v1.7.9 +``` + +### 4. Install additional packages + +```bash +# For reStructuredText markup language support install required package: +sudo apt-get install python-docutils +``` + +### 5. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake migrate_groups RAILS_ENV=production +sudo -u git -H bundle exec rake migrate_global_projects RAILS_ENV=production +sudo -u git -H bundle exec rake migrate_keys RAILS_ENV=production +sudo -u git -H bundle exec rake migrate_inline_notes RAILS_ENV=production +sudo -u git -H bundle exec rake gitlab:satellites:create RAILS_ENV=production + +# Clear redis cache +sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production + +# Clear and precompile assets +sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production +sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production +``` + +### 6. Update config files + +Note: We switched from Puma in GitLab 5.x to unicorn in GitLab 6.0. + +* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/masterconfig/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/masterconfig/unicorn.rb.example but with your settings. + +### 7. Update Init script + +```bash +cd /home/git/gitlab +sudo rm /etc/init.d/gitlab +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +sudo chmod +x /etc/init.d/gitlab +``` + +### 8. Create uploads directory + +```bash +cd /home/git/gitlab +sudo -u git -H mkdir -p public/uploads +sudo chmod -R u+rwX public/uploads +``` + +### 9. Start application + + sudo service gitlab start + sudo service nginx restart + +### 10. 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 complete! + +## Things went south? Revert to previous version (5.1) + +### 1. Revert the code to the previous version +Follow the [`upgrade guide from 5.0 to 5.1`](5.0-to-5.1.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 +``` + +### Troubleshooting +The migrations in this update are very sensitive to incomplete or inconsistent data. If you have a long-running GitLab installation and some of the previous upgrades did not work out 100% correct this may bite you now. The following commands can be run in the rails console to look for 'bad' data. + +All project owners should have an owner +``` +Project.all.select { |project| project.owner.blank? } +``` + +Every user should have a namespace +``` +User.all.select { |u| u.namespace.blank? } +``` + +Projects in the global namespace should not conflict with projects in the owner namespace +``` +Project.where(namespace_id: nil).select { |p| Project.where(path: p.path, namespace_id: p.owner.try(:namespace).try(:id)).present? } +``` diff --git a/doc/update/5.2-to-5.3.md b/doc/update/5.2-to-5.3.md index e00dfa3951a47f79bb0d09274b4e98f8ab5ede4d..7f89f6bf8873904acad38c475493dc0ed0ef2bd5 100644 --- a/doc/update/5.2-to-5.3.md +++ b/doc/update/5.2-to-5.3.md @@ -1,5 +1,8 @@ # From 5.2 to 5.3 +## Warning +GitLab 5.3 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489. Please [update to GitLab 5.4 directly](5.1-to-5.4.md). + ### 0. Backup It's useful to make a backup just in case things go south: @@ -7,7 +10,7 @@ It's useful to make a backup just in case things go south: ```bash cd /home/git/gitlab -sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` ### 1. Stop server @@ -40,8 +43,8 @@ sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production ### 4. Update config files -* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/5-3-stable/config/gitlab.yml.example but with your settings. -* Make `/home/git/gitlab/config/puma.rb` same as https://github.com/gitlabhq/gitlabhq/blob/5-3-stable/config/puma.rb.example but with your settings. +* Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-3-stable/config/gitlab.yml.example but with your settings. +* Make `/home/git/gitlab/config/puma.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-3-stable/config/puma.rb.example but with your settings. ### 5. Update Init script @@ -78,5 +81,5 @@ Follow the [`upgrade guide from 5.1 to 5.2`](5.1-to-5.2.md), except for the data ```bash cd /home/git/gitlab -sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:restore +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production ``` diff --git a/doc/update/5.3-to-5.4.md b/doc/update/5.3-to-5.4.md index 5fba0e26afa437727f281bca4df1b250ce03c4e0..7a24c11c2235e56f41467dfdd560e9b69dc84258 100644 --- a/doc/update/5.3-to-5.4.md +++ b/doc/update/5.3-to-5.4.md @@ -1,5 +1,8 @@ # From 5.3 to 5.4 +## Notice +Security vulnerabilities CVE-2013-4490 and CVE-2013-4489 have been patched in the latest version of GitLab 5.4. + ### 0. Backup It's useful to make a backup just in case things go south: @@ -7,7 +10,7 @@ It's useful to make a backup just in case things go south: ```bash cd /home/git/gitlab -sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` ### 1. Stop server @@ -19,7 +22,7 @@ sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create ```bash cd /home/git/gitlab sudo -u git -H git fetch -sudo -u git -H git checkout 5-4-stable +sudo -u git -H git checkout 5-4-stable # Latest version of 5-4-stable addresses CVE-2013-4489 ``` ### 3. Update gitlab-shell @@ -27,7 +30,7 @@ sudo -u git -H git checkout 5-4-stable ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v1.5.0 +sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulnerabilities ``` ### 4. Install libs, migrations, etc. @@ -48,8 +51,8 @@ sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production ### 5. Update config files -* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/5-4-stable/config/gitlab.yml.example but with your settings. -* Make `/home/git/gitlab/config/puma.rb` same as https://github.com/gitlabhq/gitlabhq/blob/5-4-stable/config/puma.rb.example but with your settings. +* Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-4-stable/config/gitlab.yml.example but with your settings. +* Make `/home/git/gitlab/config/puma.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-4-stable/config/puma.rb.example but with your settings. ### 6. Update Init script @@ -86,5 +89,5 @@ Follow the [`upgrade guide from 5.2 to 5.3`](5.2-to-5.3.md), except for the data ```bash cd /home/git/gitlab -sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:restore +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production ``` diff --git a/doc/update/5.4-to-6.0.md b/doc/update/5.4-to-6.0.md index 3b1d9878204b41f734ce64891688944866b29dc3..bcba3ee4d059ab43b484ad164a964981d59c6e5d 100644 --- a/doc/update/5.4-to-6.0.md +++ b/doc/update/5.4-to-6.0.md @@ -1,11 +1,14 @@ # From 5.4 to 6.0 +## Warning +GitLab 6.0 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489. Please [update to GitLab 6.2 immediately](6.0-to-6.2.md). + ### Deprecations #### Global projects The root (global) namespace for projects is deprecated. -So you need to move all your global projects under groups or users manually before update or they will be automatically moved to the project owner namespace during the update. When a project is moved all its members will receive an email with instructions how to update their git remote url. Please make sure you disable ssending email when you do a test of the upgrade. +So you need to move all your global projects under groups or users manually before update or they will be automatically moved to the project owner namespace during the update. When a project is moved all its members will receive an email with instructions how to update their git remote url. Please make sure you disable sending email when you do a test of the upgrade. #### Teams @@ -24,7 +27,7 @@ It's useful to make a backup just in case things go south: ```bash cd /home/git/gitlab -sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` ### 1. Stop server @@ -44,7 +47,7 @@ sudo -u git -H git checkout 6-0-stable ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v1.7.0 +sudo -u git -H git checkout v1.7.9 ``` ### 4. Install additional packages @@ -84,14 +87,14 @@ sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production Note: We switched from Puma in GitLab 5.4 to unicorn in GitLab 6.0. -* Make `/home/git/gitlab/config/gitlab.yml` the same as https://github.com/gitlabhq/gitlabhq/blob/master/config/gitlab.yml.example but with your settings. -* Make `/home/git/gitlab/config/unicorn.rb` the same as https://github.com/gitlabhq/gitlabhq/blob/master/config/unicorn.rb.example but with your settings. +* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/masterconfig/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/masterconfig/unicorn.rb.example but with your settings. ### 7. Update Init script ```bash sudo rm /etc/init.d/gitlab -sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/master/lib/support/init.d/gitlab +sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/6-0-stable/lib/support/init.d/gitlab sudo chmod +x /etc/init.d/gitlab ``` @@ -111,3 +114,21 @@ 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 complete! + +### Troubleshooting +The migrations in this update are very sensitive to incomplete or inconsistent data. If you have a long-running GitLab installation and some of the previous upgrades did not work out 100% correct this may bite you now. The following commands can be run in the rails console to look for 'bad' data. + +All project owners should have an owner +``` +Project.all.select { |project| project.owner.blank? } +``` + +Every user should have a namespace +``` +User.all.select { |u| u.namespace.blank? } +``` + +Projects in the global namespace should not conflict with projects in the owner namespace +``` +Project.where(namespace_id: nil).select { |p| Project.where(path: p.path, namespace_id: p.owner.try(:namespace).try(:id)).present? } +``` diff --git a/doc/update/6.0-to-6.1.md b/doc/update/6.0-to-6.1.md index 2fb762e467a60a5f474d377d68036e8066bb5f0b..ac8f0a77efd5c030ff32c83e9ccfaad5f61b48b6 100644 --- a/doc/update/6.0-to-6.1.md +++ b/doc/update/6.0-to-6.1.md @@ -1,5 +1,8 @@ # From 6.0 to 6.1 +## Warning +GitLab 6.1 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489. Please [update to GitLab 6.2 directly](6.0-to-6.2.md). + # In 6.1 we remove a lot of deprecated code. # You should update to 6.0 before installing 6.1 so all the necessary conversions are run. @@ -16,7 +19,7 @@ It's useful to make a backup just in case things go south: ```bash cd /home/git/gitlab -sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` ### 1. Stop server @@ -27,8 +30,9 @@ sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create ```bash cd /home/git/gitlab -sudo -u git -H git fetch +sudo -u git -H git fetch --all sudo -u git -H git checkout 6-1-stable +# For GitLab Enterprise Edition: sudo -u git -H git checkout 6-1-stable-ee ``` ### 3. Update gitlab-shell @@ -36,7 +40,7 @@ sudo -u git -H git checkout 6-1-stable ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v1.7.1 +sudo -u git -H git checkout v1.7.9 ``` ### 4. Install libs, migrations, etc. @@ -60,8 +64,8 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production ### 5. Update config files -* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/6-1-stable/config/gitlab.yml.example but with your settings. -* Make `/home/git/gitlab/config/unicorn.rb` same as https://github.com/gitlabhq/gitlabhq/blob/6-1-stable/config/unicorn.rb.example but with your settings. +* Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-1-stable/config/gitlab.yml.example but with your settings. +* Make `/home/git/gitlab/config/unicorn.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-1-stable/config/unicorn.rb.example but with your settings. ### 6. Update Init script @@ -80,6 +84,7 @@ sudo chmod +x /etc/init.d/gitlab Check if GitLab and its environment are configured correctly: + cd /home/git/gitlab 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: @@ -98,5 +103,5 @@ Follow the [`upgrade guide from 5.4 to 6.0`](5.4-to-6.0.md), except for the data ```bash cd /home/git/gitlab -sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:restore +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production ``` diff --git a/doc/update/6.0-to-6.5.md b/doc/update/6.0-to-6.5.md new file mode 100644 index 0000000000000000000000000000000000000000..e01cc589a83db76528b555948352344941d66cfd --- /dev/null +++ b/doc/update/6.0-to-6.5.md @@ -0,0 +1,127 @@ +# From 6.0 to 6.5 + +# In 6.1 we remove a lot of deprecated code. +# You should update to 6.0 before installing 6.1 or higher so all the necessary conversions are run. + +### Deprecations + +#### Global issue numbers + +As of 6.1 issue numbers are project specific. This means all issues are renumbered and get a new number in their url. If you use an old issue number url and the issue number does not exist yet you are redirected to the new one. This conversion does not trigger if the old number already exists for this project, this is unlikely but will happen with old issues and large projects. + +### 0. Backup + +It's useful to make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get latest code + +```bash +cd /home/git/gitlab +sudo -u git -H git fetch --all +sudo -u git -H git checkout 6-5-stable +# For GitLab Enterprise Edition: sudo -u git -H git checkout 6-5-stable-ee +``` + + +### 3. Install additional packages + +```bash +# Add support for lograte for better log file handling +sudo apt-get install logrotate +``` + +### 4. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout v1.8.0 # Addresses multiple critical security vulnerabilities +``` + +### 5. Install libs, migrations, etc. + +```bash +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 + + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production +sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production +sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production +``` + +### 6. Update config files + +TIP: to see what changed in gitlab.yml.example in this release use next command: + +``` +git diff 6-0-stable:config/gitlab.yml.example 6-5-stable:config/gitlab.yml.example +``` + +* Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-5-stable/config/gitlab.yml.example but with your settings. +* Make `/home/git/gitlab/config/unicorn.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-5-stable/config/unicorn.rb.example but with your settings. +* Copy rack attack middleware config + +```bash +sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb +``` + +* Set up logrotate + +```bash +sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab +``` + +### 7. Update Init script + +```bash +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + +### 8. Start application + + sudo service gitlab start + sudo service nginx restart + +### 9. Check application status + +Check if GitLab and its environment are configured correctly: + + cd /home/git/gitlab + 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 complete! + +## Things went south? Revert to previous version (6.0) + +### 1. Revert the code to the previous version +Follow the [`upgrade guide from 5.4 to 6.0`](5.4-to-6.0.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 +``` diff --git a/doc/update/6.1-to-6.2.md b/doc/update/6.1-to-6.2.md index 747b4860796672da837058cf16319509b0dbf98d..7342a9117c6b884fbb058881f56f211836558894 100644 --- a/doc/update/6.1-to-6.2.md +++ b/doc/update/6.1-to-6.2.md @@ -1,5 +1,8 @@ # From 6.1 to 6.2 +## Notice +Security vulnerabilities CVE-2013-4490 and CVE-2013-4489 have been patched in the latest version of GitLab 6.2. + # You should update to 6.1 before installing 6.2 so all the necessary conversions are run. ### 0. Backup @@ -9,7 +12,7 @@ It's useful to make a backup just in case things go south: ```bash cd /home/git/gitlab -sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` ### 1. Stop server @@ -20,8 +23,9 @@ sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create ```bash cd /home/git/gitlab -sudo -u git -H git fetch -sudo -u git -H git checkout 6-2-stable +sudo -u git -H git fetch --all +sudo -u git -H git checkout 6-2-stable # Latest version of 6-2-stable addresses CVE-2013-4489 +# For GitLab Enterprise Edition: sudo -u git -H git checkout 6-2-stable-ee ``` ### 3. Update gitlab-shell @@ -29,10 +33,17 @@ sudo -u git -H git checkout 6-2-stable ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v1.7.1 +sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulnerabilities ``` -### 4. Install libs, migrations, etc. +### 4. Install additional packages + +```bash +# Add support for lograte for better log file handling +sudo apt-get install logrotate +``` + +### 5. Install libs, migrations, etc. ```bash cd /home/git/gitlab @@ -45,23 +56,34 @@ sudo -u git -H bundle install --without development test mysql --deployment sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production -sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production ``` -### 5. Update config files +### 6. Update config files + +TIP: to see what changed in gitlab.yml.example in this release use next command: -* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/6-2-stable/config/gitlab.yml.example but with your settings. -* Make `/home/git/gitlab/config/unicorn.rb` same as https://github.com/gitlabhq/gitlabhq/blob/6-2-stable/config/unicorn.rb.example but with your settings. +``` +git diff 6-1-stable:config/gitlab.yml.example 6-2-stable:config/gitlab.yml.example +``` + +* Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-2-stable/config/gitlab.yml.example but with your settings. +* Make `/home/git/gitlab/config/unicorn.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-2-stable/config/unicorn.rb.example but with your settings. * Copy rack attack middleware config + ```bash sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb ``` * Uncomment `config.middleware.use Rack::Attack` in `/home/git/gitlab/config/application.rb` +* Set up logrotate + +```bash +sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab +``` -### 6. Update Init script +### 7. Update Init script ```bash sudo rm /etc/init.d/gitlab @@ -69,12 +91,12 @@ sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/6 sudo chmod +x /etc/init.d/gitlab ``` -### 7. Start application +### 8. Start application sudo service gitlab start sudo service nginx restart -### 8. Check application status +### 9. Check application status Check if GitLab and its environment are configured correctly: @@ -96,5 +118,5 @@ Follow the [`upgrade guide from 6.0 to 6.1`](6.0-to-6.1.md), except for the data ```bash cd /home/git/gitlab -sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:restore +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production ``` diff --git a/doc/update/6.2-to-6.3.md b/doc/update/6.2-to-6.3.md new file mode 100644 index 0000000000000000000000000000000000000000..7f916047369b5e3ec1bb835d4eed49bd26723f93 --- /dev/null +++ b/doc/update/6.2-to-6.3.md @@ -0,0 +1,109 @@ +# From 6.2 to 6.3 + +## Requires version: 6.1 or 6.2 + +### 0. Backup + +It's useful to make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get latest code + +```bash +cd /home/git/gitlab +sudo -u git -H git fetch --all +sudo -u git -H git checkout 6-3-stable +# For GitLab Enterprise Edition: sudo -u git -H git checkout 6-3-stable-ee +``` + +### 3. Update gitlab-shell (and its config) + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulnerabilities +``` + +The Gitlab-shell config changed recently, so check for config file changes and make `/home/git/gitlab-shell/config.yml` the same as https://github.com/gitlabhq/gitlab-shell/blob/master/config.yml.example + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +# PostgreSQL +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 +``` + +### 5. Update config files + +TIP: to see what changed in gitlab.yml.example in this release use next command: + +``` +git diff 6-2-stable:config/gitlab.yml.example 6-3-stable:config/gitlab.yml.example +``` + +* Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-3-stable/config/gitlab.yml.example but with your settings. +* Make `/home/git/gitlab/config/unicorn.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-3-stable/config/unicorn.rb.example but with your settings. + +```bash +# Copy rack attack middleware config +cd /home/git/gitlab +sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb +``` + +### 6. Update Init script + +```bash +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +sudo chmod +x /etc/init.d/gitlab +``` + +### 7. Start application + + sudo service gitlab start + sudo service nginx restart + +### 8. 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 complete! + +## Things went south? Revert to previous version (6.2) + +### 1. Revert the code to the previous version +Follow the [`upgrade guide from 6.1 to 6.2`](6.1-to-6.2.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 +``` diff --git a/doc/update/6.3-to-6.4.md b/doc/update/6.3-to-6.4.md new file mode 100644 index 0000000000000000000000000000000000000000..6874056498b70ce6b637dc737b24cab012e7751c --- /dev/null +++ b/doc/update/6.3-to-6.4.md @@ -0,0 +1,81 @@ +# From 6.3 to 6.4 + +### 0. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get latest code + +```bash +cd /home/git/gitlab +sudo -u git -H git fetch --all +sudo -u git -H git checkout 6-4-stable +# For GitLab Enterprise Edition: sudo -u git -H git checkout 6-4-stable-ee +``` + +### 3. Update gitlab-shell (and its config) + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout v1.8.0 +``` + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +# PostgreSQL +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. Start application + + sudo service gitlab start + sudo service nginx restart + +### 8. 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 complete! + +## Things went south? Revert to previous version (6.3) + +### 1. Revert the code to the previous version +Follow the [`upgrade guide from 6.2 to 6.3`](6.2-to-6.3.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 +``` diff --git a/doc/update/6.4-to-6.5.md b/doc/update/6.4-to-6.5.md new file mode 100644 index 0000000000000000000000000000000000000000..2b1fa2744fe6f5c8b5df02b21a4b45504138663d --- /dev/null +++ b/doc/update/6.4-to-6.5.md @@ -0,0 +1,81 @@ +# From 6.4 to 6.5 + +### 0. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get latest code + +```bash +cd /home/git/gitlab +sudo -u git -H git fetch --all +sudo -u git -H git checkout 6-5-stable +# For GitLab Enterprise Edition: sudo -u git -H git checkout 6-5-stable-ee +``` + +### 3. Update gitlab-shell (and its config) + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout v1.8.0 +``` + +### 4. Install libs, migrations, etc. + +```bash +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. Start application + + sudo service gitlab start + sudo service nginx restart + +### 6. 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 complete! + +## Things went south? Revert to previous version (6.4) + +### 1. Revert the code to the previous version +Follow the [`upgrade guide from 6.3 to 6.4`](6.3-to-6.4.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 +``` diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md new file mode 100644 index 0000000000000000000000000000000000000000..b284ff48365177ef2b57bf6262a2a7a535242e07 --- /dev/null +++ b/doc/update/patch_versions.md @@ -0,0 +1,68 @@ +# Universal update guide for patch versions. For example from 6.2.0 to 6.2.1, also see the [semantic versioning specification](http://semver.org/). + +### 0. Backup + +It's useful to make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get latest code for the stable branch + +```bash +cd /home/git/gitlab +sudo -u git -H git pull origin STABLE_BRANCH +``` + +Replace STABLE_BRANCH with the minor version you want to upgrade to, for example `6-3-stable`. + +### 3. Update gitlab-shell if it is not the latest version + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout LATEST_TAG +``` + +Replace LATEST_TAG with the latest GitLab Shell tag you want to upgrade to, for example `v1.7.9`. + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production +sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production +sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production +``` + +### 5. Start application + + sudo service gitlab start + sudo service nginx restart + +### 6. 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 complete! diff --git a/doc/update/ruby.md b/doc/update/ruby.md new file mode 100644 index 0000000000000000000000000000000000000000..3fc068c0ae214a6200b734186edfeed57541d01d --- /dev/null +++ b/doc/update/ruby.md @@ -0,0 +1,54 @@ +# Updating Ruby from source + +This guide explains how to update Ruby in case you installed it from source according to the instructions in https://gitlab.com/gitlab-org/gitlab-ce/blob/masterdoc/install/installation.md#2-ruby . + +### 1. Look for Ruby versions +This guide will only update `/usr/local/bin/ruby`. You can see which Ruby binaries are installed on your system by running: + +```bash +ls -l $(which -a ruby) +``` + +### 2. Stop GitLab + +```bash +sudo service gitlab stop +``` + +### 3. Install or update dependencies +Here we are assuming you are using Debian/Ubuntu. + +```bash +sudo apt-get install build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl +``` + +### 4. Download, compile and install Ruby +Find the latest stable version of Ruby 1.9 or 2.0 at https://www.ruby-lang.org/en/downloads/ . We recommend at least 2.0.0-p353, which is patched against [CVE-2013-4164](https://www.ruby-lang.org/en/news/2013/11/22/heap-overflow-in-floating-point-parsing-cve-2013-4164/). + +```bash +cd /tmp +curl --progress http://cache.ruby-lang.org/pub/ruby/2.0/ruby-2.0.0-p353.tar.gz | tar xz +cd ruby-2.0.0-p353 +./configure --disable-install-rdoc +make +sudo make install # overwrite the existing Ruby in /usr/local/bin +sudo gem install bundler +``` + +### 5. Reinstall GitLab gem bundle +Just to be sure we will reinstall the gems used by GitLab. Note that the `bundle install` command [depends on your choice of database](https://gitlab.com/gitlab-org/gitlab-ce/blob/masterdoc/install/installation.md#install-gems). + +```bash +cd /home/git/gitlab +sudo -u git -H rm -rf vendor/bundle # remove existing Gem bundle +sudo -u git -H bundle install --deployment --without development test postgres aws # Assuming MySQL +``` + +### 6. Start GitLab +We are now ready to restart GitLab. + +```bash +sudo service gitlab start +``` + +### Done diff --git a/doc/update/upgrader.md b/doc/update/upgrader.md new file mode 100644 index 0000000000000000000000000000000000000000..1eec5a8f396337b9f0ac1d7c7e56d18e46399c92 --- /dev/null +++ b/doc/update/upgrader.md @@ -0,0 +1,30 @@ +# GitLab Upgrader + +GitLab Upgrader - a ruby script that allows you easily upgrade GitLab to latest minor version. +For example it can update your application from 6.4 to latest GitLab 6 version (like 6.6.1). +You still need to create a a backup and manually restart GitLab after runnning the script but all other operations are done by this upgrade script. +If you have local changes to your GitLab repository the script will stash them and you need to use `git stash pop` after running the script. + +__GitLab Upgrader is available only for GitLab version 6.4.2 or higher__ + +### 0. Backup + + cd /home/git/gitlab + sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production + +### 1. Stop server + + sudo service gitlab stop + +### 2. Run gitlab upgrade tool + + cd /home/git/gitlab + sudo -u git -H ruby script/upgrade.rb + + # it also supports -y option to avoid waiting for user input + # sudo -u git -H ruby script/upgrade.rb -y + +### 3. Start application + + sudo service gitlab start + sudo service nginx restart diff --git a/features/admin/active_tab.feature b/features/admin/active_tab.feature index 226d3d5d5b5b567aec417932b40b648f212faae0..15fcda45e40324a432654503604d32ad51d86ba2 100644 --- a/features/admin/active_tab.feature +++ b/features/admin/active_tab.feature @@ -27,6 +27,11 @@ Feature: Admin active tab Then the active main tab should be Logs And no other main tabs should be active + Scenario: On Admin Messages + Given I visit admin messages page + Then the active main tab should be Messages + And no other main tabs should be active + Scenario: On Admin Hooks Given I visit admin hooks page Then the active main tab should be Hooks diff --git a/features/admin/broadcast_messages.feature b/features/admin/broadcast_messages.feature new file mode 100644 index 0000000000000000000000000000000000000000..5f16120b7cc0788db24cf9452996872faaf250ec --- /dev/null +++ b/features/admin/broadcast_messages.feature @@ -0,0 +1,20 @@ +Feature: Admin Broadcast Messages + Background: + Given I sign in as an admin + And application already has admin messages + And I visit admin messages page + + Scenario: See broadcast messages list + Then I should be all broadcast messages + + Scenario: Create a broadcast message + When submit form with new broadcast message + Then I should be redirected to admin messages page + And I should see newly created broadcast message + + Scenario: Create a customized broadcast message + When submit form with new customized broadcast message + Then I should be redirected to admin messages page + And I should see newly created broadcast message + Then I visit dashboard page + And I should see a customized broadcast message diff --git a/features/dashboard/archived_projects.feature b/features/dashboard/archived_projects.feature new file mode 100644 index 0000000000000000000000000000000000000000..399c9b53d814e9532d09e68a0ffc7fe5a928127c --- /dev/null +++ b/features/dashboard/archived_projects.feature @@ -0,0 +1,16 @@ +Feature: Dashboard with archived projects + Background: + Given I sign in as a user + And I own project "Shop" + And I own project "Forum" + And project "Forum" is archived + And I visit dashboard page + + Scenario: I should see non-archived projects on dashboard + Then I should see "Shop" project link + And I should not see "Forum" project link + + Scenario: I should see all projects on projects page + And I visit dashboard projects page + Then I should see "Shop" project link + And I should see "Forum" project link diff --git a/features/dashboard/issues.feature b/features/dashboard/issues.feature index 895b89aa38a5c4cdd8a73cdb8689ce34225a23cb..d316b2d92057f962cfc5d84026bcc4cb44487cfa 100644 --- a/features/dashboard/issues.feature +++ b/features/dashboard/issues.feature @@ -1,8 +1,18 @@ Feature: Dashboard Issues Background: Given I sign in as a user + And I have authored issues And I have assigned issues + And I have other issues And I visit dashboard issues page - Scenario: I should see issues list + Scenario: I should see assigned issues Then I should see issues assigned to me + + Scenario: I should see authored issues + When I click "Authored by me" link + Then I should see issues authored by me + + Scenario: I should see all issues + When I click "All" link + Then I should see all issues diff --git a/features/dashboard/merge_requests.feature b/features/dashboard/merge_requests.feature index cad65b0d79a08620145dba3d0716339ffc3b1f06..de560300735d9c24e16018fdc15ebe233a1e2e3d 100644 --- a/features/dashboard/merge_requests.feature +++ b/features/dashboard/merge_requests.feature @@ -2,7 +2,17 @@ Feature: Dashboard Merge Requests Background: Given I sign in as a user And I have authored merge requests + And I have assigned merge requests + And I have other merge requests And I visit dashboard merge requests page - Scenario: I should see projects list - Then I should see my merge requests + Scenario: I should see assigned merge_requests + Then I should see merge requests assigned to me + + Scenario: I should see authored merge_requests + When I click "Authored by me" link + Then I should see merge requests authored by me + + Scenario: I should see all merge_requests + When I click "All" link + Then I should see all merge requests diff --git a/features/group/group.feature b/features/group/group.feature index 9fec19a4dc1300cb0825e4150c150de742a849f1..ca3e67d2c1d4590322b4c0f8ed887b994f1e8437 100644 --- a/features/group/group.feature +++ b/features/group/group.feature @@ -31,3 +31,17 @@ Feature: Groups And I change group name Then I should see new group name + Scenario: I edit my group avatar + When I visit group settings page + And I change my group avatar + And I visit group settings page + Then I should see new group avatar + And I should see the "Remove avatar" button + + Scenario: I remove my group avatar + When I visit group settings page + And I have an group avatar + And I visit group settings page + And I remove my group avatar + Then I should not see my group avatar + And I should not see the "Remove avatar" button diff --git a/features/profile/profile.feature b/features/profile/profile.feature index c74b0993fb33fc41719c9547f06ddad4836eaac0..8b6ee6fd67f69bc751f92efd4685897def2aff1e 100644 --- a/features/profile/profile.feature +++ b/features/profile/profile.feature @@ -8,30 +8,43 @@ Feature: Profile Scenario: I edit profile Given I visit profile page - Then I change my contact info - And I should see new contact info + Then I change my profile info + And I should see new profile info Scenario: I change my password without old one - Given I visit profile account page + Given I visit profile password page When I try change my password w/o old one Then I should see a missing password error message - And I should be redirected to account page + And I should be redirected to password page Scenario: I change my password - Given I visit profile account page + Given I visit profile password page Then I change my password And I should be redirected to sign in page + Scenario: I edit my avatar + Given I visit profile page + Then I change my avatar + And I should see new avatar + And I should see the "Remove avatar" button + + Scenario: I remove my avatar + Given I visit profile page + And I have an avatar + When I remove my avatar + Then I should see my gravatar + And I should not see the "Remove avatar" button + Scenario: My password is expired Given my password is expired And I am not an ldap user - And I visit profile account page + Given I visit profile password page Then I redirected to expired password page And I submit new password And I redirected to sign in page Scenario: I unsuccessfully change my password - Given I visit profile account page + Given I visit profile password page When I unsuccessfully change my password Then I should see a password error message diff --git a/features/project/archived_projects.feature b/features/project/archived_projects.feature new file mode 100644 index 0000000000000000000000000000000000000000..9aac29384ba00665756e6449efd05e3149941524 --- /dev/null +++ b/features/project/archived_projects.feature @@ -0,0 +1,39 @@ +Feature: Project Archived + Background: + Given I sign in as a user + And I own project "Shop" + And I own project "Forum" + + Scenario: I should not see archived on project page of not-archive project + And project "Forum" is archived + And I visit project "Shop" page + Then I should not see "Archived" + + Scenario: I should see archived on project page of archive project + And project "Forum" is archived + And I visit project "Forum" page + Then I should see "Archived" + + Scenario: I should not see archived on projects page with no archived projects + And I visit dashboard projects page + Then I should not see "Archived" + + Scenario: I should see archived on projects page with archived projects + And project "Forum" is archived + And I visit dashboard projects page + Then I should see "Archived" + + Scenario: I archive project + When project "Shop" has push event + And I visit project "Shop" page + And I visit edit project "Shop" page + And I set project archived + Then I should see "Archived" + + Scenario: I unarchive project + When project "Shop" has push event + And project "Shop" is archived + And I visit project "Shop" page + And I visit edit project "Shop" page + And I set project unarchived + Then I should not see "Archived" diff --git a/features/project/commits/commits.feature b/features/project/commits/commits.feature index fe470f5ac99d52b142dd820e97a3ac30c0a3b78c..cbe8b3215071c62cf55c49f1c4885db2fdb47fc8 100644 --- a/features/project/commits/commits.feature +++ b/features/project/commits/commits.feature @@ -14,6 +14,12 @@ Feature: Project Browse commits Scenario: I browse commit from list Given I click on commit link Then I see commit info + And I see side-by-side diff button + + Scenario: I browse commit with side-by-side diff view + Given I click on commit link + And I click side-by-side diff button + Then I see inline diff button Scenario: I compare refs Given I visit compare refs page diff --git a/features/project/edit_issuetracker.feature b/features/project/edit_issuetracker.feature new file mode 100644 index 0000000000000000000000000000000000000000..b5477d3c7ab54457c142463bb27e1dbd80baeb7e --- /dev/null +++ b/features/project/edit_issuetracker.feature @@ -0,0 +1,18 @@ +Feature: Project Issue Tracker + Background: + Given I sign in as a user + And I own project "Shop" + And project "Shop" has issues enabled + And I visit project "Shop" page + + Scenario: I set the issue tracker to "GitLab" + When I visit edit project "Shop" page + And change the issue tracker to "GitLab" + And I save project + Then I the project should have "GitLab" as issue tracker + + Scenario: I set the issue tracker to "Redmine" + When I visit edit project "Shop" page + And change the issue tracker to "Redmine" + And I save project + Then I the project should have "Redmine" as issue tracker \ No newline at end of file diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 63f27c3acc342438a253e1c4c842aa9ba0857bbf..946f6760126bdcd7e030a20b04f51176ccd55cd8 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -55,15 +55,25 @@ Feature: Project Merge Requests Given project "Shop" have "Bug NS-05" open merge request with diffs inside And I visit merge request page "Bug NS-05" And I click on the first commit in the merge request - And I leave a comment like "Line is wrong" on line 185 of the first file + And I leave a comment like "Line is wrong" on line 185 of the first file in commit And I switch to the merge request's comments tab - Then I should see a discussion has started on commit bcf03b5de6c:L185 + Then I should see a discussion has started on commit b1e6a9dbf1:L185 @javascript Scenario: I comment on a commit in merge request Given project "Shop" have "Bug NS-05" open merge request with diffs inside And I visit merge request page "Bug NS-05" And I click on the first commit in the merge request - And I leave a comment on the diff page + And I leave a comment on the diff page in commit And I switch to the merge request's comments tab - Then I should see a discussion has started on commit bcf03b5de6c + Then I should see a discussion has started on commit b1e6a9dbf1 + + @javascript + Scenario: I accept merge request with custom commit message + Given project "Shop" have "Bug NS-05" open merge request with diffs inside + And merge request "Bug NS-05" is mergeable + And I visit merge request page "Bug NS-05" + And merge request is mergeable + Then I modify merge commit message + And I accept this merge request + Then I should see merged request diff --git a/features/project/network.feature b/features/project/network.feature index ceae08c10743c661d8499fde50e263be00b79fa5..22beb1c50bc5a2b04f397d142f7e355ae472d5ad 100644 --- a/features/project/network.feature +++ b/features/project/network.feature @@ -29,11 +29,11 @@ Feature: Project Network Graph @javascript Scenario: I should filter selected tag When I switch ref to "v2.1.0" - Then page should have content not cotaining "v2.1.0" + Then page should have content not containing "v2.1.0" When click "Show only selected branch" checkbox - Then page should not have content not cotaining "v2.1.0" + Then page should not have content not containing "v2.1.0" When click "Show only selected branch" checkbox - Then page should have content not cotaining "v2.1.0" + Then page should have content not containing "v2.1.0" Scenario: I should fail to look for a commit When I look for a commit by ";" diff --git a/features/project/project.feature b/features/project/project.feature index 59eda4a781da5cdc107e6720375287202d9e94ca..d8bb1d55e2d7b20b1153c94af121eb5ed2cd7b62 100644 --- a/features/project/project.feature +++ b/features/project/project.feature @@ -19,3 +19,8 @@ Feature: Project Feature And change project settings And I save project Then I should see project with new settings + + Scenario: I change project path + When I visit edit project "Shop" page + And change project path settings + Then I should see project with new path settings diff --git a/features/project/redirects.feature b/features/project/redirects.feature new file mode 100644 index 0000000000000000000000000000000000000000..ce197912f641f9c412ec24631441c17d99eb64ba --- /dev/null +++ b/features/project/redirects.feature @@ -0,0 +1,26 @@ +Feature: Project Redirects + Background: + Given public project "Community" + And private project "Enterprise" + + Scenario: I visit public project page + When I visit project "Community" page + Then I should see project "Community" home page + + Scenario: I visit private project page + When I visit project "Enterprise" page + Then I should be redirected to sign in page + + Scenario: I visit a non-existent project page + When I visit project "CommunityDoesNotExist" page + Then I should be redirected to sign in page + + Scenario: I visit a non-existent project page as user + Given I sign in as a user + When I visit project "CommunityDoesNotExist" page + Then page status code should be 404 + + Scenario: I visit unauthorized project page as user + Given I sign in as a user + When I visit project "Enterprise" page + Then page status code should be 404 diff --git a/features/project/service.feature b/features/project/service.feature index e685c385d1df3047f2ab6cdc3eaf278fac4c84da..46b983e8f9a36d0312d47357b892dafd3dec915b 100644 --- a/features/project/service.feature +++ b/features/project/service.feature @@ -24,3 +24,21 @@ Feature: Project Services And I click pivotaltracker service link And I fill pivotaltracker settings Then I should see pivotaltracker service settings saved + + Scenario: Activate Flowdock service + When I visit project "Shop" services page + And I click Flowdock service link + And I fill Flowdock settings + Then I should see Flowdock service settings saved + + Scenario: Activate Assembla service + When I visit project "Shop" services page + And I click Assembla service link + And I fill Assembla settings + Then I should see Assembla service settings saved + + Scenario: Activate email on push service + When I visit project "Shop" services page + And I click email on push service link + And I fill email on push settings + Then I should see email on push service settings saved diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index ee26f5371a997581e2a773c0bfd5cf0d60ef6204..fd9a2f01a28c619ce85b4865003e5f547eaf24c4 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -20,6 +20,10 @@ Feature: Project Browse files And I click link "raw" Then I should see raw file content + Scenario: I can create file + Given I click on "new file" link in repo + Then I can see new file page + @javascript Scenario: I can edit file Given I click on "Gemfile.lock" file in repo diff --git a/features/project/source/markdown_render.feature b/features/project/source/markdown_render.feature new file mode 100644 index 0000000000000000000000000000000000000000..04467b66648439e868c6b6cf54f9d26f8ee3c4ea --- /dev/null +++ b/features/project/source/markdown_render.feature @@ -0,0 +1,92 @@ +Feature: Project markdown render + Background: + Given I sign in as a user + And I own project "Delta" + Given I visit project source page + + Scenario: I browse files from master branch + Then I should see files from repository in master + And I should see rendered README which contains correct links + And I click on Gitlab API in README + Then I should see correct document rendered + + Scenario: I view README in master branch + Then I should see files from repository in master + And I should see rendered README which contains correct links + And I click on Rake tasks in README + Then I should see correct directory rendered + + Scenario: I view README in master branch to see reference links to directory + Then I should see files from repository in master + And I should see rendered README which contains correct links + And I click on GitLab API doc directory in README + Then I should see correct doc/api directory rendered + + Scenario: I view README in master branch to see reference links to file + Then I should see files from repository in master + And I should see rendered README which contains correct links + And I click on Maintenance in README + Then I should see correct maintenance file rendered + + Scenario: I navigate to doc directory to view documentation in master + And I navigate to the doc/api/README + And I see correct file rendered + And I click on users in doc/api/README + Then I should see the correct document file + + Scenario: I navigate to doc directory to view user doc in master + And I navigate to the doc/api/README + And I see correct file rendered + And I click on raketasks in doc/api/README + Then I should see correct directory rendered + + Scenario: I browse files from markdown branch + When I visit markdown branch + Then I should see files from repository in markdown branch + And I should see rendered README which contains correct links + And I click on Gitlab API in README + Then I should see correct document rendered for markdown branch + + Scenario: I browse directory from markdown branch + When I visit markdown branch + Then I should see files from repository in markdown branch + And I should see rendered README which contains correct links + And I click on Rake tasks in README + Then I should see correct directory rendered for markdown branch + + Scenario: I navigate to doc directory to view documentation in markdown branch + When I visit markdown branch + And I navigate to the doc/api/README + And I see correct file rendered in markdown branch + And I click on users in doc/api/README + Then I should see the users document file in markdown branch + + Scenario: I navigate to doc directory to view user doc in markdown branch + When I visit markdown branch + And I navigate to the doc/api/README + And I see correct file rendered in markdown branch + And I click on raketasks in doc/api/README + Then I should see correct directory rendered for markdown branch + + Scenario: I create a wiki page with different links + Given I go to wiki page + And I add various links to the wiki page + Then Wiki page should have added links + And I click on test link + Then I see new wiki page named test + When I go back to wiki page home + And I click on GitLab API doc link + Then I see Gitlab API document + When I go back to wiki page home + And I click on Rake tasks link + Then I see Rake tasks directory + + Scenario: I visit the help page with markdown + Given I visit to the help page + And I select a page with markdown + Then I should see a help page with markdown + + Scenario: Tree view should have correct links in README + Given I go directory which contains README file + And I click on a relative link in README + Then I should see the correct markdown diff --git a/features/project/source/multiselect_blob.feature b/features/project/source/multiselect_blob.feature new file mode 100644 index 0000000000000000000000000000000000000000..3038c0814ad227467b0fe5db6281f4b90b46dc0d --- /dev/null +++ b/features/project/source/multiselect_blob.feature @@ -0,0 +1,86 @@ +Feature: Project Multiselect Blob + Background: + Given I sign in as a user + And I own project "Shop" + And I visit project source page + And I click on "Gemfile.lock" file in repo + + @javascript + Scenario: I click line 1 in file + When I click line 1 in file + Then I should see "L1" as URI fragment + And I should see line 1 highlighted + + @javascript + Scenario: I shift-click line 1 in file + When I shift-click line 1 in file + Then I should see "L1" as URI fragment + And I should see line 1 highlighted + + @javascript + Scenario: I click line 1 then click line 2 in file + When I click line 1 in file + Then I should see "L1" as URI fragment + And I should see line 1 highlighted + Then I click line 2 in file + Then I should see "L2" as URI fragment + And I should see line 2 highlighted + + @javascript + Scenario: I click various line numbers to test multiselect + Then I click line 1 in file + Then I should see "L1" as URI fragment + And I should see line 1 highlighted + Then I shift-click line 2 in file + Then I should see "L1-2" as URI fragment + And I should see lines 1-2 highlighted + Then I shift-click line 3 in file + Then I should see "L1-3" as URI fragment + And I should see lines 1-3 highlighted + Then I click line 3 in file + Then I should see "L3" as URI fragment + And I should see line 3 highlighted + Then I shift-click line 1 in file + Then I should see "L1-3" as URI fragment + And I should see lines 1-3 highlighted + Then I shift-click line 5 in file + Then I should see "L1-5" as URI fragment + And I should see lines 1-5 highlighted + Then I shift-click line 4 in file + Then I should see "L1-4" as URI fragment + And I should see lines 1-4 highlighted + Then I click line 5 in file + Then I should see "L5" as URI fragment + And I should see line 5 highlighted + Then I shift-click line 3 in file + Then I should see "L3-5" as URI fragment + And I should see lines 3-5 highlighted + Then I shift-click line 1 in file + Then I should see "L1-3" as URI fragment + And I should see lines 1-3 highlighted + Then I shift-click line 1 in file + Then I should see "L1" as URI fragment + And I should see line 1 highlighted + + @javascript + Scenario: I multiselect lines 1-5 and then go back and forward in history + When I click line 1 in file + And I shift-click line 3 in file + And I shift-click line 2 in file + And I shift-click line 5 in file + Then I should see "L1-5" as URI fragment + And I should see lines 1-5 highlighted + Then I go back in history + Then I should see "L1-2" as URI fragment + And I should see lines 1-2 highlighted + Then I go back in history + Then I should see "L1-3" as URI fragment + And I should see lines 1-3 highlighted + Then I go back in history + Then I should see "L1" as URI fragment + And I should see line 1 highlighted + Then I go forward in history + And I go forward in history + And I go forward in history + Then I should see "L1-5" as URI fragment + And I should see lines 1-5 highlighted \ No newline at end of file diff --git a/features/public/public_projects.feature b/features/public/public_projects.feature index 178a769194c38b50e0dcfd31256b7a1e5179b88c..57fe834b4bfd38173132bea847c3ba307ba68407 100644 --- a/features/public/public_projects.feature +++ b/features/public/public_projects.feature @@ -1,18 +1,101 @@ Feature: Public Projects Feature Background: Given public project "Community" + And internal project "Internal" And private project "Enterprise" Scenario: I visit public area When I visit the public projects area Then I should see project "Community" + And I should not see project "Internal" And I should not see project "Enterprise" Scenario: I visit public project page When I visit project "Community" page Then I should see project "Community" home page + Scenario: I visit internal project page + When I visit project "Internal" page + Then I should be redirected to sign in page + + Scenario: I visit private project page + When I visit project "Enterprise" page + Then I should be redirected to sign in page + + Scenario: I visit an empty public project page + Given public empty project "Empty Public Project" + When I visit empty project page + Then I should see empty public project details + And I should see empty public project details with http clone info + + Scenario: I visit an empty public project page as user + Given I sign in as a user + And public empty project "Empty Public Project" + When I visit empty project page + Then I should see empty public project details + And I should see empty public project details with ssh clone info + + Scenario: I visit public area as user + Given I sign in as a user + When I visit the public projects area + Then I should see project "Community" + And I should see project "Internal" + And I should not see project "Enterprise" + + Scenario: I visit internal project page as user + Given I sign in as a user + When I visit project "Internal" page + Then I should see project "Internal" home page + + Scenario: I visit public project page + When I visit project "Community" page + Then I should see project "Community" home page + And I should see an http link to the repository + + Scenario: I visit public project page as user + Given I sign in as a user + When I visit project "Community" page + Then I should see project "Community" home page + And I should see an ssh link to the repository + Scenario: I visit an empty public project page Given public empty project "Empty Public Project" When I visit empty project page Then I should see empty public project details + + Scenario: I visit public project issues page as a non authorized user + Given I visit project "Community" page + And I visit "Community" issues page + Then I should see list of issues for "Community" project + + Scenario: I visit public project issues page as authorized user + Given I sign in as a user + Given I visit project "Community" page + And I visit "Community" issues page + Then I should see list of issues for "Community" project + + Scenario: I visit internal project issues page as authorized user + Given I sign in as a user + Given I visit project "Internal" page + And I visit "Internal" issues page + Then I should see list of issues for "Internal" project + + Scenario: I visit public project merge requests page as an authorized user + Given I sign in as a user + Given I visit project "Community" page + And I visit "Community" merge requests page + And project "Community" has "Bug fix" open merge request + Then I should see list of merge requests for "Community" project + + Scenario: I visit public project merge requests page as a non authorized user + Given I visit project "Community" page + And I visit "Community" merge requests page + And project "Community" has "Bug fix" open merge request + Then I should see list of merge requests for "Community" project + + Scenario: I visit internal project merge requests page as an authorized user + Given I sign in as a user + Given I visit project "Internal" page + And I visit "Internal" merge requests page + And project "Internal" has "Feature implemented" open merge request + Then I should see list of merge requests for "Internal" project diff --git a/features/steps/admin/admin_active_tab.rb b/features/steps/admin/admin_active_tab.rb index f14c5f396bedbb433e31452be5e6ded96f7e7941..ccafe09c18f878ae0c30a312d3a3a59dfa871280 100644 --- a/features/steps/admin/admin_active_tab.rb +++ b/features/steps/admin/admin_active_tab.rb @@ -30,4 +30,8 @@ class AdminActiveTab < Spinach::FeatureSteps Then 'the active main tab should be Resque' do ensure_active_main_tab('Background Jobs') end + + Then 'the active main tab should be Messages' do + ensure_active_main_tab('Messages') + end end diff --git a/features/steps/admin/admin_broadcast_messages.rb b/features/steps/admin/admin_broadcast_messages.rb new file mode 100644 index 0000000000000000000000000000000000000000..a35fa34a3a295e6d93fb14e3350ba41e95568af7 --- /dev/null +++ b/features/steps/admin/admin_broadcast_messages.rb @@ -0,0 +1,41 @@ +class Spinach::Features::AdminBroadcastMessages < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedAdmin + + step 'application already has admin messages' do + FactoryGirl.create(:broadcast_message, message: "Migration to new server") + end + + step 'I should be all broadcast messages' do + page.should have_content "Migration to new server" + end + + step 'submit form with new broadcast message' do + fill_in 'broadcast_message_message', with: 'Application update from 4:00 CST to 5:00 CST' + select '2018', from: "broadcast_message_ends_at_1i" + click_button "Add broadcast message" + end + + step 'I should be redirected to admin messages page' do + current_path.should == admin_broadcast_messages_path + end + + step 'I should see newly created broadcast message' do + page.should have_content 'Application update from 4:00 CST to 5:00 CST' + end + + step 'submit form with new customized broadcast message' do + fill_in 'broadcast_message_message', with: 'Application update from 4:00 CST to 5:00 CST' + click_link "Customize colors" + fill_in 'broadcast_message_color', with: '#f2dede' + fill_in 'broadcast_message_font', with: '#b94a48' + select '2018', from: "broadcast_message_ends_at_1i" + click_button "Add broadcast message" + end + + step 'I should see a customized broadcast message' do + page.should have_content 'Application update from 4:00 CST to 5:00 CST' + page.should have_selector %(div[style="background-color:#f2dede;color:#b94a48"]) + end +end diff --git a/features/steps/admin/admin_groups.rb b/features/steps/admin/admin_groups.rb index b4591f227e39aa64183be18056b7c6e30bd5049d..013fa6da8b4c5d54f3d0314bc11cb1df4cfdf218 100644 --- a/features/steps/admin/admin_groups.rb +++ b/features/steps/admin/admin_groups.rb @@ -40,7 +40,7 @@ class AdminGroups < Spinach::FeatureSteps end When 'I select user "John" from user list as "Reporter"' do - user = User.find_by_name("John") + user = User.find_by(name: "John") select2(user.id, from: "#user_ids", multiple: true) within "#new_team_member" do select "Reporter", from: "group_access" diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb index bde32128b9237695e733b4df283801f4f4f20d88..3526006c94af2bf0e5f0419b2d3b455d92832846 100644 --- a/features/steps/dashboard/dashboard.rb +++ b/features/steps/dashboard/dashboard.rb @@ -43,7 +43,7 @@ class Dashboard < Spinach::FeatureSteps end And 'user with name "John Doe" left project "Shop"' do - user = User.find_by_name "John Doe" + user = User.find_by(name: "John Doe") Event.create( project: project, author_id: user.id, @@ -85,6 +85,6 @@ class Dashboard < Spinach::FeatureSteps end def project - @project ||= Project.find_by_name "Shop" + @project ||= Project.find_by(name: "Shop") end end diff --git a/features/steps/dashboard/dashboard_issues.rb b/features/steps/dashboard/dashboard_issues.rb index fcf4296ad11b11f6061b4653ec6e50def98400a2..1344edfa80ba7580e00ff024bde1a8c0aec11711 100644 --- a/features/steps/dashboard/dashboard_issues.rb +++ b/features/steps/dashboard/dashboard_issues.rb @@ -2,19 +2,73 @@ class DashboardIssues < Spinach::FeatureSteps include SharedAuthentication include SharedPaths - Then 'I should see issues assigned to me' do - issues = @user.issues - issues.each do |issue| - page.should have_content(issue.title[0..10]) - page.should have_content(issue.project.name) - page.should have_link(issue.project.name) + step 'I should see issues assigned to me' do + should_see(assigned_issue) + should_not_see(authored_issue) + should_not_see(other_issue) + end + + step 'I should see issues authored by me' do + should_see(authored_issue) + should_not_see(assigned_issue) + should_not_see(other_issue) + end + + step 'I should see all issues' do + should_see(authored_issue) + should_see(assigned_issue) + should_see(other_issue) + end + + step 'I have authored issues' do + authored_issue + end + + step 'I have assigned issues' do + assigned_issue + end + + step 'I have other issues' do + other_issue + end + + step 'I click "Authored by me" link' do + within ".scope-filter" do + click_link 'Created by me' end end - And 'I have assigned issues' do - project = create :project - project.team << [@user, :master] + step 'I click "All" link' do + within ".scope-filter" do + click_link "Everyone's" + end + end + + def should_see(issue) + page.should have_content(issue.title[0..10]) + end + + def should_not_see(issue) + page.should_not have_content(issue.title[0..10]) + end + + def assigned_issue + @assigned_issue ||= create :issue, assignee: current_user, project: project + end + + def authored_issue + @authored_issue ||= create :issue, author: current_user, project: project + end + + def other_issue + @other_issue ||= create :issue, project: project + end - 2.times { create :issue, author: @user, assignee: @user, project: project } + def project + @project ||= begin + project =create :project + project.team << [current_user, :master] + project + end end end diff --git a/features/steps/dashboard/dashboard_merge_requests.rb b/features/steps/dashboard/dashboard_merge_requests.rb index 6c1fa39f0811c298f83f6ea64d15d753bb93ee04..62d84506c496e7fca52a250752c02eed3afdb12a 100644 --- a/features/steps/dashboard/dashboard_merge_requests.rb +++ b/features/steps/dashboard/dashboard_merge_requests.rb @@ -2,28 +2,73 @@ class DashboardMergeRequests < Spinach::FeatureSteps include SharedAuthentication include SharedPaths - Then 'I should see my merge requests' do - merge_requests = @user.merge_requests - merge_requests.each do |mr| - page.should have_content(mr.title[0..10]) - page.should have_content(mr.target_project.name) - page.should have_content(mr.source_project.name) + step 'I should see merge requests assigned to me' do + should_see(assigned_merge_request) + should_not_see(authored_merge_request) + should_not_see(other_merge_request) + end + + step 'I should see merge requests authored by me' do + should_see(authored_merge_request) + should_not_see(assigned_merge_request) + should_not_see(other_merge_request) + end + + step 'I should see all merge requests' do + should_see(authored_merge_request) + should_see(assigned_merge_request) + should_see(other_merge_request) + end + + step 'I have authored merge requests' do + authored_merge_request + end + + step 'I have assigned merge requests' do + assigned_merge_request + end + + step 'I have other merge requests' do + other_merge_request + end + + step 'I click "Authored by me" link' do + within ".scope-filter" do + click_link 'Created by me' + end + end + + step 'I click "All" link' do + within ".scope-filter" do + click_link "Everyone's" end end - And 'I have authored merge requests' do - project1_source = create :project - project1_target= create :project - project2_source = create :project - project2_target = create :project + def should_see(merge_request) + page.should have_content(merge_request.title[0..10]) + end + def should_not_see(merge_request) + page.should_not have_content(merge_request.title[0..10]) + end - project1_source.team << [@user, :master] - project1_target.team << [@user, :master] - project2_source.team << [@user, :master] - project2_target.team << [@user, :master] + def assigned_merge_request + @assigned_merge_request ||= create :merge_request, assignee: current_user, target_project: project + end + + def authored_merge_request + @authored_merge_request ||= create :merge_request, author: current_user, target_project: project + end + + def other_merge_request + @other_merge_request ||= create :merge_request, target_project: project + end - merge_request1 = create :merge_request, author: @user, source_project: project1_source, target_project: project1_target - merge_request2 = create :merge_request, author: @user, source_project: project2_source, target_project: project2_target + def project + @project ||= begin + project =create :project + project.team << [current_user, :master] + project + end end end diff --git a/features/steps/dashboard/dashboard_with_archived_projects.rb b/features/steps/dashboard/dashboard_with_archived_projects.rb new file mode 100644 index 0000000000000000000000000000000000000000..1bc69555b56fc283f5b1ffbf055ecef0be4d9958 --- /dev/null +++ b/features/steps/dashboard/dashboard_with_archived_projects.rb @@ -0,0 +1,22 @@ +class DashboardWithArchivedProjects < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedProject + + When 'project "Forum" is archived' do + project = Project.find_by(name: "Forum") + project.update_attribute(:archived, true) + end + + Then 'I should see "Shop" project link' do + page.should have_link "Shop" + end + + Then 'I should not see "Forum" project link' do + page.should_not have_link "Forum" + end + + Then 'I should see "Forum" project link' do + page.should have_link "Forum" + end +end diff --git a/features/steps/group/group.rb b/features/steps/group/group.rb index 99ec77a7613870f1aaf3b479e4153729d8de0aa5..0b0f401c3ba727409c3fbbbee9dd10b465b069b8 100644 --- a/features/steps/group/group.rb +++ b/features/steps/group/group.rb @@ -39,8 +39,8 @@ class Groups < Spinach::FeatureSteps end And 'I select user "John" from list with role "Reporter"' do - user = User.find_by_name("John") - within ".new_users_group" do + user = User.find_by(name: "John") + within ".users-group-form" do select2(user.id, from: "#user_ids", multiple: true) select "Reporter", from: "group_access" end @@ -98,6 +98,40 @@ class Groups < Spinach::FeatureSteps end end + step 'I change my group avatar' do + attach_file(:group_avatar, File.join(Rails.root, 'public', 'gitlab_logo.png')) + click_button "Save group" + @group.reload + end + + step 'I should see new group avatar' do + @group.avatar.should be_instance_of AttachmentUploader + @group.avatar.url.should == "/uploads/group/avatar/#{ @group.id }/gitlab_logo.png" + end + + step 'I should see the "Remove avatar" button' do + page.should have_link("Remove avatar") + end + + step 'I have an group avatar' do + attach_file(:group_avatar, File.join(Rails.root, 'public', 'gitlab_logo.png')) + click_button "Save group" + @group.reload + end + + step 'I remove my group avatar' do + click_link "Remove avatar" + @group.reload + end + + step 'I should not see my group avatar' do + @group.avatar?.should be_false + end + + step 'I should not see the "Remove avatar" button' do + page.should_not have_link("Remove avatar") + end + protected def current_group diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 5b2a6321265f31e9ec99a9cd6b3d63654d3ea2af..33ae6c7299851f939a68529e9c0257d44dad3ee2 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -3,45 +3,79 @@ class Profile < Spinach::FeatureSteps include SharedPaths step 'I should see my profile info' do - page.should have_content "Profile" - page.should have_content @user.name - page.should have_content @user.email + page.should have_content "Profile settings" end - step 'I change my contact info' do + step 'I change my profile info' do fill_in "user_skype", with: "testskype" fill_in "user_linkedin", with: "testlinkedin" fill_in "user_twitter", with: "testtwitter" + fill_in "user_website_url", with: "testurl" click_button "Save changes" @user.reload end - step 'I should see new contact info' do + step 'I should see new profile info' do @user.skype.should == 'testskype' @user.linkedin.should == 'testlinkedin' @user.twitter.should == 'testtwitter' + @user.website_url.should == 'testurl' + end + + step 'I change my avatar' do + attach_file(:user_avatar, File.join(Rails.root, 'public', 'gitlab_logo.png')) + click_button "Save changes" + @user.reload + end + + step 'I should see new avatar' do + @user.avatar.should be_instance_of AttachmentUploader + @user.avatar.url.should == "/uploads/user/avatar/#{ @user.id }/gitlab_logo.png" + end + + step 'I should see the "Remove avatar" button' do + page.should have_link("Remove avatar") + end + + step 'I have an avatar' do + attach_file(:user_avatar, File.join(Rails.root, 'public', 'gitlab_logo.png')) + click_button "Save changes" + @user.reload + end + + step 'I remove my avatar' do + click_link "Remove avatar" + @user.reload + end + + step 'I should see my gravatar' do + @user.avatar?.should be_false + end + + step 'I should not see the "Remove avatar" button' do + page.should_not have_link("Remove avatar") end step 'I try change my password w/o old one' do within '.update-password' do - fill_in "user_password", with: "222333" - fill_in "user_password_confirmation", with: "222333" + fill_in "user_password", with: "22233344" + fill_in "user_password_confirmation", with: "22233344" click_button "Save" end end step 'I change my password' do within '.update-password' do - fill_in "user_current_password", with: "123456" - fill_in "user_password", with: "222333" - fill_in "user_password_confirmation", with: "222333" + fill_in "user_current_password", with: "12345678" + fill_in "user_password", with: "22233344" + fill_in "user_password_confirmation", with: "22233344" click_button "Save" end end step 'I unsuccessfully change my password' do within '.update-password' do - fill_in "user_current_password", with: "123456" + fill_in "user_current_password", with: "12345678" fill_in "user_password", with: "password" fill_in "user_password_confirmation", with: "confirmation" click_button "Save" @@ -53,11 +87,7 @@ class Profile < Spinach::FeatureSteps end step "I should see a password error message" do - page.should have_content "Password doesn't match confirmation" - end - - step 'I should be redirected to sign in page' do - current_path.should == new_user_session_path + page.should have_content "Password confirmation doesn't match" end step 'I reset my token' do @@ -124,8 +154,12 @@ class Profile < Spinach::FeatureSteps current_path.should == new_user_session_path end + step 'I should be redirected to password page' do + current_path.should == edit_profile_password_path + end + step 'I should be redirected to account page' do - current_path.should == account_profile_path + current_path.should == profile_account_path end step 'I click on my profile picture' do diff --git a/features/steps/profile/profile_ssh_keys.rb b/features/steps/profile/profile_ssh_keys.rb index 65bfc505d85834a9e3d0e84ebeb8f2817e04bdd2..65ca824bb5b24941b6aa9027cb0025d87393e7df 100644 --- a/features/steps/profile/profile_ssh_keys.rb +++ b/features/steps/profile/profile_ssh_keys.rb @@ -18,7 +18,7 @@ class ProfileSshKeys < Spinach::FeatureSteps end Then 'I should see new ssh key "Laptop"' do - key = Key.find_by_title("Laptop") + key = Key.find_by(title: "Laptop") page.should have_content(key.title) page.should have_content(key.key) current_path.should == profile_key_path(key) diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb index 7f7492bfd6d23bc5cf8574e0fc6aceed01e43b5d..914da31322f6c42beb0bb7d4d565e3ef39719e1c 100644 --- a/features/steps/project/deploy_keys.rb +++ b/features/steps/project/deploy_keys.rb @@ -34,7 +34,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps end step 'other project has deploy key' do - @second_project = create :project, namespace: current_user.namespace + @second_project = create :project, namespace: create(:group) @second_project.team << [current_user, :master] create(:deploy_keys_project, project: @second_project) end diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index a96b086fae56a77580667586c1609390471f3305..92728d474b2625e0418c9cbb6c474cca0c044932 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -3,16 +3,25 @@ class ProjectFeature < Spinach::FeatureSteps include SharedProject include SharedPaths - And 'change project settings' do + step 'change project settings' do fill_in 'project_name', with: 'NewName' uncheck 'project_issues_enabled' end - And 'I save project' do + step 'I save project' do click_button 'Save changes' end - Then 'I should see project with new settings' do + step 'I should see project with new settings' do find_field('project_name').value.should == 'NewName' end + + step 'change project path settings' do + fill_in "project_path", with: "new-path" + click_button "Rename" + end + + step 'I should see project with new path settings' do + project.path.should == "new-path" + end end diff --git a/features/steps/project/project_archived.rb b/features/steps/project/project_archived.rb new file mode 100644 index 0000000000000000000000000000000000000000..dfbe762c438389548d49b22dfd640540cf13f252 --- /dev/null +++ b/features/steps/project/project_archived.rb @@ -0,0 +1,37 @@ +class ProjectArchived < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + When 'project "Forum" is archived' do + project = Project.find_by(name: "Forum") + project.update_attribute(:archived, true) + end + + When 'project "Shop" is archived' do + project = Project.find_by(name: "Shop") + project.update_attribute(:archived, true) + end + + When 'I visit project "Forum" page' do + project = Project.find_by(name: "Forum") + visit project_path(project) + end + + Then 'I should not see "Archived"' do + page.should_not have_content "Archived" + end + + Then 'I should see "Archived"' do + page.should have_content "Archived" + end + + When 'I set project archived' do + click_link "Archive" + end + + When 'I set project unarchived' do + click_link "Unarchive" + end + +end \ No newline at end of file diff --git a/features/steps/project/project_browse_branches.rb b/features/steps/project/project_browse_branches.rb index e77825411f306ceeda49ec46d15fd940c59a0f8b..ef29cc67a4eaa05a7e4517ede9326bf85bbe7354 100644 --- a/features/steps/project/project_browse_branches.rb +++ b/features/steps/project/project_browse_branches.rb @@ -29,7 +29,7 @@ class ProjectBrowseBranches < Spinach::FeatureSteps end And 'project "Shop" has protected branches' do - project = Project.find_by_name("Shop") + project = Project.find_by(name: "Shop") project.protected_branches.create(name: "stable") end end diff --git a/features/steps/project/project_browse_commits.rb b/features/steps/project/project_browse_commits.rb index 650bc3a16f7ad1363443da3f63aabd409bead6ee..d667b58240fcccb0eab59f4c31b87ca24dcf9eae 100644 --- a/features/steps/project/project_browse_commits.rb +++ b/features/steps/project/project_browse_commits.rb @@ -88,4 +88,17 @@ class ProjectBrowseCommits < Spinach::FeatureSteps links[0]['href'].should =~ %r{blob/bc3735004cb45cec5e0e4fa92710897a910a5957} links[1]['href'].should =~ %r{blob/cc1ba255d6c5ffdce87a357ba7ccc397a4f4026b} end + + Given 'I click side-by-side diff button' do + click_link "Side-by-side Diff" + end + + Then 'I see side-by-side diff button' do + page.should have_content "Side-by-side Diff" + end + + Then 'I see inline diff button' do + page.should have_content "Inline Diff" + end + end diff --git a/features/steps/project/project_browse_files.rb b/features/steps/project/project_browse_files.rb index 71360fb6bd5caf9f32b7d6a622382f89a3509d4c..069086d5eac996d3c591d698ee0e2b8af101f985 100644 --- a/features/steps/project/project_browse_files.rb +++ b/features/steps/project/project_browse_files.rb @@ -3,42 +3,51 @@ class ProjectBrowseFiles < Spinach::FeatureSteps include SharedProject include SharedPaths - Then 'I should see files from repository' do + step 'I should see files from repository' do page.should have_content "app" page.should have_content "history" page.should have_content "Gemfile" end - Then 'I should see files from repository for "8470d70"' do + step 'I should see files from repository for "8470d70"' do current_path.should == project_tree_path(@project, "8470d70") page.should have_content "app" page.should have_content "history" page.should have_content "Gemfile" end - Given 'I click on "Gemfile.lock" file in repo' do + step 'I click on "Gemfile.lock" file in repo' do click_link "Gemfile.lock" end - Then 'I should see it content' do + step 'I should see it content' do page.should have_content "DEPENDENCIES" end - And 'I click link "raw"' do + step 'I click link "raw"' do click_link "raw" end - Then 'I should see raw file content' do + step 'I should see raw file content' do page.source.should == ValidCommit::BLOB_FILE end - Given 'I click button "edit"' do + step 'I click button "edit"' do click_link 'edit' end - Then 'I can edit code' do + step 'I can edit code' do page.execute_script('editor.setValue("GitlabFileEditor")') page.evaluate_script('editor.getValue()').should == "GitlabFileEditor" end + step 'I click on "new file" link in repo' do + click_link 'new-file-link' + end + + step 'I can see new file page' do + page.should have_content "New file" + page.should have_content "File name" + page.should have_content "Commit message" + end end diff --git a/features/steps/project/project_fork.rb b/features/steps/project/project_fork.rb index 858c7d11b32ff1fe0f3e80ea0393015bcfb40527..c00d9014b1d9001abdaa648d149f0b685d2b075d 100644 --- a/features/steps/project/project_fork.rb +++ b/features/steps/project/project_fork.rb @@ -11,22 +11,22 @@ class ForkProject < Spinach::FeatureSteps end step 'I am a member of project "Shop"' do - @project = Project.find_by_name "Shop" - @project ||= create(:project_with_code, name: "Shop", group: create(:group)) + @project = Project.find_by(name: "Shop") + @project ||= create(:project, name: "Shop", group: create(:group)) @project.team << [@user, :reporter] end step 'I should see the forked project page' do page.should have_content "Project was successfully forked." current_path.should include current_user.namespace.path - @forked_project = Project.find_by_namespace_id(current_user.namespace.path) + @forked_project = Project.find_by(namespace_id: current_user.namespace.path) end step 'I already have a project named "Shop" in my namespace' do current_user.namespace ||= create(:namespace) current_user.namespace.should_not be_nil current_user.namespace.path.should_not be_nil - @my_project = create(:project_with_code, name: "Shop", namespace: current_user.namespace) + @my_project = create(:project, name: "Shop", namespace: current_user.namespace) end step 'I should see a "Name has already been taken" warning' do diff --git a/features/steps/project/project_forked_merge_requests.rb b/features/steps/project/project_forked_merge_requests.rb index f7bf085a4231199a0fbbcefcd469ce89858df72f..4cc99f8af55f6fce1c523aa6919371698787baee 100644 --- a/features/steps/project/project_forked_merge_requests.rb +++ b/features/steps/project/project_forked_merge_requests.rb @@ -3,19 +3,19 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps include SharedProject include SharedNote include SharedPaths - include ChosenHelper + include Select2Helper step 'I am a member of project "Shop"' do - @project = Project.find_by_name "Shop" - @project ||= create(:project_with_code, name: "Shop") + @project = Project.find_by(name: "Shop") + @project ||= create(:project, name: "Shop") @project.team << [@user, :reporter] end step 'I have a project forked off of "Shop" called "Forked Shop"' do @forking_user = @user forked_project_link = build(:forked_project_link) - @forked_project = Project.find_by_name "Forked Shop" - @forked_project ||= create(:source_project_with_code, name: "Forked Shop", forked_project_link: forked_project_link, creator_id: @forking_user.id , namespace: @forking_user.namespace) + @forked_project = Project.find_by(name: "Forked Shop") + @forked_project ||= create(:project, name: "Forked Shop", forked_project_link: forked_project_link, creator_id: @forking_user.id , namespace: @forking_user.namespace) forked_project_link.forked_from_project = @project forked_project_link.forked_to_project = @forked_project @@ -42,14 +42,14 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps end step 'I fill out a "Merge Request On Forked Project" merge request' do - chosen @forked_project.id, from: "#merge_request_source_project_id" - chosen @project.id, from: "#merge_request_target_project_id" + select2 @forked_project.id, from: "#merge_request_source_project_id" + select2 @project.id, from: "#merge_request_target_project_id" find(:select, "merge_request_source_project_id", {}).value.should == @forked_project.id.to_s find(:select, "merge_request_target_project_id", {}).value.should == @project.id.to_s - chosen "master", from: "#merge_request_source_branch" - chosen "stable", from: "#merge_request_target_branch" + select2 "master", from: "#merge_request_source_branch" + select2 "stable", from: "#merge_request_target_branch" find(:select, "merge_request_source_branch", {}).value.should == 'master' find(:select, "merge_request_target_branch", {}).value.should == 'stable' @@ -114,7 +114,7 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps end step 'project "Forked Shop" has push event' do - @forked_project = Project.find_by_name("Forked Shop") + @forked_project = Project.find_by(name: "Forked Shop") data = { before: "0000000000000000000000000000000000000000", @@ -172,7 +172,7 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps end def project - @project ||= Project.find_by_name!("Shop") + @project ||= Project.find_by!(name: "Shop") end # Verify a link is generated against the correct project diff --git a/features/steps/project/project_graph.rb b/features/steps/project/project_graph.rb index 50942b3cbb3210017aed126f7896c7405e2a17e4..89fe5fdeadfbcc3ac53fecc7e2f15c3c73464132 100644 --- a/features/steps/project/project_graph.rb +++ b/features/steps/project/project_graph.rb @@ -7,7 +7,7 @@ class ProjectGraph < Spinach::FeatureSteps end When 'I visit project "Shop" graph page' do - project = Project.find_by_name("Shop") + project = Project.find_by(name: "Shop") visit project_graph_path(project, "master") end end diff --git a/features/steps/project/project_hooks.rb b/features/steps/project/project_hooks.rb index 36555fb8e8cfa40699a072f01dec0887fd153473..19ff324454350b94e5cb43e94997fae273bc0ad2 100644 --- a/features/steps/project/project_hooks.rb +++ b/features/steps/project/project_hooks.rb @@ -1,9 +1,12 @@ +require 'webmock' + class ProjectHooks < Spinach::FeatureSteps include SharedAuthentication include SharedProject include SharedPaths include RSpec::Matchers include RSpec::Mocks::ExampleMethods + include WebMock::API Given 'project has hook' do @hook = create(:project_hook, project: current_project) @@ -25,8 +28,7 @@ class ProjectHooks < Spinach::FeatureSteps end When 'I click test hook button' do - test_hook_context = double(execute: true) - TestHookContext.should_receive(:new).and_return(test_hook_context) + stub_request(:post, @hook.url).to_return(status: 200) click_link 'Test Hook' end diff --git a/features/steps/project/project_issue_tracker.rb b/features/steps/project/project_issue_tracker.rb new file mode 100644 index 0000000000000000000000000000000000000000..c2fd4e15c9eb059ae8a282119ac0b27c48473cad --- /dev/null +++ b/features/steps/project/project_issue_tracker.rb @@ -0,0 +1,31 @@ +class ProjectIssueTracker < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + step 'project "Shop" has issues enabled' do + @project = Project.find_by(name: "Shop") + @project ||= create(:project, name: "Shop", namespace: @user.namespace) + @project.issues_enabled = true + end + + step 'change the issue tracker to "GitLab"' do + select 'GitLab', from: 'project_issues_tracker' + end + + step 'I the project should have "GitLab" as issue tracker' do + find_field('project_issues_tracker').value.should == 'gitlab' + end + + step 'change the issue tracker to "Redmine"' do + select 'Redmine', from: 'project_issues_tracker' + end + + step 'I the project should have "Redmine" as issue tracker' do + find_field('project_issues_tracker').value.should == 'redmine' + end + + And 'I save project' do + click_button 'Save changes' + end +end diff --git a/features/steps/project/project_issues.rb b/features/steps/project/project_issues.rb index 801fff78a52289a06fadc7b63f4cc192045e015c..4a503dfaf4f50618f36ef3897fb94467ecf9370d 100644 --- a/features/steps/project/project_issues.rb +++ b/features/steps/project/project_issues.rb @@ -54,7 +54,7 @@ class ProjectIssues < Spinach::FeatureSteps end Then 'I should see issue "500 error on profile"' do - issue = Issue.find_by_title("500 error on profile") + issue = Issue.find_by(title: "500 error on profile") page.should have_content issue.title page.should have_content issue.author_name page.should have_content issue.project.name @@ -81,14 +81,14 @@ class ProjectIssues < Spinach::FeatureSteps end Given 'project "Shop" has milestone "v2.2"' do - project = Project.find_by_name("Shop") + project = Project.find_by(name: "Shop") milestone = create(:milestone, title: "v2.2", project: project) 3.times { create(:issue, project: project, milestone: milestone) } end And 'project "Shop" has milestone "v3.0"' do - project = Project.find_by_name("Shop") + project = Project.find_by(name: "Shop") milestone = create(:milestone, title: "v3.0", project: project) 3.times { create(:issue, project: project, milestone: milestone) } @@ -104,20 +104,20 @@ class ProjectIssues < Spinach::FeatureSteps end When 'I select first assignee from "Shop" project' do - project = Project.find_by_name "Shop" + project = Project.find_by(name: "Shop") first_assignee = project.users.first select first_assignee.name, from: "assignee_id" end Then 'I should see first assignee from "Shop" as selected assignee' do issues_assignee_selector = "#issue_assignee_id_chzn > a" - project = Project.find_by_name "Shop" + project = Project.find_by(name: "Shop") assignee_name = project.users.first.name page.find(issues_assignee_selector).should have_content(assignee_name) end And 'project "Shop" have "Release 0.4" open issue' do - project = Project.find_by_name("Shop") + project = Project.find_by(name: "Shop") create(:issue, title: "Release 0.4", project: project, @@ -125,7 +125,7 @@ class ProjectIssues < Spinach::FeatureSteps end And 'project "Shop" have "Tweet control" open issue' do - project = Project.find_by_name("Shop") + project = Project.find_by(name: "Shop") create(:issue, title: "Tweet control", project: project, @@ -133,7 +133,7 @@ class ProjectIssues < Spinach::FeatureSteps end And 'project "Shop" have "Release 0.3" closed issue' do - project = Project.find_by_name("Shop") + project = Project.find_by(name: "Shop") create(:closed_issue, title: "Release 0.3", project: project, diff --git a/features/steps/project/project_labels.rb b/features/steps/project/project_labels.rb index 915190f3dae2f1e9f05bb5c7257084e2684446d2..0907cdb526f5b1178abc28393504bb62ccf319b7 100644 --- a/features/steps/project/project_labels.rb +++ b/features/steps/project/project_labels.rb @@ -16,7 +16,7 @@ class ProjectLabels < Spinach::FeatureSteps end And 'project "Shop" have issues tags: "bug", "feature"' do - project = Project.find_by_name("Shop") + project = Project.find_by(name: "Shop") ['bug', 'feature'].each do |label| create(:issue, project: project, label_list: label) end diff --git a/features/steps/project/project_markdown_render.rb b/features/steps/project/project_markdown_render.rb new file mode 100644 index 0000000000000000000000000000000000000000..1209aae64344c56fad338b7918f75926e9e3cbc8 --- /dev/null +++ b/features/steps/project/project_markdown_render.rb @@ -0,0 +1,201 @@ +class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + + And 'I own project "Delta"' do + @project = Project.find_by(name: "Delta") + @project ||= create(:project, name: "Delta", namespace: @user.namespace) + @project.team << [@user, :master] + end + + Then 'I should see files from repository in master' do + current_path.should == project_tree_path(@project, "master") + page.should have_content "Gemfile" + page.should have_content "app" + page.should have_content "README" + end + + And 'I should see rendered README which contains correct links' do + page.should have_content "Welcome to GitLab GitLab is a free project and repository management application" + page.should have_link "GitLab API doc" + page.should have_link "GitLab API website" + page.should have_link "Rake tasks" + page.should have_link "backup and restore procedure" + page.should have_link "GitLab API doc directory" + page.should have_link "Maintenance" + end + + And 'I click on Gitlab API in README' do + click_link "GitLab API doc" + end + + Then 'I should see correct document rendered' do + current_path.should == project_blob_path(@project, "master/doc/api/README.md") + page.should have_content "All API requests require authentication" + end + + And 'I click on Rake tasks in README' do + click_link "Rake tasks" + end + + Then 'I should see correct directory rendered' do + current_path.should == project_tree_path(@project, "master/doc/raketasks") + page.should have_content "backup_restore.md" + page.should have_content "maintenance.md" + end + + + And 'I click on GitLab API doc directory in README' do + click_link "GitLab API doc directory" + end + + Then 'I should see correct doc/api directory rendered' do + current_path.should == project_tree_path(@project, "master/doc/api/") + page.should have_content "README.md" + page.should have_content "users.md" + end + + And 'I click on Maintenance in README' do + click_link "Maintenance" + end + + Then 'I should see correct maintenance file rendered' do + current_path.should == project_blob_path(@project, "master/doc/raketasks/maintenance.md") + page.should have_content "bundle exec rake gitlab:env:info RAILS_ENV=production" + end + + And 'I navigate to the doc/api/README' do + click_link "doc" + click_link "api" + click_link "README.md" + end + + And 'I see correct file rendered' do + current_path.should == project_blob_path(@project, "master/doc/api/README.md") + page.should have_content "Contents" + page.should have_link "Users" + page.should have_link "Rake tasks" + end + + And 'I click on users in doc/api/README' do + click_link "Users" + end + + Then 'I should see the correct document file' do + current_path.should == project_blob_path(@project, "master/doc/api/users.md") + page.should have_content "Get a list of users." + end + + And 'I click on raketasks in doc/api/README' do + click_link "Rake tasks" + end + + When 'I visit markdown branch' do + visit project_tree_path(@project, "markdown") + end + + Then 'I should see files from repository in markdown branch' do + current_path.should == project_tree_path(@project, "markdown") + page.should have_content "Gemfile" + page.should have_content "app" + page.should have_content "README" + end + + And 'I see correct file rendered in markdown branch' do + current_path.should == project_blob_path(@project, "markdown/doc/api/README.md") + page.should have_content "Contents" + page.should have_link "Users" + page.should have_link "Rake tasks" + end + + Then 'I should see correct document rendered for markdown branch' do + current_path.should == project_blob_path(@project, "markdown/doc/api/README.md") + page.should have_content "All API requests require authentication" + end + + Then 'I should see correct directory rendered for markdown branch' do + current_path.should == project_tree_path(@project, "markdown/doc/raketasks") + page.should have_content "backup_restore.md" + page.should have_content "maintenance.md" + end + + Then 'I should see the users document file in markdown branch' do + current_path.should == project_blob_path(@project, "markdown/doc/api/users.md") + page.should have_content "Get a list of users." + end + + Given 'I go to wiki page' do + click_link "Wiki" + current_path.should == project_wiki_path(@project, "home") + end + + And 'I add various links to the wiki page' do + fill_in "wiki[content]", with: "[test](test)\n[GitLab API doc](doc/api/README.md)\n[Rake tasks](doc/raketasks)\n" + fill_in "wiki[message]", with: "Adding links to wiki" + click_button "Create page" + end + + Then 'Wiki page should have added links' do + current_path.should == project_wiki_path(@project, "home") + page.should have_content "test GitLab API doc Rake tasks" + end + + And 'I click on test link' do + click_link "test" + end + + Then 'I see new wiki page named test' do + current_path.should == project_wiki_path(@project, "test") + page.should have_content "Editing" + end + + When 'I go back to wiki page home' do + visit project_wiki_path(@project, "home") + current_path.should == project_wiki_path(@project, "home") + end + + And 'I click on GitLab API doc link' do + click_link "GitLab API" + end + + Then 'I see Gitlab API document' do + current_path.should == project_blob_path(@project, "master/doc/api/README.md") + page.should have_content "Status codes" + end + + And 'I click on Rake tasks link' do + click_link "Rake tasks" + end + + Then 'I see Rake tasks directory' do + current_path.should == project_tree_path(@project, "master/doc/raketasks") + page.should have_content "backup_restore.md" + page.should have_content "maintenance.md" + end + + Given 'I visit to the help page' do + visit help_path + end + + And 'I select a page with markdown' do + click_link "Rake Tasks" + end + + Then 'I should see a help page with markdown' do + page.should have_content "GitLab provides some specific rake tasks to enable special features or perform maintenance tasks" + end + + Given 'I go directory which contains README file' do + visit project_tree_path(@project, "master/doc/api") + current_path.should == project_tree_path(@project, "master/doc/api") + end + + And 'I click on a relative link in README' do + click_link "Users" + end + + Then 'I should see the correct markdown' do + current_path.should == project_blob_path(@project, "master/doc/api/users.md") + page.should have_content "List users" + end +end diff --git a/features/steps/project/project_merge_requests.rb b/features/steps/project/project_merge_requests.rb index 7c70482deb56842d5fb0c706472e253c93bd4c19..0c5f05a0a68d3c934a5a6dbf9f2b9bf048198c69 100644 --- a/features/steps/project/project_merge_requests.rb +++ b/features/steps/project/project_merge_requests.rb @@ -4,81 +4,89 @@ class ProjectMergeRequests < Spinach::FeatureSteps include SharedNote include SharedPaths - Given 'I click link "New Merge Request"' do + step 'I click link "New Merge Request"' do click_link "New Merge Request" end - Given 'I click link "Bug NS-04"' do + step 'I click link "Bug NS-04"' do click_link "Bug NS-04" end - Given 'I click link "All"' do + step 'I click link "All"' do click_link "All" end - Given 'I click link "Closed"' do + step 'I click link "Closed"' do click_link "Closed" end - Then 'I should see merge request "Wiki Feature"' do - page.should have_content "Wiki Feature" + step 'I should see merge request "Wiki Feature"' do + within '.merge-request' do + page.should have_content "Wiki Feature" + end end - Then 'I should see closed merge request "Bug NS-04"' do - merge_request = MergeRequest.find_by_title!("Bug NS-04") + step 'I should see closed merge request "Bug NS-04"' do + merge_request = MergeRequest.find_by!(title: "Bug NS-04") merge_request.closed?.should be_true page.should have_content "Closed by" end - Then 'I should see merge request "Bug NS-04"' do + step 'I should see merge request "Bug NS-04"' do page.should have_content "Bug NS-04" end - Then 'I should see "Bug NS-04" in merge requests' do + step 'I should see "Bug NS-04" in merge requests' do page.should have_content "Bug NS-04" end - Then 'I should see "Feature NS-03" in merge requests' do + step 'I should see "Feature NS-03" in merge requests' do page.should have_content "Feature NS-03" end - And 'I should not see "Feature NS-03" in merge requests' do + step 'I should not see "Feature NS-03" in merge requests' do page.should_not have_content "Feature NS-03" end - And 'I should not see "Bug NS-04" in merge requests' do + step 'I should not see "Bug NS-04" in merge requests' do page.should_not have_content "Bug NS-04" end - And 'I click link "Close"' do + step 'I click link "Close"' do click_link "Close" end - And 'I submit new merge request "Wiki Feature"' do - #this must come first, so that the target branch is set by the time the "select" for "notes_refactoring" is executed - select project.path_with_namespace, :from => "merge_request_target_project_id" - fill_in "merge_request_title", :with => "Wiki Feature" - select "master", :from => "merge_request_source_branch" + step 'I submit new merge request "Wiki Feature"' do + fill_in "merge_request_title", with: "Wiki Feature" + + # this must come first, so that the target branch is set + # by the time the "select" for "notes_refactoring" is executed + select project.path_with_namespace, from: "merge_request_target_project_id" + select "master", from: "merge_request_source_branch" + find(:select, "merge_request_target_project_id", {}).value.should == project.id.to_s find(:select, "merge_request_source_project_id", {}).value.should == project.id.to_s - #using "notes_refactoring" because "Bug NS-04" uses master/stable, this will fail merge_request validation if the branches are the same + # using "notes_refactoring" because "Bug NS-04" uses master/stable, + # this will fail merge_request validation if the branches are the same find(:select, "merge_request_target_branch", {}).find(:option, "notes_refactoring", {}).value.should == "notes_refactoring" - select "notes_refactoring", :from => "merge_request_target_branch" + select "notes_refactoring", from: "merge_request_target_branch" click_button "Submit merge request" end - And 'project "Shop" have "Bug NS-04" open merge request' do + step 'project "Shop" have "Bug NS-04" open merge request' do create(:merge_request, title: "Bug NS-04", source_project: project, target_project: project, + source_branch: 'stable', + target_branch: 'master', author: project.users.first) end - And 'project "Shop" have "Bug NS-05" open merge request with diffs inside' do + step 'project "Shop" have "Bug NS-05" open merge request with diffs inside' do create(:merge_request_with_diffs, title: "Bug NS-05", source_project: project, @@ -86,7 +94,7 @@ class ProjectMergeRequests < Spinach::FeatureSteps author: project.users.first) end - And 'project "Shop" have "Feature NS-03" closed merge request' do + step 'project "Shop" have "Feature NS-03" closed merge request' do create(:closed_merge_request, title: "Feature NS-03", source_project: project, @@ -94,65 +102,101 @@ class ProjectMergeRequests < Spinach::FeatureSteps author: project.users.first) end - And 'I switch to the diff tab' do + step 'I switch to the diff tab' do visit diffs_project_merge_request_path(project, merge_request) end - And 'I switch to the merge request\'s comments tab' do + step 'I switch to the merge request\'s comments tab' do visit project_merge_request_path(project, merge_request) end - And 'I click on the first commit in the merge request' do - - click_link merge_request.commits.first.short_id(8) + step 'I click on the first commit in the merge request' do + within '.first-commits' do + click_link merge_request.commits.first.short_id(8) + end end - And 'I leave a comment on the diff page' do + step 'I leave a comment on the diff page' do init_diff_note + leave_comment "One comment to rule them all" + end - within('.js-temp-notes-holder') do - fill_in "note_note", with: "One comment to rule them all" - click_button "Add Comment" - end + step 'I leave a comment on the diff page in commit' do + find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185"]').click + leave_comment "One comment to rule them all" end - And 'I leave a comment like "Line is wrong" on line 185 of the first file' do + step 'I leave a comment like "Line is wrong" on line 185 of the first file' do init_diff_note + leave_comment "Line is wrong" + end - within(".js-temp-notes-holder") do - fill_in "note_note", with: "Line is wrong" - click_button "Add Comment" - sleep 0.05 - end + step 'I leave a comment like "Line is wrong" on line 185 of the first file in commit' do + find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185"]').click + leave_comment "Line is wrong" end - Then 'I should see a discussion has started on line 185' do + step 'I should see a discussion has started on line 185' do page.should have_content "#{current_user.name} started a discussion on this merge request diff" page.should have_content "app/assets/stylesheets/tree.scss:L185" page.should have_content "Line is wrong" end - Then 'I should see a discussion has started on commit bcf03b5de6c:L185' do + step 'I should see a discussion has started on commit b1e6a9dbf1:L185' do page.should have_content "#{current_user.name} started a discussion on commit" page.should have_content "app/assets/stylesheets/tree.scss:L185" page.should have_content "Line is wrong" end - Then 'I should see a discussion has started on commit bcf03b5de6c' do - page.should have_content "#{current_user.name} started a discussion on commit bcf03b5de6c" + step 'I should see a discussion has started on commit b1e6a9dbf1' do + page.should have_content "#{current_user.name} started a discussion on commit" page.should have_content "One comment to rule them all" page.should have_content "app/assets/stylesheets/tree.scss:L185" end + step 'merge request is mergeable' do + page.should have_content 'You can accept this request automatically' + end + + step 'I modify merge commit message' do + find('.modify-merge-commit-link').click + fill_in 'merge_commit_message', with: "wow such merge" + end + + step 'merge request "Bug NS-05" is mergeable' do + merge_request.mark_as_mergeable + end + + step 'I accept this merge request' do + click_button "Accept Merge Request" + end + + step 'I should see merged request' do + within '.page-title' do + page.should have_content "Merged" + end + end + def project - @project ||= Project.find_by_name!("Shop") + @project ||= Project.find_by!(name: "Shop") end def merge_request - @merge_request ||= MergeRequest.find_by_title!("Bug NS-05") + @merge_request ||= MergeRequest.find_by!(title: "Bug NS-05") end def init_diff_note - find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185"]').click + find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_172_185"]').click + end + + def leave_comment(message) + within(".js-discussion-note-form") do + fill_in "note_note", with: message + click_button "Add Comment" + end + + within ".note-text" do + page.should have_content message + end end end diff --git a/features/steps/project/project_milestones.rb b/features/steps/project/project_milestones.rb index c4d0d176f3a8d5f7761db57df7156b2876e5df19..85962221c0fcb56edead27f88cde6097d545a9be 100644 --- a/features/steps/project/project_milestones.rb +++ b/features/steps/project/project_milestones.rb @@ -4,7 +4,7 @@ class ProjectMilestones < Spinach::FeatureSteps include SharedPaths Then 'I should see milestone "v2.2"' do - 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.expires_at) page.should have_content("Browse Issues") @@ -24,22 +24,22 @@ class ProjectMilestones < Spinach::FeatureSteps end Then 'I should see milestone "v2.3"' do - 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.expires_at) page.should have_content("Browse Issues") end And 'project "Shop" has milestone "v2.2"' do - project = Project.find_by_name("Shop") + project = Project.find_by(name: "Shop") milestone = create(:milestone, title: "v2.2", project: project) 3.times { create(:issue, project: project, milestone: milestone) } end Given 'the milestone has open and closed issues' do - project = Project.find_by_name("Shop") - milestone = project.milestones.find_by_title('v2.2') + project = Project.find_by(name: "Shop") + milestone = project.milestones.find_by(title: 'v2.2') # 3 Open issues created above; create one closed issue create(:closed_issue, project: project, milestone: milestone) diff --git a/features/steps/project/project_multiselect_blob.rb b/features/steps/project/project_multiselect_blob.rb new file mode 100644 index 0000000000000000000000000000000000000000..3d330e837c1992111edf86f0fc1bcecd36eed71d --- /dev/null +++ b/features/steps/project/project_multiselect_blob.rb @@ -0,0 +1,58 @@ +class ProjectMultiselectBlob < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + class << self + def click_line_steps(*line_numbers) + line_numbers.each do |line_number| + step "I click line #{line_number} in file" do + find("#L#{line_number}").click + end + + step "I shift-click line #{line_number} in file" do + script = "$('#L#{line_number}').trigger($.Event('click', { shiftKey: true }));" + page.evaluate_script(script) + end + end + end + + def check_state_steps(*ranges) + ranges.each do |range| + fragment = range.kind_of?(Array) ? "L#{range.first}-#{range.last}" : "L#{range}" + pluralization = range.kind_of?(Array) ? "s" : "" + + step "I should see \"#{fragment}\" as URI fragment" do + URI.parse(current_url).fragment.should == fragment + end + + step "I should see line#{pluralization} #{fragment[1..-1]} highlighted" do + ids = Array(range).map { |n| "LC#{n}" } + extra = false + + highlighted = all("#tree-content-holder .highlight .line.hll") + highlighted.each do |element| + extra ||= ids.delete(element[:id]).nil? + end + + extra.should be_false and ids.should be_empty + end + end + end + end + + click_line_steps *Array(1..5) + check_state_steps *Array(1..5), Array(1..2), Array(1..3), Array(1..4), Array(1..5), Array(3..5) + + step 'I go back in history' do + page.evaluate_script("window.history.back()") + end + + step 'I go forward in history' do + page.evaluate_script("window.history.forward()") + end + + step 'I click on "Gemfile.lock" file in repo' do + click_link "Gemfile.lock" + end +end \ No newline at end of file diff --git a/features/steps/project/project_network_graph.rb b/features/steps/project/project_network_graph.rb index 127adecf7ed089f37ade7086cc84ec7a1d6fdc87..c7d9ece6feb82b1d4d25c7bd44e4ecbca634b937 100644 --- a/features/steps/project/project_network_graph.rb +++ b/features/steps/project/project_network_graph.rb @@ -10,16 +10,16 @@ class ProjectNetworkGraph < Spinach::FeatureSteps # Stub Graph max_size to speed up test (10 commits vs. 650) Network::Graph.stub(max_count: 10) - project = Project.find_by_name("Shop") + project = Project.find_by(name: "Shop") visit project_network_path(project, "master") end And 'page should select "master" in select box' do - page.should have_selector '.chosen-single span', text: "master" + page.should have_selector '.select2-chosen', text: "master" end And 'page should select "v2.1.0" in select box' do - page.should have_selector '.chosen-single span', text: "v2.1.0" + page.should have_selector '.select2-chosen', text: "v2.1.0" end And 'page should have "master" on graph' do @@ -43,24 +43,24 @@ class ProjectNetworkGraph < Spinach::FeatureSteps sleep 2 end - Then 'page should have content not cotaining "v2.1.0"' do + Then 'page should have content not containing "v2.1.0"' do within '.network-graph' do page.should have_content 'cleaning' end end - Then 'page should not have content not cotaining "v2.1.0"' do + Then 'page should not have content not containing "v2.1.0"' do within '.network-graph' do page.should_not have_content 'cleaning' end end And 'page should select "stable" in select box' do - page.should have_selector '.chosen-single span', text: "stable" + page.should have_selector '.select2-chosen', text: "stable" end And 'page should select "v2.1.0" in select box' do - page.should have_selector '.chosen-single span', text: "v2.1.0" + page.should have_selector '.select2-chosen', text: "v2.1.0" end And 'page should have "stable" on graph' do @@ -70,7 +70,7 @@ class ProjectNetworkGraph < Spinach::FeatureSteps end When 'I looking for a commit by SHA of "v2.1.0"' do - within ".content .search" do + within ".network-form" do fill_in 'extended_sha1', with: '98d6492' find('button').click end @@ -84,7 +84,7 @@ class ProjectNetworkGraph < Spinach::FeatureSteps end When 'I look for a commit by ";"' do - within ".content .search" do + within ".network-form" do fill_in 'extended_sha1', with: ';' find('button').click end diff --git a/features/steps/project/project_services.rb b/features/steps/project/project_services.rb index a24100ff8c07866cd0999fccc9eb7d710dc2282b..54b3f18e0845a2f9503cb0f788c86fca9e1d60a2 100644 --- a/features/steps/project/project_services.rb +++ b/features/steps/project/project_services.rb @@ -3,59 +3,101 @@ class ProjectServices < Spinach::FeatureSteps include SharedProject include SharedPaths - When 'I visit project "Shop" services page' do + step 'I visit project "Shop" services page' do visit project_services_path(@project) end - Then 'I should see list of available services' do - page.should have_content 'Services' + step 'I should see list of available services' do + page.should have_content 'Project services' page.should have_content 'Campfire' page.should have_content 'Hipchat' page.should have_content 'GitLab CI' + page.should have_content 'Assembla' end - And 'I click gitlab-ci service link' do + step 'I click gitlab-ci service link' do click_link 'GitLab CI' end - And 'I fill gitlab-ci settings' do + step 'I fill gitlab-ci settings' do check 'Active' fill_in 'Project url', with: 'http://ci.gitlab.org/projects/3' fill_in 'Token', with: 'verySecret' click_button 'Save' end - Then 'I should see service settings saved' do + step 'I should see service settings saved' do find_field('Project url').value.should == 'http://ci.gitlab.org/projects/3' end - And 'I click hipchat service link' do + step 'I click hipchat service link' do click_link 'Hipchat' end - And 'I fill hipchat settings' do + step 'I fill hipchat settings' do check 'Active' fill_in 'Room', with: 'gitlab' fill_in 'Token', with: 'verySecret' click_button 'Save' end - Then 'I should see hipchat service settings saved' do + step 'I should see hipchat service settings saved' do find_field('Room').value.should == 'gitlab' end - And 'I click pivotaltracker service link' do + step 'I click pivotaltracker service link' do click_link 'PivotalTracker' end - And 'I fill pivotaltracker settings' do + step 'I fill pivotaltracker settings' do check 'Active' fill_in 'Token', with: 'verySecret' click_button 'Save' end - Then 'I should see pivotaltracker service settings saved' do + step 'I should see pivotaltracker service settings saved' do find_field('Token').value.should == 'verySecret' end + + step 'I click Flowdock service link' do + click_link 'Flowdock' + end + + step 'I fill Flowdock settings' do + check 'Active' + fill_in 'Token', with: 'verySecret' + click_button 'Save' + end + + step 'I should see Flowdock service settings saved' do + find_field('Token').value.should == 'verySecret' + end + + step 'I click Assembla service link' do + click_link 'Assembla' + end + + step 'I fill Assembla settings' do + check 'Active' + fill_in 'Token', with: 'verySecret' + click_button 'Save' + end + + step 'I should see Assembla service settings saved' do + find_field('Token').value.should == 'verySecret' + end + + step 'I click email on push service link' do + click_link 'Emails on push' + end + + step 'I fill email on push settings' do + fill_in 'Recipients', with: 'qa@company.name' + click_button 'Save' + end + + step 'I should see email on push service settings saved' do + find_field('Recipients').value.should == 'qa@company.name' + end end diff --git a/features/steps/project/project_snippets.rb b/features/steps/project/project_snippets.rb index 5082b31198acaf249a423b0cff8e5d87204acd02..c3a76bea269e9e8c20cf2250e198c8ee875155c8 100644 --- a/features/steps/project/project_snippets.rb +++ b/features/steps/project/project_snippets.rb @@ -53,7 +53,6 @@ class ProjectSnippets < Spinach::FeatureSteps And 'I submit new snippet "Snippet three"' do fill_in "project_snippet_title", :with => "Snippet three" - select "forever", :from => "project_snippet_expires_at" fill_in "project_snippet_file_name", :with => "my_snippet.rb" within('.file-editor') do find(:xpath, "//input[@id='project_snippet_content']").set 'Content of snippet three' @@ -91,10 +90,10 @@ class ProjectSnippets < Spinach::FeatureSteps end def project - @project ||= Project.find_by_name!("Shop") + @project ||= Project.find_by!(name: "Shop") end def project_snippet - @project_snippet ||= ProjectSnippet.find_by_title!("Snippet One") + @project_snippet ||= ProjectSnippet.find_by!(title: "Snippet one") end end diff --git a/features/steps/project/project_team_management.rb b/features/steps/project/project_team_management.rb index efebba1be248598f7b84d6a7fa7fd91e141db3eb..ffc5016529f1efdd8a1f56b7b19324b0ddf86521 100644 --- a/features/steps/project/project_team_management.rb +++ b/features/steps/project/project_team_management.rb @@ -10,7 +10,7 @@ class ProjectTeamManagement < Spinach::FeatureSteps end And 'I should see "Sam" in team list' do - user = User.find_by_name("Sam") + user = User.find_by(name: "Sam") page.should have_content(user.name) page.should have_content(user.username) end @@ -20,7 +20,7 @@ class ProjectTeamManagement < Spinach::FeatureSteps end And 'I select "Mike" as "Reporter"' do - user = User.find_by_name("Mike") + user = User.find_by(name: "Mike") select2(user.id, from: "#user_ids", multiple: true) within "#new_team_member" do @@ -42,7 +42,7 @@ class ProjectTeamManagement < Spinach::FeatureSteps end And 'I change "Sam" role to "Reporter"' do - user = User.find_by_name("Sam") + user = User.find_by(name: "Sam") within "#user_#{user.id}" do select "Reporter", from: "team_member_project_access" end @@ -59,7 +59,7 @@ class ProjectTeamManagement < Spinach::FeatureSteps end And 'I should not see "Sam" in team list' do - user = User.find_by_name("Sam") + user = User.find_by(name: "Sam") page.should_not have_content(user.name) page.should_not have_content(user.username) end @@ -73,19 +73,19 @@ class ProjectTeamManagement < Spinach::FeatureSteps end And '"Sam" is "Shop" developer' do - user = User.find_by_name("Sam") - project = Project.find_by_name("Shop") + user = User.find_by(name: "Sam") + project = Project.find_by(name: "Shop") project.team << [user, :developer] end Given 'I own project "Website"' do - @project = create(:project, name: "Website", namespace: @user.namespace) + @project = create(:empty_project, name: "Website", namespace: @user.namespace) @project.team << [@user, :master] end And '"Mike" is "Website" reporter' do - user = User.find_by_name("Mike") - project = Project.find_by_name("Website") + user = User.find_by(name: "Mike") + project = Project.find_by(name: "Website") project.team << [user, :reporter] end @@ -94,13 +94,13 @@ class ProjectTeamManagement < Spinach::FeatureSteps end When 'I submit "Website" project for import team' do - project = Project.find_by_name("Website") + project = Project.find_by(name: "Website") select project.name_with_namespace, from: 'source_project_id' click_button 'Import' end step 'I click cancel link for "Sam"' do - within "#user_#{User.find_by_name('Sam').id}" do + within "#user_#{User.find_by(name: 'Sam').id}" do click_link('Remove user from team') end end diff --git a/features/steps/project/project_wiki.rb b/features/steps/project/project_wiki.rb index 7aba412d751563b5e99328e17ce5bafafb617fe7..6146599cc4ac24f517184778e2a7d9e9f7981bf4 100644 --- a/features/steps/project/project_wiki.rb +++ b/features/steps/project/project_wiki.rb @@ -25,7 +25,7 @@ class ProjectWiki < Spinach::FeatureSteps page.should have_content "link test" click_link "link test" - page.should have_content "Editing page" + page.should have_content "Editing" end Given 'I have an existing Wiki page' do diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb new file mode 100644 index 0000000000000000000000000000000000000000..76ffea1bb6fae2c475f8f5122cfb1f8347dec480 --- /dev/null +++ b/features/steps/project/redirects.rb @@ -0,0 +1,35 @@ +class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedProject + + step 'public project "Community"' do + create :project, name: 'Community', visibility_level: Gitlab::VisibilityLevel::PUBLIC + end + + step 'private project "Enterprise"' do + create :project, name: 'Enterprise' + end + + step 'I visit project "Community" page' do + project = Project.find_by(name: 'Community') + visit project_path(project) + end + + step 'I should see project "Community" home page' do + within '.project-home-title' do + page.should have_content 'Community' + end + end + + step 'I visit project "Enterprise" page' do + project = Project.find_by(name: 'Enterprise') + visit project_path(project) + end + + step 'I visit project "CommunityDoesNotExist" page' do + project = Project.find_by(name: 'Community') + visit project_path(project) + 'DoesNotExist' + end +end + diff --git a/features/steps/public/projects_feature.rb b/features/steps/public/projects_feature.rb index e9a4d56e36b7f572608dbc11f445370b02644aad..84a5ebbf7a72e4bc8c2d452bec7804bfc4aa3b70 100644 --- a/features/steps/public/projects_feature.rb +++ b/features/steps/public/projects_feature.rb @@ -1,5 +1,7 @@ class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps + include SharedAuthentication include SharedPaths + include SharedProject step 'I should see project "Community"' do page.should have_content "Community" @@ -23,20 +25,20 @@ class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps end step 'public project "Community"' do - create :project_with_code, name: 'Community', public: true, default_branch: 'master' + create :project, name: 'Community', visibility_level: Gitlab::VisibilityLevel::PUBLIC end step 'public empty project "Empty Public Project"' do - create :project, name: 'Empty Public Project', public: true + create :empty_project, name: 'Empty Public Project', visibility_level: Gitlab::VisibilityLevel::PUBLIC end step 'I visit empty project page' do - project = Project.find_by_name('Empty Public Project') + project = Project.find_by(name: 'Empty Public Project') visit project_path(project) end step 'I visit project "Community" page' do - project = Project.find_by_name('Community') + project = Project.find_by(name: 'Community') visit project_path(project) end @@ -44,18 +46,155 @@ class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps page.should have_content 'Git global setup' end + step 'I should see empty public project details with http clone info' do + project = Project.find_by(name: 'Empty Public Project') + page.all(:css, '.git-empty .clone').each do |element| + element.text.should include(project.http_url_to_repo) + end + end + + step 'I should see empty public project details with ssh clone info' do + project = Project.find_by(name: 'Empty Public Project') + page.all(:css, '.git-empty .clone').each do |element| + element.text.should include(project.url_to_repo) + end + end + step 'private project "Enterprise"' do create :project, name: 'Enterprise' end + step 'I visit project "Enterprise" page' do + project = Project.find_by(name: 'Enterprise') + visit project_path(project) + end + step 'I should see project "Community" home page' do - page.should have_content 'Repo size is' + within '.project-home-title' do + page.should have_content 'Community' + end + end + + step 'internal project "Internal"' do + create :project, name: 'Internal', visibility_level: Gitlab::VisibilityLevel::INTERNAL + end + + step 'I should see project "Internal"' do + page.should have_content "Internal" + end + + step 'I should not see project "Internal"' do + page.should_not have_content "Internal" + end + + step 'I visit project "Internal" page' do + project = Project.find_by(name: 'Internal') + visit project_path(project) + end + + step 'I should see project "Internal" home page' do + within '.project-home-title' do + page.should have_content 'Internal' + end + end + + step 'I should see an http link to the repository' do + project = Project.find_by(name: 'Community') + page.should have_field('project_clone', with: project.http_url_to_repo) + end + + step 'I should see an ssh link to the repository' do + project = Project.find_by(name: 'Community') + page.should have_field('project_clone', with: project.url_to_repo) + end + + step 'I visit "Community" issues page' do + create(:issue, + title: "Bug", + project: public_project + ) + create(:issue, + title: "New feature", + project: public_project + ) + visit project_issues_path(public_project) + end + + + step 'I should see list of issues for "Community" project' do + page.should have_content "Bug" + page.should have_content public_project.name + page.should have_content "New feature" + end + + step 'I visit "Internal" issues page' do + create(:issue, + title: "Internal Bug", + project: internal_project + ) + create(:issue, + title: "New internal feature", + project: internal_project + ) + visit project_issues_path(internal_project) + end + + + step 'I should see list of issues for "Internal" project' do + page.should have_content "Internal Bug" + page.should have_content internal_project.name + page.should have_content "New internal feature" + end + + step 'I visit "Community" merge requests page' do + visit project_merge_requests_path(public_project) + end + + step 'project "Community" has "Bug fix" open merge request' do + create(:merge_request, + title: "Bug fix for public project", + source_project: public_project, + target_project: public_project, + ) + end + + step 'I should see list of merge requests for "Community" project' do + page.should have_content public_project.name + page.should have_content public_merge_request.source_project.name end - private + step 'I visit "Internal" merge requests page' do + visit project_merge_requests_path(internal_project) + end + + step 'project "Internal" has "Feature implemented" open merge request' do + create(:merge_request, + title: "Feature implemented", + source_project: internal_project, + target_project: internal_project + ) + end + + step 'I should see list of merge requests for "Internal" project' do + page.should have_content internal_project.name + page.should have_content internal_merge_request.source_project.name + end + + def internal_project + @internal_project ||= Project.find_by!(name: 'Internal') + end + + def public_project + @public_project ||= Project.find_by!(name: 'Community') + end + + + def internal_merge_request + @internal_merge_request ||= MergeRequest.find_by!(title: 'Feature implemented') + end - def project - @project ||= Project.find_by_name("Community") + def public_merge_request + @public_merge_request ||= MergeRequest.find_by!(title: 'Bug fix for public project') end end diff --git a/features/steps/shared/authentication.rb b/features/steps/shared/authentication.rb index 8c501bbc537edf281c1af5443f2dd2a522d0b987..df05754c287d3a6bf984b4c2a7ddd283032338da 100644 --- a/features/steps/shared/authentication.rb +++ b/features/steps/shared/authentication.rb @@ -12,6 +12,10 @@ module SharedAuthentication login_as :admin end + step 'I should be redirected to sign in page' do + current_path.should == new_user_session_path + end + def current_user @user || User.first end diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index c30eccce1c514b5461a7a05358e5cb92a8492f09..d287121bb8477c8a8ca30bfa420141d9a5edddd4 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -65,8 +65,12 @@ module SharedPaths visit profile_path end + step 'I visit profile password page' do + visit edit_profile_password_path + end + step 'I visit profile account page' do - visit account_profile_path + visit profile_account_path end step 'I visit profile SSH keys page' do @@ -101,6 +105,10 @@ module SharedPaths visit admin_logs_path end + step 'I visit admin messages page' do + visit admin_broadcast_messages_path + end + step 'I visit admin hooks page' do visit admin_hooks_path end @@ -233,7 +241,7 @@ module SharedPaths end step 'I visit issue page "Release 0.4"' do - issue = Issue.find_by_title("Release 0.4") + issue = Issue.find_by(title: "Release 0.4") visit project_issue_path(issue.project, issue) end @@ -242,12 +250,12 @@ module SharedPaths end step 'I visit merge request page "Bug NS-04"' do - mr = MergeRequest.find_by_title("Bug NS-04") + mr = MergeRequest.find_by(title: "Bug NS-04") visit project_merge_request_path(mr.target_project, mr) end step 'I visit merge request page "Bug NS-05"' do - mr = MergeRequest.find_by_title("Bug NS-05") + mr = MergeRequest.find_by(title: "Bug NS-05") visit project_merge_request_path(mr.target_project, mr) end @@ -284,7 +292,7 @@ module SharedPaths end step 'I visit public page for "Community" project' do - visit public_project_path(Project.find_by_name("Community")) + visit public_project_path(Project.find_by(name: "Community")) end # ---------------------------------------- @@ -308,6 +316,6 @@ module SharedPaths end def project - project = Project.find_by_name!("Shop") + project = Project.find_by!(name: "Shop") end end diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index c5d8b62bfe7d61c438d49109b1ae3ac6a2060fa8..7360482d73692d6aa38ffe6caca659d3cfe10f11 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -3,19 +3,26 @@ module SharedProject # Create a project without caring about what it's called And "I own a project" do - @project = create(:project_with_code, namespace: @user.namespace) + @project = create(:project, namespace: @user.namespace) @project.team << [@user, :master] end # Create a specific project called "Shop" And 'I own project "Shop"' do - @project = Project.find_by_name "Shop" - @project ||= create(:project_with_code, name: "Shop", namespace: @user.namespace) + @project = Project.find_by(name: "Shop") + @project ||= create(:project, name: "Shop", namespace: @user.namespace) + @project.team << [@user, :master] + end + + # Create another specific project called "Forum" + And 'I own project "Forum"' do + @project = Project.find_by(name: "Forum") + @project ||= create(:project, name: "Forum", namespace: @user.namespace, path: 'forum_project') @project.team << [@user, :master] end And 'project "Shop" has push event' do - @project = Project.find_by_name("Shop") + @project = Project.find_by(name: "Shop") data = { before: "0000000000000000000000000000000000000000", @@ -41,13 +48,13 @@ module SharedProject end Then 'I should see project "Shop" activity feed' do - project = Project.find_by_name("Shop") + project = Project.find_by(name: "Shop") page.should have_content "#{@user.name} pushed new branch new_design at #{project.name_with_namespace}" end Then 'I should see project settings' do current_path.should == edit_project_path(@project) - page.should have_content("Project name is") + page.should have_content("Project name") page.should have_content("Features:") end diff --git a/features/steps/snippets/discover_snippets.rb b/features/steps/snippets/discover_snippets.rb index 3afe019adf6db24795b0b73c23445613a0eae59d..09337937002a27c0fef80d71e6a6258c42300283 100644 --- a/features/steps/snippets/discover_snippets.rb +++ b/features/steps/snippets/discover_snippets.rb @@ -12,6 +12,6 @@ class DiscoverSnippets < Spinach::FeatureSteps end def snippet - @snippet ||= PersonalSnippet.find_by_title!("Personal snippet one") + @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one") end end diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb index bbdf5b97c8411bf1c07bd2b8c9699299f568ce47..fed54659ebca3281fa2b827c36f00e9787cc5aae 100644 --- a/features/steps/snippets/snippets.rb +++ b/features/steps/snippets/snippets.rb @@ -19,7 +19,7 @@ class SnippetsFeature < Spinach::FeatureSteps end And 'I click link "Destroy"' do - click_link "Destroy" + click_link "Remove" end And 'I submit new snippet "Personal snippet three"' do @@ -46,7 +46,7 @@ class SnippetsFeature < Spinach::FeatureSteps end And 'I uncheck "Private" checkbox' do - find(:xpath, "//input[@id='personal_snippet_private']").set true + choose "Public" click_button "Save" end @@ -59,6 +59,6 @@ class SnippetsFeature < Spinach::FeatureSteps end def snippet - @snippet ||= PersonalSnippet.find_by_title!("Personal snippet one") + @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one") end end diff --git a/features/steps/snippets/user_snippets.rb b/features/steps/snippets/user_snippets.rb index 15d6da6db3d438172e453751b4e872301358140b..2d7ffc866e7b4b91beaac703a6a1f4db3fcbaff1 100644 --- a/features/steps/snippets/user_snippets.rb +++ b/features/steps/snippets/user_snippets.rb @@ -36,6 +36,6 @@ class UserSnippets < Spinach::FeatureSteps end def snippet - @snippet ||= PersonalSnippet.find_by_title!("Personal snippet one") + @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one") end end diff --git a/features/support/env.rb b/features/support/env.rb index 8798e62ea725fd8d4135c6f965cc96fad7b30fa4..0186002c559f7929aed5410ef28a83be5bda6637 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -9,12 +9,13 @@ ENV['RAILS_ENV'] = 'test' require './config/environment' require 'rspec' +require 'rspec/expectations' require 'database_cleaner' require 'spinach/capybara' require 'sidekiq/testing/inline' -%w(valid_commit big_commits select2_helper chosen_helper test_env).each do |f| +%w(valid_commit big_commits select2_helper test_env).each do |f| require Rails.root.join('spec', 'support', f) end @@ -51,4 +52,6 @@ Spinach.hooks.before_run do RSpec::Mocks::setup self include FactoryGirl::Syntax::Methods + MergeRequestObserver.any_instance.stub(current_user: create(:user)) end + diff --git a/lib/api/api.rb b/lib/api/api.rb index c4c9f166db1d9efae6567bb07a7dae39efb1b43f..283f7642f67f5770a68f283b986e3483edc8c176 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -38,5 +38,8 @@ module API mount ProjectSnippets mount DeployKeys mount ProjectHooks + mount Services + mount Files + mount Namespaces end end diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb index 218b3d8eee2e7c4c90a1e27d7f73088e0daaea7a..7f5a125038cdb1bf5bb76c0e75dc9b17c26bced3 100644 --- a/lib/api/deploy_keys.rb +++ b/lib/api/deploy_keys.rb @@ -5,16 +5,6 @@ module API before { authorize_admin_project } resource :projects do - helpers do - def handle_project_member_errors(errors) - if errors[:project_access].any? - error!(errors[:project_access], 422) - end - not_found! - end - end - - # Get a specific project's keys # # Example Request: @@ -48,14 +38,14 @@ module API attrs[:key].strip! # check if key already exist in project - key = user_project.deploy_keys.find_by_key(attrs[:key]) + key = user_project.deploy_keys.find_by(key: attrs[:key]) if key present key, with: Entities::SSHKey return end # Check for available deploy keys in other projects - key = current_user.accessible_deploy_keys.find_by_key(attrs[:key]) + key = current_user.accessible_deploy_keys.find_by(key: attrs[:key]) if key user_project.deploy_keys << key present key, with: Entities::SSHKey diff --git a/lib/api/entities.rb b/lib/api/entities.rb index ab949f530ab74f365da34b99324eb90aaff2e2e8..8f54d0d4d841d51b841a17b430aba5c12660e541 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1,7 +1,7 @@ module API module Entities class User < Grape::Entity - expose :id, :username, :email, :name, :bio, :skype, :linkedin, :twitter, + expose :id, :username, :email, :name, :bio, :skype, :linkedin, :twitter, :website_url, :theme_id, :color_scheme_id, :state, :created_at, :extern_uid, :provider expose :is_admin?, as: :is_admin expose :can_create_group?, as: :can_create_group @@ -24,6 +24,10 @@ module API expose :id, :url, :created_at end + class ProjectHook < Hook + expose :project_id, :push_events, :issues_events, :merge_requests_events + end + class ForkedFromProject < Grape::Entity expose :id expose :name, :name_with_namespace @@ -31,30 +35,32 @@ module API end class Project < Grape::Entity - expose :id, :description, :default_branch, :public, :ssh_url_to_repo, :http_url_to_repo, :web_url + expose :id, :description, :default_branch + expose :public?, as: :public + expose :visibility_level, :ssh_url_to_repo, :http_url_to_repo, :web_url expose :owner, using: Entities::UserBasic expose :name, :name_with_namespace expose :path, :path_with_namespace - expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at, :public + expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at expose :namespace expose :forked_from_project, using: Entities::ForkedFromProject, :if => lambda{ | project, options | project.forked? } end class ProjectMember < UserBasic expose :project_access, as: :access_level do |user, options| - options[:project].users_projects.find_by_user_id(user.id).project_access + options[:project].users_projects.find_by(user_id: user.id).project_access end end class TeamMember < UserBasic expose :permission, as: :access_level do |user, options| - options[:user_team].user_team_user_relationships.find_by_user_id(user.id).permission + options[:user_team].user_team_user_relationships.find_by(user_id: user.id).permission end end class TeamProject < Project expose :greatest_access, as: :greatest_access_level do |project, options| - options[:user_team].user_team_project_relationships.find_by_project_id(project.id).greatest_access + options[:user_team].user_team_project_relationships.find_by(project_id: project.id).greatest_access end end @@ -68,12 +74,21 @@ module API class GroupMember < UserBasic expose :group_access, as: :access_level do |user, options| - options[:group].users_groups.find_by_user_id(user.id).group_access + options[:group].users_groups.find_by(user_id: user.id).group_access end end class RepoObject < Grape::Entity - expose :name, :commit + expose :name + + expose :commit do |repo_obj, options| + if repo_obj.respond_to?(:commit) + repo_obj.commit + elsif options[:project] + options[:project].repository.commit(repo_obj.target) + end + end + expose :protected do |repo, options| if options[:project] options[:project].protected_branch? repo.name @@ -81,25 +96,40 @@ module API end end + class RepoTreeObject < Grape::Entity + expose :id, :name, :type + + expose :mode do |obj, options| + filemode = obj.mode.to_s(8) + filemode = "0" + filemode if filemode.length < 6 + filemode + end + end + class RepoCommit < Grape::Entity expose :id, :short_id, :title, :author_name, :author_email, :created_at end + class RepoCommitDetail < RepoCommit + expose :parent_ids, :committed_date, :authored_date + end + class ProjectSnippet < Grape::Entity expose :id, :title, :file_name expose :author, using: Entities::UserBasic expose :expires_at, :updated_at, :created_at end - class Milestone < Grape::Entity - expose :id - expose (:project_id) {|milestone| milestone.project.id} + class ProjectEntity < Grape::Entity + expose :id, :iid + expose (:project_id) { |entity| entity.project.id } + end + + class Milestone < ProjectEntity expose :title, :description, :due_date, :state, :updated_at, :created_at end - class Issue < Grape::Entity - expose :id - expose (:project_id) {|issue| issue.project.id} + class Issue < ProjectEntity expose :title, :description expose :label_list, as: :labels expose :milestone, using: Entities::Milestone @@ -107,14 +137,14 @@ module API expose :state, :updated_at, :created_at end - class SSHKey < Grape::Entity - expose :id, :title, :key, :created_at + class MergeRequest < ProjectEntity + expose :target_branch, :source_branch, :title, :state, :upvotes, :downvotes + expose :author, :assignee, using: Entities::UserBasic + expose :source_project_id, :target_project_id end - class MergeRequest < Grape::Entity - expose :id, :target_branch, :source_branch, :title, :state - expose :target_project_id, as: :project_id - expose :author, :assignee, using: Entities::UserBasic + class SSHKey < Grape::Entity + expose :id, :title, :key, :created_at end class Note < Grape::Entity @@ -135,5 +165,9 @@ module API expose :target_id, :target_type, :author_id expose :data, :target_title end + + class Namespace < Grape::Entity + expose :id, :path, :kind + end end end diff --git a/lib/api/files.rb b/lib/api/files.rb new file mode 100644 index 0000000000000000000000000000000000000000..213604915a69e16786344ea19068d49a15b062f0 --- /dev/null +++ b/lib/api/files.rb @@ -0,0 +1,99 @@ +module API + # Projects API + class Files < Grape::API + before { authenticate! } + before { authorize! :push_code, user_project } + + resource :projects do + # Create new file in repository + # + # Parameters: + # file_path (optional) - The path to new file. Ex. lib/class.rb + # branch_name (required) - The name of branch + # content (required) - File content + # commit_message (required) - Commit message + # + # Example Request: + # POST /projects/:id/repository/files + # + post ":id/repository/files" do + required_attributes! [:file_path, :branch_name, :content, :commit_message] + attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding] + branch_name = attrs.delete(:branch_name) + file_path = attrs.delete(:file_path) + result = ::Files::CreateService.new(user_project, current_user, attrs, branch_name, file_path).execute + + if result[:status] == :success + status(201) + + { + file_path: file_path, + branch_name: branch_name + } + else + render_api_error!(result[:error], 400) + end + end + + # Update existing file in repository + # + # Parameters: + # file_path (optional) - The path to file. Ex. lib/class.rb + # branch_name (required) - The name of branch + # content (required) - File content + # commit_message (required) - Commit message + # + # Example Request: + # PUT /projects/:id/repository/files + # + put ":id/repository/files" do + required_attributes! [:file_path, :branch_name, :content, :commit_message] + attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding] + branch_name = attrs.delete(:branch_name) + file_path = attrs.delete(:file_path) + result = ::Files::UpdateService.new(user_project, current_user, attrs, branch_name, file_path).execute + + if result[:status] == :success + status(200) + + { + file_path: file_path, + branch_name: branch_name + } + else + render_api_error!(result[:error], 400) + end + end + + # Delete existing file in repository + # + # Parameters: + # file_path (optional) - The path to file. Ex. lib/class.rb + # branch_name (required) - The name of branch + # content (required) - File content + # commit_message (required) - Commit message + # + # Example Request: + # DELETE /projects/:id/repository/files + # + delete ":id/repository/files" do + required_attributes! [:file_path, :branch_name, :commit_message] + attrs = attributes_for_keys [:file_path, :branch_name, :commit_message] + branch_name = attrs.delete(:branch_name) + file_path = attrs.delete(:file_path) + result = ::Files::DeleteService.new(user_project, current_user, attrs, branch_name, file_path).execute + + if result[:status] == :success + status(200) + + { + file_path: file_path, + branch_name: branch_name + } + else + render_api_error!(result[:error], 400) + end + end + end + end +end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 396554404af4d7d12f29c87ac889d541fdf49d15..03f027706de409bda1f7d9e82afe4da60f406c04 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -7,12 +7,14 @@ module API helpers do def find_group(id) group = Group.find(id) - if current_user.admin or current_user.groups.include? group + + if can?(current_user, :read_group, group) group else render_api_error!("403 Forbidden - #{current_user.username} lacks sufficient access to #{group.name}", 403) end end + def validate_access_level?(level) Gitlab::Access.options_with_owner.values.include? level.to_i end @@ -64,6 +66,18 @@ module API present group, with: Entities::GroupDetail end + # Remove group + # + # Parameters: + # id (required) - The ID of a group + # Example Request: + # DELETE /groups/:id + delete ":id" do + group = find_group(params[:id]) + authorize! :manage_group, group + group.destroy + end + # Transfer a project to the Group namespace # # Parameters: @@ -107,11 +121,11 @@ module API render_api_error!("Wrong access level", 422) end group = find_group(params[:id]) - if group.users_groups.find_by_user_id(params[:user_id]) + if group.users_groups.find_by(user_id: params[:user_id]) render_api_error!("Already exists", 409) end group.add_users([params[:user_id]], params[:access_level]) - member = group.users_groups.find_by_user_id(params[:user_id]) + member = group.users_groups.find_by(user_id: params[:user_id]) present member.user, with: Entities::GroupMember, group: group end @@ -125,14 +139,13 @@ module API # DELETE /groups/:id/members/:user_id delete ":id/members/:user_id" do group = find_group(params[:id]) - member = group.users_groups.find_by_user_id(params[:user_id]) + member = group.users_groups.find_by(user_id: params[:user_id]) if member.nil? render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404) else member.destroy end end - end end end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 2b0c672c7fa8a0b23e26a8f0a2709fce68a13cc2..f8c48e2f3b2c4a258f924dc7b893688f7e4ea454 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -6,23 +6,23 @@ module API SUDO_PARAM = :sudo def current_user - @current_user ||= User.find_by_authentication_token(params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]) + private_token = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s + @current_user ||= User.find_by(authentication_token: private_token) identifier = sudo_identifier() + # If the sudo is the current user do nothing if (identifier && !(@current_user.id == identifier || @current_user.username == identifier)) render_api_error!('403 Forbidden: Must be admin to use sudo', 403) unless @current_user.is_admin? - begin - @current_user = User.by_username_or_id(identifier) - rescue => ex - not_found!("No user id or username for: #{identifier}") - end - not_found!("No user id or username for: #{identifier}") if current_user.nil? + @current_user = User.by_username_or_id(identifier) + not_found!("No user id or username for: #{identifier}") if @current_user.nil? end + @current_user end def sudo_identifier() identifier ||= params[SUDO_PARAM] ||= env[SUDO_HEADER] + # Regex for integers if (!!(identifier =~ /^[0-9]+$/)) identifier.to_i @@ -31,13 +31,23 @@ module API end end + def set_current_user_for_thread + Thread.current[:current_user] = current_user + + begin + yield + ensure + Thread.current[:current_user] = nil + end + end + def user_project @project ||= find_project(params[:id]) @project || not_found! end def find_project(id) - project = Project.find_by_id(id) || Project.find_with_namespace(id) + project = Project.find_by(id: id) || Project.find_with_namespace(id) if project && can?(current_user, :read_project, project) project diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 79f8eb3a543adfe20766738e4a110969ad262491..ed6b50c3a6a4986204fd2baf6ca89052b8419830 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -35,6 +35,7 @@ module API user = key.user return false if user.blocked? + return false if user.ldap_user? && Gitlab::LDAP::User.blocked?(user.extern_uid) action = case git_cmd when *DOWNLOAD_COMMANDS diff --git a/lib/api/issues.rb b/lib/api/issues.rb index a15203d1563b3eb21ed55184c4f54adfc18b8ef3..3d15c35b8cc4db079fc3d193a6db37ae001a5be1 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -2,7 +2,6 @@ module API # Issues API class Issues < Grape::API before { authenticate! } - before { Thread.current[:current_user] = current_user } resource :issues do # Get currently authenticated user's issues @@ -49,15 +48,17 @@ module API # Example Request: # POST /projects/:id/issues post ":id/issues" do - required_attributes! [:title] - attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id] - attrs[:label_list] = params[:labels] if params[:labels].present? - @issue = user_project.issues.new attrs - @issue.author = current_user - if @issue.save - present @issue, with: Entities::Issue - else - not_found! + set_current_user_for_thread do + required_attributes! [:title] + attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id] + attrs[:label_list] = params[:labels] if params[:labels].present? + @issue = user_project.issues.new attrs + @issue.author = current_user + if @issue.save + present @issue, with: Entities::Issue + else + not_found! + end end end @@ -75,16 +76,18 @@ module API # Example Request: # PUT /projects/:id/issues/:issue_id put ":id/issues/:issue_id" do - @issue = user_project.issues.find(params[:issue_id]) - authorize! :modify_issue, @issue + set_current_user_for_thread do + @issue = user_project.issues.find(params[:issue_id]) + authorize! :modify_issue, @issue - attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event] - attrs[:label_list] = params[:labels] if params[:labels].present? + attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event] + attrs[:label_list] = params[:labels] if params[:labels].present? - if @issue.update_attributes attrs - present @issue, with: Entities::Issue - else - not_found! + if @issue.update_attributes attrs + present @issue, with: Entities::Issue + else + not_found! + end end end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index d690f1d07e7ab706823d5935ac7953aed2de972d..0f62cac9a0c4d99b9501f2a90f2cdb9f60d049e7 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -2,7 +2,6 @@ module API # MergeRequest API class MergeRequests < Grape::API before { authenticate! } - before { Thread.current[:current_user] = current_user } resource :projects do helpers do @@ -70,28 +69,29 @@ module API # POST /projects/:id/merge_requests # post ":id/merge_requests" do - authorize! :write_merge_request, user_project - required_attributes! [:source_branch, :target_branch, :title] - attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id] - merge_request = user_project.merge_requests.new(attrs) - merge_request.author = current_user - merge_request.source_project = user_project - target_project_id = attrs[:target_project_id] - if not_fork?(target_project_id, user_project) - merge_request.target_project = user_project - else - if target_matches_fork(target_project_id,user_project) - merge_request.target_project = Project.find_by_id(attrs[:target_project_id]) + set_current_user_for_thread do + authorize! :write_merge_request, user_project + required_attributes! [:source_branch, :target_branch, :title] + attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id] + merge_request = user_project.merge_requests.new(attrs) + merge_request.author = current_user + merge_request.source_project = user_project + target_project_id = attrs[:target_project_id] + if not_fork?(target_project_id, user_project) + merge_request.target_project = user_project else - render_api_error!('(Bad Request) Specified target project that is not the source project, or the source fork of the project.', 400) + if target_matches_fork(target_project_id,user_project) + merge_request.target_project = Project.find_by(id: attrs[:target_project_id]) + else + render_api_error!('(Bad Request) Specified target project that is not the source project, or the source fork of the project.', 400) + end end - end - if merge_request.save - merge_request.reload_code - present merge_request, with: Entities::MergeRequest - else - handle_merge_request_errors! merge_request.errors + if merge_request.save + present merge_request, with: Entities::MergeRequest + else + handle_merge_request_errors! merge_request.errors + end end end @@ -109,17 +109,19 @@ module API # PUT /projects/:id/merge_request/:merge_request_id # put ":id/merge_request/:merge_request_id" do - attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event] - merge_request = user_project.merge_requests.find(params[:merge_request_id]) + set_current_user_for_thread do + attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event] + merge_request = user_project.merge_requests.find(params[:merge_request_id]) - authorize! :modify_merge_request, merge_request + authorize! :modify_merge_request, merge_request - if merge_request.update_attributes attrs - merge_request.reload_code - merge_request.mark_as_unchecked - present merge_request, with: Entities::MergeRequest - else - handle_merge_request_errors! merge_request.errors + if merge_request.update_attributes attrs + merge_request.reload_code + merge_request.mark_as_unchecked + present merge_request, with: Entities::MergeRequest + else + handle_merge_request_errors! merge_request.errors + end end end @@ -133,16 +135,18 @@ module API # POST /projects/:id/merge_request/:merge_request_id/comments # post ":id/merge_request/:merge_request_id/comments" do - required_attributes! [:note] + set_current_user_for_thread do + required_attributes! [:note] - merge_request = user_project.merge_requests.find(params[:merge_request_id]) - note = merge_request.notes.new(note: params[:note], project_id: user_project.id) - note.author = current_user + merge_request = user_project.merge_requests.find(params[:merge_request_id]) + note = merge_request.notes.new(note: params[:note], project_id: user_project.id) + note.author = current_user - if note.save - present note, with: Entities::MRNote - else - not_found! + if note.save + present note, with: Entities::MRNote + else + not_found! + end end end diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index aee12e7dc40e7ecf66ac1c26077cc99178203cab..f7e63b230936489dc9fc0588336670f0983c3b13 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -40,15 +40,17 @@ module API # Example Request: # POST /projects/:id/milestones post ":id/milestones" do - authorize! :admin_milestone, user_project - required_attributes! [:title] + set_current_user_for_thread do + authorize! :admin_milestone, user_project + required_attributes! [:title] - attrs = attributes_for_keys [:title, :description, :due_date] - @milestone = user_project.milestones.new attrs - if @milestone.save - present @milestone, with: Entities::Milestone - else - not_found! + attrs = attributes_for_keys [:title, :description, :due_date] + @milestone = user_project.milestones.new attrs + if @milestone.save + present @milestone, with: Entities::Milestone + else + not_found! + end end end @@ -64,14 +66,16 @@ module API # Example Request: # PUT /projects/:id/milestones/:milestone_id put ":id/milestones/:milestone_id" do - authorize! :admin_milestone, user_project + set_current_user_for_thread do + authorize! :admin_milestone, user_project - @milestone = user_project.milestones.find(params[:milestone_id]) - attrs = attributes_for_keys [:title, :description, :due_date, :state_event] - if @milestone.update_attributes attrs - present @milestone, with: Entities::Milestone - else - not_found! + @milestone = user_project.milestones.find(params[:milestone_id]) + attrs = attributes_for_keys [:title, :description, :due_date, :state_event] + if @milestone.update_attributes attrs + present @milestone, with: Entities::Milestone + else + not_found! + end end end end diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb new file mode 100644 index 0000000000000000000000000000000000000000..f9f2ed90ccc357ca49d1e0a436411f73b0888463 --- /dev/null +++ b/lib/api/namespaces.rb @@ -0,0 +1,23 @@ +module API + # namespaces API + class Namespaces < Grape::API + before { + authenticate! + authenticated_as_admin! + } + + resource :namespaces do + # Get a namespaces list + # + # Example Request: + # GET /namespaces + get do + @namespaces = Namespace.all + @namespaces = @namespaces.search(params[:search]) if params[:search].present? + @namespaces = paginate @namespaces + + present @namespaces, with: Entities::Namespace + end + end + end +end diff --git a/lib/api/notes.rb b/lib/api/notes.rb index cb2bc764476274fef42561b9e67501ae7aa1a732..f21907b1ffcf50c0e06774dc6ef220152e7113c3 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -41,17 +41,19 @@ module API # Example Request: # POST /projects/:id/notes post ":id/notes" do - required_attributes! [:body] + set_current_user_for_thread do + required_attributes! [:body] - @note = user_project.notes.new(note: params[:body]) - @note.author = current_user + @note = user_project.notes.new(note: params[:body]) + @note.author = current_user - if @note.save - present @note, with: Entities::Note - else - # :note is exposed as :body, but :note is set on error - bad_request!(:note) if @note.errors[:note].any? - not_found! + if @note.save + present @note, with: Entities::Note + else + # :note is exposed as :body, but :note is set on error + bad_request!(:note) if @note.errors[:note].any? + not_found! + end end end @@ -97,17 +99,19 @@ module API # POST /projects/:id/issues/:noteable_id/notes # POST /projects/:id/snippets/:noteable_id/notes post ":id/#{noteables_str}/:#{noteable_id_str}/notes" do - required_attributes! [:body] + set_current_user_for_thread do + required_attributes! [:body] - @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"]) - @note = @noteable.notes.new(note: params[:body]) - @note.author = current_user - @note.project = user_project + @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"]) + @note = @noteable.notes.new(note: params[:body]) + @note.author = current_user + @note.project = user_project - if @note.save - present @note, with: Entities::Note - else - not_found! + if @note.save + present @note, with: Entities::Note + else + not_found! + end end end end diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index 738974955f3dc6c6dad4025c16c5c889f85af273..c271dd8b61bdb19dd523628450684bcb594191a1 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -22,7 +22,7 @@ module API # GET /projects/:id/hooks get ":id/hooks" do @hooks = paginate user_project.hooks - present @hooks, with: Entities::Hook + present @hooks, with: Entities::ProjectHook end # Get a project hook @@ -34,7 +34,7 @@ module API # GET /projects/:id/hooks/:hook_id get ":id/hooks/:hook_id" do @hook = user_project.hooks.find(params[:hook_id]) - present @hook, with: Entities::Hook + present @hook, with: Entities::ProjectHook end @@ -47,10 +47,11 @@ module API # POST /projects/:id/hooks post ":id/hooks" do required_attributes! [:url] + attrs = attributes_for_keys [:url, :push_events, :issues_events, :merge_requests_events] + @hook = user_project.hooks.new(attrs) - @hook = user_project.hooks.new({"url" => params[:url]}) if @hook.save - present @hook, with: Entities::Hook + present @hook, with: Entities::ProjectHook else if @hook.errors[:url].present? error!("Invalid url given", 422) @@ -70,10 +71,10 @@ module API put ":id/hooks/:hook_id" do @hook = user_project.hooks.find(params[:hook_id]) required_attributes! [:url] + attrs = attributes_for_keys [:url, :push_events, :issues_events, :merge_requests_events] - attrs = attributes_for_keys [:url] if @hook.update_attributes attrs - present @hook, with: Entities::Hook + present @hook, with: Entities::ProjectHook else if @hook.errors[:url].present? error!("Invalid url given", 422) diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index bee6544ea3d420d920e077c0170701fd871efb62..8e09fff68436f30d5bbca94c75b0300b59d89d3d 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -41,7 +41,6 @@ module API # id (required) - The ID of a project # title (required) - The title of a snippet # file_name (required) - The name of a snippet file - # lifetime (optional) - The expiration date of a snippet # code (required) - The content of a snippet # Example Request: # POST /projects/:id/snippets @@ -50,7 +49,6 @@ module API required_attributes! [:title, :file_name, :code] attrs = attributes_for_keys [:title, :file_name] - attrs[:expires_at] = params[:lifetime] if params[:lifetime].present? attrs[:content] = params[:code] if params[:code].present? @snippet = user_project.snippets.new attrs @snippet.author = current_user @@ -69,7 +67,6 @@ module API # snippet_id (required) - The ID of a project snippet # title (optional) - The title of a snippet # file_name (optional) - The name of a snippet file - # lifetime (optional) - The expiration date of a snippet # code (optional) - The content of a snippet # Example Request: # PUT /projects/:id/snippets/:snippet_id @@ -78,7 +75,6 @@ module API authorize! :modify_project_snippet, @snippet attrs = attributes_for_keys [:title, :file_name] - attrs[:expires_at] = params[:lifetime] if params[:lifetime].present? attrs[:content] = params[:code] if params[:code].present? if @snippet.update_attributes attrs diff --git a/lib/api/projects.rb b/lib/api/projects.rb index cf357b23c406fbbd6074162794d559fe069a79f4..888aa7e77d225db669d0d262dd6ae21d1da84ba7 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -11,6 +11,13 @@ module API end not_found! end + + def map_public_to_visibility_level(attrs) + publik = attrs.delete(:public) + publik = [ true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON' ].include?(publik) + attrs[:visibility_level] = Gitlab::VisibilityLevel::PUBLIC if !attrs[:visibility_level].present? && publik == true + attrs + end end # Get a projects list for authenticated user @@ -31,6 +38,16 @@ module API present @projects, with: Entities::Project end + # Get all projects for admin user + # + # Example Request: + # GET /projects/all + get '/all' do + authenticated_as_admin! + @projects = paginate Project + present @projects, with: Entities::Project + end + # Get a single project # # Parameters: @@ -60,14 +77,14 @@ module API # Parameters: # name (required) - name for new project # description (optional) - short project description - # default_branch (optional) - 'master' by default # issues_enabled (optional) # wall_enabled (optional) # merge_requests_enabled (optional) # wiki_enabled (optional) # snippets_enabled (optional) # namespace_id (optional) - defaults to user namespace - # public (optional) - false by default + # public (optional) - if true same as setting visibility_level = 20 + # visibility_level (optional) - 0 by default # Example Request # POST /projects post do @@ -75,15 +92,17 @@ module API attrs = attributes_for_keys [:name, :path, :description, - :default_branch, :issues_enabled, :wall_enabled, :merge_requests_enabled, :wiki_enabled, :snippets_enabled, :namespace_id, - :public] - @project = ::Projects::CreateContext.new(current_user, attrs).execute + :public, + :visibility_level, + :import_url] + attrs = map_public_to_visibility_level(attrs) + @project = ::Projects::CreateService.new(current_user, attrs).execute if @project.saved? present @project, with: Entities::Project else @@ -106,7 +125,8 @@ module API # merge_requests_enabled (optional) # wiki_enabled (optional) # snippets_enabled (optional) - # public (optional) + # public (optional) - if true same as setting visibility_level = 20 + # visibility_level (optional) # Example Request # POST /projects/user/:user_id post "user/:user_id" do @@ -120,8 +140,10 @@ module API :merge_requests_enabled, :wiki_enabled, :snippets_enabled, - :public] - @project = ::Projects::CreateContext.new(user, attrs).execute + :public, + :visibility_level] + attrs = map_public_to_visibility_level(attrs) + @project = ::Projects::CreateService.new(user, attrs).execute if @project.saved? present @project, with: Entities::Project else @@ -129,6 +151,16 @@ module API end end + # Remove project + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # DELETE /projects/:id + delete ":id" do + authorize! :remove_project, user_project + user_project.destroy + end # Mark this project as forked from another # @@ -234,7 +266,7 @@ module API authorize! :admin_project, user_project required_attributes! [:access_level] - team_member = user_project.users_projects.find_by_user_id(params[:user_id]) + team_member = user_project.users_projects.find_by(user_id: params[:user_id]) not_found!("User can not be found") if team_member.nil? if team_member.update_attributes(project_access: params[:access_level]) @@ -254,7 +286,7 @@ module API # DELETE /projects/:id/members/:user_id delete ":id/members/:user_id" do authorize! :admin_project, user_project - team_member = user_project.users_projects.find_by_user_id(params[:user_id]) + team_member = user_project.users_projects.find_by(user_id: params[:user_id]) unless team_member.nil? team_member.destroy else @@ -272,7 +304,8 @@ module API # GET /projects/search/:query get "/search/:query" do ids = current_user.authorized_projects.map(&:id) - projects = Project.where("(id in (?) OR public = true) AND (name LIKE (?))", ids, "%#{params[:query]}%") + visibility_levels = [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ] + projects = Project.where("(id in (?) OR visibility_level in (?)) AND (name LIKE (?))", ids, visibility_levels, "%#{params[:query]}%") present paginate(projects), with: Entities::Project end end diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index c2b229b0172525a7cd78cb1170e542c02c359e73..cad64760abb35a9863b40c379f854be42a0f219c 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -1,3 +1,5 @@ +require 'mime/types' + module API # Projects API class Repositories < Grape::API @@ -49,7 +51,7 @@ module API @branch = user_project.repository.find_branch(params[:branch]) not_found! unless @branch - protected_branch = user_project.protected_branches.find_by_name(@branch.name) + protected_branch = user_project.protected_branches.find_by(name: @branch.name) user_project.protected_branches.create(name: @branch.name) unless protected_branch present @branch, with: Entities::RepoObject, project: user_project @@ -67,7 +69,7 @@ module API @branch = user_project.repository.find_branch(params[:branch]) not_found! unless @branch - protected_branch = user_project.protected_branches.find_by_name(@branch.name) + protected_branch = user_project.protected_branches.find_by(name: @branch.name) protected_branch.destroy if protected_branch present @branch, with: Entities::RepoObject, project: user_project @@ -80,7 +82,7 @@ module API # Example Request: # GET /projects/:id/repository/tags get ":id/repository/tags" do - present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject + present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject, project: user_project end # Get a project repository commits @@ -110,7 +112,7 @@ module API sha = params[:sha] commit = user_project.repository.commit(sha) not_found! "Commit" unless commit - present commit, with: Entities::RepoCommit + present commit, with: Entities::RepoCommitDetail end # Get the diff for a specific commit of a project @@ -122,9 +124,9 @@ module API # GET /projects/:id/repository/commits/:sha/diff get ":id/repository/commits/:sha/diff" do sha = params[:sha] - result = CommitLoadContext.new(user_project, current_user, {id: sha}).execute - not_found! "Commit" unless result[:commit] - result[:commit].diffs + commit = user_project.repository.commit(sha) + not_found! "Commit" unless commit + commit.diffs end # Get a project repository tree @@ -139,15 +141,9 @@ module API path = params[:path] || nil commit = user_project.repository.commit(ref) - tree = Tree.new(user_project.repository, commit.id, ref, path) - - trees = [] - - %w(trees blobs submodules).each do |type| - trees += tree.send(type).map { |t| { name: t.name, type: type.singularize, mode: t.mode, id: t.id } } - end + tree = user_project.repository.tree(commit.id, path) - trees + present tree.sorted_entries, with: Entities::RepoTreeObject end # Get a raw file contents @@ -168,15 +164,66 @@ module API commit = repo.commit(ref) not_found! "Commit" unless commit - blob = Gitlab::Git::Blob.new(repo, commit.id, ref, params[:filepath]) - not_found! "File" unless blob.exists? + blob = Gitlab::Git::Blob.find(repo, commit.id, params[:filepath]) + not_found! "File" unless blob + + env['api.format'] = :txt + + content_type blob.mime_type + present blob.data + end + + # Get a raw blob contents by blob sha + # + # Parameters: + # id (required) - The ID of a project + # sha (required) - The blob's sha + # Example Request: + # GET /projects/:id/repository/raw_blobs/:sha + get ":id/repository/raw_blobs/:sha" do + ref = params[:sha] + + repo = user_project.repository + + blob = Gitlab::Git::Blob.raw(repo, ref) + + not_found! "Blob" unless blob env['api.format'] = :txt content_type blob.mime_type present blob.data end + + # Get a an archive of the repository + # + # Parameters: + # id (required) - The ID of a project + # sha (optional) - the commit sha to download defaults to the tip of the default branch + # Example Request: + # GET /projects/:id/repository/archive + get ":id/repository/archive", requirements: { format: Gitlab::Regex.archive_formats_regex } do + authorize! :download_code, user_project + repo = user_project.repository + ref = params[:sha] + format = params[:format] + storage_path = Rails.root.join("tmp", "repositories") + + file_path = repo.archive_repo(ref, storage_path, format) + if file_path && File.exists?(file_path) + data = File.open(file_path, 'rb').read + + header["Content-Disposition"] = "attachment; filename=\"#{File.basename(file_path)}\"" + + content_type MIME::Types.type_for(file_path).first.content_type + + env['api.format'] = :binary + + present data + else + not_found! + end + end end end end - diff --git a/lib/api/services.rb b/lib/api/services.rb new file mode 100644 index 0000000000000000000000000000000000000000..bde502e32e15825dd382e1468e3405dae76e42fb --- /dev/null +++ b/lib/api/services.rb @@ -0,0 +1,44 @@ +module API + # Projects API + class Services < Grape::API + before { authenticate! } + before { authorize_admin_project } + + resource :projects do + # Set GitLab CI service for project + # + # Parameters: + # token (required) - CI project token + # project_url (required) - CI project url + # + # Example Request: + # PUT /projects/:id/services/gitlab-ci + put ":id/services/gitlab-ci" do + required_attributes! [:token, :project_url] + attrs = attributes_for_keys [:token, :project_url] + user_project.build_missing_services + + if user_project.gitlab_ci_service.update_attributes(attrs.merge(active: true)) + true + else + not_found! + end + end + + # Delete GitLab CI service settings + # + # Example Request: + # DELETE /projects/:id/keys/:id + delete ":id/services/gitlab-ci" do + if user_project.gitlab_ci_service + user_project.gitlab_ci_service.update_attributes( + active: false, + token: nil, + project_url: nil + ) + end + end + end + end +end + diff --git a/lib/api/users.rb b/lib/api/users.rb index 54d3aeecb7041d6afe4ba1b05c386d813d08b148..ae808b6272bad4e1c0642b53227460615d26b264 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -9,7 +9,7 @@ module API # Example Request: # GET /users get do - @users = User.scoped + @users = User.all @users = @users.active if params[:active].present? @users = @users.search(params[:search]) if params[:search].present? @users = paginate @users @@ -36,6 +36,7 @@ module API # skype - Skype ID # linkedin - Linkedin # twitter - Twitter account + # website_url - Website url # projects_limit - Number of projects user can create # extern_uid - External authentication provider UID # provider - External provider @@ -67,6 +68,7 @@ module API # skype - Skype ID # linkedin - Linkedin # twitter - Twitter account + # website_url - Website url # projects_limit - Limit projects each user can create # extern_uid - External authentication provider UID # provider - External provider @@ -78,7 +80,7 @@ module API put ":id" do authenticated_as_admin! - 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, :website_url, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin] user = User.find(params[:id]) not_found!("User not found") unless user @@ -117,7 +119,7 @@ module API # DELETE /users/:id delete ":id" do authenticated_as_admin! - user = User.find_by_id(params[:id]) + user = User.find_by(id: params[:id]) if user user.destroy diff --git a/lib/backup/database.rb b/lib/backup/database.rb index c4fb2e2e159d5309936cdfe5261ddb004cbbf284..ebb4f289c5289f6a0a6a6d1e4522202706bc42fb 100644 --- a/lib/backup/database.rb +++ b/lib/backup/database.rb @@ -11,23 +11,29 @@ module Backup end def dump - case config["adapter"] + success = case config["adapter"] when /^mysql/ then - system("mysqldump #{mysql_args} #{config['database']} > #{db_file_name}") + print "Dumping MySQL database #{config['database']} ... " + system('mysqldump', *mysql_args, config['database'], out: db_file_name) when "postgresql" then + print "Dumping PostgreSQL database #{config['database']} ... " pg_env - system("pg_dump #{config['database']} > #{db_file_name}") + system('pg_dump', config['database'], out: db_file_name) end + report_success(success) end def restore - case config["adapter"] + success = case config["adapter"] when /^mysql/ then - system("mysql #{mysql_args} #{config['database']} < #{db_file_name}") + print "Restoring MySQL database #{config['database']} ... " + system('mysql', *mysql_args, config['database'], in: db_file_name) when "postgresql" then + print "Restoring PostgreSQL database #{config['database']} ... " pg_env - system("psql #{config['database']} -f #{db_file_name}") + system('psql', config['database'], '-f', db_file_name) end + report_success(success) end protected @@ -45,7 +51,7 @@ module Backup 'encoding' => '--default-character-set', 'password' => '--password' } - args.map { |opt, arg| "#{arg}='#{config[opt]}'" if config[opt] }.compact.join(' ') + args.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact end def pg_env @@ -54,5 +60,13 @@ module Backup ENV['PGPORT'] = config["port"].to_s if config["port"] ENV['PGPASSWORD'] = config["password"].to_s if config["password"] end + + def report_success(success) + if success + puts '[DONE]'.green + else + puts '[FAILED]'.red + end + end end end diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index 258a0fb2589d5a8f803a24a1e16685bcb53417aa..efaefa4ce44649ade38f02694854b7111f5fd0f1 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -1,5 +1,7 @@ module Backup class Manager + BACKUP_CONTENTS = %w{repositories/ db/ uploads/ backup_information.yml} + def pack # saving additional informations s = {} @@ -16,7 +18,7 @@ module Backup # create archive print "Creating backup archive: #{s[:backup_created_at].to_i}_gitlab_backup.tar ... " - if Kernel.system("tar -cf #{s[:backup_created_at].to_i}_gitlab_backup.tar repositories/ db/ uploads/ backup_information.yml") + if Kernel.system('tar', '-cf', "#{s[:backup_created_at].to_i}_gitlab_backup.tar", *BACKUP_CONTENTS) puts "done".green else puts "failed".red @@ -25,7 +27,7 @@ module Backup def cleanup print "Deleting tmp directories ... " - if Kernel.system("rm -rf repositories/ db/ uploads/ backup_information.yml") + if Kernel.system('rm', '-rf', *BACKUP_CONTENTS) puts "done".green else puts "failed".red @@ -44,7 +46,7 @@ module Backup file_list.map! { |f| $1.to_i if f =~ /(\d+)_gitlab_backup.tar/ } file_list.sort.each do |timestamp| if Time.at(timestamp) < (Time.now - keep_time) - if system("rm #{timestamp}_gitlab_backup.tar") + if Kernel.system(*%W(rm #{timestamp}_gitlab_backup.tar)) removed += 1 end end @@ -75,7 +77,7 @@ module Backup end print "Unpacking backup ... " - unless Kernel.system("tar -xf #{tar_file}") + unless Kernel.system(*%W(tar -xf #{tar_file})) puts "failed".red exit 1 else diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index c5e3d049fd77319bb0d545b7b34014948d5d7d4a..20fd5ba92a10e9048acaee412b851f7cce4db5ce 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -18,7 +18,7 @@ module Backup # Create namespace dir if missing FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.path)) if project.namespace - if system("cd #{path_to_repo(project)} > /dev/null 2>&1 && git bundle create #{path_to_bundle(project)} --all > /dev/null 2>&1") + if system(*%W(git --git-dir=#{path_to_repo(project)} bundle create #{path_to_bundle(project)} --all), silent) puts "[DONE]".green else puts "[FAILED]".red @@ -28,7 +28,9 @@ module Backup if File.exists?(path_to_repo(wiki)) print " * #{wiki.path_with_namespace} ... " - if system("cd #{path_to_repo(wiki)} > /dev/null 2>&1 && git bundle create #{path_to_bundle(wiki)} --all > /dev/null 2>&1") + if wiki.empty? + puts " [SKIPPED]".cyan + elsif system(*%W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all), silent) puts " [DONE]".green else puts " [FAILED]".red @@ -51,7 +53,7 @@ module Backup project.namespace.ensure_dir_exist if project.namespace - if system("git clone --bare #{path_to_bundle(project)} #{path_to_repo(project)} > /dev/null 2>&1") + if system(*%W(git clone --bare #{path_to_bundle(project)} #{path_to_repo(project)}), silent) puts "[DONE]".green else puts "[FAILED]".red @@ -61,7 +63,7 @@ module Backup if File.exists?(path_to_bundle(wiki)) print " * #{wiki.path_with_namespace} ... " - if system("git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)} > /dev/null 2>&1") + if system(*%W(git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)}), silent) puts " [DONE]".green else puts " [FAILED]".red @@ -71,7 +73,7 @@ module Backup print 'Put GitLab hooks in repositories dirs'.yellow gitlab_shell_user_home = File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}") - if system("#{gitlab_shell_user_home}/gitlab-shell/support/rewrite-hooks.sh #{Gitlab.config.gitlab_shell.repos_path}") + if system("#{gitlab_shell_user_home}/gitlab-shell/support/rewrite-hooks.sh", Gitlab.config.gitlab_shell.repos_path) puts " [DONE]".green else puts " [FAILED]".red @@ -101,5 +103,9 @@ module Backup FileUtils.rm_rf(backup_repos_path) FileUtils.mkdir_p(backup_repos_path) end + + def silent + {err: '/dev/null', out: '/dev/null'} + end end end diff --git a/lib/backup/uploads.rb b/lib/backup/uploads.rb index 462d3f1e274db73c55edcca37df4b09d6241f117..e79da7e8fd258dfd89fd51319a71f3d56ec06073 100644 --- a/lib/backup/uploads.rb +++ b/lib/backup/uploads.rb @@ -19,7 +19,7 @@ module Backup FileUtils.cp_r(backup_uploads_dir, app_uploads_dir) end - + def backup_existing_uploads_dir if File.exists?(app_uploads_dir) FileUtils.mv(app_uploads_dir, Rails.root.join('public', "uploads.#{Time.now.to_i}")) diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb index 53bc079296aedb6faed3732742053601ac730795..e51cb30bdd919db3cd3829e8c7bba63ff2f84361 100644 --- a/lib/extracts_path.rb +++ b/lib/extracts_path.rb @@ -86,7 +86,6 @@ module ExtractsPath # - @ref - A string representing the ref (e.g., the branch, tag, or commit SHA) # - @path - A string representing the filesystem path # - @commit - A Commit representing the commit from the given ref - # - @tree - A Tree representing the tree at the given ref/path # # If the :id parameter appears to be requesting a specific response format, # that will be handled as well. @@ -107,15 +106,20 @@ module ExtractsPath else @commit = @repo.commit(@options[:extended_sha1]) end - @tree = Tree.new(@repo, @commit.id, @ref, @path) + + raise InvalidPathError unless @commit + @hex_path = Digest::SHA1.hexdigest(@path) @logs_path = logs_file_project_ref_path(@project, @ref, @path) - raise InvalidPathError unless @tree.exists? rescue RuntimeError, NoMethodError, InvalidPathError not_found! end + def tree + @tree ||= @repo.tree(@commit.id, @path) + end + private def get_id diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 0f1962974771e494e42c754dbc7ef762dfd478b0..955abc1bedd66cab8ff838cad023d45c18aaf975 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,7 +1,7 @@ module Gitlab class Auth def find(login, password) - user = User.find_by_email(login) || User.find_by_username(login) + user = User.find_by(email: login) || User.find_by(username: login) if user.nil? || user.ldap_user? # Second chance - try LDAP authentication diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index c522e0a413b69252014fbcda90811a799bcdf76b..60c03ce1c04f36f0c6a2bf12467f2b8a5afd402c 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -38,6 +38,16 @@ module Grack # Authentication with username and password login, password = @auth.credentials + # Allow authentication for GitLab CI service + # if valid token passed + if login == "gitlab-ci-token" && project.gitlab_ci? + token = project.gitlab_ci_service.token + + if token.present? && token == password && service_name == 'git-upload-pack' + return @app.call(env) + end + end + @user = authenticate_user(login, password) if @user @@ -48,7 +58,7 @@ module Grack end else - return unauthorized unless project.public + return unauthorized unless project.public? end if authorized_git_request? @@ -59,14 +69,7 @@ module Grack end def authorized_git_request? - # Git upload and receive - if @request.get? - authorize_request(@request.params['service']) - elsif @request.post? - authorize_request(File.basename(@request.path)) - else - false - end + authorize_request(service_name) end def authenticate_user(login, password) @@ -77,29 +80,46 @@ module Grack def authorize_request(service) case service when 'git-upload-pack' - project.public || can?(user, :download_code, project) + can?(user, :download_code, project) when'git-receive-pack' - action = if project.protected_branch?(ref) - :push_code_to_protected_branches - else - :push_code - end + refs.each do |ref| + action = if project.protected_branch?(ref) + :push_code_to_protected_branches + else + :push_code + end + + return false unless can?(user, action, project) + end - can?(user, action, project) + # Never let git-receive-pack trough unauthenticated; it's + # harmless but git < 1.8 doesn't like it + return false if user.nil? + true else false end end + def service_name + if @request.get? + @request.params['service'] + elsif @request.post? + File.basename(@request.path) + else + nil + end + end + def project @project ||= project_by_path(@request.path_info) end - def ref - @ref ||= parse_ref + def refs + @refs ||= parse_refs end - def parse_ref + def parse_refs input = if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/ Zlib::GzipReader.new(@request.body).read else @@ -108,7 +128,15 @@ module Grack # Need to reset seek point @request.body.rewind - /refs\/heads\/([\/\w\.-]+)/n.match(input.force_encoding('ascii-8bit')).to_a.last + + # Parse refs + refs = input.force_encoding('ascii-8bit').scan(/refs\/heads\/([\/\w\.-]+)/n).flatten.compact + + # Cleanup grabare from refs + # if push to multiple branches + refs.map do |ref| + ref.gsub(/00.*/, "") + end end end end diff --git a/lib/gitlab/backend/grack_helpers.rb b/lib/gitlab/backend/grack_helpers.rb index 5ac9e9f325be1fefd9e908036c4598e114c82c25..cb747fe0137a3c0354d1b487a1c8cb9e1eff1e52 100644 --- a/lib/gitlab/backend/grack_helpers.rb +++ b/lib/gitlab/backend/grack_helpers.rb @@ -1,7 +1,7 @@ module Grack module Helpers def project_by_path(path) - if m = /^\/([\w\.\/-]+)\.git/.match(path).to_a + if m = /^([\w\.\/-]+)\.git/.match(path).to_a path_with_namespace = m.last path_with_namespace.gsub!(/\.wiki$/, '') diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index c819ce56ac91319c6cb745a66f108ed3619781fe..7121c8e40d279348c75f9bdfc9549822a4c8a7cd 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -10,7 +10,7 @@ module Gitlab # add_repository("gitlab/gitlab-ci") # def add_repository(name) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "add-project", "#{name}.git" + system "#{gitlab_shell_path}/bin/gitlab-projects", "add-project", "#{name}.git" end # Import repository @@ -21,7 +21,7 @@ module Gitlab # import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git") # def import_repository(name, url) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "import-project", "#{name}.git", url + system "#{gitlab_shell_path}/bin/gitlab-projects", "import-project", "#{name}.git", url end # Move repository @@ -33,7 +33,7 @@ module Gitlab # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git") # def mv_repository(path, new_path) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "mv-project", "#{path}.git", "#{new_path}.git" + system "#{gitlab_shell_path}/bin/gitlab-projects", "mv-project", "#{path}.git", "#{new_path}.git" end # Update HEAD for repository @@ -45,7 +45,7 @@ module Gitlab # update_repository_head("gitlab/gitlab-ci", "3-1-stable") # def update_repository_head(path, branch) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "update-head", "#{path}.git", branch + system "#{gitlab_shell_path}/bin/gitlab-projects", "update-head", "#{path}.git", branch end # Fork repository to new namespace @@ -57,7 +57,7 @@ module Gitlab # fork_repository("gitlab/gitlab-ci", "randx") # def fork_repository(path, fork_namespace) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "fork-project", "#{path}.git", fork_namespace + system "#{gitlab_shell_path}/bin/gitlab-projects", "fork-project", "#{path}.git", fork_namespace end # Remove repository from file system @@ -68,7 +68,7 @@ module Gitlab # remove_repository("gitlab/gitlab-ci") # def remove_repository(name) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "rm-project", "#{name}.git" + system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-project", "#{name}.git" end # Add repository branch from passed ref @@ -81,7 +81,7 @@ module Gitlab # add_branch("gitlab/gitlab-ci", "4-0-stable", "master") # def add_branch(path, branch_name, ref) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "create-branch", "#{path}.git", branch_name, ref + system "#{gitlab_shell_path}/bin/gitlab-projects", "create-branch", "#{path}.git", branch_name, ref end # Remove repository branch @@ -93,7 +93,7 @@ module Gitlab # rm_branch("gitlab/gitlab-ci", "4-0-stable") # def rm_branch(path, branch_name) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "rm-branch", "#{path}.git", branch_name + system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-branch", "#{path}.git", branch_name end # Add repository tag from passed ref @@ -106,7 +106,7 @@ module Gitlab # add_tag("gitlab/gitlab-ci", "v4.0", "master") # def add_tag(path, tag_name, ref) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "create-tag", "#{path}.git", tag_name, ref + system "#{gitlab_shell_path}/bin/gitlab-projects", "create-tag", "#{path}.git", tag_name, ref end # Remove repository tag @@ -118,7 +118,7 @@ module Gitlab # rm_tag("gitlab/gitlab-ci", "v4.0") # def rm_tag(path, tag_name) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "rm-tag", "#{path}.git", tag_name + system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-tag", "#{path}.git", tag_name end # Add new key to gitlab-shell @@ -127,7 +127,7 @@ module Gitlab # add_key("key-42", "sha-rsa ...") # def add_key(key_id, key_content) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-keys", "add-key", key_id, key_content + system "#{gitlab_shell_path}/bin/gitlab-keys", "add-key", key_id, key_content end # Remove ssh key from gitlab shell @@ -136,7 +136,7 @@ module Gitlab # remove_key("key-342", "sha-rsa ...") # def remove_key(key_id, key_content) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-keys", "rm-key", key_id, key_content + system "#{gitlab_shell_path}/bin/gitlab-keys", "rm-key", key_id, key_content end # Remove all ssh keys from gitlab shell @@ -145,7 +145,7 @@ module Gitlab # remove_all_keys # def remove_all_keys - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-keys", "clear" + system "#{gitlab_shell_path}/bin/gitlab-keys", "clear" end # Add empty directory for storing repositories @@ -196,8 +196,21 @@ module Gitlab Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git" end + # Return GitLab shell version + def version + gitlab_shell_version_file = "#{gitlab_shell_path}/VERSION" + + if File.readable?(gitlab_shell_version_file) + File.read(gitlab_shell_version_file) + end + end + protected + def gitlab_shell_path + Gitlab.config.gitlab_shell.path + end + def gitlab_shell_user_home File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}") end diff --git a/lib/gitlab/blacklist.rb b/lib/gitlab/blacklist.rb index 2f9091e07df23308f9ae89c7db581779d4544e17..6bc2c3b487c7501712ebeb928dca2df00dde4fe2 100644 --- a/lib/gitlab/blacklist.rb +++ b/lib/gitlab/blacklist.rb @@ -3,7 +3,7 @@ module Gitlab extend self def path - %w(admin dashboard groups help profile projects search public assets u s teams merge_requests issues users snippets services repository hooks notes) + %w(admin dashboard files groups help profile projects search public assets u s teams merge_requests issues users snippets services repository hooks notes) end end end diff --git a/lib/gitlab/identifier.rb b/lib/gitlab/identifier.rb index a1ff248a77fd78696ed3f86f6e5c540f67dba6a3..6e4de197eeb3b31bbe6ba44c143decea3b811eaf 100644 --- a/lib/gitlab/identifier.rb +++ b/lib/gitlab/identifier.rb @@ -6,17 +6,17 @@ module Gitlab if identifier.blank? # Local push from gitlab email = project.repository.commit(newrev).author_email rescue nil - User.find_by_email(email) if email + User.find_by(email: email) if email elsif identifier =~ /\Auser-\d+\Z/ # git push over http user_id = identifier.gsub("user-", "") - User.find_by_id(user_id) + User.find_by(id: user_id) elsif identifier =~ /\Akey-\d+\Z/ # git push over ssh key_id = identifier.gsub("key-", "") - Key.find_by_id(key_id).try(:user) + Key.find_by(id: key_id).try(:user) end end end diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb index 260bacfeeb093d79509978496ee684d7fb46e7e8..fd36dda7d22a8b8a61538c0e36b4d95c9747e02b 100644 --- a/lib/gitlab/ldap/user.rb +++ b/lib/gitlab/ldap/user.rb @@ -23,8 +23,8 @@ module Gitlab # Look for user with same emails # # Possible cases: - # * When user already has account and need to link his LDAP account. - # * LDAP uid changed for user with same email and we need to update his uid + # * When user already has account and need to link their LDAP account. + # * LDAP uid changed for user with same email and we need to update their uid # user = find_user(email) @@ -44,13 +44,13 @@ module Gitlab end def find_user(email) - user = model.find_by_email(email) + user = model.find_by(email: email) # If no user found and allow_username_or_email_login is true - # we look for user by extracting part of his email + # we look for user by extracting part of their email if !user && email && ldap_conf['allow_username_or_email_login'] uname = email.partition('@').first - user = model.find_by_username(uname) + user = model.find_by(username: uname) end user @@ -71,6 +71,16 @@ module Gitlab find_by_uid(ldap_user.dn) if ldap_user end + # Check LDAP user existance by dn. User in git over ssh check + # + # It covers 2 cases: + # * when ldap account was removed + # * when ldap account was deactivated by change of OU membership in 'dn' + def blocked?(dn) + ldap = OmniAuth::LDAP::Adaptor.new(ldap_conf) + ldap.connection.search(base: dn, scope: Net::LDAP::SearchScope_BaseObject, size: 1).blank? + end + private def find_by_uid(uid) diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb index 1b32b99f4ba44b24228aafa83188b1be48a46201..529753c40191056be61274d79ca8196c1750b614 100644 --- a/lib/gitlab/oauth/user.rb +++ b/lib/gitlab/oauth/user.rb @@ -28,6 +28,7 @@ module Gitlab } user = model.build_user(opts, as: :admin) + user.skip_confirmation! user.save! log.info "(OAuth) Creating user #{email} from login with extern_uid => #{uid}" diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb index 2f30fde20784d27ee5d4531df2059f8168d56789..5283cf0b82151bf8667ffb076b1bc0bb6914bd65 100644 --- a/lib/gitlab/popen.rb +++ b/lib/gitlab/popen.rb @@ -1,9 +1,15 @@ +require 'fileutils' + module Gitlab module Popen def popen(cmd, path) vars = { "PWD" => path } options = { chdir: path } + unless File.directory?(path) + FileUtils.mkdir_p(path) + end + @cmd_output = "" @cmd_status = 0 Open3.popen3(vars, cmd, options) do |stdin, stdout, stderr, wait_thr| diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index b4be46d3b42a7bdc49ad9921f23fa0793d60ac07..d18fc8bf2cecbe21bbbd5da692eaf83e32c48638 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -18,10 +18,38 @@ module Gitlab default_regex end + def archive_formats_regex + #|zip|tar| tar.gz | tar.bz2 | + /(zip|tar|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)/ + end + + def git_reference_regex + # Valid git ref regex, see: + # https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html + + %r{ + (?! + (?# doesn't begins with) + \/| (?# rule #6) + (?# doesn't contain) + .*(?: + [\/.]\.| (?# rule #1,3) + \/\/| (?# rule #6) + @\{| (?# rule #8) + \\ (?# rule #9) + ) + ) + [^\000-\040\177~^:?*\[]+ (?# rule #4-5) + (?# doesn't end with) + (?<!\.lock) (?# rule #1) + (?<![\/.]) (?# rule #6-7) + }x + end + protected def default_regex - /\A[a-zA-Z0-9][a-zA-Z0-9_\-\.]*\z/ + /\A[.?]?[a-zA-Z0-9][a-zA-Z0-9_\-\.]*(?<!\.git)\z/ end end end diff --git a/lib/gitlab/satellite/files/delete_file_action.rb b/lib/gitlab/satellite/files/delete_file_action.rb new file mode 100644 index 0000000000000000000000000000000000000000..30462999aa3b5d7e35d0d6d8e0ebdb0cb7190594 --- /dev/null +++ b/lib/gitlab/satellite/files/delete_file_action.rb @@ -0,0 +1,50 @@ +require_relative 'file_action' + +module Gitlab + module Satellite + class DeleteFileAction < FileAction + # Deletes file and creates a new commit for it + # + # Returns false if committing the change fails + # Returns false if pushing from the satellite to bare repo failed or was rejected + # Returns true otherwise + def commit!(content, commit_message) + in_locked_and_timed_satellite do |repo| + prepare_satellite!(repo) + + # create target branch in satellite at the corresponding commit from bare repo + repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}") + + # update the file in the satellite's working dir + file_path_in_satellite = File.join(repo.working_dir, file_path) + + # Prevent relative links + unless safe_path?(file_path_in_satellite) + Gitlab::GitLogger.error("FileAction: Relative path not allowed") + return false + end + + File.delete(file_path_in_satellite) + + # add removed file + repo.remove(file_path_in_satellite) + + # commit the changes + # will raise CommandFailed when commit fails + repo.git.commit(raise: true, timeout: true, a: true, m: commit_message) + + + # push commit back to bare repo + # will raise CommandFailed when push fails + repo.git.push({raise: true, timeout: true}, :origin, ref) + + # everything worked + true + end + rescue Grit::Git::CommandFailed => ex + Gitlab::GitLogger.error(ex.message) + false + end + end + end +end diff --git a/lib/gitlab/satellite/edit_file_action.rb b/lib/gitlab/satellite/files/edit_file_action.rb similarity index 59% rename from lib/gitlab/satellite/edit_file_action.rb rename to lib/gitlab/satellite/files/edit_file_action.rb index d793d0ba8dc674c37cdd6cdfc1beca1aee62dbbb..cbdf70f7d129c10866b0a08e0882be7123be244c 100644 --- a/lib/gitlab/satellite/edit_file_action.rb +++ b/lib/gitlab/satellite/files/edit_file_action.rb @@ -1,40 +1,40 @@ +require_relative 'file_action' + module Gitlab module Satellite # GitLab server-side file update and commit - class EditFileAction < Action - attr_accessor :file_path, :ref - - def initialize(user, project, ref, file_path) - super user, project, git_timeout: 10.seconds - @file_path = file_path - @ref = ref - end - + class EditFileAction < FileAction # Updates the files content and creates a new commit for it # # Returns false if the ref has been updated while editing the file # Returns false if committing the change fails - # Returns false if pushing from the satellite to Gitolite failed or was rejected + # Returns false if pushing from the satellite to bare repo failed or was rejected # Returns true otherwise - def commit!(content, commit_message, last_commit) - return false unless can_edit?(last_commit) - + def commit!(content, commit_message, encoding) in_locked_and_timed_satellite do |repo| prepare_satellite!(repo) - # create target branch in satellite at the corresponding commit from Gitolite + # create target branch in satellite at the corresponding commit from bare repo repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}") # update the file in the satellite's working dir file_path_in_satellite = File.join(repo.working_dir, file_path) - File.open(file_path_in_satellite, 'w') { |f| f.write(content) } + + # Prevent relative links + unless safe_path?(file_path_in_satellite) + Gitlab::GitLogger.error("FileAction: Relative path not allowed") + return false + end + + # Write file + write_file(file_path_in_satellite, content, encoding) # commit the changes # will raise CommandFailed when commit fails repo.git.commit(raise: true, timeout: true, a: true, m: commit_message) - # push commit back to Gitolite + # push commit back to bare repo # will raise CommandFailed when push fails repo.git.push({raise: true, timeout: true}, :origin, ref) @@ -45,13 +45,6 @@ module Gitlab Gitlab::GitLogger.error(ex.message) false end - - protected - - def can_edit?(last_commit) - current_last_commit = Gitlab::Git::Commit.last_for_path(@project.repository, ref, file_path).sha - last_commit == current_last_commit - end end end end diff --git a/lib/gitlab/satellite/files/file_action.rb b/lib/gitlab/satellite/files/file_action.rb new file mode 100644 index 0000000000000000000000000000000000000000..7701a6d5d608ea1759a7df6819a468c390dff79f --- /dev/null +++ b/lib/gitlab/satellite/files/file_action.rb @@ -0,0 +1,25 @@ +module Gitlab + module Satellite + class FileAction < Action + attr_accessor :file_path, :ref + + def initialize(user, project, ref, file_path) + super user, project, git_timeout: 10.seconds + @file_path = file_path + @ref = ref + end + + def safe_path?(path) + File.absolute_path(path) == path + end + + def write_file(abs_file_path, content, file_encoding = 'text') + if file_encoding == 'base64' + File.open(abs_file_path, 'wb') { |f| f.write(Base64.decode64(content)) } + else + File.open(abs_file_path, 'w') { |f| f.write(content) } + end + end + end + end +end diff --git a/lib/gitlab/satellite/files/new_file_action.rb b/lib/gitlab/satellite/files/new_file_action.rb new file mode 100644 index 0000000000000000000000000000000000000000..15e9b7a6f77555471910088deff2a231533179ec --- /dev/null +++ b/lib/gitlab/satellite/files/new_file_action.rb @@ -0,0 +1,55 @@ +require_relative 'file_action' + +module Gitlab + module Satellite + class NewFileAction < FileAction + # Updates the files content and creates a new commit for it + # + # Returns false if the ref has been updated while editing the file + # Returns false if committing the change fails + # Returns false if pushing from the satellite to bare repo failed or was rejected + # Returns true otherwise + def commit!(content, commit_message, encoding) + in_locked_and_timed_satellite do |repo| + prepare_satellite!(repo) + + # create target branch in satellite at the corresponding commit from bare repo + repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}") + + file_path_in_satellite = File.join(repo.working_dir, file_path) + dir_name_in_satellite = File.dirname(file_path_in_satellite) + + # Prevent relative links + unless safe_path?(file_path_in_satellite) + Gitlab::GitLogger.error("FileAction: Relative path not allowed") + return false + end + + # Create dir if not exists + FileUtils.mkdir_p(dir_name_in_satellite) + + # Write file + write_file(file_path_in_satellite, content, encoding) + + # add new file + repo.add(file_path_in_satellite) + + # commit the changes + # will raise CommandFailed when commit fails + repo.git.commit(raise: true, timeout: true, a: true, m: commit_message) + + + # push commit back to bare repo + # will raise CommandFailed when push fails + repo.git.push({raise: true, timeout: true}, :origin, ref) + + # everything worked + true + end + rescue Grit::Git::CommandFailed => ex + Gitlab::GitLogger.error(ex.message) + false + end + end + end +end diff --git a/lib/gitlab/satellite/merge_action.rb b/lib/gitlab/satellite/merge_action.rb index 156483be8dd935923c9b73efe81e58f1f6c8b482..85615f282c4f6d179bc6d75a2a769cf166b14d32 100644 --- a/lib/gitlab/satellite/merge_action.rb +++ b/lib/gitlab/satellite/merge_action.rb @@ -24,11 +24,11 @@ module Gitlab # Returns false if the merge produced conflicts # Returns false if pushing from the satellite to the repository failed or was rejected # Returns true otherwise - def merge! + def merge!(merge_commit_message = nil) in_locked_and_timed_satellite do |merge_repo| prepare_satellite!(merge_repo) - if merge_in_satellite!(merge_repo) - # push merge back to Gitolite + if merge_in_satellite!(merge_repo, merge_commit_message) + # push merge back to bare repo # will raise CommandFailed when push fails merge_repo.git.push(default_options, :origin, merge_request.target_branch) # remove source branch @@ -49,14 +49,7 @@ module Gitlab in_locked_and_timed_satellite do |merge_repo| prepare_satellite!(merge_repo) update_satellite_source_and_target!(merge_repo) - - if merge_request.for_fork? - diff = merge_repo.git.native(:diff, default_options, "origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}") - else - diff = merge_repo.git.native(:diff, default_options, "#{merge_request.target_branch}", "#{merge_request.source_branch}") - end - - return diff + diff = merge_repo.git.native(:diff, default_options, "origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}") end rescue Grit::Git::CommandFailed => ex handle_exception(ex) @@ -88,14 +81,7 @@ module Gitlab in_locked_and_timed_satellite do |merge_repo| prepare_satellite!(merge_repo) update_satellite_source_and_target!(merge_repo) - - if (merge_request.for_fork?) - patch = merge_repo.git.format_patch(default_options({stdout: true}), "origin/#{merge_request.target_branch}..source/#{merge_request.source_branch}") - else - patch = merge_repo.git.format_patch(default_options({stdout: true}), "#{merge_request.target_branch}..#{merge_request.source_branch}") - end - - return patch + patch = merge_repo.git.format_patch(default_options({stdout: true}), "origin/#{merge_request.target_branch}..source/#{merge_request.source_branch}") end rescue Grit::Git::CommandFailed => ex handle_exception(ex) @@ -125,36 +111,26 @@ module Gitlab # # Returns false if the merge produced conflicts # Returns true otherwise - def merge_in_satellite!(repo) + def merge_in_satellite!(repo, message = nil) update_satellite_source_and_target!(repo) + message ||= "Merge branch '#{merge_request.source_branch}' into '#{merge_request.target_branch}'" + # merge the source branch into the satellite # will raise CommandFailed when merge fails - if merge_request.for_fork? - repo.git.pull(default_options({no_ff: true}), 'source', merge_request.source_branch) - else - repo.git.pull(default_options({no_ff: true}), 'origin', merge_request.source_branch) - end + repo.git.merge(default_options({no_ff: true}), "-m #{message}", "source/#{merge_request.source_branch}") rescue Grit::Git::CommandFailed => ex handle_exception(ex) end # Assumes a satellite exists that is a fresh clone of the projects repo, prepares satellite for merges, diffs etc def update_satellite_source_and_target!(repo) - if merge_request.for_fork? - repo.remote_add('source', merge_request.source_project.repository.path_to_repo) - repo.remote_fetch('source') - repo.git.checkout(default_options({b: true}), merge_request.target_branch, "origin/#{merge_request.target_branch}") - else - # We can't trust the input here being branch names, we can't always check it out because it could be a relative ref i.e. HEAD~3 - # we could actually remove the if true, because it should never ever happen (as long as the satellite has been prepared) - repo.git.checkout(default_options, "#{merge_request.source_branch}") - repo.git.checkout(default_options, "#{merge_request.target_branch}") - end + repo.remote_add('source', merge_request.source_project.repository.path_to_repo) + repo.remote_fetch('source') + repo.git.checkout(default_options({b: true}), merge_request.target_branch, "origin/#{merge_request.target_branch}") rescue Grit::Git::CommandFailed => ex handle_exception(ex) end - end end end diff --git a/lib/gitlab/satellite/satellite.rb b/lib/gitlab/satellite/satellite.rb index 6cb7814fae56b342da7387ed4af347f232ff2b60..353c3024aad4d6cae77db40030fd8d3d7ebc4b68 100644 --- a/lib/gitlab/satellite/satellite.rb +++ b/lib/gitlab/satellite/satellite.rb @@ -123,7 +123,7 @@ module Gitlab remotes.each { |name| repo.git.remote(default_options,'rm', name)} end - # Updates the satellite from Gitolite + # Updates the satellite from bare repo # # Note: this will only update remote branches (i.e. origin/*) def update_from_source! diff --git a/lib/gitlab/theme.rb b/lib/gitlab/theme.rb index 89604162304d14c60357e5ddbac156843d7b1bba..44237a062fcd2758419091a40ef531e91793332d 100644 --- a/lib/gitlab/theme.rb +++ b/lib/gitlab/theme.rb @@ -15,7 +15,7 @@ module Gitlab COLOR => "ui_color" } - id ||= 1 + id ||= Gitlab.config.gitlab.default_theme return themes[id] end diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb new file mode 100644 index 0000000000000000000000000000000000000000..f46685e4bbe99b9fee446cc2df7ff1f92a12409b --- /dev/null +++ b/lib/gitlab/upgrader.rb @@ -0,0 +1,96 @@ +require_relative "version_info" + +module Gitlab + class Upgrader + def execute + puts "GitLab #{current_version.major} upgrade tool" + puts "Your version is #{current_version}" + puts "Latest available version for GitLab #{current_version.major} is #{latest_version}" + + if latest_version? + puts "You are using the latest GitLab version" + else + puts "Newer GitLab version is available" + answer = if ARGV.first == "-y" + "yes" + else + prompt("Do you want to upgrade (yes/no)? ", %w{yes no}) + end + + if answer == "yes" + upgrade + else + exit 0 + end + end + end + + def latest_version? + current_version >= latest_version + end + + def current_version + @current_version ||= Gitlab::VersionInfo.parse(current_version_raw) + end + + def latest_version + @latest_version ||= Gitlab::VersionInfo.parse(latest_version_raw) + end + + def current_version_raw + File.read(File.join(gitlab_path, "VERSION")).strip + end + + def latest_version_raw + git_tags = `git ls-remote --tags origin | grep tags\/v#{current_version.major}` + git_tags = git_tags.lines.to_a.select { |version| version =~ /v\d\.\d\.\d\Z/ } + last_tag = git_tags.last.match(/v\d\.\d\.\d/).to_s + end + + def update_commands + { + "Stash changed files" => "git stash", + "Get latest code" => "git fetch", + "Switch to new version" => "git checkout v#{latest_version}", + "Install gems" => "bundle", + "Migrate DB" => "bundle exec rake db:migrate RAILS_ENV=production", + "Recompile assets" => "bundle exec rake assets:clean assets:precompile RAILS_ENV=production", + "Clear cache" => "bundle exec rake cache:clear RAILS_ENV=production" + } + end + + def upgrade + update_commands.each do |title, cmd| + puts title + puts " -> #{cmd}" + if system(cmd) + puts " -> OK" + else + puts " -> FAILED" + puts "Failed to upgrade. Try to repeat task or proceed with upgrade manually " + exit 1 + end + end + + puts "Done" + end + + def gitlab_path + File.expand_path(File.join(File.dirname(__FILE__), '../..')) + end + + # Prompt the user to input something + # + # message - the message to display before input + # choices - array of strings of acceptable answers or nil for any answer + # + # Returns the user's answer + def prompt(message, choices = nil) + begin + print(message) + answer = STDIN.gets.chomp + end while !choices.include?(answer) + answer + end + end +end diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb new file mode 100644 index 0000000000000000000000000000000000000000..eada9bcddf5b9ded74c0570e91ef9cebf01aa97f --- /dev/null +++ b/lib/gitlab/visibility_level.rb @@ -0,0 +1,42 @@ +# Gitlab::VisibilityLevel module +# +# Define allowed public modes that can be used for +# GitLab projects to determine project public mode +# +module Gitlab + module VisibilityLevel + PRIVATE = 0 + INTERNAL = 10 + PUBLIC = 20 + + class << self + def values + options.values + end + + def options + { + 'Private' => PRIVATE, + 'Internal' => INTERNAL, + 'Public' => PUBLIC + } + end + + def allowed_for?(user, level) + user.is_admin? || !Gitlab.config.gitlab.restricted_visibility_levels.include?(level) + end + end + + def private? + visibility_level_field == PRIVATE + end + + def internal? + visibility_level_field == INTERNAL + end + + def public? + visibility_level_field == PUBLIC + end + end +end diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb index d9c2d3b626d70b192873d2c5866c63fd56f0723a..6da0c1d6f96bc356f4be803eeca52e5bb36fdacf 100644 --- a/lib/redcarpet/render/gitlab_html.rb +++ b/lib/redcarpet/render/gitlab_html.rb @@ -6,14 +6,12 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML def initialize(template, options = {}) @template = template @project = @template.instance_variable_get("@project") + @ref = @template.instance_variable_get("@ref") + @request_path = @template.instance_variable_get("@path") super options end def block_code(code, language) - options = { options: {encoding: 'utf-8'} } - lexer = Pygments::Lexer.find(language) # language can be an alias - options.merge!(lexer: lexer.aliases[0].downcase) if lexer # downcase is required - # New lines are placed to fix an rendering issue # with code wrapped inside <h1> tag for next case: # @@ -23,7 +21,11 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML # <<-HTML - <div class="#{h.user_color_scheme_class}">#{Pygments.highlight(code, options)}</div> +<div class="highlighted-data #{h.user_color_scheme_class}"> + <div class="highlight"> + <pre><code class="#{language}">#{code}</code></pre> + </div> +</div> HTML end @@ -32,7 +34,19 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML h.link_to_gfm(content, link, title: title) end + def preprocess(full_document) + if @project + h.create_relative_links(full_document, @project, @ref, @request_path, is_wiki?) + else + full_document + end + end + def postprocess(full_document) h.gfm(full_document) end + + def is_wiki? + @template.instance_variable_get("@wiki") + end end diff --git a/lib/support/deploy/deploy.sh b/lib/support/deploy/deploy.sh index 0d2f8418bcf964af88c2bc0eaec959ded73b4132..b96f73058b68036da5a42b03753407963fe099bc 100755 --- a/lib/support/deploy/deploy.sh +++ b/lib/support/deploy/deploy.sh @@ -28,17 +28,18 @@ sudo -u git -H git pull origin master echo 'Deploy: Bundle and migrate' # change it to your needs -sudo -u git -H bundle --without postgres +sudo -u git -H bundle --without aws development test postgres --deployment sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production +sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production +sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production # return stashed changes (if necessary) # sudo -u git -H git stash pop - echo 'Deploy: Starting GitLab server...' sudo service gitlab start -sleep 10 sudo -u git -H rm /home/git/gitlab/public/index.html -echo 'Deploy: Done' +echo 'Deploy: Done' \ No newline at end of file diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index 0248284f8d5efe314aa5443ee873645bc031b9e3..c6e570784e07f4aafa01254e6f7bd163ddfeda6c 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -3,7 +3,6 @@ # GITLAB # Maintainer: @randx # Authors: rovanion.luckey@gmail.com, @randx -# App Version: 6.0 ### BEGIN INIT INFO # Provides: gitlab @@ -15,36 +14,44 @@ # Description: GitLab git repository management ### END INIT INFO + +### +# DO NOT EDIT THIS FILE! +# This file will be overwritten on update. +# Instead add/change your variables in /etc/default/gitlab +# An example defaults file can be found in lib/support/init.d/gitlab.default.example +### + + ### Environment variables RAILS_ENV="production" -# Script variable names should be lower-case not to conflict with internal -# /bin/sh variables such as PATH, EDITOR or SHELL. -app_root="/home/git/gitlab" +# Script variable names should be lower-case not to conflict with +# internal /bin/sh variables such as PATH, EDITOR or SHELL. app_user="git" -unicorn_conf="$app_root/config/unicorn.rb" +app_root="/home/$app_user/gitlab" pid_path="$app_root/tmp/pids" socket_path="$app_root/tmp/sockets" web_server_pid_path="$pid_path/unicorn.pid" sidekiq_pid_path="$pid_path/sidekiq.pid" - - -### Here ends user configuration ### - +# Read configuration variable file if it is present +test -f /etc/default/gitlab && . /etc/default/gitlab # Switch to the app_user if it is not he/she who is running the script. if [ "$USER" != "$app_user" ]; then sudo -u "$app_user" -H -i $0 "$@"; exit; fi -# Switch to the gitlab path, if it fails exit with an error. +# Switch to the gitlab path, exit on failure. if ! cd "$app_root" ; then echo "Failed to cd into $app_root, exiting!"; exit 1 fi + ### Init Script functions +## Gets the pids from the files check_pids(){ if ! mkdir -p "$pid_path"; then echo "Could not create the path $pid_path needed to store the pids." @@ -63,12 +70,29 @@ check_pids(){ fi } +## Called when we have started the two processes and are waiting for their pid files. +wait_for_pids(){ + # We are sleeping a bit here mostly because sidekiq is slow at writing it's pid + i=0; + while [ ! -f $web_server_pid_path -o ! -f $sidekiq_pid_path ]; do + sleep 0.1; + i=$((i+1)) + if [ $((i%10)) = 0 ]; then + echo -n "." + elif [ $((i)) = 301 ]; then + echo "Waited 30s for the processes to write their pids, something probably went wrong." + exit 1; + fi + done + echo +} + # We use the pids in so many parts of the script it makes sense to always check them. # Only after start() is run should the pids change. Sidekiq sets it's own pid. check_pids -# Checks whether the different parts of the service are already running or not. +## Checks whether the different parts of the service are already running or not. check_status(){ check_pids # If the web server is running kill -0 $wpid returns true, or rather 0. @@ -85,9 +109,16 @@ check_status(){ else sidekiq_status="-1" fi + if [ $web_status = 0 -a $sidekiq_status = 0 ]; then + gitlab_status=0 + else + # http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html + # code 3 means 'program is not running' + gitlab_status=3 + fi } -# Check for stale pids and remove them if necessary +## Check for stale pids and remove them if necessary. check_stale_pids(){ check_status # If there is a pid it is something else than 0, the service is running if @@ -95,7 +126,7 @@ check_stale_pids(){ if [ "$wpid" != "0" -a "$web_status" != "0" ]; then echo "Removing stale Unicorn web server pid. This is most likely caused by the web server crashing the last time it ran." if ! rm "$web_server_pid_path"; then - echo "Unable to remove stale pid, exiting" + echo "Unable to remove stale pid, exiting." exit 1 fi fi @@ -108,7 +139,7 @@ check_stale_pids(){ fi } -# If no parts of the service is running, bail out. +## If no parts of the service is running, bail out. exit_if_not_running(){ check_stale_pids if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then @@ -117,86 +148,92 @@ exit_if_not_running(){ fi } -# Starts Unicorn and Sidekiq. +## Starts Unicorn and Sidekiq if they're not running. start() { check_stale_pids + if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then + echo -n "Starting both the GitLab Unicorn and Sidekiq" + elif [ "$web_status" != "0" ]; then + echo -n "Starting GitLab Sidekiq" + elif [ "$sidekiq_status" != "0" ]; then + echo -n "Starting GitLab Unicorn" + fi + # Then check if the service is running. If it is: don't start again. if [ "$web_status" = "0" ]; then echo "The Unicorn web server already running with pid $wpid, not restarting." else - echo "Starting the GitLab Unicorn web server..." # Remove old socket if it exists rm -f "$socket_path"/gitlab.socket 2>/dev/null - # Start the webserver - bundle exec unicorn_rails -D -c "$unicorn_conf" -E "$RAILS_ENV" + # Start the web server + RAILS_ENV=$RAILS_ENV script/web start & fi # If sidekiq is already running, don't start it again. if [ "$sidekiq_status" = "0" ]; then echo "The Sidekiq job dispatcher is already running with pid $spid, not restarting" else - echo "Starting the GitLab Sidekiq event dispatcher..." - RAILS_ENV=$RAILS_ENV bundle exec rake sidekiq:start - # We are sleeping a bit here because sidekiq is slow at writing it's pid - sleep 2 + RAILS_ENV=$RAILS_ENV script/background_jobs start & fi + # Wait for the pids to be planted + wait_for_pids # Finally check the status to tell wether or not GitLab is running - status + print_status } -# Asks the Unicorn and the Sidekiq if they would be so kind as to stop, if not kills them. +## Asks the Unicorn and the Sidekiq if they would be so kind as to stop, if not kills them. stop() { exit_if_not_running + + if [ "$web_status" = "0" -a "$sidekiq_status" = "0" ]; then + echo -n "Shutting down both Unicorn and Sidekiq" + elif [ "$web_status" = "0" ]; then + echo -n "Shutting down Sidekiq" + elif [ "$sidekiq_status" = "0" ]; then + echo -n "Shutting down Unicorn" + fi + # If the Unicorn web server is running, tell it to stop; if [ "$web_status" = "0" ]; then - kill -QUIT "$wpid" & - echo "Stopping the GitLab Unicorn web server..." - stopping=true - else - echo "The Unicorn web was not running, doing nothing." + RAILS_ENV=$RAILS_ENV script/web stop fi # And do the same thing for the Sidekiq. if [ "$sidekiq_status" = "0" ]; then - printf "Stopping Sidekiq job dispatcher." - RAILS_ENV=$RAILS_ENV bundle exec rake sidekiq:stop & - stopping=true - else - echo "The Sidekiq was not running, must have run out of breath." + RAILS_ENV=$RAILS_ENV script/background_jobs stop fi - # If something needs to be stopped, lets wait for it to stop. Never use SIGKILL in a script. - while [ "$stopping" = "true" ]; do + while [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; do sleep 1 check_status - if [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; then - printf "." - else + printf "." + if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then printf "\n" break fi done + sleep 1 # Cleaning up unused pids rm "$web_server_pid_path" 2>/dev/null # rm "$sidekiq_pid_path" # Sidekiq seems to be cleaning up it's own pid. - status + print_status } -# Returns the status of GitLab and it's components -status() { +## Prints the status of GitLab and it's components. +print_status() { check_status if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then echo "GitLab is not running." return fi if [ "$web_status" = "0" ]; then - echo "The GitLab Unicorn webserver with pid $wpid is running." + echo "The GitLab Unicorn web server with pid $wpid is running." else - printf "The GitLab Unicorn webserver is \033[31mnot running\033[0m.\n" + printf "The GitLab Unicorn web server is \033[31mnot running\033[0m.\n" fi if [ "$sidekiq_status" = "0" ]; then echo "The GitLab Sidekiq job dispatcher with pid $spid is running." @@ -208,6 +245,7 @@ status() { fi } +## Tells unicorn to reload it's config and Sidekiq to restart reload(){ exit_if_not_running if [ "$wpid" = "0" ];then @@ -215,17 +253,16 @@ reload(){ exit 1 fi printf "Reloading GitLab Unicorn configuration... " - kill -USR2 "$wpid" + RAILS_ENV=$RAILS_ENV script/web reload echo "Done." echo "Restarting GitLab Sidekiq since it isn't capable of reloading its config..." - RAILS_ENV=$RAILS_ENV bundle exec rake sidekiq:stop - echo "Starting Sidekiq..." - RAILS_ENV=$RAILS_ENV bundle exec rake sidekiq:start - # Waiting 2 seconds for sidekiq to write it. - sleep 2 - status + RAILS_ENV=$RAILS_ENV script/background_jobs restart + + wait_for_pids + print_status } +## Restarts Sidekiq and Unicorn. restart(){ check_status if [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; then @@ -235,7 +272,7 @@ restart(){ } -## Finally the input handling. +### Finally the input handling. case "$1" in start) @@ -251,7 +288,8 @@ case "$1" in reload ;; status) - status + print_status + exit $gitlab_status ;; *) echo "Usage: service gitlab {start|stop|restart|reload|status}" diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example new file mode 100755 index 0000000000000000000000000000000000000000..9951bacedf513b368ece97d11f08546d93485810 --- /dev/null +++ b/lib/support/init.d/gitlab.default.example @@ -0,0 +1,31 @@ +# Copy this lib/support/init.d/gitlab.default.example file to +# /etc/default/gitlab in order for it to apply to your system. + +# RAILS_ENV defines the type of installation that is running. +# Normal values are "production", "test" and "development". +RAILS_ENV="production" + +# app_user defines the user that GitLab is run as. +# The default is "git". +app_user="git" + +# app_root defines the folder in which gitlab and it's components are installed. +# The default is "/home/$app_user/gitlab" +app_root="/home/$app_user/gitlab" + +# pid_path defines a folder in which the gitlab and it's components place their pids. +# This variable is also used below to define the relevant pids for the gitlab components. +# The default is "$app_root/tmp/pids" +pid_path="$app_root/tmp/pids" + +# socket_path defines the folder in which gitlab places the sockets +#The default is "$app_root/tmp/sockets" +socket_path="$app_root/tmp/sockets" + +# web_server_pid_path defines the path in which to create the pid file fo the web_server +# The default is "$pid_path/unicorn.pid" +web_server_pid_path="$pid_path/unicorn.pid" + +# sidekiq_pid_path defines the path in which to create the pid file for sidekiq +# The default is "$pid_path/sidekiq.pid" +sidekiq_pid_path="$pid_path/sidekiq.pid" diff --git a/lib/support/logrotate/gitlab b/lib/support/logrotate/gitlab new file mode 100644 index 0000000000000000000000000000000000000000..df9398d0795290f91794f72e0758bd8b34a04fe7 --- /dev/null +++ b/lib/support/logrotate/gitlab @@ -0,0 +1,22 @@ +# GitLab logrotate settings +# based on: http://stackoverflow.com/a/4883967 + +/home/git/gitlab/log/*.log { + weekly + missingok + rotate 52 + compress + delaycompress + notifempty + copytruncate +} + +/home/git/gitlab-shell/gitlab-shell.log { + weekly + missingok + rotate 52 + compress + delaycompress + notifempty + copytruncate +} diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 3e929c52990de1633104b0448a5cee5e189efb3a..882f0386046f2772e63d92e933be9e0268b08c78 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -1,6 +1,19 @@ # GITLAB # Maintainer: @randx -# App Version: 5.0 + +# CHUNKED TRANSFER +# It is a known issue that Git-over-HTTP requires chunked transfer encoding [0] which is not +# supported by Nginx < 1.3.9 [1]. As a result, pushing a large object with Git (i.e. a single large file) +# can lead to a 411 error. In theory you can get around this by tweaking this configuration file and either +# - installing an old version of Nginx with the chunkin module [2] compiled in, or +# - using a newer version of Nginx. +# +# At the time of writing we do not know if either of these theoretical solutions works. As a workaround +# users can use Git over SSH to push large files. +# +# [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99 +# [1] https://github.com/agentzh/chunkin-nginx-module#status +# [2] https://github.com/agentzh/chunkin-nginx-module upstream gitlab { server unix:/home/git/gitlab/tmp/sockets/gitlab.socket; @@ -11,6 +24,10 @@ server { server_name YOUR_SERVER_FQDN; # e.g., server_name source.example.com; server_tokens off; # don't show the version number, a security best practice root /home/git/gitlab/public; + + # Increase this if you want to upload large attachments + # Or if you want to accept large git objects over http + client_max_body_size 5m; # individual nginx logs for this gitlab vhost access_log /var/log/nginx/gitlab_access.log; @@ -25,15 +42,18 @@ server { # if a file, which is not found in the root folder is requested, # then the proxy pass the request to the upsteam (gitlab unicorn) location @gitlab { - proxy_read_timeout 300; # https://github.com/gitlabhq/gitlabhq/issues/694 - proxy_connect_timeout 300; # https://github.com/gitlabhq/gitlabhq/issues/694 + proxy_read_timeout 300; # Some requests take more than 30 seconds. + proxy_connect_timeout 300; # Some requests take more than 30 seconds. proxy_redirect off; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://gitlab; } + + error_page 502 /502.html; } diff --git a/lib/tasks/gitlab/bulk_add_permission.rake b/lib/tasks/gitlab/bulk_add_permission.rake index c270232edbac1f915c60c23c907b0d282e414776..612a9ba93a8218ec3e708d9216f366ba6e274bed 100644 --- a/lib/tasks/gitlab/bulk_add_permission.rake +++ b/lib/tasks/gitlab/bulk_add_permission.rake @@ -15,7 +15,7 @@ namespace :gitlab do desc "GITLAB | Add a specific user to all projects (as a developer)" task :user_to_projects, [:email] => :environment do |t, args| - user = User.find_by_email args.email + user = User.find_by(email: args.email) project_ids = Project.pluck(:id) puts "Importing #{user.email} users into #{project_ids.size} projects" UsersProject.add_users_into_projects(project_ids, Array.wrap(user.id), UsersProject::DEVELOPER) diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 6e2a59f62acbade887f78f03a55a30278111aebe..c91dedf74c7f2e63997ef59fcb87286ca381b4ed 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -3,6 +3,7 @@ namespace :gitlab do task check: %w{gitlab:env:check gitlab:gitlab_shell:check gitlab:sidekiq:check + gitlab:ldap:check gitlab:app:check} @@ -278,8 +279,6 @@ namespace :gitlab do start_checking "Environment" check_gitlab_git_config - check_python2_exists - check_python2_version finished_checking "Environment" end @@ -289,7 +288,6 @@ namespace :gitlab do ######################## def check_gitlab_git_config - gitlab_user = Gitlab.config.gitlab.user print "Git configured for #{gitlab_user} user? ... " options = { @@ -314,52 +312,6 @@ namespace :gitlab do fix_and_rerun end end - - def check_python2_exists - print "Has python2? ... " - - # Python prints its version to STDERR - # so we can't just use run("python2 --version") - if run_and_match("which python2", /python2$/) - puts "yes".green - else - puts "no".red - try_fixing_it( - "Make sure you have Python 2.5+ installed", - "Link it to python2" - ) - for_more_information( - see_installation_guide_section "Packages / Dependencies" - ) - fix_and_rerun - end - end - - def check_python2_version - print "python2 is supported version? ... " - - # Python prints its version to STDERR - # so we can't just use run("python2 --version") - - unless run_and_match("which python2", /python2$/) - puts "can't check because of previous errors".magenta - return - end - - if `python2 --version 2>&1` =~ /2\.[567]\.\d/ - puts "yes".green - else - puts "no".red - try_fixing_it( - "Make sure you have Python 2.5+ installed", - "Link it to python2" - ) - for_more_information( - see_installation_guide_section "Packages / Dependencies" - ) - fix_and_rerun - end - end end @@ -393,14 +345,20 @@ namespace :gitlab do hook_file = "update" gitlab_shell_hooks_path = Gitlab.config.gitlab_shell.hooks_path gitlab_shell_hook_file = File.join(gitlab_shell_hooks_path, hook_file) - gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user - unless File.exists?(gitlab_shell_hook_file) - puts "can't check because of previous errors".magenta - return + if File.exists?(gitlab_shell_hook_file) + puts "yes".green + else + puts "no".red + puts "Could not find #{gitlab_shell_hook_file}" + try_fixing_it( + 'Check the hooks_path in config/gitlab.yml', + 'Check your gitlab-shell installation' + ) + for_more_information( + see_installation_guide_section "GitLab Shell" + ) end - - puts "yes".green end def check_repo_base_exists @@ -606,10 +564,7 @@ namespace :gitlab do end def gitlab_shell_version - gitlab_shell_version_file = "#{gitlab_shell_user_home}/gitlab-shell/VERSION" - if File.readable?(gitlab_shell_version_file) - File.read(gitlab_shell_version_file) - end + Gitlab::Shell.new.version end def has_gitlab_shell3? @@ -638,12 +593,12 @@ namespace :gitlab do def check_sidekiq_running print "Running? ... " - if sidekiq_process_match + if sidekiq_process_count > 0 puts "yes".green else puts "no".red try_fixing_it( - sudo_gitlab("bundle exec rake sidekiq:start RAILS_ENV=production") + sudo_gitlab("RAILS_ENV=production script/background_jobs start") ) for_more_information( see_installation_guide_section("Install Init Script"), @@ -654,29 +609,69 @@ namespace :gitlab do end def only_one_sidekiq_running - sidekiq_match = sidekiq_process_match - return unless sidekiq_match + process_count = sidekiq_process_count + return if process_count.zero? print 'Number of Sidekiq processes ... ' - if sidekiq_match.length == 1 + if process_count == 1 puts '1'.green else - puts "#{sidekiq_match.length}".red + puts "#{process_count}".red try_fixing_it( 'sudo service gitlab stop', - 'sudo pkill -f sidekiq', - 'sleep 10 && sudo pkill -9 -f sidekiq', + "sudo pkill -u #{gitlab_user} -f sidekiq", + "sleep 10 && sudo pkill -9 -u #{gitlab_user} -f sidekiq", 'sudo service gitlab start' ) fix_and_rerun end end - def sidekiq_process_match - run_and_match("ps ux | grep -i sidekiq", /(sidekiq \d+\.\d+\.\d+.+$)/) + def sidekiq_process_count + `ps ux`.scan(/sidekiq \d+\.\d+\.\d+/).count end end + namespace :ldap do + task :check, [:limit] => :environment do |t, args| + # Only show up to 100 results because LDAP directories can be very big. + # This setting only affects the `rake gitlab:check` script. + args.with_defaults(limit: 100) + warn_user_is_not_gitlab + start_checking "LDAP" + + if ldap_config.enabled + print_users(args.limit) + else + puts 'LDAP is disabled in config/gitlab.yml' + end + + finished_checking "LDAP" + end + + def print_users(limit) + puts "LDAP users with access to your GitLab server (only showing the first #{limit} results)" + ldap.search(attributes: attributes, filter: filter, size: limit, return_result: false) do |entry| + puts "DN: #{entry.dn}\t#{ldap_config.uid}: #{entry[ldap_config.uid]}" + end + end + + def attributes + [ldap_config.uid] + end + + def filter + Net::LDAP::Filter.present?(ldap_config.uid) + end + + def ldap + @ldap ||= OmniAuth::LDAP::Adaptor.new(ldap_config).connection + end + + def ldap_config + @ldap_config ||= Gitlab.config.ldap + end + end # Helper methods ########################## @@ -709,10 +704,13 @@ namespace :gitlab do end def sudo_gitlab(command) - gitlab_user = Gitlab.config.gitlab.user "sudo -u #{gitlab_user} -H #{command}" end + def gitlab_user + Gitlab.config.gitlab.user + end + def start_checking(component) puts "Checking #{component.yellow} ..." puts "" @@ -728,7 +726,7 @@ namespace :gitlab do end def check_gitlab_shell - required_version = Gitlab::VersionInfo.new(1, 7, 1) + required_version = Gitlab::VersionInfo.new(1, 7, 9) current_version = Gitlab::VersionInfo.parse(gitlab_shell_version) print "GitLab Shell version >= #{required_version} ? ... " diff --git a/lib/tasks/gitlab/enable_namespaces.rake b/lib/tasks/gitlab/enable_namespaces.rake index 927748c0fd5f8e0d43bd4abf599e7f3de47c3a83..201f34ab5463a5ae8be5c037d220b1db6c451405 100644 --- a/lib/tasks/gitlab/enable_namespaces.rake +++ b/lib/tasks/gitlab/enable_namespaces.rake @@ -43,13 +43,13 @@ namespace :gitlab do username.gsub!("+", ".") # return username if no matches - return username unless User.find_by_username(username) + return username unless User.find_by(username: username) # look for same username (1..10).each do |i| suffixed_username = "#{username}#{i}" - return suffixed_username unless User.find_by_username(suffixed_username) + return suffixed_username unless User.find_by(username: suffixed_username) end end diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake index 8fa8927085493f919535d79c5a12791f55d40651..cbfa736c84c5b61df85cabe68b51ab12ef860f76 100644 --- a/lib/tasks/gitlab/import.rake +++ b/lib/tasks/gitlab/import.rake @@ -2,12 +2,12 @@ namespace :gitlab do namespace :import do # How to use: # - # 1. copy your bare repos under git repos_path - # 2. run bundle exec rake gitlab:import:repos RAILS_ENV=production + # 1. copy the bare repos under the repos_path (commonly /home/git/repositories) + # 2. run: bundle exec rake gitlab:import:repos RAILS_ENV=production # # Notes: - # * project owner will be a first admin - # * existing projects will be skipped + # * The project owner will set to the first administator of the system + # * Existing projects will be skipped # desc "GITLAB | Import bare repositories from gitlab_shell -> repos_path into GitLab project instance" task repos: :environment do @@ -50,7 +50,7 @@ namespace :gitlab do # find group namespace if group_name - group = Group.find_by_path(group_name) + group = Group.find_by(path: group_name) # create group namespace if !group group = Group.new(:name => group_name) @@ -66,7 +66,7 @@ namespace :gitlab do project_params[:namespace_id] = group.id end - project = Projects::CreateContext.new(user, project_params).execute + project = Projects::CreateService.new(user, project_params).execute if project.valid? puts " * Created #{project.name} (#{repo_path})".green diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake index ac2c4577c7722cc2c21d5674eb830292145ee4a7..c46d5855faf4ac5e21018c71a6fc770c686e041b 100644 --- a/lib/tasks/gitlab/task_helpers.rake +++ b/lib/tasks/gitlab/task_helpers.rake @@ -2,6 +2,16 @@ module Gitlab class TaskAbortedByUserError < StandardError; end end +unless STDOUT.isatty + module Colored + extend self + + def colorize(string, options={}) + string + end + end +end + namespace :gitlab do # Ask if the user wants to continue diff --git a/lib/tasks/sidekiq.rake b/lib/tasks/sidekiq.rake index d0e9dfe46a160279cbbcf3ad650d8c34678ea63b..e91678473a8a5eabd5b5e2fc93090287f3e4a949 100644 --- a/lib/tasks/sidekiq.rake +++ b/lib/tasks/sidekiq.rake @@ -1,20 +1,21 @@ namespace :sidekiq do desc "GITLAB | Stop sidekiq" task :stop do - system "bundle exec sidekiqctl stop #{pidfile}" + system "script/background_jobs stop" end desc "GITLAB | Start sidekiq" task :start do - system "nohup bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1 &" + system "script/background_jobs start" end - desc "GITLAB | Start sidekiq with launchd on Mac OS X" - task :launchd do - system "bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1" + desc 'GitLab | Restart sidekiq' + task :restart do + system "script/background_jobs restart" end - def pidfile - Rails.root.join("tmp", "pids", "sidekiq.pid") + desc "GITLAB | Start sidekiq with launchd on Mac OS X" + task :launchd do + system "script/background_jobs start_no_deamonize" end end diff --git a/public/500.html b/public/500.html index 5b78e3e38cba668fbe5e1739aa543beb98a22189..c84b9e90e4bee2666f5cb3688690d9824d88f3ee 100644 --- a/public/500.html +++ b/public/500.html @@ -8,6 +8,6 @@ <h1>500</h1> <h3>We're sorry, but something went wrong.</h3> <hr/> - <p>We've been notified about this issue and we'll take a look at it shortly.</p> + <p>Please contact your GitLab administrator if this problem persists.</p> </body> </html> diff --git a/public/502.html b/public/502.html new file mode 100644 index 0000000000000000000000000000000000000000..d171eccc92794792253ca82a607c9be9f3b235f9 --- /dev/null +++ b/public/502.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> +<head> + <title>GitLab is not responding (502)</title> + <link href="/static.css" media="screen" rel="stylesheet" type="text/css" /> +</head> +<body> + <h1>502</h1> + <h3>GitLab is not responding.</h3> + <hr/> + <p>Please contact your GitLab administrator if this problem persists.</p> +</body> +</html> diff --git a/public/favicon.ico b/public/favicon.ico index 057f74ac7ab0192514a2dc21bfc955e5700fb65d..bfb74960c480e6cb14f1d38437303af6b375ccaf 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/gitlab_logo.png b/public/gitlab_logo.png index e3cda5978ab5ace1be801295eab4213d8853ef32..dbe6dabb784f3f104908e2eefe79a887545d1075 100644 Binary files a/public/gitlab_logo.png and b/public/gitlab_logo.png differ diff --git a/script/background_jobs b/script/background_jobs new file mode 100755 index 0000000000000000000000000000000000000000..623e26a28312c830637c11677fdb8349b87c96ea --- /dev/null +++ b/script/background_jobs @@ -0,0 +1,56 @@ +#!/bin/bash + +cd $(dirname $0)/.. +app_root=$(pwd) +sidekiq_pidfile="$app_root/tmp/pids/sidekiq.pid" +sidekiq_logfile="$app_root/log/sidekiq.log" +gitlab_user=$(ls -l config.ru | awk '{print $3}') + +function stop +{ + bundle exec sidekiqctl stop $sidekiq_pidfile >> $sidekiq_logfile 2>&1 +} + +function killall +{ + pkill -u $gitlab_user -f sidekiq +} + +function restart +{ + if [ -f $sidekiq_pidfile ]; then + stop + fi + killall + start_sidekiq -d -L $sidekiq_logfile +} + +function start_no_deamonize +{ + start_sidekiq +} + +function start_sidekiq +{ + bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1 +} + +case "$1" in + stop) + stop + ;; + start) + restart + ;; + start_no_deamonize) + start_no_deamonize + ;; + restart) + restart + ;; + killall) + killall + ;; + *) + echo "Usage: RAILS_ENV=your_env $0 {stop|start|start_no_deamonize|restart|killall}" +esac diff --git a/script/upgrade.rb b/script/upgrade.rb new file mode 100644 index 0000000000000000000000000000000000000000..a5caecf8526e8b08dbc2ba4880bbc85d46c2635e --- /dev/null +++ b/script/upgrade.rb @@ -0,0 +1,3 @@ +require_relative "../lib/gitlab/upgrader" + +Gitlab::Upgrader.new.execute diff --git a/script/web b/script/web new file mode 100755 index 0000000000000000000000000000000000000000..5464ed040aa5f1e42edcf67c2906e51629005e66 --- /dev/null +++ b/script/web @@ -0,0 +1,49 @@ +#!/bin/bash + +cd $(dirname $0)/.. +app_root=$(pwd) + +unicorn_pidfile="$app_root/tmp/pids/unicorn.pid" +unicorn_config="$app_root/config/unicorn.rb" + +function get_unicorn_pid +{ + local pid=$(cat $unicorn_pidfile) + if [ -z $pid ] ; then + echo "Could not find a PID in $unicorn_pidfile" + exit 1 + fi + unicorn_pid=$pid +} + +function start +{ + bundle exec unicorn_rails -D -c $unicorn_config -E $RAILS_ENV +} + +function stop +{ + get_unicorn_pid + kill -QUIT $unicorn_pid +} + +function reload +{ + get_unicorn_pid + kill -USR2 $unicorn_pid +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + reload) + reload + ;; + *) + echo "Usage: RAILS_ENV=your_env $0 {start|stop|reload}" + ;; +esac diff --git a/spec/contexts/filter_context_spec.rb b/spec/contexts/filter_context_spec.rb deleted file mode 100644 index db27742b9b5cbe157d56009d90e366cd535ae203..0000000000000000000000000000000000000000 --- a/spec/contexts/filter_context_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -require 'spec_helper' - -describe FilterContext do - - let(:user) { create :user } - let(:user2) { create :user } - let(:project1) { create(:project, creator_id: user.id) } - let(:project2) { create(:project, creator_id: user.id) } - let(:merge_request1) { create(:merge_request, author_id: user.id, source_project: project1, target_project: project2) } - let(:merge_request2) { create(:merge_request, author_id: user.id, source_project: project2, target_project: project1) } - let(:merge_request3) { create(:merge_request, author_id: user.id, source_project: project2, target_project: project2) } - let(:merge_request4) { create(:merge_request, author_id: user2.id, source_project: project2, target_project: project2, target_branch:"notes_refactoring") } - let(:issue1) { create(:issue, assignee_id: user.id, project: project1) } - let(:issue2) { create(:issue, assignee_id: user.id, project: project2) } - let(:issue3) { create(:issue, assignee_id: user2.id, project: project2) } - - describe 'merge requests' do - before :each do - merge_request1 - merge_request2 - merge_request3 - merge_request4 - end - - it 'should by default filter properly' do - merge_requests = user.cared_merge_requests - params ={} - merge_requests = FilterContext.new(merge_requests, params).execute - merge_requests.size.should == 3 - end - - it 'should apply blocks passed in on creation to the filters' do - merge_requests = user.cared_merge_requests - params = {:project_id => project1.id} - merge_requests = FilterContext.new(merge_requests, params).execute - merge_requests.size.should == 1 - end - end - - describe 'issues' do - before :each do - issue1 - issue2 - issue3 - end - it 'should by default filter projects properly' do - issues = user.assigned_issues - params = {} - issues = FilterContext.new(issues, params).execute - issues.size.should == 2 - end - it 'should apply blocks passed in on creation to the filters' do - issues = user.assigned_issues - params = {:project_id => project1.id} - issues = FilterContext.new(issues, params).execute - issues.size.should == 1 - end - end -end diff --git a/spec/contexts/projects_create_context_spec.rb b/spec/contexts/projects_create_context_spec.rb deleted file mode 100644 index 8b2a49dbee575551a16dd33d2a962ffd67a2db40..0000000000000000000000000000000000000000 --- a/spec/contexts/projects_create_context_spec.rb +++ /dev/null @@ -1,75 +0,0 @@ -require 'spec_helper' - -describe Projects::CreateContext do - before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } - after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } - - describe :create_by_user do - before do - @user = create :user - @opts = { - name: "GitLab", - namespace: @user.namespace - } - end - - context 'user namespace' do - before do - @project = create_project(@user, @opts) - end - - it { @project.should be_valid } - it { @project.owner.should == @user } - it { @project.namespace.should == @user.namespace } - end - - context 'group namespace' do - before do - @group = create :group - @group.add_owner(@user) - - @opts.merge!(namespace_id: @group.id) - @project = create_project(@user, @opts) - end - - it { @project.should be_valid } - it { @project.owner.should == @group } - it { @project.namespace.should == @group } - end - - context 'respect configured public setting' do - before(:each) do - @settings = double("settings") - @settings.stub(:issues) { true } - @settings.stub(:merge_requests) { true } - @settings.stub(:wiki) { true } - @settings.stub(:wall) { true } - @settings.stub(:snippets) { true } - stub_const("Settings", Class.new) - Settings.stub_chain(:gitlab, :default_projects_features).and_return(@settings) - end - - context 'should be public when setting is public' do - before do - @settings.stub(:public) { true } - @project = create_project(@user, @opts) - end - - it { @project.public.should be_true } - end - - context 'should be private when setting is not public' do - before do - @settings.stub(:public) { false } - @project = create_project(@user, @opts) - end - - it { @project.public.should be_false } - end - end - end - - def create_project(user, opts) - Projects::CreateContext.new(user, opts).execute - end -end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index d528d12c66c3e88b1b2cda7d445637c638b29403..e1c0269b295fc5cd961f3a183fbdc9e50af93601 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -8,7 +8,7 @@ describe ApplicationController do it 'should redirect if the user is over their password expiry' do user.password_expires_at = Time.new(2002) user.ldap_user?.should be_false - controller.stub!(:current_user).and_return(user) + controller.stub(:current_user).and_return(user) controller.should_receive(:redirect_to) controller.should_receive(:new_profile_password_path) controller.send(:check_password_expiration) @@ -17,15 +17,15 @@ describe ApplicationController do it 'should not redirect if the user is under their password expiry' do user.password_expires_at = Time.now + 20010101 user.ldap_user?.should be_false - controller.stub!(:current_user).and_return(user) + controller.stub(:current_user).and_return(user) controller.should_not_receive(:redirect_to) controller.send(:check_password_expiration) end it 'should not redirect if the user is over their password expiry but they are an ldap user' do user.password_expires_at = Time.new(2002) - user.stub!(:ldap_user?).and_return(true) - controller.stub!(:current_user).and_return(user) + user.stub(:ldap_user?).and_return(true) + controller.stub(:current_user).and_return(user) controller.should_not_receive(:redirect_to) controller.send(:check_password_expiration) end diff --git a/spec/controllers/blob_controller_spec.rb b/spec/controllers/blob_controller_spec.rb index 479d8fc1a1de0faf75cb76766a9443469f92ec4e..cea6922e1c3df66ace929748d42e2acd0a17cef6 100644 --- a/spec/controllers/blob_controller_spec.rb +++ b/spec/controllers/blob_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::BlobController do - let(:project) { create(:project_with_code) } + let(:project) { create(:project) } let(:user) { create(:user) } before do diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb index fdf0884f4e260c3053fbc880d340c28aa1b412e6..f5822157ea4152f4af914e2edf165c8ffbcb0212 100644 --- a/spec/controllers/commit_controller_spec.rb +++ b/spec/controllers/commit_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::CommitController do - let(:project) { create(:project_with_code) } + let(:project) { create(:project) } let(:user) { create(:user) } let(:commit) { project.repository.commit("master") } diff --git a/spec/controllers/commits_controller_spec.rb b/spec/controllers/commits_controller_spec.rb index 8263afc97a2f988e32ecfcafefcffeaeb17fd90d..fbf4f29acfd693000cf4537bc8b19f5b610ec1d3 100644 --- a/spec/controllers/commits_controller_spec.rb +++ b/spec/controllers/commits_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::CommitsController do - let(:project) { create(:project_with_code) } + let(:project) { create(:project) } let(:user) { create(:user) } before do diff --git a/spec/controllers/merge_requests_controller_spec.rb b/spec/controllers/merge_requests_controller_spec.rb index 69708edd8b100e1a0df6b0fd620520a7fcee3950..1502bded97f4490c4a36992b5e5cc7e7c14bf041 100644 --- a/spec/controllers/merge_requests_controller_spec.rb +++ b/spec/controllers/merge_requests_controller_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' describe Projects::MergeRequestsController do - let(:project) { create(:project_with_code) } + let(:project) { create(:project) } let(:user) { create(:user) } - let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project, target_branch: "bcf03b5d~3", source_branch: "bcf03b5d") } + let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project, target_branch: "stable", source_branch: "master") } before do sign_in(user) @@ -61,7 +61,7 @@ describe Projects::MergeRequestsController do it "should really be a git email patch with commit" do get :show, project_id: project.to_param, id: merge_request.iid, format: format - expect(response.body[0..100]).to start_with("From #{merge_request.commits.last.id}") + expect(response.body[0..100]).to start_with("From 6ea87c47f0f8a24ae031c3fff17bc913889ecd00") end it "should contain git diffs" do diff --git a/spec/controllers/tree_controller_spec.rb b/spec/controllers/tree_controller_spec.rb index bb1232e6264b9f46445f854d02883868d938944b..479118a346527a5803d8817551838ba4af951111 100644 --- a/spec/controllers/tree_controller_spec.rb +++ b/spec/controllers/tree_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::TreeController do - let(:project) { create(:project_with_code) } + let(:project) { create(:project) } let(:user) { create(:user) } before do diff --git a/spec/factories.rb b/spec/factories.rb index 56561fe45955461fd285ecb7be250327f6284788..8c12c9b3e19af27358739bfda365828524abfd82 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -15,8 +15,10 @@ FactoryGirl.define do email { Faker::Internet.email } name sequence(:username) { |n| "#{Faker::Internet.user_name}#{n}" } - password "123456" + password "12345678" password_confirmation { password } + confirmed_at { Time.now } + confirmation_token { nil } trait :admin do admin true @@ -25,49 +27,28 @@ FactoryGirl.define do factory :admin, traits: [:admin] end - factory :project do + factory :empty_project, class: 'Project' do sequence(:name) { |n| "project#{n}" } path { name.downcase.gsub(/\s/, '_') } namespace creator - - trait :source do - sequence(:name) { |n| "source project#{n}" } - end - trait :target do - sequence(:name) { |n| "target project#{n}" } - end - - factory :source_project, traits: [:source] - factory :target_project, traits: [:target] - end - - - factory :redmine_project, parent: :project do - issues_tracker { "redmine" } - issues_tracker_id { "project_name_in_redmine" } end - factory :project_with_code, parent: :project do + factory :project, parent: :empty_project do path { 'gitlabhq' } - trait :source_path do - path { 'source_gitlabhq' } - end - - trait :target_path do - path { 'target_gitlabhq' } - end - - factory :source_project_with_code, traits: [:source, :source_path] - factory :target_project_with_code, traits: [:target, :target_path] - after :create do |project| TestEnv.clear_repo_dir(project.namespace, project.path) + TestEnv.reset_satellite_dir TestEnv.create_repo(project.namespace, project.path) end end + factory :redmine_project, parent: :project do + issues_tracker { "redmine" } + issues_tracker_id { "project_name_in_redmine" } + end + factory :group do sequence(:name) { |n| "group#{n}" } path { name.downcase.gsub(/\s/, '_') } @@ -106,25 +87,45 @@ FactoryGirl.define do factory :merge_request do title author - source_project factory: :source_project_with_code - target_project factory: :target_project_with_code + source_project factory: :project + target_project { source_project } + + # → git log stable..master --pretty=oneline + # b1e6a9dbf1c85e6616497a5e7bad9143a4bd0828 tree css fixes + # 8716fc78f3c65bbf7bcf7b574febd583bc5d2812 Added loading animation for notes + # cd5c4bac5042c5469dcdf7e7b2f768d3c6fd7088 notes count for wall + # 8470d70da67355c9c009e4401746b1d5410af2e3 notes controller refactored + # 1e689bfba39525ead225eaf611948cfbe8ac34cf fixed notes logic + # f0f14c8eaba69ebddd766498a9d0b0e79becd633 finished scss refactoring + # 3a4b4fb4cde7809f033822a171b9feae19d41fff Moving ui styles to one scss file, Added ui class to body + # 065c200c33f68c2bb781e35a43f9dc8138a893b5 removed unnecessary hr tags & titles + # 1e8b111be85df0db6c8000ef9a710bc0221eae83 Merge branch 'master' of github.com:gitlabhq/gitlabhq + # f403da73f5e62794a0447aca879360494b08f678 Fixed ajax loading image. Fixed wrong wording + # e6ea73c77600d413d370249b8e392734f7d1dbee Merge pull request #468 from bencevans/patch-1 + # 4a3c05b69355deee25767a74d0512ec4b510d4ef Merge pull request #470 from bgondy/patch-1 + # 0347fe2412eb51d3efeccc35210e9268bc765ac5 Update app/views/projects/team.html.haml + # 2b5c61bdece1f7eb2b901ceea7d364065cdf76ac Title for a link fixed + # 460eeb13b7560b40104044973ff933b1a6dbbcaa Increased count of notes loaded when visit wall page + # 21c141afb1c53a9180a99d2cca29ffa613eb7e3a Merge branch 'notes_refactoring' + # 292a41cbe295f16f7148913b31eb0fb91f3251c3 Fixed comments for snippets. Tests fixed + # d41d8ffb02fa74fd4571603548bd7e401ec99e0c Reply button, Comments for Merge Request diff + # b1a36b552be2a7a6bc57fbed6c52dc6ed82111f8 Merge pull request #466 from skroutz/no-rbenv + # db75dae913e8365453ca231f101b067314a7ea71 Merge pull request #465 from skroutz/branches_commit_link + # 75f040fbfe4b5af23ff004ad3207c3976df097a8 Don't enforce rbenv version + # e42fb4fda475370dcb0d8f8f1268bfdc7a0cc437 Fix broken commit link in branches page + # 215a01f63ccdc085f75a48f6f7ab6f2b15b5852c move notes login to one controller + # 81092c01984a481e312de10a28e3f1a6dda182a3 Status codes for errors, New error pages + # 7d279f9302151e3c8f4c5df9c5200a72799409b9 better error handling for not found resource, gitolite error + # 9e6d0710e927aa8ea834b8a9ae9f277be617ac7d Merge pull request #443 from CedricGatay/fix/incorrectLineNumberingInDiff + # 6ea87c47f0f8a24ae031c3fff17bc913889ecd00 Incorrect line numbering in diff + # + # → git log master..stable --pretty=oneline + # empty + source_branch "master" target_branch "stable" - # pick 3 commits "at random" (from bcf03b5d~3 to bcf03b5d) trait :with_diffs do - target_branch "master" # pretend bcf03b5d~3 - source_branch "stable" # pretend bcf03b5d - st_commits do - [ - source_project.repository.commit('bcf03b5d').to_hash, - source_project.repository.commit('bcf03b5d~1').to_hash, - source_project.repository.commit('bcf03b5d~2').to_hash - ] - end - st_diffs do - source_project.repo.diff("bcf03b5d~3", "bcf03b5d") - end end trait :closed do @@ -153,7 +154,7 @@ FactoryGirl.define do factory :note_on_merge_request_with_attachment, traits: [:on_merge_request, :with_attachment] trait :on_commit do - project factory: :project_with_code + project factory: :project commit_id "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" noteable_type "Commit" end @@ -163,7 +164,7 @@ FactoryGirl.define do end trait :on_merge_request do - project factory: :project_with_code + project factory: :project noteable_id 1 noteable_type "MergeRequest" end diff --git a/spec/factories/broadcast_messages.rb b/spec/factories/broadcast_messages.rb new file mode 100644 index 0000000000000000000000000000000000000000..6339d5c4003d0319ef5e87ddd1def9d3dfcb65b4 --- /dev/null +++ b/spec/factories/broadcast_messages.rb @@ -0,0 +1,27 @@ +# == Schema Information +# +# Table name: broadcast_messages +# +# id :integer not null, primary key +# message :text default(""), not null +# starts_at :datetime +# ends_at :datetime +# alert_type :integer +# created_at :datetime not null +# updated_at :datetime not null +# color :string(255) +# font :string(255) +# + +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :broadcast_message do + message "MyText" + starts_at "2013-11-12 13:43:25" + ends_at "2013-11-12 13:43:25" + alert_type 1 + color "#555555" + font "#BBBBBB" + end +end diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb index 2ea569a6208cbd0cc3a64eccfbaaec1cc29bd5d0..a507f0314c6bb2492efe395179007de0e08f9180 100644 --- a/spec/features/gitlab_flavored_markdown_spec.rb +++ b/spec/features/gitlab_flavored_markdown_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe "GitLab Flavored Markdown" do - let(:project) { create(:project_with_code) } + let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:fred) do diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 0c09279e3dc6eaa750b7968be6e481cbf0926b15..1d225e8bad0bb5c7b3f8056de58c1151957570fd 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -24,7 +24,7 @@ describe "Issues" do end it "should open new issue popup" do - page.should have_content("Issue ##{issue.id}") + page.should have_content("Issue ##{issue.iid}") end describe "fill in" do @@ -95,4 +95,167 @@ describe "Issues" do page.should have_content 'gitlab' end end + + describe 'filter issue' do + titles = ['foo','bar','baz'] + titles.each_with_index do |title, index| + let!(title.to_sym) { create(:issue, title: title, project: project, created_at: Time.now - (index * 60)) } + end + let(:newer_due_milestone) { create(:milestone, due_date: '2013-12-11') } + let(:later_due_milestone) { create(:milestone, due_date: '2013-12-12') } + + it 'sorts by newest' do + visit project_issues_path(project, sort: 'newest') + + first_issue.should include("foo") + last_issue.should include("baz") + end + + it 'sorts by oldest' do + visit project_issues_path(project, sort: 'oldest') + + first_issue.should include("baz") + last_issue.should include("foo") + end + + it 'sorts by most recently updated' do + baz.updated_at = Time.now + 100 + baz.save + visit project_issues_path(project, sort: 'recently_updated') + + first_issue.should include("baz") + end + + it 'sorts by least recently updated' do + baz.updated_at = Time.now - 100 + baz.save + visit project_issues_path(project, sort: 'last_updated') + + first_issue.should include("baz") + end + + describe 'sorting by milestone' do + before :each do + foo.milestone = newer_due_milestone + foo.save + bar.milestone = later_due_milestone + bar.save + end + + it 'sorts by recently due milestone' do + visit project_issues_path(project, sort: 'milestone_due_soon') + + first_issue.should include("foo") + end + + it 'sorts by least recently due milestone' do + visit project_issues_path(project, sort: 'milestone_due_later') + + first_issue.should include("bar") + end + end + + describe 'combine filter and sort' do + let(:user2) { create(:user) } + + before :each do + foo.assignee = user2 + foo.save + bar.assignee = user2 + bar.save + end + + it 'sorts with a filter applied' do + visit project_issues_path(project, sort: 'oldest', assignee_id: user2.id) + + first_issue.should include("bar") + last_issue.should include("foo") + page.should_not have_content 'baz' + end + end + end + + describe 'update assignee from issue#show' do + let(:issue) { create(:issue, project: project, author: @user) } + + context 'by autorized user' do + + it 'with dropdown menu' do + visit project_issue_path(project, issue) + + find('.edit-issue.inline-update').select(project.team.members.first.name, from: 'issue_assignee_id') + click_button 'Update Issue' + + page.should have_content "currently assigned to" + page.has_select?('issue_assignee_id', :selected => project.team.members.first.name) + end + end + + context 'by unauthorized user' do + + let(:guest) { create(:user) } + + before :each do + project.team << [[guest], :guest] + issue.assignee = @user + issue.save + end + + it 'shows assignee text' do + logout + login_with guest + + visit project_issue_path(project, issue) + page.should have_content "currently assigned to #{issue.assignee.name}" + + end + end + + end + + describe 'update milestone from issue#show' do + let!(:issue) { create(:issue, project: project, author: @user) } + let!(:milestone) { create(:milestone, project: project) } + + context 'by authorized user' do + + it 'with dropdown menu' do + visit project_issue_path(project, issue) + + find('.edit-issue.inline-update').select(milestone.title, from: 'issue_milestone_id') + click_button 'Update Issue' + + page.should have_content "Attached to milestone" + page.has_select?('issue_assignee_id', :selected => milestone.title) + end + end + + context 'by unauthorized user' do + + let(:guest) { create(:user) } + + before :each do + project.team << [[guest], :guest] + issue.milestone = milestone + issue.save + end + + it 'shows milestone text' do + logout + login_with guest + + visit project_issue_path(project, issue) + + page.should have_content "Attached to milestone #{milestone.title}" + end + end + end + + def first_issue + all("ul.issues-list li").first.text + end + + def last_issue + all("ul.issues-list li").last.text + end end diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index ba580d9484dcc079a004bd265e112cb52af95425..da723ae39bddcfe7e0c38f5aad04b0ca364cdab4 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe "On a merge request", js: true do - let!(:project) { create(:project_with_code) } + let!(:project) { create(:project) } let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let!(:note) { create(:note_on_merge_request_with_attachment, project: project) } @@ -108,7 +108,7 @@ describe "On a merge request", js: true do within("#note_#{note.id}") do should have_css(".note-last-update small") - find(".note-last-update small").text.should match(/Edited just now/) + find(".note-last-update small").text.should match(/Edited less than a minute ago/) end end end @@ -135,7 +135,7 @@ describe "On a merge request", js: true do end describe "On a merge request diff", js: true, focus: true do - let!(:project) { create(:source_project_with_code) } + let!(:project) { create(:project) } let!(:merge_request) { create(:merge_request_with_diffs, source_project: project, target_project: project) } before do @@ -149,7 +149,7 @@ describe "On a merge request diff", js: true, focus: true do describe "when adding a note" do before do - find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185"]').click + find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_172_185"]').click end describe "the notes holder" do @@ -159,22 +159,14 @@ describe "On a merge request diff", js: true, focus: true do end describe "the note form" do - it 'should be valid' do - within(".js-temp-notes-holder") { find("#note_noteable_type").value.should == "MergeRequest" } - within(".js-temp-notes-holder") { find("#note_noteable_id").value.should == merge_request.id.to_s } - within(".js-temp-notes-holder") { find("#note_commit_id").value.should == "" } - within(".js-temp-notes-holder") { find("#note_line_code").value.should == "4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185" } - should have_css(".js-close-discussion-note-form", text: "Cancel") - end - it "shouldn't add a second form for same row" do - find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185"]').click + find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_172_185"]').click - should have_css("tr[id='4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185'] + .js-temp-notes-holder form", count: 1) + should have_css("tr[id='4735dfc552ad7bf15ca468adc3cad9d05b624490_172_185'] + .js-temp-notes-holder form", count: 1) end it "should be removed when canceled" do - within(".file form[rel$='4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185']") do + within(".file form[rel$='4735dfc552ad7bf15ca468adc3cad9d05b624490_172_185']") do find(".js-close-discussion-note-form").trigger("click") end @@ -184,11 +176,11 @@ describe "On a merge request diff", js: true, focus: true do end describe "with muliple note forms" do - let!(:project) { create(:source_project_with_code) } + let!(:project) { create(:project) } let!(:merge_request) { create(:merge_request_with_diffs, source_project: project, target_project: project) } before do - find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185"]').click + find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_172_185"]').click find('a[data-line-code="342e16cbbd482ac2047dc679b2749d248cc1428f_18_17"]').click end @@ -197,7 +189,7 @@ describe "On a merge request diff", js: true, focus: true do describe "previewing them separately" do before do # add two separate texts and trigger previews on both - within("tr[id='4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185'] + .js-temp-notes-holder") do + within("tr[id='4735dfc552ad7bf15ca468adc3cad9d05b624490_172_185'] + .js-temp-notes-holder") do fill_in "note[note]", with: "One comment on line 185" find(".js-note-preview-button").trigger("click") end @@ -216,12 +208,6 @@ describe "On a merge request diff", js: true, focus: true do end end - it do - within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .js-temp-notes-holder") do - should have_no_css(".js-temp-notes-holder") - end - end - it 'should be added as discussion' do should have_content("Another comment on line 17") should have_css(".notes_holder") diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb index 80c9f5d7f14ac8e7ade7626ad0f8d23449acdd17..b67ce3c67f187b23e4996d1932796bb279e6eec8 100644 --- a/spec/features/profile_spec.rb +++ b/spec/features/profile_spec.rb @@ -12,7 +12,7 @@ describe "Profile account page" do describe "when signup is enabled" do before do Gitlab.config.gitlab.stub(:signup_enabled).and_return(true) - visit account_profile_path + visit profile_account_path end it { page.should have_content("Remove account") } @@ -26,12 +26,12 @@ describe "Profile account page" do describe "when signup is disabled" do before do Gitlab.config.gitlab.stub(:signup_enabled).and_return(false) - visit account_profile_path + visit profile_account_path end it "should not have option to remove account" do page.should_not have_content("Remove account") - current_path.should == account_profile_path + current_path.should == profile_account_path end end end diff --git a/spec/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb index 7754b28347aa29635a8fecc1b55c6542375765a6..078c257538fe197666ef7210a0c1808fb7f5e769 100644 --- a/spec/features/security/profile_access_spec.rb +++ b/spec/features/security/profile_access_spec.rb @@ -29,7 +29,7 @@ describe "Users Security" do end describe "GET /profile/account" do - subject { account_profile_path } + subject { profile_account_path } it { should be_allowed_for @u1 } it { should be_allowed_for :admin } diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..8bb1e259efa07a29ec680b0ef6be3ef0c9b4e301 --- /dev/null +++ b/spec/features/security/project/internal_access_spec.rb @@ -0,0 +1,251 @@ +require 'spec_helper' + +describe "Internal Project Access" do + let(:project) { create(:project) } + + let(:master) { create(:user) } + let(:guest) { create(:user) } + let(:reporter) { create(:user) } + + before do + # internal project + project.visibility_level = Gitlab::VisibilityLevel::INTERNAL + project.save! + + # full access + project.team << [master, :master] + + # readonly + project.team << [reporter, :reporter] + + end + + describe "Project should be internal" do + subject { project } + + its(:internal?) { should be_true } + end + + describe "GET /:project_path" do + subject { project_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/tree/master" do + subject { project_tree_path(project, project.repository.root_ref) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/commits/master" do + subject { project_commits_path(project, project.repository.root_ref, limit: 1) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/commit/:sha" do + subject { project_commit_path(project, project.repository.commit) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/compare" do + subject { project_compare_index_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/team" do + subject { project_team_index_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/wall" do + subject { project_wall_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/blob" do + before do + commit = project.repository.commit + path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name + @blob_path = project_blob_path(project, File.join(commit.id, path)) + end + + it { @blob_path.should be_allowed_for master } + it { @blob_path.should be_allowed_for reporter } + it { @blob_path.should be_allowed_for :admin } + it { @blob_path.should be_allowed_for guest } + it { @blob_path.should be_allowed_for :user } + it { @blob_path.should be_denied_for :visitor } + end + + describe "GET /:project_path/edit" do + subject { edit_project_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/deploy_keys" do + subject { project_deploy_keys_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/issues" do + subject { project_issues_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/snippets" do + subject { project_snippets_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/snippets/new" do + subject { new_project_snippet_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/merge_requests" do + subject { project_merge_requests_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/merge_requests/new" do + subject { new_project_merge_request_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/branches/recent" do + subject { recent_project_branches_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/branches" do + subject { project_branches_path(project) } + + before do + # Speed increase + Project.any_instance.stub(:branches).and_return([]) + end + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/tags" do + subject { project_tags_path(project) } + + before do + # Speed increase + Project.any_instance.stub(:tags).and_return([]) + end + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/hooks" do + subject { project_hooks_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end +end diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index 7f3f8c50f02b2f30d3e67c45a2f00f4eec73381e..0402ff3973528b433f6384b7b0e995872eec07e3 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe "Private Project Access" do - let(:project) { create(:project_with_code) } + let(:project) { create(:project) } let(:master) { create(:user) } let(:guest) { create(:user) } @@ -15,6 +15,12 @@ describe "Private Project Access" do project.team << [reporter, :reporter] end + describe "Project should be private" do + subject { project } + + its(:private?) { should be_true } + end + describe "GET /:project_path" do subject { project_path(project) } diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index 267643fd8ef2d485002e6f0da81b141b7716bf02..7e6a39fad69faa0c640ce3993f5330b0985ac7e9 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe "Public Project Access" do - let(:project) { create(:project_with_code) } + let(:project) { create(:project) } let(:master) { create(:user) } let(:guest) { create(:user) } @@ -9,7 +9,7 @@ describe "Public Project Access" do before do # public project - project.public = true + project.visibility_level = Gitlab::VisibilityLevel::PUBLIC project.save! # full access diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 229f49659cf1d9fe5f4b272c72fadc6e9890c3a0..c58c83a2970e9eda6eaa3a52515c77f3434847d9 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe ApplicationHelper do describe 'current_controller?' do before do - controller.stub!(:controller_name).and_return('foo') + controller.stub(:controller_name).and_return('foo') end it "returns true when controller matches argument" do @@ -22,7 +22,7 @@ describe ApplicationHelper do describe 'current_action?' do before do - stub!(:action_name).and_return('foo') + allow(self).to receive(:action_name).and_return('foo') end it "returns true when action matches argument" do @@ -39,46 +39,81 @@ describe ApplicationHelper do end end + describe "group_icon" do + avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png') + + it "should return an url for the avatar" do + group = create(:group) + group.avatar = File.open(avatar_file_path) + group.save! + group_icon(group.path).to_s.should == "/uploads/group/avatar/#{ group.id }/gitlab_logo.png" + end + + it "should give default avatar_icon when no avatar is present" do + group = create(:group) + group.save! + group_icon(group.path).to_s.should == "/assets/no_group_avatar.png" + end + end + + describe "avatar_icon" do + avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png') + + it "should return an url for the avatar" do + user = create(:user) + user.avatar = File.open(avatar_file_path) + user.save! + avatar_icon(user.email).to_s.should == "/uploads/user/avatar/#{ user.id }/gitlab_logo.png" + end + + it "should call gravatar_icon when no avatar is present" do + user = create(:user) + user.save! + allow(self).to receive(:gravatar_icon).and_return('gravatar_method_called') + avatar_icon(user.email).to_s.should == "gravatar_method_called" + end + end + describe "gravatar_icon" do let(:user_email) { 'user@email.com' } it "should return a generic avatar path when Gravatar is disabled" do Gitlab.config.gravatar.stub(:enabled).and_return(false) - gravatar_icon(user_email).should == 'no_avatar.png' + gravatar_icon(user_email).should == '/assets/no_avatar.png' end it "should return a generic avatar path when email is blank" do - gravatar_icon('').should == 'no_avatar.png' + gravatar_icon('').should == '/assets/no_avatar.png' end it "should return default gravatar url" do - stub!(:request).and_return(double(:ssl? => false)) + allow(self).to receive(:request).and_return(double(:ssl? => false)) gravatar_icon(user_email).should match('http://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118') end it "should use SSL when appropriate" do - stub!(:request).and_return(double(:ssl? => true)) + allow(self).to receive(:request).and_return(double(:ssl? => true)) gravatar_icon(user_email).should match('https://secure.gravatar.com') end it "should return custom gravatar path when gravatar_url is set" do - stub!(:request).and_return(double(:ssl? => false)) + allow(self).to receive(:request).and_return(double(:ssl? => false)) Gitlab.config.gravatar.stub(:plain_url).and_return('http://example.local/?s=%{size}&hash=%{hash}') gravatar_icon(user_email, 20).should == 'http://example.local/?s=20&hash=b58c6f14d292556214bd64909bcdb118' end it "should accept a custom size" do - stub!(:request).and_return(double(:ssl? => false)) + allow(self).to receive(:request).and_return(double(:ssl? => false)) gravatar_icon(user_email, 64).should match(/\?s=64/) end it "should use default size when size is wrong" do - stub!(:request).and_return(double(:ssl? => false)) + allow(self).to receive(:request).and_return(double(:ssl? => false)) gravatar_icon(user_email, nil).should match(/\?s=40/) end it "should be case insensitive" do - stub!(:request).and_return(double(:ssl? => false)) + allow(self).to receive(:request).and_return(double(:ssl? => false)) gravatar_icon(user_email).should == gravatar_icon(user_email.upcase + " ") end @@ -87,7 +122,7 @@ describe ApplicationHelper do describe "user_color_scheme_class" do context "with current_user is nil" do it "should return a string" do - stub!(:current_user).and_return(nil) + allow(self).to receive(:current_user).and_return(nil) user_color_scheme_class.should be_kind_of(String) end end @@ -97,7 +132,7 @@ describe ApplicationHelper do context "with color_scheme_id == #{color_scheme_id}" do it "should return a string" do current_user = double(:color_scheme_id => color_scheme_id) - stub!(:current_user).and_return(current_user) + allow(self).to receive(:current_user).and_return(current_user) user_color_scheme_class.should be_kind_of(String) end end @@ -105,4 +140,21 @@ describe ApplicationHelper do end end + describe "simple_sanitize" do + let(:a_tag) { '<a href="#">Foo</a>' } + + it "allows the a tag" do + simple_sanitize(a_tag).should == a_tag + end + + it "allows the span tag" do + input = '<span class="foo">Bar</span>' + simple_sanitize(input).should == input + end + + it "disallows other tags" do + input = "<strike><b>#{a_tag}</b></strike>" + simple_sanitize(input).should == a_tag + end + end end diff --git a/spec/helpers/broadcast_messages_helper_spec.rb b/spec/helpers/broadcast_messages_helper_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..1338ce4873d61d0ff77d29402a688ce6c3c2b05e --- /dev/null +++ b/spec/helpers/broadcast_messages_helper_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe BroadcastMessagesHelper do + describe 'broadcast_styling' do + let(:broadcast_message) { double(color: "", font: "") } + + context "default style" do + it "should have no style" do + broadcast_styling(broadcast_message).should match('') + end + end + + context "customiezd style" do + before { broadcast_message.stub(color: "#f2dede", font: "#b94a48") } + + it "should have a customized style" do + broadcast_styling(broadcast_message).should match('background-color:#f2dede;color:#b94a48') + end + end + end +end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index d49247accc2f48c869d38ab4441a60cf29408a57..59abfb38ec0df9a0bec1d4d50ad095146b1e28ac 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -4,7 +4,7 @@ describe GitlabMarkdownHelper do include ApplicationHelper include IssuesHelper - let!(:project) { create(:project_with_code) } + let!(:project) { create(:project) } let(:user) { create(:user, username: 'gfm') } let(:commit) { project.repository.commit } @@ -16,6 +16,7 @@ describe GitlabMarkdownHelper do before do # Helper expects a @project instance variable @project = project + @repository = project.repository end describe "#gfm" do @@ -378,9 +379,10 @@ describe GitlabMarkdownHelper do it "should leave code blocks untouched" do helper.stub(:user_color_scheme_class).and_return(:white) - helper.markdown("\n some code from $#{snippet.id}\n here too\n").should include("<div class=\"white\"><div class=\"highlight\"><pre><span class=\"n\">some</span> <span class=\"n\">code</span> <span class=\"n\">from</span> $#{snippet.id}\n<span class=\"n\">here</span> <span class=\"n\">too</span>\n</pre></div></div>") + target_html = "\n<div class=\"highlighted-data white\">\n <div class=\"highlight\">\n <pre><code class=\"\">some code from $#{snippet.id}\nhere too\n</code></pre>\n </div>\n</div>\n\n" - helper.markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n").should include("<div class=\"white\"><div class=\"highlight\"><pre><span class=\"n\">some</span> <span class=\"n\">code</span> <span class=\"n\">from</span> $#{snippet.id}\n<span class=\"n\">here</span> <span class=\"n\">too</span>\n</pre></div></div>") + helper.markdown("\n some code from $#{snippet.id}\n here too\n").should == target_html + helper.markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n").should == target_html end it "should leave inline code untouched" do @@ -392,7 +394,7 @@ describe GitlabMarkdownHelper do end it "should leave ref-like href of 'manual' links untouched" do - markdown("why not [inspect !#{merge_request.iid}](http://example.tld/#!#{merge_request.iid})").should == "<p>why not <a href=\"http://example.tld/#!#{merge_request.iid}\">inspect </a><a href=\"#{project_merge_request_url(project, merge_request)}\" class=\"gfm gfm-merge_request \" title=\"Merge Request: #{merge_request.title}\">!#{merge_request.iid}</a><a href=\"http://example.tld/#!#{merge_request.iid}\"></a></p>\n" + markdown("why not [inspect !#{merge_request.iid}](http://example.tld/#!#{merge_request.iid})").should == "<p>why not <a href=\"http://example.tld/#!#{merge_request.iid}\">inspect </a><a class=\"gfm gfm-merge_request \" href=\"#{project_merge_request_url(project, merge_request)}\" title=\"Merge Request: #{merge_request.title}\">!#{merge_request.iid}</a><a href=\"http://example.tld/#!#{merge_request.iid}\"></a></p>\n" end it "should leave ref-like src of images untouched" do @@ -406,11 +408,53 @@ describe GitlabMarkdownHelper do it "should generate absolute urls for emoji" do markdown(":smile:").should include("src=\"#{url_to_image("emoji/smile")}") end + + it "should handle relative urls for a file in master" do + actual = "[GitLab API doc](doc/api/README.md)\n" + expected = "<p><a href=\"/#{project.path_with_namespace}/blob/master/doc/api/README.md\">GitLab API doc</a></p>\n" + markdown(actual).should match(expected) + end + + it "should handle relative urls for a directory in master" do + actual = "[GitLab API doc](doc/api)\n" + expected = "<p><a href=\"/#{project.path_with_namespace}/tree/master/doc/api\">GitLab API doc</a></p>\n" + markdown(actual).should match(expected) + end + + it "should handle absolute urls" do + actual = "[GitLab](https://www.gitlab.com)\n" + expected = "<p><a href=\"https://www.gitlab.com\">GitLab</a></p>\n" + markdown(actual).should match(expected) + end + + it "should handle wiki urls" do + actual = "[Link](test/link)\n" + expected = "<p><a href=\"/#{project.path_with_namespace}/wikis/test/link\">Link</a></p>\n" + markdown(actual).should match(expected) + end + + it "should handle relative urls in reference links for a file in master" do + actual = "[GitLab API doc][GitLab readme]\n [GitLab readme]: doc/api/README.md\n" + expected = "<p><a href=\"/#{project.path_with_namespace}/blob/master/doc/api/README.md\">GitLab API doc</a></p>\n" + markdown(actual).should match(expected) + end + + it "should handle relative urls in reference links for a directory in master" do + actual = "[GitLab API doc directory][GitLab readmes]\n [GitLab readmes]: doc/api/\n" + expected = "<p><a href=\"/#{project.path_with_namespace}/tree/master/doc/api/\">GitLab API doc directory</a></p>\n" + markdown(actual).should match(expected) + end + + it "should not handle malformed relative urls in reference links for a file in master" do + actual = "[GitLab readme]: doc/api/README.md\n" + expected = "" + markdown(actual).should match(expected) + end end describe "#render_wiki_content" do before do - @wiki = stub('WikiPage') + @wiki = double('WikiPage') @wiki.stub(:content).and_return('wiki content') end @@ -424,7 +468,7 @@ describe GitlabMarkdownHelper do it "should use the Gollum renderer for all other file types" do @wiki.stub(:format).and_return(:rdoc) - formatted_content_stub = stub('formatted_content') + formatted_content_stub = double('formatted_content') formatted_content_stub.should_receive(:html_safe) @wiki.stub(:formatted_content).and_return(formatted_content_stub) diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 3595af324312b93366ffdb4e1befd63b8ce96e73..9c95bc044f3dbf7f21c0815b0e6fecd08758b05b 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -47,6 +47,17 @@ describe IssuesHelper do url_for_project_issues.should eq "" end + + describe "when external tracker was enabled and then config removed" do + before do + @project = ext_project + Gitlab.config.stub(:issues_tracker).and_return(nil) + end + + it "should return path to internal tracker" do + url_for_project_issues.should match(polymorphic_path([@project])) + end + end end describe :url_for_issue do @@ -75,6 +86,17 @@ describe IssuesHelper do url_for_issue(issue.iid).should eq "" end + + describe "when external tracker was enabled and then config removed" do + before do + @project = ext_project + Gitlab.config.stub(:issues_tracker).and_return(nil) + end + + it "should return internal path" do + url_for_issue(issue.iid).should match(polymorphic_path([@project, issue])) + end + end end describe :url_for_new_issue do @@ -101,6 +123,17 @@ describe IssuesHelper do url_for_new_issue.should eq "" end + + describe "when external tracker was enabled and then config removed" do + before do + @project = ext_project + Gitlab.config.stub(:issues_tracker).and_return(nil) + end + + it "should return internal path" do + url_for_new_issue.should match(new_project_issue_path(@project)) + end + end end end diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb index f97959ee8f4e63170d834a783746700184612050..c1efc1fb2a09f10edd9ee0434d804caf4eeda19d 100644 --- a/spec/helpers/notifications_helper_spec.rb +++ b/spec/helpers/notifications_helper_spec.rb @@ -1,15 +1,35 @@ require 'spec_helper' -# Specs in this file have access to a helper object that includes -# the NotificationsHelper. For example: -# -# describe NotificationsHelper do -# describe "string concat" do -# it "concats two strings with spaces" do -# helper.concat_strings("this","that").should == "this that" -# end -# end -# end describe NotificationsHelper do - pending "add some examples to (or delete) #{__FILE__}" + describe 'notification_icon' do + let(:notification) { double(disabled?: false, participating?: false, watch?: false) } + + context "disabled notification" do + before { notification.stub(disabled?: true) } + + it "has a red icon" do + notification_icon(notification).should match('class="icon-circle cred"') + end + end + + context "participating notification" do + before { notification.stub(participating?: true) } + + it "has a blue icon" do + notification_icon(notification).should match('class="icon-circle cblue"') + end + end + + context "watched notification" do + before { notification.stub(watch?: true) } + + it "has a green icon" do + notification_icon(notification).should match('class="icon-circle cgreen"') + end + end + + it "has a blue icon" do + notification_icon(notification).should match('class="icon-circle-blank cblue"') + end + end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..114058e3095ef6bfc29dc476a5527dd5a1b872ca --- /dev/null +++ b/spec/helpers/projects_helper_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe ProjectsHelper do + describe '#project_issues_trackers' do + it "returns the correct issues trackers available" do + project_issues_trackers.should == + "<option value=\"redmine\">Redmine</option>\n" \ + "<option value=\"gitlab\">GitLab</option>" + end + + it "returns the correct issues trackers available with current tracker 'gitlab' selected" do + project_issues_trackers('gitlab').should == + "<option value=\"redmine\">Redmine</option>\n" \ + "<option selected=\"selected\" value=\"gitlab\">GitLab</option>" + end + + it "returns the correct issues trackers available with current tracker 'redmine' selected" do + project_issues_trackers('redmine').should == + "<option selected=\"selected\" value=\"redmine\">Redmine</option>\n" \ + "<option value=\"gitlab\">GitLab</option>" + end + end +end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..733f2754727329d401f795036c0bd3f2559b3840 --- /dev/null +++ b/spec/helpers/search_helper_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe SearchHelper do + # Override simple_sanitize for our testing purposes + def simple_sanitize(str) + str + end + + describe 'search_autocomplete_source' do + context "with no current user" do + before do + allow(self).to receive(:current_user).and_return(nil) + end + + it "it returns nil" do + search_autocomplete_opts("q").should be_nil + end + end + + context "with a user" do + let(:user) { create(:user) } + + before do + allow(self).to receive(:current_user).and_return(user) + end + + it "includes Help sections" do + search_autocomplete_opts("hel").size.should == 9 + end + + it "includes default sections" do + search_autocomplete_opts("adm").size.should == 1 + end + + it "includes the user's groups" do + create(:group).add_owner(user) + search_autocomplete_opts("gro").size.should == 1 + end + + it "includes the user's projects" do + project = create(:project, namespace: create(:namespace, owner: user)) + search_autocomplete_opts(project.name).size.should == 1 + end + + context "with a current project" do + before { @project = create(:project) } + + it "includes project-specific sections" do + search_autocomplete_opts("Files").size.should == 1 + search_autocomplete_opts("Commits").size.should == 1 + end + end + end + end +end diff --git a/spec/helpers/tab_helper_spec.rb b/spec/helpers/tab_helper_spec.rb index ef8e4cf6375c27fd3d7feaf53ce1eca36d71a4f1..fa8a3f554f75c892009d621a924eab1343415812 100644 --- a/spec/helpers/tab_helper_spec.rb +++ b/spec/helpers/tab_helper_spec.rb @@ -5,8 +5,8 @@ describe TabHelper do describe 'nav_link' do before do - controller.stub!(:controller_name).and_return('foo') - stub!(:action_name).and_return('foo') + controller.stub(:controller_name).and_return('foo') + allow(self).to receive(:action_name).and_return('foo') end it "captures block output" do diff --git a/spec/javascripts/stat_graph_contributors_graph_spec.js b/spec/javascripts/stat_graph_contributors_graph_spec.js index 8d2e2038a55a62c17b0811a3830c61ecf3ff74ce..1090cb7f620724bc757387a0a58cff5283526e3d 100644 --- a/spec/javascripts/stat_graph_contributors_graph_spec.js +++ b/spec/javascripts/stat_graph_contributors_graph_spec.js @@ -88,19 +88,20 @@ describe("ContributorsGraph", function () { describe("ContributorsMasterGraph", function () { - describe("#process_dates", function () { - it("gets and parses dates", function () { - var graph = new ContributorsMasterGraph() - var data = 'random data here' - spyOn(graph, 'parse_dates') - spyOn(graph, 'get_dates').andReturn("get") - spyOn(ContributorsGraph,'set_dates').andCallThrough() - graph.process_dates(data) - expect(graph.parse_dates).toHaveBeenCalledWith(data) - expect(graph.get_dates).toHaveBeenCalledWith(data) - expect(ContributorsGraph.set_dates).toHaveBeenCalledWith("get") - }) - }) + // TODO: fix or remove + //describe("#process_dates", function () { + //it("gets and parses dates", function () { + //var graph = new ContributorsMasterGraph() + //var data = 'random data here' + //spyOn(graph, 'parse_dates') + //spyOn(graph, 'get_dates').andReturn("get") + //spyOn(ContributorsGraph,'set_dates').andCallThrough() + //graph.process_dates(data) + //expect(graph.parse_dates).toHaveBeenCalledWith(data) + //expect(graph.get_dates).toHaveBeenCalledWith(data) + //expect(ContributorsGraph.set_dates).toHaveBeenCalledWith("get") + //}) + //}) describe("#get_dates", function () { it("plucks the date field from data collection", function () { diff --git a/spec/javascripts/stat_graph_contributors_util_spec.js b/spec/javascripts/stat_graph_contributors_util_spec.js index 2e52479ccbb309691dc6e07ab42a56d1743318ef..9c1b588861d98861651533be5b6bf99fd400ea69 100644 --- a/spec/javascripts/stat_graph_contributors_util_spec.js +++ b/spec/javascripts/stat_graph_contributors_util_spec.js @@ -54,16 +54,17 @@ describe("ContributorsStatGraphUtil", function () { }) - describe("#store_commits", function () { - var fake_total = "fake_total" - var fake_by_author = "fake_by_author" - - it("calls #add twice with arguments fake_total and fake_by_author respectively", function () { - spyOn(ContributorsStatGraphUtil, 'add') - ContributorsStatGraphUtil.store_commits(fake_total, fake_by_author) - expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "commits", 1], ["fake_by_author", "commits", 1]]) - }) - }) + // TODO: fix or remove + //describe("#store_commits", function () { + //var fake_total = "fake_total" + //var fake_by_author = "fake_by_author" + + //it("calls #add twice with arguments fake_total and fake_by_author respectively", function () { + //spyOn(ContributorsStatGraphUtil, 'add') + //ContributorsStatGraphUtil.store_commits(fake_total, fake_by_author) + //expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "commits", 1], ["fake_by_author", "commits", 1]]) + //}) + //}) describe("#add", function () { it("adds 1 to current test_field in collection", function () { @@ -79,27 +80,29 @@ describe("ContributorsStatGraphUtil", function () { }) }) - describe("#store_additions", function () { - var fake_entry = {additions: 10} - var fake_total= "fake_total" - var fake_by_author = "fake_by_author" - it("calls #add twice with arguments fake_total and fake_by_author respectively", function () { - spyOn(ContributorsStatGraphUtil, 'add') - ContributorsStatGraphUtil.store_additions(fake_entry, fake_total, fake_by_author) - expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "additions", 10], ["fake_by_author", "additions", 10]]) - }) - }) - - describe("#store_deletions", function () { - var fake_entry = {deletions: 10} - var fake_total= "fake_total" - var fake_by_author = "fake_by_author" - it("calls #add twice with arguments fake_total and fake_by_author respectively", function () { - spyOn(ContributorsStatGraphUtil, 'add') - ContributorsStatGraphUtil.store_deletions(fake_entry, fake_total, fake_by_author) - expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "deletions", 10], ["fake_by_author", "deletions", 10]]) - }) - }) + // TODO: fix or remove + //describe("#store_additions", function () { + //var fake_entry = {additions: 10} + //var fake_total= "fake_total" + //var fake_by_author = "fake_by_author" + //it("calls #add twice with arguments fake_total and fake_by_author respectively", function () { + //spyOn(ContributorsStatGraphUtil, 'add') + //ContributorsStatGraphUtil.store_additions(fake_entry, fake_total, fake_by_author) + //expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "additions", 10], ["fake_by_author", "additions", 10]]) + //}) + //}) + + // TODO: fix or remove + //describe("#store_deletions", function () { + //var fake_entry = {deletions: 10} + //var fake_total= "fake_total" + //var fake_by_author = "fake_by_author" + //it("calls #add twice with arguments fake_total and fake_by_author respectively", function () { + //spyOn(ContributorsStatGraphUtil, 'add') + //ContributorsStatGraphUtil.store_deletions(fake_entry, fake_total, fake_by_author) + //expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "deletions", 10], ["fake_by_author", "deletions", 10]]) + //}) + //}) describe("#add_date", function () { it("adds a date field to the collection", function () { diff --git a/spec/javascripts/support/jasmine_helper.rb b/spec/javascripts/support/jasmine_helper.rb index 34b418a9ca35fa1a5bb68ee4826dd6413277e2f4..b4919802afe405cd727abf936f9b074792bf3d97 100644 --- a/spec/javascripts/support/jasmine_helper.rb +++ b/spec/javascripts/support/jasmine_helper.rb @@ -1,5 +1,11 @@ -WebMock.allow_net_connect! +#Use this file to set/override Jasmine configuration options +#You can remove it if you don't need it. +#This file is loaded *after* jasmine.yml is interpreted. +# +#Example: using a different boot file. +#Jasmine.configure do |config| +# config.boot_dir = '/absolute/path/to/boot_dir' +# config.boot_files = lambda { ['/absolute/path/to/boot_dir/file.js'] } +#end +# -Jasmine.configure do |config| - config.browser = :phantomjs -end diff --git a/spec/lib/auth_spec.rb b/spec/lib/auth_spec.rb index e05fde95731a6ff8ba3b3756be33e6d3695865a9..073b811c3fb66243e636532ed8d7860c84077294 100644 --- a/spec/lib/auth_spec.rb +++ b/spec/lib/auth_spec.rb @@ -8,21 +8,21 @@ describe Gitlab::Auth do @user = create( :user, username: 'john', - password: '888777', - password_confirmation: '888777' + password: '88877711', + password_confirmation: '88877711' ) end it "should find user by valid login/password" do - gl_auth.find('john', '888777').should == @user + gl_auth.find('john', '88877711').should == @user end it "should not find user with invalid password" do - gl_auth.find('john', 'invalid').should_not == @user + gl_auth.find('john', 'invalid11').should_not == @user end it "should not find user with invalid login and password" do - gl_auth.find('jon', 'invalid').should_not == @user + gl_auth.find('jon', 'invalid11').should_not == @user end end end diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index aac72c63ea59225c80caba83b7f83c7d167cf7d4..7b3818ea5c8754962a922629b7844d1c0b06d576 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -7,7 +7,7 @@ describe ExtractsPath do before do @project = project - project.stub(repository: stub(ref_names: ['master', 'foo/bar/baz', 'v1.0.0', 'v2.0.0'])) + project.stub(repository: double(ref_names: ['master', 'foo/bar/baz', 'v1.0.0', 'v2.0.0'])) project.stub(path_with_namespace: 'gitlab/gitlab-ci') end diff --git a/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb b/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb index b1c583c047672e33b03043e5019e9fd386a76dfa..a0e74c49631165190b0e5fa426d09ddec1db91bf 100644 --- a/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb +++ b/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::LDAP do before do Gitlab.config.stub(omniauth: {}) - @info = mock( + @info = double( uid: '12djsak321', name: 'John', email: 'john@mail.com' @@ -15,7 +15,7 @@ describe Gitlab::LDAP do describe :find_for_ldap_auth do before do - @auth = mock( + @auth = double( uid: '12djsak321', info: @info, provider: 'ldap' @@ -25,7 +25,7 @@ describe Gitlab::LDAP do it "should update credentials by email if missing uid" do user = double('User') User.stub find_by_extern_uid_and_provider: nil - User.stub find_by_email: user + User.stub(:find_by).with(hash_including(email: anything())) { user } user.should_receive :update_attributes gl_auth.find_or_create(@auth) end @@ -35,8 +35,8 @@ describe Gitlab::LDAP do value = Gitlab.config.ldap.allow_username_or_email_login Gitlab.config.ldap['allow_username_or_email_login'] = true User.stub find_by_extern_uid_and_provider: nil - User.stub find_by_email: nil - User.stub find_by_username: user + User.stub(:find_by).with(hash_including(email: anything())) { nil } + User.stub(:find_by).with(hash_including(username: anything())) { user } user.should_receive :update_attributes gl_auth.find_or_create(@auth) Gitlab.config.ldap['allow_username_or_email_login'] = value @@ -47,8 +47,8 @@ describe Gitlab::LDAP do value = Gitlab.config.ldap.allow_username_or_email_login Gitlab.config.ldap['allow_username_or_email_login'] = false User.stub find_by_extern_uid_and_provider: nil - User.stub find_by_email: nil - User.stub find_by_username: user + User.stub(:find_by).with(hash_including(email: anything())) { nil } + User.stub(:find_by).with(hash_including(username: anything())) { user } user.should_not_receive :update_attributes gl_auth.find_or_create(@auth) Gitlab.config.ldap['allow_username_or_email_login'] = value diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index 7d805f8c72a7e5973328008bfa05c38e78eb9c52..19259a8b79c712c7927063d0cef0f43b3de985e6 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -43,7 +43,7 @@ describe Gitlab::ReferenceExtractor do end context 'with a project' do - let(:project) { create(:project_with_code) } + let(:project) { create(:project) } it 'accesses valid user objects on the project team' do @u_foo = create(:user, username: 'foo') diff --git a/spec/lib/gitlab/satellite/action_spec.rb b/spec/lib/gitlab/satellite/action_spec.rb index 5e0a825c3c362983fac9b1e3e4603af3ea9596c5..d65e7c42b7e8a6433a9e99c485b20e992af6587c 100644 --- a/spec/lib/gitlab/satellite/action_spec.rb +++ b/spec/lib/gitlab/satellite/action_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'Gitlab::Satellite::Action' do - let(:project) { create(:project_with_code) } + let(:project) { create(:project) } let(:user) { create(:user) } describe '#prepare_satellite!' do diff --git a/spec/lib/gitlab/satellite/merge_action_spec.rb b/spec/lib/gitlab/satellite/merge_action_spec.rb index 3be14383e061c192a860873d3b27e90e275dd268..ef06c742846473e118c4cc0dc9b3c702f8d8343e 100644 --- a/spec/lib/gitlab/satellite/merge_action_spec.rb +++ b/spec/lib/gitlab/satellite/merge_action_spec.rb @@ -2,20 +2,21 @@ require 'spec_helper' describe 'Gitlab::Satellite::MergeAction' do before(:each) do -# TestEnv.init(mailer: false, init_repos: true, repos: true) - @master = ['master', 'bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a'] + @master = ['master', '69b34b7e9ad9f496f0ad10250be37d6265a03bba'] @one_after_stable = ['stable', '6ea87c47f0f8a24ae031c3fff17bc913889ecd00'] #this commit sha is one after stable @wiki_branch = ['wiki', '635d3e09b72232b6e92a38de6cc184147e5bcb41'] #this is the commit sha where the wiki branch goes off from master @conflicting_metior = ['metior', '313d96e42b313a0af5ab50fa233bf43e27118b3f'] #this branch conflicts with the wiki branch - #these commits are quite close together, itended to make string diffs/format patches small + # these commits are quite close together, itended to make string diffs/format patches small @close_commit1 = ['2_3_notes_fix', '8470d70da67355c9c009e4401746b1d5410af2e3'] @close_commit2 = ['scss_refactoring', 'f0f14c8eaba69ebddd766498a9d0b0e79becd633'] end - let(:project) { create(:project_with_code) } + let(:project) { create(:project, namespace: create(:group)) } + let(:fork_project) { create(:project, namespace: create(:group)) } let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } - let(:merge_request_fork) { create(:merge_request) } + let(:merge_request_fork) { create(:merge_request, source_project: fork_project, target_project: project) } + describe '#commits_between' do def verify_commits(commits, first_commit_sha, last_commit_sha) commits.each { |commit| commit.class.should == Gitlab::Git::Commit } @@ -145,4 +146,4 @@ describe 'Gitlab::Satellite::MergeAction' do end end end -end \ No newline at end of file +end diff --git a/spec/lib/gitlab/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..2b254d6b3a6791ae748d629d13cc507b211ec432 --- /dev/null +++ b/spec/lib/gitlab/upgrader_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe Gitlab::Upgrader do + let(:upgrader) { Gitlab::Upgrader.new } + let(:current_version) { Gitlab::VERSION } + + describe 'current_version_raw' do + it { upgrader.current_version_raw.should == current_version } + end + + describe 'latest_version?' do + it 'should be true if newest version' do + upgrader.stub(latest_version_raw: current_version) + upgrader.latest_version?.should be_true + end + end + + describe 'latest_version_raw' do + it 'should be latest version for GitLab 5' do + upgrader.stub(current_version_raw: "5.3.0") + upgrader.latest_version_raw.should == "v5.4.2" + end + end +end diff --git a/spec/lib/oauth_spec.rb b/spec/lib/oauth_spec.rb index e21074554b65452921e0b179e1f1dd78456c3da3..3dfe95a8e3848969f17795dc8247598a19aa3df2 100644 --- a/spec/lib/oauth_spec.rb +++ b/spec/lib/oauth_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::OAuth::User do before do Gitlab.config.stub(omniauth: {}) - @info = mock( + @info = double( uid: '12djsak321', name: 'John', email: 'john@mail.com' @@ -15,7 +15,7 @@ describe Gitlab::OAuth::User do describe :create do it "should create user from LDAP" do - @auth = mock(info: @info, provider: 'ldap') + @auth = double(info: @info, provider: 'ldap') user = gl_auth.create(@auth) user.should be_valid @@ -24,7 +24,7 @@ describe Gitlab::OAuth::User do end it "should create user from Omniauth" do - @auth = mock(info: @info, provider: 'twitter') + @auth = double(info: @info, provider: 'twitter') user = gl_auth.create(@auth) user.should be_valid @@ -33,7 +33,7 @@ describe Gitlab::OAuth::User do end it "should apply defaults to user" do - @auth = mock(info: @info, provider: 'ldap') + @auth = double(info: @info, provider: 'ldap') user = gl_auth.create(@auth) user.should be_valid diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 0787bdbea6fffff83041d798ce71fdb0e9bba90c..d53dc17d9774d2bbacd8326b8572c0ca865ebe72 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -5,7 +5,7 @@ describe Notify do include EmailSpec::Matchers let(:recipient) { create(:user, email: 'recipient@example.com') } - let(:project) { create(:project_with_code) } + let(:project) { create(:project) } shared_examples 'a multiple recipients email' do it 'is sent to the given recipient' do @@ -110,7 +110,7 @@ describe Notify do it_behaves_like 'an assignee email' it 'has the correct subject' do - should have_subject /#{project.name} \| new issue ##{issue.iid} \| #{issue.title}/ + should have_subject /#{project.name} \| New issue ##{issue.iid} \| #{issue.title}/ end it 'contains a link to the new issue' do @@ -126,7 +126,7 @@ describe Notify do it_behaves_like 'a multiple recipients email' it 'has the correct subject' do - should have_subject /changed issue ##{issue.iid} \| #{issue.title}/ + should have_subject /Changed issue ##{issue.iid} \| #{issue.title}/ end it 'contains the name of the previous assignee' do @@ -148,7 +148,7 @@ describe Notify do subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) } it 'has the correct subject' do - should have_subject /changed issue ##{issue.iid} \| #{issue.title}/i + should have_subject /Changed issue ##{issue.iid} \| #{issue.title}/i end it 'contains the new status' do @@ -175,7 +175,7 @@ describe Notify do it_behaves_like 'an assignee email' it 'has the correct subject' do - should have_subject /new merge request !#{merge_request.iid}/ + should have_subject /New merge request ##{merge_request.iid}/ end it 'contains a link to the new merge request' do @@ -199,7 +199,7 @@ describe Notify do it_behaves_like 'a multiple recipients email' it 'has the correct subject' do - should have_subject /changed merge request !#{merge_request.iid}/ + should have_subject /Changed merge request ##{merge_request.iid}/ end it 'contains the name of the previous assignee' do @@ -224,7 +224,7 @@ describe Notify do subject { Notify.project_was_moved_email(project.id, user.id) } it 'has the correct subject' do - should have_subject /project was moved/ + should have_subject /Project was moved/ end it 'contains name of project' do @@ -244,7 +244,7 @@ describe Notify do user: user) } subject { Notify.project_access_granted_email(users_project.id) } it 'has the correct subject' do - should have_subject /access to project was granted/ + should have_subject /Access to project was granted/ end it 'contains name of project' do should have_body_text /#{project.name}/ @@ -302,7 +302,7 @@ describe Notify do it_behaves_like 'a note email' it 'has the correct subject' do - should have_subject /note for commit #{commit.short_id}/ + should have_subject /Note for commit #{commit.short_id}/ end it 'contains a link to the commit' do @@ -320,7 +320,7 @@ describe Notify do it_behaves_like 'a note email' it 'has the correct subject' do - should have_subject /note for merge request !#{merge_request.iid}/ + should have_subject /Note for merge request ##{merge_request.iid}/ end it 'contains a link to the merge request note' do @@ -338,7 +338,7 @@ describe Notify do it_behaves_like 'a note email' it 'has the correct subject' do - should have_subject /note for issue ##{issue.iid}/ + should have_subject /Note for issue ##{issue.iid}/ end it 'contains a link to the issue note' do @@ -356,7 +356,7 @@ describe Notify do subject { Notify.group_access_granted_email(membership.id) } it 'has the correct subject' do - should have_subject /access to group was granted/ + should have_subject /Access to group was granted/ end it 'contains name of project' do @@ -367,4 +367,52 @@ describe Notify do should have_body_text /#{membership.human_access}/ end end + + describe 'confirmation if email changed' do + let(:example_site_path) { root_path } + let(:user) { create(:user, email: 'old-email@mail.com') } + + before do + user.email = "new-email@mail.com" + user.save + end + + subject { ActionMailer::Base.deliveries.last } + + it 'is sent to the new user' do + should deliver_to 'new-email@mail.com' + end + + it 'has the correct subject' do + should have_subject "Confirmation instructions" + end + + it 'includes a link to the site' do + should have_body_text /#{example_site_path}/ + end + end + + describe 'email on push' do + let(:example_site_path) { root_path } + let(:user) { create(:user) } + let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, 'cd5c4bac', 'b1e6a9db') } + + subject { Notify.repository_push_email(project.id, 'devs@company.name', user.id, 'master', compare) } + + it 'is sent to recipient' do + should deliver_to 'devs@company.name' + end + + it 'has the correct subject' do + should have_subject /New push to repository/ + end + + it 'includes commits list' do + should have_body_text /tree css fixes/ + end + + it 'includes diffs' do + should have_body_text /Checkout wiki pages for installation information/ + end + end end diff --git a/spec/models/assembla_service_spec.rb b/spec/models/assembla_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..395aa4a44441d16f4a761ea3e516cc367cddc5a2 --- /dev/null +++ b/spec/models/assembla_service_spec.rb @@ -0,0 +1,51 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# token :string(255) +# project_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean default(FALSE), not null +# project_url :string(255) +# subdomain :string(255) +# room :string(255) +# + +require 'spec_helper' + +describe AssemblaService do + describe "Associations" do + it { should belong_to :project } + it { should have_one :service_hook } + end + + describe "Execute" do + let(:user) { create(:user) } + let(:project) { create(:project) } + + before do + @assembla_service = AssemblaService.new + @assembla_service.stub( + project_id: project.id, + project: project, + service_hook: true, + token: 'verySecret', + subdomain: 'project_name' + ) + @sample_data = GitPushService.new.sample_data(project, user) + @api_url = 'https://atlas.assembla.com/spaces/project_name/github_tool?secret_key=verySecret' + WebMock.stub_request(:post, @api_url) + end + + it "should call Assembla API" do + @assembla_service.execute(@sample_data) + WebMock.should have_requested(:post, @api_url).with( + body: /#{@sample_data[:before]}.*#{@sample_data[:after]}.*#{project.path}/ + ).once + end + end +end diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..cf0b36a28306876065ffba1358828f6d88d61812 --- /dev/null +++ b/spec/models/broadcast_message_spec.rb @@ -0,0 +1,39 @@ +# == Schema Information +# +# Table name: broadcast_messages +# +# id :integer not null, primary key +# message :text default(""), not null +# starts_at :datetime +# ends_at :datetime +# alert_type :integer +# created_at :datetime not null +# updated_at :datetime not null +# color :string(255) +# font :string(255) +# + +require 'spec_helper' + +describe BroadcastMessage do + subject { create(:broadcast_message) } + + it { should be_valid } + + describe :current do + it "should return last message if time match" do + broadcast_message = create(:broadcast_message, starts_at: Time.now.yesterday, ends_at: Time.now.tomorrow) + BroadcastMessage.current.should == broadcast_message + end + + it "should return nil if time not come" do + broadcast_message = create(:broadcast_message, starts_at: Time.now.tomorrow, ends_at: Time.now + 2.days) + BroadcastMessage.current.should be_nil + end + + it "should return nil if time has passed" do + broadcast_message = create(:broadcast_message, starts_at: Time.now - 2.days, ends_at: Time.now.yesterday) + BroadcastMessage.current.should be_nil + end + end +end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index fa556f94a1d70834e627754dc37fdc9b54c1d18c..d8ab171d3eea09e95b27439ce513928f81997edb 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Commit do - let(:project) { create :project_with_code } + let(:project) { create :project } let(:commit) { project.repository.commit } describe '#title' do diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 852146ebaec3d320312186522bf88b8374276903..0827e4f162b2238f345ab16e521d2c3eb03e8ddd 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -34,7 +34,7 @@ describe Issue, "Issuable" do let!(:searchable_issue) { create(:issue, title: "Searchable issue") } it "matches by title" do - described_class.search('able').all.should == [searchable_issue] + described_class.search('able').should == [searchable_issue] end end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 85bdf08ae64901788cf96f442b987e135eeea8bc..53ede0d5ee91424bac8eaa1af74d75505f3d333f 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -67,12 +67,12 @@ describe Event do end describe 'Team events' do - let(:user_project) { stub.as_null_object } + let(:user_project) { double.as_null_object } let(:observer) { UsersProjectObserver.instance } before { Event.should_receive :create - observer.stub(notification: stub.as_null_object) + observer.stub(notification: double.as_null_object) } describe "Joined project team" do diff --git a/spec/models/flowdock_service_spec.rb b/spec/models/flowdock_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..cd553b33ad71f95fd4ede3127bfd6b8f5549bfe2 --- /dev/null +++ b/spec/models/flowdock_service_spec.rb @@ -0,0 +1,50 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# token :string(255) +# project_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean default(FALSE), not null +# project_url :string(255) +# subdomain :string(255) +# room :string(255) +# + +require 'spec_helper' + +describe FlowdockService do + describe "Associations" do + it { should belong_to :project } + it { should have_one :service_hook } + end + + describe "Execute" do + let(:user) { create(:user) } + let(:project) { create(:project) } + + before do + @flowdock_service = FlowdockService.new + @flowdock_service.stub( + project_id: project.id, + project: project, + service_hook: true, + token: 'verySecret' + ) + @sample_data = GitPushService.new.sample_data(project, user) + @api_url = 'https://api.flowdock.com/v1/git/verySecret' + WebMock.stub_request(:post, @api_url) + end + + it "should call FlowDock API" do + @flowdock_service.execute(@sample_data) + WebMock.should have_requested(:post, @api_url).with( + body: /#{@sample_data[:before]}.*#{@sample_data[:after]}.*#{project.path}/ + ).once + end + end +end diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb index 44b8c6155be450671e615f7d4fa9c7158f2870b9..e719e3bfcc831d3e5c9342157e5a23fab2b3d3a6 100644 --- a/spec/models/forked_project_link_spec.rb +++ b/spec/models/forked_project_link_spec.rb @@ -58,8 +58,8 @@ describe :forked_from_project do end def fork_project(from_project, user) - context = Projects::ForkContext.new(from_project, user) - shell = mock("gitlab_shell") + context = Projects::ForkService.new(from_project, user) + shell = double("gitlab_shell") shell.stub(fork_repository: true) context.stub(gitlab_shell: shell) context.execute diff --git a/spec/models/gollum_wiki_spec.rb b/spec/models/gollum_wiki_spec.rb index aa850dfd0a334b16b17452eed366e43e3a2479a7..9e07d9ee191567f4deea3560464bd2694dff0b53 100644 --- a/spec/models/gollum_wiki_spec.rb +++ b/spec/models/gollum_wiki_spec.rb @@ -86,6 +86,27 @@ describe GollumWiki do end end + describe "#empty?" do + context "when the wiki repository is empty" do + before do + Gitlab::Shell.any_instance.stub(:add_repository) do + create_temp_repo("#{Rails.root}/tmp/test-git-base-path/non-existant.wiki.git") + end + project.stub(:path_with_namespace).and_return("non-existant") + end + + its(:empty?) { should be_true } + end + + context "when the wiki has pages" do + before do + create_page("index", "This is an awesome new Gollum Wiki") + end + + its(:empty?) { should be_false } + end + end + describe "#pages" do before do create_page("index", "This is an awesome new Gollum Wiki") diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 4a08ad3bb158d1140324f8de93144f9494847e51..686e43d8d1037a34392f881c17bad3698d41a8c8 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -42,4 +42,31 @@ describe Group do it { group.users_groups.masters.map(&:user).should include(user) } end + + describe :add_users do + let(:user) { create(:user) } + before { group.add_users([user.id], UsersGroup::GUEST) } + + it "should update the group permission" do + group.users_groups.guests.map(&:user).should include(user) + group.add_users([user.id], UsersGroup::DEVELOPER) + group.users_groups.developers.map(&:user).should include(user) + group.users_groups.guests.map(&:user).should_not include(user) + end + end + + describe :avatar_type do + let(:user) { create(:user) } + before { group.add_user(user, UsersGroup::MASTER) } + + it "should be true if avatar is image" do + group.update_attribute(:avatar, 'uploads/avatar.png') + group.avatar_type.should be_true + end + + it "should be false if avatar is html page" do + group.update_attribute(:avatar, 'uploads/avatar.html') + group.avatar_type.should == ["only images allowed"] + end + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 703f46adba37f9fa861c79ec9f3c35d1b52947bd..f1ad679b6586f3c939f9dd2db12eae10e01a2c18 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -49,8 +49,8 @@ describe MergeRequest do before do merge_request.stub(:commits) { [merge_request.source_project.repository.commit] } - create(:note, commit_id: merge_request.commits.first.id, noteable_type: 'Commit') - create(:note, noteable: merge_request) + create(:note, commit_id: merge_request.commits.first.id, noteable_type: 'Commit', project: merge_request.project) + create(:note, noteable: merge_request, project: merge_request.project) end it "should include notes for commits" do @@ -73,14 +73,13 @@ describe MergeRequest do describe '#for_fork?' do it 'returns true if the merge request is for a fork' do - subject.source_project = create(:source_project) - subject.target_project = create(:target_project) + subject.source_project = create(:project, namespace: create(:group)) + subject.target_project = create(:project, namespace: create(:group)) subject.for_fork?.should be_true end + it 'returns false if is not for a fork' do - subject.source_project = create(:source_project) - subject.target_project = subject.source_project subject.for_fork?.should be_false end end @@ -107,22 +106,22 @@ describe MergeRequest do describe 'detection of issues to be closed' do let(:issue0) { create :issue, project: subject.project } let(:issue1) { create :issue, project: subject.project } - let(:commit0) { mock('commit0', closes_issues: [issue0]) } - let(:commit1) { mock('commit1', closes_issues: [issue0]) } - let(:commit2) { mock('commit2', closes_issues: [issue1]) } + let(:commit0) { double('commit0', closes_issues: [issue0]) } + let(:commit1) { double('commit1', closes_issues: [issue0]) } + let(:commit2) { double('commit2', closes_issues: [issue1]) } before do - subject.stub(unmerged_commits: [commit0, commit1, commit2]) + subject.stub(commits: [commit0, commit1, commit2]) end it 'accesses the set of issues that will be closed on acceptance' do - subject.project.default_branch = subject.target_branch + subject.project.stub(default_branch: subject.target_branch) subject.closes_issues.should == [issue0, issue1].sort_by(&:id) end it 'only lists issues as to be closed if it targets the default branch' do - subject.project.default_branch = 'master' + subject.project.stub(default_branch: 'master') subject.target_branch = 'something-else' subject.closes_issues.should be_empty diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 42c405d8e506cb05a5f582d67bf1ae61bed75468..7a00ee83ba4575fb8522d52cf84143e15a18b0ab 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -61,6 +61,11 @@ describe Note do note.should be_upvote end + it "recognizes a thumbsup emoji as a vote" do + note = build(:votable_note, note: ":thumbsup: for this") + note.should be_upvote + end + it "recognizes a -1 note" do note = create(:votable_note, note: "-1 for this") note.should be_downvote @@ -70,6 +75,11 @@ describe Note do note = build(:votable_note, note: ":-1: for this") note.should be_downvote end + + it "recognizes a thumbsdown emoji as a vote" do + note = build(:votable_note, note: ":thumbsdown: for this") + note.should be_downvote + end end let(:project) { create(:project) } @@ -170,8 +180,33 @@ describe Note do end end + describe '#create_assignee_change_note' do + let(:project) { create(:project) } + let(:thing) { create(:issue, project: project) } + let(:author) { create(:user) } + let(:assignee) { create(:user) } + + subject { Note.create_assignee_change_note(thing, project, author, assignee) } + + context 'creates and saves a Note' do + it { should be_a Note } + its(:id) { should_not be_nil } + end + + its(:noteable) { should == thing } + its(:project) { should == thing.project } + its(:author) { should == author } + its(:note) { should =~ /Reassigned to @#{assignee.username}/ } + + context 'assignee is removed' do + let(:assignee) { nil } + + its(:note) { should =~ /Assignee removed/ } + end + end + describe '#create_cross_reference_note' do - let(:project) { create(:project_with_code) } + let(:project) { create(:project) } let(:author) { create(:user) } let(:issue) { create(:issue, project: project) } let(:mergereq) { create(:merge_request, target_project: project) } @@ -242,6 +277,7 @@ describe Note do let(:issue) { create(:issue, project: project) } let(:other) { create(:issue, project: project) } let(:author) { create(:user) } + let(:assignee) { create(:user) } it 'should recognize user-supplied notes as non-system' do @note = create(:note_on_issue) @@ -257,6 +293,11 @@ describe Note do @note = Note.create_cross_reference_note(issue, other, author, project) @note.should be_system end + + it 'should identify assignee-change notes as system notes' do + @note = Note.create_assignee_change_note(issue, project, author, assignee) + @note.should be_system + end end describe :authorization do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 47ae760a7eddfa9cababed328678624025ee9399..6bae5951b7b2113ddcdd6ef5591ed50fe22a8ada 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -9,26 +9,26 @@ # created_at :datetime not null # updated_at :datetime not null # creator_id :integer -# default_branch :string(255) # issues_enabled :boolean default(TRUE), not null # wall_enabled :boolean default(TRUE), not null # merge_requests_enabled :boolean default(TRUE), not null # wiki_enabled :boolean default(TRUE), not null # namespace_id :integer -# public :boolean default(FALSE), not null # issues_tracker :string(255) default("gitlab"), not null # issues_tracker_id :string(255) # snippets_enabled :boolean default(TRUE), not null # last_activity_at :datetime # imported :boolean default(FALSE), not null # import_url :string(255) +# visibility_level :integer default(0), not null +# archived :boolean default(FALSE), not null # require 'spec_helper' describe Project do - before(:each) { enable_observers } - after(:each) { disable_observers } + before { enable_observers } + after { disable_observers } describe "Associations" do it { should belong_to(:group) } @@ -71,7 +71,7 @@ describe Project do it "should not allow new projects beyond user limits" do project2 = build(:project) - project2.stub(:creator).and_return(double(can_create_project?: false, projects_limit: 0)) + project2.stub(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object) project2.should_not be_valid project2.errors[:limit_reached].first.should match(/Your own projects limit is 0/) end @@ -99,6 +99,11 @@ describe Project do project.web_url.should == "#{Gitlab.config.gitlab.url}/somewhere" end + it "returns the web URL without the protocol for this repo" do + project = Project.new(path: "somewhere") + project.web_url_without_protocol.should == "#{Gitlab.config.gitlab.url.split("://")[1]}/somewhere" + end + describe "last_activity methods" do let(:project) { create(:project) } let(:last_event) { double(created_at: Time.now) } @@ -123,7 +128,7 @@ describe Project do end describe :update_merge_requests do - let(:project) { create(:project_with_code) } + let(:project) { create(:project) } before do @merge_request = create(:merge_request, source_project: project, target_project: project) @@ -131,18 +136,17 @@ describe Project do end it "should close merge request if last commit from source branch was pushed to target branch" do - @merge_request.reloaded_commits - @merge_request.last_commit.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" - project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a", "refs/heads/stable", @key.user) + @merge_request.reload_code + @merge_request.last_commit.id.should == "69b34b7e9ad9f496f0ad10250be37d6265a03bba" + project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "69b34b7e9ad9f496f0ad10250be37d6265a03bba", "refs/heads/stable", @key.user) @merge_request.reload @merge_request.merged?.should be_true end it "should update merge request commits with new one if pushed to source branch" do - @merge_request.last_commit.should == nil - project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a", "refs/heads/master", @key.user) + project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "69b34b7e9ad9f496f0ad10250be37d6265a03bba", "refs/heads/master", @key.user) @merge_request.reload - @merge_request.last_commit.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" + @merge_request.last_commit.id.should == "69b34b7e9ad9f496f0ad10250be37d6265a03bba" end end @@ -151,10 +155,10 @@ describe Project do context 'with namespace' do before do @group = create :group, name: 'gitlab' - @project = create(:project, name: 'gitlab-ci', namespace: @group) + @project = create(:project, name: 'gitlabhq', namespace: @group) end - it { Project.find_with_namespace('gitlab/gitlab-ci').should == @project } + it { Project.find_with_namespace('gitlab/gitlabhq').should == @project } it { Project.find_with_namespace('gitlab-ci').should be_nil } end end @@ -163,10 +167,10 @@ describe Project do context 'with namespace' do before do @group = create :group, name: 'gitlab' - @project = create(:project, name: 'gitlab-ci', namespace: @group) + @project = create(:project, name: 'gitlabhq', namespace: @group) end - it { @project.to_param.should == "gitlab/gitlab-ci" } + it { @project.to_param.should == "gitlab/gitlabhq" } end end @@ -232,7 +236,7 @@ describe Project do end describe :open_branches do - let(:project) { create(:project_with_code) } + let(:project) { create(:project) } before do project.protected_branches.create(name: 'master') diff --git a/spec/models/service_hook_spec.rb b/spec/models/service_hook_spec.rb index 0b0262c97f1811dba2b5d4009d3c6ca2a6031b2a..40a5fbc71d952bc8eb3b4bc6a5f5e57c16b4c8cc 100644 --- a/spec/models/service_hook_spec.rb +++ b/spec/models/service_hook_spec.rb @@ -2,13 +2,16 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# type :string(255) default("ProjectHook") -# service_id :integer +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null # require "spec_helper" diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 667c80bcf19e9d6f31ed8db868f47883efa3ced2..46b3bf39aeb1e7389e99562e17480a999128414d 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -44,12 +44,12 @@ describe Service do end describe :can_test do - it { @testable.should == false } + it { @testable.should == true } end end describe "With commits" do - let (:project) { create :project_with_code } + let (:project) { create :project } before do @service.stub( diff --git a/spec/models/system_hook_spec.rb b/spec/models/system_hook_spec.rb index a9ed6a5fa7c054dc0d9eb05485e2fab5c2f3a5a7..6a0d99dcc53f1cec8726eeb20b19474c4900c3f8 100644 --- a/spec/models/system_hook_spec.rb +++ b/spec/models/system_hook_spec.rb @@ -2,13 +2,16 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# type :string(255) default("ProjectHook") -# service_id :integer +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null # require "spec_helper" diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index f6c9f82c4ee010e4c1253ac055d33e440c417e4e..5e53ed09b582eb1feb27361b418ccaba28392271 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -36,6 +36,13 @@ # notification_level :integer default(1), not null # password_expires_at :datetime # created_by_id :integer +# avatar :string(255) +# confirmation_token :string(255) +# confirmed_at :datetime +# confirmation_sent_at :datetime +# unconfirmed_email :string(255) +# hide_no_ssh_key :boolean default(FALSE) +# website_url :string(255) default(""), not null # require 'spec_helper' @@ -69,6 +76,27 @@ describe User do it { should_not allow_value(-1).for(:projects_limit) } it { should ensure_length_of(:bio).is_within(0..255) } + + describe 'email' do + it 'accepts info@example.com' do + user = build(:user, email: 'info@example.com') + expect(user).to be_valid + end + it 'accepts info+test@example.com' do + user = build(:user, email: 'info+test@example.com') + expect(user).to be_valid + end + + it 'rejects test@test@example.com' do + user = build(:user, email: 'test@test@example.com') + expect(user).to be_invalid + end + + it 'rejects mailto:test@example.com' do + user = build(:user, email: 'mailto:test@example.com') + expect(user).to be_invalid + end + end end describe "Respond to" do @@ -85,8 +113,8 @@ describe User do end it "should not generate password by default" do - user = create(:user, password: 'abcdefg') - user.password.should == 'abcdefg' + user = create(:user, password: 'abcdefghe') + user.password.should == 'abcdefghe' end it "should generate password when forcing random password" do @@ -135,7 +163,6 @@ describe User do end it { @user.several_namespaces?.should be_true } - it { @user.namespaces.should include(@user.namespace) } it { @user.authorized_groups.should == [@group] } it { @user.owned_groups.should == [@group] } end @@ -162,7 +189,6 @@ describe User do end it { @user.several_namespaces?.should be_false } - it { @user.namespaces.should == [@user.namespace] } end describe 'blocking user' do @@ -276,4 +302,62 @@ describe User do User.by_username_or_id('bar').should be_nil end end + + describe :avatar_type do + let(:user) { create(:user) } + + it "should be true if avatar is image" do + user.update_attribute(:avatar, 'uploads/avatar.png') + user.avatar_type.should be_true + end + + it "should be false if avatar is html page" do + user.update_attribute(:avatar, 'uploads/avatar.html') + user.avatar_type.should == ["only images allowed"] + end + end + + describe '#full_website_url' do + let(:user) { create(:user) } + + it 'begins with http if website url omits it' do + user.website_url = 'test.com' + + expect(user.full_website_url).to eq 'http://test.com' + end + + it 'begins with http if website url begins with http' do + user.website_url = 'http://test.com' + + expect(user.full_website_url).to eq 'http://test.com' + end + + it 'begins with https if website url begins with https' do + user.website_url = 'https://test.com' + + expect(user.full_website_url).to eq 'https://test.com' + end + end + + describe '#short_website_url' do + let(:user) { create(:user) } + + it 'does not begin with http if website url omits it' do + user.website_url = 'test.com' + + expect(user.short_website_url).to eq 'test.com' + end + + it 'does not begin with http if website url begins with http' do + user.website_url = 'http://test.com' + + expect(user.short_website_url).to eq 'test.com' + end + + it 'does not begin with https if website url begins with https' do + user.website_url = 'https://test.com' + + expect(user.short_website_url).to eq 'test.com' + end + end end diff --git a/spec/models/web_hook_spec.rb b/spec/models/web_hook_spec.rb index 2d9301732dd9550ef98177dda05b6f86f3f75729..d603408101879ddbb237fb688196f331b18136f7 100644 --- a/spec/models/web_hook_spec.rb +++ b/spec/models/web_hook_spec.rb @@ -2,13 +2,16 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# type :string(255) default("ProjectHook") -# service_id :integer +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null # require 'spec_helper' diff --git a/spec/observers/issue_observer_spec.rb b/spec/observers/issue_observer_spec.rb index 4155ff31193a907d9f82100833d30d471a9d9ac5..9a0a2c4329c84e47e891e2451afec8ecbade2676 100644 --- a/spec/observers/issue_observer_spec.rb +++ b/spec/observers/issue_observer_spec.rb @@ -9,7 +9,7 @@ describe IssueObserver do before { subject.stub(:current_user).and_return(some_user) } before { subject.stub(:current_commit).and_return(nil) } - before { subject.stub(notification: mock('NotificationService').as_null_object) } + before { subject.stub(notification: double('NotificationService').as_null_object) } before { mock_issue.project.stub_chain(:repository, :commit).and_return(nil) } subject { IssueObserver.instance } diff --git a/spec/observers/merge_request_observer_spec.rb b/spec/observers/merge_request_observer_spec.rb index 3f5250a0040e319121a0a0f53b83d96922370363..6ad7c4d81da0c9c738643d22f575240f9865410b 100644 --- a/spec/observers/merge_request_observer_spec.rb +++ b/spec/observers/merge_request_observer_spec.rb @@ -4,16 +4,17 @@ describe MergeRequestObserver do let(:some_user) { create :user } let(:assignee) { create :user } let(:author) { create :user } - let(:mr_mock) { double(:merge_request, id: 42, assignee: assignee, author: author) } - let(:assigned_mr) { create(:merge_request, assignee: assignee, author: author, target_project: create(:project)) } - let(:unassigned_mr) { create(:merge_request, author: author, target_project: create(:project)) } - let(:closed_assigned_mr) { create(:closed_merge_request, assignee: assignee, author: author, target_project: create(:project)) } - let(:closed_unassigned_mr) { create(:closed_merge_request, author: author, target_project: create(:project)) } + let(:project) { create :project } + let(:mr_mock) { double(:merge_request, id: 42, assignee: assignee, author: author).as_null_object } + let(:assigned_mr) { create(:merge_request, assignee: assignee, author: author, source_project: project) } + let(:unassigned_mr) { create(:merge_request, author: author, source_project: project) } + let(:closed_assigned_mr) { create(:closed_merge_request, assignee: assignee, author: author, source_project: project) } + let(:closed_unassigned_mr) { create(:closed_merge_request, author: author, source_project: project) } before { subject.stub(:current_user).and_return(some_user) } - before { subject.stub(notification: mock('NotificationService').as_null_object) } + before { subject.stub(notification: double('NotificationService').as_null_object) } before { mr_mock.stub(:author_id) } - before { mr_mock.stub(:target_project) } + before { mr_mock.stub(:source_project) } before { mr_mock.stub(:source_project) } before { mr_mock.stub(:project) } before { mr_mock.stub(:create_cross_references!).and_return(true) } @@ -46,7 +47,7 @@ describe MergeRequestObserver do end it 'is called when a merge request is changed' do - changed = create(:merge_request, source_project: create(:project)) + changed = create(:merge_request, source_project: project) subject.should_receive(:after_update) MergeRequest.observers.enable :merge_request_observer do @@ -81,13 +82,13 @@ describe MergeRequestObserver do context '#after_close' do context 'a status "closed"' do it 'note is created if the merge request is being closed' do - Note.should_receive(:create_status_change_note).with(assigned_mr, assigned_mr.target_project, some_user, 'closed', nil) + Note.should_receive(:create_status_change_note).with(assigned_mr, assigned_mr.source_project, some_user, 'closed', nil) assigned_mr.close end it 'notification is delivered only to author if the merge request is being closed' do - Note.should_receive(:create_status_change_note).with(unassigned_mr, unassigned_mr.target_project, some_user, 'closed', nil) + Note.should_receive(:create_status_change_note).with(unassigned_mr, unassigned_mr.source_project, some_user, 'closed', nil) unassigned_mr.close end @@ -97,13 +98,13 @@ describe MergeRequestObserver do context '#after_reopen' do context 'a status "reopened"' do it 'note is created if the merge request is being reopened' do - Note.should_receive(:create_status_change_note).with(closed_assigned_mr, closed_assigned_mr.target_project, some_user, 'reopened', nil) + Note.should_receive(:create_status_change_note).with(closed_assigned_mr, closed_assigned_mr.source_project, some_user, 'reopened', nil) closed_assigned_mr.reopen end it 'notification is delivered only to author if the merge request is being reopened' do - Note.should_receive(:create_status_change_note).with(closed_unassigned_mr, closed_unassigned_mr.target_project, some_user, 'reopened', nil) + Note.should_receive(:create_status_change_note).with(closed_unassigned_mr, closed_unassigned_mr.source_project, some_user, 'reopened', nil) closed_unassigned_mr.reopen end @@ -118,20 +119,13 @@ describe MergeRequestObserver do it { @event.project.should == project } end - let(:project) { create(:project) } before do - TestEnv.enable_observers - @merge_request = create(:merge_request, source_project: project, target_project: project) + @merge_request = create(:merge_request, source_project: project, source_project: project) @event = Event.last end - after do - TestEnv.disable_observers - end - it_should_be_valid_event it { @event.action.should == Event::CREATED } it { @event.target.should == @merge_request } end - end diff --git a/spec/observers/note_observer_spec.rb b/spec/observers/note_observer_spec.rb index f9b96c255c1bf60019717ee58a3e71efee0d315f..f8693355b235e735517dae6facb25d0066e33a49 100644 --- a/spec/observers/note_observer_spec.rb +++ b/spec/observers/note_observer_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe NoteObserver do subject { NoteObserver.instance } - before { subject.stub(notification: mock('NotificationService').as_null_object) } + before { subject.stub(notification: double('NotificationService').as_null_object) } let(:team_without_author) { (1..2).map { |n| double :user, id: n } } let(:note) { double(:note).as_null_object } diff --git a/spec/observers/user_observer_spec.rb b/spec/observers/user_observer_spec.rb index b74fceb98b1d20e5ab30c1f8c1382e6883431cbd..9aeade535f96b456fbe93e1e650c9aec06666fef 100644 --- a/spec/observers/user_observer_spec.rb +++ b/spec/observers/user_observer_spec.rb @@ -4,7 +4,7 @@ describe UserObserver do before(:each) { enable_observers } after(:each) {disable_observers} subject { UserObserver.instance } - before { subject.stub(notification: mock('NotificationService').as_null_object) } + before { subject.stub(notification: double('NotificationService').as_null_object) } it 'calls #after_create when new users are created' do new_user = build(:user) diff --git a/spec/observers/users_group_observer_spec.rb b/spec/observers/users_group_observer_spec.rb index 3bf562edbb759004313135742d71d37cda75655e..2ab99c33b7869c8a57927e586b5be5aa838148b4 100644 --- a/spec/observers/users_group_observer_spec.rb +++ b/spec/observers/users_group_observer_spec.rb @@ -5,7 +5,7 @@ describe UsersGroupObserver do after(:each) { disable_observers } subject { UsersGroupObserver.instance } - before { subject.stub(notification: mock('NotificationService').as_null_object) } + before { subject.stub(notification: double('NotificationService').as_null_object) } describe "#after_create" do it "should send email to user" do @@ -23,5 +23,10 @@ describe UsersGroupObserver do subject.should_receive(:notification) @membership.update_attribute(:group_access, UsersGroup::MASTER) end + + it "does not send an email when the access level has not changed" do + subject.should_not_receive(:notification) + @membership.update_attribute(:group_access, UsersGroup::OWNER) + end end end diff --git a/spec/observers/users_project_observer_spec.rb b/spec/observers/users_project_observer_spec.rb index e33d8cc50fd6175ddb6d3908a3b9c224fcaa046b..dea90b2bfa7da16a54501c81db61be8b25468cc7 100644 --- a/spec/observers/users_project_observer_spec.rb +++ b/spec/observers/users_project_observer_spec.rb @@ -7,27 +7,7 @@ describe UsersProjectObserver do let(:user) { create(:user) } let(:project) { create(:project) } subject { UsersProjectObserver.instance } - before { subject.stub(notification: mock('NotificationService').as_null_object) } - - describe "#after_commit" do - it "should called when UsersProject created" do - subject.should_receive(:after_commit) - create(:users_project) - end - - it "should send email to user" do - subject.should_receive(:notification) - Event.stub(create: true) - - create(:users_project) - end - - it "should create new event" do - Event.should_receive(:create) - - create(:users_project) - end - end + before { subject.stub(notification: double('NotificationService').as_null_object) } describe "#after_update" do before do @@ -35,7 +15,7 @@ describe UsersProjectObserver do end it "should called when UsersProject updated" do - subject.should_receive(:after_commit) + subject.should_receive(:after_update) @users_project.update_attribute(:project_access, UsersProject::MASTER) end @@ -45,7 +25,7 @@ describe UsersProjectObserver do end it "should not called after UsersProject destroyed" do - subject.should_not_receive(:after_commit) + subject.should_not_receive(:after_update) @users_project.destroy end end @@ -65,4 +45,43 @@ describe UsersProjectObserver do @users_project.destroy end end + + describe "#after_create" do + context 'wiki_enabled creates repository directory' do + context 'wiki_enabled true creates wiki repository directory' do + before do + @project = create(:project, wiki_enabled: true) + @path = GollumWiki.new(@project, user).send(:path_to_repo) + end + + after do + FileUtils.rm_rf(@path) + end + + it { File.exists?(@path).should be_true } + end + + context 'wiki_enabled false does not create wiki repository directory' do + before do + @project = create(:project, wiki_enabled: false) + @path = GollumWiki.new(@project, user).send(:path_to_repo) + end + + it { File.exists?(@path).should be_false } + end + end + + it "should send email to user" do + subject.should_receive(:notification) + Event.stub(create: true) + + create(:users_project) + end + + it "should create new event" do + Event.should_receive(:create) + + create(:users_project) + end + end end diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..acef7df87771d75f5d0b4a65abd7c3e261cd6db9 --- /dev/null +++ b/spec/requests/api/files_spec.rb @@ -0,0 +1,115 @@ +require 'spec_helper' + +describe API::API do + include ApiHelpers + before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } + after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } + + let(:user) { create(:user) } + let!(:project) { create(:project, namespace: user.namespace ) } + before { project.team << [user, :developer] } + + describe "POST /projects/:id/repository/files" do + let(:valid_params) { + { + file_path: 'newfile.rb', + branch_name: 'master', + content: 'puts 8', + commit_message: 'Added newfile' + } + } + + it "should create a new file in project repo" do + Gitlab::Satellite::NewFileAction.any_instance.stub( + commit!: true, + ) + + post api("/projects/#{project.id}/repository/files", user), valid_params + response.status.should == 201 + json_response['file_path'].should == 'newfile.rb' + end + + it "should return a 400 bad request if no params given" do + post api("/projects/#{project.id}/repository/files", user) + response.status.should == 400 + end + + it "should return a 400 if satellite fails to create file" do + Gitlab::Satellite::NewFileAction.any_instance.stub( + commit!: false, + ) + + post api("/projects/#{project.id}/repository/files", user), valid_params + response.status.should == 400 + end + end + + describe "PUT /projects/:id/repository/files" do + let(:valid_params) { + { + file_path: 'spec/spec_helper.rb', + branch_name: 'master', + content: 'puts 8', + commit_message: 'Changed file' + } + } + + it "should update existing file in project repo" do + Gitlab::Satellite::EditFileAction.any_instance.stub( + commit!: true, + ) + + put api("/projects/#{project.id}/repository/files", user), valid_params + response.status.should == 200 + json_response['file_path'].should == 'spec/spec_helper.rb' + end + + it "should return a 400 bad request if no params given" do + put api("/projects/#{project.id}/repository/files", user) + response.status.should == 400 + end + + it "should return a 400 if satellite fails to create file" do + Gitlab::Satellite::EditFileAction.any_instance.stub( + commit!: false, + ) + + put api("/projects/#{project.id}/repository/files", user), valid_params + response.status.should == 400 + end + end + + describe "DELETE /projects/:id/repository/files" do + let(:valid_params) { + { + file_path: 'spec/spec_helper.rb', + branch_name: 'master', + commit_message: 'Changed file' + } + } + + it "should delete existing file in project repo" do + Gitlab::Satellite::DeleteFileAction.any_instance.stub( + commit!: true, + ) + + delete api("/projects/#{project.id}/repository/files", user), valid_params + response.status.should == 200 + json_response['file_path'].should == 'spec/spec_helper.rb' + end + + it "should return a 400 bad request if no params given" do + delete api("/projects/#{project.id}/repository/files", user) + response.status.should == 400 + end + + it "should return a 400 if satellite fails to create file" do + Gitlab::Satellite::DeleteFileAction.any_instance.stub( + commit!: false, + ) + + delete api("/projects/#{project.id}/repository/files", user), valid_params + response.status.should == 400 + end + end +end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index a6ce72e11e92a65c0892c8b2e03f433c90a6f588..1a5f11038b71fd3640331086db46053c89e9c3f4 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -106,10 +106,48 @@ describe API::API do end end + describe "DELETE /groups/:id" do + context "when authenticated as user" do + it "should remove group" do + delete api("/groups/#{group1.id}", user1) + response.status.should == 200 + end + + it "should not remove a group if not an owner" do + user3 = create(:user) + group1.add_user(user3, Gitlab::Access::MASTER) + delete api("/groups/#{group1.id}", user3) + response.status.should == 403 + end + + it "should not remove a non existing group" do + delete api("/groups/1328", user1) + response.status.should == 404 + end + + it "should not remove a group not attached to user1" do + delete api("/groups/#{group2.id}", user1) + response.status.should == 403 + end + end + + context "when authenticated as admin" do + it "should remove any existing group" do + delete api("/groups/#{group2.id}", admin) + response.status.should == 200 + end + + it "should not remove a non existing group" do + delete api("/groups/1328", admin) + response.status.should == 404 + end + end + end + describe "POST /groups/:id/projects/:project_id" do let(:project) { create(:project) } before(:each) do - project.stub!(:transfer).and_return(true) + project.stub(:transfer).and_return(true) Project.stub(:find).and_return(project) end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index e8870f4d5d8c83401e78d224f67f2a2cd0a78cd9..5f6dff92c0aac0f9c00e55f06c602deae227cdcb 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -103,6 +103,33 @@ describe API::API do end end + context "archived project" do + let(:personal_project) { create(:project, namespace: user.namespace) } + + before do + project.team << [user, :developer] + project.archive! + end + + context "git pull" do + it do + pull(key, project) + + response.status.should == 200 + response.body.should == 'true' + end + end + + context "git push" do + it do + push(key, project) + + response.status.should == 200 + response.body.should == 'false' + end + end + end + context "deploy key" do let(:key) { create(:deploy_key) } diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index a97d6a282a9bf55195ef6920146280b395f513a1..c55025d72b5fe12b50831b0021d22917774f492c 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -42,6 +42,7 @@ describe API::API do get api("/projects/#{project.id}/issues/#{issue.id}", user) response.status.should == 200 json_response['title'].should == issue.title + json_response['iid'].should == issue.iid end it "should return 404 if issue id not found" do @@ -99,4 +100,16 @@ describe API::API do response.status.should == 405 end end + + describe "PUT /projects/:id/issues/:issue_id to test observer on close" do + before { enable_observers } + after { disable_observers } + + it "should create an activity event when an issue is closed" do + Event.should_receive(:create) + + put api("/projects/#{project.id}/issues/#{issue.id}", user), + state_event: "close" + end + end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 2f11f562aa1c4898bf11071891ced6cc2553dc03..412b6c95ffaa7f250bddef97c088dd66a0e0109a 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -5,7 +5,7 @@ describe API::API do before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } let(:user) { create(:user) } - let!(:project) {create(:project_with_code, creator_id: user.id, namespace: user.namespace) } + let!(:project) {create(:project, creator_id: user.id, namespace: user.namespace) } let!(:merge_request) { create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, title: "Test") } before { project.team << [user, :reporters] @@ -34,6 +34,7 @@ describe API::API do get api("/projects/#{project.id}/merge_request/#{merge_request.id}", user) response.status.should == 200 json_response['title'].should == merge_request.title + json_response['iid'].should == merge_request.iid end it "should return a 404 error if merge_request_id not found" do @@ -46,32 +47,32 @@ describe API::API do context 'between branches projects' do it "should return merge_request" do post api("/projects/#{project.id}/merge_requests", user), - title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user + title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user response.status.should == 201 json_response['title'].should == 'Test merge_request' end it "should return 422 when source_branch equals target_branch" do post api("/projects/#{project.id}/merge_requests", user), - title: "Test merge_request", source_branch: "master", target_branch: "master", author: user + title: "Test merge_request", source_branch: "master", target_branch: "master", author: user response.status.should == 422 end it "should return 400 when source_branch is missing" do post api("/projects/#{project.id}/merge_requests", user), - title: "Test merge_request", target_branch: "master", author: user + title: "Test merge_request", target_branch: "master", author: user response.status.should == 400 end it "should return 400 when target_branch is missing" do post api("/projects/#{project.id}/merge_requests", user), - title: "Test merge_request", source_branch: "stable", author: user + title: "Test merge_request", source_branch: "stable", author: user response.status.should == 400 end it "should return 400 when title is missing" do post api("/projects/#{project.id}/merge_requests", user), - target_branch: 'master', source_branch: 'stable' + target_branch: 'master', source_branch: 'stable' response.status.should == 400 end end @@ -79,8 +80,8 @@ describe API::API do context 'forked projects' do let!(:user2) {create(:user)} let!(:forked_project_link) { build(:forked_project_link) } - let!(:fork_project) { create(:source_project_with_code, forked_project_link: forked_project_link, namespace: user2.namespace, creator_id: user2.id) } - let!(:unrelated_project) { create(:target_project_with_code, namespace: user2.namespace, creator_id: user2.id) } + let!(:fork_project) { create(:project, forked_project_link: forked_project_link, namespace: user2.namespace, creator_id: user2.id) } + let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) } before :each do |each| fork_project.team << [user2, :reporters] @@ -91,7 +92,7 @@ describe API::API do it "should return merge_request" do post api("/projects/#{fork_project.id}/merge_requests", user2), - title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user2, target_project_id: project.id + title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user2, target_project_id: project.id response.status.should == 201 json_response['title'].should == 'Test merge_request' end @@ -101,44 +102,44 @@ describe API::API do fork_project.forked?.should be_true fork_project.forked_from_project.should == project post api("/projects/#{fork_project.id}/merge_requests", user2), - title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id + title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id response.status.should == 201 json_response['title'].should == 'Test merge_request' end it "should return 400 when source_branch is missing" do post api("/projects/#{fork_project.id}/merge_requests", user2), - title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id + title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id response.status.should == 400 end it "should return 400 when target_branch is missing" do post api("/projects/#{fork_project.id}/merge_requests", user2), - title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id + title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id response.status.should == 400 end it "should return 400 when title is missing" do post api("/projects/#{fork_project.id}/merge_requests", user2), - target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: project.id + target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: project.id response.status.should == 400 end it "should return 400 when target_branch is specified and not a forked project" do post api("/projects/#{project.id}/merge_requests", user), - title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user, target_project_id: fork_project.id + title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user, target_project_id: fork_project.id response.status.should == 400 end it "should return 400 when target_branch is specified and for a different fork" do post api("/projects/#{fork_project.id}/merge_requests", user2), - title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: unrelated_project.id + title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: unrelated_project.id response.status.should == 400 end it "should return 201 when target_branch is specified and for the same project" do post api("/projects/#{fork_project.id}/merge_requests", user2), - title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: fork_project.id + title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: fork_project.id response.status.should == 201 end end @@ -169,7 +170,7 @@ describe API::API do it "should return 422 when source_branch and target_branch are renamed the same" do put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), - source_branch: "master", target_branch: "master" + source_branch: "master", target_branch: "master" response.status.should == 422 end @@ -197,5 +198,4 @@ describe API::API do response.status.should == 404 end end - end diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index 246fe262ce86a21fb73e74a6786548a9a06dd259..febfc63921ea6773ec84ffcca6cfb612f5b52950 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -30,6 +30,7 @@ describe API::API do get api("/projects/#{project.id}/milestones/#{milestone.id}", user) response.status.should == 200 json_response['title'].should == milestone.title + json_response['iid'].should == milestone.iid end it "should return 401 error if user not authenticated" do @@ -89,4 +90,16 @@ describe API::API do json_response['state'].should == 'closed' end end + + describe "PUT /projects/:id/milestones/:milestone_id to test observer on close" do + before { enable_observers } + after { disable_observers } + + it "should create an activity event when an milestone is closed" do + Event.should_receive(:create) + + put api("/projects/#{project.id}/milestones/#{milestone.id}", user), + state_event: 'close' + end + end end diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..2b1a4bf6ec83b95a62a85dde3c20ddd90d107a52 --- /dev/null +++ b/spec/requests/api/namespaces_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe API::API do + include ApiHelpers + before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } + after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } + + let(:admin) { create(:admin) } + let!(:group1) { create(:group) } + let!(:group2) { create(:group) } + + describe "GET /namespaces" do + context "when unauthenticated" do + it "should return authentication error" do + get api("/namespaces") + response.status.should == 401 + end + end + + context "when authenticated as admin" do + it "admin: should return an array of all namespaces" do + get api("/namespaces", admin) + response.status.should == 200 + json_response.should be_an Array + + # Admin namespace + 2 group namespaces + json_response.length.should == 3 + end + end + end +end diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index ba18b123039c97912ef97eb2b3d8417bd4cab497..6ed96eb97f3658dbd8c03e7539f2529e5e69e362 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -176,4 +176,16 @@ describe API::API do end end end + + describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do + before { enable_observers } + after { disable_observers } + + it "should create an activity event when an issue note is created" do + Event.should_receive(:create) + + post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!' + end + end + end diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..c8ace0b9462619ba25593954769c1185ca30ee9d --- /dev/null +++ b/spec/requests/api/project_hooks_spec.rb @@ -0,0 +1,132 @@ +require 'spec_helper' + +describe API::API, 'ProjectHooks' do + include ApiHelpers + before(:each) { enable_observers } + after(:each) { disable_observers } + + let(:user) { create(:user) } + let(:user3) { create(:user) } + let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } + let!(:hook) { create(:project_hook, project: project, url: "http://example.com") } + + before do + project.team << [user, :master] + project.team << [user3, :developer] + end + + describe "GET /projects/:id/hooks" do + context "authorized user" do + it "should return project hooks" do + get api("/projects/#{project.id}/hooks", user) + response.status.should == 200 + + json_response.should be_an Array + json_response.count.should == 1 + json_response.first['url'].should == "http://example.com" + end + end + + context "unauthorized user" do + it "should not access project hooks" do + get api("/projects/#{project.id}/hooks", user3) + response.status.should == 403 + end + end + end + + describe "GET /projects/:id/hooks/:hook_id" do + context "authorized user" do + it "should return a project hook" do + get api("/projects/#{project.id}/hooks/#{hook.id}", user) + response.status.should == 200 + json_response['url'].should == hook.url + end + + it "should return a 404 error if hook id is not available" do + get api("/projects/#{project.id}/hooks/1234", user) + response.status.should == 404 + end + end + + context "unauthorized user" do + it "should not access an existing hook" do + get api("/projects/#{project.id}/hooks/#{hook.id}", user3) + response.status.should == 403 + end + end + + it "should return a 404 error if hook id is not available" do + get api("/projects/#{project.id}/hooks/1234", user) + response.status.should == 404 + end + end + + describe "POST /projects/:id/hooks" do + it "should add hook to project" do + expect { + post api("/projects/#{project.id}/hooks", user), + url: "http://example.com", issues_events: true + }.to change {project.hooks.count}.by(1) + response.status.should == 201 + end + + it "should return a 400 error if url not given" do + post api("/projects/#{project.id}/hooks", user) + response.status.should == 400 + end + + it "should return a 422 error if url not valid" do + post api("/projects/#{project.id}/hooks", user), "url" => "ftp://example.com" + response.status.should == 422 + end + end + + describe "PUT /projects/:id/hooks/:hook_id" do + it "should update an existing project hook" do + put api("/projects/#{project.id}/hooks/#{hook.id}", user), + url: 'http://example.org', push_events: false + response.status.should == 200 + json_response['url'].should == 'http://example.org' + end + + it "should return 404 error if hook id not found" do + put api("/projects/#{project.id}/hooks/1234", user), url: 'http://example.org' + response.status.should == 404 + end + + it "should return 400 error if url is not given" do + put api("/projects/#{project.id}/hooks/#{hook.id}", user) + response.status.should == 400 + end + + it "should return a 422 error if url is not valid" do + put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'ftp://example.com' + response.status.should == 422 + end + end + + describe "DELETE /projects/:id/hooks/:hook_id" do + it "should delete hook from project" do + expect { + delete api("/projects/#{project.id}/hooks/#{hook.id}", user) + }.to change {project.hooks.count}.by(-1) + response.status.should == 200 + end + + it "should return success when deleting hook" do + delete api("/projects/#{project.id}/hooks/#{hook.id}", user) + response.status.should == 200 + end + + it "should return success when deleting non existent hook" do + delete api("/projects/#{project.id}/hooks/42", user) + response.status.should == 200 + end + + it "should return a 405 error if hook id not given" do + delete api("/projects/#{project.id}/hooks", user) + response.status.should == 405 + end + end +end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index b8c0b6f33edf4c3180164fb541b1cc4321f35ea2..342587ba5d6086513ea417d482cc5c3507502a8b 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -9,15 +9,14 @@ describe API::API do let(:user2) { create(:user) } let(:user3) { create(:user) } let(:admin) { create(:admin) } - let!(:project) { create(:project_with_code, creator_id: user.id, namespace: user.namespace) } - let!(:hook) { create(:project_hook, project: project, url: "http://example.com") } - let!(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') } - let!(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) } - let!(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) } - - before { project.team << [user, :reporter] } + let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } + let(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') } + let(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) } + let(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) } describe "GET /projects" do + before { project } + context "when unauthenticated" do it "should return authentication error" do get api("/projects") @@ -36,6 +35,34 @@ describe API::API do end end + describe "GET /projects/all" do + before { project } + + context "when unauthenticated" do + it "should return authentication error" do + get api("/projects/all") + response.status.should == 401 + end + end + + context "when authenticated as regular user" do + it "should return authentication error" do + get api("/projects/all", user) + response.status.should == 403 + end + end + + context "when authenticated as admin" do + it "should return an array of all projects" do + get api("/projects/all", admin) + response.status.should == 200 + json_response.should be_an Array + json_response.first['name'].should == project.name + json_response.first['owner']['email'].should == user.email + end + end + end + describe "POST /projects" do context "maximum number of projects reached" do before do @@ -91,7 +118,6 @@ describe API::API do it "should assign attributes to project" do project = attributes_for(:project, { description: Faker::Lorem.sentence, - default_branch: 'stable', issues_enabled: false, wall_enabled: false, merge_requests_enabled: false, @@ -107,22 +133,50 @@ describe API::API do end it "should set a project as public" do + project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PUBLIC }) + post api("/projects", user), project + json_response['public'].should be_true + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC + end + + it "should set a project as public using :public" do project = attributes_for(:project, { public: true }) post api("/projects", user), project json_response['public'].should be_true + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC + end + it "should set a project as internal" do + project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::INTERNAL }) + post api("/projects", user), project + json_response['public'].should be_false + json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL end - it "should set a project as private" do - project = attributes_for(:project, { public: false }) + it "should set a project as internal overriding :public" do + project = attributes_for(:project, { public: true, visibility_level: Gitlab::VisibilityLevel::INTERNAL }) post api("/projects", user), project json_response['public'].should be_false + json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL + end + it "should set a project as private" do + project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PRIVATE }) + post api("/projects", user), project + json_response['public'].should be_false + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE end + it "should set a project as private using :public" do + project = attributes_for(:project, { public: false }) + post api("/projects", user), project + json_response['public'].should be_false + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE + end end describe "POST /projects/user/:id" do + before { project } before { admin } it "should create new project without path" do @@ -146,7 +200,6 @@ describe API::API do it "should assign attributes to project" do project = attributes_for(:project, { description: Faker::Lorem.sentence, - default_branch: 'stable', issues_enabled: false, wall_enabled: false, merge_requests_enabled: false, @@ -162,22 +215,51 @@ describe API::API do end it "should set a project as public" do + project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PUBLIC }) + post api("/projects/user/#{user.id}", admin), project + json_response['public'].should be_true + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC + end + + it "should set a project as public using :public" do project = attributes_for(:project, { public: true }) post api("/projects/user/#{user.id}", admin), project json_response['public'].should be_true + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC + end + it "should set a project as internal" do + project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::INTERNAL }) + post api("/projects/user/#{user.id}", admin), project + json_response['public'].should be_false + json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL end - it "should set a project as private" do - project = attributes_for(:project, { public: false }) + it "should set a project as internal overriding :public" do + project = attributes_for(:project, { public: true, visibility_level: Gitlab::VisibilityLevel::INTERNAL }) post api("/projects/user/#{user.id}", admin), project json_response['public'].should be_false + json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL + end + it "should set a project as private" do + project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PRIVATE }) + post api("/projects/user/#{user.id}", admin), project + json_response['public'].should be_false + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE end + it "should set a project as private using :public" do + project = attributes_for(:project, { public: false }) + post api("/projects/user/#{user.id}", admin), project + json_response['public'].should be_false + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE + end end describe "GET /projects/:id" do + before { project } + it "should return a project by id" do get api("/projects/#{project.id}", user) response.status.should == 200 @@ -205,6 +287,8 @@ describe API::API do end describe "GET /projects/:id/events" do + before { users_project } + it "should return a project events" do get api("/projects/#{project.id}/events", user) response.status.should == 200 @@ -228,6 +312,9 @@ describe API::API do end describe "GET /projects/:id/members" do + before { users_project } + before { users_project2 } + it "should return project team members" do get api("/projects/#{project.id}/members", user) response.status.should == 200 @@ -251,6 +338,8 @@ describe API::API do end describe "GET /projects/:id/members/:user_id" do + before { users_project } + it "should return project team member" do get api("/projects/#{project.id}/members/#{user.id}", user) response.status.should == 200 @@ -306,6 +395,8 @@ describe API::API do end describe "PUT /projects/:id/members/:user_id" do + before { users_project2 } + it "should update project team member" do put api("/projects/#{project.id}/members/#{user3.id}", user), access_level: UsersProject::MASTER response.status.should == 200 @@ -330,6 +421,9 @@ describe API::API do end describe "DELETE /projects/:id/members/:user_id" do + before { users_project } + before { users_project2 } + it "should remove user from project team" do expect { delete api("/projects/#{project.id}/members/#{user3.id}", user) @@ -348,9 +442,7 @@ describe API::API do delete api("/projects/#{project.id}/members/#{user3.id}", user) response.status.should == 200 end - end - describe "DELETE /projects/:id/members/:user_id" do it "should return 200 OK when the user was not member" do expect { delete api("/projects/#{project.id}/members/1000000", user) @@ -361,122 +453,9 @@ describe API::API do end end - describe "GET /projects/:id/hooks" do - context "authorized user" do - it "should return project hooks" do - get api("/projects/#{project.id}/hooks", user) - response.status.should == 200 - - json_response.should be_an Array - json_response.count.should == 1 - json_response.first['url'].should == "http://example.com" - end - end - - context "unauthorized user" do - it "should not access project hooks" do - get api("/projects/#{project.id}/hooks", user3) - response.status.should == 403 - end - end - end - - describe "GET /projects/:id/hooks/:hook_id" do - context "authorized user" do - it "should return a project hook" do - get api("/projects/#{project.id}/hooks/#{hook.id}", user) - response.status.should == 200 - json_response['url'].should == hook.url - end - - it "should return a 404 error if hook id is not available" do - get api("/projects/#{project.id}/hooks/1234", user) - response.status.should == 404 - end - end - - context "unauthorized user" do - it "should not access an existing hook" do - get api("/projects/#{project.id}/hooks/#{hook.id}", user3) - response.status.should == 403 - end - end - - it "should return a 404 error if hook id is not available" do - get api("/projects/#{project.id}/hooks/1234", user) - response.status.should == 404 - end - end - - describe "POST /projects/:id/hooks" do - it "should add hook to project" do - expect { - post api("/projects/#{project.id}/hooks", user), - url: "http://example.com" - }.to change {project.hooks.count}.by(1) - response.status.should == 201 - end - - it "should return a 400 error if url not given" do - post api("/projects/#{project.id}/hooks", user) - response.status.should == 400 - end - - it "should return a 422 error if url not valid" do - post api("/projects/#{project.id}/hooks", user), "url" => "ftp://example.com" - response.status.should == 422 - end - end - - describe "PUT /projects/:id/hooks/:hook_id" do - it "should update an existing project hook" do - put api("/projects/#{project.id}/hooks/#{hook.id}", user), - url: 'http://example.org' - response.status.should == 200 - json_response['url'].should == 'http://example.org' - end - - it "should return 404 error if hook id not found" do - put api("/projects/#{project.id}/hooks/1234", user), url: 'http://example.org' - response.status.should == 404 - end - - it "should return 400 error if url is not given" do - put api("/projects/#{project.id}/hooks/#{hook.id}", user) - response.status.should == 400 - end - - it "should return a 422 error if url is not valid" do - put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'ftp://example.com' - response.status.should == 422 - end - end - - describe "DELETE /projects/:id/hooks/:hook_id" do - it "should delete hook from project" do - expect { - delete api("/projects/#{project.id}/hooks/#{hook.id}", user) - }.to change {project.hooks.count}.by(-1) - response.status.should == 200 - end - - it "should return success when deleting hook" do - delete api("/projects/#{project.id}/hooks/#{hook.id}", user) - response.status.should == 200 - end - - it "should return success when deleting non existent hook" do - delete api("/projects/#{project.id}/hooks/42", user) - response.status.should == 200 - end - - it "should return a 405 error if hook id not given" do - delete api("/projects/#{project.id}/hooks", user) - response.status.should == 405 - end - end - describe "GET /projects/:id/snippets" do + before { snippet } + it "should return an array of project snippets" do get api("/projects/#{project.id}/snippets", user) response.status.should == 200 @@ -543,6 +522,8 @@ describe API::API do end describe "DELETE /projects/:id/snippets/:snippet_id" do + before { snippet } + it "should delete existing project snippet" do expect { delete api("/projects/#{project.id}/snippets/#{snippet.id}", user) @@ -628,10 +609,10 @@ describe API::API do describe :fork_admin do let(:project_fork_target) { create(:project) } - let(:project_fork_source) { create(:project, public: true) } + let(:project_fork_source) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } describe "POST /projects/:id/fork/:forked_from_id" do - let(:new_project_fork_source) { create(:project, public: true) } + let(:new_project_fork_source) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } it "shouldn't available for non admin users" do post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user) @@ -695,13 +676,15 @@ describe API::API do describe "GET /projects/search/:query" do let!(:query) { 'query'} - let!(:search) { create(:project, name: query, creator_id: user.id, namespace: user.namespace) } - let!(:pre) { create(:project, name: "pre_#{query}", creator_id: user.id, namespace: user.namespace) } - let!(:post) { create(:project, name: "#{query}_post", creator_id: user.id, namespace: user.namespace) } - let!(:pre_post) { create(:project, name: "pre_#{query}_post", creator_id: user.id, namespace: user.namespace) } - let!(:unfound) { create(:project, name: 'unfound', creator_id: user.id, namespace: user.namespace) } - let!(:public) { create(:project, name: "another #{query}",public: true) } - let!(:unfound_public) { create(:project, name: 'unfound public', public: true) } + let!(:search) { create(:empty_project, name: query, creator_id: user.id, namespace: user.namespace) } + let!(:pre) { create(:empty_project, name: "pre_#{query}", creator_id: user.id, namespace: user.namespace) } + let!(:post) { create(:empty_project, name: "#{query}_post", creator_id: user.id, namespace: user.namespace) } + let!(:pre_post) { create(:empty_project, name: "pre_#{query}_post", creator_id: user.id, namespace: user.namespace) } + let!(:unfound) { create(:empty_project, name: 'unfound', creator_id: user.id, namespace: user.namespace) } + let!(:internal) { create(:empty_project, name: "internal #{query}", visibility_level: Gitlab::VisibilityLevel::INTERNAL) } + let!(:unfound_internal) { create(:empty_project, name: 'unfound internal', visibility_level: Gitlab::VisibilityLevel::INTERNAL) } + let!(:public) { create(:empty_project, name: "public #{query}", visibility_level: Gitlab::VisibilityLevel::PUBLIC) } + let!(:unfound_public) { create(:empty_project, name: 'unfound public', visibility_level: Gitlab::VisibilityLevel::PUBLIC) } context "when unauthenticated" do it "should return authentication error" do @@ -715,7 +698,7 @@ describe API::API do get api("/projects/search/#{query}",user) response.status.should == 200 json_response.should be_an Array - json_response.size.should == 5 + json_response.size.should == 6 json_response.each {|project| project['name'].should =~ /.*query.*/} end end @@ -725,8 +708,46 @@ describe API::API do get api("/projects/search/#{query}", user2) response.status.should == 200 json_response.should be_an Array - json_response.size.should == 1 - json_response.first['name'].should == "another #{query}" + json_response.size.should == 2 + json_response.each {|project| project['name'].should =~ /(internal|public) query/} + end + end + end + + describe "DELETE /projects/:id" do + context "when authenticated as user" do + it "should remove project" do + delete api("/projects/#{project.id}", user) + response.status.should == 200 + end + + it "should not remove a project if not an owner" do + user3 = create(:user) + project.team << [user3, :developer] + delete api("/projects/#{project.id}", user3) + response.status.should == 403 + end + + it "should not remove a non existing project" do + delete api("/projects/1328", user) + response.status.should == 404 + end + + it "should not remove a project not attached to user" do + delete api("/projects/#{project.id}", user2) + response.status.should == 404 + end + end + + context "when authenticated as admin" do + it "should remove any existing project" do + delete api("/projects/#{project.id}", admin) + response.status.should == 200 + end + + it "should not remove a non existing project" do + delete api("/projects/1328", admin) + response.status.should == 404 end end end diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 2e509ea2933ccedf42b7ec925a591386fa0fa8bc..4700872825274412a3df756fcfc5277d4162f913 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'mime/types' describe API::API do include ApiHelpers @@ -7,7 +8,7 @@ describe API::API do let(:user) { create(:user) } let(:user2) { create(:user) } - let!(:project) { create(:project_with_code, creator_id: user.id) } + let!(:project) { create(:project, creator_id: user.id) } let!(:master) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) } let!(:guest) { create(:users_project, user: user2, project: project, project_access: UsersProject::GUEST) } @@ -225,4 +226,41 @@ describe API::API do end end + describe "GET /projects/:id/repository/raw_blobs/:sha" do + it "should get the raw file contents" do + get api("/projects/#{project.id}/repository/raw_blobs/d1aff2896d99d7acc4d9780fbb716b113c45ecf7", user) + response.status.should == 200 + end + end + + describe "GET /projects/:id/repository/archive(.:format)?:sha" do + it "should get the archive" do + get api("/projects/#{project.id}/repository/archive", user) + repo_name = project.repository.name.gsub("\.git", "") + response.status.should == 200 + response.headers['Content-Disposition'].should =~ /filename\=\"#{repo_name}\-[^\.]+\.tar.gz\"/ + response.content_type.should == MIME::Types.type_for('file.tar.gz').first.content_type + end + + it "should get the archive.zip" do + get api("/projects/#{project.id}/repository/archive.zip", user) + repo_name = project.repository.name.gsub("\.git", "") + response.status.should == 200 + response.headers['Content-Disposition'].should =~ /filename\=\"#{repo_name}\-[^\.]+\.zip\"/ + response.content_type.should == MIME::Types.type_for('file.zip').first.content_type + end + + it "should get the archive.tar.bz2" do + get api("/projects/#{project.id}/repository/archive.tar.bz2", user) + repo_name = project.repository.name.gsub("\.git", "") + response.status.should == 200 + response.headers['Content-Disposition'].should =~ /filename\=\"#{repo_name}\-[^\.]+\.tar.bz2\"/ + response.content_type.should == MIME::Types.type_for('file.tar.bz2').first.content_type + end + + it "should return 404 for invalid sha" do + get api("/projects/#{project.id}/repository/archive/?sha=xxx", user) + response.status.should == 404 + end + end end diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..aecd18bc14a4e45d3a8051e177277b468ba1fc87 --- /dev/null +++ b/spec/requests/api/services_spec.rb @@ -0,0 +1,33 @@ +require "spec_helper" + +describe API::API do + include ApiHelpers + before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } + after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } + + let(:user) { create(:user) } + let(:project) {create(:project, creator_id: user.id, namespace: user.namespace) } + + describe "POST /projects/:id/services/gitlab-ci" do + it "should update gitlab-ci settings" do + put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'secret-token', project_url: "http://ci.example.com/projects/1" + + response.status.should == 200 + end + + it "should return if required fields missing" do + put api("/projects/#{project.id}/services/gitlab-ci", user), project_url: "http://ci.example.com/projects/1", active: true + + response.status.should == 400 + end + end + + describe "DELETE /projects/:id/services/gitlab-ci" do + it "should update gitlab-ci settings" do + delete api("/projects/#{project.id}/services/gitlab-ci", user) + + response.status.should == 200 + project.gitlab_ci_service.should be_nil + end + end +end diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb index 0fd90c567e060be81786ce06d24e077f8926d4b1..668007dc29fc94322090c6e95f43c441ef9239b6 100644 --- a/spec/requests/api/session_spec.rb +++ b/spec/requests/api/session_spec.rb @@ -8,7 +8,7 @@ describe API::API do describe "POST /session" do context "when valid password" do it "should return private token" do - post api("/session"), email: user.email, password: '123456' + post api("/session"), email: user.email, password: '12345678' response.status.should == 201 json_response['email'].should == user.email diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 4ef78b8e5d023d537691dd223b27dc7b23c3193f..c4be510200202350c2729aa006ac08c290e0f002 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -93,7 +93,7 @@ describe API::API do expect { post api("/users", admin), attr }.to change { User.count }.by(1) - user = User.find_by_username(attr[:username]) + user = User.find_by(username: attr[:username]) user.projects_limit.should == limit user.theme_id.should == Gitlab::Theme::MARS Gitlab.config.gitlab.unstub(:default_projects_limit) diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 20c04e94c24704339da8c0e1bcbe887cd168275c..97f7392e50aae6a9fa973d06abc3a4ce5335a0cb 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -130,6 +130,14 @@ describe Projects::RepositoriesController, "routing" do get("/gitlab/gitlabhq/repository/archive").should route_to('projects/repositories#archive', project_id: 'gitlab/gitlabhq') end + it "to #archive format:zip" do + get("/gitlab/gitlabhq/repository/archive.zip").should route_to('projects/repositories#archive', project_id: 'gitlab/gitlabhq', format: 'zip') + end + + it "to #archive format:tar.bz2" do + get("/gitlab/gitlabhq/repository/archive.tar.bz2").should route_to('projects/repositories#archive', project_id: 'gitlab/gitlabhq', format: 'tar.bz2') + end + it "to #show" do get("/gitlab/gitlabhq/repository").should route_to('projects/repositories#show', project_id: 'gitlab/gitlabhq') end @@ -138,12 +146,24 @@ end describe Projects::BranchesController, "routing" do it "to #branches" do get("/gitlab/gitlabhq/branches").should route_to('projects/branches#index', project_id: 'gitlab/gitlabhq') + delete("/gitlab/gitlabhq/branches/feature%2345").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45') + delete("/gitlab/gitlabhq/branches/feature%2B45").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45') + delete("/gitlab/gitlabhq/branches/feature@45").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45') + delete("/gitlab/gitlabhq/branches/feature%2345/foo/bar/baz").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45/foo/bar/baz') + delete("/gitlab/gitlabhq/branches/feature%2B45/foo/bar/baz").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45/foo/bar/baz') + delete("/gitlab/gitlabhq/branches/feature@45/foo/bar/baz").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45/foo/bar/baz') end end describe Projects::TagsController, "routing" do it "to #tags" do get("/gitlab/gitlabhq/tags").should route_to('projects/tags#index', project_id: 'gitlab/gitlabhq') + delete("/gitlab/gitlabhq/tags/feature%2345").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45') + delete("/gitlab/gitlabhq/tags/feature%2B45").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45') + delete("/gitlab/gitlabhq/tags/feature@45").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45') + delete("/gitlab/gitlabhq/tags/feature%2345/foo/bar/baz").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45/foo/bar/baz') + delete("/gitlab/gitlabhq/tags/feature%2B45/foo/bar/baz").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45/foo/bar/baz') + delete("/gitlab/gitlabhq/tags/feature@45/foo/bar/baz").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45/foo/bar/baz') end end @@ -183,9 +203,11 @@ describe Projects::RefsController, "routing" do get("/gitlab/gitlabhq/refs/stable/logs_tree").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable') get("/gitlab/gitlabhq/refs/feature%2345/logs_tree").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature#45') get("/gitlab/gitlabhq/refs/feature%2B45/logs_tree").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature+45') + get("/gitlab/gitlabhq/refs/feature@45/logs_tree").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature@45') get("/gitlab/gitlabhq/refs/stable/logs_tree/foo/bar/baz").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable', path: 'foo/bar/baz') get("/gitlab/gitlabhq/refs/feature%2345/logs_tree/foo/bar/baz").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature#45', path: 'foo/bar/baz') get("/gitlab/gitlabhq/refs/feature%2B45/logs_tree/foo/bar/baz").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature+45', path: 'foo/bar/baz') + get("/gitlab/gitlabhq/refs/feature@45/logs_tree/foo/bar/baz").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature@45', path: 'foo/bar/baz') get("/gitlab/gitlabhq/refs/stable/logs_tree/files.scss").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable', path: 'files.scss') end end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 946ef7c28cb18de515088701767f9aa4d0cbaa39..bcbf37e2eeb46be9f4d9f8b280e2a9ae651e6e3d 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -111,13 +111,6 @@ describe HelpController, "routing" do end end -# errors_githost GET /errors/githost(.:format) errors#githost -describe ErrorsController, "routing" do - it "to #githost" do - get("/errors/githost").should route_to('errors#githost') - end -end - # profile_account GET /profile/account(.:format) profile#account # profile_history GET /profile/history(.:format) profile#history # profile_password PUT /profile/password(.:format) profile#password_update @@ -128,7 +121,7 @@ end # profile_update PUT /profile/update(.:format) profile#update describe ProfilesController, "routing" do it "to #account" do - get("/profile/account").should route_to('profiles#account') + get("/profile/account").should route_to('profiles/accounts#show') end it "to #history" do @@ -185,6 +178,13 @@ describe Profiles::KeysController, "routing" do end end +# profile_avatar DELETE /profile/avatar(.:format) profiles/avatars#destroy +describe Profiles::AvatarsController, "routing" do + it "to #destroy" do + delete("/profile/avatar").should route_to('profiles/avatars#destroy') + end +end + # dashboard GET /dashboard(.:format) dashboard#show # dashboard_issues GET /dashboard/issues(.:format) dashboard#issues # dashboard_merge_requests GET /dashboard/merge_requests(.:format) dashboard#merge_requests diff --git a/spec/seed_project.tar.gz b/spec/seed_project.tar.gz index 3ffafed18d0f47aa0d89e24b5a8a7321bc7210a2..ee8c3f23c7019111d15bd62b8ed000229a6fa98e 100644 Binary files a/spec/seed_project.tar.gz and b/spec/seed_project.tar.gz differ diff --git a/spec/services/filtering_service_spec.rb b/spec/services/filtering_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..596601264b3a7f6a052694dba9c6fbdd93273e13 --- /dev/null +++ b/spec/services/filtering_service_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe FilteringService do + let(:user) { create :user } + let(:user2) { create :user } + let(:project1) { create(:project) } + let(:project2) { create(:project) } + let(:merge_request1) { create(:merge_request, author: user, source_project: project1, target_project: project2) } + let(:merge_request2) { create(:merge_request, author: user, source_project: project2, target_project: project1) } + let(:merge_request3) { create(:merge_request, author: user, source_project: project2, target_project: project2) } + let(:issue1) { create(:issue, assignee: user, project: project1) } + let(:issue2) { create(:issue, assignee: user, project: project2) } + let(:issue3) { create(:issue, assignee: user2, project: project2) } + + before do + project1.team << [user, :master] + project2.team << [user, :developer] + end + + describe 'merge requests' do + before :each do + merge_request1 + merge_request2 + merge_request3 + end + + it 'should filter by scope' do + params = { scope: 'authored', state: 'opened' } + merge_requests = FilteringService.new.execute(MergeRequest, user, params) + merge_requests.size.should == 3 + end + + it 'should filter by project' do + params = { project_id: project1.id, scope: 'authored', state: 'opened' } + merge_requests = FilteringService.new.execute(MergeRequest, user, params) + merge_requests.size.should == 1 + end + end + + describe 'issues' do + before :each do + issue1 + issue2 + issue3 + end + + it 'should filter by all' do + params = { scope: "all", state: 'opened' } + issues = FilteringService.new.execute(Issue, user, params) + issues.size.should == 3 + end + + it 'should filter by assignee' do + params = { scope: "assigned-to-me", state: 'opened' } + issues = FilteringService.new.execute(Issue, 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 = FilteringService.new.execute(Issue, user, params) + issues.size.should == 1 + end + end +end diff --git a/spec/contexts/fork_context_spec.rb b/spec/services/fork_service_spec.rb similarity index 92% rename from spec/contexts/fork_context_spec.rb rename to spec/services/fork_service_spec.rb index ed51b0c3f8e7b8fabeb97cc254d1cb9aec7b5baf..b6573095dbd2a06746d6c2ee00d3296218dbdd4d 100644 --- a/spec/contexts/fork_context_spec.rb +++ b/spec/services/fork_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Projects::ForkContext do +describe Projects::ForkService do describe :fork_by_user do before do @from_namespace = create(:namespace) @@ -47,8 +47,8 @@ describe Projects::ForkContext do end def fork_project(from_project, user, fork_success = true) - context = Projects::ForkContext.new(from_project, user) - shell = mock("gitlab_shell") + context = Projects::ForkService.new(from_project, user) + shell = double("gitlab_shell") shell.stub(fork_repository: fork_success) context.stub(gitlab_shell: shell) context.execute diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index e3c25fa046985d163a1c9624f1208e668edda21c..90738c681fa0a9d4328d9ad5078e6cb0712eb4ba 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe GitPushService do let (:user) { create :user } - let (:project) { create :project_with_code } + let (:project) { create :project } let (:service) { GitPushService.new } before do @@ -26,6 +26,7 @@ describe GitPushService do it { should include(ref: @ref) } it { should include(user_id: user.id) } it { should include(user_name: user.name) } + it { should include(project_id: project.id) } context "with repository data" do subject { @push_data[:repository] } @@ -73,38 +74,19 @@ describe GitPushService do end describe "Web Hooks" do - context "with web hooks" do - before do - @project_hook = create(:project_hook) - @project_hook_2 = create(:project_hook) - project.hooks << [@project_hook, @project_hook_2] - - stub_request(:post, @project_hook.url) - stub_request(:post, @project_hook_2.url) - end - - it "executes multiple web hook" do - @project_hook.should_receive(:async_execute).once - @project_hook_2.should_receive(:async_execute).once - - service.execute(project, user, @oldrev, @newrev, @ref) - end - end - context "execute web hooks" do - before do - @project_hook = create(:project_hook) - project.hooks << [@project_hook] - stub_request(:post, @project_hook.url) - end - it "when pushing a branch for the first time" do - @project_hook.should_receive(:async_execute) + project.should_receive(:execute_hooks) service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master') end + it "when pushing new commits to existing branch" do + project.should_receive(:execute_hooks) + service.execute(project, user, 'oldrev', 'newrev', 'refs/heads/master') + end + it "when pushing tags" do - @project_hook.should_not_receive(:async_execute) + project.should_not_receive(:execute_hooks) service.execute(project, user, 'newrev', 'newrev', 'refs/tags/v1.0.0') end end diff --git a/spec/contexts/issues/bulk_update_context_spec.rb b/spec/services/issues/bulk_update_context_spec.rb similarity index 85% rename from spec/contexts/issues/bulk_update_context_spec.rb rename to spec/services/issues/bulk_update_context_spec.rb index 058e43ba0905accdbdfc24b69cc6d200ff53a53a..548109a845030c7d651c0b024fbe3d1a0c890abc 100644 --- a/spec/contexts/issues/bulk_update_context_spec.rb +++ b/spec/services/issues/bulk_update_context_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Issues::BulkUpdateContext do +describe Issues::BulkUpdateService do before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } @@ -14,7 +14,7 @@ describe Issues::BulkUpdateContext do name: "GitLab", namespace: @user.namespace } - @project = Projects::CreateContext.new(@user, opts).execute + @project = Projects::CreateService.new(@user, opts).execute end describe :close_issue do @@ -32,7 +32,7 @@ describe Issues::BulkUpdateContext do end it { - result = Issues::BulkUpdateContext.new(@project, @user, @params).execute + result = Issues::BulkUpdateService.new(@project, @user, @params).execute result[:success].should be_true result[:count].should == @issues.count @@ -57,7 +57,7 @@ describe Issues::BulkUpdateContext do end it { - result = Issues::BulkUpdateContext.new(@project, @user, @params).execute + result = Issues::BulkUpdateService.new(@project, @user, @params).execute result[:success].should be_true result[:count].should == @issues.count @@ -80,7 +80,7 @@ describe Issues::BulkUpdateContext do end it { - result = Issues::BulkUpdateContext.new(@project, @user, @params).execute + result = Issues::BulkUpdateService.new(@project, @user, @params).execute result[:success].should be_true result[:count].should == 1 @@ -102,7 +102,7 @@ describe Issues::BulkUpdateContext do end it { - result = Issues::BulkUpdateContext.new(@project, @user, @params).execute + result = Issues::BulkUpdateService.new(@project, @user, @params).execute result[:success].should be_true result[:count].should == 1 diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index a112835d4d004afb88ad68bbcbdda652e0dc0bb7..09a5debe1dcce5f0e29fab8ea9e4dba3337bb879 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -233,6 +233,31 @@ describe NotificationService do end end + describe 'Projects' do + let(:project) { create :project } + + before do + build_team(project) + end + + describe :project_was_moved do + it do + should_email(@u_watcher.id) + should_email(@u_participating.id) + should_not_email(@u_disabled.id) + notification.project_was_moved(project) + end + + def should_email(user_id) + Notify.should_receive(:project_was_moved_email).with(project.id, user_id) + end + + def should_not_email(user_id) + Notify.should_not_receive(:project_was_moved_email).with(project.id, user_id) + end + end + end + def build_team(project) @u_watcher = create(:user, notification_level: Notification::N_WATCH) @u_participating = create(:user, notification_level: Notification::N_PARTICIPATING) diff --git a/spec/services/projects_create_service_spec.rb b/spec/services/projects_create_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..0a41832a2111ebf88fac9fd212d8eb0b2da19cc3 --- /dev/null +++ b/spec/services/projects_create_service_spec.rb @@ -0,0 +1,142 @@ +require 'spec_helper' + +describe Projects::CreateService do + before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } + after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } + + describe :create_by_user do + before do + @user = create :user + @admin = create :user, admin: true + @opts = { + name: "GitLab", + namespace: @user.namespace + } + end + + context 'user namespace' do + before do + @project = create_project(@user, @opts) + end + + it { @project.should be_valid } + it { @project.owner.should == @user } + it { @project.namespace.should == @user.namespace } + end + + context 'group namespace' do + before do + @group = create :group + @group.add_owner(@user) + + @opts.merge!(namespace_id: @group.id) + @project = create_project(@user, @opts) + end + + it { @project.should be_valid } + it { @project.owner.should == @group } + it { @project.namespace.should == @group } + end + + context 'respect configured visibility setting' do + before(:each) do + @settings = double("settings") + @settings.stub(:issues) { true } + @settings.stub(:merge_requests) { true } + @settings.stub(:wiki) { true } + @settings.stub(:wall) { true } + @settings.stub(:snippets) { true } + stub_const("Settings", Class.new) + @restrictions = double("restrictions") + @restrictions.stub(:restricted_visibility_levels) { [] } + Settings.stub_chain(:gitlab).and_return(@restrictions) + Settings.stub_chain(:gitlab, :default_projects_features).and_return(@settings) + end + + context 'should be public when setting is public' do + before do + @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC } + @project = create_project(@user, @opts) + end + + it { @project.public?.should be_true } + end + + context 'should be private when setting is private' do + before do + @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE } + @project = create_project(@user, @opts) + end + + it { @project.private?.should be_true } + end + + context 'should be internal when setting is internal' do + before do + @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::INTERNAL } + @project = create_project(@user, @opts) + end + + it { @project.internal?.should be_true } + end + end + + context 'respect configured visibility restrictions setting' do + before(:each) do + @settings = double("settings") + @settings.stub(:issues) { true } + @settings.stub(:merge_requests) { true } + @settings.stub(:wiki) { true } + @settings.stub(:wall) { true } + @settings.stub(:snippets) { true } + @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE } + stub_const("Settings", Class.new) + @restrictions = double("restrictions") + @restrictions.stub(:restricted_visibility_levels) { [ Gitlab::VisibilityLevel::PUBLIC ] } + Settings.stub_chain(:gitlab).and_return(@restrictions) + Settings.stub_chain(:gitlab, :default_projects_features).and_return(@settings) + end + + context 'should be private when option is public' do + before do + @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + @project = create_project(@user, @opts) + end + + it { @project.private?.should be_true } + end + + context 'should be public when option is public for admin' do + before do + @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + @project = create_project(@admin, @opts) + end + + it { @project.public?.should be_true } + end + + context 'should be private when option is private' do + before do + @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + @project = create_project(@user, @opts) + end + + it { @project.private?.should be_true } + end + + context 'should be internal when option is internal' do + before do + @opts.merge!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) + @project = create_project(@user, @opts) + end + + it { @project.internal?.should be_true } + end + end + end + + def create_project(user, opts) + Projects::CreateService.new(user, opts).execute + end +end + diff --git a/spec/services/projects_update_service_spec.rb b/spec/services/projects_update_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..1854c0d82339e9df02f3eac242fd8e7e1ef92fef --- /dev/null +++ b/spec/services/projects_update_service_spec.rb @@ -0,0 +1,111 @@ +require 'spec_helper' + +describe Projects::UpdateService do + before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } + after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } + + describe :update_by_user do + before do + @user = create :user + @admin = create :user, admin: true + @project = create :project, creator_id: @user.id, namespace: @user.namespace + @opts = { project: {} } + end + + context 'should be private when updated to private' do + before do + @created_private = @project.private? + + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + update_project(@project, @user, @opts) + end + + it { @created_private.should be_true } + it { @project.private?.should be_true } + end + + context 'should be internal when updated to internal' do + before do + @created_private = @project.private? + + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) + update_project(@project, @user, @opts) + end + + it { @created_private.should be_true } + it { @project.internal?.should be_true } + end + + context 'should be public when updated to public' do + before do + @created_private = @project.private? + + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + update_project(@project, @user, @opts) + end + + it { @created_private.should be_true } + it { @project.public?.should be_true } + end + + context 'respect configured visibility restrictions setting' do + before(:each) do + @restrictions = double("restrictions") + @restrictions.stub(:restricted_visibility_levels) { [ Gitlab::VisibilityLevel::PUBLIC ] } + Settings.stub_chain(:gitlab).and_return(@restrictions) + end + + context 'should be private when updated to private' do + before do + @created_private = @project.private? + + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + update_project(@project, @user, @opts) + end + + it { @created_private.should be_true } + it { @project.private?.should be_true } + end + + context 'should be internal when updated to internal' do + before do + @created_private = @project.private? + + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) + update_project(@project, @user, @opts) + end + + it { @created_private.should be_true } + it { @project.internal?.should be_true } + end + + context 'should be private when updated to public' do + before do + @created_private = @project.private? + + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + update_project(@project, @user, @opts) + end + + it { @created_private.should be_true } + it { @project.private?.should be_true } + end + + context 'should be public when updated to public by admin' do + before do + @created_private = @project.private? + + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + update_project(@project, @admin, @opts) + end + + it { @created_private.should be_true } + it { @project.public?.should be_true } + end + end + end + + def update_project(project, user, opts) + Projects::UpdateService.new(project, user, opts).execute + end +end diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..457cb3c0ca36a958e740c818620c63cdd0306378 --- /dev/null +++ b/spec/services/search_service_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe 'Search::GlobalService' do + let(:found_namespace) { create(:namespace, name: 'searchable namespace', path:'another_thing') } + let(:user) { create(:user, namespace: found_namespace) } + let!(:found_project) { create(:project, name: 'searchable_project', creator_id: user.id, namespace: found_namespace, visibility_level: Gitlab::VisibilityLevel::PRIVATE) } + + let(:unfound_namespace) { create(:namespace, name: 'unfound namespace', path: 'yet_something_else') } + let!(:unfound_project) { create(:project, name: 'unfound_project', creator_id: user.id, namespace: unfound_namespace, visibility_level: Gitlab::VisibilityLevel::PRIVATE) } + + let(:internal_namespace) { create(:namespace, path: 'something_internal',name: 'searchable internal namespace') } + let(:internal_user) { create(:user, namespace: internal_namespace) } + let!(:internal_project) { create(:project, name: 'searchable_internal_project', creator_id: internal_user.id, namespace: internal_namespace, visibility_level: Gitlab::VisibilityLevel::INTERNAL) } + + let(:public_namespace) { create(:namespace, path: 'something_public',name: 'searchable public namespace') } + let(:public_user) { create(:user, namespace: public_namespace) } + let!(:public_project) { create(:project, name: 'searchable_public_project', creator_id: public_user.id, namespace: public_namespace, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } + + describe '#execute' do + context 'unauthenticated' do + it 'should return public projects only' do + context = Search::GlobalService.new(nil, search: "searchable") + results = context.execute + results[:projects].should have(1).items + results[:projects].should include(public_project) + end + end + + context 'authenticated' do + it 'should return public, internal and private projects' do + context = Search::GlobalService.new(user, search: "searchable") + results = context.execute + results[:projects].should have(3).items + results[:projects].should include(public_project) + results[:projects].should include(found_project) + results[:projects].should include(internal_project) + end + + it 'should return only public & internal projects' do + context = Search::GlobalService.new(internal_user, search: "searchable") + results = context.execute + results[:projects].should have(2).items + results[:projects].should include(internal_project) + results[:projects].should include(public_project) + end + + it 'namespace name should be searchable' do + context = Search::GlobalService.new(user, search: "searchable namespace") + results = context.execute + results[:projects].should == [found_project] + end + end + end +end diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index 7f1590f559ea097540c86f8be1a37bdf586f4d4a..f1df7e55dd0a5f13bb8d9962ec05c8a68fe7f043 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -5,37 +5,29 @@ describe SystemHooksService do let (:project) { create :project } let (:users_project) { create :users_project } - context 'it should build event data' do - it 'should build event data for user' do - SystemHooksService.build_event_data(user, :create).should include(:event_name, :name, :created_at, :email) - end - - it 'should build event data for project' do - SystemHooksService.build_event_data(project, :create).should include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email) - end - - it 'should build event data for users project' do - SystemHooksService.build_event_data(users_project, :create).should include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :project_access) - end + context 'event data' do + it { event_data(user, :create).should include(:event_name, :name, :created_at, :email, :user_id) } + it { event_data(user, :destroy).should include(:event_name, :name, :created_at, :email, :user_id) } + it { event_data(project, :create).should include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email) } + it { event_data(project, :destroy).should include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email) } + it { event_data(users_project, :create).should include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :project_access) } + it { event_data(users_project, :destroy).should include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :project_access) } end - context 'it should build event names' do - it 'should build event names for user' do - SystemHooksService.build_event_name(user, :create).should eq "user_create" - - SystemHooksService.build_event_name(user, :destroy).should eq "user_destroy" - end - - it 'should build event names for project' do - SystemHooksService.build_event_name(project, :create).should eq "project_create" - - SystemHooksService.build_event_name(project, :destroy).should eq "project_destroy" - end + context 'event names' do + it { event_name(user, :create).should eq "user_create" } + it { event_name(user, :destroy).should eq "user_destroy" } + it { event_name(project, :create).should eq "project_create" } + it { event_name(project, :destroy).should eq "project_destroy" } + it { event_name(users_project, :create).should eq "user_add_to_team" } + it { event_name(users_project, :destroy).should eq "user_remove_from_team" } + end - it 'should build event names for users project' do - SystemHooksService.build_event_name(users_project, :create).should eq "user_add_to_team" + def event_data(*args) + SystemHooksService.new.send :build_event_data, *args + end - SystemHooksService.build_event_name(users_project, :destroy).should eq "user_remove_from_team" - end + def event_name(*args) + SystemHooksService.new.send :build_event_name, *args end end diff --git a/spec/services/test_hook_service_spec.rb b/spec/services/test_hook_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..76af5bf7b88c9071e38bcad0f49d26b6bcdb40ec --- /dev/null +++ b/spec/services/test_hook_service_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe TestHookService do + let (:user) { create :user } + let (:project) { create :project } + let (:hook) { create :project_hook, project: project } + + describe :execute do + it "should execute successfully" do + stub_request(:post, hook.url).to_return(status: 200) + TestHookService.new.execute(hook, user).should be_true + end + end +end diff --git a/spec/support/chosen_helper.rb b/spec/support/chosen_helper.rb deleted file mode 100644 index 42c9342c77a6aa1937dbb493fd908c7196365215..0000000000000000000000000000000000000000 --- a/spec/support/chosen_helper.rb +++ /dev/null @@ -1,21 +0,0 @@ -# Chosen programmatic helper -# It allows you to select value from chosen select -# -# Params -# value - real value of selected item -# opts - options containing css selector -# -# Usage: -# -# chosen(2, from: '#user_ids') -# - -module ChosenHelper - def chosen(value, options={}) - raise "Must pass a hash containing 'from'" if not options.is_a?(Hash) or not options.has_key?(:from) - - selector = options[:from] - - page.execute_script("$('#{selector}').val('#{value}').trigger('chosen:updated');") - end -end diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index 025534a900d13c47c55195937aaf6eb2a6cefd36..cc0ec2f4e3d60d7484e03c23aaa0c542f86fd05d 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -16,7 +16,7 @@ module LoginHelpers def login_with(user) visit new_user_session_path fill_in "user_login", with: user.email - fill_in "user_password", with: "123456" + fill_in "user_password", with: "12345678" click_button "Sign in" Thread.current[:current_user] = user end diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index a7f189777c88c2ad80b8d71f977a1b77fe98e3d8..3802e94ecf0e26e99a6a0a93dd93462f8ec7a66c 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -11,8 +11,8 @@ def common_mentionable_setup let(:mentioned_issue) { create :issue, project: mproject } let(:other_issue) { create :issue, project: mproject } - let(:mentioned_mr) { create :merge_request, target_project: mproject, source_branch: 'different' } - let(:mentioned_commit) { mock('commit', sha: '1234567890abcdef').as_null_object } + let(:mentioned_mr) { create :merge_request, source_project: mproject, source_branch: 'different' } + let(:mentioned_commit) { double('commit', sha: '1234567890abcdef').as_null_object } # Override to add known commits to the repository stub. let(:extra_commits) { [] } @@ -30,7 +30,7 @@ def common_mentionable_setup commitmap = { '123456' => mentioned_commit } extra_commits.each { |c| commitmap[c.sha[0..5]] = c } - repo = mock('repository') + repo = double('repository') repo.stub(:commit) { |sha| commitmap[sha] } mproject.stub(repository: repo) diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 203e661f514d435e4dae6f100316fa028dfddbac..43aec1cd43d0f4b8b996394b5a6d15c01ed7c291 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -45,6 +45,7 @@ module TestEnv def disable_mailer NotificationService.any_instance.stub(mailer: double.as_null_object) end + def enable_mailer NotificationService.any_instance.unstub(:mailer) end @@ -68,7 +69,12 @@ module TestEnv remove_repository: true, update_repository_head: true, add_key: true, - remove_key: true + remove_key: true, + version: '6.3.0' + ) + + Gitlab::Satellite::MergeAction.any_instance.stub( + merge!: true, ) Gitlab::Satellite::Satellite.any_instance.stub( @@ -84,6 +90,10 @@ module TestEnv Repository.any_instance.stub( size: 12.45 ) + + ActivityObserver.any_instance.stub( + current_user: double("current_user", id: 1) + ) end def clear_repo_dir(namespace, name) @@ -92,6 +102,15 @@ module TestEnv FileUtils.rm_rf File.join(testing_path(), "#{name}.wiki.git") end + def reset_satellite_dir + setup_stubs + FileUtils.cd(seed_satellite_path) do + `git reset --hard --quiet` + `git clean -fx` + `git checkout --quiet origin/master` + end + end + # Create a repo and it's satellite def create_repo(namespace, name) setup_stubs diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index cba243226db57c839f468a660ef6dfe19f52b7e9..a5541bee87698a66c1b18138b0a563baee5c779e 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -5,6 +5,7 @@ describe 'gitlab:app namespace rake task' do before :all do Rake.application.rake_require "tasks/gitlab/task_helpers" Rake.application.rake_require "tasks/gitlab/backup" + Rake.application.rake_require "tasks/gitlab/shell" # empty task as env is already loaded Rake::Task.define_task :environment end @@ -26,6 +27,9 @@ describe 'gitlab:app namespace rake task' do Dir.stub :chdir File.stub exists?: true Kernel.stub system: true + FileUtils.stub cp_r: true + FileUtils.stub mv: true + Rake::Task["gitlab:shell:setup"].stub invoke: true end let(:gitlab_version) { %x{git rev-parse HEAD}.gsub(/\n/,"") } @@ -39,7 +43,8 @@ describe 'gitlab:app namespace rake task' do YAML.stub load_file: {gitlab_version: gitlab_version} Rake::Task["gitlab:backup:db:restore"].should_receive :invoke Rake::Task["gitlab:backup:repo:restore"].should_receive :invoke - expect { run_rake_task }.to_not raise_error SystemExit + Rake::Task["gitlab:shell:setup"].should_receive :invoke + expect { run_rake_task }.to_not raise_error end end diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index 46e86dbe00aee86ddc83eb3c9313153feefc18c3..e6bf79b853c43290d51ffe6c57997ba7139753d8 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -9,7 +9,7 @@ describe PostReceive do end context "web hook" do - let(:project) { create(:project_with_code) } + let(:project) { create(:project) } let(:key) { create(:key, user: project.owner) } let(:key_id) { key.shell_id } @@ -19,7 +19,7 @@ describe PostReceive do end it "does not run if the author is not in the project" do - Key.stub(find_by_id: nil) + Key.stub(:find_by).with(hash_including(id: anything())) { nil } project.should_not_receive(:execute_hooks) diff --git a/vendor/assets/images/authbuttons/github_32.png b/vendor/assets/images/authbuttons/github_32.png index 247e52a5f4282110243afa350cd4a13b0154c2a1..c56eef05eb981c1e2361312355dceecac3a7b5a4 100644 Binary files a/vendor/assets/images/authbuttons/github_32.png and b/vendor/assets/images/authbuttons/github_32.png differ diff --git a/vendor/assets/images/authbuttons/github_64.png b/vendor/assets/images/authbuttons/github_64.png index fca7bf44652470a58edd12b410613656da95aed3..39de55bc7964dd263adfebcdd3f7260d7d7e21c0 100644 Binary files a/vendor/assets/images/authbuttons/github_64.png and b/vendor/assets/images/authbuttons/github_64.png differ diff --git a/vendor/assets/images/authbuttons/google_32.png b/vendor/assets/images/authbuttons/google_32.png index 3909e9de93b22e1f3a2e6c264f6289331412a3bb..6225cc9c2d77b8be6b060d212e1624b86935a46c 100644 Binary files a/vendor/assets/images/authbuttons/google_32.png and b/vendor/assets/images/authbuttons/google_32.png differ diff --git a/vendor/assets/images/authbuttons/google_64.png b/vendor/assets/images/authbuttons/google_64.png index e55f34f1b7d57c52172eb2bab0964037c532629b..4d608f710089bb406f9b3567270354434e7a58cd 100644 Binary files a/vendor/assets/images/authbuttons/google_64.png and b/vendor/assets/images/authbuttons/google_64.png differ diff --git a/vendor/assets/images/authbuttons/twitter_32.png b/vendor/assets/images/authbuttons/twitter_32.png index daadcffd315f11026cface9d99409997050ab9b9..696eb02484d27ee6d453cbb07189c9923780d24b 100644 Binary files a/vendor/assets/images/authbuttons/twitter_32.png and b/vendor/assets/images/authbuttons/twitter_32.png differ diff --git a/vendor/assets/images/authbuttons/twitter_64.png b/vendor/assets/images/authbuttons/twitter_64.png index 68b74530c06b558b6c4848c2637d4caeaf4c969b..2893274766f8bcfe2c3ee7604d912122ca9b1be3 100644 Binary files a/vendor/assets/images/authbuttons/twitter_64.png and b/vendor/assets/images/authbuttons/twitter_64.png differ diff --git a/vendor/assets/images/bg_fallback.png b/vendor/assets/images/bg_fallback.png index 4b2754b8040e4bb430bd910225bb9760d25e8794..d9066ad7d7b2e7f339bbf92c9968fd9ccd96887d 100644 Binary files a/vendor/assets/images/bg_fallback.png and b/vendor/assets/images/bg_fallback.png differ diff --git a/vendor/assets/images/icon_sprite.png b/vendor/assets/images/icon_sprite.png index 636c80f221621c2461cb2f78764b21a1d5ce2f5a..9ad65fc443bdbbc50fa25797bced0680c8135b5c 100644 Binary files a/vendor/assets/images/icon_sprite.png and b/vendor/assets/images/icon_sprite.png differ diff --git a/vendor/assets/images/progress_bar.gif b/vendor/assets/images/progress_bar.gif index 156fbb53137ff519901e8b1021e2468974131c39..c3d43fa40b2fd90186d22f8a82bdc4673d8ab904 100644 Binary files a/vendor/assets/images/progress_bar.gif and b/vendor/assets/images/progress_bar.gif differ diff --git a/vendor/assets/images/slider_handles.png b/vendor/assets/images/slider_handles.png index b95a46eca97b9001da25067948cb05a0021be2a1..a6d477033fa5436fbad5409adcb549ac8d8f01e0 100644 Binary files a/vendor/assets/images/slider_handles.png and b/vendor/assets/images/slider_handles.png differ diff --git a/vendor/assets/images/ui-icons_222222_256x240.png b/vendor/assets/images/ui-icons_222222_256x240.png index b273ff111d219c9b9a8b96d57683d0075fb7871a..8bc06cbf03b830a60f29857361df57214e172dfe 100644 Binary files a/vendor/assets/images/ui-icons_222222_256x240.png and b/vendor/assets/images/ui-icons_222222_256x240.png differ diff --git a/vendor/assets/images/ui-icons_454545_256x240.png b/vendor/assets/images/ui-icons_454545_256x240.png index 59bd45b907c4fd965697774ce8c5fc6b2fd9c105..cfd1eaffaae0f5fe30d8e86d2e54b990d2a1ccd0 100644 Binary files a/vendor/assets/images/ui-icons_454545_256x240.png and b/vendor/assets/images/ui-icons_454545_256x240.png differ diff --git a/vendor/assets/javascripts/ace-src-noconflict/mode-diff.js b/vendor/assets/javascripts/ace-src-noconflict/mode-diff.js index 75e26cc7056752f3cb5f37813a44fcca7a10a413..8d1e7cee0967542ba1804c8a90fbbd8f096ae4e3 100644 --- a/vendor/assets/javascripts/ace-src-noconflict/mode-diff.js +++ b/vendor/assets/javascripts/ace-src-noconflict/mode-diff.js @@ -66,7 +66,7 @@ var DiffHighlightRules = function() { "regex": "^(?:\\*{15}|={67}|-{3}|\\+{3})$", "token": "punctuation.definition.separator.diff", "name": "keyword" - }, { //diff.range.unified + }, { //diff.range.inline "regex": "^(@@)(\\s*.+?\\s*)(@@)(.*)$", "token": [ "constant", diff --git a/vendor/assets/javascripts/highlightjs.min.js b/vendor/assets/javascripts/highlightjs.min.js new file mode 100644 index 0000000000000000000000000000000000000000..d8acc5c53206a8d946f2c63c944322f52d1dc8ed --- /dev/null +++ b/vendor/assets/javascripts/highlightjs.min.js @@ -0,0 +1 @@ +var hljs=new function(){function k(v){return v.replace(/&/gm,"&").replace(/</gm,"<").replace(/>/gm,">")}function t(v){return v.nodeName.toLowerCase()}function i(w,x){var v=w&&w.exec(x);return v&&v.index==0}function d(v){return Array.prototype.map.call(v.childNodes,function(w){if(w.nodeType==3){return b.useBR?w.nodeValue.replace(/\n/g,""):w.nodeValue}if(t(w)=="br"){return"\n"}return d(w)}).join("")}function r(w){var v=(w.className+" "+(w.parentNode?w.parentNode.className:"")).split(/\s+/);v=v.map(function(x){return x.replace(/^language-/,"")});return v.filter(function(x){return j(x)||x=="no-highlight"})[0]}function o(x,y){var v={};for(var w in x){v[w]=x[w]}if(y){for(var w in y){v[w]=y[w]}}return v}function u(x){var v=[];(function w(y,z){for(var A=y.firstChild;A;A=A.nextSibling){if(A.nodeType==3){z+=A.nodeValue.length}else{if(t(A)=="br"){z+=1}else{if(A.nodeType==1){v.push({event:"start",offset:z,node:A});z=w(A,z);v.push({event:"stop",offset:z,node:A})}}}}return z})(x,0);return v}function q(w,y,C){var x=0;var F="";var z=[];function B(){if(!w.length||!y.length){return w.length?w:y}if(w[0].offset!=y[0].offset){return(w[0].offset<y[0].offset)?w:y}return y[0].event=="start"?w:y}function A(H){function G(I){return" "+I.nodeName+'="'+k(I.value)+'"'}F+="<"+t(H)+Array.prototype.map.call(H.attributes,G).join("")+">"}function E(G){F+="</"+t(G)+">"}function v(G){(G.event=="start"?A:E)(G.node)}while(w.length||y.length){var D=B();F+=k(C.substr(x,D[0].offset-x));x=D[0].offset;if(D==w){z.reverse().forEach(E);do{v(D.splice(0,1)[0]);D=B()}while(D==w&&D.length&&D[0].offset==x);z.reverse().forEach(A)}else{if(D[0].event=="start"){z.push(D[0].node)}else{z.pop()}v(D.splice(0,1)[0])}}return F+k(C.substr(x))}function m(y){function v(z){return(z&&z.source)||z}function w(A,z){return RegExp(v(A),"m"+(y.cI?"i":"")+(z?"g":""))}function x(D,C){if(D.compiled){return}D.compiled=true;D.k=D.k||D.bK;if(D.k){var z={};function E(G,F){if(y.cI){F=F.toLowerCase()}F.split(" ").forEach(function(H){var I=H.split("|");z[I[0]]=[G,I[1]?Number(I[1]):1]})}if(typeof D.k=="string"){E("keyword",D.k)}else{Object.keys(D.k).forEach(function(F){E(F,D.k[F])})}D.k=z}D.lR=w(D.l||/\b[A-Za-z0-9_]+\b/,true);if(C){if(D.bK){D.b=D.bK.split(" ").join("|")}if(!D.b){D.b=/\B|\b/}D.bR=w(D.b);if(!D.e&&!D.eW){D.e=/\B|\b/}if(D.e){D.eR=w(D.e)}D.tE=v(D.e)||"";if(D.eW&&C.tE){D.tE+=(D.e?"|":"")+C.tE}}if(D.i){D.iR=w(D.i)}if(D.r===undefined){D.r=1}if(!D.c){D.c=[]}var B=[];D.c.forEach(function(F){if(F.v){F.v.forEach(function(G){B.push(o(F,G))})}else{B.push(F=="self"?D:F)}});D.c=B;D.c.forEach(function(F){x(F,D)});if(D.starts){x(D.starts,C)}var A=D.c.map(function(F){return F.bK?"\\.?\\b("+F.b+")\\b\\.?":F.b}).concat([D.tE]).concat([D.i]).map(v).filter(Boolean);D.t=A.length?w(A.join("|"),true):{exec:function(F){return null}};D.continuation={}}x(y)}function c(S,L,J,R){function v(U,V){for(var T=0;T<V.c.length;T++){if(i(V.c[T].bR,U)){return V.c[T]}}}function z(U,T){if(i(U.eR,T)){return U}if(U.eW){return z(U.parent,T)}}function A(T,U){return !J&&i(U.iR,T)}function E(V,T){var U=M.cI?T[0].toLowerCase():T[0];return V.k.hasOwnProperty(U)&&V.k[U]}function w(Z,X,W,V){var T=V?"":b.classPrefix,U='<span class="'+T,Y=W?"":"</span>";U+=Z+'">';return U+X+Y}function N(){var U=k(C);if(!I.k){return U}var T="";var X=0;I.lR.lastIndex=0;var V=I.lR.exec(U);while(V){T+=U.substr(X,V.index-X);var W=E(I,V);if(W){H+=W[1];T+=w(W[0],V[0])}else{T+=V[0]}X=I.lR.lastIndex;V=I.lR.exec(U)}return T+U.substr(X)}function F(){if(I.sL&&!f[I.sL]){return k(C)}var T=I.sL?c(I.sL,C,true,I.continuation.top):g(C);if(I.r>0){H+=T.r}if(I.subLanguageMode=="continuous"){I.continuation.top=T.top}return w(T.language,T.value,false,true)}function Q(){return I.sL!==undefined?F():N()}function P(V,U){var T=V.cN?w(V.cN,"",true):"";if(V.rB){D+=T;C=""}else{if(V.eB){D+=k(U)+T;C=""}else{D+=T;C=U}}I=Object.create(V,{parent:{value:I}})}function G(T,X){C+=T;if(X===undefined){D+=Q();return 0}var V=v(X,I);if(V){D+=Q();P(V,X);return V.rB?0:X.length}var W=z(I,X);if(W){var U=I;if(!(U.rE||U.eE)){C+=X}D+=Q();do{if(I.cN){D+="</span>"}H+=I.r;I=I.parent}while(I!=W.parent);if(U.eE){D+=k(X)}C="";if(W.starts){P(W.starts,"")}return U.rE?0:X.length}if(A(X,I)){throw new Error('Illegal lexeme "'+X+'" for mode "'+(I.cN||"<unnamed>")+'"')}C+=X;return X.length||1}var M=j(S);if(!M){throw new Error('Unknown language: "'+S+'"')}m(M);var I=R||M;var D="";for(var K=I;K!=M;K=K.parent){if(K.cN){D=w(K.cN,D,true)}}var C="";var H=0;try{var B,y,x=0;while(true){I.t.lastIndex=x;B=I.t.exec(L);if(!B){break}y=G(L.substr(x,B.index-x),B[0]);x=B.index+y}G(L.substr(x));for(var K=I;K.parent;K=K.parent){if(K.cN){D+="</span>"}}return{r:H,value:D,language:S,top:I}}catch(O){if(O.message.indexOf("Illegal")!=-1){return{r:0,value:k(L)}}else{throw O}}}function g(y,x){x=x||b.languages||Object.keys(f);var v={r:0,value:k(y)};var w=v;x.forEach(function(z){if(!j(z)){return}var A=c(z,y,false);A.language=z;if(A.r>w.r){w=A}if(A.r>v.r){w=v;v=A}});if(w.language){v.second_best=w}return v}function h(v){if(b.tabReplace){v=v.replace(/^((<[^>]+>|\t)+)/gm,function(w,z,y,x){return z.replace(/\t/g,b.tabReplace)})}if(b.useBR){v=v.replace(/\n/g,"<br>")}return v}function p(z){var y=d(z);var A=r(z);if(A=="no-highlight"){return}var v=A?c(A,y,true):g(y);var w=u(z);if(w.length){var x=document.createElementNS("http://www.w3.org/1999/xhtml","pre");x.innerHTML=v.value;v.value=q(w,u(x),y)}v.value=h(v.value);z.innerHTML=v.value;z.className+=" hljs "+(!A&&v.language||"");z.result={language:v.language,re:v.r};if(v.second_best){z.second_best={language:v.second_best.language,re:v.second_best.r}}}var b={classPrefix:"hljs-",tabReplace:null,useBR:false,languages:undefined};function s(v){b=o(b,v)}function l(){if(l.called){return}l.called=true;var v=document.querySelectorAll("pre code");Array.prototype.forEach.call(v,p)}function a(){addEventListener("DOMContentLoaded",l,false);addEventListener("load",l,false)}var f={};var n={};function e(v,x){var w=f[v]=x(this);if(w.aliases){w.aliases.forEach(function(y){n[y]=v})}}function j(v){return f[v]||f[n[v]]}this.highlight=c;this.highlightAuto=g;this.fixMarkup=h;this.highlightBlock=p;this.configure=s;this.initHighlighting=l;this.initHighlightingOnLoad=a;this.registerLanguage=e;this.getLanguage=j;this.inherit=o;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE]};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE]};this.CLCM={cN:"comment",b:"//",e:"$"};this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"};this.HCM={cN:"comment",b:"#",e:"$"};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.REGEXP_MODE={cN:"regexp",b:/\//,e:/\/[gim]*/,i:/\n/,c:[this.BE,{b:/\[/,e:/\]/,r:0,c:[this.BE]}]};this.TM={cN:"title",b:this.IR,r:0};this.UTM={cN:"title",b:this.UIR,r:0}}();hljs.registerLanguage("bash",function(b){var a={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)\}/}]};var d={cN:"string",b:/"/,e:/"/,c:[b.BE,a,{cN:"variable",b:/\$\(/,e:/\)/,c:[b.BE]}]};var c={cN:"string",b:/'/,e:/'/};return{l:/-?[a-z\.]+/,k:{keyword:"if then else elif fi for break continue while in do done exit return set declare case esac export exec",literal:"true false",built_in:"printf echo read cd pwd pushd popd dirs let eval unset typeset readonly getopts source shopt caller type hash bind help sudo",operator:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"shebang",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:true,c:[b.inherit(b.TM,{b:/\w[\w\d_]*/})],r:0},b.HCM,b.NM,d,c,a]}});hljs.registerLanguage("cs",function(b){var a="abstract as base bool break byte case catch char checked const continue decimal default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long new null object operator out override params private protected public readonly ref return sbyte sealed short sizeof stackalloc static string struct switch this throw true try typeof uint ulong unchecked unsafe ushort using virtual volatile void while async await ascending descending from get group into join let orderby partial select set value var where yield";return{k:a,c:[{cN:"comment",b:"///",e:"$",rB:true,c:[{cN:"xmlDocTag",b:"///|<!--|-->"},{cN:"xmlDocTag",b:"</?",e:">"}]},b.CLCM,b.CBLCLM,{cN:"preprocessor",b:"#",e:"$",k:"if else elif endif define undef warning error line region endregion pragma checksum"},{cN:"string",b:'@"',e:'"',c:[{b:'""'}]},b.ASM,b.QSM,b.CNM,{bK:"protected public private internal",e:/[{;=]/,k:a,c:[{bK:"class namespace interface",starts:{c:[b.TM]}},{b:b.IR+"\\s*\\(",rB:true,c:[b.TM]}]}]}});hljs.registerLanguage("ruby",function(e){var h="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?";var g="and false then defined module in return redo if BEGIN retry end for true self when next until do begin unless END rescue nil else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor";var a={cN:"yardoctag",b:"@[A-Za-z]+"};var i={cN:"comment",v:[{b:"#",e:"$",c:[a]},{b:"^\\=begin",e:"^\\=end",c:[a],r:10},{b:"^__END__",e:"\\n$"}]};var c={cN:"subst",b:"#\\{",e:"}",k:g};var d={cN:"string",c:[e.BE,c],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:"%[qw]?\\(",e:"\\)"},{b:"%[qw]?\\[",e:"\\]"},{b:"%[qw]?{",e:"}"},{b:"%[qw]?<",e:">",r:10},{b:"%[qw]?/",e:"/",r:10},{b:"%[qw]?%",e:"%",r:10},{b:"%[qw]?-",e:"-",r:10},{b:"%[qw]?\\|",e:"\\|",r:10},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/}]};var b={cN:"params",b:"\\(",e:"\\)",k:g};var f=[d,i,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{cN:"inheritance",b:"<\\s*",c:[{cN:"parent",b:"("+e.IR+"::)?"+e.IR}]},i]},{cN:"function",bK:"def",e:" |$|;",r:0,c:[e.inherit(e.TM,{b:h}),b,i]},{cN:"constant",b:"(::)?(\\b[A-Z]\\w*(::)?)+",r:0},{cN:"symbol",b:":",c:[d,{b:h}],r:0},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{b:"("+e.RSR+")\\s*",c:[i,{cN:"regexp",c:[e.BE,c],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}],r:0}];c.c=f;b.c=f;return{k:g,c:f}});hljs.registerLanguage("diff",function(a){return{c:[{cN:"chunk",r:10,v:[{b:/^\@\@ +\-\d+,\d+ +\+\d+,\d+ +\@\@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"header",v:[{b:/Index: /,e:/$/},{b:/=====/,e:/=====$/},{b:/^\-\-\-/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+\+\+/,e:/$/},{b:/\*{5}/,e:/\*{5}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"change",b:"^\\!",e:"$"}]}});hljs.registerLanguage("javascript",function(a){return{aliases:["js"],k:{keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require"},c:[{cN:"pi",b:/^\s*('|")use strict('|")/,r:10},a.ASM,a.QSM,a.CLCM,a.CBLCLM,a.CNM,{b:"("+a.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[a.CLCM,a.CBLCLM,a.REGEXP_MODE,{b:/</,e:/>;/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,c:[a.inherit(a.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,c:[a.CLCM,a.CBLCLM],i:/["'\(]/}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+a.IR,r:0}]}});hljs.registerLanguage("xml",function(a){var c="[A-Za-z0-9\\._:-]+";var d={b:/<\?(php)?(?!\w)/,e:/\?>/,sL:"php",subLanguageMode:"continuous"};var b={eW:true,i:/</,r:0,c:[d,{cN:"attribute",b:c,r:0},{b:"=",r:0,c:[{cN:"value",v:[{b:/"/,e:/"/},{b:/'/,e:/'/},{b:/[^\s\/>]+/}]}]}]};return{aliases:["html"],cI:true,c:[{cN:"doctype",b:"<!DOCTYPE",e:">",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"<!--",e:"-->",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"<style(?=\\s|>|$)",e:">",k:{title:"style"},c:[b],starts:{e:"</style>",rE:true,sL:"css"}},{cN:"tag",b:"<script(?=\\s|>|$)",e:">",k:{title:"script"},c:[b],starts:{e:"<\/script>",rE:true,sL:"javascript"}},{b:"<%",e:"%>",sL:"vbscript"},d,{cN:"pi",b:/<\?\w+/,e:/\?>/,r:10},{cN:"tag",b:"</?",e:"/?>",c:[{cN:"title",b:"[^ /><]+",r:0},b]}]}});hljs.registerLanguage("markdown",function(a){return{c:[{cN:"header",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"blockquote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"`.+?`"},{b:"^( {4}|\t)",e:"$",r:0}]},{cN:"horizontal_rule",b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].+?[\\)\\]]",rB:true,c:[{cN:"link_label",b:"\\[",e:"\\]",eB:true,rE:true,r:0},{cN:"link_url",b:"\\]\\(",e:"\\)",eB:true,eE:true},{cN:"link_reference",b:"\\]\\[",e:"\\]",eB:true,eE:true,}],r:10},{b:"^\\[.+\\]:",e:"$",rB:true,c:[{cN:"link_reference",b:"\\[",e:"\\]",eB:true,eE:true},{cN:"link_url",b:"\\s",e:"$"}]}]}});hljs.registerLanguage("css",function(a){var b="[a-zA-Z-][a-zA-Z0-9_-]*";var c={cN:"function",b:b+"\\(",e:"\\)",c:["self",a.NM,a.ASM,a.QSM]};return{cI:true,i:"[=/|']",c:[a.CBLCLM,{cN:"id",b:"\\#[A-Za-z0-9_-]+"},{cN:"class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"attr_selector",b:"\\[",e:"\\]",i:"$"},{cN:"pseudo",b:":(:)?[a-zA-Z0-9\\_\\-\\+\\(\\)\\\"\\']+"},{cN:"at_rule",b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{cN:"at_rule",b:"@",e:"[{;]",c:[{cN:"keyword",b:/\S+/},{b:/\s/,eW:true,eE:true,r:0,c:[c,a.ASM,a.QSM,a.NM]}]},{cN:"tag",b:b,r:0},{cN:"rules",b:"{",e:"}",i:"[^\\s]",r:0,c:[a.CBLCLM,{cN:"rule",b:"[^\\s]",rB:true,e:";",eW:true,c:[{cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:true,i:"[^\\s]",starts:{cN:"value",eW:true,eE:true,c:[c,a.NM,a.QSM,a.ASM,a.CBLCLM,{cN:"hexcolor",b:"#[0-9A-Fa-f]+"},{cN:"important",b:"!important"}]}}]}]}]}});hljs.registerLanguage("http",function(a){return{i:"\\S",c:[{cN:"status",b:"^HTTP/[0-9\\.]+",e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{cN:"request",b:"^[A-Z]+ (.*?) HTTP/[0-9\\.]+$",rB:true,e:"$",c:[{cN:"string",b:" ",e:" ",eB:true,eE:true}]},{cN:"attribute",b:"^\\w",e:": ",eE:true,i:"\\n|\\s|=",starts:{cN:"string",e:"$"}},{b:"\\n\\n",starts:{sL:"",eW:true}}]}});hljs.registerLanguage("java",function(b){var a="false synchronized int abstract float private char boolean static null if const for true while long throw strictfp finally protected import native final return void enum else break transient new catch instanceof byte super volatile case assert short package default double public try this switch continue throws";return{k:a,i:/<\//,c:[{cN:"javadoc",b:"/\\*\\*",e:"\\*/",c:[{cN:"javadoctag",b:"(^|\\s)@[A-Za-z]+"}],r:10},b.CLCM,b.CBLCLM,b.ASM,b.QSM,{bK:"protected public private",e:/[{;=]/,k:a,c:[{cN:"class",bK:"class interface",eW:true,i:/[:"<>]/,c:[{bK:"extends implements",r:10},b.UTM]},{b:b.UIR+"\\s*\\(",rB:true,c:[b.UTM]}]},b.CNM,{cN:"annotation",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("php",function(b){var e={cN:"variable",b:"\\$+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*"};var a={cN:"preprocessor",b:/<\?(php)?|\?>/};var c={cN:"string",c:[b.BE,a],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},b.inherit(b.ASM,{i:null}),b.inherit(b.QSM,{i:null})]};var d={v:[b.BNM,b.CNM]};return{cI:true,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[b.CLCM,b.HCM,{cN:"comment",b:"/\\*",e:"\\*/",c:[{cN:"phpdoc",b:"\\s@[A-Za-z]+"},a]},{cN:"comment",b:"__halt_compiler.+?;",eW:true,k:"__halt_compiler",l:b.UIR},{cN:"string",b:"<<<['\"]?\\w+['\"]?$",e:"^\\w+;",c:[b.BE]},a,e,{cN:"function",bK:"function",e:/[;{]/,i:"\\$|\\[|%",c:[b.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",e,b.CBLCLM,c,d]}]},{cN:"class",bK:"class interface",e:"{",i:/[:\(\$"]/,c:[{bK:"extends implements",r:10},b.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[b.UTM]},{bK:"use",e:";",c:[b.UTM]},{b:"=>"},c,d]}});hljs.registerLanguage("python",function(a){var f={cN:"prompt",b:/^(>>>|\.\.\.) /};var b={cN:"string",c:[a.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[f],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[f],r:10},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/,},{b:/(b|br)"/,e:/"/,},a.ASM,a.QSM]};var d={cN:"number",r:0,v:[{b:a.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:a.CNR+"[lLjJ]?"}]};var e={cN:"params",b:/\(/,e:/\)/,c:["self",f,d,b]};var c={e:/:/,i:/[${=;\n]/,c:[a.UTM,e]};return{k:{keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},i:/(<\/|->|\?)/,c:[f,d,b,a.HCM,a.inherit(c,{cN:"function",bK:"def",r:10}),a.inherit(c,{cN:"class",bK:"class"}),{cN:"decorator",b:/@/,e:/$/},{b:/\b(print|exec)\(/}]}});hljs.registerLanguage("sql",function(a){return{cI:true,i:/[<>]/,c:[{cN:"operator",b:"\\b(begin|end|start|commit|rollback|savepoint|lock|alter|create|drop|rename|call|delete|do|handler|insert|load|replace|select|truncate|update|set|show|pragma|grant|merge)\\b(?!:)",e:";",eW:true,k:{keyword:"all partial global month current_timestamp using go revoke smallint indicator end-exec disconnect zone with character assertion to add current_user usage input local alter match collate real then rollback get read timestamp session_user not integer bit unique day minute desc insert execute like ilike|2 level decimal drop continue isolation found where constraints domain right national some module transaction relative second connect escape close system_user for deferred section cast current sqlstate allocate intersect deallocate numeric public preserve full goto initially asc no key output collation group by union session both last language constraint column of space foreign deferrable prior connection unknown action commit view or first into float year primary cascaded except restrict set references names table outer open select size are rows from prepare distinct leading create only next inner authorization schema corresponding option declare precision immediate else timezone_minute external varying translation true case exception join hour default double scroll value cursor descriptor values dec fetch procedure delete and false int is describe char as at in varchar null trailing any absolute current_time end grant privileges when cross check write current_date pad begin temporary exec time update catalog user sql date on identity timezone_hour natural whenever interval work order cascade diagnostics nchar having left call do handler load replace truncate start lock show pragma exists number trigger if before after each row merge matched database",aggregate:"count sum min max avg"},c:[{cN:"string",b:"'",e:"'",c:[a.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[a.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[a.BE]},a.CNM]},a.CBLCLM,{cN:"comment",b:"--",e:"$"}]}});hljs.registerLanguage("ini",function(a){return{cI:true,i:/\S/,c:[{cN:"comment",b:";",e:"$"},{cN:"title",b:"^\\[",e:"\\]"},{cN:"setting",b:"^[a-z0-9\\[\\]_-]+[ \\t]*=[ \\t]*",e:"$",c:[{cN:"value",eW:true,k:"on off true false yes no",c:[a.QSM,a.NM],r:0}]}]}});hljs.registerLanguage("perl",function(c){var d="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when";var f={cN:"subst",b:"[$@]\\{",e:"\\}",k:d};var g={b:"->{",e:"}"};var a={cN:"variable",v:[{b:/\$\d/},{b:/[\$\%\@\*](\^\w\b|#\w+(\:\:\w+)*|{\w+}|\w+(\:\:\w*)*)/},{b:/[\$\%\@\*][^\s\w{]/,r:0}]};var e={cN:"comment",b:"^(__END__|__DATA__)",e:"\\n$",r:5};var h=[c.BE,f,a];var b=[a,c.HCM,e,{cN:"comment",b:"^\\=\\w",e:"\\=cut",eW:true},g,{cN:"string",c:h,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",r:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",r:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",r:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",r:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",r:5},{b:"qw\\s+q",e:"q",r:5},{b:"'",e:"'",c:[c.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[c.BE]},{b:"{\\w+}",c:[],r:0},{b:"-?\\w+\\s*\\=\\>",c:[],r:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\/\\/|"+c.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",r:0,c:[c.HCM,e,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",r:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[c.BE],r:0}]},{cN:"sub",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",r:5},{cN:"operator",b:"-\\w\\b",r:0}];f.c=b;g.c=b;return{k:d,c:b}});hljs.registerLanguage("objectivec",function(a){var d={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign self synchronized id nonatomic super unichar IBOutlet IBAction strong weak @private @protected @public @try @property @end @throw @catch @finally @synthesize @dynamic @selector @optional @required",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"NSString NSDictionary CGRect CGPoint UIButton UILabel UITextView UIWebView MKMapView UISegmentedControl NSObject UITableViewDelegate UITableViewDataSource NSThread UIActivityIndicator UITabbar UIToolBar UIBarButtonItem UIImageView NSAutoreleasePool UITableView BOOL NSInteger CGFloat NSException NSLog NSMutableString NSMutableArray NSMutableDictionary NSURL NSIndexPath CGSize UITableViewCell UIView UIViewController UINavigationBar UINavigationController UITabBarController UIPopoverController UIPopoverControllerDelegate UIImage NSNumber UISearchBar NSFetchedResultsController NSFetchedResultsChangeType UIScrollView UIScrollViewDelegate UIEdgeInsets UIColor UIFont UIApplication NSNotFound NSNotificationCenter NSNotification UILocalNotification NSBundle NSFileManager NSTimeInterval NSDate NSCalendar NSUserDefaults UIWindow NSRange NSArray NSError NSURLRequest NSURLConnection UIInterfaceOrientation MPMoviePlayerController dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"};var c=/[a-zA-Z@][a-zA-Z0-9_]*/;var b="@interface @class @protocol @implementation";return{k:d,l:c,i:"</",c:[a.CLCM,a.CBLCLM,a.CNM,a.QSM,{cN:"string",b:"'",e:"[^\\\\]'",i:"[^\\\\][^']"},{cN:"preprocessor",b:"#import",e:"$",c:[{cN:"title",b:'"',e:'"'},{cN:"title",b:"<",e:">"}]},{cN:"preprocessor",b:"#",e:"$"},{cN:"class",b:"("+b.split(" ").join("|")+")\\b",e:"({|$)",k:b,l:c,c:[a.UTM]},{cN:"variable",b:"\\."+a.UIR,r:0}]}});hljs.registerLanguage("coffeescript",function(c){var b={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",reserved:"case default function var void with const let enum export import native __hasProp __extends __slice __bind __indexOf",built_in:"npm require console print module exports global window document"};var a="[A-Za-z$_][0-9A-Za-z$_]*";var f=c.inherit(c.TM,{b:a});var e={cN:"subst",b:/#\{/,e:/}/,k:b};var d=[c.BNM,c.inherit(c.CNM,{starts:{e:"(\\s*/)?",r:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[c.BE]},{b:/'/,e:/'/,c:[c.BE]},{b:/"""/,e:/"""/,c:[c.BE,e]},{b:/"/,e:/"/,c:[c.BE,e]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[e,c.HCM]},{b:"//[gim]*",r:0},{b:"/\\S(\\\\.|[^\\n])*?/[gim]*(?=\\s|\\W|$)"}]},{cN:"property",b:"@"+a},{b:"`",e:"`",eB:true,eE:true,sL:"javascript"}];e.c=d;return{k:b,c:d.concat([{cN:"comment",b:"###",e:"###"},c.HCM,{cN:"function",b:"("+a+"\\s*=\\s*)?(\\(.*\\))?\\s*\\B[-=]>",e:"[-=]>",rB:true,c:[f,{cN:"params",b:"\\(",rB:true,c:[{b:/\(/,e:/\)/,k:b,c:["self"].concat(d)}]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:true,i:/[:="\[\]]/,c:[f]},f]},{cN:"attribute",b:a+":",e:":",rB:true,eE:true,r:0}])}});hljs.registerLanguage("nginx",function(c){var b={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+c.UIR}]};var a={eW:true,l:"[a-z/_]+",k:{built_in:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},r:0,i:"=>",c:[c.HCM,{cN:"string",c:[c.BE,b],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{cN:"url",b:"([a-z]+):/",e:"\\s",eW:true,eE:true},{cN:"regexp",c:[c.BE,b],v:[{b:"\\s\\^",e:"\\s|{|;",rE:true},{b:"~\\*?\\s+",e:"\\s|{|;",rE:true},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",r:0},b]};return{c:[c.HCM,{b:c.UIR+"\\s",e:";|{",rB:true,c:[c.inherit(c.UTM,{starts:a})],r:0}],i:"[^\\s\\}]"}});hljs.registerLanguage("json",function(a){var e={literal:"true false null"};var d=[a.QSM,a.CNM];var c={cN:"value",e:",",eW:true,eE:true,c:d,k:e};var b={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:true,eE:true,c:[a.BE],i:"\\n",starts:c}],i:"\\S"};var f={b:"\\[",e:"\\]",c:[a.inherit(c,{cN:null})],i:"\\S"};d.splice(d.length,0,b,f);return{c:d,k:e,i:"\\S"}});hljs.registerLanguage("apache",function(a){var b={cN:"number",b:"[\\$%]\\d+"};return{cI:true,c:[a.HCM,{cN:"tag",b:"</?",e:">"},{cN:"keyword",b:/\w+/,r:0,k:{common:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,r:0,k:{literal:"on off all"},c:[{cN:"sqbracket",b:"\\s\\[",e:"\\]$"},{cN:"cbracket",b:"[\\$%]\\{",e:"\\}",c:["self",b]},b,a.QSM]}}],i:/\S/}});hljs.registerLanguage("cpp",function(a){var b={keyword:"false int float while private char catch export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const struct for static_cast|10 union namespace unsigned long throw volatile static protected bool template mutable if public friend do return goto auto void enum else break new extern using true class asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue wchar_t inline delete alignof char16_t char32_t constexpr decltype noexcept nullptr static_assert thread_local restrict _Bool complex _Complex _Imaginary",built_in:"std string cin cout cerr clog stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf"};return{aliases:["c"],k:b,i:"</",c:[a.CLCM,a.CBLCLM,a.QSM,{cN:"string",b:"'\\\\?.",e:"'",i:"."},{cN:"number",b:"\\b(\\d+(\\.\\d*)?|\\.\\d+)(u|U|l|L|ul|UL|f|F)"},a.CNM,{cN:"preprocessor",b:"#",e:"$",c:[{b:"include\\s*<",e:">",i:"\\n"},a.CLCM]},{cN:"stl_container",b:"\\b(deque|list|queue|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",e:">",k:b,r:10,c:["self"]}]}});hljs.registerLanguage("makefile",function(a){var b={cN:"variable",b:/\$\(/,e:/\)/,c:[a.BE]};return{c:[a.HCM,{b:/^\w+\s*\W*=/,rB:true,r:0,starts:{cN:"constant",e:/\s*\W*=/,eE:true,starts:{e:/$/,r:0,c:[b],}}},{cN:"title",b:/^[\w]+:\s*$/},{cN:"phony",b:/^\.PHONY:/,e:/$/,k:".PHONY",l:/[\.\w]+/},{b:/^\t+/,e:/$/,c:[a.QSM,b]}]}}); \ No newline at end of file diff --git a/vendor/assets/stylesheets/highlightjs.min.css b/vendor/assets/stylesheets/highlightjs.min.css new file mode 100644 index 0000000000000000000000000000000000000000..f2429be6228c35364f067f72a05260aec642c93c --- /dev/null +++ b/vendor/assets/stylesheets/highlightjs.min.css @@ -0,0 +1 @@ +.hljs{display:block;padding:.5em;background:#f0f0f0}.hljs,.hljs-subst,.hljs-tag .hljs-title,.lisp .hljs-title,.clojure .hljs-built_in,.nginx .hljs-title{color:black}.hljs-string,.hljs-title,.hljs-constant,.hljs-parent,.hljs-tag .hljs-value,.hljs-rules .hljs-value,.hljs-rules .hljs-value .hljs-number,.hljs-preprocessor,.hljs-pragma,.haml .hljs-symbol,.ruby .hljs-symbol,.ruby .hljs-symbol .hljs-string,.hljs-aggregate,.hljs-template_tag,.django .hljs-variable,.smalltalk .hljs-class,.hljs-addition,.hljs-flow,.hljs-stream,.bash .hljs-variable,.apache .hljs-tag,.apache .hljs-cbracket,.tex .hljs-command,.tex .hljs-special,.erlang_repl .hljs-function_or_atom,.asciidoc .hljs-header,.markdown .hljs-header,.coffeescript .hljs-attribute{color:#800}.smartquote,.hljs-comment,.hljs-annotation,.hljs-template_comment,.diff .hljs-header,.hljs-chunk,.asciidoc .hljs-blockquote,.markdown .hljs-blockquote{color:#888}.hljs-number,.hljs-date,.hljs-regexp,.hljs-literal,.hljs-hexcolor,.smalltalk .hljs-symbol,.smalltalk .hljs-char,.go .hljs-constant,.hljs-change,.lasso .hljs-variable,.makefile .hljs-variable,.asciidoc .hljs-bullet,.markdown .hljs-bullet,.asciidoc .hljs-link_url,.markdown .hljs-link_url{color:#080}.hljs-label,.hljs-javadoc,.ruby .hljs-string,.hljs-decorator,.hljs-filter .hljs-argument,.hljs-localvars,.hljs-array,.hljs-attr_selector,.hljs-important,.hljs-pseudo,.hljs-pi,.haml .hljs-bullet,.hljs-doctype,.hljs-deletion,.hljs-envvar,.hljs-shebang,.apache .hljs-sqbracket,.nginx .hljs-built_in,.tex .hljs-formula,.erlang_repl .hljs-reserved,.hljs-prompt,.asciidoc .hljs-link_label,.markdown .hljs-link_label,.vhdl .hljs-attribute,.clojure .hljs-attribute,.asciidoc .hljs-attribute,.lasso .hljs-attribute,.coffeescript .hljs-property,.hljs-phony{color:#88F}.hljs-keyword,.hljs-id,.hljs-title,.hljs-built_in,.hljs-aggregate,.css .hljs-tag,.hljs-javadoctag,.hljs-phpdoc,.hljs-yardoctag,.smalltalk .hljs-class,.hljs-winutils,.bash .hljs-variable,.apache .hljs-tag,.go .hljs-typename,.tex .hljs-command,.asciidoc .hljs-strong,.markdown .hljs-strong,.hljs-request,.hljs-status{font-weight:bold}.asciidoc .hljs-emphasis,.markdown .hljs-emphasis{font-style:italic}.nginx .hljs-built_in{font-weight:normal}.coffeescript .javascript,.javascript .xml,.lasso .markup,.tex .hljs-formula,.xml .javascript,.xml .vbscript,.xml .css,.xml .hljs-cdata{opacity:.5} \ No newline at end of file