diff --git a/.travis.yml b/.travis.yml index c137aec907c2358f74141eb2453082ca740ef7f6..75b4c5c7030cd08ad09b6fa3570ddc23e7ae88ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,6 @@ env: - TASK=jasmine:ci before_install: - sudo apt-get install libicu-dev -y - - gem install charlock_holmes -v="0.6.9" branches: only: - 'master' @@ -17,9 +16,12 @@ rvm: - 2.0.0 services: - mysql + - redis-server before_script: - "cp config/database.yml.$DB config/database.yml" - "cp config/gitlab.yml.example config/gitlab.yml" - "bundle exec rake db:setup" - "bundle exec rake db:seed_fu" script: "bundle exec rake $TASK --trace" +notifications: + email: false diff --git a/CHANGELOG b/CHANGELOG index 393d69755e168535f43521950205c74c693fbf4f..3fa5143d43ffbc253694153cb8baf6666f4eec6c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,15 @@ +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) + 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) @@ -9,6 +21,34 @@ v 6.3.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 project pages are now visible to everyone (files, issues, wik, etc.) @@ -30,7 +70,7 @@ v 6.2.0 - 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 his email if signup enabled + - User must confirm their email if signup enabled - User must confirm changed email v 6.1.0 @@ -52,7 +92,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 @@ -95,6 +135,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 d1fdd93850a6e1c478d3f131137b378fa3fdc3e7..62dc5d60b5fad73d5a356d923f722e18018ef6f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,6 +9,14 @@ This guide details how to use issues and pull requests to improve GitLab. If you want to know how the GitLab team handles contributions have a look at [the GitLab contributing process](PROCESS.md). +## 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 pull 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. @@ -74,6 +82,3 @@ We will accept pull requests if: * 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). - -## Security vulnerabilities -Please report security vulnerabilities in private to support@gitlab.com; also see http://www.gitlab.com/disclosure/. Do NOT create GitHub issues for security vulnerabilities. diff --git a/Gemfile b/Gemfile index a543c5e3a6df9a718bd39a00943f1cac78c3228c..f89fae0095f3b251bca8ca31bf166351838369f5 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,7 @@ def linux_only(require_as) RUBY_PLATFORM.include?('linux') && require_as end -gem "rails", "3.2.15" +gem "rails", "3.2.16" # Supported DBs gem "mysql2", group: :mysql @@ -24,26 +24,27 @@ gem 'omniauth-github' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", "~> 3.0.0.rc2" +gem "gitlab_git", "~> 3.1.0" # Ruby/Rack Git Smart-HTTP Server Handler -gem 'gitlab-grack', '~> 1.0.1', require: 'grack' +gem 'gitlab-grack', '~> 1.1.0', 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-pygments.rb", '~> 0.5.4', require: 'pygments.rb' # Git Wiki -gem "gitlab-gollum-lib", "~> 1.0.1", require: 'gollum-lib' +gem "gitlab-gollum-lib", "~> 1.0.2", require: 'gollum-lib' # Language detection -gem "github-linguist", require: "linguist" +gem "gitlab-linguist", "~> 2.9.6", require: "linguist" # API gem "grape", "~> 0.4.1" gem "grape-entity", "~> 0.3.0" +gem 'rack-cors', require: 'rack/cors' # Format dates and times # based on human-friendly examples @@ -135,7 +136,7 @@ group :assets do gem 'turbolinks' gem 'jquery-turbolinks' - gem 'chosen-rails', "1.0.0" + gem 'chosen-rails', "1.0.1" gem 'select2-rails' gem 'jquery-atwho-rails', "0.3.0" gem "jquery-rails", "2.1.3" diff --git a/Gemfile.lock b/Gemfile.lock index 0b4bf8fe32783c1fc3e67cce4938b0886e026f99..23615fc692f8ce51b777697fb4570fb82e570c12 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,12 +1,12 @@ GEM remote: https://rubygems.org/ specs: - actionmailer (3.2.15) - actionpack (= 3.2.15) + actionmailer (3.2.16) + actionpack (= 3.2.16) mail (~> 2.5.4) - actionpack (3.2.15) - activemodel (= 3.2.15) - activesupport (= 3.2.15) + actionpack (3.2.16) + activemodel (= 3.2.16) + activesupport (= 3.2.16) builder (~> 3.0.0) erubis (~> 2.7.0) journey (~> 1.0.4) @@ -14,18 +14,18 @@ GEM rack-cache (~> 1.2) rack-test (~> 0.6.1) sprockets (~> 2.2.1) - activemodel (3.2.15) - activesupport (= 3.2.15) + activemodel (3.2.16) + activesupport (= 3.2.16) builder (~> 3.0.0) - activerecord (3.2.15) - activemodel (= 3.2.15) - activesupport (= 3.2.15) + activerecord (3.2.16) + activemodel (= 3.2.16) + activesupport (= 3.2.16) arel (~> 3.0.2) tzinfo (~> 0.3.29) - activeresource (3.2.15) - activemodel (= 3.2.15) - activesupport (= 3.2.15) - activesupport (3.2.15) + activeresource (3.2.16) + activemodel (= 3.2.16) + activesupport (= 3.2.16) + activesupport (3.2.16) i18n (~> 0.6, >= 0.6.4) multi_json (~> 1.0) acts-as-taggable-on (2.4.1) @@ -34,11 +34,11 @@ GEM annotate (2.6.0.beta2) activerecord (>= 2.3.0) rake (>= 0.8.7) - arel (3.0.2) + arel (3.0.3) asciidoctor (0.1.3) awesome_print (1.2.0) backports (3.3.2) - bcrypt-ruby (3.1.1) + bcrypt-ruby (3.1.2) better_errors (1.0.1) coderay (>= 1.0.0) erubis (>= 2.6.6) @@ -61,12 +61,12 @@ GEM charlock_holmes (0.6.9.4) childprocess (0.3.9) ffi (~> 1.0, >= 1.0.11) - chosen-rails (1.0.0) + chosen-rails (1.0.1) coffee-rails (>= 3.2) compass-rails (>= 1.0) railties (>= 3.0) sass-rails (>= 3.2) - chunky_png (1.2.8) + chunky_png (1.2.9) cliver (0.2.1) code_analyzer (0.4.3) sexp_processor @@ -77,7 +77,7 @@ GEM 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) @@ -101,14 +101,14 @@ GEM database_cleaner (1.1.1) debug_inspector (0.0.2) descendants_tracker (0.0.1) - devise (2.2.5) + devise (2.2.8) bcrypt-ruby (~> 3.0) orm_adapter (~> 0.1) railties (~> 3.1) warden (~> 1.2.1) devise-async (0.8.0) devise (>= 2.2, < 3.2) - diff-lcs (1.2.4) + diff-lcs (1.2.5) dotenv (0.8.0) email_spec (1.4.0) launchy (~> 2.1) @@ -119,8 +119,7 @@ GEM escape_utils (0.2.4) eventmachine (1.0.3) excon (0.13.4) - execjs (1.4.0) - multi_json (~> 1.0) + execjs (2.0.2) factory_girl (4.2.0) activesupport (>= 3.0.0) factory_girl_rails (4.2.1) @@ -151,38 +150,39 @@ GEM 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-markdown (0.5.5) github-markup (0.7.5) gitlab-flowdock-git-hook (0.4.2.2) gitlab-grit (>= 2.4.1) multi_json - gitlab-gollum-lib (1.0.1) + gitlab-gollum-lib (1.0.2) github-markdown (~> 0.5.3) github-markup (>= 0.7.5, < 1.0.0) - gitlab-grit (>= 2.5.1) + gitlab-grit (~> 2.6.1) + gitlab-pygments.rb (~> 0.5.4) nokogiri (~> 1.5.9) - pygments.rb (~> 0.4.2) sanitize (~> 2.0.3) stringex (~> 1.5.1) - gitlab-grack (1.0.1) + gitlab-grack (1.1.0) rack (~> 1.4.1) - gitlab-grit (2.6.1) + gitlab-grit (2.6.3) charlock_holmes (~> 0.6.9) diff-lcs (~> 1.1) mime-types (~> 1.15) posix-spawn (~> 0.3.6) - gitlab-pygments.rb (0.3.2) + gitlab-linguist (2.9.6) + charlock_holmes (~> 0.6.6) + escape_utils (~> 0.2.4) + gitlab-pygments.rb (~> 0.5.4) + mime-types (~> 1.19) + gitlab-pygments.rb (0.5.4) posix-spawn (~> 0.3.6) yajl-ruby (~> 1.1.0) - gitlab_git (3.0.0.rc2) + gitlab_git (3.1.0) activesupport (~> 3.2.13) - github-linguist (~> 2.3.4) gitlab-grit (~> 2.6.1) + gitlab-linguist (~> 2.9.5) + gitlab-pygments.rb (~> 0.5.4) gitlab_meta (6.0) gitlab_omniauth-ldap (1.0.3) net-ldap (~> 0.3.1) @@ -235,7 +235,7 @@ GEM multi_json (~> 1.0) multi_xml (>= 0.5.2) httpauth (0.2.0) - i18n (0.6.5) + i18n (0.6.9) jasmine (1.3.2) jasmine-core (~> 1.3.1) rack (~> 1.0) @@ -274,7 +274,7 @@ GEM mime-types (~> 1.16) treetop (~> 1.4.8) method_source (0.8.1) - mime-types (1.25) + mime-types (1.25.1) minitest (4.7.4) modernizr (2.6.2) sprockets (~> 2.0) @@ -312,7 +312,7 @@ GEM omniauth-twitter (0.0.17) multi_json (~> 1.3) omniauth-oauth (~> 1.0) - orm_adapter (0.4.0) + orm_adapter (0.5.0) pg (0.15.1) poltergeist (1.4.1) capybara (~> 2.1.0) @@ -325,9 +325,6 @@ GEM coderay (~> 1.0.5) 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) @@ -338,6 +335,7 @@ GEM 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) @@ -348,14 +346,14 @@ GEM rack rack-test (0.6.2) rack (>= 1.0) - rails (3.2.15) - actionmailer (= 3.2.15) - actionpack (= 3.2.15) - activerecord (= 3.2.15) - activeresource (= 3.2.15) - activesupport (= 3.2.15) + rails (3.2.16) + actionmailer (= 3.2.16) + actionpack (= 3.2.16) + activerecord (= 3.2.16) + activeresource (= 3.2.16) + activesupport (= 3.2.16) bundler (~> 1.0) - railties (= 3.2.15) + railties (= 3.2.16) rails-dev-tweaks (0.6.1) actionpack (~> 3.1) railties (~> 3.1) @@ -368,9 +366,9 @@ GEM i18n require_all ruby-progressbar - railties (3.2.15) - actionpack (= 3.2.15) - activesupport (= 3.2.15) + railties (3.2.16) + actionpack (= 3.2.16) + activesupport (= 3.2.16) rack-ssl (~> 1.3.2) rake (>= 0.8.7) rdoc (~> 3.4) @@ -431,7 +429,7 @@ GEM safe_yaml (0.9.3) sanitize (2.0.3) nokogiri (>= 1.4.4, < 1.6) - sass (3.2.11) + sass (3.2.12) sass-rails (3.2.6) railties (~> 3.2.0) sass (>= 3.1.10) @@ -559,7 +557,7 @@ DEPENDENCIES bootstrap-sass capybara carrierwave - chosen-rails (= 1.0.0) + chosen-rails (= 1.0.1) coffee-rails colored coveralls @@ -575,13 +573,13 @@ DEPENDENCIES font-awesome-rails foreman gemoji (~> 1.2.1) - github-linguist github-markup (~> 0.7.4) gitlab-flowdock-git-hook (~> 0.4.2) - gitlab-gollum-lib (~> 1.0.1) - gitlab-grack (~> 1.0.1) - gitlab-pygments.rb (~> 0.3.2) - gitlab_git (~> 3.0.0.rc2) + gitlab-gollum-lib (~> 1.0.2) + gitlab-grack (~> 1.1.0) + gitlab-linguist (~> 2.9.6) + gitlab-pygments.rb (~> 0.5.4) + gitlab_git (~> 3.1.0) gitlab_meta (= 6.0) gitlab_omniauth-ldap (= 1.0.3) gon @@ -613,8 +611,9 @@ DEPENDENCIES pry quiet_assets (~> 1.0.1) rack-attack + rack-cors rack-mini-profiler - rails (= 3.2.15) + rails (= 3.2.16) rails-dev-tweaks rails_best_practices raphael-rails (~> 2.1.2) diff --git a/README.md b/README.md index 64e40fdc51af3c683eb8edd44a21099879ad3e6b..84a11ea05287b0b48af83414e3c797671b2f0264 100644 --- a/README.md +++ b/README.md @@ -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://github.com/gitlabhq/gitlab-ci/blob/master/README.md) is a continuous integration (CI) server that is easy to integrate with GitLab. ### Requirements @@ -44,31 +46,24 @@ ** More details are in the [requirements doc](doc/install/requirements.md) -### 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. * [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. @@ -79,7 +74,6 @@ Since 2011 GitLab is released on the 22nd of every month. Every new release incl * 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: @@ -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 @@ -147,15 +141,17 @@ 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. * [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 is unofficial but offers a way to get in touch with other GitLab users who may be able to help you. + +* [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 diff --git a/VERSION b/VERSION index 5a903253f44ecc70589298653e9c71e9eab1fb03..26cb0a3b27ce5917b17f9144d295a45ad11bece4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.3.0.pre +6.4.0.pre diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico index 057f74ac7ab0192514a2dc21bfc955e5700fb65d..bfb74960c480e6cb14f1d38437303af6b375ccaf 100644 Binary files a/app/assets/images/favicon.ico and b/app/assets/images/favicon.ico differ diff --git a/app/assets/images/logo-black.png b/app/assets/images/logo-black.png index 6567f2e546364c898056691dbbe0a54bb427b362..31c4a63cd08619dab4c647af9b11f1765c35520c 100644 Binary files a/app/assets/images/logo-black.png and b/app/assets/images/logo-black.png differ diff --git a/app/assets/images/logo-white.png b/app/assets/images/logo-white.png index a63fb1c9c0a9fd630b394ecd40cd45e6c7f22f6f..8a4ec851b1d9f62173bb2d0dd827d7a76ce80a1a 100644 Binary files a/app/assets/images/logo-white.png and b/app/assets/images/logo-white.png differ diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee index a36d944cbcb6f9d9f31372abe516b529a6c5a1ad..5f4a38ebbd00735d88cb1be2f680871bdf762611 100644 --- a/app/assets/javascripts/api.js.coffee +++ b/app/assets/javascripts/api.js.coffee @@ -2,6 +2,7 @@ users_path: "/api/:version/users.json" user_path: "/api/:version/users/:id.json" notes_path: "/api/:version/projects/:id/notes.json" + namespaces_path: "/api/:version/namespaces.json" # Get 20 (depends on api) recent notes # and sort the ascending from oldest to newest @@ -49,6 +50,20 @@ ).done (users) -> callback(users) + # Return namespaces list. Filtered by query + namespaces: (query, callback) -> + url = Api.buildUrl(Api.namespaces_path) + + $.ajax( + url: url + data: + private_token: gon.api_token + search: query + per_page: 20 + dataType: "json" + ).done (namespaces) -> + callback(namespaces) + buildUrl: (url) -> url = gon.relative_url_root + url if gon.relative_url_root? return url.replace(':version', gon.api_version) diff --git a/app/assets/javascripts/blob.js.coffee b/app/assets/javascripts/blob.js.coffee index 03e280f89769471bac2ece86eaedf114fe9c76fb..99cb1cec911b9d99eb3579f0dac33ac8fdda2960 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/commits.js.coffee b/app/assets/javascripts/commits.js.coffee index de4c06a2728ed4d97a0ddb6049eee053a2c1c130..9c004c997edf698681516ce185b96ae16c119582 100644 --- a/app/assets/javascripts/commits.js.coffee +++ b/app/assets/javascripts/commits.js.coffee @@ -4,13 +4,13 @@ class CommitsList limit: 0 offset: 0 @disable = false - + @showProgress: -> $('.loading').show() - + @hideProgress: -> $('.loading').hide() - + @init: (ref, limit) -> $(".day-commits-table li.commit").live 'click', (event) -> if event.target.nodeName != "A" @@ -21,7 +21,7 @@ class CommitsList @data.ref = ref @data.limit = limit @data.offset = limit - + this.initLoadMore() this.showProgress() @@ -32,7 +32,9 @@ class CommitsList url: location.href data: @data complete: this.hideProgress - dataType: "script" + success: (data) -> + CommitsList.append(data.count, data.html) + dataType: "json" @append: (count, html) -> $("#commits-list").append(html) @@ -40,7 +42,7 @@ class CommitsList @data.offset += count else @disable = true - + @initLoadMore: -> $(document).unbind('scroll') $(document).endlessScroll diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee index 67d9498c50a6a7f4938599ffd7116363bbae98b2..c273ddbd39147b0ab9aaf916d91fce01428b9303 100644 --- a/app/assets/javascripts/issues.js.coffee +++ b/app/assets/javascripts/issues.js.coffee @@ -22,7 +22,7 @@ backgroundColor: '#DDD' opacity: .4 ) - + reload: -> Issues.initSelects() Issues.initChecks() @@ -54,7 +54,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") diff --git a/app/assets/javascripts/main.js.coffee b/app/assets/javascripts/main.js.coffee index ccd5ad48dbfdad4b905782010694f26f93de327f..a9bcb2fb2e44bb88c9e8b5f05362ec40d90fff44 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() diff --git a/app/assets/javascripts/merge_requests.js.coffee b/app/assets/javascripts/merge_requests.js.coffee index 5400bc5c1ad08199d92e0007f2ec785307593bbe..2eef7df1c64ebe48b2dd9bbd6235a242107c0c1c 100644 --- a/app/assets/javascripts/merge_requests.js.coffee +++ b/app/assets/javascripts/merge_requests.js.coffee @@ -21,7 +21,7 @@ class MergeRequest this.initMergeWidget() this.$('.show-all-commits').on 'click', => this.showAllCommits() - + modal = $('#modal_merge_info').modal(show: false) # Local jQuery finder @@ -83,12 +83,12 @@ class MergeRequest url: this.$('.nav-tabs .diffs-tab a').attr('href') beforeSend: => this.$('.status').addClass 'loading' - complete: => @diffs_loaded = true this.$('.status').removeClass 'loading' - - dataType: 'script' + success: (data) => + this.$(".diffs").html(data.html) + dataType: 'json' showAllCommits: -> this.$('.first-commits').remove() diff --git a/app/assets/javascripts/namespace_select.js.coffee b/app/assets/javascripts/namespace_select.js.coffee new file mode 100644 index 0000000000000000000000000000000000000000..00d135d1449d860aaa6fbefa88bc8dded9823f3d --- /dev/null +++ b/app/assets/javascripts/namespace_select.js.coffee @@ -0,0 +1,24 @@ +$ -> + namespaceFormatResult = (namespace) -> + markup = "<div class='namespace-result'>" + markup += "<span class='namespace-kind'>" + namespace.kind + "</span>" + markup += "<span class='namespace-path'>" + namespace.path + "</span>" + markup += "</div>" + markup + + formatSelection = (namespace) -> + namespace.kind + ": " + namespace.path + + $('.ajax-namespace-select').each (i, select) -> + $(select).select2 + placeholder: "Search for namespace" + multiple: $(select).hasClass('multiselect') + minimumInputLength: 0 + query: (query) -> + Api.namespaces query.term, (namespaces) -> + data = { results: namespaces } + query.callback(data) + + dropdownCssClass: "ajax-namespace-dropdown" + formatResult: namespaceFormatResult + formatSelection: formatSelection diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 5225623c1f0ce7e273a04bea18eb71a94ffe09ed..e39bfc0f7924950ad03429d7ea4a1c32123664a6 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -6,7 +6,7 @@ var NoteList = { target_type: null, init: function(tid, tt, path) { - NoteList.notes_path = path + ".js"; + NoteList.notes_path = path + ".json"; NoteList.target_id = tid; NoteList.target_type = tt; NoteList.target_params = "target_type=" + NoteList.target_type + "&target_id=" + NoteList.target_id; @@ -233,10 +233,12 @@ var NoteList = { 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(); + if (form.find(".note-original-content").length === 0) { + 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(); }, @@ -409,7 +411,10 @@ var NoteList = { data: NoteList.target_params, complete: function(){ $('.js-notes-busy').removeClass("loading")}, beforeSend: function() { $('.js-notes-busy').addClass("loading") }, - dataType: "script" + success: function(data) { + NoteList.setContent(data.html); + }, + dataType: "json" }); }, @@ -417,7 +422,7 @@ var NoteList = { * Called in response to getContent(). * Replaces the content of #notes-list with the given html. */ - setContent: function(newNoteIds, html) { + setContent: function(html) { $("#notes-list").html(html); }, @@ -532,6 +537,8 @@ var NoteList = { note_text.html(response.note).show(); var note_form = note_li.find(".note-edit-form"); + var original_content = note_form.find(".note-original-content"); + original_content.remove(); note_form.hide(); note_form.find(".btn-save").enableButton(); diff --git a/app/assets/javascripts/pager.js.coffee b/app/assets/javascripts/pager.js.coffee index 5bd11d273a7fed6a6f09112bbece0aacd99c1a31..1f763e8b9561f52fbf71721f6541d6685aa8fa2f 100644 --- a/app/assets/javascripts/pager.js.coffee +++ b/app/assets/javascripts/pager.js.coffee @@ -19,8 +19,9 @@ data: "limit=" + @limit + "&offset=" + @offset complete: -> $(".loading").hide() - - dataType: "script" + success: (data) -> + Pager.append(data.count, data.html) + dataType: "json" append: (count, html) -> $(".content_list").append html diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index bdb18574b6d43f86af016007a093bb4a2fe8be69..9a41ec7a0becf77fdfda0c7622a8bfe9f063a245 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -40,3 +40,9 @@ $ -> # 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/stylesheets/common.scss b/app/assets/stylesheets/common.scss index dbd50b4dcc824ae0264da3dd1748671ef351c3d8..b57fe826b09a2d5c7d6434ce852ec558b918a303 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -220,7 +220,6 @@ li.note { .error-message { padding: 10px; background: #C67; - padding-left: 20px; margin: 0; color: #FFF; @@ -228,8 +227,18 @@ li.note { color: #fff; text-decoration: underline; } - &.centered { - text-align: center; +} + +.no-ssh-key-message { + padding: 10px 0; + background: #C67; + margin: 0; + color: #FFF; + text-align: center; + + a { + color: #fff; + text-decoration: underline; } } @@ -341,4 +350,46 @@ table { .navbar-gitlab .navbar-inner .nav > li .btn-sign-in { @extend .btn-new; padding: 5px 15px; + text-shadow: none; +} + +.broadcast-message { + padding: 10px; + text-align: center; + background: #555; + color: #BBB; +} + +.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; + } } diff --git a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss index 53de8067abca9e031ddda5fd4ca3f9d8d1c2e1a2..8f6358acb11a1e98aa646d70fb8696239f400ada 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss @@ -34,9 +34,7 @@ &.ui-box-show { color: #666; margin:20px 0; - background: #FFF; - box-shadow: inset 0 1px 0 #fff, 0 1px 5px #f1f1f1; - @include linear-gradient(#fafafa, #f1f1f1); + background: #FAFAFA; .control-group { margin-bottom: 0; @@ -44,11 +42,13 @@ } &.ui-box-danger { + background: #f7f7f7; + border: none; + .title { - @include linear-gradient(#F26E5E, #bd362f); + background: #D65; color: #fff; text-shadow: 0 1px 1px #900; - font-weight: bold; } } @@ -98,9 +98,9 @@ } .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; diff --git a/app/assets/stylesheets/gitlab_bootstrap/buttons.scss b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss index 9c547cc1cea370435984fe02a66d102f4d15bbb3..347da1ad68017240dd52d7ae18397197facf973b 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/buttons.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss @@ -1,96 +1,106 @@ .btn { display: inline-block; - padding: 6px 12px; margin-bottom: 0; - font-size: 13px; - line-height: $baseLineHeight; + font-weight: normal; 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; + 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: $style_color; - background: #f1f1f1; - border-color: #AAA; + color: #444444; text-decoration: none; - @include linear-gradient(#fAfAfA, #f1f1f1); + background-color: #ebebeb; + border-color: #adadad; } &.focus, &:focus { + color: #444444; text-decoration: none; - @include box-shadow(inset 0 2px 4px rgba(0,0,0,.15)); + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; } &.active, &:active { - background-image: none; outline: 0; - text-decoration: none; - @include box-shadow(inset 0 2px 4px rgba(0,0,0,.15)); + 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: default; - background-image: none; - @include opacity(65); - @include box-shadow(none); + cursor: not-allowed; + pointer-events: none; + opacity: 0.65; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; } &.btn-primary { - color: #FFF; - border-color: #189; - text-shadow: 0 1px 1px #189; - @include linear-gradient(#4AC, #289); + color: #ffffff; + background-color: #429bca; + border-color: #358ebd; &.hover, &:hover, &.disabled, &[disabled] { - color: #FFF; - background: #389; + color: #ffffff; + background-color: #3286b1; + border-color: #286e8e; } } &.btn-success { - color: #FFF; - border-color: #1A1; - text-shadow: 0 1px 1px #FFF; - text-shadow: 0 1px 1px #181; - @include linear-gradient(#62C452, #51a351); + color: #ffffff; + background-color: #5cb85c; + border-color: #4cae4c; &.hover, &:hover, &.disabled, &[disabled] { - color: #FFF; - background: #2A2; + color: #ffffff; + background-color: #47a447; + border-color: #398439; } } &.btn-danger { - color: #FFF; - text-shadow: 0 1px 1px #811; - border-color: #BD362F; - @include linear-gradient(#EE5F5B, #BD362F); + color: #ffffff; + background-color: #d9534f; + border-color: #d43f3a; &.hover, &:hover, &.disabled, &[disabled] { - color: #FFF; - background: #A22; + color: #ffffff; + background-color: #d2322d; + border-color: #ac2925; } } diff --git a/app/assets/stylesheets/gitlab_bootstrap/common.scss b/app/assets/stylesheets/gitlab_bootstrap/common.scss index 2ca60178df71e5b947b524bba37a3b435f8139fd..26fe02e4928ee18ba251e81fc20d65d0e078a9f5 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/common.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/common.scss @@ -1,10 +1,12 @@ /** 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 } @@ -93,6 +95,12 @@ pre.well-pre { font-size: 12px; font-style: normal; font-weight: normal; + + &.label-gray { + background-color: #eee; + color: #999; + text-shadow: none; + } } /** Big Labels **/ @@ -116,3 +124,12 @@ pre.well-pre { color: #FFF; } } + +.dropdown-menu > li > a { + text-shadow: none; +} + +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + background: #29b; +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/files.scss b/app/assets/stylesheets/gitlab_bootstrap/files.scss index a286e530cd6025a1b958c7c24ba98d0cd887c52a..3f9e4989a276a6fac39a50042dc9e4a7ac912faa 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/files.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/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; diff --git a/app/assets/stylesheets/gitlab_bootstrap/forms.scss b/app/assets/stylesheets/gitlab_bootstrap/forms.scss index a2612166c7858f6feedb29af472bb794ead65edc..1a310a75c31402c1457ed6c41755ebac1303e69a 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/forms.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/forms.scss @@ -3,6 +3,23 @@ form { label { @extend .control-label; + + &.radio-label { + text-align: left; + width: 100%; + margin-left: 0; + + input[type="radio"] { + margin-top: 1px !important; + } + } + + &.list-label { + float: none; + padding: 0 !important; + margin: 0; + text-align: left; + } } } @@ -49,3 +66,9 @@ fieldset legend { font-size: 16px; margin-bottom: 10px; } + +.datetime-controls { + select { + width: 100px; + } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/mixins.scss b/app/assets/stylesheets/gitlab_bootstrap/mixins.scss index edb086989c5be30804d0e74d9bf8b9fab85172f1..2645e0ecb7d59e6aee1dee97601ec0dd94fe3d21 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/mixins.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/mixins.scss @@ -89,10 +89,26 @@ } 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: 2em; + } + + h4 { + margin-top: 30px; + font-size: 1.5em; + } blockquote p { color: #888; @@ -107,6 +123,16 @@ background: #EEE; } } + + code { + font-size: inherit; + font-weight: inherit; + color: #555; + } + + li { + line-height: 1.5; + } } @mixin page-title { diff --git a/app/assets/stylesheets/gitlab_bootstrap/nav.scss b/app/assets/stylesheets/gitlab_bootstrap/nav.scss index aa4cb1ed5fd12af75b634eb39165527170fe9f66..cc2bf0f912e9b983b76b71aabb685244605c792e 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/nav.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/nav.scss @@ -15,18 +15,16 @@ > li > a { border-left: 4px solid #EEE; padding: 12px; + color: #777; } > .active > a { border-color: $primary_color; - border-radius: 0; - background: #F1F1F1; - color: $style_color; - font-weight: bold; - text-shadow: 0 1px 1px #fff; + background: none; + color: #333; + font-weight: bolder; } &.nav-stacked-menu { - background: #FAFAFA; li > a { padding: 16px; } @@ -36,6 +34,7 @@ &.nav-pills-small { > li > a { padding: 8px 12px; + font-size: 12px; } } } diff --git a/app/assets/stylesheets/sections/admin.scss b/app/assets/stylesheets/sections/admin.scss index e189fd27ac670af95b1cb2fc03a394692531dfb1..8ad9bc732b268cbc61b2d9e7b82393f282864b90 100644 --- a/app/assets/stylesheets/sections/admin.scss +++ b/app/assets/stylesheets/sections/admin.scss @@ -20,4 +20,19 @@ label { width: 110px; } .controls { margin-left: 130px; } .form-actions { padding-left: 130px; background: #fff } + .visibility-levels { + .controls { + margin-bottom: 9px; + } + + i { + color: inherit; + } + } +} + +.broadcast-messages { + .message { + line-height: 2; + } } diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss index 787d81a997b605222006fe303080e645a1fe570d..81f7ace66bf7e1c00022485685305dc65b414b80 100644 --- a/app/assets/stylesheets/sections/commits.scss +++ b/app/assets/stylesheets/sections/commits.scss @@ -16,37 +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: -ms-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; } } @@ -56,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; } } @@ -78,7 +70,7 @@ font-size: 12px; } } - .old_line, .new_line { + .old_line, .new_line, .diff_line { margin: 0px; padding: 0px; border: none; @@ -100,6 +92,15 @@ text-decoration: underline; } } + &.new { + background: #CFD; + } + &.old { + background: #FDD; + } + } + .diff_line { + padding: 0; } .line_holder { &.old .old_line, @@ -130,6 +131,11 @@ color: #ccc; background: #fafafa; } + &.parallel { + display: table-cell; + overflow: hidden; + width: 50%; + } } } .image { diff --git a/app/assets/stylesheets/sections/dashboard.scss b/app/assets/stylesheets/sections/dashboard.scss index 61043147fc13547e30bfff7e80dcc3e19cd33be7..99bbcbd2108a178c20e14380cff75c3eba66db26 100644 --- a/app/assets/stylesheets/sections/dashboard.scss +++ b/app/assets/stylesheets/sections/dashboard.scss @@ -51,7 +51,7 @@ li { &.active { a { - @include linear-gradient(#f5f5f5, #eee); + background-color: #EEE; border-bottom: 1px solid #EEE !important; &:hover { background: #eee; @@ -100,3 +100,21 @@ 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; + width: 16px; + text-align: center; + + i { + color: #BBB; + } +} diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss index bd72d08295a3646773f570cf8ec4f257cd860e96..157b9dd9e8a18b4e87df77707197784e92a68667 100644 --- a/app/assets/stylesheets/sections/header.scss +++ b/app/assets/stylesheets/sections/header.scss @@ -36,8 +36,8 @@ header { float: left; margin-right: 9px; position: relative; - top: -5px; - padding-top: 5px; + top: -3px; + padding-top: 3px; a { float: left; @@ -46,8 +46,8 @@ 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; width: 40px; @@ -152,8 +152,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; } diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss index e384aebc76cb846e528543868745c5930d688d86..792bcef02f2dcd98168e98684d5bb69cf3d8a4b7 100644 --- a/app/assets/stylesheets/sections/issues.scss +++ b/app/assets/stylesheets/sections/issues.scss @@ -77,8 +77,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 .chosen-container { min-width: 120px; } } +@media (min-width: 1200px) { .issues_bulk_update .chosen-container { min-width: 160px; } } .issues-holder { .issues_filters { @@ -103,3 +103,19 @@ input.check_all_issues { .participants { margin-bottom: 10px; } + +.issues_bulk_update { + .chosen-container { + text-shadow: none; + } +} + +.issue-search-form { + margin: 0; + height: 24px; + + .issue_search { + border: 1px solid #DDD !important; + background-color: #f4f4f4; + } +} diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss index aa61cae4b9a02d6cca0eae422aafc1a4cd17e18c..0f0f8c858d1e563877628656b3e0810e6e3dab51 100644 --- a/app/assets/stylesheets/sections/merge_requests.scss +++ b/app/assets/stylesheets/sections/merge_requests.scss @@ -110,9 +110,29 @@ .merge-request-angle { text-align: center; - margin: 0; + margin: 0 auto; + background: #eee; + border-radius: 100px; + width: 60px; + line-height: 60px; + color: #777; + text-shadow: 0 1px 2px #FFF; } .merge-request-form-info { - padding: 15px 0; + padding-top: 15px; +} + +.merge-request-branches { + .commit-row-message { + font-weight: normal !important; + } + + .chosen-container .chosen-single { + padding: 2px 0 2px 10px; + span { + font-weight: bold; + color: #555; + } + } } diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss index 94b9ca3b181722e8ef03fbd217d97631d6a6b8aa..ac0caa3004015e9ccdb2dba18f62ee1b9e8384eb 100644 --- a/app/assets/stylesheets/sections/notes.scss +++ b/app/assets/stylesheets/sections/notes.scss @@ -130,6 +130,12 @@ 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; @@ -270,10 +276,9 @@ ul.notes { // preview/edit buttons > a { - font-size: 24px; - padding: 4px; position: absolute; - right: 10px; + right: 5px; + bottom: -60px; } .note_preview { background: #f5f5f5; @@ -306,10 +311,8 @@ ul.notes { .common-note-form { margin: 0; - height: 140px; background: #F9F9F9; padding: 3px; - padding-bottom: 25px; border: 1px solid #DDD; } @@ -320,7 +323,7 @@ ul.notes { padding: 0 5px; .note-form-option { - margin-top: 10px; + margin-top: 8px; margin-left: 30px; @extend .pull-left; } @@ -358,3 +361,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 21f4ed0577dbfab42e5be5b90f5ffc7af5280821..7e5668ae8a744f70a766b78258850eeb7c929477 100644 --- a/app/assets/stylesheets/sections/profile.scss +++ b/app/assets/stylesheets/sections/profile.scss @@ -42,3 +42,8 @@ margin-right: 12px; } +.profile-avatar-form-option { + hr { + margin: 10px 0; + } +} diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss index 0e5f99d4b7d86d1bd7ef327e748889a850f5fe01..45e52bb23a6f75964e050bf36cfccfe24386fc0e 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/sections/projects.scss @@ -16,9 +16,15 @@ .project-home-panel { border-bottom: 1px solid #DDD; - padding-bottom: 30px; + padding-bottom: 25px; margin-bottom: 30px; + &.empty-project { + border-bottom: 0px; + padding-bottom: 15px; + margin-bottom: 0px; + } + .project-home-title { font-size: 18px; color: #777; @@ -45,7 +51,7 @@ } } - .public-label { + .visibility-level-label { font-size: 14px; background: #f1f1f1; padding: 8px 10px; @@ -53,6 +59,10 @@ margin-left: 10px; color: #888; text-shadow: 0 1px 1px #FFF; + + i { + color: inherit; + } } } @@ -87,9 +97,40 @@ } } -.project-public-holder { - .help-inline { - padding-top: 7px; +.project-visibility-level-holder { + .controls { + padding-bottom: 9px; + } + + .controls { + input { + float: left; + } + .descr { + display: block; + margin-left: 1.5em; + &.restricted { + color: #888; + } + + label { + float: none; + padding: 0; + margin: 0; + text-align: left; + } + } + .info { + display: block; + margin-top: 5px; + } + strong { + display: inline-block; + width: 4em; + } + } + i { + color: inherit; } } @@ -130,7 +171,8 @@ ul.nav.nav-projects-tabs { margin: 0px; } -.my-projects { +.my-projects, +.public-projects { li { .project-info { margin-bottom: 10px; @@ -166,3 +208,61 @@ ul.nav.nav-projects-tabs { color: #777; } } + +.project-side { + .btn-block { + background-image: none; + background-color: #F1f1f1; + border-color: #EEE; + &:hover { + background-color: #eee; + border-color: #DDD; + } + } + .project-fork-icon { + float: left; + font-size: 26px; + margin-right: 10px; + line-height: 1.5; + } +} + +.transfer-project .chosen-container { + min-width: 200px; +} + +/** 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-single span { + font-weight: bold; + color: #555; + } + + &.chosen-container-active { + .chosen-drop { + min-width: 400px; + } + + .chosen-results { + max-height: 400px; + } + } +} diff --git a/app/assets/stylesheets/selects.scss b/app/assets/stylesheets/selects.scss index fd77efe34f0b57a466a9a74b82ea836c0bb56f60..8a695d8b16dd180186024b944a2ad3da0ef9cd1c 100644 --- a/app/assets/stylesheets/selects.scss +++ b/app/assets/stylesheets/selects.scss @@ -1,134 +1,33 @@ -/* 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.js selectbox style override **/ .chosen-container { min-width: 100px; - .chosen-search { - input:focus { - @include box-shadow(none); - } + .chosen-single { + background: #EEE !important; + border: 1px solid #DDD !important; + @include box-shadow(none !important); + @include border-radius(4px !important); } - .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-results li.highlighted { + background: #29b; } -} -.chosen-container .chosen-single, -.chosen-container.chosen-with-drop .chosen-single { - @include bg-light-gray-gradient; - - div { - background: transparent; - border-left: none; + .chosen-drop { + margin-top: 10px; + border: 1px solid #DDD !important; + @include border-radius(4px !important); } - span { - font-weight: normal; + .chosen-search input { + border: 1px solid #CCC !important; + @include box-shadow(none !important); } } /** 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: -ms-linear-gradient(whitesmoke 6.6%, #e1e1e1); - background-image: -o-linear-gradient(whitesmoke 6.6%, #e1e1e1); + @include bg-light-gray-gradient; } .select2-container .select2-choice div { diff --git a/app/assets/stylesheets/themes/ui_color.scss b/app/assets/stylesheets/themes/ui_color.scss index 1fd54ff18a664df6d57acdd1217926879d5f4460..7d9cab215c8e01cf45bb9c93643dd5b42f101a47 100644 --- a/app/assets/stylesheets/themes/ui_color.scss +++ b/app/assets/stylesheets/themes/ui_color.scss @@ -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_mars.scss b/app/assets/stylesheets/themes/ui_mars.scss index a2b8c21ea11df88a8e0f97514806bd8273cf9481..aba3e0ca82726037a3bcd6ab019e471c3d3abc4c 100644 --- a/app/assets/stylesheets/themes/ui_mars.scss +++ b/app/assets/stylesheets/themes/ui_mars.scss @@ -23,12 +23,17 @@ background-color: #373D47; } } + .separator { + background: #373D47; + border-left: 1px solid #575D67; + } + .nav > li > a { + color: #979DA7; + } + .search-input { + border-color: #979DA7; + } } } - - .separator { - background: #31363E; - border-left: 1px solid #666; - } } } diff --git a/app/assets/stylesheets/themes/ui_modern.scss b/app/assets/stylesheets/themes/ui_modern.scss index 6173757082e08122cd1d7f50665b2889206d8838..015a4bbf0c3d5f45a79d35b4d8b5db7eaf4f3716 100644 --- a/app/assets/stylesheets/themes/ui_modern.scss +++ b/app/assets/stylesheets/themes/ui_modern.scss @@ -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/files/create_context.rb b/app/contexts/files/create_context.rb index e1554c47bd6b716792a8e452168fdce0f9b2ad12..1027313855962e88090e2dfa1723145a6f031a79 100644 --- a/app/contexts/files/create_context.rb +++ b/app/contexts/files/create_context.rb @@ -15,29 +15,23 @@ module Files return error("You can only create files if you are on top of a branch") end - file_name = params[:file_name] + file_name = File.basename(path) + file_path = path unless file_name =~ Gitlab::Regex.path_regex return error("Your changes could not be commited, because file name contains not allowed characters") end - file_path = if path.blank? - file_name - else - File.join(path, file_name) - end - blob = repository.blob_at(ref, file_path) if blob return error("Your changes could not be commited, because file with such name exists") end - new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, path) + new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path) created_successfully = new_file_action.commit!( params[:content], - params[:commit_message], - file_name, + params[:commit_message] ) if created_successfully diff --git a/app/contexts/files/delete_context.rb b/app/contexts/files/delete_context.rb new file mode 100644 index 0000000000000000000000000000000000000000..b1937721d42b36b994c2cf067d1587955806d1ca --- /dev/null +++ b/app/contexts/files/delete_context.rb @@ -0,0 +1,38 @@ +module Files + class DeleteContext < BaseContext + 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(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 commited, because the file has been changed") + end + end + end +end diff --git a/app/contexts/files/update_context.rb b/app/contexts/files/update_context.rb index 000d3d02f126211928efc4385f1c8353b15d14dd..f40c749699477df5da6f3ea6da418d63b75026d7 100644 --- a/app/contexts/files/update_context.rb +++ b/app/contexts/files/update_context.rb @@ -24,8 +24,7 @@ module Files new_file_action = Gitlab::Satellite::EditFileAction.new(current_user, project, ref, path) created_successfully = new_file_action.commit!( params[:content], - params[:commit_message], - params[:last_commit] + params[:commit_message] ) if created_successfully diff --git a/app/contexts/issues/list_context.rb b/app/contexts/issues/list_context.rb index da2eed0e2591a55344e30567c56a7c74c9747826..fd27356d1cd48d739fad969da29165ce5b77064f 100644 --- a/app/contexts/issues/list_context.rb +++ b/app/contexts/issues/list_context.rb @@ -29,8 +29,26 @@ module Issues if params[:milestone_id].present? @issues = @issues.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id])) end + + # Sort by :sort param + @issues = sort(@issues, params[:sort]) @issues end + + private + + def sort(issues, condition) + case condition + when 'newest' then issues.except(:order).order('created_at DESC') + when 'oldest' then issues.except(:order).order('created_at ASC') + when 'recently_updated' then issues.except(:order).order('updated_at DESC') + when 'last_updated' then issues.except(:order).order('updated_at ASC') + when 'milestone_due_soon' then issues.except(:order).joins(:milestone).order("milestones.due_date ASC") + when 'milestone_due_later' then issues.except(:order).joins(:milestone).order("milestones.due_date DESC") + else issues + end + end + end end diff --git a/app/contexts/projects/create_context.rb b/app/contexts/projects/create_context.rb index 1c60a5de141afa8e344b516b57cf0ca8967ab67d..2acb9fbfe14bbc37bd5cf352827d9daf6f80137b 100644 --- a/app/contexts/projects/create_context.rb +++ b/app/contexts/projects/create_context.rb @@ -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, diff --git a/app/contexts/projects/update_context.rb b/app/contexts/projects/update_context.rb index 40385fa65b09caf3fde9a6a9b9080d5ce39526e7..55a4a6abecb29144ea49bfa27afc959e5b2ec826 100644 --- a/app/contexts/projects/update_context.rb +++ b/app/contexts/projects/update_context.rb @@ -2,7 +2,23 @@ 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) + # 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 != project.default_branch + GitlabShellWorker.perform_async( + :update_repository_head, + project.path_with_namespace, + new_branch + ) + + project.reload_default_branch + end + project.update_attributes(params[:project], as: role) end end diff --git a/app/contexts/search_context.rb b/app/contexts/search_context.rb index 2f9438f6bb4a4346cf8566a793e891d3949a27dd..5985ab1fb0c1ddfc1359bf539ac2bb2e3b1c6746 100644 --- a/app/contexts/search_context.rb +++ b/app/contexts/search_context.rb @@ -1,8 +1,8 @@ class SearchContext - attr_accessor :project_ids, :params + attr_accessor :project_ids, :current_user, :params - def initialize(project_ids, params) - @project_ids, @params = project_ids, params.dup + def initialize(project_ids, user, params) + @project_ids, @current_user, @params = project_ids, user, params.dup end def execute @@ -10,7 +10,8 @@ class SearchContext query = Shellwords.shellescape(query) if query.present? return result unless query.present? - result[:projects] = Project.where("projects.id in (?) OR projects.public = true", project_ids).search(query).limit(20) + visibility_levels = @current_user ? [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ] : [ Gitlab::VisibilityLevel::PUBLIC ] + result[:projects] = Project.where("projects.id in (?) OR projects.visibility_level in (?)", project_ids, visibility_levels).search(query).limit(20) # Search inside single project single_project_search(Project.where(id: project_ids), query) diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..9a70ef9d199088ac7485615e4ae2e851bb071902 --- /dev/null +++ b/app/controllers/admin/broadcast_messages_controller.rb @@ -0,0 +1,32 @@ +class Admin::BroadcastMessagesController < Admin::ApplicationController + before_filter :broadcast_messages + + def index + @broadcast_message = BroadcastMessage.new + end + + def create + @broadcast_message = BroadcastMessage.new(params[:broadcast_message]) + + if @broadcast_message.save + redirect_to admin_broadcast_messages_path, notice: 'Broadcast Message was successfully created.' + else + render :index + end + end + + def destroy + BroadcastMessage.find(params[:id]).destroy + + respond_to do |format| + format.html { redirect_to :back } + format.js { render nothing: true } + end + end + + protected + + def broadcast_messages + @broadcast_messages ||= BroadcastMessage.order("starts_at DESC").page(params[:page]) + end +end diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index 3c80b6503fabbab26e979ca6264bb706e08c43d2..be19139c9b1113c19bfad3b4a4b1584ceff2b145 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -2,5 +2,6 @@ class Admin::DashboardController < Admin::ApplicationController def index @projects = Project.order("created_at DESC").limit(10) @users = User.order("created_at DESC").limit(10) + @groups = Group.order("created_at DESC").limit(10) end end diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 088174fd3b80fe71fe1caa8d881e699a926f2acc..0e8335f3d8bbacc7ed40f14d1d71434375954462 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) @projects = user ? user.owned_projects : Project.scoped - @projects = @projects.where(public: true) if params[:public_only].present? + @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::TransferContext.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/application_controller.rb b/app/controllers/application_controller.rb index cfa3cac5e88e55e17166b09e1308efa1e200ea97..e5b5a3a47778e95f93d7a87578d83eae6e991d47 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -81,6 +81,9 @@ class ApplicationController < ActionController::Base 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 @@ -102,7 +105,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! @@ -174,4 +177,26 @@ 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 end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index ac319384434e66dea478489a58818c15875f8979..045e5805bd0ad450b629b6354b128f605f54a434 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -22,7 +22,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 @@ -40,6 +40,7 @@ class DashboardController < ApplicationController end @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).sorted_by_activity @labels = current_user.authorized_projects.tags_on(:labels) diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index bb46af14d52c9493c368c3bb86bf5f69b4dcccf7..fd0aa03476cb0e85a5f952aab06ff7d4e7fb3cda 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -38,7 +38,7 @@ 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 diff --git a/app/controllers/profiles/avatars_controller.rb b/app/controllers/profiles/avatars_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..e90eaafd4404f4e9fb2189bf73a4cdaa3111d811 --- /dev/null +++ b/app/controllers/profiles/avatars_controller.rb @@ -0,0 +1,11 @@ +class Profiles::AvatarsController < ApplicationController + layout "profile" + + def destroy + @user = current_user + @user.remove_avatar! + + @user.save + redirect_to profile_path + end +end diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 47cfc5e63f56863df19acf7263b5ae7cfdba0cfe..9234cd1708fa153709ffe292dd339d2420a46e7b 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -13,6 +13,8 @@ class ProfilesController < ApplicationController end def update + params[:user].delete(:email) if @user.ldap_user? + if @user.update_attributes(params[:user]) flash[:notice] = "Profile was successfully updated" else diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 80aeb5cd6cc04240aaf308d157a0dfbb429d9db5..7e4580017dd7e6c26373a1b80186f7dd518a71b0 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -10,7 +10,7 @@ class Projects::ApplicationController < ApplicationController id = params[:project_id] || params[:id] @project = Project.find_with_namespace(id) - return if @project && @project.public + return if @project && @project.public? end super diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index ba466251b29db862d5b3220f5338d4bf9e0f2b93..087c1639ac64fc9e2e09836a84c3d8c1fbb4b591 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -7,9 +7,30 @@ class Projects::BlobController < Projects::ApplicationController before_filter :authorize_code_access! before_filter :require_non_empty_project + before_filter :blob + def show - @blob = @repository.blob_at(@commit.id, @path) + end + + def destroy + result = Files::DeleteContext.new(@project, current_user, params, @ref, @path).execute + + if result[:status] == :success + flash[:notice] = "Your changes have been successfully commited" + 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 - not_found! unless @blob + @blob end end diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index bdffc940ea5241ba714101d6c829f0486d531f13..12856191c2657744418efdefb741ba75bb225c5c 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -16,7 +16,7 @@ class Projects::CommitsController < Projects::ApplicationController respond_to do |format| format.html # index.html.erb - format.js + format.json { pager_json("projects/commits/_commits", @commits.size) } format.atom { render layout: false } end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index e8f845b2d1710a75da654f9dae477d55b3921934..5dcdba5d38841a8f508658590e365e333f2f0075 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -11,7 +11,7 @@ class Projects::IssuesController < Projects::ApplicationController # Allow modify issue before_filter :authorize_modify_issue!, only: [:edit, :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 @@ -45,10 +52,7 @@ class Projects::IssuesController < Projects::ApplicationController @target_type = :issue @target_id = @issue.id - respond_to do |format| - format.html - format.js - end + respond_with(@issue) end def create diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 0cc09caf1d2c58eaf316627f3438e03b13c0f57f..2f285f8ba859bee54f940ba0113865505b940e03 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] @@ -26,8 +26,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,6 +42,11 @@ 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 diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index 3e88656cdf159fad6ea70c89d648129253b2d61f..ecb1fc1d56604088f7d62b9e5ac0298065c045e9 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -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 diff --git a/app/controllers/projects/new_tree_controller.rb b/app/controllers/projects/new_tree_controller.rb index 9f9e0191e98cd713717c9fd064833e96c1f9bec2..d6d474cf9c5ab1391f931a3248170d2ddec623f2 100644 --- a/app/controllers/projects/new_tree_controller.rb +++ b/app/controllers/projects/new_tree_controller.rb @@ -5,11 +5,12 @@ class Projects::NewTreeController < Projects::BaseTreeController end def update - result = Files::CreateContext.new(@project, current_user, params, @ref, @path).execute + file_path = File.join(@path, File.basename(params[:file_name])) + result = Files::CreateContext.new(@project, current_user, params, @ref, file_path).execute if result[:status] == :success flash[:notice] = "Your changes have been successfully commited" - redirect_to project_blob_path(@project, File.join(@id, params[:file_name])) + redirect_to project_blob_path(@project, File.join(@ref, file_path)) else flash[:alert] = result[:error] render :show diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 8214163c3157416b63462172e14b29515d1aa457..2738a99459de4c83b07ce7d6f70c563d09b4681c 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -14,7 +14,14 @@ class Projects::NotesController < Projects::ApplicationController @discussions = discussions_from_notes end - respond_with(@notes) + respond_to do |format| + format.html { redirect_to :back } + format.json do + render json: { + html: view_to_html_string("projects/notes/_notes") + } + end + end end def create diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 7264128691ee93eb3ffc0b3cee854f22e9085e58..1835671fe984856f0b683eedfd75d492230a143c 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -55,17 +55,13 @@ class ProjectsController < ApplicationController 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 diff --git a/app/controllers/public/projects_controller.rb b/app/controllers/public/projects_controller.rb index 87e903a1d2df38b327c26adc147840dc89580ebb..8d66250d7b6d6e61bf1ea7d3c313b1362c877138 100644 --- a/app/controllers/public/projects_controller.rb +++ b/app/controllers/public/projects_controller.rb @@ -6,7 +6,7 @@ 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) end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index f5c3bb133ed74bee9a0bc42da816c5b9bc13a4c9..2a2748dc1fb0ddab3222dacfe46b7b3e35ad052c 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -14,7 +14,7 @@ class SearchController < ApplicationController project_ids.select! { |id| id == project_id.to_i} end - result = SearchContext.new(project_ids, params).execute + result = SearchContext.new(project_ids, current_user, params).execute @projects = result[:projects] @merge_requests = result[:merge_requests] diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index e2d97901410c25c9891250fb312777f068e28421..4b9514750a99a521bc7c91f9ad6f95f7e32154d9 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -84,8 +84,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 - @@ -126,6 +126,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 @@ -184,14 +187,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" @@ -208,4 +203,16 @@ 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: user_color_scheme_class do + Pygments::Lexer[:js].highlight(string).html_safe + end + end end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index c340eb30be14b250fdd2c0eca39e4dc2aef6b4b8..663369e45840443129ebab1d294be060043fe2a1 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -105,6 +105,10 @@ module CommitsHelper 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 @@ -125,7 +129,9 @@ module CommitsHelper source_name end - user = User.where('name like ? or email like ?', source_name, source_email).first + # Prefer email match over name match + user = User.where(email: source_email).first + user ||= User.where(name: source_name).first options = { class: "commit-#{options[:source]}-link has_tooltip", diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb index ea2540bf385e31511bc1c438cb5fd14391e4082a..5ff19b8829381a9e0e0c363def1cd73d8886f9b2 100644 --- a/app/helpers/compare_helper.rb +++ b/app/helpers/compare_helper.rb @@ -1,6 +1,8 @@ module CompareHelper def compare_to_mr_button? - params[:from].present? && params[:to].present? && + @project.merge_requests_enabled && + params[:from].present? && + params[:to].present? && @repository.branch_names.include?(params[:from]) && @repository.branch_names.include?(params[:to]) && params[:from] != params[:to] && diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 4aeb65752f0b4a0769df2dcdb64d67caaf7ff197..d3671efa559b33d7b73b9a73b8851711754ab52b 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -102,15 +102,11 @@ module EventsHelper 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? diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index b34f7033536a9b5842e096e5ead5c0d9dd793d72..8894a01eaeabd1041083074a7d9ed7c907bed934 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -64,7 +64,9 @@ module GitlabMarkdownHelper # 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_path_with_namespace, ref, requested_path, wiki = false) + 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| new_path = rebuild_path(project_path_with_namespace, file_path, requested_path, ref) @@ -145,13 +147,18 @@ module GitlabMarkdownHelper def file_exists?(path) return false if path.nil? || path.empty? - File.exists?(Rails.root.join(path)) + File.exists?(path_on_fs(path)) 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) - File.directory?(Rails.root.join(path)) ? "tree" : "blob" + File.directory?(path_on_fs(path)) ? "tree" : "blob" + end + + # Path to the file in the satellites repository on the filesystem + def path_on_fs(path) + [@path_to_satellite, path].join("/") end # We will assume that if no ref exists we can point to master diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 8573c59dc9486e82dc98c79f2ff88330afef377f..7c09273d53edcaaa50229393834f0ef6e1549af9 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -2,4 +2,23 @@ module GroupsHelper def remove_user_from_group_message(group, user) "You are going to remove #{user.name} from #{group.name} Group. Are you sure?" end + + def group_head_title + title = @group.name + + title = if current_action?(:issues) + "Issues - " + title + elsif current_action?(:merge_requests) + "Merge requests - " + title + elsif current_action?(:members) + "Members - " + title + elsif current_action?(:edit) + "Settings - " + title + else + title + end + + title + + end end diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..53d4a8f2e6e42eff33cb0e4a66db5e149dbb160e --- /dev/null +++ b/app/helpers/icons_helper.rb @@ -0,0 +1,21 @@ +module IconsHelper + def boolean_to_icon(value) + if value.to_s == "true" + content_tag :i, nil, class: 'icon-ok cgreen' + else + content_tag :i, nil, class: 'icon-off clgray' + end + end + + def public_icon + content_tag :i, nil, class: 'icon-globe' + end + + def internal_icon + content_tag :i, nil, class: 'icon-shield' + end + + def private_icon + content_tag :i, nil, class: 'icon-lock' + end +end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 2221583912d34e31c6c1c661c5ff3f017dad52d8..56b776cff46a36b0da28a13200b8e5323626bb2d 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -68,4 +68,12 @@ module IssuesHelper 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 end diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index f7979c8b641d3327057b46e129166fab840f472c..c363c7ffd7450ed12fd4434bcc3a0fc3180ddd12 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -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/projects_helper.rb b/app/helpers/projects_helper.rb index d70d1876323a2ab03528a7d7f1e1bb6abb463022..096cef02b2b018b83ebad10c3e6406455eb02253 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -70,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) @@ -135,12 +137,46 @@ module ProjectsHelper end end - def repository_size - "#{@project.repository.size} MB" + 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} - " + 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 end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 33c5e4fb9db2e65493a1e2102e76cc267956cbfb..8ff0bc67b71a9e4651b8b1a8270331f281a73f52 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -1,10 +1,10 @@ module SearchHelper def search_autocomplete_source return unless current_user - [ groups_autocomplete, projects_autocomplete, + public_projects_autocomplete, default_autocomplete, project_autocomplete, help_autocomplete @@ -75,4 +75,11 @@ module SearchHelper { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) } end end + + # Autocomplete results for the current user's projects + def public_projects_autocomplete + Project.public_or_internal_only(current_user).map do |p| + { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) } + end + end end diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..81e10f3685c9783faeff9d6c8fa325d53d613b2d --- /dev/null +++ b/app/helpers/visibility_level_helper.rb @@ -0,0 +1,49 @@ +module VisibilityLevelHelper + def visibility_level_color(level) + case level + when Gitlab::VisibilityLevel::PRIVATE + 'cgreen' + when Gitlab::VisibilityLevel::INTERNAL + 'camber' + when Gitlab::VisibilityLevel::PUBLIC + 'cblue' + end + end + + def visibility_level_description(level) + capture_haml do + haml_tag :span do + case level + when Gitlab::VisibilityLevel::PRIVATE + haml_concat "Project access must be granted explicitly for each user." + when Gitlab::VisibilityLevel::INTERNAL + haml_concat "The project can be cloned by" + haml_concat "any logged in user." + when Gitlab::VisibilityLevel::PUBLIC + haml_concat "The project can be cloned" + haml_concat "without any" + haml_concat "authentication." + end + end + end + end + + def visibility_level_icon(level) + case level + when Gitlab::VisibilityLevel::PRIVATE + private_icon + when Gitlab::VisibilityLevel::INTERNAL + internal_icon + when Gitlab::VisibilityLevel::PUBLIC + public_icon + end + end + + def visibility_level_label(level) + Project.visibility_levels.key(level) + end + + def restricted_visibility_levels + current_user.is_admin? ? [] : gitlab_config.restricted_visibility_levels + end +end diff --git a/app/mailers/emails/groups.rb b/app/mailers/emails/groups.rb index 2e9d28981e37f2f987ae5144ef7c74d88ce5e081..1c8ae122c4699d04d1cc9a683ced2a57bdb5de7c 100644 --- a/app/mailers/emails/groups.rb +++ b/app/mailers/emails/groups.rb @@ -5,7 +5,7 @@ module Emails @group = @membership.group mail(to: @membership.user.email, - subject: subject("access to group was granted")) + subject: subject("Access to group was granted")) end end end diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index 6eda88c792115ac34e344529c65fe781bc33dbbe..5abdf99529cb0cec36a075c8e4f3d8842489cc8a 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 @project = @issue.project - mail(to: recipient(recipient_id), subject: subject("changed issue ##{@issue.iid}", @issue.title)) + mail(to: recipient(recipient_id), subject: subject("Changed issue ##{@issue.iid}", @issue.title)) end def closed_issue_email(recipient_id, issue_id, updated_by_user_id) @@ -27,7 +27,7 @@ module Emails @project = @issue.project @updated_by = User.find updated_by_user_id mail(to: recipient(recipient_id), - subject: subject("changed issue ##{@issue.iid}", @issue.title)) + subject: subject("Changed issue ##{@issue.iid}", @issue.title)) end end end diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 57c1925fa47e786d45bf899300134111ff900524..67544f2a331685faa3556cefcc0b2341698179c2 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -2,24 +2,24 @@ 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)) + 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)) + 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)) + 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)) + 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 ba4f0dd862c62c847de1d3706107a75b069006d4..e967cf6dc739cb7f4257caf3bd456edb44bcaeaa 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -4,27 +4,27 @@ module Emails @note = Note.find(note_id) @commit = @note.noteable @project = @note.project - mail(to: recipient(recipient_id), subject: subject("note for commit #{@commit.short_id}", @commit.title)) + mail(to: recipient(recipient_id), subject: subject("Note for commit #{@commit.short_id}", @commit.title)) end def note_issue_email(recipient_id, note_id) @note = Note.find(note_id) @issue = @note.noteable @project = @note.project - mail(to: recipient(recipient_id), subject: subject("note for issue ##{@issue.iid}")) + mail(to: recipient(recipient_id), subject: subject("Note for issue ##{@issue.iid}")) end def note_merge_request_email(recipient_id, note_id) @note = Note.find(note_id) @merge_request = @note.noteable @project = @note.project - mail(to: recipient(recipient_id), subject: subject("note for merge request ##{@merge_request.iid}")) + mail(to: recipient(recipient_id), subject: subject("Note for merge request ##{@merge_request.iid}")) end def note_wall_email(recipient_id, note_id) @note = Note.find(note_id) @project = @note.project - mail(to: recipient(recipient_id), subject: subject("note on wall")) + mail(to: recipient(recipient_id), subject: subject("Note on wall")) end end end diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index 4d5fe9ef6142858ca51797ff56d3e9d5c7d9b05e..0e40450bfeed47f29c7786b86f2c0ed59cdc5c03 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -4,14 +4,14 @@ 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 end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 85476089145fab798b5251fb20ed174302109932..6df56eed5b8fd803336f7ecd980db73cfe785b86 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, @@ -71,7 +71,7 @@ class Ability rules << project_guest_rules end - if project.public? + if project.public? || project.internal? rules << public_project_rules end @@ -89,7 +89,7 @@ class Ability def public_project_rules project_guest_rules + [ :download_code, - :fork_project, + :fork_project ] end @@ -145,7 +145,7 @@ class Ability def project_admin_rules project_master_rules + [ :change_namespace, - :change_public_mode, + :change_visibility_level, :rename_project, :remove_project ] diff --git a/app/models/assembla_service.rb b/app/models/assembla_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..66ecf394784d6a41cba7f47af77c00f6f1aff1c0 --- /dev/null +++ b/app/models/assembla_service.rb @@ -0,0 +1,45 @@ +# == 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 + 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: '' } + ] + end + + def execute(push) + url = "https://atlas.assembla.com/spaces/ouposp/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/broadcast_message.rb b/app/models/broadcast_message.rb new file mode 100644 index 0000000000000000000000000000000000000000..a8b1db9c24ec5b4104d1b39eca5d584f38b0cde5 --- /dev/null +++ b/app/models/broadcast_message.rb @@ -0,0 +1,24 @@ +# == 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 +# + +class BroadcastMessage < ActiveRecord::Base + attr_accessible :alert_type, :ends_at, :message, :starts_at + + validates :message, presence: true + validates :starts_at, presence: true + validates :ends_at, presence: true + + def self.current + where("ends_at > :now AND starts_at < :now", now: Time.zone.now).last + end +end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 7f820f950b02b02d601eca4779ad4b586009945b..58bf621f91b42469e9472288556626699d94946e 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -111,4 +111,11 @@ module Issuable end users.concat(mentions.reduce([], :|)).uniq end + + def to_hook_data + { + object_kind: self.class.name.underscore, + object_attributes: self.attributes + } + end end diff --git a/app/models/event.rb b/app/models/event.rb index 095a06c956b82dddb5279357c1d4e5562a86cdaa..771c6280567e91958f08152b137da04bb18fbadd 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -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/flowdock_service.rb b/app/models/flowdock_service.rb index 6ec431d4a10715d9843f52105fe283f9cfc49429..f72d9fa90151c5d08a6401e3db2b46522aebb86d 100644 --- a/app/models/flowdock_service.rb +++ b/app/models/flowdock_service.rb @@ -11,6 +11,8 @@ # 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" diff --git a/app/models/gollum_wiki.rb b/app/models/gollum_wiki.rb index 05fc2a745b411c03d4a81afda0b3278300cf7787..7ebaaff61cb55ed85ac58c89527a65a1870d76f7 100644 --- a/app/models/gollum_wiki.rb +++ b/app/models/gollum_wiki.rb @@ -33,7 +33,7 @@ class GollumWiki end def http_url_to_repo - http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') + [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') end # Returns the Gollum::Wiki object. diff --git a/app/models/hipchat_service.rb b/app/models/hipchat_service.rb index 7fec5c4fbe8861531d00fa4a648979669497e2e9..ea2169fb1688af2f882c3be2ed3b1aafff5dc192 100644 --- a/app/models/hipchat_service.rb +++ b/app/models/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 diff --git a/app/models/issue.rb b/app/models/issue.rb index f3ec322126facfbbac0bdd9ef6763afd82e03a46..d350b237d37ee5978f7838bd6a5a4df3d9b1c686 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -21,6 +21,8 @@ class Issue < ActiveRecord::Base include Issuable include InternalId + ActsAsTaggableOn.strict_case_match = true + belongs_to :project validates :project, presence: true diff --git a/app/models/namespace.rb b/app/models/namespace.rb index fde06649c7883f75c9ee7d6cabda5e62696689b8..8f837c72ff5f7b692562a0f784fcee2dceda8cbf 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -87,4 +87,8 @@ class Namespace < ActiveRecord::Base def send_update_instructions projects.each(&:send_move_instructions) end + + def kind + type == 'Group' ? 'group' : 'user' + end end diff --git a/app/models/note.rb b/app/models/note.rb index 7e7387abed6f92fb23abe9a3b99607beb25bfd96..8284da8616ff5cb40c78eca967860ff1640f00c5 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -157,7 +157,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 +207,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 diff --git a/app/models/project.rb b/app/models/project.rb index 52682ac0a9e14e293522c6079a70722a17a109d2..ed30b5ea892a7679367c4d77e19ff314fd7b61e3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -9,33 +9,37 @@ # 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'" @@ -47,6 +51,7 @@ class Project < ActiveRecord::Base 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 @@ -104,7 +109,8 @@ 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 ]) } enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab @@ -136,6 +142,10 @@ class Project < ActiveRecord::Base where(path: id, namespace_id: nil).last end end + + def visibility_levels + Gitlab::VisibilityLevel.options + end end def team @@ -143,7 +153,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? @@ -221,7 +231,7 @@ class Project < ActiveRecord::Base end def available_services_names - %w(gitlab_ci campfire hipchat pivotaltracker flowdock) + %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla) end def gitlab_ci? @@ -288,8 +298,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) @@ -300,14 +312,6 @@ 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/", "") @@ -390,7 +394,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 @@ -451,4 +455,17 @@ class Project < ActiveRecord::Base 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 end diff --git a/app/models/project_hook.rb b/app/models/project_hook.rb index 2576fc979d4b8f6798d92358c19d35315cd30396..e1c9ed01bc5c052b87660e552f3bc34b3177e0df 100644 --- a/app/models/project_hook.rb +++ b/app/models/project_hook.rb @@ -2,15 +2,24 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# type :string(255) default("ProjectHook") -# service_id :integer +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null # class ProjectHook < WebHook belongs_to :project + + attr_accessible :push_events, :issues_events, :merge_requests_events + + scope :push_hooks, -> { where(push_events: true) } + scope :issue_hooks, -> { where(issues_events: true) } + scope :merge_request_hooks, -> { where(merge_requests_events: true) } end diff --git a/app/models/repository.rb b/app/models/repository.rb index 97b4330092a4cde7cbef289640c6682ccb97b569..1255b8145339854d67fd112100925ba8648a0b32 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -3,7 +3,7 @@ class Repository attr_accessor :raw_repository, :path_with_namespace - def initialize(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 @@ -57,7 +57,7 @@ class Repository def recent_branches(limit = 20) branches.sort do |a, b| - a.commit.committed_date <=> b.commit.committed_date + b.commit.committed_date <=> a.commit.committed_date end[0..limit] end @@ -133,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 @@ -159,4 +160,10 @@ class Repository def blob_at(sha, path) Gitlab::Git::Blob.find(self, sha, path) end + + def readme + Rails.cache.fetch(cache_key(:readme)) do + Tree.new(self, self.root_ref).readme + end + end end diff --git a/app/models/service_hook.rb b/app/models/service_hook.rb index 4cd2b272eece51050c3392b321b1f32900bd50b3..6f22a863d988b72dfd64d89e749c613f7f018d31 100644 --- a/app/models/service_hook.rb +++ b/app/models/service_hook.rb @@ -2,13 +2,16 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# type :string(255) default("ProjectHook") -# service_id :integer +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null # class ServiceHook < WebHook diff --git a/app/models/system_hook.rb b/app/models/system_hook.rb index 5cdf046644f184b0536f45f3c3832c84689dc977..bffcbbf00f49acbe554eadf34d81c9593947b194 100644 --- a/app/models/system_hook.rb +++ b/app/models/system_hook.rb @@ -2,13 +2,16 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# type :string(255) default("ProjectHook") -# service_id :integer +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null # class SystemHook < WebHook diff --git a/app/models/user.rb b/app/models/user.rb index ce8c88ca69e8574021cba17f2d558d3f92b01082..f522f9133b61d604594cde16f2774b82803129e3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -36,6 +36,11 @@ # 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) # require 'carrierwave/orm/activerecord' diff --git a/app/models/web_hook.rb b/app/models/web_hook.rb index 3f22b1082fb0aaad525f8b57be062622b5501240..c0aa373491725cd3fdf3021df2806d7ed55cac86 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 diff --git a/app/observers/issue_observer.rb b/app/observers/issue_observer.rb index 886d8b776fb7612df313835417f9e65cbddab366..b150e39e239b70503c7e3fa68e2a20a15524fc24 100644 --- a/app/observers/issue_observer.rb +++ b/app/observers/issue_observer.rb @@ -1,14 +1,14 @@ 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) @@ -29,4 +29,8 @@ class IssueObserver < BaseObserver def create_note(issue) Note.create_status_change_note(issue, issue.project, current_user, issue.state, current_commit) end + + def execute_hooks(issue) + issue.project.execute_hooks(issue.to_hook_data, :issue_hooks) + end end diff --git a/app/observers/merge_request_observer.rb b/app/observers/merge_request_observer.rb index d70da514cd20d47195a027603e2655df9b0e2936..96492198111e2fb1148f9dfaf33fbda1356a6d06 100644 --- a/app/observers/merge_request_observer.rb +++ b/app/observers/merge_request_observer.rb @@ -7,15 +7,15 @@ 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) + create_note(merge_request) + execute_hooks(merge_request) end def after_merge(merge_request, transition) @@ -31,11 +31,13 @@ class MergeRequestObserver < ActivityObserver action: Event::MERGED, author_id: merge_request.author_id_of_changes ) + + 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) end def after_update(merge_request) @@ -53,4 +55,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 f301f30645858abde79b800c72c5cc66c44c5517..4e3deec01bf38943c33f45fd12e59734ed8381c7 100644 --- a/app/observers/project_observer.rb +++ b/app/observers/project_observer.rb @@ -30,12 +30,6 @@ class ProjectObserver < BaseObserver 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/users_group_observer.rb b/app/observers/users_group_observer.rb index ecdbede89d96b1f29e5f1839660f552886b0cc28..42a05b5e177e55a5acfc29f57e41932eec1805d8 100644 --- a/app/observers/users_group_observer.rb +++ b/app/observers/users_group_observer.rb @@ -4,6 +4,6 @@ class UsersGroupObserver < BaseObserver end def after_update(membership) - notification.update_group_member(membership) + notification.update_group_member(membership) if membership.group_access_changed? end end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 3ad41de397fae3a3228eb491213864fce5646d9a..e54f88e42de58102dce63ac8f1cf6331317155d2 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -24,7 +24,6 @@ class GitPushService create_push_event project.ensure_satellite_exists - project.discover_default_branch project.repository.expire_cache if push_to_existing_branch?(ref, oldrev) @@ -33,7 +32,7 @@ class GitPushService end if push_to_branch?(ref) - project.execute_hooks(@push_data.dup) + project.execute_hooks(@push_data.dup, :push_hooks) project.execute_services(@push_data.dup) end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 750a71aea6bc1f84f06f163f43ba7411cae35d34..eb42cac3f83177f3d6eceaeee7d81cf6a64d75d9 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) diff --git a/app/services/project_transfer_service.rb b/app/services/project_transfer_service.rb index 7150c1c78c0c213b8a27ebc248f125dd032fa1f4..7055ef32aee816d0f8ceb5d10635f4ff428df9bb 100644 --- a/app/services/project_transfer_service.rb +++ b/app/services/project_transfer_service.rb @@ -18,6 +18,10 @@ class ProjectTransferService raise TransferError.new("Project with same path in target namespace already exists") end + # Remove old satellite + project.satellite.destroy + + # Apply new namespace id project.namespace = new_namespace project.save! @@ -29,8 +33,8 @@ class ProjectTransferService # Move wiki repo also if present gitlab_shell.mv_repository("#{old_path}.wiki", "#{new_path}.wiki") - # create satellite repo - project.ensure_satellite_exists + # Create a new satellite (reload project from DB) + Project.find(project.id).ensure_satellite_exists # clear project cached events project.reset_events_cache diff --git a/app/views/admin/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..b16d82f4abf0f4b26644d87ff4073d074b868513 --- /dev/null +++ b/app/views/admin/broadcast_messages/index.html.haml @@ -0,0 +1,46 @@ +%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. +%hr + += form_for [:admin, @broadcast_message] do |f| + -if @broadcast_message.errors.any? + .alert.alert-error + - @broadcast_message.errors.full_messages.each do |msg| + %p= msg + .control-group + = f.label :message + .controls + = f.text_area :message, class: "input-xxlarge", rows: 2, required: true + .control-group + = f.label :starts_at + .controls.datetime-controls + = f.datetime_select :starts_at + .control-group + = f.label :ends_at + .controls.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 3064763b993bfe4c4690cf47d3ea763534675778..d5c8585804924bd3d0f29f652a4f25f3afb0ba16 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -51,6 +51,19 @@ = time_ago_in_words user.created_at ago + .span4 + %h4 Latest groups + %hr + - @groups.each do |group| + %p + = link_to [:admin, group] do + = group.name + %span.light.pull-right + = time_ago_in_words group.created_at + ago + +%br +.row .span4 %h4 Stats %hr @@ -82,3 +95,43 @@ Milestones %span.light.pull-right = Milestone.count + .span4 + %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 + .span4 + %h4 Components + %hr + %p + GitLab + %span.pull-right + = Gitlab::VERSION + %p + GitLab Shell + %span.pull-right + = Gitlab::Shell.new.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/index.html.haml b/app/views/admin/groups/index.html.haml index c9d7c24f204f54fe18e7778c09bcf3e1af5a6727..173419f240b65091fcc7c407b62d04a181e1c8bc 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -7,7 +7,7 @@ = 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" + = text_field_tag :name, params[:name], class: "span6 input-xpadding" = submit_tag "Search", class: "btn submit btn-primary" %hr diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index ca51a57000a2340b2076e833961732ada107f0de..299f397c51f630c0bb52695f28a3abc5d387c89e 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -39,6 +39,8 @@ %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" diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index bc297209ae5d4537dfb65699b20ca1349c9abdac..50b47c4c55a1d2082b1ba1cf5011dfb6025e2a49 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -10,11 +10,15 @@ .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] + = users_select_tag :owner_id, selected: params[:owner_id], class: 'input-large input-clamp' + .control-group.visibility-levels + = label_tag :visibility_level, 'Visibility Levels', class: 'control-label' + - Project.visibility_levels.each do |label, level| + .controls + = check_box_tag 'visibility_levels[]', level, params[:visibility_levels].present? && params[:visibility_levels].include?(level.to_s) + %span.descr + = visibility_level_icon(level) + = label .control-group = label_tag :with_push, 'Not empty', class: 'control-label' .controls @@ -42,12 +46,12 @@ %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" - if @projects.blank? diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 65b9911dc460dcee3e430377d28c6f7d8fefdf59..42c427aad635ba6983a73479a8b2510b1d65442a 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -66,14 +66,24 @@ %li %span.light access: %strong - - if @project.public - %span.cblue - %i.icon-share - Public - - else - %span.cgreen - %i.icon-lock - Private + %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 + .ui-box-body + = form_for @project, url: transfer_admin_project_path(@project), method: :put do |f| + .control-group + = f.label :namespace_id, "Namespace" + .controls + = namespace_select_tag :namespace_id, selected: params[:namespace_id], class: 'input-large' + + .control-group + .controls + = f.submit 'Transfer', class: 'btn btn-primary' + .span6 - if @group .ui-box diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index b32f0ae87ccf09449e155ba5e2865806cdc99f67..9c5796a661d33c0165593c28523f46e3ab34ed64 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -2,8 +2,8 @@ .span3 .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 + = search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'input-xpadding span2' + = button_tag type: 'submit', class: 'btn btn-primary' do %i.icon-search %ul.nav.nav-pills.nav-stacked %li{class: "#{'active' unless params[:filter]}"} diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml index 904ac2d00a2b8c9d2e3de9a2b8849080ebd14466..a0ef76c8f2cacd0c105137fa798505a28be9653a 100644 --- a/app/views/dashboard/projects.html.haml +++ b/app/views/dashboard/projects.html.haml @@ -26,6 +26,14 @@ %span.pull-right = current_user.owned_projects.count + %fieldset + %legend Visibility + %ul.bordered-list.visibility-filter + - Gitlab::VisibilityLevel.values.each do |level| + %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' } + = link_to projects_dashboard_path(visibility_level: level) do + = visibility_level_icon(level) + = visibility_level_label(level) - if @groups.present? %fieldset @@ -56,12 +64,10 @@ - @projects.each do |project| %li.my-project-row %h4.project-title + .project-access-icon + = visibility_level_icon(project.visibility_level) = link_to project_path(project), class: dom_class(project) do = project.name_with_namespace - - if project.public - %small.access-icon - = public_icon - Public - if current_user.can_leave_project?(project) .pull-right diff --git a/app/views/dashboard/show.js.haml b/app/views/dashboard/show.js.haml deleted file mode 100644 index 7e5a148e5ef2d56f87c72403ee7525cffc8cfc14..0000000000000000000000000000000000000000 --- a/app/views/dashboard/show.js.haml +++ /dev/null @@ -1,2 +0,0 @@ -:plain - Pager.append(#{@events.count}, "#{escape_javascript(render(@events))}"); diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb deleted file mode 100644 index adc9b6720923e921b9acc94427020f7038b586c9..0000000000000000000000000000000000000000 --- a/app/views/devise/confirmations/new.html.erb +++ /dev/null @@ -1,12 +0,0 @@ -<h2>Resend confirmation instructions</h2> - -<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> - <%= devise_error_messages! %> - - <div><%= f.label :email %><br /> - <%= f.email_field :email %></div> - - <div><%= f.submit "Resend confirmation instructions" %></div> -<% end %> - -<%= render partial: "devise/shared/links" %> diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..387ec7676d4763e15db48c064cf68b970ae1382b --- /dev/null +++ b/app/views/devise/confirmations/new.html.haml @@ -0,0 +1,8 @@ +.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_error_messages! + = f.email_field :email, placeholder: 'Email' + %div= f.submit "Resend confirmation instructions", class: 'btn btn-success' + %hr + = link_to "Sign in", new_session_path(resource_name) diff --git a/app/views/events/_events.html.haml b/app/views/events/_events.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..3d62d4788690528c4d2c3f06019d73605e466423 --- /dev/null +++ b/app/views/events/_events.html.haml @@ -0,0 +1 @@ += render @events diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 10b974ea222a70b6793160c6484e37c87ae7c35f..5b5f8a20c197bd8cb6e4cb1684d26ebe01690c31 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -51,10 +51,7 @@ %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" diff --git a/app/views/groups/show.js.haml b/app/views/groups/show.js.haml deleted file mode 100644 index 7e5a148e5ef2d56f87c72403ee7525cffc8cfc14..0000000000000000000000000000000000000000 --- a/app/views/groups/show.js.haml +++ /dev/null @@ -1,2 +0,0 @@ -:plain - Pager.append(#{@events.count}, "#{escape_javascript(render(@events))}"); diff --git a/app/views/help/_layout.html.haml b/app/views/help/_layout.html.haml index da917888eeece82f46355f29d4f224fbb3ef3577..7928937c60aebd5d8aed0db324ad93d9a9d24d5a 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 + %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 .span9.pull-right = yield diff --git a/app/views/help/permissions.html.haml b/app/views/help/permissions.html.haml index df35f41fc9046c6383220b28274c34f9ceb02606..15e3bf3a135c755af28a4b9999fde3abe7a8a811 100644 --- a/app/views/help/permissions.html.haml +++ b/app/views/help/permissions.html.haml @@ -143,7 +143,7 @@ %td.permission-x ✓ %td.permission-x ✓ %tr - %td Switch public mode + %td Switch visibility level %td %td %td diff --git a/app/views/help/public_access.html.haml b/app/views/help/public_access.html.haml index c67402ee319610a1cc54f13fbe474ca1275037d0..ba2df8c355393bca832ddca1dc4940c2c28a0d95 100644 --- a/app/views/help/public_access.html.haml +++ b/app/views/help/public_access.html.haml @@ -1,15 +1,46 @@ = render layout: 'help/layout' do %h3.page-title Public Access - %p - GitLab allows you to open selected projects to be accessed publicly. - These projects will be cloneable - %em without any - authentication. - Also they will be listed on the #{link_to "public access directory", public_root_path}. + %p.slead + GitLab allows you to open selected projects to be accessed + %strong publicly + or + %strong internally + \. + %br + Projects with either of these visibility levels will be listed in the #{link_to "public access directory", public_root_path}. + %br + Internal projects will only be available to authenticated users. + .clearfix + .dashboard-intro-icon + = public_icon + %h4 + Public projects + %p + Public project can be cloned + %strong without any + authentication. + %br + It will also be listed on the #{link_to "public access directory", public_root_path}. + %br + %strong Any logged in user + will have #{link_to "Guest", help_permissions_path} permissions on the repository. + + .clearfix + .dashboard-intro-icon + = internal_icon + %h4 + Internal projects + %p + Internal project can be cloned by any logged in user. + %br + It will also be listed on the #{link_to "public access directory", public_root_path} for logged in users. + %br + Any logged in user will have #{link_to "Guest", help_permissions_path} permissions on the repository. + + %h4 How to change project visibility %ol %li Go to your project dashboard %li Click on the "Edit" tab - %li Select "Public clone access" - + %li Change "Visibility Level" diff --git a/app/views/help/web_hooks.html.haml b/app/views/help/web_hooks.html.haml index 25865251de35127cca9a536373fb21f0edf06f4c..66ab7b75bdad45d33f070f7699a3f830caeb19c3 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 closed. + %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 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/layouts/_broadcast.html.haml b/app/views/layouts/_broadcast.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..4c4de743fdf513d42b72200044ca8024b8714ecb --- /dev/null +++ b/app/views/layouts/_broadcast.html.haml @@ -0,0 +1,4 @@ +- if broadcast_message.present? + .broadcast-message + %i.icon-bullhorn + = broadcast_message.message diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 792fe5e4a284e615a307e68d8fcf098e4fb96e8f..92edc71823539c875d72e27fc2475b9488224b11 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -2,6 +2,7 @@ %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 diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index 0e955d59ff8fc1b5c525220dc3622037ea25ed8f..b546a9fa84f96bc0350225f6f326a0e69fdd9ca4 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -1,7 +1,8 @@ !!! 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 diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index 73946f9988925039c83a26e36a6a2fb19eea6941..48c569f868400046269b94a6ba4b65847d65355f 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -10,6 +10,8 @@ = link_to "Users", admin_users_path = nav_link(controller: :logs) do = link_to "Logs", admin_logs_path + = nav_link(controller: :broadcast_messages) do + = link_to "Messages", admin_broadcast_messages_path = nav_link(controller: :hooks) do = link_to "Hooks", admin_hooks_path = nav_link(controller: :background_jobs) do diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index 30a0532bc2b1e065368b588a23b84c17713e4f66..7284963957137730662c4e2ed5f4b4a9221015f8 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -2,6 +2,7 @@ %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 diff --git a/app/views/layouts/project_settings.html.haml b/app/views/layouts/project_settings.html.haml index ea739da73d861b3548be95aa76516300eeb46a25..6a10d6cf9e16dd2ad7a2d89847b2ea13f0e6b31f 100644 --- a/app/views/layouts/project_settings.html.haml +++ b/app/views/layouts/project_settings.html.haml @@ -2,6 +2,7 @@ %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" diff --git a/app/views/layouts/projects.html.haml b/app/views/layouts/projects.html.haml index 6d8bf9b710b0fdca72c27e7500c9718fdf781f10..55214c6a5c936db9ff1a598332763156df599bf9 100644 --- a/app/views/layouts/projects.html.haml +++ b/app/views/layouts/projects.html.haml @@ -1,7 +1,8 @@ !!! 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" diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml index f922dcc420378f0fbca72a14c5933b0adedd5f93..8f490f61a9cf6bdbbebdfcc01ba062ddb67399d8 100644 --- a/app/views/layouts/public.html.haml +++ b/app/views/layouts/public.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "Public Projects" - %body{class: "ui_mars application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} - if current_user = render "layouts/head_panel", title: "Public Projects" - else diff --git a/app/views/layouts/public_projects.html.haml b/app/views/layouts/public_projects.html.haml index cfe6a63055adde65f7b4bb944df87f12fa107fb7..1e8814134f5c1b2433abe9aa7c5acfd9739f65f7 100644 --- a/app/views/layouts/public_projects.html.haml +++ b/app/views/layouts/public_projects.html.haml @@ -1,7 +1,7 @@ !!! 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 .container= render 'layouts/nav/project' diff --git a/app/views/notify/_note_message.html.haml b/app/views/notify/_note_message.html.haml index 88c4df55b7fdf715a90fcc8a445620e468f6733c..9e329af2d478918aa60ffd867450951917d2fe72 100644 --- a/app/views/notify/_note_message.html.haml +++ b/app/views/notify/_note_message.html.haml @@ -1,6 +1,6 @@ %p %strong #{@note.author_name} - left next message: + wrote: %cite{style: 'color: #666'} = markdown(@note.note) diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml index 6b2782ebb0b9b7a03477336c607632837fe68c1d..321f9418ded9c3e1648b2c42cc8096fee03b91a7 100644 --- a/app/views/notify/new_merge_request_email.html.haml +++ b/app/views/notify/new_merge_request_email.html.haml @@ -1,5 +1,5 @@ %p - = "New Merge Request !#{@merge_request.iid}" + = "New Merge Request ##{@merge_request.iid}" %p = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.target_project, @merge_request) %p diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb index 2d27350486e4f0e36a1393ab890ec50157ff300e..16be4bb619f04a68b749777f2e3e2c276a225222 100644 --- a/app/views/notify/new_merge_request_email.text.erb +++ b/app/views/notify/new_merge_request_email.text.erb @@ -1,4 +1,4 @@ -New Merge Request <%= @merge_request.iid %> +New Merge Request #<%= @merge_request.iid %> <%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %> diff --git a/app/views/notify/reassigned_merge_request_email.html.haml b/app/views/notify/reassigned_merge_request_email.html.haml index 3a615f86e69edce9ffc5dba7bc6e29f042bb2ecb..d2d82d36c4831da94bbea0b88ba349b6d251331f 100644 --- a/app/views/notify/reassigned_merge_request_email.html.haml +++ b/app/views/notify/reassigned_merge_request_email.html.haml @@ -1,5 +1,5 @@ %p - = "Reassigned Merge Request !#{@merge_request.iid}" + = "Reassigned Merge Request ##{@merge_request.iid}" = link_to_gfm truncate(@merge_request.title, length: 30), project_merge_request_url(@merge_request.target_project, @merge_request) %p Assignee changed diff --git a/app/views/notify/reassigned_merge_request_email.text.erb b/app/views/notify/reassigned_merge_request_email.text.erb index eecf055ff6f800821168e293d9ec1088a9136f62..87a7847e06d19f0b1f95cf4147464b64683fee05 100644 --- a/app/views/notify/reassigned_merge_request_email.text.erb +++ b/app/views/notify/reassigned_merge_request_email.text.erb @@ -1,4 +1,4 @@ -Reassigned Merge Request <%= @merge_request.iid %> +Reassigned Merge Request #<%= @merge_request.iid %> <%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %> diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index d382d2d70c439cb61af7788a9dfc86902b316866..d0c46ca6aff9eb4a2f66a92c988a049c38396778 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -21,16 +21,22 @@ .controls = f.text_field :name, class: "input-xlarge", required: true %span.help-block Enter your name, so people you know can recognize you. + .control-group = f.label :email, class: "control-label" .controls - = f.text_field :email, class: "input-xlarge", required: true - - if @user.unconfirmed_email.present? - %span.help-block - We sent confirmation email to - %strong #{@user.unconfirmed_email} + - if @user.ldap_user? + = f.text_field :email, class: "input-xlarge", required: true, readonly: true + %span.help-block.light + Email is read-only for LDAP user - else - %span.help-block We also use email for avatar detection if no avatar is uploaded. + = f.text_field :email, class: "input-xlarge", 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. .control-group = f.label :skype, class: "control-label" .controls= f.text_field :skype, class: "input-xlarge" @@ -53,9 +59,14 @@ .clearfix .profile-avatar-form-option %p.light - You can upload an avatar here - %br - or change it at #{link_to "gravatar.com", "http://gravatar.com"} + - 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 @@ -63,7 +74,10 @@ %span.file_name.js-avatar-filename File name... = f.file_field :avatar, class: "js-user-avatar-input hide" - %span.help-block The maximum file size allowed is 100KB. + .light The maximum file size allowed is 100KB. + - if @user.avatar? + %hr + = link_to 'Remove avatar', profile_avatar_path, 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/projects/_dropdown.html.haml b/app/views/projects/_dropdown.html.haml index e646d04282e50abb2a13522dd9b3900b07208aee..e283bd2bf1d951858dcd9976a27d0e972b78e2e6 100644 --- a/app/views/projects/_dropdown.html.haml +++ b/app/views/projects/_dropdown.html.haml @@ -6,15 +6,19 @@ - if @project.issues_enabled && can?(current_user, :write_issue, @project) %li = link_to url_for_new_issue, title: "New Issue" do - Issue + 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 - Merge Request + 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 - Snippet + 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 @@ -26,9 +30,4 @@ %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/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..19c150b54fb2f8f835fb43447d7139cb9261d04a --- /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)} + .row + .span5 + %h4.project-home-title + = @project.name_with_namespace + %span.visibility-level-label + = visibility_level_icon(@project.visibility_level) + = visibility_level_label(@project.visibility_level) + + .span7 + - unless empty_repo + .project-home-dropdown + = render "dropdown" + .form-horizontal + = render "shared/clone_panel" + + .project-home-extra.clearfix + .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 + .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 \ No newline at end of file diff --git a/app/views/projects/_visibility_level.html.haml b/app/views/projects/_visibility_level.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..7fc257811ca72076239781da640765e32db9f496 --- /dev/null +++ b/app/views/projects/_visibility_level.html.haml @@ -0,0 +1,26 @@ +.control-group.project-visibility-level-holder + = f.label :visibility_level do + Visibility Level + = link_to "(?)", help_public_access_path + - if can_change_visibility_level + - Gitlab::VisibilityLevel.values.each do |level| + - restricted = restricted_visibility_levels.include?(level) + .controls + = f.radio_button :visibility_level, level, checked: (visibility_level == level), disabled: restricted + %span.descr{:class => ("restricted" if restricted)} + = label :project_visibility_level, level do + = visibility_level_icon(level) + %strong + = visibility_level_label(level) + .light= visibility_level_description(level) + - unless restricted_visibility_levels.empty? + .controls + %span.info + Some visibility level settings have been restricted by the administrator. + - else + .controls + %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 f6cc62e707e09473c875d47d5fad35ac1bea402c..2f82bfe52f32122bba85be41760e395f1319bb36 100644 --- a/app/views/projects/blob/_actions.html.haml +++ b/app/views/projects/blob/_actions.html.haml @@ -4,7 +4,7 @@ - if allowed_tree_edit? = link_to "edit", project_edit_tree_path(@project, @id), class: "btn btn-small" - else - %span.btn.btn-small.disabled Edit + %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? @@ -13,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/_remove.html.haml b/app/views/projects/blob/_remove.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..1c097330c44bc073559285c8c311aabf31845a38 --- /dev/null +++ b/app/views/projects/blob/_remove.html.haml @@ -0,0 +1,19 @@ +%div#modal-remove-blob.modal.hide + .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 do + .control-group.commit_message-group + = label_tag 'commit_message', class: "control-label" do + Commit message + .controls + = text_area_tag 'commit_message', params[:commit_message], placeholder: "Removed this file because...", required: true, rows: 3 + .control-group + .controls + = 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/show.html.haml b/app/views/projects/blob/show.html.haml index d96595bc7f08b8ef479340a50a0b969a0fec7d10..56220e520f3b376d679ebd6419e67e28131151de 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -2,3 +2,6 @@ = render 'shared/ref_switcher', destination: 'blob', path: @path %div#tree-holder.tree-holder = render 'blob', blob: @blob + +- if allowed_tree_edit? + = render 'projects/blob/remove' diff --git a/app/views/projects/branches/_filter.html.haml b/app/views/projects/branches/_filter.html.haml index 7ea11a74a2b8ca6f1ea61f2593d03ab7c221410c..7e1478292e01b3deb8c3aaed00d311ded225cbbd 100644 --- a/app/views/projects/branches/_filter.html.haml +++ b/app/views/projects/branches/_filter.html.haml @@ -1,12 +1,22 @@ %ul.nav.nav-pills.nav-stacked = nav_link(path: 'branches#recent') do - = link_to 'Recent', recent_project_branches_path(@project) + = link_to recent_project_branches_path(@project) do + Recent + .pull-right + = @repository.recent_branches.count + = nav_link(path: 'protected_branches#index') do = link_to project_protected_branches_path(@project) do Protected %i.icon-lock + .pull-right + = @project.protected_branches.count + = nav_link(path: 'branches#index') do - = link_to 'All branches', project_branches_path(@project) + = link_to project_branches_path(@project) do + All branches + .pull-right + = @repository.branch_names.count %hr diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index d70fd96accdf22d8f5872b5b9073e45a02fa91fb..9c60c40980811001f8173ed6b12249fe05cc6429 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -7,7 +7,7 @@ .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 diff --git a/app/views/projects/commits/_diffs.html.haml b/app/views/projects/commits/_diffs.html.haml index 2e61b9ece19b77e66725f32e122430b96f3873a5..2a32f56e8f0d9b216600d8053e99ec8bd139c2c5 100644 --- a/app/views/projects/commits/_diffs.html.haml +++ b/app/views/projects/commits/_diffs.html.haml @@ -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 @@ -46,7 +50,7 @@ %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,7 +66,10 @@ -# 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 = 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 diff --git a/app/views/projects/commits/_parallel_view.html.haml b/app/views/projects/commits/_parallel_view.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..3234e9da0ac88fa923ee22fb156ea49cdc5e56db --- /dev/null +++ b/app/views/projects/commits/_parallel_view.html.haml @@ -0,0 +1,75 @@ +/ Side-by-side diff view +- old_file = get_old_file(project, @commit, diff) +- deleted_lines = {} +- added_lines = {} +- each_diff_line(diff, index) do |line, type, line_code, line_new, line_old, raw_line| + - if type == "old" + - deleted_lines[line_old] = { line_code: line_code, type: type, line: line } + - elsif type == "new" + - added_lines[line_new] = { line_code: line_code, type: type, line: line } + +- max_length = old_file.sloc + added_lines.length if old_file +- max_length ||= file.sloc +- offset1 = 0 +- offset2 = 0 + +%div.text-file-parallel + %table{ style: "table-layout: fixed;" } + - max_length.times do |line_index| + - line_index1 = line_index - offset1 + - line_index2 = line_index - offset2 + - deleted_line = deleted_lines[line_index1 + 1] + - added_line = added_lines[line_index2 + 1] + - old_line = old_file.lines[line_index1] if old_file + - new_line = file.lines[line_index2] + + - if deleted_line && added_line + - elsif deleted_line + - new_line = nil + - offset2 += 1 + - elsif added_line + - old_line = nil + - offset1 += 1 + + %tr.line_holder.parallel + - if line_index == 0 && diff.new_file + %td.line_content.parallel= "File was created" + %td.old_line= "" + - elsif deleted_line + %td.line_content{class: "parallel noteable_line old #{deleted_line[:line_code]}", "line_code" => deleted_line[:line_code] }= old_line + %td.old_line.old + = line_index1 + 1 + - if @comments_allowed + =# render "projects/notes/diff_note_link", line_code: deleted_line[:line_code] + - elsif old_line + %td.line_content.parallel= old_line + %td.old_line= line_index1 + 1 + - else + %td.line_content.parallel= "" + %td.old_line= "" + + %td.diff_line= "" + + - if diff.deleted_file && line_index == 0 + %td.new_line= "" + %td.line_content.parallel= "File was deleted" + - elsif added_line + %td.new_line.new + = line_index2 + 1 + - if @comments_allowed + =# render "projects/notes/diff_note_link", line_code: added_line[:line_code] + %td.line_content{class: "parallel noteable_line new #{added_line[:line_code]}", "line_code" => added_line[:line_code] }= new_line + - elsif new_line + %td.new_line= line_index2 + 1 + %td.line_content.parallel= new_line + - else + %td.new_line= "" + %td.line_content.parallel= "" + + - if @reply_allowed + - comments1 = [] + - comments2 = [] + - comments1 = @line_notes.select { |n| n.line_code == deleted_line[:line_code] }.sort_by(&:created_at) if deleted_line + - comments2 = @line_notes.select { |n| n.line_code == added_line[:line_code] }.sort_by(&:created_at) if added_line + - unless comments1.empty? && comments2.empty? + = render "projects/notes/diff_notes_with_reply_parallel", notes1: comments1, notes2: comments2, line1: deleted_line, line2: added_line \ No newline at end of file diff --git a/app/views/projects/commits/_text_file.html.haml b/app/views/projects/commits/_text_file.html.haml index c724213878ac5d949fb87bd2820a36413876a2a9..c827d96d855847b8738c65a1546a5121f421c2a6 100644 --- a/app/views/projects/commits/_text_file.html.haml +++ b/app/views/projects/commits/_text_file.html.haml @@ -21,3 +21,4 @@ - comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at) - unless comments.empty? = render "projects/notes/diff_notes_with_reply", notes: comments, line: line + diff --git a/app/views/projects/commits/show.js.haml b/app/views/projects/commits/show.js.haml deleted file mode 100644 index 045c9dd83d705dc839e10e3f10bbb9ebda99f364..0000000000000000000000000000000000000000 --- a/app/views/projects/commits/show.js.haml +++ /dev/null @@ -1,3 +0,0 @@ -:plain - CommitsList.append(#{@commits.count}, "#{escape_javascript(render('projects/commits/commits'))}"); - diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 6ecb24fcf950ae37f6d9bda6fe738a07e1110847..d282fc626e1eff76597e19d9be8b184671520ec2 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -29,22 +29,7 @@ .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. + = render "visibility_level", f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can?(current_user, :change_visibility_level, @project) %fieldset.features %legend @@ -124,7 +109,7 @@ %span Namespace .controls .control-group - = f.select :namespace_id, namespaces_options(@project.namespace_id), {prompt: 'Choose a project namespace'}, {class: 'chosen'} + = f.select :namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace' }, { class: 'chosen' } %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. @@ -144,7 +129,9 @@ %span Path .controls .control-group - = f.text_field :path + .input-append + = f.text_field :path + %span.add-on .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. diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 04fc0c31733dfea69f40e915ab79276590c9dc0c..3ed22015c0bcde0abe207f50ee6ef08846338e36 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -1,7 +1,4 @@ -%h3.page-title - = @project.name_with_namespace - .form-horizontal.pull-right - = render "shared/clone_panel" += render "home_panel" - if @project.import? && !@project.imported .save-project-loader diff --git a/app/views/projects/fork.html.haml b/app/views/projects/fork.html.haml index a1c109e5d624da6c6f08e771cecc56bc402d7497..227fde8a735286fb221d9dd26f02da445edff842 100644 --- a/app/views/projects/fork.html.haml +++ b/app/views/projects/fork.html.haml @@ -3,9 +3,9 @@ %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/hooks/_data_ex.html.erb b/app/views/projects/hooks/_data_ex.html.erb deleted file mode 100644 index 5092aaf6750e54cb2af13bbd3869da2e9cdbd2fb..0000000000000000000000000000000000000000 --- a/app/views/projects/hooks/_data_ex.html.erb +++ /dev/null @@ -1,44 +0,0 @@ -<% data_ex_str = <<eos -{ - "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, -}; -eos -%> -<div class="<%= user_color_scheme_class%>"> - <%= raw Pygments::Lexer[:js].highlight(data_ex_str) %> -</div> diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml index f748eb29294294b19ea61d55a26a57136df8fab2..e1166742b2ea23f6900beafce68b9f51aee47035 100644 --- a/app/views/projects/hooks/index.html.haml +++ b/app/views/projects/hooks/index.html.haml @@ -1,9 +1,9 @@ %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 happends to the the project. %hr.clearfix @@ -13,23 +13,50 @@ - @hook.errors.full_messages.each do |msg| %p= msg .control-group - = f.label :url, "URL:" + = 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" + .control-group + = f.label :url, "Trigger" + .controls + %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 in case of push to 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 for created issues + %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 for created merge requests %hr -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" + .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/_head.html.haml b/app/views/projects/issues/_head.html.haml index 438cc02b47719c29ac5e36bb925cf0194f2d2b4d..a44db78a92b4f4f40cb66cd7922d754a62c91452 100644 --- a/app/views/projects/issues/_head.html.haml +++ b/app/views/projects/issues/_head.html.haml @@ -1,11 +1,25 @@ %ul.nav.nav-tabs = 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 + - 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, id: "issue_search_form", class: 'pull-right issue-search-form' do + = 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' } diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index 427d653313462fd539b7718138f7e64f594121ba..e2ce26feac3fe175891358672981113f2672fb59 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" @@ -78,6 +78,29 @@ %strong= milestone.title %small.light= milestone.expires_at + .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 + %ul.well-list.issues-list = render @issues @@ -90,4 +113,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.html.haml b/app/views/projects/issues/index.html.haml index 81594b4972e3161d2d0b655f9ec8c994f2f322b3..3694798a7495041766f976a3401ae24ba15ab19e 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -1,21 +1,4 @@ = 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 = render 'shared/project_filter', project_entities_path: project_issues_path(@project) diff --git a/app/views/projects/issues/index.js.haml b/app/views/projects/issues/index.js.haml deleted file mode 100644 index 1be6a64f535792057aefc30822dede6475e053ce..0000000000000000000000000000000000000000 --- a/app/views/projects/issues/index.js.haml +++ /dev/null @@ -1,4 +0,0 @@ -:plain - $('.issues-holder').html("#{escape_javascript(render('issues'))}"); - History.replaceState({path: "#{request.url}"}, document.title, "#{request.url}"); - Issues.reload(); diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index d08a8af2bb935890e919a789f8bb2f98c3af3b00..36ea57805a8b0b23477bc752d9b3fd5d569545f3 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -33,6 +33,8 @@ %h4.box-title - if @issue.closed? .state-label.state-label-red Closed + - else + .state-label.state-label-green Open = gfm escape_once(@issue.title) .ui-box-body @@ -71,4 +73,4 @@ - @issue.participants.each do |participant| = link_to_member(@project, participant, name: false, size: 24) -.voting_notes#notes= render "projects/notes/notes_with_form" +.voting_notes#notes= render "projects/notes/notes_with_form" \ No newline at end of file diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml index ce72756303e76ce190f710720ac6fc65a482c67b..c78be4965d0337044fef01507dcd21062aa2b771 100644 --- a/app/views/projects/merge_requests/_form.html.haml +++ b/app/views/projects/merge_requests/_form.html.haml @@ -13,7 +13,6 @@ = f.select(:source_project_id,[[@merge_request.source_project.path_with_namespace,@merge_request.source_project.id]] , {}, {class: 'source_project chosen span3'}) .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'}) .mr_source_commit.prepend-top-10 .span2 @@ -26,7 +25,6 @@ = f.select(:target_project_id, projects.map { |proj| [proj.path_with_namespace,proj.id] }, {include_blank: "Select Target Project" }, {class: 'target_project chosen span3'}) .pull-left - %i.icon-code-fork = f.select(:target_branch, @target_branches, { include_blank: "Select branch" }, {class: 'target_branch chosen span2'}) .mr_target_commit.prepend-top-10 diff --git a/app/views/projects/merge_requests/commits.js.haml b/app/views/projects/merge_requests/commits.js.haml deleted file mode 100644 index 923b1ea032f544166c91638f3b5dd520a7d55235..0000000000000000000000000000000000000000 --- a/app/views/projects/merge_requests/commits.js.haml +++ /dev/null @@ -1,4 +0,0 @@ -:plain - merge_request.$(".commits").html("#{escape_javascript(render(partial: "commits"))}"); - - diff --git a/app/views/projects/merge_requests/diffs.js.haml b/app/views/projects/merge_requests/diffs.js.haml deleted file mode 100644 index 2964f0ec462a02c2be298ed8cbd19cb80de16bd7..0000000000000000000000000000000000000000 --- a/app/views/projects/merge_requests/diffs.js.haml +++ /dev/null @@ -1,2 +0,0 @@ -:plain - merge_request.$(".diffs").html("#{escape_javascript(render(partial: "projects/merge_requests/show/diffs"))}"); diff --git a/app/views/projects/merge_requests/show.js.haml b/app/views/projects/merge_requests/show.js.haml deleted file mode 100644 index 2ce6eb6329015cf4e3fa16cc30f7a02d597147c3..0000000000000000000000000000000000000000 --- a/app/views/projects/merge_requests/show.js.haml +++ /dev/null @@ -1,2 +0,0 @@ -:plain - merge_request.$(".notes").html("#{escape_javascript(render "notes/notes_with_form")}"); diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml index e1d0c7c11eccfe3623c8262f4bed9dd313b4a1ca..b1c73b439cc9253eaaf132a7da2f6c31fbe04e77 100644 --- a/app/views/projects/merge_requests/show/_mr_box.html.haml +++ b/app/views/projects/merge_requests/show/_mr_box.html.haml @@ -13,13 +13,14 @@ .ui-box-body %div %cite.cgray - Created on #{@merge_request.created_at.stamp("Aug 21, 2011")} by #{link_to_member(@project, @merge_request.author)} + Created on #{@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)} + Currently assigned to #{link_to_member(@project, @merge_request.assignee)}. - if @merge_request.milestone - 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? diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 466a63dee83b10bc247f070232ca3b8a72c932a6..ee6c42b6ea84740fef47901d5038a93df5f63a43 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -47,12 +47,7 @@ %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.default_projects_features.public }, true, false - %span.help-inline Make project visible to everyone + = 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 diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..936dbb354cd501c11c345e4c02b0e01eb4a2fef9 --- /dev/null +++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml @@ -0,0 +1,34 @@ +- note1 = notes1.first # example note +- note2 = notes2.first # example note +%tr.notes_holder + -# Check if line want not changed since comment was left + /- if !defined?(line1) || line1 == note1.diff_line + - if note1 + %td.notes_content + %ul.notes{ rel: note1.discussion_id } + = render notes1 + = render "projects/notes/discussion_reply_button", note: note1 + %td.notes_line2 + %span.btn.disabled.parallel-comment + %i.icon-comment + = notes1.count + - else + %td= "" + %td= "" + + %td= "" + + -# Check if line want not changed since comment was left + /- if !defined?(line2) || line2 == note2.diff_line + - if note2 + %td.notes_line + %span.btn.disabled.parallel-comment + %i.icon-comment + = notes2.count + %td.notes_content + %ul.notes{ rel: note2.discussion_id } + = render notes2 + = render "projects/notes/discussion_reply_button", note: note2 + - else + %td= "" + %td= "" diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index 7add2921830fd24916657b4742556f01576a7459..a742140cf5a3288a792001f10b23b719518229b6 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -7,10 +7,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.btn-primary.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,7 +29,7 @@ %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 ... diff --git a/app/views/projects/notes/index.js.haml b/app/views/projects/notes/index.js.haml deleted file mode 100644 index 6c4ed203497228da165b276c8c166b090808c8c1..0000000000000000000000000000000000000000 --- a/app/views/projects/notes/index.js.haml +++ /dev/null @@ -1,4 +0,0 @@ -- unless @notes.blank? - var notesHtml = "#{escape_javascript(render 'projects/notes/notes')}"; - - new_note_ids = @notes.map(&:id) - NoteList.setContent(#{new_note_ids}, notesHtml); diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index a099193cb6e762983ffb374afe97a90208acf2da..202bf3272176e3aa88ed6afe6755d338012e51b7 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 diff --git a/app/views/projects/services/index.html.haml b/app/views/projects/services/index.html.haml index 82b85a18acdf44765d677a73735ec2b50ae1ef72..190aa69dab7f5f8a1c7a1a83131fe83758e3ab7c 100644 --- a/app/views/projects/services/index.html.haml +++ b/app/views/projects/services/index.html.haml @@ -6,12 +6,8 @@ - @services.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 cad072a83db86dbf63e8200fdb00d154248f668b..bfcd917d7f4bdb0d38da18203e3f4b4e8fca5629 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,32 +1,4 @@ -.project-home-panel - .row - .span4 - %h4.project-home-title - = @project.name_with_namespace - - if @project.public - %span.public-label Public - - else - %span.public-label Private - - .span8 - .project-home-dropdown - = render "dropdown" - .form-horizontal - = render "shared/clone_panel" - - .project-home-extra.clearfix - .project-home-desc - - if @project.description.present? - = @project.description - - if can?(current_user, :admin_project, @project) - – - %strong= link_to 'Edit', edit_project_path - - .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 += render "home_panel" .row .span9 @@ -34,19 +6,20 @@ = render 'shared/event_filter' .content_list .loading.hide - .span3 + .span3.project-side .clearfix - if @project.forked_from_project .alert.alert-success - %i.icon-code-fork + %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-ok - Already forked + %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 @@ -56,8 +29,15 @@ = link_to archive_project_repository_path(@project), class: "btn btn-block" do %i.icon-download-alt %span Download - %br - .light-well + = 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 on #{@project.created_at.stamp('Aug 22, 2013')} diff --git a/app/views/projects/show.js.haml b/app/views/projects/show.js.haml deleted file mode 100644 index 511f278929e3fa818793bd14a8e199b8c803b983..0000000000000000000000000000000000000000 --- a/app/views/projects/show.js.haml +++ /dev/null @@ -1,2 +0,0 @@ -:plain - Pager.append(#{@events.count}, "#{escape_javascript(render(@events))}"); diff --git a/app/views/public/projects/index.html.haml b/app/views/public/projects/index.html.haml index 21aee6445795df849988cbf5d7cb24784c55c9bd..b88169add3c83334b61d57fc728a14cfc8529a02 100644 --- a/app/views/public/projects/index.html.haml +++ b/app/views/public/projects/index.html.haml @@ -19,6 +19,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/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index ac7b9ee7f2d8143744882f40f7f8b4f285077ff4..097c81100ba0eebc461b7c35a93f9605ed5b8a8a 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -1,4 +1,4 @@ .git-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 span5", readonly: true + %button{class: "btn #{ current_user ? 'active' : '' }", :"data-clone" => @project.ssh_url_to_repo} SSH + %button{class: "btn #{ current_user ? '' : 'active' }", :"data-clone" => @project.http_url_to_repo}= gitlab_config.protocol.upcase + = text_field_tag :project_clone, (current_user ? @project.url_to_repo : @project.http_url_to_repo), class: "one_click_select span5", readonly: true diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml index b7a7ca8fcc8874c9414f1898d6081c54ff1c5df4..368aec5a4622a5c83a0f51ffe4948daad6a80155 100644 --- a/app/views/shared/_merge_requests.html.haml +++ b/app/views/shared/_merge_requests.html.haml @@ -4,6 +4,7 @@ - project = group[0] .title = link_to_project project + = link_to 'show all', project_merge_requests_path(project), class: 'pull-right' %ul.well-list.mr-list - group[1].each do |merge_request| = render 'projects/merge_requests/merge_request', merge_request: merge_request diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml index 6d363807d62541a05b2a70c4688e0d7c718283c3..2a365ce4f63c8da6aff68ed908649bd0e5e6fe8f 100644 --- a/app/views/shared/_no_ssh.html.haml +++ b/app/views/shared/_no_ssh.html.haml @@ -1,3 +1,6 @@ -- 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? + .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 + = link_to '#', class: 'pull-right hide-no-ssh-message' do + %i.icon-remove diff --git a/app/views/snippets/_blob.html.haml b/app/views/snippets/_blob.html.haml index c2e0d97a1179171accdb47929741891e53d8de2e..dc856f84be94034adaae4eb95b13453a36f63eb1 100644 --- a/app/views/snippets/_blob.html.haml +++ b/app/views/snippets/_blob.html.haml @@ -8,9 +8,18 @@ = 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 "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) + - 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 + = raw GitHub::Markup.render(@snippet.file_name, @snippet.data) - else + .file-content.code + %div{class: user_color_scheme_class} + = raw @snippet.colorize(formatter: :gitlab) + - else + .file-content.code %p.nothing_here_message Empty file diff --git a/app/views/snippets/_form.html.haml b/app/views/snippets/_form.html.haml index e77550e7be3921bb771dd8eda9bb7d3b4cd07284..517c81fae8dda222e8003173e4494f130337ce81 100644 --- a/app/views/snippets/_form.html.haml +++ b/app/views/snippets/_form.html.haml @@ -13,9 +13,20 @@ = f.label :title .controls= f.text_field :title, placeholder: "Example Snippet", class: 'input-xlarge', required: true .control-group - = f.label "Private?" + = f.label "Access" .controls - = f.check_box :private, {class: ''} + = 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 can see this snippet) + .control-group .file-editor = f.label :file_name, "File" @@ -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), 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 8514bc3ddd00317642dff2d59dfc61c566d85835..a50d813825f6cd9d50e5e9b634526bc15d42caaa 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 diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index a3b4bd0c9b58f1af30707178521d0fd4371abb81..95b80bca7c0d3839815561d17b83d2141de86da2 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -14,7 +14,6 @@ class RepositoryImportWorker project.imported = true project.save project.satellite.create unless project.satellite.exists? - project.discover_default_branch else project.imported = false end diff --git a/config/application.rb b/config/application.rb index d06d47c773a6aaf0de0fdb9178c4918b296e2cef..ca80e9718ce9cecd57a06e9f085964b736018842 100644 --- a/config/application.rb +++ b/config/application.rb @@ -70,7 +70,7 @@ 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 + # 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 @@ -80,7 +80,14 @@ module Gitlab # # 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] + end + end end end diff --git a/config/database.yml.mysql b/config/database.yml.mysql index e7a9227e41e935fda40da67d6e9e336fe97acb79..55ac088bc1df920a4c11fc3741f3b3cecb410b97 100644 --- a/config/database.yml.mysql +++ b/config/database.yml.mysql @@ -7,7 +7,7 @@ production: reconnect: false database: gitlabhq_production pool: 10 - username: gitlab + username: git password: "secure password" # host: localhost # socket: /tmp/mysql.sock diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index bea0b71fc1b005d26f21c545d003c901cb45e25f..ba779d384c173dbeec12ed91fcd5f893efa7564c 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -20,7 +20,7 @@ production: &base https: false # Uncomment and customize the last line to run in a non-root path - # WARNING: This feature is known to work, but unsupported + # 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 @@ -57,11 +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 expression, all issues referenced from the matched text will be closed. - # This happends when the commit is pushed or merged into the default branch of a project. + # 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]loses|[Ff]ixes) +#\d+ + # issue_closing_pattern: ([Cc]lose[sd]|[Ff]ixe[sd]) +#\d+ ## Default project features settings default_projects_features: @@ -70,7 +74,7 @@ production: &base wiki: true wall: false snippets: false - public: false + visibility_level: "private" # can be "private" | "internal" | "public" ## External issues trackers issues_tracker: @@ -112,6 +116,8 @@ production: &base # ========================== ## LDAP settings + # You can inspect the first 100 LDAP users with login access by running: + # bundle exec rake gitlab:ldap:check[100] RAILS_ENV=production ldap: enabled: false host: '_your_ldap_server' @@ -138,7 +144,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: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 942b77ffd2e2377444f4461dbaac461b6b65106f..2b13bb51e02515c8bcd93dd0ff06d90ecf8db1f7 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -30,6 +30,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,6 +91,7 @@ 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['default_projects_features'] ||= {} @@ -76,7 +100,7 @@ Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.g 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 diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index b7cb808d2e522243cd8096e658c6fe22d3cc3119..5da8932a6518758960cd1f72cad88c6740bdff4f 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -74,8 +74,8 @@ 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 + # 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 confirm_within 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 @@ -101,7 +101,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 diff --git a/config/initializers/rack_attack.rb.example b/config/initializers/rack_attack.rb.example index 76fa7ad282e9350fa1b14b0f882493e42deee3b3..1d10a53d505d0c4f8d449bc45c6984b8ad3994a8 100644 --- a/config/initializers/rack_attack.rb.example +++ b/config/initializers/rack_attack.rb.example @@ -1,16 +1,17 @@ -# 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}/api/#{API::API.version}/session.json", + "#{Rails.application.config.relative_url_root}/api/#{API::API.version}/session", "#{Rails.application.config.relative_url_root}/users" ] -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/routes.rb b/config/routes.rb index 58bbd2b650e2806ff538a73a31dd58b6b0267d8a..35143a4268cb54f27d7113e3ca597c48216782f0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -86,9 +86,16 @@ 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 @@ -120,6 +127,7 @@ Gitlab::Application.routes.draw do delete :leave end end + resource :avatar, only: [:destroy] end end @@ -166,7 +174,7 @@ Gitlab::Application.routes.draw do end scope module: :projects do - resources :blob, 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' diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example index 911c93b53f4fc8ee8e7c7a16f94ead82681b5dde..ba5e5cdde0b14776b080ff7c3ceb3f129faf07be 100644 --- a/config/unicorn.rb.example +++ b/config/unicorn.rb.example @@ -9,7 +9,7 @@ # documentation. # Uncomment and customize the last line to run in a non-root path -# WARNING: This feature is known to work, but unsupported +# 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 diff --git a/db/migrate/20131106151520_remove_default_branch.rb b/db/migrate/20131106151520_remove_default_branch.rb new file mode 100644 index 0000000000000000000000000000000000000000..88a890eb3eb7e759ced674d594f3218d57a91efe --- /dev/null +++ b/db/migrate/20131106151520_remove_default_branch.rb @@ -0,0 +1,9 @@ +class RemoveDefaultBranch < ActiveRecord::Migration + def up + remove_column :projects, :default_branch + end + + def down + add_column :projects, :default_branch, :string + end +end diff --git a/db/migrate/20131112114325_create_broadcast_messages.rb b/db/migrate/20131112114325_create_broadcast_messages.rb new file mode 100644 index 0000000000000000000000000000000000000000..147178e9dcf2dc7d490f82507aa6fe144f449cf3 --- /dev/null +++ b/db/migrate/20131112114325_create_broadcast_messages.rb @@ -0,0 +1,12 @@ +class CreateBroadcastMessages < ActiveRecord::Migration + def change + create_table :broadcast_messages do |t| + t.text :message, null: false + t.datetime :starts_at + t.datetime :ends_at + t.integer :alert_type + + t.timestamps + end + end +end diff --git a/db/migrate/20131112220935_add_visibility_level_to_projects.rb b/db/migrate/20131112220935_add_visibility_level_to_projects.rb new file mode 100644 index 0000000000000000000000000000000000000000..cf1e9f912a04957ee92486d189ae1edf74ddd83f --- /dev/null +++ b/db/migrate/20131112220935_add_visibility_level_to_projects.rb @@ -0,0 +1,13 @@ +class AddVisibilityLevelToProjects < ActiveRecord::Migration + def self.up + add_column :projects, :visibility_level, :integer, :default => 0, :null => false + Project.where(public: true).update_all(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + remove_column :projects, :public + end + + def self.down + add_column :projects, :public, :boolean, :default => false, :null => false + Project.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).update_all(public: true) + remove_column :projects, :visibility_level + end +end diff --git a/db/migrate/20131202192556_add_event_fields_for_web_hook.rb b/db/migrate/20131202192556_add_event_fields_for_web_hook.rb new file mode 100644 index 0000000000000000000000000000000000000000..d29e996852ec3e9a1f8e8f215a9ecfd74ec421fa --- /dev/null +++ b/db/migrate/20131202192556_add_event_fields_for_web_hook.rb @@ -0,0 +1,7 @@ +class AddEventFieldsForWebHook < ActiveRecord::Migration + def change + add_column :web_hooks, :push_events, :boolean, default: true, null: false + add_column :web_hooks, :issues_events, :boolean, default: false, null: false + add_column :web_hooks, :merge_requests_events, :boolean, default: false, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index d6acb2f90e935c2e1eab4bb513294e848f19b7fb..6219ce77696a06e026fcd782ecae078be400f29d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,16 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20131009115346) do +ActiveRecord::Schema.define(:version => 20131202192556) 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 + end create_table "deploy_keys_projects", :force => true do |t| t.integer "deploy_key_id", :null => false @@ -171,19 +180,18 @@ ActiveRecord::Schema.define(:version => 20131009115346) do t.datetime "created_at", :null => false t.datetime "updated_at", :null => false 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.integer "namespace_id" - t.boolean "public", :default => false, :null => false t.string "issues_tracker", :default => "gitlab", :null => false t.string "issues_tracker_id" t.boolean "snippets_enabled", :default => true, :null => false t.datetime "last_activity_at" t.boolean "imported", :default => false, :null => false t.string "import_url" + t.integer "visibility_level", :default => 0, :null => false end add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id" @@ -326,10 +334,13 @@ ActiveRecord::Schema.define(:version => 20131009115346) do 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", :null => false + t.datetime "updated_at", :null => false + 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" diff --git a/doc/api/projects.md b/doc/api/projects.md index 3ae9af59fc3ec089260e1c4f4ae70210d9648551..53acc4a025e424060033db72d9eab83200cd5ef0 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 @@ -15,6 +15,7 @@ GET /projects "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", @@ -49,6 +50,7 @@ GET /projects "description": null, "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", @@ -82,6 +84,22 @@ GET /projects ``` +#### 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 NAMESPACE/PROJECT_NAME , which is owned by the authentication user. @@ -101,6 +119,7 @@ Parameters: "description": null, "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", @@ -213,13 +232,13 @@ 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) ++ `public` (optional) - if `true` same as setting visibility_level = 20 ++ `visibility_level` (optional) ### Create project for user @@ -241,7 +260,8 @@ 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 @@ -382,6 +402,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" } ``` @@ -399,6 +423,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 @@ -414,6 +441,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 @@ -458,7 +488,7 @@ Parameters: "id":"3f94fc7c85061973edc9906ae170cc269b07ca55" }], "tree": "c68537c6534a02cc2b176ca1549f4ffa190b58ee", - "message":"give caolan his credit where it's due (up top)", + "message":"give caolan credit where it's due (up top)", "author": { "name":"Jeremy Ashkenas", "email":"jashkenas@example.com" diff --git a/doc/api/repositories.md b/doc/api/repositories.md index 2769c22d6aa69078f93f9abe0fec414b94d3d998..af7b82ca76df2c9bf409099cb7edabddcb91c073 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -368,4 +368,43 @@ 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 \ No newline at end of file ++ `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 ++ `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 ++ `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/development/architecture.md b/doc/development/architecture.md new file mode 100644 index 0000000000000000000000000000000000000000..db22f0bda8528688595a60cb8f9c4fb03be39554 --- /dev/null +++ b/doc/development/architecture.md @@ -0,0 +1,23 @@ +# GitLab project architecture + +GitLab project consists of two parts: GitLab and GitLab shell. + +## GitLab + +Web application with background jobs workers. +Provides you with UI and most of functionality. +For some operations like repo creation - uses GitLab shell. + +Uses: + * Ruby as main language for application code and most libraries. + * [Rails](http://rubyonrails.org/) web framework as main framework for application. + * Mysql or postgres as main databases. Used for persistent data storage(users, project, issues etc). + * Redis database. Used for cache and exchange data between some components. + * Python2 because of [pygments](http://pygments.org/) as code syntax highlighter. + +## GitLab shell + +Command line ruby application. Used by GitLab through shell commands. +It provides interface to all kind of manipulations with repositories and ssh keys. +Full list of commands you can find in README of GitLab shell repo. +Works on pure ruby and do not require any additional software. diff --git a/doc/install/databases.md b/doc/install/databases.md index be7bc0aad2ec471782a096548ad4b5f87aaca1ae..6016e97ede5a1c68599372a5ea084364d0744c3f 100644 --- a/doc/install/databases.md +++ b/doc/install/databases.md @@ -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 diff --git a/doc/install/installation.md b/doc/install/installation.md index 134ec3c1eccd55097a168e1a8706786929eb68f6..3e90663dbd81f5f2638ecc865c6c000f20ea38e7 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -118,8 +118,8 @@ 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 + 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 @@ -149,7 +149,7 @@ GitLab Shell is an ssh access and repository management software developed speci cd gitlab-shell # switch to right version - sudo -u git -H git checkout v1.7.4 + sudo -u git -H git checkout v1.7.9 sudo -u git -H cp config.yml.example config.yml @@ -180,10 +180,10 @@ To setup the MySQL/PostgreSQL database and dependencies please see [`doc/install cd /home/git/gitlab # Checkout to stable release - sudo -u git -H git checkout 6-2-stable + sudo -u git -H git checkout 6-3-stable **Note:** -You can change `6-2-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +You can change `6-3-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ## Configure it @@ -227,10 +227,6 @@ You can change `6-2-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" @@ -265,8 +261,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 @@ -428,5 +422,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 a9dd3481059fe464d104bcb922d55fc20343fb16..e9c95ba2ef949e6c4d665e4e387a3dbf3487346b 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -1,40 +1,45 @@ # Operating Systems -## Linux - 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 + +- Arch Linux - Fedora - Gentoo -- RHEL -## Other Unix Systems +On the above distributions it is pretty easy to install GitLab yourself. + +## Unsupported Unix Systems -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!** +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. -## Windows +## 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. +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 +# Ruby versions -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. +GitLab requires Ruby (MRI) 1.9.3 or 2.0+. +While it is generally possible to use other Rubies +(like [JRuby](http://jruby.org/) or [Rubinius](http://rubini.us/)) +it might require some work since GitLab uses several Gems that have native extensions. # Hardware requirements diff --git a/doc/legal/corporate_contributor_license_agreement.md b/doc/legal/corporate_contributor_license_agreement.md new file mode 100644 index 0000000000000000000000000000000000000000..bbc274f3b0cc76cdeafce2449889a9c067e1c6cf --- /dev/null +++ b/doc/legal/corporate_contributor_license_agreement.md @@ -0,0 +1,25 @@ +You accept and agree to the following terms and conditions for Your present and future Contributions submitted to GitLab.com. Except for the license granted herein to GitLab.com and recipients of software distributed by GitLab.com, You reserve all right, title, and interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with GitLab.com. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean the code, documentation or other original works of authorship expressly identified in Schedule B, as well as any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to GitLab.com for inclusion in, or documentation of, any of the products owned or managed by GitLab.com (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab.com or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab.com for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab.com and to recipients of software distributed by GitLab.com a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab.com and to recipients of software distributed by GitLab.com a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. + +4. You represent that You are legally entitled to grant the above license. You represent further that each employee of the Corporation designated on Schedule A below (or in a subsequent written modification to that Schedule) is authorized to submit Contributions on behalf of the Corporation. + +5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). + +6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, You may submit it to GitLab.com separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]". + +8. It is your responsibility to notify GitLab.com when any change is required to the list of designated employees authorized to submit Contributions on behalf of the Corporation, or to the Corporation's Point of Contact with GitLab.com. + +--------------------------------------- + +This text is licensed under the [Creative Commons Attribution 3.0 License](http://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office. diff --git a/doc/legal/individual_contributor_license_agreement.md b/doc/legal/individual_contributor_license_agreement.md new file mode 100644 index 0000000000000000000000000000000000000000..eaf5812ca4c5a2cba421df41b946f757bc9ec6df --- /dev/null +++ b/doc/legal/individual_contributor_license_agreement.md @@ -0,0 +1,25 @@ +You accept and agree to the following terms and conditions for Your present and future Contributions submitted to GitLab.com. Except for the license granted herein to GitLab.com and recipients of software distributed by GitLab.com, You reserve all right, title, and interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with GitLab.com. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to GitLab.com for inclusion in, or documentation of, any of the products owned or managed by GitLab.com (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab.com or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab.com for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab.com and to recipients of software distributed by GitLab.com a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab.com and to recipients of software distributed by GitLab.com a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. + +4. You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to GitLab.com, or that your employer has executed a separate Corporate CLA with GitLab.com. + +5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions. + +6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, You may submit it to GitLab.com separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [[]named here]". + +8. You agree to notify GitLab.com of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect. + +--------------------------------------- + +This text is licensed under the [Creative Commons Attribution 3.0 License](http://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office. diff --git a/doc/make_release.md b/doc/release/monthly.md similarity index 98% rename from doc/make_release.md rename to doc/release/monthly.md index 7d1115eca52f2693faf4f7ea29bf29346a2026e2..1e56e080675d93ddea1611fc3efff7f6bc8933d5 100644 --- a/doc/make_release.md +++ b/doc/release/monthly.md @@ -1,4 +1,4 @@ -# Things to do when creating new release +# 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? diff --git a/doc/release/security.md b/doc/release/security.md new file mode 100644 index 0000000000000000000000000000000000000000..a77cbae3eaa252a93b867a6ff7c861b922000612 --- /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 and @git_lab +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/update/5.1-to-5.4.md b/doc/update/5.1-to-5.4.md index e61303a6548955e506fc04180e8a4dcb317bdfb0..3061507f6a6e496cf1a0f0378939d462ab7afd0b 100644 --- a/doc/update/5.1-to-5.4.md +++ b/doc/update/5.1-to-5.4.md @@ -31,7 +31,7 @@ sudo -u git -H git checkout 5-4-stable # Latest version of 5-4-stable addresses ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v1.7.4 # Addresses CVE-2013-4490 +sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulnerabilities ``` ### 4. Install libs, migrations, etc. diff --git a/doc/update/5.1-to-6.0.md b/doc/update/5.1-to-6.0.md index 3907af1f98b39ac107091bcfbae8a6a239df302e..53a3c645241edd22bdf31afac7ff438f3b0c81b0 100644 --- a/doc/update/5.1-to-6.0.md +++ b/doc/update/5.1-to-6.0.md @@ -47,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 diff --git a/doc/update/5.3-to-5.4.md b/doc/update/5.3-to-5.4.md index 9e60f3bb8d5cd283cbbf5a7b0a4e656f35904b4c..11c0f7c627fcdc2fe72da7628caa5798d6835945 100644 --- a/doc/update/5.3-to-5.4.md +++ b/doc/update/5.3-to-5.4.md @@ -30,7 +30,7 @@ sudo -u git -H git checkout 5-4-stable # Latest version of 5-4-stable addresses ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v1.7.4 # Addresses CVE-2013-4490 +sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulnerabilities ``` ### 4. Install libs, migrations, etc. diff --git a/doc/update/5.4-to-6.0.md b/doc/update/5.4-to-6.0.md index 1137f371518ff832f48dc5feeb52450ac6188f3f..8990f8d034fb0184649cacfdc4915c09eaf91f98 100644 --- a/doc/update/5.4-to-6.0.md +++ b/doc/update/5.4-to-6.0.md @@ -47,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 diff --git a/doc/update/6.0-to-6.1.md b/doc/update/6.0-to-6.1.md index 43ec211fd14dfb6c56bdd277ca12ee5b8cf6081b..f02f054fda8319e7ba72ab09f8bc91c3254e6522 100644 --- a/doc/update/6.0-to-6.1.md +++ b/doc/update/6.0-to-6.1.md @@ -39,7 +39,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.4 +sudo -u git -H git checkout v1.7.9 ``` ### 4. Install libs, migrations, etc. diff --git a/doc/update/6.0-to-6.2.md b/doc/update/6.0-to-6.2.md index 00a27fcd43604f1b2e8ccb7fad1a2977a98e929c..03d7e96effe1197725b7b54e5d6f95e7a92be91f 100644 --- a/doc/update/6.0-to-6.2.md +++ b/doc/update/6.0-to-6.2.md @@ -47,7 +47,7 @@ sudo apt-get install logrotate ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v1.7.4 # Addresses CVE-2013-4490 +sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulnerabilities ``` ### 5. Install libs, migrations, etc. @@ -74,7 +74,7 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production TIP: to see what changed in gitlab.yml.example in this release use next command: ``` -git diff 6-1-stable:config/gitlab.yml.example 6-2-stable:config/gitlab.yml.example +git diff 6-0-stable:config/gitlab.yml.example 6-2-stable:config/gitlab.yml.example ``` * 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. diff --git a/doc/update/6.1-to-6.2.md b/doc/update/6.1-to-6.2.md index 2b5ad2a73ad6b7ad14bbf4261324a6d2c78be168..767ad80641c4b89fc9d7e29c48534b597f74623c 100644 --- a/doc/update/6.1-to-6.2.md +++ b/doc/update/6.1-to-6.2.md @@ -32,7 +32,7 @@ sudo -u git -H git checkout 6-2-stable # Latest version of 6-2-stable addresses ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v1.7.4 # Addresses CVE-2013-4490 +sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulnerabilities ``` ### 4. Install additional packages diff --git a/doc/update/6.2-to-6.3.md b/doc/update/6.2-to-6.3.md new file mode 100644 index 0000000000000000000000000000000000000000..ad4a5095447f3e0673e89afd3499ccd64291d9a0 --- /dev/null +++ b/doc/update/6.2-to-6.3.md @@ -0,0 +1,103 @@ +# 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 +sudo -u git -H git checkout 6-3-stable +``` + +### 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://github.com/gitlabhq/gitlabhq/blob/6-3-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-3-stable/config/unicorn.rb.example but with your settings. +* Copy rack attack middleware config + +### 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/patch_versions.md b/doc/update/patch_versions.md index 204047d35ab80bd3e1f940f47a666165575d6d6a..b284ff48365177ef2b57bf6262a2a7a535242e07 100644 --- a/doc/update/patch_versions.md +++ b/doc/update/patch_versions.md @@ -1,4 +1,4 @@ -# Universal update guide for patch versions. Ex. from From 6.2.0 to 6.2.1 +# 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 @@ -14,21 +14,25 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production sudo service gitlab stop -### 2. Get latest code for your current stable branch +### 2. Get latest code for the stable branch ```bash cd /home/git/gitlab -sudo -u git -H git pull origin 6-2-stable +sudo -u git -H git pull origin STABLE_BRANCH ``` -### 3. Update gitlab-shell if necessary +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 v1.7.4 +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 diff --git a/doc/update/ruby.md b/doc/update/ruby.md new file mode 100644 index 0000000000000000000000000000000000000000..f6f2fd5856bf8d94f145f41625cfd6291ebd24aa --- /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://github.com/gitlabhq/gitlabhq/blob/master/doc/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://github.com/gitlabhq/gitlabhq/blob/master/doc/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/features/admin/active_tab.feature b/features/admin/active_tab.feature index 226d3d5d5b5b567aec417932b40b648f212faae0..15fcda45e40324a432654503604d32ad51d86ba2 100644 --- a/features/admin/active_tab.feature +++ b/features/admin/active_tab.feature @@ -27,6 +27,11 @@ Feature: Admin active tab Then the active main tab should be Logs And no other main tabs should be active + Scenario: On Admin Messages + Given I visit admin messages page + Then the active main tab should be Messages + And no other main tabs should be active + Scenario: On Admin Hooks Given I visit admin hooks page Then the active main tab should be Hooks diff --git a/features/admin/broadcast_messages.feature b/features/admin/broadcast_messages.feature new file mode 100644 index 0000000000000000000000000000000000000000..0294b51a7c57e57f893c18afcc5729bd3752e8f1 --- /dev/null +++ b/features/admin/broadcast_messages.feature @@ -0,0 +1,13 @@ +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 diff --git a/features/profile/profile.feature b/features/profile/profile.feature index 6198fd2b306d635af0afc2363cfb55b6d08756a5..6b0421a20b354675da5c047aa5dd71a7aacf397d 100644 --- a/features/profile/profile.feature +++ b/features/profile/profile.feature @@ -26,6 +26,14 @@ Feature: Profile 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 diff --git a/features/project/commits/commits.feature b/features/project/commits/commits.feature index fe470f5ac99d52b142dd820e97a3ac30c0a3b78c..cbe8b3215071c62cf55c49f1c4885db2fdb47fc8 100644 --- a/features/project/commits/commits.feature +++ b/features/project/commits/commits.feature @@ -14,6 +14,12 @@ Feature: Project Browse commits Scenario: I browse commit from list Given I click on commit link Then I see commit info + And I see side-by-side diff button + + Scenario: I browse commit with side-by-side diff view + Given I click on commit link + And I click side-by-side diff button + Then I see inline diff button Scenario: I compare refs Given I visit compare refs page diff --git a/features/project/network.feature b/features/project/network.feature index ceae08c10743c661d8499fde50e263be00b79fa5..22beb1c50bc5a2b04f397d142f7e355ae472d5ad 100644 --- a/features/project/network.feature +++ b/features/project/network.feature @@ -29,11 +29,11 @@ Feature: Project Network Graph @javascript Scenario: I should filter selected tag When I switch ref to "v2.1.0" - Then page should have content not cotaining "v2.1.0" + Then page should have content not containing "v2.1.0" When click "Show only selected branch" checkbox - Then page should not have content not cotaining "v2.1.0" + Then page should not have content not containing "v2.1.0" When click "Show only selected branch" checkbox - Then page should have content not cotaining "v2.1.0" + Then page should have content not containing "v2.1.0" Scenario: I should fail to look for a commit When I look for a commit by ";" diff --git a/features/project/redirects.feature b/features/project/redirects.feature new file mode 100644 index 0000000000000000000000000000000000000000..ce197912f641f9c412ec24631441c17d99eb64ba --- /dev/null +++ b/features/project/redirects.feature @@ -0,0 +1,26 @@ +Feature: Project Redirects + Background: + Given public project "Community" + And private project "Enterprise" + + Scenario: I visit public project page + When I visit project "Community" page + Then I should see project "Community" home page + + Scenario: I visit private project page + When I visit project "Enterprise" page + Then I should be redirected to sign in page + + Scenario: I visit a non-existent project page + When I visit project "CommunityDoesNotExist" page + Then I should be redirected to sign in page + + Scenario: I visit a non-existent project page as user + Given I sign in as a user + When I visit project "CommunityDoesNotExist" page + Then page status code should be 404 + + Scenario: I visit unauthorized project page as user + Given I sign in as a user + When I visit project "Enterprise" page + Then page status code should be 404 diff --git a/features/project/service.feature b/features/project/service.feature index 4805d2befbea9abcdc0d5cd046d704720e2e0602..f8684f3b3b703a2ae92454271f3b2edf61c73451 100644 --- a/features/project/service.feature +++ b/features/project/service.feature @@ -30,3 +30,9 @@ Feature: Project Services 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 \ No newline at end of file diff --git a/features/project/source/multiselect_blob.feature b/features/project/source/multiselect_blob.feature new file mode 100644 index 0000000000000000000000000000000000000000..3038c0814ad227467b0fe5db6281f4b90b46dc0d --- /dev/null +++ b/features/project/source/multiselect_blob.feature @@ -0,0 +1,86 @@ +Feature: Project Multiselect Blob + Background: + Given I sign in as a user + And I own project "Shop" + And I visit project source page + And I click on "Gemfile.lock" file in repo + + @javascript + Scenario: I click line 1 in file + When I click line 1 in file + Then I should see "L1" as URI fragment + And I should see line 1 highlighted + + @javascript + Scenario: I shift-click line 1 in file + When I shift-click line 1 in file + Then I should see "L1" as URI fragment + And I should see line 1 highlighted + + @javascript + Scenario: I click line 1 then click line 2 in file + When I click line 1 in file + Then I should see "L1" as URI fragment + And I should see line 1 highlighted + Then I click line 2 in file + Then I should see "L2" as URI fragment + And I should see line 2 highlighted + + @javascript + Scenario: I click various line numbers to test multiselect + Then I click line 1 in file + Then I should see "L1" as URI fragment + And I should see line 1 highlighted + Then I shift-click line 2 in file + Then I should see "L1-2" as URI fragment + And I should see lines 1-2 highlighted + Then I shift-click line 3 in file + Then I should see "L1-3" as URI fragment + And I should see lines 1-3 highlighted + Then I click line 3 in file + Then I should see "L3" as URI fragment + And I should see line 3 highlighted + Then I shift-click line 1 in file + Then I should see "L1-3" as URI fragment + And I should see lines 1-3 highlighted + Then I shift-click line 5 in file + Then I should see "L1-5" as URI fragment + And I should see lines 1-5 highlighted + Then I shift-click line 4 in file + Then I should see "L1-4" as URI fragment + And I should see lines 1-4 highlighted + Then I click line 5 in file + Then I should see "L5" as URI fragment + And I should see line 5 highlighted + Then I shift-click line 3 in file + Then I should see "L3-5" as URI fragment + And I should see lines 3-5 highlighted + Then I shift-click line 1 in file + Then I should see "L1-3" as URI fragment + And I should see lines 1-3 highlighted + Then I shift-click line 1 in file + Then I should see "L1" as URI fragment + And I should see line 1 highlighted + + @javascript + Scenario: I multiselect lines 1-5 and then go back and forward in history + When I click line 1 in file + And I shift-click line 3 in file + And I shift-click line 2 in file + And I shift-click line 5 in file + Then I should see "L1-5" as URI fragment + And I should see lines 1-5 highlighted + Then I go back in history + Then I should see "L1-2" as URI fragment + And I should see lines 1-2 highlighted + Then I go back in history + Then I should see "L1-3" as URI fragment + And I should see lines 1-3 highlighted + Then I go back in history + Then I should see "L1" as URI fragment + And I should see line 1 highlighted + Then I go forward in history + And I go forward in history + And I go forward in history + Then I should see "L1-5" as URI fragment + And I should see lines 1-5 highlighted \ No newline at end of file diff --git a/features/public/public_projects.feature b/features/public/public_projects.feature index 178a769194c38b50e0dcfd31256b7a1e5179b88c..5a30c03dd4a5609b53b6e79f1147ab777227862a 100644 --- a/features/public/public_projects.feature +++ b/features/public/public_projects.feature @@ -1,18 +1,51 @@ 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 + + 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 a http link to the repository + + Scenario: I visit public area 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 a ssh link to the repository diff --git a/features/steps/admin/admin_active_tab.rb b/features/steps/admin/admin_active_tab.rb index f14c5f396bedbb433e31452be5e6ded96f7e7941..ccafe09c18f878ae0c30a312d3a3a59dfa871280 100644 --- a/features/steps/admin/admin_active_tab.rb +++ b/features/steps/admin/admin_active_tab.rb @@ -30,4 +30,8 @@ class AdminActiveTab < Spinach::FeatureSteps Then 'the active main tab should be Resque' do ensure_active_main_tab('Background Jobs') end + + Then 'the active main tab should be Messages' do + ensure_active_main_tab('Messages') + end end diff --git a/features/steps/admin/admin_broadcast_messages.rb b/features/steps/admin/admin_broadcast_messages.rb new file mode 100644 index 0000000000000000000000000000000000000000..4dfaac06ae431902dfb3835836ede291683a32a5 --- /dev/null +++ b/features/steps/admin/admin_broadcast_messages.rb @@ -0,0 +1,27 @@ +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 +end diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 753e2c19bcb325bd37de1e016dcd9287babdfcc9..a72f8a44f96ee74c3fe80f8c49e077b0ac7e2bf7 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -31,26 +31,49 @@ class Profile < Spinach::FeatureSteps @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" @@ -65,10 +88,6 @@ class Profile < Spinach::FeatureSteps 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 - end - step 'I reset my token' do within '.update-token' do @old_token = @user.private_token diff --git a/features/steps/project/project_browse_commits.rb b/features/steps/project/project_browse_commits.rb index 650bc3a16f7ad1363443da3f63aabd409bead6ee..d667b58240fcccb0eab59f4c31b87ca24dcf9eae 100644 --- a/features/steps/project/project_browse_commits.rb +++ b/features/steps/project/project_browse_commits.rb @@ -88,4 +88,17 @@ class ProjectBrowseCommits < Spinach::FeatureSteps links[0]['href'].should =~ %r{blob/bc3735004cb45cec5e0e4fa92710897a910a5957} links[1]['href'].should =~ %r{blob/cc1ba255d6c5ffdce87a357ba7ccc397a4f4026b} end + + Given 'I click side-by-side diff button' do + click_link "Side-by-side Diff" + end + + Then 'I see side-by-side diff button' do + page.should have_content "Side-by-side Diff" + end + + Then 'I see inline diff button' do + page.should have_content "Inline Diff" + end + end diff --git a/features/steps/project/project_multiselect_blob.rb b/features/steps/project/project_multiselect_blob.rb new file mode 100644 index 0000000000000000000000000000000000000000..3d330e837c1992111edf86f0fc1bcecd36eed71d --- /dev/null +++ b/features/steps/project/project_multiselect_blob.rb @@ -0,0 +1,58 @@ +class ProjectMultiselectBlob < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + class << self + def click_line_steps(*line_numbers) + line_numbers.each do |line_number| + step "I click line #{line_number} in file" do + find("#L#{line_number}").click + end + + step "I shift-click line #{line_number} in file" do + script = "$('#L#{line_number}').trigger($.Event('click', { shiftKey: true }));" + page.evaluate_script(script) + end + end + end + + def check_state_steps(*ranges) + ranges.each do |range| + fragment = range.kind_of?(Array) ? "L#{range.first}-#{range.last}" : "L#{range}" + pluralization = range.kind_of?(Array) ? "s" : "" + + step "I should see \"#{fragment}\" as URI fragment" do + URI.parse(current_url).fragment.should == fragment + end + + step "I should see line#{pluralization} #{fragment[1..-1]} highlighted" do + ids = Array(range).map { |n| "LC#{n}" } + extra = false + + highlighted = all("#tree-content-holder .highlight .line.hll") + highlighted.each do |element| + extra ||= ids.delete(element[:id]).nil? + end + + extra.should be_false and ids.should be_empty + end + end + end + end + + click_line_steps *Array(1..5) + check_state_steps *Array(1..5), Array(1..2), Array(1..3), Array(1..4), Array(1..5), Array(3..5) + + step 'I go back in history' do + page.evaluate_script("window.history.back()") + end + + step 'I go forward in history' do + page.evaluate_script("window.history.forward()") + end + + step 'I click on "Gemfile.lock" file in repo' do + click_link "Gemfile.lock" + end +end \ No newline at end of file diff --git a/features/steps/project/project_network_graph.rb b/features/steps/project/project_network_graph.rb index 127adecf7ed089f37ade7086cc84ec7a1d6fdc87..4954db2d7b1709ed7a67d50cd27a9b2d2a559a9d 100644 --- a/features/steps/project/project_network_graph.rb +++ b/features/steps/project/project_network_graph.rb @@ -43,13 +43,13 @@ 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 diff --git a/features/steps/project/project_services.rb b/features/steps/project/project_services.rb index 70eafc875d484483ce18b064fb7921ea31a60e0a..2f248090831effeb26ea8f4aeb9a88f0b37fd5a2 100644 --- a/features/steps/project/project_services.rb +++ b/features/steps/project/project_services.rb @@ -12,6 +12,7 @@ class ProjectServices < Spinach::FeatureSteps 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 @@ -72,4 +73,18 @@ class ProjectServices < Spinach::FeatureSteps Then 'I should see Flowdock service settings saved' do find_field('Token').value.should == 'verySecret' end + + And 'I click Assembla service link' do + click_link 'Assembla' + end + + And 'I fill Assembla settings' do + check 'Active' + fill_in 'Token', with: 'verySecret' + click_button 'Save' + end + + Then 'I should see Assembla service settings saved' do + find_field('Token').value.should == 'verySecret' + end end diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb new file mode 100644 index 0000000000000000000000000000000000000000..4ac53075704281407fa200f82a351d8449a6e0ef --- /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_with_code, 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 8abc6ae9f236082dba504167b79cc61cc3b4c83e..a4209bb9c78bbc3c7b0e06607aed55c36219fdec 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,11 +25,11 @@ 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_with_code, 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 :project, name: 'Empty Public Project', visibility_level: Gitlab::VisibilityLevel::PUBLIC end step 'I visit empty project page' do @@ -48,16 +50,48 @@ class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps 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 within '.project-home-title' do page.should have_content 'Community' end end - private + step 'internal project "Internal"' do + create :project_with_code, 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 + + Then 'I should see a 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 - def project - @project ||= Project.find_by_name("Community") + Then 'I should see a ssh link to the repository' do + project = Project.find_by_name 'Community' + page.should have_field('project_clone', with: project.url_to_repo) end end diff --git a/features/steps/shared/authentication.rb b/features/steps/shared/authentication.rb index 8c501bbc537edf281c1af5443f2dd2a522d0b987..df05754c287d3a6bf984b4c2a7ddd283032338da 100644 --- a/features/steps/shared/authentication.rb +++ b/features/steps/shared/authentication.rb @@ -12,6 +12,10 @@ module SharedAuthentication login_as :admin end + step 'I should be redirected to sign in page' do + current_path.should == new_user_session_path + end + def current_user @user || User.first end diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index 156fa5bab4e5b96308d262a1caed328ce1d316c2..987cd3120c9fa65235f05a9a76fd79bbad4d06de 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -105,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 diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb index bbdf5b97c8411bf1c07bd2b8c9699299f568ce47..1aea01f6cdf71317de8f0e550607c8ef92432fa9 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 diff --git a/lib/api/api.rb b/lib/api/api.rb index 4db81f42b4ce8fd8352b01dd0fa08df7ec32d2e0..283f7642f67f5770a68f283b986e3483edc8c176 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -39,5 +39,7 @@ module API mount DeployKeys mount ProjectHooks mount Services + mount Files + mount Namespaces end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 429083d75be1dd20106d9453a1d377eb7c94e4c4..7daf8ace2424223045bcc09b96df0b04d72e7e8a 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -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,11 +35,13 @@ 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 @@ -136,5 +142,9 @@ module API expose :target_id, :target_type, :author_id expose :data, :target_title end + + class Namespace < Grape::Entity + expose :id, :path, :kind + end end end diff --git a/lib/api/files.rb b/lib/api/files.rb new file mode 100644 index 0000000000000000000000000000000000000000..6a5419a580f108025a9de0e8e0fdf82b039df82c --- /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] + branch_name = attrs.delete(:branch_name) + file_path = attrs.delete(:file_path) + result = ::Files::CreateContext.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] + branch_name = attrs.delete(:branch_name) + file_path = attrs.delete(:file_path) + result = ::Files::UpdateContext.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::DeleteContext.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/helpers.rb b/lib/api/helpers.rb index edc662eaaabc6cc69f2b7629f17ffb2d41655895..b0f8d5a6da96714ea48661ef3c8a883e6e15f292 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -6,19 +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? @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 @@ -29,6 +33,7 @@ module API def set_current_user_for_thread Thread.current[:current_user] = current_user + begin yield ensure diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb new file mode 100644 index 0000000000000000000000000000000000000000..3a9ab66957ec2de20e5a6ef969ef5bbdf9ac34bc --- /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.scoped + @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/project_hooks.rb b/lib/api/project_hooks.rb index 738974955f3dc6c6dad4025c16c5c889f85af273..c271dd8b61bdb19dd523628450684bcb594191a1 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -22,7 +22,7 @@ module API # GET /projects/:id/hooks get ":id/hooks" do @hooks = paginate user_project.hooks - present @hooks, with: Entities::Hook + present @hooks, with: Entities::ProjectHook end # Get a project hook @@ -34,7 +34,7 @@ module API # GET /projects/:id/hooks/:hook_id get ":id/hooks/:hook_id" do @hook = user_project.hooks.find(params[:hook_id]) - present @hook, with: Entities::Hook + present @hook, with: Entities::ProjectHook end @@ -47,10 +47,11 @@ module API # POST /projects/:id/hooks post ":id/hooks" do required_attributes! [:url] + attrs = attributes_for_keys [:url, :push_events, :issues_events, :merge_requests_events] + @hook = user_project.hooks.new(attrs) - @hook = user_project.hooks.new({"url" => params[:url]}) if @hook.save - present @hook, with: Entities::Hook + present @hook, with: Entities::ProjectHook else if @hook.errors[:url].present? error!("Invalid url given", 422) @@ -70,10 +71,10 @@ module API put ":id/hooks/:hook_id" do @hook = user_project.hooks.find(params[:hook_id]) required_attributes! [:url] + attrs = attributes_for_keys [:url, :push_events, :issues_events, :merge_requests_events] - attrs = attributes_for_keys [:url] if @hook.update_attributes attrs - present @hook, with: Entities::Hook + present @hook, with: Entities::ProjectHook else if @hook.errors[:url].present? error!("Invalid url given", 422) diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 221f1f1e23ccac9696ff842f50db97a94bafb9e6..003533fb59a388ec773fee876340858bd4202b96 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,14 +92,15 @@ 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] + :public, + :visibility_level] + attrs = map_public_to_visibility_level(attrs) @project = ::Projects::CreateContext.new(current_user, attrs).execute if @project.saved? present @project, with: Entities::Project @@ -106,7 +124,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,7 +139,9 @@ module API :merge_requests_enabled, :wiki_enabled, :snippets_enabled, - :public] + :public, + :visibility_level] + attrs = map_public_to_visibility_level(attrs) @project = ::Projects::CreateContext.new(user, attrs).execute if @project.saved? present @project, with: Entities::Project @@ -282,7 +303,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/backup/database.rb b/lib/backup/database.rb index c4fb2e2e159d5309936cdfe5261ddb004cbbf284..7af7140246ae45d4037833398fa9ee5392dcb4de 100644 --- a/lib/backup/database.rb +++ b/lib/backup/database.rb @@ -13,20 +13,20 @@ module Backup def dump case config["adapter"] when /^mysql/ then - system("mysqldump #{mysql_args} #{config['database']} > #{db_file_name}") + system('mysqldump', *mysql_args, config['database'], out: db_file_name) when "postgresql" then pg_env - system("pg_dump #{config['database']} > #{db_file_name}") + system('pg_dump', config['database'], out: db_file_name) end end def restore case config["adapter"] when /^mysql/ then - system("mysql #{mysql_args} #{config['database']} < #{db_file_name}") + system('mysql', *mysql_args, config['database'], in: db_file_name) when "postgresql" then pg_env - system("psql #{config['database']} -f #{db_file_name}") + system('psql', config['database'], '-f', db_file_name) end end @@ -45,7 +45,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 diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index 258a0fb2589d5a8f803a24a1e16685bcb53417aa..efaefa4ce44649ade38f02694854b7111f5fd0f1 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -1,5 +1,7 @@ module Backup class Manager + BACKUP_CONTENTS = %w{repositories/ db/ uploads/ backup_information.yml} + def pack # saving additional informations s = {} @@ -16,7 +18,7 @@ module Backup # create archive print "Creating backup archive: #{s[:backup_created_at].to_i}_gitlab_backup.tar ... " - if Kernel.system("tar -cf #{s[:backup_created_at].to_i}_gitlab_backup.tar repositories/ db/ uploads/ backup_information.yml") + if Kernel.system('tar', '-cf', "#{s[:backup_created_at].to_i}_gitlab_backup.tar", *BACKUP_CONTENTS) puts "done".green else puts "failed".red @@ -25,7 +27,7 @@ module Backup def cleanup print "Deleting tmp directories ... " - if Kernel.system("rm -rf repositories/ db/ uploads/ backup_information.yml") + if Kernel.system('rm', '-rf', *BACKUP_CONTENTS) puts "done".green else puts "failed".red @@ -44,7 +46,7 @@ module Backup file_list.map! { |f| $1.to_i if f =~ /(\d+)_gitlab_backup.tar/ } file_list.sort.each do |timestamp| if Time.at(timestamp) < (Time.now - keep_time) - if system("rm #{timestamp}_gitlab_backup.tar") + if Kernel.system(*%W(rm #{timestamp}_gitlab_backup.tar)) removed += 1 end end @@ -75,7 +77,7 @@ module Backup end print "Unpacking backup ... " - unless Kernel.system("tar -xf #{tar_file}") + unless Kernel.system(*%W(tar -xf #{tar_file})) puts "failed".red exit 1 else diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index 252201f11be767f7117a87bbb6191a44cf82afef..20fd5ba92a10e9048acaee412b851f7cce4db5ce 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -18,7 +18,7 @@ module Backup # Create namespace dir if missing FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.path)) if project.namespace - if system("cd #{path_to_repo(project)} > /dev/null 2>&1 && git bundle create #{path_to_bundle(project)} --all > /dev/null 2>&1") + if system(*%W(git --git-dir=#{path_to_repo(project)} bundle create #{path_to_bundle(project)} --all), silent) puts "[DONE]".green else puts "[FAILED]".red @@ -30,7 +30,7 @@ module Backup print " * #{wiki.path_with_namespace} ... " if wiki.empty? puts " [SKIPPED]".cyan - elsif system("cd #{path_to_repo(wiki)} > /dev/null 2>&1 && git bundle create #{path_to_bundle(wiki)} --all > /dev/null 2>&1") + 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 @@ -53,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 @@ -63,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 @@ -73,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 @@ -103,5 +103,9 @@ module Backup FileUtils.rm_rf(backup_repos_path) FileUtils.mkdir_p(backup_repos_path) end + + def silent + {err: '/dev/null', out: '/dev/null'} + end end end diff --git a/lib/backup/uploads.rb b/lib/backup/uploads.rb index 462d3f1e274db73c55edcca37df4b09d6241f117..e79da7e8fd258dfd89fd51319a71f3d56ec06073 100644 --- a/lib/backup/uploads.rb +++ b/lib/backup/uploads.rb @@ -19,7 +19,7 @@ module Backup FileUtils.cp_r(backup_uploads_dir, app_uploads_dir) end - + def backup_existing_uploads_dir if File.exists?(app_uploads_dir) FileUtils.mv(app_uploads_dir, Rails.root.join('public', "uploads.#{Time.now.to_i}")) diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index e09cf3119722d1f98f44ab875afeb958f4bc90c2..c629144118c2c92e6ce6827fdf481cf7235df6a5 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -58,7 +58,7 @@ module Grack end else - return unauthorized unless project.public + return unauthorized unless project.public? end if authorized_git_request? @@ -80,15 +80,19 @@ 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) + true else false end @@ -108,11 +112,11 @@ module Grack @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 @@ -121,7 +125,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/shell.rb b/lib/gitlab/backend/shell.rb index c819ce56ac91319c6cb745a66f108ed3619781fe..5020fa1f991bd211e47647ffc750bee846d21ed7 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -196,6 +196,15 @@ module Gitlab Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git" end + # Return GitLab shell version + def 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 + end + protected def gitlab_shell_user_home diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb index 3d57f3a2e357b4009a92e10126c861a7339b7464..59f0fa64a6acd234fc9236fcbf92de0c5eb8acff 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) @@ -47,7 +47,7 @@ module Gitlab 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) diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 55aa240a9f926d650c255c17f743bf7caa428e46..eb6b91e26b5faad145b14799520699c80976714d 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -44,7 +44,7 @@ module Gitlab protected def default_regex - /\A[a-zA-Z0-9][a-zA-Z0-9_\-\.]*\z/ + /\A[a-zA-Z0-9][a-zA-Z0-9_\-\.]*(?<!\.git)\z/ end end end diff --git a/lib/gitlab/satellite/files/delete_file_action.rb b/lib/gitlab/satellite/files/delete_file_action.rb new file mode 100644 index 0000000000000000000000000000000000000000..30462999aa3b5d7e35d0d6d8e0ebdb0cb7190594 --- /dev/null +++ b/lib/gitlab/satellite/files/delete_file_action.rb @@ -0,0 +1,50 @@ +require_relative 'file_action' + +module Gitlab + module Satellite + class DeleteFileAction < FileAction + # Deletes file and creates a new commit for it + # + # Returns false if committing the change fails + # Returns false if pushing from the satellite to bare repo failed or was rejected + # Returns true otherwise + def commit!(content, commit_message) + in_locked_and_timed_satellite do |repo| + prepare_satellite!(repo) + + # create target branch in satellite at the corresponding commit from bare repo + repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}") + + # update the file in the satellite's working dir + file_path_in_satellite = File.join(repo.working_dir, file_path) + + # Prevent relative links + unless safe_path?(file_path_in_satellite) + Gitlab::GitLogger.error("FileAction: Relative path not allowed") + return false + end + + File.delete(file_path_in_satellite) + + # add removed file + repo.remove(file_path_in_satellite) + + # commit the changes + # will raise CommandFailed when commit fails + repo.git.commit(raise: true, timeout: true, a: true, m: commit_message) + + + # push commit back to bare repo + # will raise CommandFailed when push fails + repo.git.push({raise: true, timeout: true}, :origin, ref) + + # everything worked + true + end + rescue Grit::Git::CommandFailed => ex + Gitlab::GitLogger.error(ex.message) + false + end + end + end +end diff --git a/lib/gitlab/satellite/files/edit_file_action.rb b/lib/gitlab/satellite/files/edit_file_action.rb index 72e12fb077c54572068266cd9307d8962992a548..f410ecb79846991587fc001c82be6910fe24f9d3 100644 --- a/lib/gitlab/satellite/files/edit_file_action.rb +++ b/lib/gitlab/satellite/files/edit_file_action.rb @@ -8,19 +8,24 @@ module Gitlab # # 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) 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) + + # Prevent relative links + unless safe_path?(file_path_in_satellite) + Gitlab::GitLogger.error("FileAction: Relative path not allowed") + return false + end + File.open(file_path_in_satellite, 'w') { |f| f.write(content) } # commit the changes @@ -28,7 +33,7 @@ module Gitlab 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) diff --git a/lib/gitlab/satellite/files/file_action.rb b/lib/gitlab/satellite/files/file_action.rb index 4ac53c2cd5a41f6170e064b50b5aa7427259a0c9..0f7afde647d9bb2f5839090ab92df07d0c614ce8 100644 --- a/lib/gitlab/satellite/files/file_action.rb +++ b/lib/gitlab/satellite/files/file_action.rb @@ -9,11 +9,8 @@ module Gitlab @ref = ref 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 + def safe_path?(path) + File.absolute_path(path) == path end end end diff --git a/lib/gitlab/satellite/files/new_file_action.rb b/lib/gitlab/satellite/files/new_file_action.rb index 9fe5a38eb8051a5352ed2cbd4e4a190576c490c7..57d101ff535d914ece38ffb1ae50aa1009a93984 100644 --- a/lib/gitlab/satellite/files/new_file_action.rb +++ b/lib/gitlab/satellite/files/new_file_action.rb @@ -7,17 +7,28 @@ module Gitlab # # 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, file_name) + 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 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_name) + 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 File.open(file_path_in_satellite, 'w') { |f| f.write(content) } # add new file @@ -28,7 +39,7 @@ module Gitlab 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) diff --git a/lib/gitlab/satellite/merge_action.rb b/lib/gitlab/satellite/merge_action.rb index d74d4194ff62914f25cd12bd6e6de3b179983ee0..54afd6ab95ca259e100ef31a92395a92a76e2452 100644 --- a/lib/gitlab/satellite/merge_action.rb +++ b/lib/gitlab/satellite/merge_action.rb @@ -28,7 +28,7 @@ module Gitlab in_locked_and_timed_satellite do |merge_repo| prepare_satellite!(merge_repo) if merge_in_satellite!(merge_repo) - # push merge back to Gitolite + # 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 diff --git a/lib/gitlab/satellite/satellite.rb b/lib/gitlab/satellite/satellite.rb index 6cb7814fae56b342da7387ed4af347f232ff2b60..353c3024aad4d6cae77db40030fd8d3d7ebc4b68 100644 --- a/lib/gitlab/satellite/satellite.rb +++ b/lib/gitlab/satellite/satellite.rb @@ -123,7 +123,7 @@ module Gitlab remotes.each { |name| repo.git.remote(default_options,'rm', name)} end - # Updates the satellite from Gitolite + # Updates the satellite from bare repo # # Note: this will only update remote branches (i.e. origin/*) def update_from_source! diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb new file mode 100644 index 0000000000000000000000000000000000000000..eada9bcddf5b9ded74c0570e91ef9cebf01aa97f --- /dev/null +++ b/lib/gitlab/visibility_level.rb @@ -0,0 +1,42 @@ +# Gitlab::VisibilityLevel module +# +# Define allowed public modes that can be used for +# GitLab projects to determine project public mode +# +module Gitlab + module VisibilityLevel + PRIVATE = 0 + INTERNAL = 10 + PUBLIC = 20 + + class << self + def values + options.values + end + + def options + { + 'Private' => PRIVATE, + 'Internal' => INTERNAL, + 'Public' => PUBLIC + } + end + + def allowed_for?(user, level) + user.is_admin? || !Gitlab.config.gitlab.restricted_visibility_levels.include?(level) + end + end + + def private? + visibility_level_field == PRIVATE + end + + def internal? + visibility_level_field == INTERNAL + end + + def public? + visibility_level_field == PUBLIC + end + end +end diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb index b84c005524fda67e43b590296d42fa2192198751..2d1e0aec5e543981d87f6469e5b719d42333baca 100644 --- a/lib/redcarpet/render/gitlab_html.rb +++ b/lib/redcarpet/render/gitlab_html.rb @@ -36,7 +36,7 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML def preprocess(full_document) if @project - h.create_relative_links(full_document, @project.path_with_namespace, @ref, @request_path, is_wiki?) + h.create_relative_links(full_document, @project, @ref, @request_path, is_wiki?) else full_document end diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index fbb7380ac4746a04c4576b5e5b27516156732d74..9cf3aa5fe8599305c534ba433df6e20d9ba6bdcb 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -31,6 +31,8 @@ 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 diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 3e929c52990de1633104b0448a5cee5e189efb3a..4fb203c730cb70f45e47017bb215f7b58fd9ecc5 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -11,6 +11,9 @@ 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; + + # Set value of client_max_body_size to at least the value of git.max_size in gitlab.yml + client_max_body_size 5m; # individual nginx logs for this gitlab vhost access_log /var/log/nginx/gitlab_access.log; diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index f8c17f412b819df7946fa8b3be564e015fb2d5dd..20d5f03d6ef04716fc9e47a7eacf10e3f312498a 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} @@ -611,10 +612,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? @@ -682,6 +680,44 @@ namespace :gitlab do end end + namespace :ldap do + task :check, [:limit] => :environment do |t, args| + 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 (limit: #{limit}):" + 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 ########################## @@ -736,7 +772,7 @@ namespace :gitlab do end def check_gitlab_shell - required_version = Gitlab::VersionInfo.new(1, 7, 4) + 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/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake index ac2c4577c7722cc2c21d5674eb830292145ee4a7..c46d5855faf4ac5e21018c71a6fc770c686e041b 100644 --- a/lib/tasks/gitlab/task_helpers.rake +++ b/lib/tasks/gitlab/task_helpers.rake @@ -2,6 +2,16 @@ module Gitlab class TaskAbortedByUserError < StandardError; end end +unless STDOUT.isatty + module Colored + extend self + + def colorize(string, options={}) + string + end + end +end + namespace :gitlab do # Ask if the user wants to continue diff --git a/public/favicon.ico b/public/favicon.ico index 057f74ac7ab0192514a2dc21bfc955e5700fb65d..bfb74960c480e6cb14f1d38437303af6b375ccaf 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/spec/contexts/issues/list_context_spec.rb b/spec/contexts/issues/list_context_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..70ce956499c972c9f3a9f754154166f57ecbcbae --- /dev/null +++ b/spec/contexts/issues/list_context_spec.rb @@ -0,0 +1,73 @@ +require 'spec_helper' + +describe Issues::ListContext do + + let(:user) { create(:user) } + let(:project) { create(:project, creator: user) } + + 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 + + describe 'sorting' do + it 'sorts by newest' do + params = {sort: 'newest'} + + issues = Issues::ListContext.new(project, user, params).execute + issues.first.should eq foo + end + + it 'sorts by oldest' do + params = {sort: 'oldest'} + + issues = Issues::ListContext.new(project, user, params).execute + issues.first.should eq baz + end + + it 'sorts by recently updated' do + params = {sort: 'recently_updated'} + baz.updated_at = Time.now + 10 + baz.save + + issues = Issues::ListContext.new(project, user, params).execute + issues.first.should eq baz + end + + it 'sorts by least recently updated' do + params = {sort: 'last_updated'} + bar.updated_at = Time.now - 10 + bar.save + + issues = Issues::ListContext.new(project, user, params).execute + issues.first.should eq bar + end + + describe 'sorting by milestone' do + let(:newer_due_milestone) { create(:milestone, due_date: '2013-12-11') } + let(:later_due_milestone) { create(:milestone, due_date: '2013-12-12') } + + before :each do + foo.milestone = newer_due_milestone + foo.save + bar.milestone = later_due_milestone + bar.save + end + + it 'sorts by most recently due milestone' do + params = {sort: 'milestone_due_soon'} + + issues = Issues::ListContext.new(project, user, params).execute + issues.first.should eq foo + + end + + it 'sorts by least recently due milestone' do + params = {sort: 'milestone_due_later'} + + issues = Issues::ListContext.new(project, user, params).execute + issues.first.should eq bar + end + end + end +end diff --git a/spec/contexts/projects_create_context_spec.rb b/spec/contexts/projects_create_context_spec.rb index 8b2a49dbee575551a16dd33d2a962ffd67a2db40..d5b1cb83510566f9b51056323fdeb947a36bf463 100644 --- a/spec/contexts/projects_create_context_spec.rb +++ b/spec/contexts/projects_create_context_spec.rb @@ -7,6 +7,7 @@ describe Projects::CreateContext do describe :create_by_user do before do @user = create :user + @admin = create :user, admin: true @opts = { name: "GitLab", namespace: @user.namespace @@ -37,7 +38,7 @@ describe Projects::CreateContext do it { @project.namespace.should == @group } end - context 'respect configured public setting' do + context 'respect configured visibility setting' do before(:each) do @settings = double("settings") @settings.stub(:issues) { true } @@ -46,25 +47,90 @@ describe Projects::CreateContext do @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(:public) { true } + @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC } @project = create_project(@user, @opts) end - it { @project.public.should be_true } + it { @project.public?.should be_true } end - context 'should be private when setting is not public' do + context 'should be private when setting is private' do before do - @settings.stub(:public) { false } + @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE } @project = create_project(@user, @opts) end - it { @project.public.should be_false } + 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 @@ -73,3 +139,4 @@ describe Projects::CreateContext do Projects::CreateContext.new(user, opts).execute end end + diff --git a/spec/contexts/projects_update_context_spec.rb b/spec/contexts/projects_update_context_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..edcaf844e5db61683712c86eef856abe2d939a97 --- /dev/null +++ b/spec/contexts/projects_update_context_spec.rb @@ -0,0 +1,111 @@ +require 'spec_helper' + +describe Projects::UpdateContext 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::UpdateContext.new(project, user, opts).execute + end +end \ No newline at end of file diff --git a/spec/contexts/search_context_spec.rb b/spec/contexts/search_context_spec.rb index 58f747e87252f872912d7e282dbfbf919b783bca..c25743e0032a02ea2a04c1bedd3114961d2ca106 100644 --- a/spec/contexts/search_context_spec.rb +++ b/spec/contexts/search_context_spec.rb @@ -3,23 +3,39 @@ require 'spec_helper' describe SearchContext 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, public: false) } + 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, public: false) } - let(:public_namespace) { create(:namespace, path: 'something_else',name: 'searchable public namespace') } - let(:other_user) { create(:user, namespace: public_namespace) } - let!(:public_project) { create(:project, name: 'searchable_public_project', creator_id: other_user.id, namespace: public_namespace, public: true) } + 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 it 'public projects should be searchable' do - context = SearchContext.new([found_project.id], {search_code: false, search: "searchable"}) + context = SearchContext.new([found_project.id], nil, {search_code: false, search: "searchable"}) results = context.execute results[:projects].should == [found_project, public_project] end + it 'internal projects should be searchable' do + context = SearchContext.new([found_project.id], user, {search_code: false, search: "searchable"}) + results = context.execute + # can't seem to rely on the return order, so check this way + #subject { results[:projects] } + results[:projects].should have(3).items + results[:projects].should include(found_project) + results[:projects].should include(internal_project) + results[:projects].should include(public_project) + end + it 'namespace name should be searchable' do - context = SearchContext.new([found_project.id], {search_code: false, search: "searchable namespace"}) + context = SearchContext.new([found_project.id], user, {search_code: false, search: "searchable namespace"}) results = context.execute results[:projects].should == [found_project] end diff --git a/spec/factories.rb b/spec/factories.rb index 624cb0f76541cd93003c4fe0bc0890c45b2abe20..daf841736484377933a78c7f88c518fc1a02f924 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -15,7 +15,7 @@ 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 } @@ -66,6 +66,7 @@ FactoryGirl.define do 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 diff --git a/spec/factories/broadcast_messages.rb b/spec/factories/broadcast_messages.rb new file mode 100644 index 0000000000000000000000000000000000000000..84dea94502559d017e8ca3976d96d9631e845220 --- /dev/null +++ b/spec/factories/broadcast_messages.rb @@ -0,0 +1,23 @@ +# == 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 +# + +# 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 + end +end diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 8cb984946b42d9ec7a57110e69d9485994b6a5fd..bb0c4dbd5ddede120fe22080fecce62966b3b651 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -95,4 +95,91 @@ 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 + + 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/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..5abccd259d424c2a4d401034a7ae429956d3c729 --- /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_with_code) } + + let(:master) { create(:user) } + let(:guest) { create(:user) } + let(:reporter) { create(:user) } + + before do + # internal project + project.visibility_level = Gitlab::VisibilityLevel::INTERNAL + project.save! + + # full access + project.team << [master, :master] + + # readonly + project.team << [reporter, :reporter] + + end + + describe "Project should be internal" do + subject { project } + + its(:internal?) { should be_true } + end + + describe "GET /:project_path" do + subject { project_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/tree/master" do + subject { project_tree_path(project, project.repository.root_ref) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/commits/master" do + subject { project_commits_path(project, project.repository.root_ref, limit: 1) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/commit/:sha" do + subject { project_commit_path(project, project.repository.commit) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/compare" do + subject { project_compare_index_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/team" do + subject { project_team_index_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/wall" do + subject { project_wall_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/blob" do + before do + commit = project.repository.commit + path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name + @blob_path = project_blob_path(project, File.join(commit.id, path)) + end + + it { @blob_path.should be_allowed_for master } + it { @blob_path.should be_allowed_for reporter } + it { @blob_path.should be_allowed_for :admin } + it { @blob_path.should be_allowed_for guest } + it { @blob_path.should be_allowed_for :user } + it { @blob_path.should be_denied_for :visitor } + end + + describe "GET /:project_path/edit" do + subject { edit_project_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/deploy_keys" do + subject { project_deploy_keys_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/issues" do + subject { project_issues_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/snippets" do + subject { project_snippets_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/snippets/new" do + subject { new_project_snippet_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/merge_requests" do + subject { project_merge_requests_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/merge_requests/new" do + subject { new_project_merge_request_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/branches/recent" do + subject { recent_project_branches_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/branches" do + subject { project_branches_path(project) } + + before do + # Speed increase + Project.any_instance.stub(:branches).and_return([]) + end + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/tags" do + subject { project_tags_path(project) } + + before do + # Speed increase + Project.any_instance.stub(:tags).and_return([]) + end + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/hooks" do + subject { project_hooks_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end +end diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index 7f3f8c50f02b2f30d3e67c45a2f00f4eec73381e..481d8cec41698e4e9a0cd121d1bfa7b03333544c 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -15,6 +15,12 @@ describe "Private Project Access" do project.team << [reporter, :reporter] end + describe "Project should be private" do + subject { project } + + its(:private?) { should be_true } + end + describe "GET /:project_path" do subject { project_path(project) } diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index 267643fd8ef2d485002e6f0da81b141b7716bf02..3f1016473f5c41f7ea96edc412c8b94e2d9e8363 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -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/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index a0bbc026421b5d4eaf81439e206c1a4b3df331ed..6afd4b5f0ac3721c00f549395e7c09ccd7331ed6 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -378,9 +378,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 = "<div class=\"white\"><div class=\"highlight\"><pre><span class=\"n\">some</span> <span class=\"n\">code</span> <span class=\"n\">from</span> <span class=\"err\">$</span><span class=\"mi\">#{snippet.id}</span>" - 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 include(target_html) + helper.markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n").should include(target_html) end it "should leave inline code untouched" do diff --git a/spec/lib/auth_spec.rb b/spec/lib/auth_spec.rb index e05fde95731a6ff8ba3b3756be33e6d3695865a9..073b811c3fb66243e636532ed8d7860c84077294 100644 --- a/spec/lib/auth_spec.rb +++ b/spec/lib/auth_spec.rb @@ -8,21 +8,21 @@ describe Gitlab::Auth do @user = create( :user, username: 'john', - password: '888777', - password_confirmation: '888777' + password: '88877711', + password_confirmation: '88877711' ) end it "should find user by valid login/password" do - gl_auth.find('john', '888777').should == @user + gl_auth.find('john', '88877711').should == @user end it "should not find user with invalid password" do - gl_auth.find('john', 'invalid').should_not == @user + gl_auth.find('john', 'invalid11').should_not == @user end it "should not find user with invalid login and password" do - gl_auth.find('jon', 'invalid').should_not == @user + gl_auth.find('jon', 'invalid11').should_not == @user end end end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 666c6ccefff94ebbce1973e6c18a84f0a7fb6677..d287239cfe3eb0a915e23753fb97a6c71776bea7 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -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,28 @@ 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 end diff --git a/spec/models/assembla_service_spec.rb b/spec/models/assembla_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..0b961c81ac115d84e5a39bdc3c3cd701370c4018 --- /dev/null +++ b/spec/models/assembla_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 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_with_code) } + + before do + @assembla_service = AssemblaService.new + @assembla_service.stub( + project_id: project.id, + project: project, + service_hook: true, + token: 'verySecret' + ) + @sample_data = GitPushService.new.sample_data(project, user) + @api_url = 'https://atlas.assembla.com/spaces/ouposp/github_tool?secret_key=verySecret' + WebMock.stub_request(:post, @api_url) + end + + it "should call FlowDock API" do + @assembla_service.execute(@sample_data) + WebMock.should have_requested(:post, @api_url).with( + body: /#{@sample_data[:before]}.*#{@sample_data[:after]}.*#{project.path}/ + ).once + end + end +end diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..998e89fa26af2d8ddd0c40d3b20b2b80eca029f3 --- /dev/null +++ b/spec/models/broadcast_message_spec.rb @@ -0,0 +1,37 @@ +# == 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 +# + +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/flowdock_service_spec.rb b/spec/models/flowdock_service_spec.rb index b22193c9e930716826cef3ad7c81508e4f3860ed..636aba2f0124ecbe3211da2e2651a4e5f82f44cf 100644 --- a/spec/models/flowdock_service_spec.rb +++ b/spec/models/flowdock_service_spec.rb @@ -11,6 +11,8 @@ # updated_at :datetime not null # active :boolean default(FALSE), not null # project_url :string(255) +# subdomain :string(255) +# room :string(255) # require 'spec_helper' diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index b17183a281c221e797a5146374eb341a06d9193a..fe65096fd1ec5888fb1887568d7cb57e39b1bdc1 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -116,13 +116,13 @@ describe MergeRequest do end it 'accesses the set of issues that will be closed on acceptance' do - subject.project.default_branch = subject.target_branch + subject.project.stub(default_branch: subject.target_branch) subject.closes_issues.should == [issue0, issue1].sort_by(&:id) end it 'only lists issues as to be closed if it targets the default branch' do - subject.project.default_branch = 'master' + subject.project.stub(default_branch: 'master') subject.target_branch = 'something-else' subject.closes_issues.should be_empty diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 42c405d8e506cb05a5f582d67bf1ae61bed75468..55b264ce8cf8f5170d29ae31ab31fce2ec355dc8 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) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 88ea692679045e03c7bb7f3cfe1acce21a30cbe5..0167d51dd39d81504e8a53c77a4541857067fdc1 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -9,19 +9,18 @@ # 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 # require 'spec_helper' diff --git a/spec/models/service_hook_spec.rb b/spec/models/service_hook_spec.rb index 0b0262c97f1811dba2b5d4009d3c6ca2a6031b2a..40a5fbc71d952bc8eb3b4bc6a5f5e57c16b4c8cc 100644 --- a/spec/models/service_hook_spec.rb +++ b/spec/models/service_hook_spec.rb @@ -2,13 +2,16 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# type :string(255) default("ProjectHook") -# service_id :integer +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null # require "spec_helper" diff --git a/spec/models/system_hook_spec.rb b/spec/models/system_hook_spec.rb index a9ed6a5fa7c054dc0d9eb05485e2fab5c2f3a5a7..6a0d99dcc53f1cec8726eeb20b19474c4900c3f8 100644 --- a/spec/models/system_hook_spec.rb +++ b/spec/models/system_hook_spec.rb @@ -2,13 +2,16 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# type :string(255) default("ProjectHook") -# service_id :integer +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null # require "spec_helper" diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 66493a8d22de3bcd4e59bc855ebf5bc127f89a04..59f75ae552abbd54ed175e50ae5481c90b1d4d89 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -36,6 +36,11 @@ # 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) # require 'spec_helper' @@ -85,8 +90,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 diff --git a/spec/models/web_hook_spec.rb b/spec/models/web_hook_spec.rb index 2d9301732dd9550ef98177dda05b6f86f3f75729..d603408101879ddbb237fb688196f331b18136f7 100644 --- a/spec/models/web_hook_spec.rb +++ b/spec/models/web_hook_spec.rb @@ -2,13 +2,16 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# type :string(255) default("ProjectHook") -# service_id :integer +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null # require 'spec_helper' diff --git a/spec/observers/merge_request_observer_spec.rb b/spec/observers/merge_request_observer_spec.rb index 3f5250a0040e319121a0a0f53b83d96922370363..3e5cdfaf5d6e03dba827ca6110f9014bdc1e783f 100644 --- a/spec/observers/merge_request_observer_spec.rb +++ b/spec/observers/merge_request_observer_spec.rb @@ -4,7 +4,7 @@ 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(:mr_mock) { double(:merge_request, id: 42, assignee: assignee, author: author).as_null_object } 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)) } diff --git a/spec/observers/users_group_observer_spec.rb b/spec/observers/users_group_observer_spec.rb index 3bf562edbb759004313135742d71d37cda75655e..65484806b19d20aeb966079f0868222111f7f94e 100644 --- a/spec/observers/users_group_observer_spec.rb +++ b/spec/observers/users_group_observer_spec.rb @@ -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/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..2d1f8df47dd98fe33946974b7db6035a80e293a1 --- /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_with_code, 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/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..2b1a4bf6ec83b95a62a85dde3c20ddd90d107a52 --- /dev/null +++ b/spec/requests/api/namespaces_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe API::API do + include ApiHelpers + before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } + after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } + + let(:admin) { create(:admin) } + let!(:group1) { create(:group) } + let!(:group2) { create(:group) } + + describe "GET /namespaces" do + context "when unauthenticated" do + it "should return authentication error" do + get api("/namespaces") + response.status.should == 401 + end + end + + context "when authenticated as admin" do + it "admin: should return an array of all namespaces" do + get api("/namespaces", admin) + response.status.should == 200 + json_response.should be_an Array + + # Admin namespace + 2 group namespaces + json_response.length.should == 3 + end + end + end +end diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..beccd61866e8d75eb147e5088175b61e37f9d7c6 --- /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_with_code, 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 bf4a1749418072a365d34143e690a4f67d700202..8e0b9067672f5f357220725e7a18e5e90dc39609 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -10,7 +10,6 @@ describe API::API do 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) } @@ -36,6 +35,32 @@ describe API::API do end end + describe "GET /projects/all" do + 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 +116,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,19 +131,46 @@ 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 @@ -146,7 +197,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,19 +212,46 @@ 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 @@ -361,121 +438,6 @@ 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 it "should return an array of project snippets" do get api("/projects/#{project.id}/snippets", user) @@ -628,10 +590,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) @@ -700,8 +662,10 @@ describe API::API do 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!(:internal) { create(:project, name: "internal #{query}", visibility_level: Gitlab::VisibilityLevel::INTERNAL) } + let!(:unfound_internal) { create(:project, name: 'unfound internal', visibility_level: Gitlab::VisibilityLevel::INTERNAL) } + let!(:public) { create(:project, name: "public #{query}", visibility_level: Gitlab::VisibilityLevel::PUBLIC) } + let!(:unfound_public) { create(:project, name: 'unfound public', visibility_level: Gitlab::VisibilityLevel::PUBLIC) } context "when unauthenticated" do it "should return authentication error" do @@ -715,7 +679,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 +689,8 @@ 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 diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb index 0fd90c567e060be81786ce06d24e077f8926d4b1..668007dc29fc94322090c6e95f43c441ef9239b6 100644 --- a/spec/requests/api/session_spec.rb +++ b/spec/requests/api/session_spec.rb @@ -8,7 +8,7 @@ describe API::API do describe "POST /session" do context "when valid password" do it "should return private token" do - post api("/session"), email: user.email, password: '123456' + post api("/session"), email: user.email, password: '12345678' response.status.should == 201 json_response['email'].should == user.email diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 1b1d19d26b1945185aba125909470a2f0e32abf8..1af052d873912e1920deddabcadfb8d37d637e14 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -185,6 +185,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/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 2870f59195ae8b07607275c435b76e14409a0765..b46022fb2da0b7d7c46943f57999a0256fc46d89 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -74,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/support/login_helpers.rb b/spec/support/login_helpers.rb index 025534a900d13c47c55195937aaf6eb2a6cefd36..cc0ec2f4e3d60d7484e03c23aaa0c542f86fd05d 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -16,7 +16,7 @@ module LoginHelpers def login_with(user) visit new_user_session_path fill_in "user_login", with: user.email - fill_in "user_password", with: "123456" + fill_in "user_password", with: "12345678" click_button "Sign in" Thread.current[:current_user] = user end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 16e10b1a62bc9029b62b9f54eff9ed42f4d85583..e2bc2a5d7dd984eeb89fd1cf8442e611aafcd593 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,8 @@ module TestEnv remove_repository: true, update_repository_head: true, add_key: true, - remove_key: true + remove_key: true, + version: '6.3.0' ) Gitlab::Satellite::Satellite.any_instance.stub( @@ -96,6 +98,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/vendor/assets/javascripts/ace-src-noconflict/mode-diff.js b/vendor/assets/javascripts/ace-src-noconflict/mode-diff.js index 75e26cc7056752f3cb5f37813a44fcca7a10a413..8d1e7cee0967542ba1804c8a90fbbd8f096ae4e3 100644 --- a/vendor/assets/javascripts/ace-src-noconflict/mode-diff.js +++ b/vendor/assets/javascripts/ace-src-noconflict/mode-diff.js @@ -66,7 +66,7 @@ var DiffHighlightRules = function() { "regex": "^(?:\\*{15}|={67}|-{3}|\\+{3})$", "token": "punctuation.definition.separator.diff", "name": "keyword" - }, { //diff.range.unified + }, { //diff.range.inline "regex": "^(@@)(\\s*.+?\\s*)(@@)(.*)$", "token": [ "constant",