diff options
920 files changed, 17756 insertions, 8856 deletions
diff --git a/.gitignore b/.gitignore index 084edd30df7..4c25c8abf82 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 f0fc2fb8829..75b4c5c7030 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 1459ef84ae9..c152fead167 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,114 @@ -v 6.2.0 +v 6.6.0 - Retrieving user ssh keys publically(github style): http://__HOST__/__USERNAME__.keys + - 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 @@ -13,6 +121,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 @@ -33,7 +148,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 @@ -76,6 +191,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 9d9be5bdc21..0a97faaf301 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). @@ -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 +#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 7327e5c2815..19d2fcf6b56 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,52 @@ 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) - pg (0.15.1) + orm_adapter (0.5.0) + 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 +362,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) @@ -601,14 +602,15 @@ DEPENDENCIES omniauth-github omniauth-google-oauth2 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 +642,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 668cacc870a..bf757025c40 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 ce0f4a8a1c5..9ac064723bc 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/) @@ -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 Binary files differdeleted file mode 100644 index 8a97c91987a..00000000000 --- a/app/assets/images/ajax_loader.gif +++ /dev/null diff --git a/app/assets/images/ajax_loader_gray.gif b/app/assets/images/ajax_loader_gray.gif Binary files differdeleted file mode 100644 index af3f618bd0b..00000000000 --- a/app/assets/images/ajax_loader_gray.gif +++ /dev/null diff --git a/app/assets/images/ajax_loader_tree.gif b/app/assets/images/ajax_loader_tree.gif Binary files differdeleted file mode 100644 index 99d5a0f37f3..00000000000 --- a/app/assets/images/ajax_loader_tree.gif +++ /dev/null diff --git a/app/assets/images/bg-header.png b/app/assets/images/bg-header.png Binary files differindex 8759ca589fc..9ecdaf4e2d5 100644 --- a/app/assets/images/bg-header.png +++ b/app/assets/images/bg-header.png diff --git a/app/assets/images/dark-scheme-preview.png b/app/assets/images/dark-scheme-preview.png Binary files differindex 055a9069b63..6dac6cd8ca1 100644 --- a/app/assets/images/dark-scheme-preview.png +++ b/app/assets/images/dark-scheme-preview.png diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico Binary files differindex 057f74ac7ab..bfb74960c48 100644 --- a/app/assets/images/favicon.ico +++ b/app/assets/images/favicon.ico diff --git a/app/assets/images/file_txt.png b/app/assets/images/file_txt.png Binary files differindex f3638cb4e1e..b3230b5add0 100644 --- a/app/assets/images/file_txt.png +++ b/app/assets/images/file_txt.png diff --git a/app/assets/images/icon-search.png b/app/assets/images/icon-search.png Binary files differindex 7632915cacc..084b89e3a7c 100644 --- a/app/assets/images/icon-search.png +++ b/app/assets/images/icon-search.png diff --git a/app/assets/images/images.png b/app/assets/images/images.png Binary files differindex 973d3bdd39d..da91f6b1f4c 100644 --- a/app/assets/images/images.png +++ b/app/assets/images/images.png diff --git a/app/assets/images/logo-black.png b/app/assets/images/logo-black.png Binary files differindex 6567f2e5463..4a96572d570 100644 --- a/app/assets/images/logo-black.png +++ b/app/assets/images/logo-black.png diff --git a/app/assets/images/logo-white.png b/app/assets/images/logo-white.png Binary files differindex a63fb1c9c0a..bc2ef601a53 100644 --- a/app/assets/images/logo-white.png +++ b/app/assets/images/logo-white.png diff --git a/app/assets/images/monokai-scheme-preview.png b/app/assets/images/monokai-scheme-preview.png Binary files differindex 9477941778e..3aeed886a02 100644 --- a/app/assets/images/monokai-scheme-preview.png +++ b/app/assets/images/monokai-scheme-preview.png diff --git a/app/assets/images/no_avatar.png b/app/assets/images/no_avatar.png Binary files differindex 752d26adba7..dac3ab1bb89 100644 --- a/app/assets/images/no_avatar.png +++ b/app/assets/images/no_avatar.png diff --git a/app/assets/images/no_group_avatar.png b/app/assets/images/no_group_avatar.png Binary files differnew file mode 100644 index 00000000000..a97d4515982 --- /dev/null +++ b/app/assets/images/no_group_avatar.png diff --git a/app/assets/images/onion_skin_sprites.gif b/app/assets/images/onion_skin_sprites.gif Binary files differindex 85d20260edc..337aa1bfb63 100644 --- a/app/assets/images/onion_skin_sprites.gif +++ b/app/assets/images/onion_skin_sprites.gif diff --git a/app/assets/images/solarized-dark-scheme-preview.png b/app/assets/images/solarized-dark-scheme-preview.png Binary files differindex 728964bc4c8..ae092ab5213 100644 --- a/app/assets/images/solarized-dark-scheme-preview.png +++ b/app/assets/images/solarized-dark-scheme-preview.png diff --git a/app/assets/images/swipemode_sprites.gif b/app/assets/images/swipemode_sprites.gif Binary files differindex 327b3c31ffd..b010b4e4482 100644 --- a/app/assets/images/swipemode_sprites.gif +++ b/app/assets/images/swipemode_sprites.gif diff --git a/app/assets/images/switch_icon.png b/app/assets/images/switch_icon.png Binary files differindex 7c11f206593..6b8bde41bc9 100644 --- a/app/assets/images/switch_icon.png +++ b/app/assets/images/switch_icon.png diff --git a/app/assets/images/trans_bg.gif b/app/assets/images/trans_bg.gif Binary files differindex c7e98e044f5..5f6ed04a43c 100644 --- a/app/assets/images/trans_bg.gif +++ b/app/assets/images/trans_bg.gif diff --git a/app/assets/images/white-scheme-preview.png b/app/assets/images/white-scheme-preview.png Binary files differindex 67eb8763044..d1866e00158 100644 --- a/app/assets/images/white-scheme-preview.png +++ b/app/assets/images/white-scheme-preview.png diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee index 6230fe7f93f..6634bb6cc34 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 a36d944cbcb..5f4a38ebbd0 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 0767b82032d..9ff116a6644 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 7e438c51c1c..5afb656e696 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 03e280f8976..584f6faea16 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 318538509a5..dd09ee51fe0 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 de4c06a2728..9c004c997ed 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 e264e281309..9afb5974858 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 c0ffccd8f70..7850eb14e74 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 67d9498c50a..6c239c66c0a 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 011244a5868..9cf4dba815b 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 5400bc5c1ad..ff843c68d68 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 00000000000..00d135d1449 --- /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 5225623c1f0..00000000000 --- 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 00000000000..d200d962cae --- /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 00000000000..b31eb9ac9de --- /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 5bd11d273a7..1f763e8b956 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 e7974611cbe..744f3086d55 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 83236b34814..4262418fd5e 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 3418690e109..e144dfa1d68 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 48443644169..834c7e5dab0 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 8286ca2f0c1..92cd3a9905b 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 b1a23427add..cc5fdf61405 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 index c23970c13eb..4f038b977e2 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 00000000000..1cbd7439835 --- /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 00000000000..219e6ebd68b --- /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 index 1572227ec3a..4824194ec42 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 index 8ba8c93e3d6..20877507c91 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 00000000000..931b75a3234 --- /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 00000000000..4110bddf4f3 --- /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 00000000000..afe9c5f8186 --- /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 index 83066b5beec..de70e47333f 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 00000000000..c506bff8a74 --- /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 index d3986556376..419a63d4d3a 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 index fcf1159cd33..7a977eae70d 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 faf36b702c0..00000000000 --- 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 9eb32ca95e6..00000000000 --- 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 bc6c786da50..00000000000 --- 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 a2612166c78..00000000000 --- 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 aa4cb1ed5fd..00000000000 --- 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 00000000000..7f45d64fb7c --- /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 129d33dcac3..ca51da3fdd4 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 c9709fa7f12..36bc5df2f44 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 cc82f39ac93..b9bec225188 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 df127a7c491..880387a3483 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 index 8cc9986415c..8cc9986415c 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/fonts.scss +++ b/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 00000000000..9e009a5e0ad --- /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 index 8b975a12cf7..4afe61d756c 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 index aeabe7ad2e8..decc5f50469 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 e189fd27ac6..8ad9bc732b2 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 be6fb29c817..ff293bc4a00 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 3f7825d71ce..1fd82c84fc9 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 a71e5438936..057f7b7fd44 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 39b2ad7a09c..801233519b6 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 bd72d08295a..96b7062ff2b 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 e384aebc76c..5afb13a86ba 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 33bef59c089..a78a9cd4879 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 aa61cae4b9a..4388da00735 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 54263523e85..372fa9669ca 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 94b9ca3b181..9f5f1579fbd 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 5c7516ce6f9..3a3bf7cdf2a 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 0491b68db57..5757858a1ce 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 e67ca794f25..84404b6ee36 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 cd1aa2b011a..e69de29bb2d 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 2a84741f0d6..dfdcbb59b6b 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 49489babab7..13f811e01a1 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 d6ac08fcf6f..3705afdb87c 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 ed3a432ded0..dfaeba41cf6 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 09ae57b6692..00000000000 --- 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 1fd54ff18a6..0fc72d4e0a8 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 41c08c840e2..959febad6fe 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 a2b8c21ea11..9af5adbf10a 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 6173757082e..b0827deb1ac 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 2930c5b1668..00000000000 --- 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 2607b12b4ae..00000000000 --- 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 da2eed0e259..00000000000 --- 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 c3408db6e11..00000000000 --- 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 40385fa65b0..00000000000 --- 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 48def0784fd..00000000000 --- 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 63eda6c7d06..00000000000 --- 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 994e707965a..159ab2027c2 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 00000000000..9a70ef9d199 --- /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 3c80b6503fa..be19139c9b1 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 89b395786b3..4bb3cf07da0 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 088174fd3b8..13a7bdcf34a 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 70bbe306562..bdbb9a354b4 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 d974600dcc1..442b1b65ce8 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 ac319384434..656eda9dec2 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 a0c8a000fc7..00000000000 --- 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 00000000000..38071410f40 --- /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 f80167da4cb..7b418ec98f5 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 00000000000..fe121691a10 --- /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 00000000000..57f3bbf0627 --- /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 378ff6bcf34..bdd991bec06 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 3bb1b0c2f2a..7c97987d006 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 432899f857d..df6954554ea 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 75f12f8a6af..9234cd1708f 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 8fd4565f367..7e4580017dd 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 00000000000..5e305934433 --- /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 e58b4507202..a3c41301676 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 b1329c01ce7..a1a8bed09f4 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 bdc501d73bb..c56df65dcba 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 bdffc940ea5..12856191c26 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 b7531e2cefb..696cb7a4ba2 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 0750e0a146f..6e1a76ff417 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 3b945fc7126..6bd1a455f32 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 1a94dbab5ea..a863b318324 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 e8f845b2d17..f260a2e0597 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 0cc09caf1d2..de31ee12b6a 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 39cd579cce5..aea92a19f34 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 00000000000..3a51a78ef6f --- /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 8214163c315..9c9c2decc78 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 81531bb0ac0..cfc1bd99a20 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 0c23d411f4c..18ace028b0c 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 e5c090e1f4d..16621c0371e 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 20e2a9311ee..f686db70bd4 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 dd0c1a57089..f93f2d5f9bb 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 9dbb0d81888..818c5d971e9 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 b4b318fa59e..44068878cd1 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 5d543f35665..30c94ec6da0 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 7264128691e..6ec109b9145 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 87e903a1d2d..d7297161c22 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 f5c3bb133ed..c1648d6c387 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 b91f68aab5e..e54a968326f 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 4947c33f959..6a5ce62909e 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 749da1e1413..bc5db445528 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 7e5c10fee05..1550e8b7e05 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 00000000000..29ff47663da --- /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 f8f84ff8b62..663369e4584 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 ea2540bf385..5ff19b88293 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 35c7bcbd2cf..d5712ab3374 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 cd8761a6113..929f9a9c381 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 375f8861dae..315f1b805b5 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 8573c59dc94..7c09273d53e 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 00000000000..53d4a8f2e6e --- /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 5977c9cbae2..cdba6ce84dc 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 9a6aea85ee9..d1eb3808f9e 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 4ba48aa4339..5e3f82fe9ce 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 dc88e178360..c363c7ffd74 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 a3ec4cca59d..695147cd6db 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 88d9f184d0e..dd9e03d95a8 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 9071c688df1..a6a507360bd 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 00000000000..470a495f036 --- /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 ce675872264..bd373c5f3cf 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 73d36d0801c..2dbc1cffb16 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 00000000000..81e10f3685c --- /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 2e9d28981e3..1c8ae122c46 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 6eda88c7921..b2b4b83d6c3 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 57c1925fa47..e60887d525a 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 761b4c8161f..e967cf6dc73 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 4d5fe9ef614..df21d7b5b02 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 2f7be00c33e..6c1a3328960 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 85476089145..038668fccff 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 00000000000..bebe0da9c61 --- /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 dd1f9801878..bcc1bcbd96a 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 7f820f950b0..0f1dad4ef16 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 095a06c956b..ddb863c1be2 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 d1edbab4533..7ebaaff61cb 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 d6272ca46f5..8de0c78c158 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 f3ec322126f..6580c5004af 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 a26190015b2..ca2644ec735 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 00000000000..3ea610197e6 --- /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 fde06649c78..d5b98f588e8 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 7e7387abed6..f4c0be3307f 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 709a6b1ea49..d9da2c377c8 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 2576fc979d4..e1c9ed01bc5 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 00000000000..ad7eade5c7b --- /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 index fb2a49fd586..fb2a49fd586 100644 --- a/app/models/campfire_service.rb +++ b/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 00000000000..2a46eff7846 --- /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 00000000000..f72d9fa9015 --- /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 index 7f5380a4551..7f5380a4551 100644 --- a/app/models/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb diff --git a/app/models/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index c3fb4826334..c0ba9f1e7f3 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 index c5b1b9ab8d3..c5b1b9ab8d3 100644 --- a/app/models/pivotaltracker_service.rb +++ b/app/models/project_services/pivotaltracker_service.rb diff --git a/app/models/project_team.rb b/app/models/project_team.rb index bc35c4041ba..eca13e56061 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 aeec48ee5cc..a408fe13f80 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 4cd2b272eec..6f22a863d98 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 5cdf046644f..bffcbbf00f4 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 042050527c1..4f866f1a33d 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 4c58effaf38..2a58692375d 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,7 +407,36 @@ 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} + keys.collect{|x| x.key}.join("\n") end end diff --git a/app/models/web_hook.rb b/app/models/web_hook.rb index 3f22b1082fb..8a5c4b6cd47 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 886d8b776fb..6ef13eb5d5e 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 d70da514cd2..ef31498e7d0 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 acbb719b69a..4e3deec01bf 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 3a649fd590d..80de177b9a2 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 ecdbede89d9..42a05b5e177 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 ca9649c76ab..93233898cc8 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 index 101be50d54b..610f0474872 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 00000000000..f1765d38976 --- /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 00000000000..1998fb79e7d --- /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 00000000000..ff5dc6ef34c --- /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 00000000000..c631f28749c --- /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 00000000000..ebd394ee758 --- /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 f9d43e60de6..e54f88e42de 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 index 73a3c353523..f72a346af6f 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 00000000000..d60d61ed54a --- /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 00000000000..dbdb0063074 --- /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 00000000000..1d5af04cdbb --- /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 index 36ea76ff949..fb87e175933 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 index 234e9ac3cdd..f7ad7d60a3a 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 750a71aea6b..7c02777e914 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 7150c1c78c0..7055ef32aee 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 index 1c60a5de141..ba131d8ffbe 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 index fbc67220d5d..2f1c7b18aa0 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 index 3011984e3f8..f8e27f6f1c6 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 00000000000..d9d371da5c4 --- /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 00000000000..c1130401578 --- /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 00000000000..3ebaafc752c --- /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 39aec943f75..4969198b8c2 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 00000000000..17d86a7a274 --- /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 98794c9470b..b122b6c8658 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 2d4ffc10d5f..e5af56ffc5c 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 00000000000..f28dadfb659 --- /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 9ed788d0d9d..dd663945ea9 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 8ed463a8191..eb5c91050af 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 c9d7c24f204..7a373ee586c 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 0ae35eb6b43..ae0604cf984 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 1566c345809..252111875fa 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 b69aa92716d..00000000000 --- 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 00000000000..9861e372efc --- /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 eb6570af30e..ff90d513ca1 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 bc297209ae5..940a539d5e1 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 c8a87328207..34a91cce163 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") @@ -54,6 +54,11 @@ = @repository.path_to_repo %li + %span.light Size + %strong + = repository_size(@project) + + %li %span.light last commit: %strong = last_commit(@project) @@ -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 3f930c45fa6..881a043f36f 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 b32f0ae87cc..1fa6fdfaff1 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 3df9903e409..e888358dc63 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 89117726317..39dd600dba3 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 b4f3866228d..2ff1c33c53a 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 50b833f3d82..e326bee53ab 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 b79b27fc95a..8313cc07b5e 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 00000000000..8c9893ba84f --- /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 0f0f3466e92..f5413557783 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 82880d5ef63..19bd4e7bd54 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 9c96edeefd5..b487a4d6666 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 0dcb1a87e9a..8feef97c732 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 a913df92299..f4cf24ccd99 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 2305eae1f71..e5b7fbf097e 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 7e5a148e5ef..00000000000 --- 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 adc9b672092..00000000000 --- 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 00000000000..dd63a232fe2 --- /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 7b4fd526964..553d08369e9 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 d85b4ab08b2..95c52608e1f 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 3df65d037af..a14ef2995c8 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 139acf28a9f..b11817af95d 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 d749d7bac09..24bc0406544 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 0c8be9d5c48..a2f85fa3fe2 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 575d33949b6..bb1d0a4001f 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 c6e1d8db577..938f61d2093 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 db931b86288..49e99e25c1d 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 4d4b24009f4..135320da57e 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 b3543460d65..8cf26671e3b 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 de5634d3c55..6db05a1a5a6 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 00000000000..3d62d478869 --- /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 adba9a5f619..f181df23eb4 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 234392c03e1..5801139a9f2 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 16a3c60f660..029d6cb0efa 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 2682d31ff47..e274a799674 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 701747bdbc3..f2005193f83 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 482613f172d..e24df310910 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 8a9b03535bc..eaf85bbdbc8 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 02049bb2ee6..955a107e542 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 edf03642d82..e07bb7d2fb7 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 e613ed3eaa3..6256c047929 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 7e5a148e5ef..00000000000 --- 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 502cc31a80c..c211b658410 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 da917888eee..a413616bad0 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 e979e7c0d07..500e5dc65e1 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 fadc2dc21cb..f1cb723ebac 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 bab1e7c0a41..6505609022a 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: @@ -99,6 +101,13 @@ %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 %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 c67402ee319..ba2df8c3553 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 72a21b89cab..1c7b08aa7cd 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 25865251de3..7bde7fcc3d0 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 6f9fb332261..4f7996e4996 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 00000000000..5794e3de338 --- /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 0775abea3dd..5723250151a 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 6492c122ba0..1d181d3519c 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 7d16a75af6e..6a20dedf62f 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 3c4bd857c22..65c806a915f 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 9a0db99332a..a0e55b21c32 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 3a23cbdb376..439cb978a76 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 792fe5e4a28..511db389e0f 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 c4729836faa..c5041dd71b8 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 0e955d59ff8..fb4a3a3ba95 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 73946f99889..48c569f8684 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 cae24c3170d..12fd49e609f 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 7c3acfc398a..d44cb975ea5 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 e9d535f6972..1f70cf17987 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 30a0532bc2b..2d869a6cdcb 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 ea739da73d8..5659cfab31d 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 6d8bf9b710b..3ae4961b137 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 f922dcc4203..8f490f61a9c 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 cfe6a63055a..a8e3236d865 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 e64e68d2446..191ad406c3c 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 88c4df55b7f..9e329af2d47 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 6b2782ebb0b..321f9418ded 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 2d27350486e..16be4bb619f 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 3a615f86e69..d2d82d36c48 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 eecf055ff6f..87a7847e06d 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 00000000000..d0b30c08338 --- /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 00000000000..6718ca68359 --- /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 42c7ec051cb..00000000000 --- 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 00000000000..c60e1ca9297 --- /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 6fc27be5db4..1d24e636bf4 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 158979c0ee5..f905417f0e2 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 d0a3fe32c35..81411a7565e 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 f1b8fe08d5c..3d5a947948d 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 b736ab17087..b6724a7cb5d 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 5f62c8099d0..d123b8f9407 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 8353b2f5f23..878d7f77430 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 00000000000..2a7d317aa3e --- /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 a4e7dadd16a..f333879cf6c 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 25bf7912f1e..523a07db400 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 abd90269c93..249680bcab6 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 c2f85e8ebe8..00000000000 --- 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 00000000000..e283bd2bf1d --- /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 bb9759353a3..7c8bb33ed7e 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 00000000000..48e6ea91d94 --- /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 f59e2871aa3..e4cfabc3100 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 00000000000..eb38fce0ecf --- /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 5641c528a4f..2f82bfe52f3 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 6524aaeef4c..32ea967105a 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 f3da1a2a219..ff317f90209 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 00000000000..6384703671a --- /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 bed493d6d8c..080a39ab944 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 d96595bc7f0..56220e520f3 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 b8392525791..40b6fc5d72e 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 7ea11a74a2b..7e1478292e0 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 45b9c6c8521..690df98a2ab 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 37612d3fd7b..5da2ede2937 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 25f416c78f2..37d7919121e 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 1f493452064..3d666807cf9 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 210d8d9e207..398ce771426 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 d70fd96accd..9772d3ef2ef 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 b6404778073..e3411b62eb6 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 c51f1b6eff5..a41a89bb972 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 c2da9f273b3..b9ab27d212c 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 73f87289d3d..9a8b7c857e5 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 5be8460e061..b36369b4285 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 00000000000..3234e9da0ac --- /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 c724213878a..c827d96d855 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 46f9838e84a..32c82edb248 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 723c5a1c340..3a4f304a255 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 045c9dd83d7..00000000000 --- 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 ca1ea4073e6..da6157cf1b6 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 4be62d36917..9bd49855369 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 45f80ecd556..2b4f36fb4b8 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 d49b2204537..ebb92b36b47 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 53d6e36c62c..90d86102aca 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 49566f83d20..67615e1781f 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 117ee13140e..b9cd5a20d50 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 b1fb4f8637d..10dae3c8ff0 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 9f3502e90de..489b9b0e951 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 a1c109e5d62..bdd75de447a 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 a21cb9e7861..27348232ba2 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 43e776e5330..dcf6cacdceb 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 b4281fa18c7..00000000000 --- 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 f748eb29294..a095fd06d2f 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 6acad9134d1..c95e8178594 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 438cc02b477..61213e752f8 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 b9a2c18efdc..1bd93b774f1 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 00000000000..7ddf470b6a0 --- /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 539c45edd94..87d30a4a163 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 00ddd4bf702..012ba235951 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 81594b4972e..71a89af61a2 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 1be6a64f535..00000000000 --- 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 6d1a088721c..cd4a158e427 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 7f66022a2de..59524e0f224 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 2b1aafc546b..6e1ca0d8f2f 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 ce72756303e..b4ba127da25 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 933d78bcbfb..ff763bca307 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 47c14abdedd..42641765c5c 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 923b1ea032f..00000000000 --- 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 2964f0ec462..00000000000 --- 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 67a1541d9bf..839c63986ab 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 2bd5a027a02..a525a49015f 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 c962811a3e4..b9c466657de 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 2ce6eb63290..00000000000 --- 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 7b0e67053a5..8ca1326c96a 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 25f63804858..2c7507cfb8b 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 030ac285f2a..9540453ce3e 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 299e1bc1e08..83f2db03b18 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 1f750e22c65..b4f648ab197 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 9b15c4526e9..915d24dd021 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 096a9167645..08a3fdf869a 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 00000000000..203e2612de4 --- /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 dfccb586ec7..ca21b3bc0de 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 78e4cd2243e..d770bb5b371 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 bf81cfda45f..21939ad0132 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 7f815894069..8e30a42a608 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 bc3368b765c..4dec3838d97 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 ddb46bcb5cb..6cfe4d28778 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 b755a813419..e7c3785c056 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 2790ed6f594..d61ff789b65 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 492f77341f7..da0cfa84c2d 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 f0bedcf2d35..dc82adcb2c6 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 0213576927b..9ee54fef062 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 00000000000..9d7c7afbeac --- /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 00000000000..936dbb354cd --- /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 a964d86a8dc..ee65ae1e2f5 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 7add2921830..bcaedc8bd7c 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 324b698f3b5..fd2a3f43674 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 ac8901fe704..ca60dd239b2 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 ac28c7432ef..3bd592e3982 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 c113b3482ec..00000000000 --- 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 6c4ed203497..00000000000 --- 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 8930ec4b30a..a32679a18db 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 213c54f5f75..e7343e0997f 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 00000000000..b03feded0a7 --- /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 faa3ed1746c..c77ffff43fe 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 454296e82fd..fd353978572 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 a099193cb6e..a3b5296ed1e 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 82b85a18acd..7271dd830ca 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 06ca5169dff..32a42916bd6 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 511f278929e..00000000000 --- 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 f14a2bd4ec0..af326a1a99a 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 d414ee2d1ec..866346990d3 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 fc1c0893b08..b2c35edc44c 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 4a07ebf7fd9..ac32f4866b6 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 00000000000..70dedcf9155 --- /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 5361517b2fc..2d53a5dd66a 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 67030785e06..a9fd97f8915 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 5214a54e909..dd059fb99d3 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 916cf2e7a87..d93bb44ab96 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 1d98b986210..d3e4a762018 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 b179ad7d245..6fee604b554 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 98bacb49562..ab572f2e97b 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 00000000000..5a9e77b63df --- /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 26aad16e2c0..badc7d992bd 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 ae5f30c0004..ee850e2bc1b 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 7ae2582c130..bd50dd4d9a2 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 f8856afc866..1b3900bcbae 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 0f7692aba7f..6b33493b7d2 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 88aecee0815..c6afec443f4 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 16061c9dcbb..06d8660630d 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 0a7e51e974c..5e5aa5170d6 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 f64772b2001..8cb7fa8aa0b 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 66be82777c9..47b236083b3 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 dd01bb99041..b62c4975416 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 344a5e88e6e..55efb624e23 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 59d104a985b..7a890816568 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 63e7f12b7fe..f7d4fe42b57 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 21aee644579..2fc93c5b742 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 f7a00b23480..979b18e3856 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 00000000000..7f4f0e5e000 --- /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 00000000000..ea324b3a9aa --- /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 5f7540d1b16..00000000000 --- 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 00000000000..2336d0f71d5 --- /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 index 559fdd794fc..f9d217e8408 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 00000000000..7a24b76bced --- /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 00000000000..22d7587f6c1 --- /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 00000000000..abc86c72bef --- /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 f1f65981b37..3b6f10d4d9c 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 5120902fa0e..8cd426c71e6 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 00000000000..166a13bd76c --- /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 2f051cea48b..6063b4a0732 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 3b3888a50e9..199000656fe 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 b7a7ca8fcc8..ddad28339c8 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 6d363807d62..e70eb4d01b9 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 f3d032ef986..9b89c5c8007 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 dc8c656e12e..4d9534f49b1 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 00000000000..2e875669967 --- /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 c2e0d97a117..15867f071ef 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 00000000000..81055451b66 --- /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 e77550e7be3..d466dc1af14 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 9689c9c4d38..e6f83167330 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 51030f965a1..bf712b2c7e7 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 37f9e7576f5..a680e5eb5b7 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 49636c3f5f0..1cb53ec6a25 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 4cd1eebdf91..7ffd43e837d 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 f1b2c8dd7f7..a61c6ba5b86 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 743ab0949a1..65f46500a89 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 5cdb5bb8c40..5f477f3c976 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 bded53b2f21..788d9065a7b 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 00000000000..9982b362a10 --- /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 a3b4bd0c9b5..95b80bca7c0 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 dfd2d862237..e90863a5c21 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 d85bcab7885..88759ce7cf3 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 a3eff1a74f8..55ac088bc1d 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 4b74f3348f8..66960551cfd 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 6cba17f6ea2..e4c7649fda0 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 e3476be8fba..9ac4622abc2 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 b626986299b..3860dc5c74c 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 0b1560ac587..ce57465d687 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 1c8758d9420..cf6c79bb50e 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 8b298e821e7..6540ac839cb 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 e60d9559c94..7c2e7f39000 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 45bc68f3220..6875fa74edd 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 39c1b7c235b..a02bf9d4aec 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 00000000000..05a1852cdbd --- /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 00000000000..97305825e07 --- /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 8c85aad5d3b..6cc33aced77 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 76fa7ad282e..bc3234bf0b6 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 16d1d4a9fdd..98400290113 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 52a099c3e16..f80b67a554b 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 e62ad0f4b71..3711b03796e 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 1d2b4d73736..1cc6242c623 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 e4e13426831..ba5e5cdde0b 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 ecea8211393..3e76d76e838 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 43178dee25d..50726a5a51e 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 cbb3e636acc..3440a645408 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 31ba77254a3..2b81d7a2597 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 1e61ea28636..2b1f4160211 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 1b77d94905d..a919fad7040 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 e00f275c55d..00000000000 --- 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 45b76f95563..00000000000 --- 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 a89798ae86f..00000000000 --- 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 73c0b9df834..00000000000 --- 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 d4615b4babf..00000000000 --- 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 74142b05cb5..00000000000 --- 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 b2eaa51cccf..00000000000 --- 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 e572793952a..00000000000 --- 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 63b42ad998e..00000000000 --- 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 f54a02bd70a..00000000000 --- 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 c53bf46ef72..00000000000 --- 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 3c743aa2874..00000000000 --- 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 321587e6957..00000000000 --- 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 aac9ff7888c..00000000000 --- 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 72a0e817617..00000000000 --- 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 3e641089799..00000000000 --- 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 dc9d1f0171a..00000000000 --- 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 37d9cf10258..00000000000 --- 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 82bd94b8089..00000000000 --- 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 8d265533add..00000000000 --- 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 8eb8382b515..00000000000 --- 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 61452006397..00000000000 --- 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 41451a0cabb..00000000000 --- 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 9b0bf201cba..00000000000 --- 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 511a6793f4f..00000000000 --- 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 d378d225ec1..00000000000 --- 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 b0ffe5366a4..00000000000 --- 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 6e506c1cf9d..00000000000 --- 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 676eaaf8c1b..00000000000 --- 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 0d94b33376b..00000000000 --- 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 d762d361aec..00000000000 --- 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 0677fee6b97..00000000000 --- 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 16610615f9d..00000000000 --- 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 30bcdfb543b..00000000000 --- 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 84433656d6c..00000000000 --- 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 4946c6fb0bc..00000000000 --- 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 1555ae84041..00000000000 --- 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 e2695fdafa7..00000000000 --- 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 22e0c1ce994..00000000000 --- 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 5c2107f7aa5..00000000000 --- 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 c6ba89c10e1..00000000000 --- 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 fe06c3aa34a..00000000000 --- 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 dc80cbdb71f..00000000000 --- 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 9bdcce3c47d..00000000000 --- 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 ee07176a267..00000000000 --- 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 d110e8cd63b..00000000000 --- 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 d63de0c2be3..00000000000 --- 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 841d08c33d5..00000000000 --- 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 36d02cf972a..00000000000 --- 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 38947df389c..00000000000 --- 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 5f2d5970a89..00000000000 --- 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 ebd71bea84d..00000000000 --- 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 8a6c0a06ef0..00000000000 --- 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 c01f557aaaa..00000000000 --- 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 aade3d90a80..00000000000 --- 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 7c2e55692b0..00000000000 --- 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 1d24d41e424..00000000000 --- 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 2dc1dfb4b6e..00000000000 --- 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 2deb59e4420..00000000000 --- 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 e47e3f8e898..00000000000 --- 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 80a4dec5971..00000000000 --- 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 de7c68ee1cb..00000000000 --- 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 4fbef622da4..00000000000 --- 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 ed3a510d660..00000000000 --- 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 a6b44090c1c..00000000000 --- 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 050450bf034..00000000000 --- 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 f8d0797197b..00000000000 --- 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 cf86e660876..00000000000 --- 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 18ab024c817..00000000000 --- 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 d5e66ba4d3b..00000000000 --- 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 f5956fe8751..00000000000 --- 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 ac178294c02..00000000000 --- 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 683fbfec513..00000000000 --- 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 b9a971387d1..00000000000 --- 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 b5167724348..00000000000 --- 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 f7cac1445a7..00000000000 --- 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 77adf92578f..00000000000 --- 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 f45ef52e6f0..00000000000 --- 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 13ffbdb192f..00000000000 --- 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 fc8b023d814..00000000000 --- 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 904f3aa32be..00000000000 --- 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 04232a119d9..00000000000 --- 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 fb10baf58cf..00000000000 --- 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 502a2ccbcda..00000000000 --- 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 52170a7c597..00000000000 --- 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 a2b36f7fdf9..00000000000 --- 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 6f2da4136a3..00000000000 --- 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 4c5d041ce81..00000000000 --- 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 65c4d053982..00000000000 --- 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 8eb654c8728..00000000000 --- 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 7783b0ae432..00000000000 --- 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 00000000000..90f5eb08e8c --- /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 4214fd45d14..6b794cfb5c1 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 00000000000..7b4de37ad72 --- /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 00000000000..249cbe704ed --- /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 00000000000..88a890eb3eb --- /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 00000000000..147178e9dcf --- /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 00000000000..cf1e9f912a0 --- /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 00000000000..917e690ba47 --- /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 00000000000..473f355eceb --- /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 00000000000..d29e996852e --- /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 00000000000..7cec79e7ee8 --- /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 00000000000..9695c251352 --- /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 00000000000..0996fdcad73 --- /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 00000000000..ef592305a23 --- /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 00000000000..1595e2b6472 --- /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 00000000000..8f863d85a68 --- /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 00000000000..2911096dd5d --- /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 713d9f733d6..acbb793bbe8 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 6971e08f010..517a9fae6f6 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 fbb1e45bccd..50d2b82c36f 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 fb77901c65b..f5f5d769050 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 723c8acf381..ef37940faf0 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 111c52112eb..2ddaea5a584 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 aa8f1bf5e02..e899e28d219 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 4b57f636a01..397aa87aadf 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 04ea367d518..e16e1e84596 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 5150331e7d7..559553a1108 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 cb0626972e5..01607263008 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 162d4c8bf78..fd18d5c5e97 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 dca22c43f83..355ce31c126 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 cf467a54667..00000000000 --- 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 50c0f560d87..4098da72b30 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 00000000000..bf7e356cad1 --- /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 Binary files differnew file mode 100644 index 00000000000..b7e02f8fa78 --- /dev/null +++ b/doc/development/resources/gitlab_diagram_overview.odg diff --git a/doc/development/resources/gitlab_diagram_overview.png b/doc/development/resources/gitlab_diagram_overview.png Binary files differnew file mode 100644 index 00000000000..b5831cf0a4c --- /dev/null +++ b/doc/development/resources/gitlab_diagram_overview.png diff --git a/doc/install/databases.md b/doc/install/databases.md index 6477e1c967c..481a698a8c8 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 03ea5fbb28f..cc2641f6f37 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 1dba04f4237..ea172733b11 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 f580ea159a2..67ca1895374 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 00000000000..02eadfd410a --- /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 Binary files differnew file mode 100644 index 00000000000..0692a7b458a --- /dev/null +++ b/doc/integration/jira-integration-points.png diff --git a/doc/legal/corporate_contributor_license_agreement.md b/doc/legal/corporate_contributor_license_agreement.md new file mode 100644 index 00000000000..bbc274f3b0c --- /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 00000000000..eaf5812ca4c --- /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 5be0d5980a1..00000000000 --- 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 a84222f9fc2..bfb93a4701c 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 d2da64f3d3c..bdff6ad5da8 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 8fa2ed1311c..9884c9c0fe3 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 00000000000..62bb0f7b40a --- /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 00000000000..7ec3991de85 --- /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 00000000000..c8d66e9636c --- /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 536f22415e2..3aaf17b7727 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 053a50ebc88..5bf8c367734 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 45fc3436ebe..24d96e43bad 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 8599c4323ea..e4eaee91b8e 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 00000000000..39cacd381a3 --- /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 00000000000..fa0f9ce54b6 --- /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 e00dfa3951a..7f89f6bf887 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 5fba0e26afa..7a24c11c223 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 3b1d9878204..bcba3ee4d05 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 2fb762e467a..ac8f0a77efd 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 00000000000..e01cc589a83 --- /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 747b4860796..7342a9117c6 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 00000000000..7f916047369 --- /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 00000000000..6874056498b --- /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 00000000000..2b1fa2744fe --- /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 00000000000..b284ff48365 --- /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 00000000000..3fc068c0ae2 --- /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 00000000000..1eec5a8f396 --- /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 226d3d5d5b5..15fcda45e40 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 00000000000..5f16120b7cc --- /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 00000000000..399c9b53d81 --- /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 895b89aa38a..d316b2d9205 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 cad65b0d79a..de560300735 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 9fec19a4dc1..ca3e67d2c1d 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 c74b0993fb3..8b6ee6fd67f 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 00000000000..9aac29384ba --- /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 fe470f5ac99..cbe8b321507 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 00000000000..b5477d3c7ab --- /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 63f27c3acc3..946f6760126 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 ceae08c1074..22beb1c50bc 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 59eda4a781d..d8bb1d55e2d 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 00000000000..ce197912f64 --- /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 e685c385d1d..46b983e8f9a 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 ee26f5371a9..fd9a2f01a28 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 00000000000..04467b66648 --- /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 00000000000..3038c0814ad --- /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 178a769194c..57fe834b4bf 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 f14c5f396be..ccafe09c18f 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 00000000000..a35fa34a3a2 --- /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 b4591f227e3..013fa6da8b4 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 bde32128b92..3526006c94a 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 fcf4296ad11..1344edfa80b 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 6c1fa39f081..62d84506c49 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 00000000000..1bc69555b56 --- /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 99ec77a7613..0b0f401c3ba 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 5b2a6321265..33ae6c72998 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 65bfc505d85..65ca824bb5b 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 7f7492bfd6d..914da31322f 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 a96b086fae5..92728d474b2 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 00000000000..dfbe762c438 --- /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 e77825411f3..ef29cc67a4e 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 650bc3a16f7..d667b58240f 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 71360fb6bd5..069086d5eac 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 858c7d11b32..c00d9014b1d 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 f7bf085a423..4cc99f8af55 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 50942b3cbb3..89fe5fdeadf 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 36555fb8e8c..19ff3244543 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 00000000000..c2fd4e15c9e --- /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 801fff78a52..4a503dfaf4f 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 915190f3dae..0907cdb526f 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 00000000000..1209aae6434 --- /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 7c70482deb5..0c5f05a0a68 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 c4d0d176f3a..85962221c0f 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 00000000000..3d330e837c1 --- /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 127adecf7ed..c7d9ece6feb 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 a24100ff8c0..54b3f18e084 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 5082b31198a..c3a76bea269 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 efebba1be24..ffc5016529f 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 7aba412d751..6146599cc4a 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 00000000000..76ffea1bb6f --- /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 e9a4d56e36b..84a5ebbf7a7 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 8c501bbc537..df05754c287 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 c30eccce1c5..d287121bb84 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 c5d8b62bfe7..7360482d736 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 3afe019adf6..09337937002 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 bbdf5b97c84..fed54659ebc 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 15d6da6db3d..2d7ffc866e7 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 8798e62ea72..0186002c559 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 c4c9f166db1..283f7642f67 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 218b3d8eee2..7f5a125038c 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 ab949f530ab..8f54d0d4d84 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 00000000000..213604915a6 --- /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 396554404af..03f027706de 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 2b0c672c7fa..f8c48e2f3b2 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 79f8eb3a543..ed6b50c3a6a 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 a15203d1563..3d15c35b8cc 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 d690f1d07e7..0f62cac9a0c 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 aee12e7dc40..f7e63b23093 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 00000000000..f9f2ed90ccc --- /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 cb2bc764476..f21907b1ffc 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 738974955f3..c271dd8b61b 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 bee6544ea3d..8e09fff6843 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 cf357b23c40..888aa7e77d2 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 c2b229b0172..cad64760abb 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 00000000000..bde502e32e1 --- /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 54d3aeecb70..ae808b6272b 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 c4fb2e2e159..ebb4f289c52 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 258a0fb2589..efaefa4ce44 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 c5e3d049fd7..20fd5ba92a1 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 462d3f1e274..e79da7e8fd2 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 53bc079296a..e51cb30bdd9 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 0f196297477..955abc1bedd 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 c522e0a413b..60c03ce1c04 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 5ac9e9f325b..cb747fe0137 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 c819ce56ac9..7121c8e40d2 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 2f9091e07df..6bc2c3b487c 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 a1ff248a77f..6e4de197eeb 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 260bacfeeb0..fd36dda7d22 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 1b32b99f4ba..529753c4019 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 2f30fde2078..5283cf0b821 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 b4be46d3b42..d18fc8bf2ce 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 00000000000..30462999aa3 --- /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 index d793d0ba8dc..cbdf70f7d12 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 00000000000..7701a6d5d60 --- /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 00000000000..15e9b7a6f77 --- /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 156483be8dd..85615f282c4 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 6cb7814fae5..353c3024aad 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 89604162304..44237a062fc 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 00000000000..f46685e4bbe --- /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 00000000000..eada9bcddf5 --- /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 d9c2d3b626d..6da0c1d6f96 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 0d2f8418bcf..b96f73058b6 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 0248284f8d5..c6e570784e0 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 00000000000..9951bacedf5 --- /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 00000000000..df9398d0795 --- /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 3e929c52990..882f0386046 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 c270232edba..612a9ba93a8 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 6e2a59f62ac..c91dedf74c7 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 927748c0fd5..201f34ab546 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 8fa89270854..cbfa736c84c 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 ac2c4577c77..c46d5855faf 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 d0e9dfe46a1..e91678473a8 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 5b78e3e38cb..c84b9e90e4b 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 00000000000..d171eccc927 --- /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 Binary files differindex 057f74ac7ab..bfb74960c48 100644 --- a/public/favicon.ico +++ b/public/favicon.ico diff --git a/public/gitlab_logo.png b/public/gitlab_logo.png Binary files differindex e3cda5978ab..dbe6dabb784 100644 --- a/public/gitlab_logo.png +++ b/public/gitlab_logo.png diff --git a/script/background_jobs b/script/background_jobs new file mode 100755 index 00000000000..623e26a2831 --- /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 00000000000..a5caecf8526 --- /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 00000000000..5464ed040aa --- /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 db27742b9b5..00000000000 --- 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 8b2a49dbee5..00000000000 --- 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 d528d12c66c..e1c0269b295 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 479d8fc1a1d..cea6922e1c3 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 fdf0884f4e2..f5822157ea4 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 8263afc97a2..fbf4f29acfd 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 69708edd8b1..1502bded97f 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 bb1232e6264..479118a3465 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 56561fe4595..8c12c9b3e19 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 00000000000..6339d5c4003 --- /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 2ea569a6208..a507f0314c6 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 0c09279e3dc..1d225e8bad0 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 ba580d9484d..da723ae39bd 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 80c9f5d7f14..b67ce3c67f1 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 7754b28347a..078c257538f 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 00000000000..8bb1e259efa --- /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 7f3f8c50f02..0402ff39735 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 267643fd8ef..7e6a39fad69 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 229f49659cf..c58c83a2970 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 00000000000..1338ce4873d --- /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 d49247accc2..59abfb38ec0 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 3595af32431..9c95bc044f3 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 f97959ee8f4..c1efc1fb2a0 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 00000000000..114058e3095 --- /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 00000000000..733f2754727 --- /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 ef8e4cf6375..fa8a3f554f7 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 8d2e2038a55..1090cb7f620 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 2e52479ccbb..9c1b588861d 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 34b418a9ca3..b4919802afe 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 e05fde95731..073b811c3fb 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 aac72c63ea5..7b3818ea5c8 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 b1c583c0476..a0e74c49631 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 7d805f8c72a..19259a8b79c 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 5e0a825c3c3..d65e7c42b7e 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 3be14383e06..ef06c742846 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 00000000000..2b254d6b3a6 --- /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 e21074554b6..3dfe95a8e38 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 0787bdbea6f..d53dc17d977 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 00000000000..395aa4a4444 --- /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 00000000000..cf0b36a2830 --- /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 fa556f94a1d..d8ab171d3ee 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 852146ebaec..0827e4f162b 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 85bdf08ae64..53ede0d5ee9 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 00000000000..cd553b33ad7 --- /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 44b8c6155be..e719e3bfcc8 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 aa850dfd0a3..9e07d9ee191 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 4a08ad3bb15..686e43d8d10 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 703f46adba3..f1ad679b658 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 42c405d8e50..7a00ee83ba4 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 47ae760a7ed..6bae5951b7b 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 0b0262c97f1..40a5fbc71d9 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 667c80bcf19..46b3bf39aeb 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 a9ed6a5fa7c..6a0d99dcc53 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 f7e242af00a..3b09f7978bc 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 @@ -286,6 +312,65 @@ describe User do user.all_ssh_keys.should include(key.key) 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 2d9301732dd..d6034081018 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 4155ff31193..9a0a2c4329c 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 3f5250a0040..6ad7c4d81da 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 f9b96c255c1..f8693355b23 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 b74fceb98b1..9aeade535f9 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 3bf562edbb7..2ab99c33b78 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 e33d8cc50fd..dea90b2bfa7 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 00000000000..acef7df8777 --- /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 a6ce72e11e9..1a5f11038b7 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 e8870f4d5d8..5f6dff92c0a 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 a97d6a282a9..c55025d72b5 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 2f11f562aa1..412b6c95ffa 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 246fe262ce8..febfc63921e 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 00000000000..2b1a4bf6ec8 --- /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 ba18b123039..6ed96eb97f3 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 00000000000..c8ace0b9462 --- /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 b8c0b6f33ed..342587ba5d6 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 2e509ea2933..47008728252 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 00000000000..aecd18bc14a --- /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 0fd90c567e0..668007dc29f 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 4ef78b8e5d0..c4be5102002 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 20c04e94c24..97f7392e50a 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 f5ffb211530..8929a48973d 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 @@ -190,6 +183,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 Binary files differindex 3ffafed18d0..ee8c3f23c70 100644 --- a/spec/seed_project.tar.gz +++ b/spec/seed_project.tar.gz diff --git a/spec/services/filtering_service_spec.rb b/spec/services/filtering_service_spec.rb new file mode 100644 index 00000000000..596601264b3 --- /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 index ed51b0c3f8e..b6573095dbd 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 e3c25fa0469..90738c681fa 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 index 058e43ba090..548109a8450 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 a112835d4d0..09a5debe1dc 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 00000000000..0a41832a211 --- /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 00000000000..1854c0d8233 --- /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 00000000000..457cb3c0ca3 --- /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 7f1590f559e..f1df7e55dd0 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 00000000000..76af5bf7b88 --- /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 42c9342c77a..00000000000 --- 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 025534a900d..cc0ec2f4e3d 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 a7f189777c8..3802e94ecf0 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 203e661f514..43aec1cd43d 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 cba243226db..a5541bee876 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 46e86dbe00a..e6bf79b853c 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 Binary files differindex 247e52a5f42..c56eef05eb9 100644 --- a/vendor/assets/images/authbuttons/github_32.png +++ b/vendor/assets/images/authbuttons/github_32.png diff --git a/vendor/assets/images/authbuttons/github_64.png b/vendor/assets/images/authbuttons/github_64.png Binary files differindex fca7bf44652..39de55bc796 100644 --- a/vendor/assets/images/authbuttons/github_64.png +++ b/vendor/assets/images/authbuttons/github_64.png diff --git a/vendor/assets/images/authbuttons/google_32.png b/vendor/assets/images/authbuttons/google_32.png Binary files differindex 3909e9de93b..6225cc9c2d7 100644 --- a/vendor/assets/images/authbuttons/google_32.png +++ b/vendor/assets/images/authbuttons/google_32.png diff --git a/vendor/assets/images/authbuttons/google_64.png b/vendor/assets/images/authbuttons/google_64.png Binary files differindex e55f34f1b7d..4d608f71008 100644 --- a/vendor/assets/images/authbuttons/google_64.png +++ b/vendor/assets/images/authbuttons/google_64.png diff --git a/vendor/assets/images/authbuttons/twitter_32.png b/vendor/assets/images/authbuttons/twitter_32.png Binary files differindex daadcffd315..696eb02484d 100644 --- a/vendor/assets/images/authbuttons/twitter_32.png +++ b/vendor/assets/images/authbuttons/twitter_32.png diff --git a/vendor/assets/images/authbuttons/twitter_64.png b/vendor/assets/images/authbuttons/twitter_64.png Binary files differindex 68b74530c06..2893274766f 100644 --- a/vendor/assets/images/authbuttons/twitter_64.png +++ b/vendor/assets/images/authbuttons/twitter_64.png diff --git a/vendor/assets/images/bg_fallback.png b/vendor/assets/images/bg_fallback.png Binary files differindex 4b2754b8040..d9066ad7d7b 100644 --- a/vendor/assets/images/bg_fallback.png +++ b/vendor/assets/images/bg_fallback.png diff --git a/vendor/assets/images/icon_sprite.png b/vendor/assets/images/icon_sprite.png Binary files differindex 636c80f2216..9ad65fc443b 100644 --- a/vendor/assets/images/icon_sprite.png +++ b/vendor/assets/images/icon_sprite.png diff --git a/vendor/assets/images/progress_bar.gif b/vendor/assets/images/progress_bar.gif Binary files differindex 156fbb53137..c3d43fa40b2 100644 --- a/vendor/assets/images/progress_bar.gif +++ b/vendor/assets/images/progress_bar.gif diff --git a/vendor/assets/images/slider_handles.png b/vendor/assets/images/slider_handles.png Binary files differindex b95a46eca97..a6d477033fa 100644 --- a/vendor/assets/images/slider_handles.png +++ b/vendor/assets/images/slider_handles.png diff --git a/vendor/assets/images/ui-icons_222222_256x240.png b/vendor/assets/images/ui-icons_222222_256x240.png Binary files differindex b273ff111d2..8bc06cbf03b 100644 --- a/vendor/assets/images/ui-icons_222222_256x240.png +++ b/vendor/assets/images/ui-icons_222222_256x240.png diff --git a/vendor/assets/images/ui-icons_454545_256x240.png b/vendor/assets/images/ui-icons_454545_256x240.png Binary files differindex 59bd45b907c..cfd1eaffaae 100644 --- a/vendor/assets/images/ui-icons_454545_256x240.png +++ b/vendor/assets/images/ui-icons_454545_256x240.png diff --git a/vendor/assets/javascripts/ace-src-noconflict/mode-diff.js b/vendor/assets/javascripts/ace-src-noconflict/mode-diff.js index 75e26cc7056..8d1e7cee096 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 00000000000..d8acc5c5320 --- /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 00000000000..f2429be6228 --- /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 |