diff --git a/.travis.yml b/.travis.yml index f56311bb5a..75b4c5c703 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,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 30710fcebf..d311984676 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,29 @@ +v 6.4.0 + - Added sorting to project issues page (Jason Blanchard) + - Assembla integration (Carlos Paramio) + - Fixed another 500 error with submodules + - UI: More compact issues page + - Minimal password length increased to 8 symbols + - Side-by-side diff view (Steven Thonus) + - Internal projects (Jason Hollingsworth) + - Allow removal of avatar (Drew Blessing) + - Project web hooks now support issues and merge request events + - Visiting project page while not logged in will redirect to sign-in instead of 404 (Jason Hollingsworth) + - Expire event cache on avatar creation/removal (Drew Blessing) + - Archiving old projects (Steven Thonus) + - Rails 4 + - Add time ago tooltips to show actual date/time + - UI: Fixed UI for admin system hooks + - Ruby script for easier GitLab upgrade + - Do not remove Merge requests if fork project was removed + - Improve sign-in/signup UX + - Add resend confirmation link to sign-in page + - Set noreply@HOSTNAME for reply_to field in all emails + - Show GitLab API version on Admin#dashboard + - API Cross-origin resource sharing + - Show READMe link at project home page + - Show repo size for projects in Admin area + v 6.3.0 - API for adding gitlab-ci service - Init script now waits for pids to appear after (re)starting before reporting status (Rovanion Luckey) @@ -58,7 +84,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 @@ -80,7 +106,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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 62dc5d60b5..778018d25e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -65,8 +65,13 @@ If you can, please submit a pull request with the fix or improvements including 1. Add your changes to the [CHANGELOG](CHANGELOG) 1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) 1. Push the commit to your fork -1. Submit a pull request -2. [Search for issues](https://github.com/gitlabhq/gitlabhq/search?q=&ref=cmdform&type=Issues) related to your pull request and mention them in the pull request description +1. Submit a pull request (PR) +1. The PR title should describes the change you want to make +1. The PR description should give a motive for your change and the method you used to achieve it +* If the PR changes the UI it should include before and after screenshots +1. [Search for issues](https://github.com/gitlabhq/gitlabhq/search?q=&ref=cmdform&type=Issues) related to your pull request and mention them in the pull request description + +Please keep the change in a single PR as small as possible. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? The smaller a PR is the more likely it is it will be merged, after that you can send more PR's to enhance it. We will accept pull requests if: @@ -74,11 +79,9 @@ We will accept pull requests if: * It can be merged without problems (if not please use: `git rebase master`) * It does not break any existing functionality * It's quality code that conforms to the [Ruby](https://github.com/bbatsov/ruby-style-guide) and [Rails](https://github.com/bbatsov/rails-style-guide) style guides and best practices -* The description includes a motive for your change and the method you used to achieve it * It is not a catch all pull request but rather fixes a specific issue or implements a specific feature * It keeps the GitLab code base clean and well structured * We think other users will benefit from the same functionality -* If it makes changes to the UI the pull request should include screenshots * It is a single commit (please use `git rebase -i` to squash commits) For examples of feedback on pull requests please look at already [closed pull requests](https://github.com/gitlabhq/gitlabhq/pulls?direction=desc&page=1&sort=created&state=closed). diff --git a/Gemfile b/Gemfile index 79cd007e76..1431bd32fc 100644 --- a/Gemfile +++ b/Gemfile @@ -8,15 +8,21 @@ def linux_only(require_as) RUBY_PLATFORM.include?('linux') && require_as end -gem "rails", "3.2.16" +gem "rails", "~> 4.0.0" + +gem "protected_attributes" +gem 'rails-observers' +gem 'actionpack-page_caching' +gem 'actionpack-action_caching' +gem 'activerecord-deprecated_finders' # Supported DBs gem "mysql2", group: :mysql gem "pg", group: :postgres # Auth -gem "devise", '~> 2.2' -gem "devise-async" +gem "devise", '3.0.4' +gem "devise-async", '0.8.0' gem 'omniauth', "~> 1.1.3" gem 'omniauth-google-oauth2' gem 'omniauth-twitter' @@ -24,27 +30,28 @@ 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", "~> 4.0.0.pre" # Ruby/Rack Git Smart-HTTP Server Handler -gem 'gitlab-grack', '~> 1.0.1', require: 'grack' +gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack' # LDAP Auth gem 'gitlab_omniauth-ldap', '1.0.3', require: "omniauth-ldap" gem 'net-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", "~> 0.6.1" gem "grape-entity", "~> 0.3.0" +gem 'rack-cors', require: 'rack/cors' # Format dates and times # based on human-friendly examples @@ -79,7 +86,10 @@ gem "github-markup", "~> 0.7.4", require: 'github/markup' gem "asciidoctor" # Application server -gem "unicorn", '~> 4.6.3', group: :unicorn +group :unicorn do + gem "unicorn", '~> 4.6.3' + gem 'unicorn-worker-killer' +end # State machine gem "state_machine" @@ -128,26 +138,24 @@ gem "sanitize" # Protect against bruteforcing gem "rack-attack" -group :assets do - gem "sass-rails" - gem "coffee-rails" - gem "uglifier" - gem "therubyracer" - gem 'turbolinks' - gem 'jquery-turbolinks' +gem "sass-rails" +gem "coffee-rails" +gem "uglifier" +gem "therubyracer" +gem 'turbolinks' +gem 'jquery-turbolinks' - gem 'chosen-rails', "1.0.0" - gem 'select2-rails' - gem 'jquery-atwho-rails', "0.3.0" - gem "jquery-rails", "2.1.3" - gem "jquery-ui-rails", "2.0.2" - gem "modernizr", "2.6.2" - gem "raphael-rails", "~> 2.1.2" - gem 'bootstrap-sass' - gem "font-awesome-rails" - gem "gemoji", "~> 1.2.1", require: 'emoji/railtie' - gem "gon" -end +gem 'chosen-rails', "1.0.1" +gem 'select2-rails' +gem 'jquery-atwho-rails', "~> 0.3.3" +gem "jquery-rails", "2.1.3" +gem "jquery-ui-rails", "2.0.2" +gem "modernizr", "2.6.2" +gem "raphael-rails", "~> 2.1.2" +gem 'bootstrap-sass', '~> 2.3' +gem "font-awesome-rails", '~> 3.2' +gem "gemoji", "~> 1.3.0" +gem "gon", git: "https://github.com/gitlabhq/gon.git", ref: '58ca8e17273051cb370182cabd3602d1da6783ab' group :development do gem "annotate", "~> 2.6.0.beta2" @@ -170,7 +178,7 @@ end group :development, :test do gem 'coveralls', require: false - gem 'rails-dev-tweaks' + # gem 'rails-dev-tweaks' gem 'spinach-rails' gem "rspec-rails" gem "capybara" @@ -199,7 +207,7 @@ group :development, :test do gem 'poltergeist', '~> 1.4.1' gem 'spork', '~> 1.0rc' - gem 'jasmine' + gem 'jasmine', '2.0.0.rc5' end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 7668a034d7..d0e4e204ef 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,44 +1,57 @@ +GIT + remote: https://github.com/gitlabhq/gon.git + revision: 58ca8e17273051cb370182cabd3602d1da6783ab + ref: 58ca8e17273051cb370182cabd3602d1da6783ab + specs: + gon (4.1.1) + actionpack (>= 2.3.0) + json + GEM remote: https://rubygems.org/ specs: - actionmailer (3.2.16) - actionpack (= 3.2.16) + actionmailer (4.0.2) + actionpack (= 4.0.2) mail (~> 2.5.4) - actionpack (3.2.16) - activemodel (= 3.2.16) - activesupport (= 3.2.16) - builder (~> 3.0.0) + actionpack (4.0.2) + activesupport (= 4.0.2) + builder (~> 3.1.0) erubis (~> 2.7.0) - journey (~> 1.0.4) - rack (~> 1.4.5) - rack-cache (~> 1.2) - rack-test (~> 0.6.1) - sprockets (~> 2.2.1) - activemodel (3.2.16) - activesupport (= 3.2.16) - builder (~> 3.0.0) - activerecord (3.2.16) - activemodel (= 3.2.16) - activesupport (= 3.2.16) - arel (~> 3.0.2) - tzinfo (~> 0.3.29) - activeresource (3.2.16) - activemodel (= 3.2.16) - activesupport (= 3.2.16) - activesupport (3.2.16) + rack (~> 1.5.2) + rack-test (~> 0.6.2) + actionpack-action_caching (1.1.0) + actionpack (>= 4.0.0, < 5.0) + actionpack-page_caching (1.0.2) + actionpack (>= 4.0.0, < 5) + activemodel (4.0.2) + activesupport (= 4.0.2) + builder (~> 3.1.0) + activerecord (4.0.2) + activemodel (= 4.0.2) + activerecord-deprecated_finders (~> 1.0.2) + activesupport (= 4.0.2) + arel (~> 4.0.0) + activerecord-deprecated_finders (1.0.3) + activesupport (4.0.2) i18n (~> 0.6, >= 0.6.4) - multi_json (~> 1.0) + minitest (~> 4.2) + multi_json (~> 1.3) + thread_safe (~> 0.1) + tzinfo (~> 0.3.37) acts-as-taggable-on (2.4.1) rails (>= 3, < 5) - addressable (2.3.4) - annotate (2.6.0.beta2) + addressable (2.3.5) + annotate (2.6.0) activerecord (>= 2.3.0) rake (>= 0.8.7) - arel (3.0.3) - asciidoctor (0.1.3) + arel (4.0.1) + asciidoctor (0.1.4) + atomic (1.1.14) awesome_print (1.2.0) - backports (3.3.2) - bcrypt-ruby (3.1.1) + axiom-types (0.0.5) + descendants_tracker (~> 0.0.1) + ice_nine (~> 0.9) + bcrypt-ruby (3.1.2) better_errors (1.0.1) coderay (>= 1.0.0) erubis (>= 2.6.6) @@ -46,92 +59,94 @@ GEM debug_inspector (>= 0.0.1) bootstrap-sass (2.3.2.2) sass (~> 3.2) - builder (3.0.4) + builder (3.1.4) capybara (2.1.0) mime-types (>= 1.16) nokogiri (>= 1.3.3) rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) - carrierwave (0.8.0) + carrierwave (0.9.0) activemodel (>= 3.2.0) activesupport (>= 3.2.0) - celluloid (0.14.1) - timers (>= 1.0.0) + json (>= 1.7) + celluloid (0.15.2) + timers (~> 1.1.0) charlock_holmes (0.6.9.4) - childprocess (0.3.9) - ffi (~> 1.0, >= 1.0.11) - chosen-rails (1.0.0) + 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) - cliver (0.2.1) + chunky_png (1.2.9) + cliver (0.2.2) code_analyzer (0.4.3) sexp_processor - coderay (1.0.9) - coffee-rails (3.2.2) + coderay (1.1.0) + coercible (1.0.0) + descendants_tracker (~> 0.0.1) + coffee-rails (4.0.1) coffee-script (>= 2.2.0) - railties (~> 3.2.0) + railties (>= 4.0.0, < 5.0) coffee-script (2.2.0) coffee-script-source execjs - coffee-script-source (1.6.2) + coffee-script-source (1.6.3) colored (1.2) colorize (0.5.8) compass (0.12.2) chunky_png (~> 1.2) fssm (>= 0.2.7) sass (~> 3.1) - compass-rails (1.0.3) - compass (>= 0.12.2, < 0.14) - connection_pool (1.1.0) + compass-rails (1.1.1) + compass (>= 0.12.2) + connection_pool (1.2.0) coveralls (0.7.0) multi_json (~> 1.3) rest-client simplecov (>= 0.7) term-ansicolor thor - crack (0.4.0) + crack (0.4.1) safe_yaml (~> 0.9.0) d3_rails (3.1.10) railties (>= 3.1.0) daemons (1.1.9) - database_cleaner (1.1.1) + database_cleaner (1.2.0) debug_inspector (0.0.2) - descendants_tracker (0.0.1) - devise (2.2.5) + descendants_tracker (0.0.3) + devise (3.0.4) bcrypt-ruby (~> 3.0) orm_adapter (~> 0.1) - railties (~> 3.1) - warden (~> 1.2.1) + railties (>= 3.2.6, < 5) + warden (~> 1.2.3) devise-async (0.8.0) devise (>= 2.2, < 3.2) diff-lcs (1.2.5) - dotenv (0.8.0) - email_spec (1.4.0) + docile (1.1.1) + dotenv (0.9.0) + email_spec (1.5.0) launchy (~> 2.1) mail (~> 2.2) - enumerize (0.6.1) + enumerize (0.7.0) activesupport (>= 3.2) + equalizer (0.0.8) erubis (2.7.0) escape_utils (0.2.4) eventmachine (1.0.3) excon (0.13.4) - execjs (1.4.0) - multi_json (~> 1.0) - factory_girl (4.2.0) + execjs (2.0.2) + factory_girl (4.3.0) activesupport (>= 3.0.0) - factory_girl_rails (4.2.1) - factory_girl (~> 4.2.0) + factory_girl_rails (4.3.0) + factory_girl (~> 4.3.0) railties (>= 3.0.0) - faraday (0.8.7) - multipart-post (~> 1.1) + faraday (0.8.8) + multipart-post (~> 1.2.0) faraday_middleware (0.9.0) faraday (>= 0.7.4, < 0.9) - ffaker (1.18.0) - ffi (1.9.0) + ffaker (1.22.1) + ffi (1.9.3) fog (1.3.1) builder excon (~> 0.13.0) @@ -149,50 +164,49 @@ GEM thor (>= 0.13.6) formatador (0.2.4) fssm (0.2.10) - gemoji (1.2.1) - gherkin-ruby (0.3.0) - github-linguist (2.3.4) - charlock_holmes (~> 0.6.6) - escape_utils (~> 0.2.3) - mime-types (~> 1.19) - pygments.rb (>= 0.2.13) - github-markdown (0.5.3) + gemoji (1.3.1) + gherkin-ruby (0.3.1) + racc + 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) - rack (~> 1.4.1) + gitlab-grack (2.0.0.pre) + rack (~> 1.5.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) - activesupport (~> 3.2.13) - github-linguist (~> 2.3.4) + gitlab_git (4.0.0.pre) + activesupport (~> 4.0.0) 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) omniauth (~> 1.0) pyu-ruby-sasl (~> 0.0.3.1) rubyntlm (~> 0.1.1) - gon (4.1.1) - actionpack (>= 2.3.0) - json - grape (0.4.1) + grape (0.6.1) activesupport builder hashie (>= 1.2.0) @@ -201,91 +215,91 @@ GEM rack (>= 1.3.0) rack-accept rack-mount - virtus + virtus (>= 1.0.0) grape-entity (0.3.0) activesupport multi_json (>= 1.3.2) growl (1.0.3) - guard (1.8.1) + guard (2.2.4) formatador (>= 0.2.4) - listen (>= 1.0.0) - lumberjack (>= 1.0.2) - pry (>= 0.9.10) - thor (>= 0.14.6) - guard-rspec (3.0.2) - guard (>= 1.8) - rspec (~> 2.13) + listen (~> 2.1) + lumberjack (~> 1.0) + pry (>= 0.9.12) + thor (>= 0.18.1) + guard-rspec (4.2.0) + guard (>= 2.1.1) + rspec (>= 2.14, < 4.0) guard-spinach (0.0.2) guard (>= 1.1) spinach - haml (4.0.3) + haml (4.0.4) tilt - haml-rails (0.4) - actionpack (>= 3.1, < 4.1) - activesupport (>= 3.1, < 4.1) - haml (>= 3.1, < 4.1) - railties (>= 3.1, < 4.1) - hashie (1.2.0) + haml-rails (0.5.1) + actionpack (~> 4.0.0) + activesupport (~> 4.0.0) + haml (>= 3.1, < 5.0) + railties (~> 4.0.0) + hashie (2.0.5) hike (1.2.3) hipchat (0.9.0) httparty httparty http_parser.rb (0.5.3) - httparty (0.11.0) - multi_json (~> 1.0) + httparty (0.12.0) + json (~> 1.8) multi_xml (>= 0.5.2) httpauth (0.2.0) i18n (0.6.9) - jasmine (1.3.2) - jasmine-core (~> 1.3.1) - rack (~> 1.0) - rspec (>= 1.3.1) - selenium-webdriver (>= 0.1.3) - jasmine-core (1.3.1) - journey (1.0.4) - jquery-atwho-rails (0.3.0) + ice_nine (0.10.0) + jasmine (2.0.0.rc5) + jasmine-core (~> 2.0.0.rc5) + phantomjs + rack (>= 1.2.1) + rake + jasmine-core (2.0.0.rc5) + jquery-atwho-rails (0.3.3) jquery-rails (2.1.3) railties (>= 3.1.0, < 5.0) thor (~> 0.14) - jquery-turbolinks (1.0.0) + jquery-turbolinks (2.0.1) railties (>= 3.1.0) turbolinks jquery-ui-rails (2.0.2) jquery-rails railties (>= 3.1.0) - json (1.7.7) + json (1.8.1) jwt (0.1.8) multi_json (>= 1.5) kaminari (0.14.1) actionpack (>= 3.0.0) activesupport (>= 3.0.0) - kgio (2.8.0) - launchy (2.3.0) + kgio (2.8.1) + launchy (2.4.2) addressable (~> 2.3) - letter_opener (1.1.1) + letter_opener (1.1.2) launchy (~> 2.2) - libv8 (3.11.8.17) - listen (1.2.2) + libv8 (3.16.14.3) + listen (2.3.1) + celluloid (>= 0.15.2) rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9) - rb-kqueue (>= 0.2) - lumberjack (1.0.3) + lumberjack (1.0.4) mail (2.5.4) mime-types (~> 1.16) treetop (~> 1.4.8) - method_source (0.8.1) + method_source (0.8.2) mime-types (1.25.1) - minitest (4.7.4) + minitest (4.7.5) modernizr (2.6.2) sprockets (~> 2.0) multi_json (1.8.2) - multi_xml (0.5.4) + multi_xml (0.5.5) multipart-post (1.2.0) mysql2 (0.3.11) net-ldap (0.3.1) net-scp (1.0.4) net-ssh (>= 1.99.1) - net-ssh (2.6.8) + net-ssh (2.7.0) nokogiri (1.5.10) oauth (0.4.7) oauth2 (0.8.1) @@ -297,10 +311,10 @@ GEM omniauth (1.1.4) hashie (>= 1.2, < 3) rack - omniauth-github (1.1.0) + omniauth-github (1.1.1) omniauth (~> 1.0) omniauth-oauth2 (~> 1.1) - omniauth-google-oauth2 (0.1.19) + omniauth-google-oauth2 (0.2.1) omniauth (~> 1.0) omniauth-oauth2 omniauth-oauth (1.0.1) @@ -309,11 +323,12 @@ GEM omniauth-oauth2 (1.1.1) oauth2 (~> 0.8.0) omniauth (~> 1.0) - omniauth-twitter (0.0.17) + omniauth-twitter (1.0.1) multi_json (~> 1.3) omniauth-oauth (~> 1.0) - orm_adapter (0.4.0) + orm_adapter (0.5.0) pg (0.15.1) + phantomjs (1.9.2.0) poltergeist (1.4.1) capybara (~> 2.1.0) cliver (~> 0.2.1) @@ -321,44 +336,40 @@ GEM websocket-driver (>= 0.2.0) polyglot (0.3.3) posix-spawn (0.3.6) - pry (0.9.12.2) - coderay (~> 1.0.5) + protected_attributes (1.0.5) + activemodel (>= 4.0.1, < 5.0) + pry (0.9.12.4) + coderay (~> 1.0) method_source (~> 0.8) slop (~> 3.4) - pygments.rb (0.4.2) - posix-spawn (~> 0.3.6) - yajl-ruby (~> 1.1.0) pyu-ruby-sasl (0.0.3.3) quiet_assets (1.0.2) railties (>= 3.1, < 5.0) - rack (1.4.5) + racc (1.4.10) + rack (1.5.2) rack-accept (0.4.5) rack (>= 0.4) - rack-attack (2.2.1) + rack-attack (2.3.0) rack - rack-cache (1.2) - rack (>= 0.4) + rack-cors (0.2.9) rack-mini-profiler (0.1.31) rack (>= 1.1.3) rack-mount (0.8.3) rack (>= 1.0.0) - rack-protection (1.5.0) - rack - rack-ssl (1.3.3) + rack-protection (1.5.1) rack rack-test (0.6.2) rack (>= 1.0) - rails (3.2.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.16) - rails-dev-tweaks (0.6.1) - actionpack (~> 3.1) - railties (~> 3.1) + rails (4.0.2) + actionmailer (= 4.0.2) + actionpack (= 4.0.2) + activerecord (= 4.0.2) + activesupport (= 4.0.2) + bundler (>= 1.3.0, < 2.0) + railties (= 4.0.2) + sprockets-rails (~> 2.0.0) + rails-observers (0.1.2) + activemodel (~> 4.0) rails_best_practices (1.14.4) activesupport awesome_print @@ -368,180 +379,179 @@ GEM i18n require_all ruby-progressbar - railties (3.2.16) - actionpack (= 3.2.16) - activesupport (= 3.2.16) - rack-ssl (~> 1.3.2) + railties (4.0.2) + actionpack (= 4.0.2) + activesupport (= 4.0.2) rake (>= 0.8.7) - rdoc (~> 3.4) - thor (>= 0.14.6, < 2.0) - raindrops (0.11.0) + thor (>= 0.18.1, < 2.0) + raindrops (0.12.0) rake (10.1.0) raphael-rails (2.1.2) rb-fsevent (0.9.3) - rb-inotify (0.9.0) - ffi (>= 0.5.0) - rb-kqueue (0.2.0) + rb-inotify (0.9.2) ffi (>= 0.5.0) rdoc (3.12.2) json (~> 1.4) redcarpet (2.2.2) - redis (3.0.4) - redis-actionpack (3.2.4) - actionpack (~> 3.2.0) - redis-rack (~> 1.4.4) - redis-store (~> 1.1.4) - redis-activesupport (3.2.4) - activesupport (~> 3.2.0) + redis (3.0.6) + redis-actionpack (4.0.0) + actionpack (~> 4) + redis-rack (~> 1.5.0) + redis-store (~> 1.1.0) + redis-activesupport (4.0.0) + activesupport (~> 4) + redis-store (~> 1.1.0) + redis-namespace (1.4.1) + redis (~> 3.0.4) + redis-rack (1.5.0) + rack (~> 1.5) + redis-store (~> 1.1.0) + redis-rails (4.0.0) + redis-actionpack (~> 4) + redis-activesupport (~> 4) redis-store (~> 1.1.0) - redis-namespace (1.3.1) - redis (~> 3.0.0) - redis-rack (1.4.4) - rack (~> 1.4.0) - redis-store (~> 1.1.4) - redis-rails (3.2.4) - redis-actionpack (~> 3.2.4) - redis-activesupport (~> 3.2.4) - redis-store (~> 1.1.4) redis-store (1.1.4) redis (>= 2.2) ref (1.0.5) - require_all (1.3.1) + require_all (1.3.2) rest-client (1.6.7) mime-types (>= 1.16) - rspec (2.13.0) - rspec-core (~> 2.13.0) - rspec-expectations (~> 2.13.0) - rspec-mocks (~> 2.13.0) - rspec-core (2.13.1) - rspec-expectations (2.13.0) + rspec (2.14.1) + rspec-core (~> 2.14.0) + rspec-expectations (~> 2.14.0) + rspec-mocks (~> 2.14.0) + rspec-core (2.14.7) + rspec-expectations (2.14.4) diff-lcs (>= 1.1.3, < 2.0) - rspec-mocks (2.13.1) - rspec-rails (2.13.2) + rspec-mocks (2.14.4) + rspec-rails (2.14.0) actionpack (>= 3.0) activesupport (>= 3.0) railties (>= 3.0) - rspec-core (~> 2.13.0) - rspec-expectations (~> 2.13.0) - rspec-mocks (~> 2.13.0) + rspec-core (~> 2.14.0) + rspec-expectations (~> 2.14.0) + rspec-mocks (~> 2.14.0) ruby-hmac (0.4.0) ruby-progressbar (1.2.0) rubyntlm (0.1.1) - rubyzip (0.9.9) - safe_yaml (0.9.3) - sanitize (2.0.3) - nokogiri (>= 1.4.4, < 1.6) - sass (3.2.11) - sass-rails (3.2.6) - railties (~> 3.2.0) + safe_yaml (0.9.7) + sanitize (2.0.6) + nokogiri (>= 1.4.4) + sass (3.2.12) + sass-rails (4.0.1) + railties (>= 4.0.0, < 5.0) sass (>= 3.1.10) - tilt (~> 1.3) + sprockets-rails (~> 2.0.0) sdoc (0.3.20) json (>= 1.1.3) rdoc (~> 3.10) - seed-fu (2.2.0) - activerecord (~> 3.1) - activesupport (~> 3.1) - select2-rails (3.4.2) - sass-rails + seed-fu (2.3.0) + activerecord (>= 3.1, < 4.1) + activesupport (>= 3.1, < 4.1) + select2-rails (3.5.2) thor (~> 0.14) - selenium-webdriver (2.33.0) - childprocess (>= 0.2.5) - multi_json (~> 1.0) - rubyzip - websocket (~> 1.0.4) settingslogic (2.0.9) - sexp_processor (4.3.0) + sexp_processor (4.4.0) shoulda-matchers (2.1.0) activesupport (>= 3.0.0) - sidekiq (2.14.0) - celluloid (>= 0.14.1) + sidekiq (2.17.0) + celluloid (>= 0.15.2) connection_pool (>= 1.0.0) json redis (>= 3.0.4) - redis-namespace + redis-namespace (>= 1.3.1) simple_oauth (0.1.9) - simplecov (0.7.1) - multi_json (~> 1.0) - simplecov-html (~> 0.7.1) - simplecov-html (0.7.1) - sinatra (1.4.3) + simplecov (0.8.2) + docile (~> 1.1.0) + multi_json + simplecov-html (~> 0.8.0) + simplecov-html (0.8.0) + sinatra (1.4.4) rack (~> 1.4) rack-protection (~> 1.4) tilt (~> 1.3, >= 1.3.4) six (0.2.0) - slim (2.0.0) - temple (~> 0.6.5) - tilt (~> 1.3, >= 1.3.3) - slop (3.4.5) - spinach (0.8.3) + slim (2.0.2) + temple (~> 0.6.6) + tilt (>= 1.3.3, < 2.1) + slop (3.4.7) + spinach (0.8.7) colorize (= 0.5.8) - gherkin-ruby (~> 0.3.0) + gherkin-ruby (>= 0.3.1) spinach-rails (0.2.1) capybara (>= 2.0.0) railties (>= 3) spinach (>= 0.4) - spork (1.0.0rc2) - sprockets (2.2.2) + spork (1.0.0rc4) + sprockets (2.10.1) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) + sprockets-rails (2.0.1) + actionpack (>= 3.0) + activesupport (>= 3.0) + sprockets (~> 2.8) stamp (0.5.0) state_machine (1.2.0) stringex (1.5.1) - temple (0.6.5) + temple (0.6.7) term-ansicolor (1.2.2) tins (~> 0.8) - test_after_commit (0.2.1) - therubyracer (0.11.4) - libv8 (~> 3.11.8.12) + test_after_commit (0.2.2) + therubyracer (0.12.0) + libv8 (~> 3.16.14.0) ref - thin (1.5.1) + thin (1.6.1) daemons (>= 1.0.9) - eventmachine (>= 0.12.6) + eventmachine (>= 1.0.0) rack (>= 1.0.0) thor (0.18.1) + thread_safe (0.1.3) + atomic tilt (1.4.1) timers (1.1.0) - tinder (1.9.2) + tinder (1.9.3) eventmachine (~> 1.0) faraday (~> 0.8) faraday_middleware (~> 0.9) - hashie (~> 1.0) - json (~> 1.7.5) + hashie (>= 1.0, < 3) + json (~> 1.8.0) mime-types (~> 1.19) - multi_json (~> 1.5) + multi_json (~> 1.7) twitter-stream (~> 0.1) - tins (0.11.0) + tins (0.13.1) treetop (1.4.15) polyglot polyglot (>= 0.3.1) - turbolinks (1.2.0) + turbolinks (2.0.0) coffee-rails twitter-stream (0.1.16) eventmachine (>= 0.12.8) http_parser.rb (~> 0.5.1) simple_oauth (~> 0.1.4) tzinfo (0.3.38) - uglifier (2.1.1) + uglifier (2.3.2) execjs (>= 0.3.0) - multi_json (~> 1.0, >= 1.0.2) + json (>= 1.8.0) underscore-rails (1.4.4) unicorn (4.6.3) kgio (~> 2.6) rack raindrops (~> 0.7) - virtus (0.5.5) - backports (~> 3.3) + unicorn-worker-killer (0.4.2) + unicorn (~> 4) + virtus (1.0.1) + axiom-types (~> 0.0.5) + coercible (~> 1.0) descendants_tracker (~> 0.0.1) + equalizer (~> 0.0.7) warden (1.2.3) rack (>= 1.0) - webmock (1.11.0) + webmock (1.16.0) addressable (>= 2.2.7) crack (>= 0.3.2) - websocket (1.0.7) - websocket-driver (0.3.0) + websocket-driver (0.3.1) xpath (2.0.0) nokogiri (~> 1.3) yajl-ruby (1.1.0) @@ -550,42 +560,45 @@ PLATFORMS ruby DEPENDENCIES + actionpack-action_caching + actionpack-page_caching + activerecord-deprecated_finders acts-as-taggable-on annotate (~> 2.6.0.beta2) asciidoctor awesome_print better_errors binding_of_caller - bootstrap-sass + bootstrap-sass (~> 2.3) capybara carrierwave - chosen-rails (= 1.0.0) + chosen-rails (= 1.0.1) coffee-rails colored coveralls d3_rails (~> 3.1.4) database_cleaner - devise (~> 2.2) - devise-async + devise (= 3.0.4) + devise-async (= 0.8.0) email_spec enumerize factory_girl_rails ffaker fog (~> 1.3.1) - font-awesome-rails + font-awesome-rails (~> 3.2) foreman - gemoji (~> 1.2.1) - github-linguist + gemoji (~> 1.3.0) 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 (~> 2.0.0.pre) + gitlab-linguist (~> 2.9.6) + gitlab-pygments.rb (~> 0.5.4) + gitlab_git (~> 4.0.0.pre) gitlab_meta (= 6.0) gitlab_omniauth-ldap (= 1.0.3) - gon - grape (~> 0.4.1) + gon! + grape (~> 0.6.1) grape-entity (~> 0.3.0) growl guard-rspec @@ -593,8 +606,8 @@ DEPENDENCIES haml-rails hipchat (~> 0.9.0) httparty - jasmine - jquery-atwho-rails (= 0.3.0) + jasmine (= 2.0.0.rc5) + jquery-atwho-rails (~> 0.3.3) jquery-rails (= 2.1.3) jquery-turbolinks jquery-ui-rails (= 2.0.2) @@ -611,12 +624,14 @@ DEPENDENCIES omniauth-twitter pg poltergeist (~> 1.4.1) + protected_attributes pry quiet_assets (~> 1.0.1) rack-attack + rack-cors rack-mini-profiler - rails (= 3.2.16) - rails-dev-tweaks + rails (~> 4.0.0) + rails-observers rails_best_practices raphael-rails (~> 2.1.2) rb-fsevent @@ -648,4 +663,5 @@ DEPENDENCIES uglifier underscore-rails (~> 1.4.4) unicorn (~> 4.6.3) + unicorn-worker-killer webmock diff --git a/README.md b/README.md index 5cc3081dcb..fcf5fdeea9 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 @@ -46,30 +48,24 @@ ### Installation -#### Official production installation +#### Official installation methods -* [Installation guide for a production server](doc/install/installation.md) +* [Manual installation guide for a production server](doc/install/installation.md) +* [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. -#### Official development installation +#### Third party one-click installers -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. +* [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. -* [Vagrant virtual machine for development](https://github.com/gitlabhq/gitlab-vagrant-vm) +* [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. - -* [Digital Ocean 1-Click Application Install](https://www.digitalocean.com/) Have a new server up in 55 seconds. Digital Ocean uses SSD disks which is great for an IO intensive app as GitLab. Look for GitLab under 'Select Image' => 'Applications' when creating a droplet. - -* [BitNami one-click installers](http://bitnami.com/stack/gitlab) Get an image with GitLab and GitLab CI preinstalled for Amazon Web Services, Azure, VMware or your local server. - - ### New versions and upgrading Since 2011 GitLab is released on the 22nd of every month. Every new release includes an upgrade guide. @@ -80,7 +76,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: @@ -111,7 +106,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 @@ -148,15 +143,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 to get in touch with other GitLab users and get help, it's managed by James Newton, Drew Blessing and Sam Gleske + +* [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 1276b99da6..40a3a69007 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.3.1-ee +6.4.0-ee diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico index 057f74ac7a..bfb74960c4 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 6567f2e546..31c4a63cd0 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 a63fb1c9c0..8a4ec851b1 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/blob.js.coffee b/app/assets/javascripts/blob.js.coffee index 03e280f897..99cb1cec91 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) - 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() + [ 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 = $("
") + .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) + + # 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 de4c06a272..9c004c997e 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 67d9498c50..c273ddbd39 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 ccd5ad48db..10c40dff5f 100644 --- a/app/assets/javascripts/main.js.coffee +++ b/app/assets/javascripts/main.js.coffee @@ -1,6 +1,3 @@ -window.updatePage = (data) -> - $.ajax({type: "GET", url: location.href, data: data, dataType: "script"}) - window.slugify = (text) -> text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() @@ -56,7 +53,7 @@ window.unbindEvents = -> document.addEventListener("page:fetch", startSpinner) document.addEventListener("page:fetch", unbindEvents) -document.addEventListener("page:receive", stopSpinner) +document.addEventListener("page:change", stopSpinner) $ -> # Click a .one_click_select field, select the contents diff --git a/app/assets/javascripts/merge_requests.js.coffee b/app/assets/javascripts/merge_requests.js.coffee index 5400bc5c1a..2eef7df1c6 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/notes.js b/app/assets/javascripts/notes.js index defbf81f58..e39bfc0f79 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; @@ -411,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" }); }, @@ -419,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); }, diff --git a/app/assets/javascripts/pager.js.coffee b/app/assets/javascripts/pager.js.coffee index 5bd11d273a..1f763e8b95 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/stylesheets/common.scss b/app/assets/stylesheets/common.scss index b1a15c8969..c55be11fb4 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -5,6 +5,7 @@ html { /** LAYOUT **/ body { + -webkit-font-smoothing: antialiased; margin-bottom: 20px; } @@ -354,6 +355,7 @@ table { .navbar-gitlab .navbar-inner .nav > li .btn-sign-in { @extend .btn-new; padding: 5px 15px; + text-shadow: none; } .broadcast-message { @@ -369,6 +371,10 @@ table { &.input-large { width: 210px; } + + &.input-clamp { + max-width: 100%; + } } .user-result { diff --git a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss index 8f6358acb1..9f5c4a1de9 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss @@ -72,10 +72,11 @@ .ui-box-head { .box-title { - font-size: 18px; - font-weight: normal; + font-size: 20px; + font-weight: 500; line-height: 28px; margin: 0; + color: #444; } h3 { margin: 0; @@ -154,7 +155,7 @@ } .row_title { - font-weight: bold; + font-weight: 500; color: #444; &:hover { color: #444; diff --git a/app/assets/stylesheets/gitlab_bootstrap/common.scss b/app/assets/stylesheets/gitlab_bootstrap/common.scss index c2787af234..26fe02e492 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/common.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/common.scss @@ -6,6 +6,7 @@ .cblue { color: #29A } .cblack { color: #111 } .cdark { color: #444 } +.camber { color: #ffc000 } .cwhite { color: #fff!important } .bgred { background: #F2DEDE!important } @@ -127,3 +128,8 @@ pre.well-pre { .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/forms.scss b/app/assets/stylesheets/gitlab_bootstrap/forms.scss index d4da3fe713..1a310a75c3 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/forms.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/forms.scss @@ -13,6 +13,13 @@ form { margin-top: 1px !important; } } + + &.list-label { + float: none; + padding: 0 !important; + margin: 0; + text-align: left; + } } } diff --git a/app/assets/stylesheets/gitlab_bootstrap/nav.scss b/app/assets/stylesheets/gitlab_bootstrap/nav.scss index aa4cb1ed5f..cc2bf0f912 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 82556e91da..8ad9bc732b 100644 --- a/app/assets/stylesheets/sections/admin.scss +++ b/app/assets/stylesheets/sections/admin.scss @@ -20,6 +20,15 @@ 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 { diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss index 769e95d591..cd3e5d56c2 100644 --- a/app/assets/stylesheets/sections/commits.scss +++ b/app/assets/stylesheets/sections/commits.scss @@ -70,7 +70,7 @@ font-size: 12px; } } - .old_line, .new_line { + .old_line, .new_line, .diff_line { margin: 0px; padding: 0px; border: none; @@ -92,6 +92,15 @@ text-decoration: underline; } } + &.new { + background: #CFD; + } + &.old { + background: #FDD; + } + } + .diff_line { + padding: 0; } .line_holder { &.old .old_line, @@ -122,6 +131,11 @@ color: #ccc; background: #fafafa; } + &.parallel { + display: table-cell; + overflow: hidden; + width: 50%; + } } } .image { @@ -468,8 +482,8 @@ li.commit { } .commit-row-message { - color: #555; - font-weight: bolder; + color: #333; + font-weight: 500; &:hover { color: #444; text-decoration: underline; @@ -478,13 +492,14 @@ li.commit { } .commit-row-info { + color: #777; + a { color: #777; } .committed_ago { float: right; - @extend .cgray; } } diff --git a/app/assets/stylesheets/sections/dashboard.scss b/app/assets/stylesheets/sections/dashboard.scss index f70822c039..99bbcbd210 100644 --- a/app/assets/stylesheets/sections/dashboard.scss +++ b/app/assets/stylesheets/sections/dashboard.scss @@ -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 979c1e90ac..157b9dd9e8 100644 --- a/app/assets/stylesheets/sections/header.scss +++ b/app/assets/stylesheets/sections/header.scss @@ -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 e384aebc76..792bcef02f 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/login.scss b/app/assets/stylesheets/sections/login.scss index 33bef59c08..402b44fe7a 100644 --- a/app/assets/stylesheets/sections/login.scss +++ b/app/assets/stylesheets/sections/login.scss @@ -46,3 +46,10 @@ body.login-page{ margin: 2px; } } + +.devise-errors { + h2 { + font-size: 14px; + color: #a00; + } +} diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss index 94b9ca3b18..ac0caa3004 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 21f4ed0577..7e5668ae8a 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 e9162b3c16..98e1a15f70 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/sections/projects.scss @@ -19,6 +19,12 @@ 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; + } } } @@ -61,13 +71,24 @@ border: 1px solid #E1E1E1; @include border-radius(4px); + input[type="text"], .btn { - margin-left: 3px; border: none; - background: none; + @include border-radius(0px); + border-left: 1px solid #E1E1E1; box-shadow: none; + padding: 6px 10px; + } + + .btn { + float: left; + background: none; color: #29b; - padding: 6px; + + &:first-child { + @include border-radius-left(4px); + border-left: 0px; + } &.active { color: #333; @@ -76,20 +97,46 @@ } input[type="text"] { - margin-left: 2px; - border: none; - border-radius: 0; - border-left: 1px solid #E1E1E1; + cursor: auto; @extend .monospace; - box-shadow: none; background: #FAFAFA; - padding: 6px 10px; } } -.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 +177,8 @@ ul.nav.nav-projects-tabs { margin: 0px; } -.my-projects { +.my-projects, +.public-projects { li { .project-info { margin-bottom: 10px; diff --git a/app/contexts/files/create_context.rb b/app/contexts/files/create_context.rb index 1027313855..b3d62a028c 100644 --- a/app/contexts/files/create_context.rb +++ b/app/contexts/files/create_context.rb @@ -1,3 +1,5 @@ +require_relative "base_context" + module Files class CreateContext < BaseContext def execute @@ -19,13 +21,13 @@ module Files 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") + return error("Your changes could not be committed, because file name contains not allowed characters") end blob = repository.blob_at(ref, file_path) if blob - return error("Your changes could not be commited, because file with such name exists") + return error("Your changes could not be committed, because file with such name exists") end new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path) @@ -37,7 +39,7 @@ module Files if created_successfully success else - error("Your changes could not be commited, because the file has been changed") + error("Your changes could not be committed, because the file has been changed") end end end diff --git a/app/contexts/files/delete_context.rb b/app/contexts/files/delete_context.rb index b1937721d4..39ff7c2a4b 100644 --- a/app/contexts/files/delete_context.rb +++ b/app/contexts/files/delete_context.rb @@ -1,3 +1,5 @@ +require_relative "base_context" + module Files class DeleteContext < BaseContext def execute @@ -31,7 +33,7 @@ module Files if deleted_successfully success else - error("Your changes could not be commited, because the file has been changed") + error("Your changes could not be committed, because the file has been changed") end end end diff --git a/app/contexts/files/update_context.rb b/app/contexts/files/update_context.rb index f40c749699..556027a325 100644 --- a/app/contexts/files/update_context.rb +++ b/app/contexts/files/update_context.rb @@ -1,3 +1,5 @@ +require_relative "base_context" + module Files class UpdateContext < BaseContext def execute @@ -30,7 +32,7 @@ module Files if created_successfully success else - error("Your changes could not be commited, because the file has been changed") + error("Your changes could not be committed, because the file has been changed") end end end diff --git a/app/contexts/issues/bulk_update_context.rb b/app/contexts/issues/bulk_update_context.rb index 73a3c35352..ab38a4f8ab 100644 --- a/app/contexts/issues/bulk_update_context.rb +++ b/app/contexts/issues/bulk_update_context.rb @@ -22,7 +22,7 @@ module Issues opts[:milestone_id] = milestone_id if milestone_id.present? opts[:assignee_id] = assignee_id if assignee_id.present? - issues = Issue.where(id: issues_ids).all + issues = Issue.where(id: issues_ids) issues = issues.select { |issue| can?(current_user, :modify_issue, issue) } issues.each do |issue| diff --git a/app/contexts/issues/list_context.rb b/app/contexts/issues/list_context.rb index da2eed0e25..fd27356d1c 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 904c60a0f2..2acb9fbfe1 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)) diff --git a/app/contexts/projects/update_context.rb b/app/contexts/projects/update_context.rb index 9564dd9468..ed0d451a31 100644 --- a/app/contexts/projects/update_context.rb +++ b/app/contexts/projects/update_context.rb @@ -2,15 +2,15 @@ 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.repository.root_ref - GitlabShellWorker.perform_async( - :update_repository_head, - project.path_with_namespace, - new_branch - ) + if project.repository.exists? && new_branch != project.default_branch + project.change_head(new_branch) end project.update_attributes(params[:project], as: role) diff --git a/app/contexts/search_context.rb b/app/contexts/search_context.rb index 2f9438f6bb..5985ab1fb0 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/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 4de0a65b5f..7e3e29f59f 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -7,8 +7,8 @@ class Admin::ProjectsController < Admin::ApplicationController 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 = user ? user.owned_projects : Project.all + @projects = @projects.where("visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present? @projects = @projects.with_push if params[:with_push].present? @projects = @projects.abandoned if params[:abandoned].present? @projects = @projects.search(params[:name]) if params[:name].present? diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index c9875b9632..426001e621 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -2,8 +2,7 @@ class Admin::UsersController < Admin::ApplicationController before_filter :user, only: [:show, :edit, :update, :destroy] def index - @users = User.scoped - @users = @users.filter(params[:filter]) + @users = User.filter(params[:filter]) @users = @users.search(params[:name]) if params[:name].present? @users = @users.alphabetically.page(params[:page]) end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ccab2fc5c6..a98b51d7ca 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,5 @@ +require 'gon' + class ApplicationController < ActionController::Base before_filter :authenticate_user! before_filter :reject_blocked! @@ -8,6 +10,7 @@ class ApplicationController < ActionController::Base before_filter :dev_tools if Rails.env == 'development' before_filter :default_headers before_filter :add_gon_variables + before_filter :configure_permitted_parameters, if: :devise_controller? protect_from_forgery @@ -82,6 +85,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 @@ -103,7 +109,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! @@ -193,4 +199,31 @@ class ApplicationController < ActionController::Base def gitlab_ldap_access Gitlab::LDAP::Access.new end + + # JSON for infinite scroll via Pager object + def pager_json(partial, count) + html = render_to_string( + partial, + layout: false, + formats: [:html] + ) + + render json: { + html: html, + count: count + } + end + + def view_to_html_string(partial) + render_to_string( + partial, + layout: false, + formats: [:html] + ) + end + + def configure_permitted_parameters + devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:username, :email, :password) } + devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :email, :name, :password, :password_confirmation) } + end end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index ac31938443..aaab4b40c4 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) @@ -72,6 +73,6 @@ class DashboardController < ApplicationController protected def load_projects - @projects = current_user.authorized_projects.sorted_by_activity + @projects = current_user.authorized_projects.sorted_by_activity.non_archived end end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index fe4aa4c631..9b7278c2ca 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -40,7 +40,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 0000000000..57f3bbf062 --- /dev/null +++ b/app/controllers/profiles/avatars_controller.rb @@ -0,0 +1,13 @@ +class Profiles::AvatarsController < ApplicationController + layout "profile" + + def destroy + @user = current_user + @user.remove_avatar! + + @user.save + @user.reset_events_cache + + redirect_to profile_path + end +end diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index c36dae2abd..541319e8d0 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -2,7 +2,7 @@ class Profiles::KeysController < ApplicationController layout "profile" def index - @keys = current_user.keys.order('id DESC').all + @keys = current_user.keys.order('id DESC') end def show diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 47cfc5e63f..9234cd1708 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 80aeb5cd6c..7e4580017d 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 087c1639ac..fc9807e3c4 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -16,7 +16,7 @@ class Projects::BlobController < Projects::ApplicationController result = Files::DeleteContext.new(@project, current_user, params, @ref, @path).execute if result[:status] == :success - flash[:notice] = "Your changes have been successfully commited" + flash[:notice] = "Your changes have been successfully committed" redirect_to project_tree_path(@project, @ref) else flash[:alert] = result[:error] diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index bdffc940ea..12856191c2 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/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index 0750e0a146..6e1a76ff41 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -7,7 +7,7 @@ class Projects::DeployKeysController < Projects::ApplicationController layout "project_settings" def index - @enabled_keys = @project.deploy_keys.all + @enabled_keys = @project.deploy_keys @available_keys = available_keys - @enabled_keys end diff --git a/app/controllers/projects/edit_tree_controller.rb b/app/controllers/projects/edit_tree_controller.rb index f6c547a020..3921273620 100644 --- a/app/controllers/projects/edit_tree_controller.rb +++ b/app/controllers/projects/edit_tree_controller.rb @@ -10,7 +10,7 @@ class Projects::EditTreeController < Projects::BaseTreeController result = Files::UpdateContext.new(@project, current_user, params, @ref, @path).execute if result[:status] == :success - flash[:notice] = "Your changes have been successfully commited" + flash[:notice] = "Your changes have been successfully committed" redirect_to project_blob_path(@project, @id) else flash[:alert] = result[:error] diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index 1a94dbab5e..314d87df03 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -7,7 +7,7 @@ class Projects::HooksController < Projects::ApplicationController layout "project_settings" def index - @hooks = @project.hooks.all + @hooks = @project.hooks @hook = ProjectHook.new end @@ -18,7 +18,7 @@ class Projects::HooksController < Projects::ApplicationController if @hook.valid? redirect_to project_hooks_path(@project) else - @hooks = @project.hooks.all + @hooks = @project.hooks render :index end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index e8f845b2d1..e7b4c837ae 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 @@ -70,6 +74,7 @@ class Projects::IssuesController < Projects::ApplicationController def update @issue.update_attributes(params[:issue].merge(author_id_of_changes: current_user.id)) + @issue.reset_events_cache respond_to do |format| format.js diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 0cc09caf1d..d644026b2b 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 @@ -76,9 +79,30 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def update + # If we close MergeRequest we want to ignore validation + # so we can close broken one (Ex. fork project removed) + if params[:merge_request] == {"state_event"=>"close"} + @merge_request.allow_broken = true + + if @merge_request.close + opts = { notice: 'Merge request was successfully closed.' } + else + opts = { alert: 'Failed to close merge request.' } + end + + redirect_to [@merge_request.target_project, @merge_request], opts + return + end + + # We dont allow change of source/target projects + # after merge request was created + params[:merge_request].delete(:source_project_id) + params[:merge_request].delete(:target_project_id) + if @merge_request.update_attributes(params[:merge_request].merge(author_id_of_changes: current_user.id)) @merge_request.reload_code @merge_request.mark_as_unchecked + @merge_request.reset_events_cache redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully updated.' else render "edit" @@ -157,14 +181,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def validates_merge_request + # If source project was removed (Ex. mr from fork to origin) + return invalid_mr unless @merge_request.source_project + # Show git not found page # if there is no saved commits between source & target branch if @merge_request.commits.blank? - # and if source target doesn't exist - return invalid_mr unless @merge_request.target_project.repository.branch_names.include?(@merge_request.target_branch) + # and if target branch doesn't exist + return invalid_mr unless @merge_request.target_branch_exists? - # or if source branch doesn't exist - return invalid_mr unless @merge_request.source_project.repository.branch_names.include?(@merge_request.source_branch) + # or if source branch doesn't exist + return invalid_mr unless @merge_request.source_branch_exists? end end diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index 3e88656cdf..ecb1fc1d56 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 d6d474cf9c..933a0cb876 100644 --- a/app/controllers/projects/new_tree_controller.rb +++ b/app/controllers/projects/new_tree_controller.rb @@ -9,7 +9,7 @@ class Projects::NewTreeController < Projects::BaseTreeController result = Files::CreateContext.new(@project, current_user, params, @ref, file_path).execute if result[:status] == :success - flash[:notice] = "Your changes have been successfully commited" + flash[:notice] = "Your changes have been successfully committed" redirect_to project_blob_path(@project, File.join(@ref, file_path)) else flash[:alert] = result[:error] diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 8214163c31..5ff5c5b7d9 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 @@ -32,6 +39,7 @@ class Projects::NotesController < Projects::ApplicationController @note = @project.notes.find(params[:id]) return access_denied! unless can?(current_user, :admin_note, @note) @note.destroy + @note.reset_events_cache respond_to do |format| format.js { render nothing: true } @@ -43,6 +51,7 @@ class Projects::NotesController < Projects::ApplicationController return access_denied! unless can?(current_user, :admin_note, @note) @note.update_attributes(params[:note]) + @note.reset_events_cache respond_to do |format| format.js do diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 66d6edd810..e1c55e7d91 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -5,7 +5,7 @@ class ProjectsController < ApplicationController # Authorize before_filter :authorize_read_project!, except: [:index, :new, :create] - before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer] + before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive] before_filter :require_non_empty_project, only: [:blob, :tree, :graph] layout 'navless', only: [:new, :create, :fork] @@ -55,7 +55,7 @@ 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 @@ -73,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 @@ -116,6 +116,24 @@ class ProjectsController < ApplicationController end end + def archive + return access_denied! unless can?(current_user, :archive_project, project) + project.archive! + + respond_to do |format| + format.html { redirect_to @project } + end + end + + def unarchive + return access_denied! unless can?(current_user, :archive_project, project) + project.unarchive! + + respond_to do |format| + format.html { redirect_to @project } + end + end + private def set_title diff --git a/app/controllers/public/projects_controller.rb b/app/controllers/public/projects_controller.rb index 87e903a1d2..8d66250d7b 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 f5c3bb133e..2a2748dc1f 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 04fda026bf..157518c4ba 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -62,7 +62,7 @@ module ApplicationHelper size = 40 if size.nil? || size <= 0 if !Gitlab.config.gravatar.enabled || user_email.blank? - 'no_avatar.png' + '/assets/no_avatar.png' else gravatar_url = request.ssl? || gitlab_config.https ? Gitlab.config.gravatar.ssl_url : Gitlab.config.gravatar.plain_url user_email.strip! @@ -72,7 +72,7 @@ module ApplicationHelper def last_commit(project) if project.repo_exists? - time_ago_in_words(project.repository.commit.committed_date) + " ago" + time_ago_with_tooltip(project.repository.commit.committed_date) + " ago" else "Never" end @@ -136,9 +136,9 @@ module ApplicationHelper Digest::SHA1.hexdigest string end - def project_last_activity project + def project_last_activity(project) if project.last_activity_at - time_ago_in_words(project.last_activity_at) + " ago" + time_ago_with_tooltip(project.last_activity_at, 'bottom', 'last_activity_time_ago') + " ago" else "Never" end @@ -207,4 +207,22 @@ module ApplicationHelper 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 + + def time_ago_with_tooltip(date, placement = 'top', html_class = 'time_ago') + capture_haml do + haml_tag :time, time_ago_in_words(date), + class: html_class, datetime: date, title: date.stamp("Aug 21, 2011 9:23pm"), + data: { toggle: 'tooltip', placement: placement } + + haml_tag :script, "$('." + html_class + "').tooltip()" + end.html_safe + end end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 0eca129532..663369e458 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 diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb index ea2540bf38..5ff19b8829 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 4aeb65752f..d3671efa55 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/icons_helper.rb b/app/helpers/icons_helper.rb index e4dfc236ca..53d4a8f2e6 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -8,10 +8,14 @@ module IconsHelper end def public_icon - content_tag :i, nil, class: 'icon-globe cblue' + 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 cgreen' + content_tag :i, nil, class: 'icon-lock' end end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 2221583912..56b776cff4 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/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 4ba48aa433..5e3f82fe9c 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -36,7 +36,7 @@ module MergeRequestsHelper def merge_path_description(merge_request, separator) if merge_request.for_fork? - "Project:Branches: #{@merge_request.source_project.path_with_namespace}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.path_with_namespace}:#{@merge_request.target_branch}" + "Project:Branches: #{@merge_request.source_project_path}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.path_with_namespace}:#{@merge_request.target_branch}" else "Branches: #{@merge_request.source_branch} #{separator} #{@merge_request.target_branch}" end diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index a3ec4cca59..822d27cf17 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -31,8 +31,14 @@ module NotesHelper def note_timestamp(note) # Shows the created at time and the updated at time if different - ts = "#{time_ago_in_words(note.created_at)} ago" - ts << content_tag(:small, " (Edited #{time_ago_in_words(note.updated_at)} ago)") if note.updated_at != note.created_at + ts = "#{time_ago_with_tooltip(note.created_at, 'bottom', 'note_created_ago')} ago" + if note.updated_at != note.created_at + ts << capture_haml do + haml_tag :small do + haml_concat " (Edited #{time_ago_with_tooltip(note.updated_at, 'bottom', 'note_edited_ago')} ago)" + end + end + end ts.html_safe end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index ee0e681674..660528952b 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) @@ -80,7 +82,7 @@ module ProjectsHelper end def project_active_milestones - @project.milestones.active.order("due_date, title ASC").all + @project.milestones.active.order("due_date, title ASC") end def project_issues_trackers(current_tracker = nil) @@ -135,8 +137,8 @@ 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 @@ -177,4 +179,12 @@ module ProjectsHelper title end + + def default_url_to_repo + current_user ? @project.url_to_repo : @project.http_url_to_repo + end + + def default_clone_protocol + current_user ? "ssh" : "http" + end end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 33c5e4fb9d..f24156e4d8 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -1,14 +1,16 @@ module SearchHelper def search_autocomplete_source return unless current_user - [ groups_autocomplete, projects_autocomplete, + public_projects_autocomplete, default_autocomplete, project_autocomplete, help_autocomplete - ].flatten.to_json + ].flatten.uniq do |item| + item[:label] + end.to_json end private @@ -71,7 +73,14 @@ module SearchHelper # Autocomplete results for the current user's projects def projects_autocomplete - current_user.authorized_projects.map do |p| + current_user.authorized_projects.non_archived.map do |p| + { 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).non_archived.map do |p| { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) } end end diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb new file mode 100644 index 0000000000..81e10f3685 --- /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/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 67544f2a33..25b8bf755e 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -2,23 +2,27 @@ module Emails module MergeRequests def new_merge_request_email(recipient_id, merge_request_id) @merge_request = MergeRequest.find(merge_request_id) + @project = @merge_request.project mail(to: recipient(recipient_id), subject: subject("New merge request ##{@merge_request.iid}", @merge_request.title)) end def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id) @merge_request = MergeRequest.find(merge_request_id) @previous_assignee = User.find_by_id(previous_assignee_id) if previous_assignee_id + @project = @merge_request.project mail(to: recipient(recipient_id), subject: subject("Changed merge request ##{@merge_request.iid}", @merge_request.title)) end def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) @merge_request = MergeRequest.find(merge_request_id) @updated_by = User.find updated_by_user_id + @project = @merge_request.project mail(to: recipient(recipient_id), subject: subject("Closed merge request ##{@merge_request.iid}", @merge_request.title)) end def merged_merge_request_email(recipient_id, merge_request_id) @merge_request = MergeRequest.find(merge_request_id) + @project = @merge_request.project mail(to: recipient(recipient_id), subject: subject("Accepted merge request ##{@merge_request.iid}", @merge_request.title)) end end diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index 0e40450bfe..df21d7b5b0 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -13,5 +13,15 @@ module Emails mail(to: @user.email, subject: subject("Project was moved")) end + + def repository_push_email(project_id, recipient, author_id, branch, compare) + @project = Project.find(project_id) + @author = User.find(author_id) + @commits = Commit.decorate(compare.commits) + @diffs = compare.diffs + @branch = branch + + mail(to: recipient, subject: subject("New push to repository")) + end end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 2f7be00c33..aec4c57cba 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -16,6 +16,7 @@ class Notify < ActionMailer::Base default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root default from: Gitlab.config.gitlab.email_from + default reply_to: "noreply@#{Gitlab.config.gitlab.host}" # Just send email with 3 seconds delay def self.delay diff --git a/app/models/ability.rb b/app/models/ability.rb index 8547608914..cf925141f2 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -29,7 +29,7 @@ class Ability nil end - if project && project.public + if project && project.public? [ :read_project, :read_wiki, @@ -59,37 +59,41 @@ class Ability # Rules based on role in project if team.masters.include?(user) - rules << project_master_rules + rules += project_master_rules elsif team.developers.include?(user) - rules << project_dev_rules + rules += project_dev_rules elsif team.reporters.include?(user) - rules << project_report_rules + rules += project_report_rules elsif team.guests.include?(user) - rules << project_guest_rules + rules += project_guest_rules end - if project.public? - rules << public_project_rules + if project.public? || project.internal? + rules += public_project_rules end if project.owner == user || user.admin? - rules << project_admin_rules + rules += project_admin_rules end if project.group && project.group.has_owner?(user) - rules << project_admin_rules + rules += project_admin_rules end - rules.flatten + if project.archived? + rules -= project_archived_rules + end + + rules end def public_project_rules project_guest_rules + [ :download_code, - :fork_project, + :fork_project ] end @@ -125,6 +129,16 @@ class Ability ] end + def project_archived_rules + [ + :write_merge_request, + :push_code, + :push_code_to_protected_branches, + :modify_merge_request, + :admin_merge_request + ] + end + def project_master_rules project_dev_rules + [ :push_code_to_protected_branches, @@ -145,9 +159,10 @@ class Ability def project_admin_rules project_master_rules + [ :change_namespace, - :change_public_mode, + :change_visibility_level, :rename_project, - :remove_project + :remove_project, + :archive_project ] end @@ -160,7 +175,7 @@ class Ability # Only group owner and administrators can manage group if group.has_owner?(user) || user.admin? - rules << [ + rules += [ :manage_group, :manage_namespace ] @@ -174,7 +189,7 @@ class Ability # Only namespace owner and administrators can manage it if namespace.owner == user || user.admin? - rules << [ + rules += [ :manage_namespace ] end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 7f820f950b..58bf621f91 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 f78f127242..ddb863c1be 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -18,7 +18,7 @@ class Event < ActiveRecord::Base attr_accessible :project, :action, :data, :author_id, :project_id, :target_id, :target_type - default_scope where("author_id IS NOT NULL") + default_scope { where.not(author_id: nil) } CREATED = 1 UPDATED = 2 @@ -223,7 +223,7 @@ class Event < ActiveRecord::Base # Max 20 commits from push DESC def commits - @commits ||= data[:commits].reverse + @commits ||= (data[:commits] || []).reverse end def commits_count diff --git a/app/models/gollum_wiki.rb b/app/models/gollum_wiki.rb index 05fc2a745b..7ebaaff61c 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/group.rb b/app/models/group.rb index ba9461c42c..2e1cbf603a 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -36,7 +36,7 @@ class Group < Namespace def add_users(user_ids, group_access) user_ids.compact.each do |user_id| - user = self.users_groups.find_or_initialize_by_user_id(user_id) + user = self.users_groups.find_or_initialize_by(user_id: user_id) user.update_attributes(group_access: group_access) end end diff --git a/app/models/issue.rb b/app/models/issue.rb index d350b237d3..6580c5004a 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -28,7 +28,7 @@ class Issue < ActiveRecord::Base scope :of_group, ->(group) { where(project_id: group.project_ids) } scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) } - scope :opened, -> { with_state(:opened) } + scope :opened, -> { with_state(:opened, :reopened) } scope :closed, -> { with_state(:closed) } attr_accessible :title, :assignee_id, :position, :description, @@ -56,12 +56,23 @@ class Issue < ActiveRecord::Base state :closed end - # Both open and reopened issues should be listed as opened - scope :opened, -> { with_state(:opened, :reopened) } - # Mentionable overrides. def gfm_reference "issue ##{iid}" end + + # Reset issue events cache + # + # Since we do cache @event we need to reset cache in special cases: + # * when an issue is updated + # Events cache stored like events/23-20130109142513. + # The cache key includes updated_at timestamp. + # Thus it will automatically generate a new fragment + # when the event is updated because the key changes. + def reset_events_cache + Event.where(target_id: self.id, target_type: 'Issue'). + order('id DESC').limit(100). + update_all(updated_at: Time.now) + end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index b164ea1107..e59aee8b44 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -35,6 +35,10 @@ class MergeRequest < ActiveRecord::Base attr_accessor :should_remove_source_branch + # When this attribute is true some MR validation is ignored + # It allows us to close or modify broken merge requests + attr_accessor :allow_broken + state_machine :state, initial: :opened do event :close do transition [:reopened, :opened] => :closed @@ -80,7 +84,7 @@ class MergeRequest < ActiveRecord::Base serialize :st_commits serialize :st_diffs - validates :source_project, presence: true + validates :source_project, presence: true, unless: :allow_broken validates :source_branch, presence: true validates :target_project, presence: true validates :target_branch, presence: true @@ -262,7 +266,7 @@ class MergeRequest < ActiveRecord::Base # Return the set of issues that will be closed if this merge request is accepted. def closes_issues if target_branch == project.default_branch - unmerged_commits.map { |c| c.closes_issues(project) }.flatten.uniq.sort_by(&:id) + commits.map { |c| c.closes_issues(project) }.flatten.uniq.sort_by(&:id) else [] end @@ -273,6 +277,48 @@ class MergeRequest < ActiveRecord::Base "merge request !#{iid}" end + def target_project_path + if target_project + target_project.path_with_namespace + else + "(removed)" + end + end + + def source_project_path + if source_project + source_project.path_with_namespace + else + "(removed)" + end + end + + def source_branch_exists? + return false unless self.source_project + + self.source_project.repository.branch_names.include?(self.source_branch) + end + + def target_branch_exists? + return false unless self.target_project + + self.target_project.repository.branch_names.include?(self.target_branch) + end + + # Reset merge request events cache + # + # Since we do cache @event we need to reset cache in special cases: + # * when a merge request is updated + # Events cache stored like events/23-20130109142513. + # The cache key includes updated_at timestamp. + # Thus it will automatically generate a new fragment + # when the event is updated because the key changes. + def reset_events_cache + Event.where(target_id: self.id, target_type: 'MergeRequest'). + order('id DESC').limit(100). + update_all(updated_at: Time.now) + end + private def dump_commits(commits) diff --git a/app/models/note.rb b/app/models/note.rb index 7e7387abed..b23f7df774 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 @@ -237,4 +239,19 @@ class Note < ActiveRecord::Base def noteable_type=(sType) super(sType.to_s.classify.constantize.base_class.to_s) end + + # Reset notes events cache + # + # Since we do cache @event we need to reset cache in special cases: + # * when a note is updated + # * when a note is removed + # Events cache stored like events/23-20130109142513. + # The cache key includes updated_at timestamp. + # Thus it will automatically generate a new fragment + # when the event is updated because the key changes. + def reset_events_cache + Event.where(target_id: self.id, target_type: 'Note'). + order('id DESC').limit(100). + update_all(updated_at: Time.now) + end end diff --git a/app/models/project.rb b/app/models/project.rb index 673d952bdf..d164ff58db 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -14,24 +14,25 @@ # 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 - + 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 @@ -41,23 +42,29 @@ class Project < ActiveRecord::Base # Relations belongs_to :creator, foreign_key: "creator_id", class_name: "User" - belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'" + belongs_to :group, -> { where(type: Group) }, foreign_key: "namespace_id" belongs_to :namespace - has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id' + has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' has_one :gitlab_ci_service, dependent: :destroy has_one :campfire_service, dependent: :destroy + has_one :emails_on_push_service, dependent: :destroy has_one :pivotaltracker_service, dependent: :destroy has_one :hipchat_service, dependent: :destroy has_one :flowdock_service, dependent: :destroy + has_one :assembla_service, dependent: :destroy has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" has_one :forked_from_project, through: :forked_project_link + # Merge Requests for target project should be removed with it + has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id" + + # Merge requests from source project should be kept when source project was removed + has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest + + has_many :issues, -> { order "state DESC, created_at DESC" }, dependent: :destroy has_many :services, dependent: :destroy has_many :events, dependent: :destroy - has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id" - has_many :fork_merge_requests,dependent: :destroy, foreign_key: "source_project_id", class_name: MergeRequest - has_many :issues, dependent: :destroy, order: "state DESC, created_at DESC" has_many :milestones, dependent: :destroy has_many :notes, dependent: :destroy has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet" @@ -77,8 +84,8 @@ class Project < ActiveRecord::Base delegate :members, to: :team, prefix: true # Validations - validates :creator, presence: true - validates :description, length: { within: 0..2000 } + validates :creator, presence: true, on: :create + validates :description, length: { maximum: 2000 }, allow_blank: true validates :name, presence: true, length: { within: 0..255 }, format: { with: Gitlab::Regex.project_name_regex, message: "only letters, digits, spaces & '_' '-' '.' allowed. Letter or digit should be first" } @@ -88,7 +95,7 @@ class Project < ActiveRecord::Base message: "only letters, digits & '_' '-' '.' allowed. Letter or digit should be first" } validates :issues_enabled, :wall_enabled, :merge_requests_enabled, :wiki_enabled, inclusion: { in: [true, false] } - validates :issues_tracker_id, length: { within: 0..255 } + validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true validates :namespace, presence: true validates_uniqueness_of :name, scope: :namespace_id @@ -110,7 +117,10 @@ class Project < ActiveRecord::Base scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") } scope :personal, ->(user) { where(namespace_id: user.namespace_id) } scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) } - scope :public_only, -> { where(public: true) } + scope :public_only, -> { where(visibility_level: PUBLIC) } + scope :public_or_internal_only, ->(user) { where("visibility_level IN (:levels)", levels: user ? [ INTERNAL, PUBLIC ] : [ PUBLIC ]) } + + scope :non_archived, -> { where(archived: false) } enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab @@ -128,7 +138,7 @@ class Project < ActiveRecord::Base end def search query - joins(:namespace).where("projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query OR projects.description LIKE :query", query: "%#{query}%") + joins(:namespace).where("projects.archived = ?", false).where("projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query OR projects.description LIKE :query", query: "%#{query}%") end def find_with_namespace(id) @@ -142,6 +152,10 @@ class Project < ActiveRecord::Base where(path: id, namespace_id: nil).last end end + + def visibility_levels + Gitlab::VisibilityLevel.options + end end def team @@ -227,7 +241,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 emails_on_push) end def gitlab_ci? @@ -294,8 +308,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) @@ -312,14 +328,14 @@ class Project < ActiveRecord::Base c_ids = self.repository.commits_between(oldrev, newrev).map(&:id) # Update code for merge requests into project between project branches - mrs = self.merge_requests.opened.by_branch(branch_name).all + mrs = self.merge_requests.opened.by_branch(branch_name).to_a # Update code for merge requests between project and project fork - mrs += self.fork_merge_requests.opened.by_branch(branch_name).all + mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked } # Close merge requests - mrs = self.merge_requests.opened.where(target_branch: branch_name).all + mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) } mrs.each { |merge_request| merge_request.merge!(user.id) } @@ -388,7 +404,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 @@ -453,4 +469,26 @@ class Project < ActiveRecord::Base def default_branch @default_branch ||= repository.root_ref if repository.exists? end + + def reload_default_branch + @default_branch = nil + default_branch + end + + def visibility_level_field + visibility_level + end + + def archive! + update_attribute(:archived, true) + end + + def unarchive! + update_attribute(:archived, false) + end + + def change_head(branch) + gitlab_shell.update_repository_head(self.path_with_namespace, branch) + reload_default_branch + end end diff --git a/app/models/project_hook.rb b/app/models/project_hook.rb index 2576fc979d..e1c9ed01bc 100644 --- a/app/models/project_hook.rb +++ b/app/models/project_hook.rb @@ -2,15 +2,24 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# type :string(255) default("ProjectHook") -# service_id :integer +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null # class ProjectHook < WebHook belongs_to :project + + attr_accessible :push_events, :issues_events, :merge_requests_events + + scope :push_hooks, -> { where(push_events: true) } + scope :issue_hooks, -> { where(issues_events: true) } + scope :merge_request_hooks, -> { where(merge_requests_events: true) } end diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb new file mode 100644 index 0000000000..66ecf39478 --- /dev/null +++ b/app/models/project_services/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/campfire_service.rb b/app/models/project_services/campfire_service.rb similarity index 100% rename from app/models/campfire_service.rb rename to app/models/project_services/campfire_service.rb diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb new file mode 100644 index 0000000000..2a46eff784 --- /dev/null +++ b/app/models/project_services/emails_on_push_service.rb @@ -0,0 +1,44 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# token :string(255) +# project_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean default(FALSE), not null +# project_url :string(255) +# subdomain :string(255) +# room :string(255) +# + +class EmailsOnPushService < Service + attr_accessible :recipients + + validates :recipients, presence: true, if: :activated? + + def title + 'Emails on push' + end + + def description + 'Email the commits and diff of each push to a list of recipients.' + end + + def to_param + 'emails_on_push' + end + + def execute(push_data) + EmailsOnPushWorker.perform_async(project_id, recipients, push_data) + end + + def fields + [ + { type: 'textarea', name: 'recipients', placeholder: 'Emails separated by whitespace' }, + ] + end +end diff --git a/app/models/flowdock_service.rb b/app/models/project_services/flowdock_service.rb similarity index 100% rename from app/models/flowdock_service.rb rename to app/models/project_services/flowdock_service.rb diff --git a/app/models/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb similarity index 100% rename from app/models/gitlab_ci_service.rb rename to app/models/project_services/gitlab_ci_service.rb diff --git a/app/models/hipchat_service.rb b/app/models/project_services/hipchat_service.rb similarity index 100% rename from app/models/hipchat_service.rb rename to app/models/project_services/hipchat_service.rb diff --git a/app/models/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb similarity index 100% rename from app/models/pivotaltracker_service.rb rename to app/models/project_services/pivotaltracker_service.rb diff --git a/app/models/project_team.rb b/app/models/project_team.rb index ef297fa8b9..db6ff9d480 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -87,9 +87,8 @@ class ProjectTeam def import(source_project) target_project = project - source_team = source_project.users_projects.all - target_team = target_project.users_projects.all - target_user_ids = target_team.map(&:user_id) + source_team = source_project.users_projects.to_a + target_user_ids = target_project.users_projects.pluck(:user_id) source_team.reject! do |tm| # Skip if user already present in team diff --git a/app/models/repository.rb b/app/models/repository.rb index 5813070057..1255b81453 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -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 4cd2b272ee..6f22a863d9 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 5cdf046644..bffcbbf00f 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 4659cc73d5..c20202f1df 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -41,6 +41,7 @@ # confirmed_at :datetime # confirmation_sent_at :datetime # unconfirmed_email :string(255) +# hide_no_ssh_key :boolean default(FALSE), not null # require 'carrierwave/orm/activerecord' @@ -52,7 +53,7 @@ class User < ActiveRecord::Base attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username, :skype, :linkedin, :twitter, :color_scheme_id, :theme_id, :force_random_password, - :extern_uid, :provider, :password_expires_at, :avatar, + :extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key, as: [:default, :admin] attr_accessible :projects_limit, :can_create_group, @@ -72,7 +73,7 @@ class User < ActiveRecord::Base # # Namespace for personal projects - has_one :namespace, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace", conditions: 'type IS NULL' + has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace" # Profile has_many :keys, dependent: :destroy @@ -80,8 +81,7 @@ class User < ActiveRecord::Base # Groups has_many :users_groups, dependent: :destroy has_many :groups, through: :users_groups - has_many :owned_groups, through: :users_groups, source: :group, conditions: { users_groups: { group_access: UsersGroup::OWNER } } - + has_many :owned_groups, -> { where users_groups: { group_access: UsersGroup::OWNER } }, through: :users_groups, source: :group # Projects has_many :groups_projects, through: :groups, source: :projects has_many :personal_projects, through: :namespace, source: :projects @@ -94,7 +94,7 @@ class User < ActiveRecord::Base has_many :notes, dependent: :destroy, foreign_key: :author_id has_many :merge_requests, dependent: :destroy, foreign_key: :author_id has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event" - has_many :recent_events, foreign_key: :author_id, class_name: "Event", order: "id DESC" + has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id, class_name: "Event" has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue" has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest" @@ -104,7 +104,7 @@ class User < ActiveRecord::Base # validates :name, presence: true validates :email, presence: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/ } - validates :bio, length: { within: 0..255 } + validates :bio, length: { maximum: 255 }, allow_blank: true validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider} validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0} validates :username, presence: true, uniqueness: true, @@ -164,7 +164,7 @@ class User < ActiveRecord::Base scope :alphabetically, -> { order('name ASC') } scope :in_team, ->(team){ where(id: team.member_ids) } scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) } - scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : scoped } + scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)') } scope :ldap, -> { where(provider: 'ldap') } @@ -199,7 +199,7 @@ class User < ActiveRecord::Base end def by_username_or_id(name_or_id) - where('username = ? OR id = ?', name_or_id, name_or_id).first + where('users.username = ? OR users.id = ?', name_or_id, name_or_id.to_i).first end def build_user(attrs = {}, options= {}) @@ -377,7 +377,7 @@ class User < ActiveRecord::Base end def accessible_deploy_keys - DeployKey.in_projects(self.authorized_projects).uniq + DeployKey.in_projects(self.authorized_projects.pluck(:id)).uniq end def created_by @@ -413,4 +413,18 @@ class User < ActiveRecord::Base project.namespace != namespace && project.project_member(self) end + + # Reset project events cache related to this user + # + # Since we do cache @event we need to reset cache in special cases: + # * when the user changes their avatar + # Events cache stored like events/23-20130109142513. + # The cache key includes updated_at timestamp. + # Thus it will automatically generate a new fragment + # when the event is updated because the key changes. + def reset_events_cache + Event.where(author_id: self.id). + order('id DESC').limit(1000). + update_all(updated_at: Time.now) + end end diff --git a/app/models/web_hook.rb b/app/models/web_hook.rb index 3f22b1082f..c0aa373491 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 886d8b776f..1575cf0f19 100644 --- a/app/observers/issue_observer.rb +++ b/app/observers/issue_observer.rb @@ -1,18 +1,19 @@ class IssueObserver < BaseObserver def after_create(issue) notification.new_issue(issue, current_user) - issue.create_cross_references!(issue.project, current_user) + execute_hooks(issue) end def after_close(issue, transition) notification.close_issue(issue, current_user) - create_note(issue) + execute_hooks(issue) end def after_reopen(issue, transition) create_note(issue) + execute_hooks(issue) end def after_update(issue) @@ -21,6 +22,7 @@ class IssueObserver < BaseObserver end issue.notice_added_references(issue.project, current_user) + execute_hooks(issue) end protected @@ -29,4 +31,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 d70da514cd..0ac555fce7 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,17 +31,21 @@ 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) + execute_hooks(merge_request) end def after_update(merge_request) notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned? merge_request.notice_added_references(merge_request.project, current_user) + execute_hooks(merge_request) end def create_event(record, status) @@ -53,4 +57,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/users_project_observer.rb b/app/observers/users_project_observer.rb index ca9649c76a..93233898cc 100644 --- a/app/observers/users_project_observer.rb +++ b/app/observers/users_project_observer.rb @@ -1,8 +1,4 @@ class UsersProjectObserver < BaseObserver - def after_commit(users_project) - return if users_project.destroyed? - end - def after_create(users_project) Event.create( project_id: users_project.project.id, diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index e732b5fd1e..e54f88e42d 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -32,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 750a71aea6..eb42cac3f8 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/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb index 98794c9470..b122b6c865 100644 --- a/app/uploaders/attachment_uploader.rb +++ b/app/uploaders/attachment_uploader.rb @@ -3,6 +3,8 @@ class AttachmentUploader < CarrierWave::Uploader::Base storage :file + after :store, :reset_events_cache + def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end @@ -27,4 +29,8 @@ class AttachmentUploader < CarrierWave::Uploader::Base def file_storage? self.class.storage == CarrierWave::Storage::File end + + def reset_events_cache(file) + model.reset_events_cache if model.is_a?(User) + end end diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 9cd909611b..cc8e0c88ef 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -37,8 +37,7 @@ %p = link_to project.name_with_namespace, [:admin, project] %span.light.pull-right - = time_ago_in_words project.created_at - ago + #{time_ago_with_tooltip(project.created_at)} ago .span4 %h4 Latest users @@ -48,8 +47,7 @@ = link_to [:admin, user] do = user.name %span.light.pull-right - = time_ago_in_words user.created_at - ago + #{time_ago_with_tooltip(user.created_at)} ago .span4 %h4 Latest groups @@ -59,8 +57,7 @@ = link_to [:admin, group] do = group.name %span.light.pull-right - = time_ago_in_words group.created_at - ago + #{time_ago_with_tooltip(group.created_at)} ago %br .row @@ -126,3 +123,16 @@ GitLab Shell %span.pull-right = Gitlab::Shell.new.version + %p + GitLab API + %span.pull-right + = API::API::version + %p + Ruby + %span.pull-right + #{RUBY_VERSION}p#{RUBY_PATCHLEVEL} + + %p + Rails + %span.pull-right + #{Rails::VERSION::STRING} diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index c9d7c24f20..70098a442d 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 @@ -18,7 +18,7 @@ .clearfix .pull-right.prepend-top-10 = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-small" - = link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove" + = link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: "btn btn-small btn-remove" %h4 = link_to [:admin, group] do diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index ca51a57000..1de3d2e36f 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" @@ -72,5 +74,5 @@ = link_to user.name, admin_user_path(user) %span.pull-right.light = member.human_access - = link_to group_users_group_path(@group, member), confirm: remove_user_from_group_message(@group, user), method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do + = link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do %i.icon-minus.icon-white diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index 1bd7b8eac6..84546a7514 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -1,5 +1,5 @@ %h3.page-title - System Hooks + System hooks %p.light #{link_to "System hooks ", help_system_hooks_path, class: "vlink"} can be @@ -22,21 +22,14 @@ %hr -if @hooks.any? - %h3 - Hooks - %small (#{@hooks.count}) - %br - %table - %tr - %th URL - %th Method - %th - - @hooks.each do |hook| - %tr - %td + .ui-box + .title + System hooks (#{@hooks.count}) + %ul.well-list + - @hooks.each do |hook| + %li + .pull-right + = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-small" + = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-small" = link_to admin_hook_path(hook) do %strong= hook.url - = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-small pull-right" - %td POST - %td - = link_to 'Remove', admin_hook_path(hook), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove btn-small pull-right" diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index bc297209ae..d2f827e083 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,14 +46,14 @@ %ul.well-list - @projects.each do |project| %li - - if project.public - = public_icon - - else - = private_icon + %span{ class: visibility_level_color(project.visibility_level) } + = visibility_level_icon(project.visibility_level) = link_to project.name_with_namespace, [:admin, project] .pull-right + %span.label.label-gray + = repository_size(project) = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" - = link_to 'Destroy', [project], confirm: remove_project_message(project), method: :delete, class: "btn btn-small btn-remove" + = link_to 'Destroy', [project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-small btn-remove" - if @projects.blank? %p.nothing_here_message 0 projects matches = paginate @projects, theme: "gitlab" diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index c9c9c38d9f..5ac36a1acb 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -53,6 +53,11 @@ %strong = @repository.path_to_repo + %li + %span.light Size + %strong + = repository_size(@project) + %li %span.light last commit: %strong @@ -66,14 +71,10 @@ %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 @@ -88,9 +89,6 @@ .controls = f.submit 'Transfer', class: 'btn btn-primary' - - - .span6 - if @group .ui-box @@ -124,5 +122,5 @@ %span.light Owner - else %span.light= users_project.human_access - = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, remote: true, class: "btn btn-small btn-remove" do + = link_to project_team_member_path(@project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, remote: true, class: "btn btn-small btn-remove" do %i.icon-remove diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index 3f930c45fa..9ccbcd868c 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -68,7 +68,7 @@ = link_to 'Unblock User', unblock_admin_user_path(@user), method: :put, class: "btn btn-small" - else %p Blocked users will be removed from all projects & will not be able to login to GitLab. - = link_to 'Block User', block_admin_user_path(@user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-small btn-remove" + = link_to 'Block User', block_admin_user_path(@user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-small btn-remove" %fieldset %legend Profile .control-group diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index b32f0ae87c..5bc73d11f3 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]}"} @@ -53,6 +53,6 @@ - if user.blocked? = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn btn-small success" - else - = link_to 'Block', block_admin_user_path(user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-small btn-remove" - = link_to 'Destroy', [:admin, user], confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn btn-small btn-remove" + = link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-small btn-remove" + = link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All tickets linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: "btn btn-small btn-remove" = paginate @users, theme: "gitlab" diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 655ed5f7f7..5cfe4d553f 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -76,7 +76,7 @@ %li User will be removed from joined projects and groups %li Personal projects will be left %li Owned groups will be left - = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-new", confirm: 'Are you sure?' + = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-new", data: { confirm: 'Are you sure?' } - else .alert %h4 Block this user @@ -88,7 +88,7 @@ %li User will be removed from joined projects and groups %li Personal projects will be left %li Owned groups will be left - = link_to 'Block user', block_admin_user_path(@user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-remove" + = link_to 'Block user', block_admin_user_path(@user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put, class: "btn btn-remove" .alert.alert-error %h4 @@ -104,7 +104,7 @@ %li Next groups with all content will be removed: %strong #{@user.solo_owned_groups.map(&:name).join(', ')} - = link_to 'Remove user', [:admin, @user], confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn btn-remove" + = link_to 'Remove user', [:admin, @user], data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove" .span6 - if @user.users_groups.present? @@ -118,7 +118,7 @@ .pull-right %span.light= user_group.human_access - unless user_group.owner? - = link_to group_users_group_path(group, user_group), confirm: remove_user_from_group_message(group, @user), method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do + = link_to group_users_group_path(group, user_group), data: { confirm: remove_user_from_group_message(group, @user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do %i.icon-remove.icon-white .ui-box @@ -138,7 +138,7 @@ %span.light= tm.human_access - if tm.respond_to? :project - = link_to project_team_member_path(project, @user), confirm: remove_from_project_team_message(project, @user), remote: true, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from project' do + = link_to project_team_member_path(project, @user), data: { confirm: remove_from_project_team_message(project, @user) }, remote: true, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from project' do %i.icon-remove diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml index 904ac2d00a..96917c39b5 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,16 +64,14 @@ - @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 - = link_to leave_project_team_members_path(project), confirm: "Leave project?", method: :delete, remote: true, class: "btn-tiny btn remove-row", title: 'Leave project' do + = link_to leave_project_team_members_path(project), data: { confirm: "Leave project?"}, method: :delete, remote: true, class: "btn-tiny btn remove-row", title: 'Leave project' do %i.icon-signout Leave @@ -76,6 +82,10 @@ = link_to project.forked_from_project.name_with_namespace, project_path(project.forked_from_project) .project-info .pull-right + - if project.archived? + %span.label + %i.icon-archive + Archived - project.labels.each do |label| %span.label.label-info %i.icon-tag diff --git a/app/views/dashboard/show.js.haml b/app/views/dashboard/show.js.haml deleted file mode 100644 index 7e5a148e5e..0000000000 --- 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.haml b/app/views/devise/confirmations/new.html.haml index 387ec7676d..cf111e1f02 100644 --- a/app/views/devise/confirmations/new.html.haml +++ b/app/views/devise/confirmations/new.html.haml @@ -1,8 +1,14 @@ .login-box %h3.page-title Resend confirmation instructions = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| - = devise_error_messages! - = f.email_field :email, placeholder: 'Email' - %div= f.submit "Resend confirmation instructions", class: 'btn btn-success' + .devise-errors + = devise_error_messages! + = f.email_field :email, placeholder: 'Email', class: "text", required: true + .clearfix.append-bottom-10 + = f.submit "Resend confirmation instructions", class: 'btn btn-success' %hr - = link_to "Sign in", new_session_path(resource_name) + %p + %span.light + Already have login and password? + %strong + = link_to "Sign in", new_session_path(resource_name) diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb index 7b4fd52696..553d08369e 100644 --- a/app/views/devise/mailer/confirmation_instructions.html.erb +++ b/app/views/devise/mailer/confirmation_instructions.html.erb @@ -1,5 +1,9 @@ -

Welcome <%= @resource.email %>!

+

Welcome <%= @resource.name %>!

-

You can confirm your account through the link below:

+<% if @resource.unconfirmed_email.present? %> +

You can confirm your email (<%= @resource.unconfirmed_email %>) through the link below:

+<% else %> +

You can confirm your account through the link below:

+<% end %>

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @resource.confirmation_token) %>

diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index 3e4a4a0f1c..fb519e7d65 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -1,11 +1,12 @@ = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: "login-box" }) do |f| %h3 Change your password - = devise_error_messages! + .devise-errors + = devise_error_messages! = f.hidden_field :reset_password_token %div - = f.password_field :password, class: "text top", placeholder: "New password" + = f.password_field :password, class: "text top", placeholder: "New password", required: true %div - = f.password_field :password_confirmation, class: "text bottom", placeholder: "Confirm new password" + = f.password_field :password_confirmation, class: "text bottom", placeholder: "Confirm new password", required: true %div .clearfix.append-bottom-10 = f.submit "Change my password", class: "btn btn-primary" diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml index 3df65d037a..5d6df7d892 100644 --- a/app/views/devise/passwords/new.html.haml +++ b/app/views/devise/passwords/new.html.haml @@ -1,10 +1,13 @@ = form_for(resource, as: resource_name, url: password_path(resource_name), html: { class: "login-box", method: :post }) do |f| %h3.page-title Reset password - = devise_error_messages! - = f.email_field :email, placeholder: "Email", class: "text" - %br/ - %br/ - = f.submit "Reset password", class: "btn-primary btn" - .pull-right - = link_to "Sign in", new_session_path(resource_name), class: "btn" - %br/ + .devise-errors + = devise_error_messages! + = f.email_field :email, placeholder: "Email", class: "text", required: true + .clearfix.append-bottom-10 + = f.submit "Reset password", class: "btn-primary btn" + %hr + %p + %span.light + Already have login and password? + %strong + = link_to "Sign in", new_session_path(resource_name) diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb index 139acf28a9..b11817af95 100644 --- a/app/views/devise/registrations/edit.html.erb +++ b/app/views/devise/registrations/edit.html.erb @@ -23,6 +23,6 @@

Cancel my account

-

Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), confirm: "Are you sure?", method: :delete %>.

+

Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>.

<%= link_to "Back", :back %> diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index d749d7bac0..a3ac3e4356 100644 --- a/app/views/devise/registrations/new.html.haml +++ b/app/views/devise/registrations/new.html.haml @@ -1,7 +1,7 @@ = form_for(resource, as: resource_name, url: registration_path(resource_name), html: { class: "login-box" }) do |f| %h3.page-title Sign Up - %br - = devise_error_messages! + .devise-errors + = devise_error_messages! %div = f.text_field :name, class: "text top", placeholder: "Name", required: true %div @@ -15,5 +15,10 @@ %div = f.submit "Sign up", class: "btn-create btn" %hr - = link_to "Sign in", new_session_path(resource_name) - = link_to "Forgot your password?", new_password_path(resource_name), class: "pull-right" + %p + %span.light + Have an account? + %strong + = link_to "Sign in", new_session_path(resource_name) + %p + = link_to "Forgot your password?", new_password_path(resource_name) diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index 15854effbf..bb87d9ecb4 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -17,14 +17,20 @@ = render 'devise/sessions/oauth_providers' if devise_mapping.omniauthable? + %hr - if gitlab_config.signup_enabled - %hr - %div - Don't have an account? + %p + %span.light + Don't have an account? %strong = link_to "Sign up", new_registration_path(resource_name) + %p + %span.light Did not receive confirmation email? + = link_to "Send again", new_confirmation_path(resource_name) + + - if extra_config.has_key?('sign_in_text') %hr = markdown(extra_config.sign_in_text) diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index 892dacafa6..daed911678 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -1,10 +1,10 @@ - if event.proper? .event-item{class: "#{event.body? ? "event-block" : "event-inline" }"} %span.cgray.pull-right - #{time_ago_in_words(event.created_at)} ago. + #{time_ago_with_tooltip(event.created_at)} ago = cache event do - = image_tag avatar_icon(event.author_email), class: "avatar s24", alt:'' + = image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:'' - if event.push? = render "events/event/push", event: event diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml index de5634d3c5..adb331c2b2 100644 --- a/app/views/events/_event_last_push.html.haml +++ b/app/views/events/_event_last_push.html.haml @@ -5,9 +5,8 @@ %strong= truncate(event.ref_name, length: 28) at %strong= link_to_project event.project - %span - = time_ago_in_words(event.created_at) - ago. + #{time_ago_with_tooltip(event.created_at)} ago + .pull-right = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-create btn-small" do Create Merge Request diff --git a/app/views/events/_events.html.haml b/app/views/events/_events.html.haml new file mode 100644 index 0000000000..3d62d47886 --- /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 53f9322955..acd3902641 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -53,15 +53,12 @@ %ul.well-list - @group.projects.each do |project| %li - - if project.public - = public_icon - - else - = private_icon + = visibility_level_icon(project.visibility_level) = link_to project.name_with_namespace, project .pull-right = link_to 'Members', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" - = link_to 'Remove', project, confirm: remove_project_message(project), method: :delete, class: "btn btn-small btn-remove" + = link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-small btn-remove" - if @group.projects.blank? %p.nothing_here_message This group has no projects yet @@ -102,4 +99,4 @@ %p %strong Removed group can not be restored! - = link_to 'Remove Group', @group, confirm: 'Removed group can not be restored! Are you sure?', method: :delete, class: "btn btn-remove" + = link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove" diff --git a/app/views/groups/show.js.haml b/app/views/groups/show.js.haml deleted file mode 100644 index 7e5a148e5e..0000000000 --- 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 da917888ee..7928937c60 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 df35f41fc9..15e3bf3a13 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 c67402ee31..ba2df8c355 100644 --- a/app/views/help/public_access.html.haml +++ b/app/views/help/public_access.html.haml @@ -1,15 +1,46 @@ = render layout: 'help/layout' do %h3.page-title Public Access - %p - GitLab allows you to open selected projects to be accessed publicly. - These projects will be cloneable - %em without any - authentication. - Also they will be listed on the #{link_to "public access directory", public_root_path}. + %p.slead + GitLab allows you to open selected projects to be accessed + %strong publicly + or + %strong internally + \. + %br + Projects with either of these visibility levels will be listed in the #{link_to "public access directory", public_root_path}. + %br + Internal projects will only be available to authenticated users. + .clearfix + .dashboard-intro-icon + = public_icon + %h4 + Public projects + %p + Public project can be cloned + %strong without any + authentication. + %br + It will also be listed on the #{link_to "public access directory", public_root_path}. + %br + %strong Any logged in user + will have #{link_to "Guest", help_permissions_path} permissions on the repository. + + .clearfix + .dashboard-intro-icon + = internal_icon + %h4 + Internal projects + %p + Internal project can be cloned by any logged in user. + %br + It will also be listed on the #{link_to "public access directory", public_root_path} for logged in users. + %br + Any logged in user will have #{link_to "Guest", help_permissions_path} permissions on the repository. + + %h4 How to change project visibility %ol %li Go to your project dashboard %li Click on the "Edit" tab - %li Select "Public clone access" - + %li Change "Visibility Level" diff --git a/app/views/help/ssh.html.haml b/app/views/help/ssh.html.haml index 72a21b89ca..773e490ef8 100644 --- a/app/views/help/ssh.html.haml +++ b/app/views/help/ssh.html.haml @@ -5,18 +5,13 @@ SSH key allows you to establish a secure connection between your computer and GitLab %p.slead - To generate a new SSH key just open your terminal and use code below. + To generate a new SSH key just open your terminal and use code below. Press enter to accept the defaults when generating the key. %pre.dark ssh-keygen -t rsa -C "#{current_user.email}" - \# Creates a new ssh key using the provided email - \# Generating public/private rsa key pair... - %p.slead Next just use code below to dump your public key and add to GitLab SSH Keys %pre.dark cat ~/.ssh/id_rsa.pub - - \# ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6eNtGpNGwstc.... diff --git a/app/views/help/web_hooks.html.haml b/app/views/help/web_hooks.html.haml index 25865251de..7bde7fcc3d 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. - %br - GitLab will send POST request with commits information on every push. - %h5 Hooks request example: - = render "projects/hooks/data_ex" + %hr + %h4 Push events + %p.light + Triggered when you push to the repository except pushing tags. + %br + Request body: + = highlight_js do + :erb + { + "before": "95790bf891e76fee5e1747ab589903a6a1f80f22", + "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "ref": "refs/heads/master", + "user_id": 4, + "user_name": "John Smith", + "project_id": 15, + "repository": { + "name": "Diaspora", + "url": "git@localhost:diaspora.git", + "description": "", + "homepage": "http://localhost/diaspora", + }, + "commits": [ + { + "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", + "message": "Update Catalan translation to e38cb41.", + "timestamp": "2011-12-12T14:27:31+02:00", + "url": "http://localhost/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", + "author": { + "name": "Jordi Mallach", + "email": "jordi@softcatala.org", + } + }, + // ... + { + "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "message": "fixed readme", + "timestamp": "2012-01-03T23:36:29+02:00", + "url": "http://localhost/diaspora/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "author": { + "name": "GitLab dev user", + "email": "gitlabdev@dv6700.(none)", + }, + }, + ], + "total_commits_count": 4, + }; + + + %h4.prepend-top-20 Issues events + %p.light + Triggered when new issue created or existing issue was updated/closed/reopened. + %br + Request body: + = highlight_js do + :erb + { + "object_kind":"issue", + "object_attributes":{ + "id":301, + "title":"New API: create/update/delete file", + "assignee_id":51, + "author_id":51, + "project_id":14, + "created_at":"2013-12-03T17:15:43Z", + "updated_at":"2013-12-03T17:15:43Z", + "position":0, + "branch_name":null, + "description":"Create new API for manipulations with repository", + "milestone_id":null, + "state":"opened", + "iid":23 + } + } + %h4.prepend-top-20 Merge request events + %p.light + Triggered when new merge request created or existing merge request was updated/merged/closed. + %br + Request body: + = highlight_js do + :erb + { + "object_kind":"merge_request", + "object_attributes":{ + "id":99, + "target_branch":"master", + "source_branch":"ms-viewport", + "source_project_id":14, + "author_id":51, + "assignee_id":6, + "title":"MS-Viewport", + "created_at":"2013-12-03T17:23:34Z", + "updated_at":"2013-12-03T17:23:34Z", + "st_commits":null, + "st_diffs":null, + "milestone_id":null, + "state":"opened", + "merge_status":"unchecked", + "target_project_id":14, + "iid":1, + "description":"" + } + } diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml index 7d16a75af6..6a20dedf62 100644 --- a/app/views/layouts/_init_auto_complete.html.haml +++ b/app/views/layouts/_init_auto_complete.html.haml @@ -1,4 +1,4 @@ :javascript GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_project_path(@project)}" - GitLab.GfmAutoComplete.Emoji.assetBase = '#{image_path("emoji")}' + GitLab.GfmAutoComplete.Emoji.assetBase = "#{Gitlab.config.gitlab.relative_url_root + '/assets/emoji'}" GitLab.GfmAutoComplete.setup(); diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index e3f1f0623c..72110c44e4 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -11,7 +11,9 @@ GitLab is open source software to collaborate on code. %br #{link_to "Sign in", new_user_session_path} or browse for #{link_to "public projects", public_projects_path}. - %hr + %hr + .container + .content = yield %center.prepend-top-20 %h6 Enterprise Edition diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index 7462121deb..b546a9fa84 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -2,6 +2,7 @@ %html{ lang: "en"} = 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/profile.html.haml b/app/views/layouts/profile.html.haml index 30a0532bc2..7284963957 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 ea739da73d..6a10d6cf9e 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/public.html.haml b/app/views/layouts/public.html.haml index f922dcc420..8f490f61a9 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 cfe6a63055..1e8814134f 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/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml new file mode 100644 index 0000000000..d0b30c0833 --- /dev/null +++ b/app/views/notify/repository_push_email.html.haml @@ -0,0 +1,23 @@ +%h3 #{@author.name} pushed to #{@branch} at #{@project.name_with_namespace} + +%h4 Commits: + +%ul + - @commits.each do |commit| + %li + #{commit.short_id} - #{commit.title} + +%h4 Diff: +- @diffs.each do |diff| + %li + %strong + - if diff.old_path == diff.new_path + = diff.new_path + - elsif diff.new_path && diff.old_path + #{diff.old_path} → #{diff.new_path} + - else + = diff.new_path || diff.old_path + %hr + %pre + = diff.diff + %br diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml new file mode 100644 index 0000000000..6718ca6835 --- /dev/null +++ b/app/views/notify/repository_push_email.text.haml @@ -0,0 +1,20 @@ +#{@author.name} pushed to #{@branch} at #{@project.name_with_namespace} + +\ +Commits: +- @commits.each do |commit| + #{commit.short_id} - #{truncate(commit.title, length: 40)} +\ +\ +Diff: +- @diffs.each do |diff| + \ + \===================================== + - if diff.old_path == diff.new_path + = diff.new_path + - elsif diff.new_path && diff.old_path + #{diff.old_path} → #{diff.new_path} + - else + = diff.new_path || diff.old_path + \===================================== + = diff.diff diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index b76339027a..0b1ccee9c3 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -24,7 +24,7 @@ %p.cgray - if current_user.private_token = text_field_tag "token", current_user.private_token, class: "input-xlarge input-xpadding pull-left" - = f.submit 'Reset', confirm: "Are you sure?", class: "btn btn-primary btn-build-token prepend-left-10" + = f.submit 'Reset', data: { confirm: "Are you sure?" }, class: "btn btn-primary btn-build-token prepend-left-10" - else %span You don`t have one yet. Click generate to fix it. = f.submit 'Generate', class: "btn success btn-build-token" @@ -70,4 +70,4 @@ %li The following groups will be abandoned. You should transfer or remove them: %strong #{current_user.solo_owned_groups.map(&:name).join(', ')} - = link_to 'Delete account', user_registration_path, confirm: "REMOVE #{current_user.name}? Are you sure?", method: :delete, class: "btn btn-remove" + = link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove" diff --git a/app/views/profiles/groups/index.html.haml b/app/views/profiles/groups/index.html.haml index 6fc27be5db..1d24e636bf 100644 --- a/app/views/profiles/groups/index.html.haml +++ b/app/views/profiles/groups/index.html.haml @@ -22,7 +22,7 @@ %i.icon-cogs Settings - = link_to leave_profile_group_path(group), confirm: "Are you sure you want to leave #{group.name} group?", method: :delete, class: "btn-small btn grouped", title: 'Remove user from group' do + = link_to leave_profile_group_path(group), data: { confirm: "Are you sure you want to leave #{group.name} group?"}, method: :delete, class: "btn-small btn grouped", title: 'Remove user from group' do %i.icon-signout Leave diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml index d0a3fe32c3..23f8de1dff 100644 --- a/app/views/profiles/keys/_key.html.haml +++ b/app/views/profiles/keys/_key.html.haml @@ -4,8 +4,6 @@ %span (#{key.fingerprint}) %span.cgray - added - = time_ago_in_words(key.created_at) - ago + added #{time_ago_with_tooltip(key.created_at)} ago - = link_to 'Remove', profile_key_path(key), confirm: 'Are you sure?', method: :delete, class: "btn btn-small btn-remove delete-key pull-right" + = link_to 'Remove', profile_key_path(key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-small btn-remove delete-key pull-right" diff --git a/app/views/profiles/keys/show.html.haml b/app/views/profiles/keys/show.html.haml index 2852c338af..a0a315a5b7 100644 --- a/app/views/profiles/keys/show.html.haml +++ b/app/views/profiles/keys/show.html.haml @@ -19,4 +19,4 @@ = @key.key .pull-right - = link_to 'Remove', profile_key_path(@key), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove delete-key" + = link_to 'Remove', profile_key_path(@key), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key" diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index d382d2d70c..ca3f824722 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -8,7 +8,7 @@ -= form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit_user form-horizontal" } do |f| += form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit_user form-horizontal" }, authenticity_token: true do |f| -if @user.errors.any? %div.alert.alert-error %ul @@ -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, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar" .form-actions = f.submit 'Save changes', class: "btn btn-save" diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml new file mode 100644 index 0000000000..8703b264f3 --- /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 diff --git a/app/views/projects/_visibility_level.html.haml b/app/views/projects/_visibility_level.html.haml new file mode 100644 index 0000000000..7fc257811c --- /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/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 4372647a41..b41942baf4 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -18,7 +18,7 @@ Compare - if can?(current_user, :admin_project, @project) && branch.name != @repository.root_ref - = link_to project_branch_path(@project, branch.name), class: 'btn grouped btn-small remove-row', method: :delete, confirm: 'Removed branch cannot be restored. Are you sure?', remote: true do + = link_to project_branch_path(@project, branch.name), class: 'btn grouped btn-small remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do %i.icon-trash %p @@ -27,6 +27,4 @@ = image_tag avatar_icon(commit.author_email), class: "avatar s16", alt: '' %span.light = gfm escape_once(truncate(commit.title, length: 40)) - %span - = time_ago_in_words(commit.committed_date) - ago + #{time_ago_with_tooltip(commit.committed_date)} ago diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 111446db58..1dff75f51c 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -23,16 +23,14 @@ %span.light Authored by %strong = commit_author_link(@commit, avatar: true, size: 24) - %time{title: @commit.authored_date.stamp("Aug 21, 2011 9:23pm")} - #{time_ago_in_words(@commit.authored_date)} ago + #{time_ago_with_tooltip(@commit.authored_date)} ago - if @commit.different_committer? .commit-info-row %span.light Committed by %strong = commit_committer_link(@commit, avatar: true, size: 24) - %time{title: @commit.committed_date.stamp("Aug 21, 2011 9:23pm")} - #{time_ago_in_words(@commit.committed_date)} ago + #{time_ago_with_tooltip(@commit.committed_date)} ago .commit-info-row %span.cgray= pluralize(@commit.parents.count, "parent") diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index d70fd96acc..658b23fbcf 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -7,13 +7,11 @@ .notes_count - notes = project.notes.for_commit_id(commit.id) - if notes.any? - %span.badge.badge-info + %span.label.label-gray %i.icon-comment = notes.count .commit-row-info = commit_author_link(commit, avatar: true, size: 16) - %time.committed_ago{ datetime: commit.committed_date, title: commit.committed_date.stamp("Aug 21, 2011 9:23pm") } - = time_ago_in_words(commit.committed_date) - ago -   + .committed_ago + #{time_ago_with_tooltip(commit.committed_date)} ago   diff --git a/app/views/projects/commits/_diffs.html.haml b/app/views/projects/commits/_diffs.html.haml index f23403a50c..2a32f56e8f 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 @@ -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/_inline_commit.html.haml b/app/views/projects/commits/_inline_commit.html.haml index 5be8460e06..2129a45b9a 100644 --- a/app/views/projects/commits/_inline_commit.html.haml +++ b/app/views/projects/commits/_inline_commit.html.haml @@ -3,7 +3,4 @@ = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id"   = link_to_gfm truncate(commit.title, length: 40), project_commit_path(project, commit.id), class: "commit-row-message" - %time.committed_ago{ datetime: commit.committed_date, title: commit.committed_date.stamp("Aug 21, 2011 9:23pm") } - = time_ago_in_words(commit.committed_date) - ago -   + #{time_ago_with_tooltip(commit.committed_date)} ago   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 0000000000..3234e9da0a --- /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 c724213878..c827d96d85 100644 --- a/app/views/projects/commits/_text_file.html.haml +++ b/app/views/projects/commits/_text_file.html.haml @@ -21,3 +21,4 @@ - comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at) - unless comments.empty? = render "projects/notes/diff_notes_with_reply", notes: comments, line: line + diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder index 27c8fa6da7..32c82edb24 100644 --- a/app/views/projects/commits/show.atom.builder +++ b/app/views/projects/commits/show.atom.builder @@ -3,7 +3,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.title "Recent commits to #{@project.name}:#{@ref}" xml.link :href => project_commits_url(@project, @ref, format: :atom), :rel => "self", :type => "application/atom+xml" xml.link :href => project_commits_url(@project, @ref), :rel => "alternate", :type => "text/html" - xml.id project_commits_url(@project) + xml.id project_commits_url(@project, @ref) xml.updated @commits.first.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ") if @commits.any? @commits.each do |commit| diff --git a/app/views/projects/commits/show.js.haml b/app/views/projects/commits/show.js.haml deleted file mode 100644 index 045c9dd83d..0000000000 --- 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/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml index 45f80ecd55..1c59dd699c 100644 --- a/app/views/projects/deploy_keys/_deploy_key.html.haml +++ b/app/views/projects/deploy_keys/_deploy_key.html.haml @@ -10,7 +10,7 @@ %i.icon-off Disable - else - = link_to 'Remove', project_deploy_key_path(@project, deploy_key), confirm: 'You are going to remove deploy key. Are you sure?', method: :delete, class: "btn btn-remove delete-key btn-small pull-right" + = link_to 'Remove', project_deploy_key_path(@project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :delete, class: "btn btn-remove delete-key btn-small pull-right" = link_to project_deploy_key_path(deploy_key.projects.include?(@project) ? @project : deploy_key.projects.first, deploy_key) do @@ -21,5 +21,4 @@ - deploy_key.projects.map(&:name_with_namespace).each do |project_name| %span.label= project_name %small.pull-right - Created #{time_ago_in_words(deploy_key.created_at)} ago - + Created #{time_ago_with_tooltip(deploy_key.created_at)} ago diff --git a/app/views/projects/deploy_keys/show.html.haml b/app/views/projects/deploy_keys/show.html.haml index 49566f83d2..67615e1781 100644 --- a/app/views/projects/deploy_keys/show.html.haml +++ b/app/views/projects/deploy_keys/show.html.haml @@ -10,4 +10,4 @@ %hr %pre= @key.key .pull-right - = link_to 'Remove', project_deploy_key_path(@project, @key), confirm: 'Are you sure?', method: :delete, class: "btn-remove btn delete-key" + = link_to 'Remove', project_deploy_key_path(@project, @key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn-remove btn delete-key" diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 3afc607d90..c56919e792 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 @@ -113,6 +98,33 @@ %i.icon-chevron-down .js-toggle-visibility-container.hide + - if can? current_user, :archive_project, @project + .ui-box.ui-box-danger + .title + - if @project.archived? + Unarchive project + - else + Archive project + .ui-box-body + - if @project.archived? + %p + Unarchiving the project will mark its repository as active. + %br + The project can be committed to. + %br + %strong Once active this project shows up in the search and on the dashboard. + = link_to 'Unarchive', unarchive_project_path(@project), confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be comitted to again.", method: :post, class: "btn btn-remove" + - else + %p + Archiving the project will mark its repository as read-only. + %br + It is hidden from the dashboard and doesn't show up in searches. + %br + %strong Archived projects cannot be committed to! + = link_to 'Archive', archive_project_path(@project), confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to.", method: :post, class: "btn btn-remove" + - else + %p.nothing_here_message Only the project owner can archive a project + - if can?(current_user, :change_namespace, @project) .ui-box.ui-box-danger .title Transfer project @@ -162,7 +174,7 @@ %br %strong Removed projects cannot be restored! - = link_to 'Remove project', @project, confirm: remove_project_message(@project), method: :delete, class: "btn btn-remove" + = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project) }, method: :delete, class: "btn btn-remove" - else %p.nothing_here_message Only project owner can remove a project diff --git a/app/views/projects/edit_tree/show.html.haml b/app/views/projects/edit_tree/show.html.haml index b1fb4f8637..9bb3eefe0c 100644 --- a/app/views/projects/edit_tree/show.html.haml +++ b/app/views/projects/edit_tree/show.html.haml @@ -11,7 +11,7 @@ %strong= @ref %span.options .btn-group.tree-btn-group - = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-tiny btn-cancel", confirm: leave_edit_message + = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-tiny btn-cancel", data: { confirm: leave_edit_message } .file-content.code %pre#editor= @blob.data @@ -28,7 +28,7 @@ .message to branch %strong= @ref - = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-cancel", confirm: leave_edit_message + = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-cancel", data: { confirm: leave_edit_message} :javascript ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict") diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 04fc0c3173..15cf150460 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 @@ -32,7 +29,7 @@ touch README git add README git commit -m 'first commit' - %span.clone= "git remote add origin #{@project.url_to_repo}" + %span.clone= "git remote add origin #{default_url_to_repo}" :preserve git push -u origin master @@ -41,10 +38,10 @@ %pre.dark :preserve cd existing_git_repo - %span.clone= "git remote add origin #{@project.url_to_repo}" + %span.clone= "git remote add origin #{default_url_to_repo}" :preserve git push -u origin master - if can? current_user, :remove_project, @project .prepend-top-20 - = link_to 'Remove project', @project, confirm: remove_project_message(@project), method: :delete, class: "btn btn-remove pull-right" + = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right" diff --git a/app/views/projects/hooks/_data_ex.html.erb b/app/views/projects/hooks/_data_ex.html.erb deleted file mode 100644 index 5092aaf675..0000000000 --- a/app/views/projects/hooks/_data_ex.html.erb +++ /dev/null @@ -1,44 +0,0 @@ -<% data_ex_str = < -
- <%= raw Pygments::Lexer[:js].highlight(data_ex_str) %> -
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml index f748eb2929..2ac67140bd 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" + = link_to 'Remove', project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-small grouped" + .clearfix + %span.monospace= hook.url + %p + - %w(push_events issues_events merge_requests_events).each do |trigger| + - if hook.send(trigger) + %span.label.label-gray= trigger.titleize diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index 05cfb2788b..d56009fac6 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -29,7 +29,7 @@ = f.label :milestone_id do %i.icon-time Milestone - .controls= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'}) + .controls= f.select(:milestone_id, @project.milestones.active.collect {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'}) .ui-box-bottom .control-group diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml index 438cc02b47..a44db78a92 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/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index effad718c8..57a05ee730 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -26,7 +26,7 @@ %i.icon-time = issue.milestone.title .pull-right - %small updated #{time_ago_in_words(issue.updated_at)} ago + %small updated #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_update_ago')} ago .issue-labels - issue.labels.each do |label| diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index 427d653313..e2ce26feac 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 81594b4972..3694798a74 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 (#{@issues.total_count}) - .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 1be6a64f53..0000000000 --- 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 d08a8af2bb..36ea57805a 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 c78be4965d..6792cfab36 100644 --- a/app/views/projects/merge_requests/_form.html.haml +++ b/app/views/projects/merge_requests/_form.html.haml @@ -10,7 +10,7 @@ .span5 .clearfix .pull-left - = f.select(:source_project_id,[[@merge_request.source_project.path_with_namespace,@merge_request.source_project.id]] , {}, {class: 'source_project chosen span3'}) + = f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project chosen span3', disabled: @merge_request.persisted? }) .pull-left   = f.select(:source_branch, @merge_request.source_project.repository.branch_names, { include_blank: "Select branch" }, {class: 'source_branch chosen span2'}) @@ -22,7 +22,7 @@ .clearfix .pull-left - projects = @project.forked_from_project.nil? ? [@project] : [ @project,@project.forked_from_project] - = f.select(:target_project_id, projects.map { |proj| [proj.path_with_namespace,proj.id] }, {include_blank: "Select Target Project" }, {class: 'target_project chosen span3'}) + = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace'), {}, { class: 'target_project chosen span3', disabled: @merge_request.persisted? }) .pull-left   = f.select(:target_branch, @target_branches, { include_blank: "Select branch" }, {class: 'target_branch chosen span2'}) @@ -44,7 +44,7 @@ = f.label :milestone_id do %i.icon-time Milestone - .controls= f.select(:milestone_id, @project.milestones.active.all.map {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'}) + .controls= f.select(:milestone_id, @project.milestones.active.map {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'}) .control-group = f.label :description, "Description" .controls diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 933d78bcbf..648d0ed07f 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -10,7 +10,7 @@ %span.pull-right - if merge_request.for_fork? %span.light - = "#{merge_request.source_project.path_with_namespace}" + = "#{merge_request.source_project_path}" = "#{merge_request.source_branch}" %i.icon-angle-right.light = "#{merge_request.target_branch}" @@ -34,4 +34,4 @@ .pull-right - %small updated #{time_ago_in_words(merge_request.updated_at)} ago + %small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')} ago diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 47c14abded..0b9e4df3fd 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -2,7 +2,11 @@ = render "projects/merge_requests/show/mr_title" = render "projects/merge_requests/show/how_to_merge" = render "projects/merge_requests/show/mr_box" - = render "projects/merge_requests/show/mr_accept" + - if @merge_request.opened? + - if @merge_request.source_branch_exists? && @merge_request.target_branch_exists? + = render "projects/merge_requests/show/mr_accept" + - else + = render "projects/merge_requests/show/no_accept" - if @merge_request.source_project.gitlab_ci? = render "projects/merge_requests/show/mr_ci" = render "projects/merge_requests/show/commits" 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 923b1ea032..0000000000 --- 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 2964f0ec46..0000000000 --- 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/invalid.html.haml b/app/views/projects/merge_requests/invalid.html.haml index c962811a3e..16e9fee42a 100644 --- a/app/views/projects/merge_requests/invalid.html.haml +++ b/app/views/projects/merge_requests/invalid.html.haml @@ -3,15 +3,21 @@ = render "projects/merge_requests/show/mr_box" .alert.alert-error - %h5 - %i.icon-exclamation-sign - We cannot find - %span.label-branch= @merge_request.source_branch - or - %span.label-branch= @merge_request.target_branch - branches in the repository. - %p - Maybe it was removed or never pushed. %p + We cannot render this merge request properly because + - if @merge_request.for_fork? && !@merge_request.source_project + fork project was removed + - elsif !@merge_request.source_branch_exists? + %span.label.label-inverse= @merge_request.source_branch + does not exist in + %span.label.label-info= @merge_request.source_project_path + - elsif !@merge_request.target_branch_exists? + %span.label.label-inverse= @merge_request.target_branch + does not exist in + %span.label.label-info= @merge_request.target_project_path + - else + of internal error + + %strong Please close Merge Request or change branches with existing one diff --git a/app/views/projects/merge_requests/show.js.haml b/app/views/projects/merge_requests/show.js.haml deleted file mode 100644 index 2ce6eb6329..0000000000 --- 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/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml index 030ac285f2..98d373e8ad 100644 --- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml +++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml @@ -10,7 +10,7 @@ %strong Step 1. Checkout target branch and get recent objects from GitLab Assuming remote for #{@merge_request.target_project.path_with_namespace} is called #{target_remote} - remote for #{@merge_request.source_project.path_with_namespace} is called #{source_remote} + remote for #{@merge_request.source_project_path} is called #{source_remote} %pre.dark :preserve git checkout #{target_remote} #{@merge_request.target_branch} 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 b1c73b439c..b85a6ec996 100644 --- a/app/views/projects/merge_requests/show/_mr_box.html.haml +++ b/app/views/projects/merge_requests/show/_mr_box.html.haml @@ -34,13 +34,13 @@ %span %i.icon-remove Closed by #{link_to_member(@project, @merge_request.closed_event.author)} - %span #{time_ago_in_words(@merge_request.closed_event.created_at)} ago. + #{time_ago_with_tooltip(@merge_request.closed_event.created_at)} ago. - if @merge_request.merged? .ui-box-bottom.alert-success %span %i.icon-ok Merged by #{link_to_member(@project, @merge_request.merge_event.author)} - #{time_ago_in_words(@merge_request.merge_event.created_at)} ago. + #{time_ago_with_tooltip(@merge_request.merge_event.created_at)} ago. - if !@closes_issues.empty? && @merge_request.opened? .ui-box-bottom.alert-info %span diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index 096a916764..456101fb5e 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -3,7 +3,7 @@   -if @merge_request.for_fork? %span.label-branch - %span.label-project= truncate(@merge_request.source_project.path_with_namespace, length: 25) + %span.label-project= truncate(@merge_request.source_project_path, length: 25) #{@merge_request.source_branch} → %span.label-branch= @merge_request.target_branch @@ -24,7 +24,7 @@ %li= link_to "Email Patches", project_merge_request_path(@project, @merge_request, format: :patch) %li= link_to "Plain Diff", project_merge_request_path(@project, @merge_request, format: :diff) - = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn grouped btn-close", title: "Close merge request" + = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: { state_event: :close }), method: :put, class: "btn grouped btn-close", title: "Close merge request" = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn grouped", id:"edit_merge_request" do %i.icon-edit diff --git a/app/views/projects/merge_requests/show/_no_accept.html.haml b/app/views/projects/merge_requests/show/_no_accept.html.haml new file mode 100644 index 0000000000..a0507b24ad --- /dev/null +++ b/app/views/projects/merge_requests/show/_no_accept.html.haml @@ -0,0 +1,12 @@ +.alert.alert-error + %p + This merge request can not be accepted because branch + - unless @merge_request.source_branch_exists? + %span.label.label-inverse= @merge_request.source_branch + does not exist in + %span.label.label-info= @merge_request.source_project_path + - else + %span.label.label-inverse= @merge_request.target_branch + does not exist in + %span.label.label-info= @merge_request.target_project_path + %strong Please close this merge request or change branches with existing one diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 466a63dee8..ee6c42b6ea 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/new_tree/show.html.haml b/app/views/projects/new_tree/show.html.haml index d5525303d7..327aac0d71 100644 --- a/app/views/projects/new_tree/show.html.haml +++ b/app/views/projects/new_tree/show.html.haml @@ -33,7 +33,7 @@ .message to branch %strong= @ref - = link_to "Cancel", project_tree_path(@project, @id), class: "btn btn-cancel", confirm: leave_edit_message + = link_to "Cancel", project_tree_path(@project, @id), class: "btn btn-cancel", data: { confirm: leave_edit_message} :javascript ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict") 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 0000000000..936dbb354c --- /dev/null +++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml @@ -0,0 +1,34 @@ +- note1 = notes1.first # example note +- note2 = notes2.first # example note +%tr.notes_holder + -# Check if line want not changed since comment was left + /- if !defined?(line1) || line1 == note1.diff_line + - if note1 + %td.notes_content + %ul.notes{ rel: note1.discussion_id } + = render notes1 + = render "projects/notes/discussion_reply_button", note: note1 + %td.notes_line2 + %span.btn.disabled.parallel-comment + %i.icon-comment + = notes1.count + - else + %td= "" + %td= "" + + %td= "" + + -# Check if line want not changed since comment was left + /- if !defined?(line2) || line2 == note2.diff_line + - if note2 + %td.notes_line + %span.btn.disabled.parallel-comment + %i.icon-comment + = notes2.count + %td.notes_content + %ul.notes{ rel: note2.discussion_id } + = render notes2 + = render "projects/notes/discussion_reply_button", note: note2 + - else + %td= "" + %td= "" diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml index e0aad0cc95..527dd8b7fc 100644 --- a/app/views/projects/notes/_discussion.html.haml +++ b/app/views/projects/notes/_discussion.html.haml @@ -32,8 +32,7 @@ last updated by = link_to_member(@project, last_note.author, avatar: false) %span.discussion-last-update - = time_ago_in_words(last_note.updated_at) - ago + #{time_ago_with_tooltip(last_note.updated_at, 'bottom', 'discussion_updated_ago')} ago .discussion-body - if note.for_diff_line? - if note.active? diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index 7add292183..0572e4f6fb 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project, @note], remote: true, html: { multipart: true, id: nil, class: "new_note js-new-note-form common-note-form" } do |f| += form_for [@project, @note], remote: true, html: { multipart: true, id: nil, class: "new_note js-new-note-form common-note-form" }, authenticity_token: true do |f| = note_target_fields = f.hidden_field :commit_id @@ -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.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/_note.html.haml b/app/views/projects/notes/_note.html.haml index e56e9153c2..e2de98b3ba 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -10,7 +10,7 @@ %i.icon-edit Edit   - = link_to project_note_path(@project, note), title: "Remove comment", method: :delete, confirm: 'Are you sure you want to remove this comment?', remote: true, class: "danger js-note-delete" do + = link_to project_note_path(@project, note), title: "Remove comment", method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: "danger js-note-delete" do %i.icon-trash.cred Remove = image_tag avatar_icon(note.author_email), class: "avatar s32" @@ -61,6 +61,6 @@ %i.icon-paper-clip = note.attachment_identifier = link_to delete_attachment_project_note_path(@project, note), - title: "Delete this attachment", method: :delete, remote: true, confirm: 'Are you sure you want to remove the attachment?', class: "danger js-note-attachment-delete" do + title: "Delete this attachment", method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: "danger js-note-attachment-delete" do %i.icon-trash.cred .clear diff --git a/app/views/projects/notes/index.js.haml b/app/views/projects/notes/index.js.haml deleted file mode 100644 index 6c4ed20349..0000000000 --- a/app/views/projects/notes/index.js.haml +++ /dev/null @@ -1,4 +0,0 @@ -- unless @notes.blank? - var notesHtml = "#{escape_javascript(render 'projects/notes/notes')}"; - - new_note_ids = @notes.map(&:id) - NoteList.setContent(#{new_note_ids}, notesHtml); diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index 8930ec4b30..31ef281ab8 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -39,15 +39,13 @@ %i.icon-lock .pull-right - if can? current_user, :admin_project, @project - = link_to 'Unprotect', [@project, branch], confirm: 'Branch will be writable for developers. Are you sure?', method: :delete, class: "btn btn-remove btn-small" + = link_to 'Unprotect', [@project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-small" - if commit = branch.commit = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do = commit.short_id %span.light = gfm escape_once(truncate(commit.title, length: 40)) - %span - = time_ago_in_words(commit.committed_date) - ago + #{time_ago_with_tooltip(commit.committed_date)} ago - else (branch was removed from repository) diff --git a/app/views/projects/refs/logs_tree.js.haml b/app/views/projects/refs/logs_tree.js.haml index 213c54f5f7..2c8fc4dff2 100644 --- a/app/views/projects/refs/logs_tree.js.haml +++ b/app/views/projects/refs/logs_tree.js.haml @@ -5,5 +5,5 @@ :plain var row = $("table.table_#{@hex_path} tr.file_#{hexdigest(file_name)}"); - row.find("td.tree_time_ago").html('#{escape_javascript time_ago_in_words(commit.committed_date)} ago'); + row.find("td.tree_time_ago").html('#{escape_javascript time_ago_with_tooltip(commit.committed_date)} ago'); row.find("td.tree_commit").html('#{escape_javascript render("projects/tree/tree_commit_column", commit: commit)}'); diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index 202bf32721..34ba55fc4c 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -33,6 +33,8 @@ .controls - if type == 'text' = f.text_field name, class: "input-xlarge", placeholder: placeholder + - elsif type == 'textarea' + = f.text_area name, rows: 5, class: "input-xxlarge", placeholder: placeholder - elsif type == 'checkbox' = f.check_box name diff --git a/app/views/projects/services/index.html.haml b/app/views/projects/services/index.html.haml index 190aa69dab..7271dd830c 100644 --- a/app/views/projects/services/index.html.haml +++ b/app/views/projects/services/index.html.haml @@ -1,9 +1,9 @@ -%h3.page-title Services -%p.light Services allow you to integrate GitLab with other applications +%h3.page-title Project services +%p.light Project services allow you to integrate GitLab with other applications %hr %ul.bordered-list - - @services.each do |service| + - @services.sort_by(&:title).each do |service| %li %h4 = link_to edit_project_service_path(@project, service.to_param) do diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 5e5b316d72..d2ced59f2c 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 @@ -36,6 +8,14 @@ .loading.hide .span3.project-side .clearfix + - if @project.archived? + .alert + %h5 + %i.icon-warning-sign + Archived project! + %p Repository is read-only + + - if @project.forked_from_project .alert.alert-success %i.icon-code-fork.project-fork-icon @@ -59,6 +39,12 @@ %span Download = 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 diff --git a/app/views/projects/show.js.haml b/app/views/projects/show.js.haml deleted file mode 100644 index 511f278929..0000000000 --- a/app/views/projects/show.js.haml +++ /dev/null @@ -1,2 +0,0 @@ -:plain - Pager.append(#{@events.count}, "#{escape_javascript(render(@events))}"); diff --git a/app/views/projects/snippets/_form.html.haml b/app/views/projects/snippets/_form.html.haml index d414ee2d1e..198ec2170c 100644 --- a/app/views/projects/snippets/_form.html.haml +++ b/app/views/projects/snippets/_form.html.haml @@ -34,7 +34,7 @@ = link_to "Cancel", project_snippets_path(@project), class: "btn btn-cancel" - unless @snippet.new_record? - = link_to 'Remove snippet', project_snippet_path(@project, @snippet), confirm: 'Are you sure?', method: :delete, class: "btn pull-right btn-remove delete-snippet prepend-left-10", id: "destroy_snippet_#{@snippet.id}" + = link_to 'Remove snippet', project_snippet_path(@project, @snippet), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn pull-right btn-remove delete-snippet prepend-left-10", id: "destroy_snippet_#{@snippet.id}" :javascript var editor = ace.edit("editor"); diff --git a/app/views/projects/snippets/_snippet.html.haml b/app/views/projects/snippets/_snippet.html.haml index 6185e35cf6..79de35d0b1 100644 --- a/app/views/projects/snippets/_snippet.html.haml +++ b/app/views/projects/snippets/_snippet.html.haml @@ -18,4 +18,5 @@ by = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16" = snippet.author_name - %span.light #{time_ago_in_words(snippet.created_at)} ago + %span.light + #{time_ago_with_tooltip(snippet.created_at)} ago diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 5361517b2f..21d4c54685 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -24,8 +24,7 @@ .pull-right %small.cdark %i.icon-calendar - = time_ago_in_words(commit.committed_date) - ago + #{time_ago_with_tooltip(commit.committed_date)} ago %p.prepend-left-20 = link_to commit.short_id(8), project_commit_path(@project, commit), class: "monospace" – @@ -37,7 +36,7 @@ %i.icon-download-alt Download - if can?(current_user, :admin_project, @project) - = link_to project_tag_path(@project, tag.name), class: 'btn btn-small remove-row', method: :delete, confirm: 'Removed tag cannot be restored. Are you sure?', remote: true do + = link_to project_tag_path(@project, tag.name), class: 'btn btn-small remove-row', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do %i.icon-trash = paginate @tags, theme: 'gitlab' diff --git a/app/views/projects/team_members/_team_member.html.haml b/app/views/projects/team_members/_team_member.html.haml index 5c93e6e3ea..d93bb44ab9 100644 --- a/app/views/projects/team_members/_team_member.html.haml +++ b/app/views/projects/team_members/_team_member.html.haml @@ -7,7 +7,7 @@ = form_for(member, as: :team_member, url: project_team_member_path(@project, member.user)) do |f| = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2 trigger-submit"   - = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do + = link_to project_team_member_path(@project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do %i.icon-minus.icon-white = image_tag avatar_icon(user.email, 32), class: "avatar s32" %p diff --git a/app/views/projects/walls/show.html.haml b/app/views/projects/walls/show.html.haml index 88aecee081..4fe3e6bccf 100644 --- a/app/views/projects/walls/show.html.haml +++ b/app/views/projects/walls/show.html.haml @@ -3,7 +3,7 @@ - if can? current_user, :write_note, @project .note-form-holder - = form_for [@project, @note], remote: true, html: { multipart: true, id: nil, class: "new_note wall-note-form" } do |f| + = form_for [@project, @note], remote: true, html: { multipart: true, id: nil, class: "new_note wall-note-form" }, authenticity_token: true do |f| = note_target_fields .note_text_and_preview = f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input turn-on' diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 16061c9dcb..3b5f80e528 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project, @wiki] do |f| += form_for [@project, @wiki], method: @wiki.persisted? ? :put : :post do |f| -if @wiki.errors.any? #error_explanation %h2= "#{pluralize(@wiki.errors.count, "error")} prohibited this wiki from being saved:" @@ -25,11 +25,11 @@ .ui-box-bottom .control-group = f.label :content - .controls= f.text_area :content, class: 'span8 js-gfm-input' + .controls= f.text_area :content, class: 'span8 js-gfm-input', rows: 18 .ui-box-bottom .control-group = f.label :commit_message - .controls= f.text_field :message, class: 'span8' + .controls= f.text_field :message, class: 'span8', rows: 18 .form-actions - if @wiki && @wiki.persisted? = f.submit 'Save changes', class: "btn-save btn" diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml index 66be82777c..1400f2ec78 100644 --- a/app/views/projects/wikis/edit.html.haml +++ b/app/views/projects/wikis/edit.html.haml @@ -6,5 +6,5 @@ .pull-right - if @wiki.persisted? && can?(current_user, :admin_wiki, @project) - = link_to project_wiki_path(@project, @wiki), confirm: "Are you sure you want to delete this page?", method: :delete, class: "btn btn-small btn-remove" do + = link_to project_wiki_path(@project, @wiki), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-small btn-remove" do Delete this page diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml index 344a5e88e6..cb79540807 100644 --- a/app/views/projects/wikis/history.html.haml +++ b/app/views/projects/wikis/history.html.haml @@ -23,8 +23,7 @@ %td = commit.title %td - = time_ago_in_words(version.date) - ago + #{time_ago_with_tooltip(version.date)} ago %td %strong = @wiki.page.wiki.page(@wiki.page.name, commit.id).try(:format) diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml index 59d104a985..4df406c421 100644 --- a/app/views/projects/wikis/pages.html.haml +++ b/app/views/projects/wikis/pages.html.haml @@ -8,4 +8,4 @@ = link_to wiki_page.title.titleize, project_wiki_path(@project, wiki_page) %small (#{wiki_page.format}) .pull-right - %small Last edited #{time_ago_in_words(wiki_page.commit.created_at)} ago + %small Last edited #{time_ago_with_tooltip(wiki_page.commit.created_at)} ago diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index 63e7f12b7f..0ec989238e 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -12,4 +12,4 @@ = preserve do = render_wiki_content(@wiki) -%p.time Last edited by #{commit_author_link(@wiki.commit, avatar: true, size: 16)} #{time_ago_in_words @wiki.commit.created_at} ago +%p.span Last edited by #{commit_author_link(@wiki.commit, avatar: true, size: 16)} #{time_ago_with_tooltip(@wiki.commit.created_at)} ago diff --git a/app/views/public/projects/index.html.haml b/app/views/public/projects/index.html.haml index 21aee64457..b88169add3 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 ac7b9ee7f2..2ae3e6d97f 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 #{ 'active' if default_clone_protocol == 'ssh' }", :"data-clone" => @project.ssh_url_to_repo} SSH + %button{class: "btn #{ 'active' if default_clone_protocol == 'http' }", :"data-clone" => @project.http_url_to_repo}= gitlab_config.protocol.upcase + = text_field_tag :project_clone, default_url_to_repo, 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 b7a7ca8fcc..368aec5a46 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 2a365ce4f6..077e6c6a80 100644 --- a/app/views/shared/_no_ssh.html.haml +++ b/app/views/shared/_no_ssh.html.haml @@ -1,6 +1,8 @@ -- if cookies[:hide_no_ssh_message].blank? && current_user.require_ssh_key? +- if cookies[:hide_no_ssh_message].blank? && current_user.require_ssh_key? && !current_user.hide_no_ssh_key .no-ssh-key-message .container You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path} to your profile - = link_to '#', class: 'pull-right hide-no-ssh-message' do - %i.icon-remove + %div.pull-right + = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'hide-no-ssh-message', remote: true + | + = link_to 'Remind later', '#', class: 'hide-no-ssh-message' diff --git a/app/views/snippets/_blob.html.haml b/app/views/snippets/_blob.html.haml index dc856f84be..e0152143a9 100644 --- a/app/views/snippets/_blob.html.haml +++ b/app/views/snippets/_blob.html.haml @@ -6,7 +6,7 @@ .btn-group.tree-btn-group.pull-right - if @snippet.author == current_user = link_to "Edit", edit_snippet_path(@snippet), class: "btn btn-tiny", title: 'Edit Snippet' - = link_to "Delete", snippet_path(@snippet), method: :delete, confirm: "Are you sure?", class: "btn btn-tiny", title: 'Delete Snippet' + = link_to "Delete", snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-tiny", title: 'Delete Snippet' = link_to "Raw", raw_snippet_path(@snippet), class: "btn btn-tiny", target: "_blank" - unless @snippet.content.empty? - if gitlab_markdown?(@snippet.file_name) diff --git a/app/views/snippets/_form.html.haml b/app/views/snippets/_form.html.haml index 517c81fae8..0541ed4d63 100644 --- a/app/views/snippets/_form.html.haml +++ b/app/views/snippets/_form.html.haml @@ -25,7 +25,7 @@ = f.radio_button :private, false %span %strong Public - (GitLab users can can see this snippet) + (GitLab users can see this snippet) .control-group .file-editor @@ -46,7 +46,7 @@ - unless @snippet.new_record? .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 'Remove', snippet_path(@snippet), data: { confirm: 'Removed snippet cannot be restored! Are you sure?'}, method: :delete, class: "btn btn-remove delete-snippet", id: "destroy_snippet_#{@snippet.id}" = link_to "Cancel", snippets_path(@project), class: "btn btn-cancel" diff --git a/app/views/snippets/_snippet.html.haml b/app/views/snippets/_snippet.html.haml index a50d813825..4c9ef3f9f3 100644 --- a/app/views/snippets/_snippet.html.haml +++ b/app/views/snippets/_snippet.html.haml @@ -20,4 +20,4 @@ = link_to user_snippets_path(snippet.author) do = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: '' = snippet.author_name - %span.light #{time_ago_in_words(snippet.created_at)} ago + %span.light #{time_ago_with_tooltip(snippet.created_at)} ago diff --git a/app/views/users_groups/_users_group.html.haml b/app/views/users_groups/_users_group.html.haml index 5934ff72e8..5f477f3c97 100644 --- a/app/views/users_groups/_users_group.html.haml +++ b/app/views/users_groups/_users_group.html.haml @@ -13,7 +13,7 @@ - if show_controls && can?(current_user, :manage_group, @group) && current_user != user = link_to '#', class: "btn-tiny btn js-toggle-button", title: 'Edit access level' do %i.icon-edit - = link_to group_users_group_path(@group, member), confirm: remove_user_from_group_message(@group, user), method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do + = link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do %i.icon-minus.icon-white .edit-member.hide.js-toggle-content diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb new file mode 100644 index 0000000000..9982b362a1 --- /dev/null +++ b/app/workers/emails_on_push_worker.rb @@ -0,0 +1,25 @@ +class EmailsOnPushWorker + include Sidekiq::Worker + + def perform(project_id, recipients, push_data) + project = Project.find(project_id) + before_sha = push_data["before"] + after_sha = push_data["after"] + branch = push_data["ref"] + author_id = push_data["user_id"] + + if before_sha =~ /^000000/ || after_sha =~ /^000000/ + # skip if new branch was pushed or branch was removed + return true + end + + compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha) + + # Do not send emails if git compare failed + return false unless compare && compare.commits.present? + + recipients.split(" ").each do |recipient| + Notify.delay.repository_push_email(project_id, recipient, author_id, branch, compare) + end + end +end diff --git a/config.ru b/config.ru index dfd2d86223..1edcd391e4 100644 --- a/config.ru +++ b/config.ru @@ -1,5 +1,11 @@ # This file is used by Rack-based servers to start the application. +# Unicorn self-process killer +require 'unicorn/worker_killer' + +# # Max memory size (RSS) per worker +use Unicorn::WorkerKiller::Oom, (200 * (1 << 20)), (250 * (1 << 20)) + require ::File.expand_path('../config/environment', __FILE__) map ENV['RAILS_RELATIVE_URL_ROOT'] || "/" do diff --git a/config/application.rb b/config/application.rb index 82e74e7250..1c91134f52 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,13 +1,9 @@ require File.expand_path('../boot', __FILE__) require 'rails/all' +require 'devise' -if defined?(Bundler) - # If you precompile assets before deploying to production, use this line - # Bundler.require(*Rails.groups(assets: %w(development test))) - # If you want your assets lazily compiled in production, use this line - Bundler.require(:default, :assets, Rails.env) -end +Bundler.require(:default, Rails.env) module Gitlab class Application < Rails::Application @@ -16,7 +12,7 @@ module Gitlab # -- all .rb files in that directory are automatically loaded. # Custom directories with classes and modules you want to be autoloadable. - config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/models/concerns) + config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/models/concerns #{config.root}/app/models/project_services) # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named. @@ -70,14 +66,24 @@ module Gitlab config.assets.version = '1.0' # Uncomment and customize the last line to run in a non-root path - # WARNING: This feature is known to work, but unsupported - # Note that three settings need to be changed for this to work. + # WARNING: We recommend creating a FQDN to host GitLab in a root path instead of this. + # Note that four settings need to be changed for this to work. # 1) In your application.rb file: config.relative_url_root = "/gitlab" # 2) In your gitlab.yml file: relative_url_root: /gitlab # 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab" + # 4) In ../gitlab-shell/config.yml: gitlab_url: "http://127.0.0.1/gitlab" + # To update the path, run: sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production # # config.relative_url_root = "/gitlab" config.middleware.use Rack::Attack + + # Allow access to GitLab API from other domains + config.middleware.use Rack::Cors do + allow do + origins '*' + resource '/api/*', headers: :any, methods: [:get, :post, :options, :put, :delete] + end + end end end diff --git a/config/database.yml.mysql b/config/database.yml.mysql index e7a9227e41..55ac088bc1 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/environments/development.rb b/config/environments/development.rb index 6cba17f6ea..e4c7649fda 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -6,9 +6,6 @@ Gitlab::Application.configure do # since you don't have to restart the web server when you make code changes. config.cache_classes = false - # Log error messages when you accidentally call methods on nil. - config.whiny_nils = true - # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false @@ -25,10 +22,6 @@ Gitlab::Application.configure do # Raise exception on mass assignment protection for Active Record models config.active_record.mass_assignment_sanitizer = :strict - # Log the query plan for queries taking more than this (works - # with SQLite, MySQL, and PostgreSQL) - config.active_record.auto_explain_threshold_in_seconds = 0.5 - # Do not compress assets config.assets.compress = false @@ -39,4 +32,6 @@ Gitlab::Application.configure do config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } # Open sent mails in browser config.action_mailer.delivery_method = :letter_opener + + config.eager_load = false end diff --git a/config/environments/production.rb b/config/environments/production.rb index e3476be8fb..9ac4622abc 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -80,4 +80,9 @@ Gitlab::Application.configure do # # } config.action_mailer.perform_deliveries = true config.action_mailer.raise_delivery_errors = true + + config.eager_load = true + config.assets.js_compressor = :uglifier + + config.allow_concurrency = false end diff --git a/config/environments/test.rb b/config/environments/test.rb index b626986299..3860dc5c74 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -11,9 +11,6 @@ Gitlab::Application.configure do config.serve_static_assets = true config.static_cache_control = "public, max-age=3600" - # Log error messages when you accidentally call methods on nil - config.whiny_nils = true - # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false @@ -34,4 +31,6 @@ Gitlab::Application.configure do # Print deprecation notices to the stderr config.active_support.deprecation = :stderr + + config.eager_load = false end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 6d11e68641..9961d84443 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -20,11 +20,13 @@ 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 - # Note that three settings need to be changed for this to work. + # WARNING: We recommend creating a FQDN to host GitLab in a root path instead of this. + # Note that four settings need to be changed for this to work. # 1) In your application.rb file: config.relative_url_root = "/gitlab" # 2) In your gitlab.yml file: relative_url_root: /gitlab # 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab" + # 4) In ../gitlab-shell/config.yml: gitlab_url: "http://127.0.0.1/gitlab" + # To update the path, run: sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production # # relative_url_root: /gitlab @@ -55,6 +57,10 @@ 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 happens when the commit is pushed or merged into the default branch of a project. @@ -68,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: @@ -110,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' @@ -185,6 +193,8 @@ production: &base ## GitLab Shell settings gitlab_shell: + path: /home/git/gitlab-shell/ + # REPOS_PATH MUST NOT BE A SYMLINK!!! repos_path: /home/git/repositories/ hooks_path: /home/git/gitlab-shell/hooks/ diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 942b77ffd2..ea391ca601 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 @@ -90,6 +114,7 @@ Settings.gravatar['ssl_url'] ||= 'https://secure.gravatar.com/avatar/%{hash}? # GitLab Shell # Settings['gitlab_shell'] ||= Settingslogic.new({}) +Settings.gitlab_shell['path'] ||= Settings.gitlab['user_home'] + '/gitlab-shell/' Settings.gitlab_shell['hooks_path'] ||= Settings.gitlab['user_home'] + '/gitlab-shell/hooks/' Settings.gitlab_shell['receive_pack'] = true if Settings.gitlab_shell['receive_pack'].nil? Settings.gitlab_shell['upload_pack'] = true if Settings.gitlab_shell['upload_pack'].nil? diff --git a/config/initializers/carrierwave.rb b/config/initializers/carrierwave.rb index 45bc68f322..6875fa74ed 100644 --- a/config/initializers/carrierwave.rb +++ b/config/initializers/carrierwave.rb @@ -15,5 +15,7 @@ if File.exists?(aws_file) config.fog_directory = AWS_CONFIG['bucket'] # required config.fog_public = false # optional, defaults to true config.fog_attributes = {'Cache-Control'=>'max-age=315576000'} # optional, defaults to {} + config.fog_authenticated_url_expiration = 1 << 29 # optional time (in seconds) that authenticated urls will be valid. + # when fog_public is false and provider is AWS or Google, defaults to 600 end end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index b7cb808d2e..25390978cf 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -6,6 +6,7 @@ Devise.setup do |config| # note that it will be overwritten if you use your own mailer class with default "from" parameter. config.mailer_sender = Gitlab.config.gitlab.email_from + # Configure the class responsible to send e-mails. # config.mailer = "Devise::Mailer" @@ -74,8 +75,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 +102,7 @@ Devise.setup do |config| # ==> Configuration for :validatable # Range for password length. Default is 6..128. - config.password_length = 6..128 + config.password_length = 8..128 # Email regex used to validate email formats. It simply asserts that # an one (and only one) @ exists in the given string. This is mainly diff --git a/config/initializers/gemoji.rb b/config/initializers/gemoji.rb index 8c85aad5d3..6cc33aced7 100644 --- a/config/initializers/gemoji.rb +++ b/config/initializers/gemoji.rb @@ -1,2 +1,3 @@ # Workaround for https://github.com/github/gemoji/pull/18 +require 'gemoji' Gitlab::Application.config.assets.paths << Emoji.images_path diff --git a/config/initializers/rack_attack.rb.example b/config/initializers/rack_attack.rb.example index 1d2dee9a05..1d10a53d50 100644 --- a/config/initializers/rack_attack.rb.example +++ b/config/initializers/rack_attack.rb.example @@ -5,11 +5,13 @@ 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" ] unless Rails.env.test? - Rack::Attack.throttle('protected paths', limit: 6, period: 60.seconds) do |req| + Rack::Attack.throttle('protected paths', limit: 10, period: 60.seconds) do |req| req.ip if paths_to_be_protected.include?(req.path) && req.post? end end diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb index 16d1d4a9fd..9840029011 100644 --- a/config/initializers/secret_token.rb +++ b/config/initializers/secret_token.rb @@ -21,3 +21,4 @@ def find_secure_token end Gitlab::Application.config.secret_token = find_secure_token +Gitlab::Application.config.secret_key_base = find_secure_token diff --git a/config/routes.rb b/config/routes.rb index 563fc4c109..33c8a423c8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -22,7 +22,7 @@ Gitlab::Application.routes.draw do project_root: Gitlab.config.gitlab_shell.repos_path, upload_pack: Gitlab.config.gitlab_shell.upload_pack, receive_pack: Gitlab.config.gitlab_shell.receive_pack - }), at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) } + }), at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post] # # Help @@ -127,10 +127,11 @@ Gitlab::Application.routes.draw do delete :leave end end + resource :avatar, only: [:destroy] end end - match "/u/:username" => "users#show", as: :user, constraints: { username: /.*/ } + match "/u/:username" => "users#show", as: :user, constraints: { username: /.*/ }, via: :get @@ -169,6 +170,8 @@ Gitlab::Application.routes.draw do member do put :transfer post :fork + post :archive + post :unarchive get :autocomplete_sources end diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example index e4e1342683..ba5e5cdde0 100644 --- a/config/unicorn.rb.example +++ b/config/unicorn.rb.example @@ -9,11 +9,13 @@ # documentation. # Uncomment and customize the last line to run in a non-root path -# WARNING: This feature is known to work, but unsupported -# Note that three settings need to be changed for this to work. +# WARNING: We recommend creating a FQDN to host GitLab in a root path instead of this. +# Note that four settings need to be changed for this to work. # 1) In your application.rb file: config.relative_url_root = "/gitlab" # 2) In your gitlab.yml file: relative_url_root: /gitlab # 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab" +# 4) In ../gitlab-shell/config.yml: gitlab_url: "http://127.0.0.1/gitlab" +# To update the path, run: sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production # # ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab" diff --git a/db/migrate/20131112220935_add_visibility_level_to_projects.rb b/db/migrate/20131112220935_add_visibility_level_to_projects.rb new file mode 100644 index 0000000000..cf1e9f912a --- /dev/null +++ b/db/migrate/20131112220935_add_visibility_level_to_projects.rb @@ -0,0 +1,13 @@ +class AddVisibilityLevelToProjects < ActiveRecord::Migration + def self.up + add_column :projects, :visibility_level, :integer, :default => 0, :null => false + Project.where(public: true).update_all(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + remove_column :projects, :public + end + + def self.down + add_column :projects, :public, :boolean, :default => false, :null => false + Project.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).update_all(public: true) + remove_column :projects, :visibility_level + end +end diff --git a/db/migrate/20131129154016_add_archived_to_projects.rb b/db/migrate/20131129154016_add_archived_to_projects.rb new file mode 100644 index 0000000000..917e690ba4 --- /dev/null +++ b/db/migrate/20131129154016_add_archived_to_projects.rb @@ -0,0 +1,5 @@ +class AddArchivedToProjects < ActiveRecord::Migration + def change + add_column :projects, :archived, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20131202192556_add_event_fields_for_web_hook.rb b/db/migrate/20131202192556_add_event_fields_for_web_hook.rb new file mode 100644 index 0000000000..d29e996852 --- /dev/null +++ b/db/migrate/20131202192556_add_event_fields_for_web_hook.rb @@ -0,0 +1,7 @@ +class AddEventFieldsForWebHook < ActiveRecord::Migration + def change + add_column :web_hooks, :push_events, :boolean, default: true, null: false + add_column :web_hooks, :issues_events, :boolean, default: false, null: false + add_column :web_hooks, :merge_requests_events, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20131214224427_add_hide_no_ssh_key_to_users.rb b/db/migrate/20131214224427_add_hide_no_ssh_key_to_users.rb new file mode 100644 index 0000000000..7cec79e7ee --- /dev/null +++ b/db/migrate/20131214224427_add_hide_no_ssh_key_to_users.rb @@ -0,0 +1,5 @@ +class AddHideNoSshKeyToUsers < ActiveRecord::Migration + def change + add_column :users, :hide_no_ssh_key, :boolean, :default => false + end +end diff --git a/db/migrate/20131217102743_add_recipients_to_service.rb b/db/migrate/20131217102743_add_recipients_to_service.rb new file mode 100644 index 0000000000..9695c25135 --- /dev/null +++ b/db/migrate/20131217102743_add_recipients_to_service.rb @@ -0,0 +1,5 @@ +class AddRecipientsToService < ActiveRecord::Migration + def change + add_column :services, :recipients, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index 2d6677b944..5256c67b08 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -9,64 +9,64 @@ # from scratch. The latter is a flawed and unsustainable approach (the more migrations # you'll amass, the slower it'll run and the greater likelihood for issues). # -# It's strongly recommended to check this file into your version control system. +# It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(:version => 20131112114325) do +ActiveRecord::Schema.define(version: 20131217102743) do - create_table "broadcast_messages", :force => true do |t| - t.text "message", :null => false + create_table "broadcast_messages", force: true do |t| + t.text "message", null: false t.datetime "starts_at" t.datetime "ends_at" t.integer "alert_type" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.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 - t.integer "project_id", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + create_table "deploy_keys_projects", force: true do |t| + t.integer "deploy_key_id", null: false + t.integer "project_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "deploy_keys_projects", ["project_id"], :name => "index_deploy_keys_projects_on_project_id" + add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree - create_table "events", :force => true do |t| + create_table "events", force: true do |t| t.string "target_type" t.integer "target_id" t.string "title" t.text "data" t.integer "project_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "action" t.integer "author_id" end - add_index "events", ["action"], :name => "index_events_on_action" - add_index "events", ["author_id"], :name => "index_events_on_author_id" - add_index "events", ["created_at"], :name => "index_events_on_created_at" - add_index "events", ["project_id"], :name => "index_events_on_project_id" - add_index "events", ["target_id"], :name => "index_events_on_target_id" - add_index "events", ["target_type"], :name => "index_events_on_target_type" + add_index "events", ["action"], name: "index_events_on_action", using: :btree + add_index "events", ["author_id"], name: "index_events_on_author_id", using: :btree + add_index "events", ["created_at"], name: "index_events_on_created_at", using: :btree + add_index "events", ["project_id"], name: "index_events_on_project_id", using: :btree + add_index "events", ["target_id"], name: "index_events_on_target_id", using: :btree + add_index "events", ["target_type"], name: "index_events_on_target_type", using: :btree - create_table "forked_project_links", :force => true do |t| - t.integer "forked_to_project_id", :null => false - t.integer "forked_from_project_id", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + create_table "forked_project_links", force: true do |t| + t.integer "forked_to_project_id", null: false + t.integer "forked_from_project_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "forked_project_links", ["forked_to_project_id"], :name => "index_forked_project_links_on_forked_to_project_id", :unique => true + add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree - create_table "issues", :force => true do |t| + create_table "issues", force: true do |t| t.string "title" t.integer "assignee_id" t.integer "author_id" t.integer "project_id" - t.datetime "created_at" - t.datetime "updated_at" - t.integer "position", :default => 0 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "position", default: 0 t.string "branch_name" t.text "description" t.integer "milestone_id" @@ -74,106 +74,106 @@ ActiveRecord::Schema.define(:version => 20131112114325) do t.integer "iid" end - add_index "issues", ["assignee_id"], :name => "index_issues_on_assignee_id" - add_index "issues", ["author_id"], :name => "index_issues_on_author_id" - add_index "issues", ["created_at"], :name => "index_issues_on_created_at" - add_index "issues", ["milestone_id"], :name => "index_issues_on_milestone_id" - add_index "issues", ["project_id"], :name => "index_issues_on_project_id" - add_index "issues", ["title"], :name => "index_issues_on_title" + add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree + add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree + add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree + add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree + add_index "issues", ["project_id"], name: "index_issues_on_project_id", using: :btree + add_index "issues", ["title"], name: "index_issues_on_title", using: :btree - create_table "keys", :force => true do |t| + create_table "keys", force: true do |t| t.integer "user_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.text "key" t.string "title" t.string "type" t.string "fingerprint" end - add_index "keys", ["user_id"], :name => "index_keys_on_user_id" + add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree - create_table "merge_requests", :force => true do |t| - t.string "target_branch", :null => false - t.string "source_branch", :null => false - t.integer "source_project_id", :null => false + create_table "merge_requests", force: true do |t| + t.string "target_branch", null: false + t.string "source_branch", null: false + t.integer "source_project_id", null: false t.integer "author_id" t.integer "assignee_id" t.string "title" - t.datetime "created_at" - t.datetime "updated_at" - t.text "st_commits", :limit => 2147483647 - t.text "st_diffs", :limit => 2147483647 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "st_commits", limit: 2147483647 + t.text "st_diffs", limit: 2147483647 t.integer "milestone_id" t.string "state" t.string "merge_status" - t.integer "target_project_id", :null => false + t.integer "target_project_id", null: false t.integer "iid" t.text "description" end - add_index "merge_requests", ["assignee_id"], :name => "index_merge_requests_on_assignee_id" - add_index "merge_requests", ["author_id"], :name => "index_merge_requests_on_author_id" - add_index "merge_requests", ["created_at"], :name => "index_merge_requests_on_created_at" - add_index "merge_requests", ["milestone_id"], :name => "index_merge_requests_on_milestone_id" - add_index "merge_requests", ["source_branch"], :name => "index_merge_requests_on_source_branch" - add_index "merge_requests", ["source_project_id"], :name => "index_merge_requests_on_project_id" - add_index "merge_requests", ["target_branch"], :name => "index_merge_requests_on_target_branch" - add_index "merge_requests", ["title"], :name => "index_merge_requests_on_title" + add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree + add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree + add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree + add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree + add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree + add_index "merge_requests", ["source_project_id"], name: "index_merge_requests_on_project_id", using: :btree + add_index "merge_requests", ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree + add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree - create_table "milestones", :force => true do |t| - t.string "title", :null => false - t.integer "project_id", :null => false + create_table "milestones", force: true do |t| + t.string "title", null: false + t.integer "project_id", null: false t.text "description" t.date "due_date" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "state" t.integer "iid" end - add_index "milestones", ["due_date"], :name => "index_milestones_on_due_date" - add_index "milestones", ["project_id"], :name => "index_milestones_on_project_id" + add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree + add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree - create_table "namespaces", :force => true do |t| - t.string "name", :null => false - t.string "path", :null => false + create_table "namespaces", force: true do |t| + t.string "name", null: false + t.string "path", null: false t.integer "owner_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "type" - t.string "description", :default => "", :null => false t.string "ldap_cn" t.integer "ldap_access" + t.string "description", default: "", null: false end - add_index "namespaces", ["name"], :name => "index_namespaces_on_name" - add_index "namespaces", ["owner_id"], :name => "index_namespaces_on_owner_id" - add_index "namespaces", ["path"], :name => "index_namespaces_on_path" - add_index "namespaces", ["type"], :name => "index_namespaces_on_type" + add_index "namespaces", ["name"], name: "index_namespaces_on_name", using: :btree + add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree + add_index "namespaces", ["path"], name: "index_namespaces_on_path", using: :btree + add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree - create_table "notes", :force => true do |t| + create_table "notes", force: true do |t| t.text "note" t.string "noteable_type" t.integer "author_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "project_id" t.string "attachment" t.string "line_code" t.string "commit_id" t.integer "noteable_id" t.text "st_diff" - t.boolean "system", :default => false, :null => false + t.boolean "system", default: false, null: false end - add_index "notes", ["author_id"], :name => "index_notes_on_author_id" - add_index "notes", ["commit_id"], :name => "index_notes_on_commit_id" - add_index "notes", ["created_at"], :name => "index_notes_on_created_at" - add_index "notes", ["noteable_id", "noteable_type"], :name => "index_notes_on_noteable_id_and_noteable_type" - add_index "notes", ["noteable_type"], :name => "index_notes_on_noteable_type" - add_index "notes", ["project_id", "noteable_type"], :name => "index_notes_on_project_id_and_noteable_type" - add_index "notes", ["project_id"], :name => "index_notes_on_project_id" + add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree + add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree + add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree + add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree + add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree + add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree + add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree create_table "project_group_links", :force => true do |t| t.integer "project_id", :null => false @@ -183,74 +183,76 @@ ActiveRecord::Schema.define(:version => 20131112114325) do t.integer "group_access", :default => 30, :null => false end - create_table "projects", :force => true do |t| + create_table "projects", force: true do |t| t.string "name" t.string "path" t.text "description" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "creator_id" - t.boolean "issues_enabled", :default => true, :null => false - t.boolean "wall_enabled", :default => true, :null => false - t.boolean "merge_requests_enabled", :default => true, :null => false - t.boolean "wiki_enabled", :default => true, :null => false + t.boolean "issues_enabled", default: true, null: false + t.boolean "wall_enabled", default: true, null: false + t.boolean "merge_requests_enabled", default: true, null: false + t.boolean "wiki_enabled", default: true, null: false t.integer "namespace_id" - t.boolean "public", :default => false, :null => false - t.string "issues_tracker", :default => "gitlab", :null => false + t.string "issues_tracker", default: "gitlab", null: false t.string "issues_tracker_id" - t.boolean "snippets_enabled", :default => true, :null => false + t.boolean "snippets_enabled", default: true, null: false t.datetime "last_activity_at" - t.boolean "imported", :default => false, :null => false + t.boolean "imported", default: false, null: false t.string "import_url" + t.integer "visibility_level", default: 0, null: false + t.boolean "archived", default: false, null: false end - add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id" - add_index "projects", ["last_activity_at"], :name => "index_projects_on_last_activity_at" - add_index "projects", ["namespace_id"], :name => "index_projects_on_namespace_id" + add_index "projects", ["creator_id"], name: "index_projects_on_owner_id", using: :btree + add_index "projects", ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree + add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree - create_table "protected_branches", :force => true do |t| - t.integer "project_id", :null => false - t.string "name", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + create_table "protected_branches", force: true do |t| + t.integer "project_id", null: false + t.string "name", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "protected_branches", ["project_id"], :name => "index_protected_branches_on_project_id" + add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree - create_table "services", :force => true do |t| + create_table "services", force: true do |t| t.string "type" t.string "title" t.string "token" - t.integer "project_id", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.boolean "active", :default => false, :null => false + t.integer "project_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "active", default: false, null: false t.string "project_url" t.string "subdomain" t.string "room" + t.text "recipients" end - add_index "services", ["project_id"], :name => "index_services_on_project_id" + add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree - create_table "snippets", :force => true do |t| + create_table "snippets", force: true do |t| t.string "title" - t.text "content", :limit => 2147483647 - t.integer "author_id", :null => false + t.text "content", limit: 2147483647 + t.integer "author_id", null: false t.integer "project_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "file_name" t.datetime "expires_at" - t.boolean "private", :default => true, :null => false + t.boolean "private", default: true, null: false t.string "type" end - add_index "snippets", ["author_id"], :name => "index_snippets_on_author_id" - add_index "snippets", ["created_at"], :name => "index_snippets_on_created_at" - add_index "snippets", ["expires_at"], :name => "index_snippets_on_expires_at" - add_index "snippets", ["project_id"], :name => "index_snippets_on_project_id" + add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree + add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree + add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree + add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree - create_table "taggings", :force => true do |t| + create_table "taggings", force: true do |t| t.integer "tag_id" t.integer "taggable_id" t.string "taggable_type" @@ -260,42 +262,45 @@ ActiveRecord::Schema.define(:version => 20131112114325) do t.datetime "created_at" end - create_table "tags", :force => true do |t| + add_index "taggings", ["tag_id"], name: "index_taggings_on_tag_id", using: :btree + add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree + + create_table "tags", force: true do |t| t.string "name" end - create_table "users", :force => true do |t| - t.string "email", :default => "", :null => false - t.string "encrypted_password", :limit => 128, :default => "", :null => false + create_table "users", force: true do |t| + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", :default => 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" t.string "last_sign_in_ip" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "name" - t.boolean "admin", :default => false, :null => false - t.integer "projects_limit", :default => 10 - t.string "skype", :default => "", :null => false - t.string "linkedin", :default => "", :null => false - t.string "twitter", :default => "", :null => false + t.boolean "admin", default: false, null: false + t.integer "projects_limit", default: 10 + t.string "skype", default: "", null: false + t.string "linkedin", default: "", null: false + t.string "twitter", default: "", null: false t.string "authentication_token" - t.integer "theme_id", :default => 1, :null => false + t.integer "theme_id", default: 1, null: false t.string "bio" - t.integer "failed_attempts", :default => 0 + t.integer "failed_attempts", default: 0 t.datetime "locked_at" t.string "extern_uid" t.string "provider" t.string "username" - t.boolean "can_create_group", :default => true, :null => false - t.boolean "can_create_team", :default => true, :null => false + t.boolean "can_create_group", default: true, null: false + t.boolean "can_create_team", default: true, null: false t.string "state" - t.integer "color_scheme_id", :default => 1, :null => false - t.integer "notification_level", :default => 1, :null => false + t.integer "color_scheme_id", default: 1, null: false + t.integer "notification_level", default: 1, null: false t.datetime "password_expires_at" t.integer "created_by_id" t.datetime "last_credential_check_at" @@ -304,49 +309,54 @@ ActiveRecord::Schema.define(:version => 20131112114325) do t.datetime "confirmed_at" t.datetime "confirmation_sent_at" t.string "unconfirmed_email" + t.boolean "hide_no_ssh_key", default: false end - add_index "users", ["admin"], :name => "index_users_on_admin" - add_index "users", ["authentication_token"], :name => "index_users_on_authentication_token", :unique => true - add_index "users", ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true - add_index "users", ["email"], :name => "index_users_on_email", :unique => true - add_index "users", ["name"], :name => "index_users_on_name" - add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true - add_index "users", ["username"], :name => "index_users_on_username" + add_index "users", ["admin"], name: "index_users_on_admin", using: :btree + add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree + add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree + add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree + add_index "users", ["extern_uid", "provider"], name: "index_users_on_extern_uid_and_provider", unique: true, using: :btree + add_index "users", ["name"], name: "index_users_on_name", using: :btree + add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree + add_index "users", ["username"], name: "index_users_on_username", using: :btree - create_table "users_groups", :force => true do |t| - t.integer "group_access", :null => false - t.integer "group_id", :null => false - t.integer "user_id", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "notification_level", :default => 3, :null => false + create_table "users_groups", force: true do |t| + t.integer "group_access", null: false + t.integer "group_id", null: false + t.integer "user_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "notification_level", default: 3, null: false end - add_index "users_groups", ["user_id"], :name => "index_users_groups_on_user_id" + add_index "users_groups", ["user_id"], name: "index_users_groups_on_user_id", using: :btree - create_table "users_projects", :force => true do |t| - t.integer "user_id", :null => false - t.integer "project_id", :null => false - t.datetime "created_at" - t.datetime "updated_at" - t.integer "project_access", :default => 0, :null => false - t.integer "notification_level", :default => 3, :null => false + create_table "users_projects", force: true do |t| + t.integer "user_id", null: false + t.integer "project_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "project_access", default: 0, null: false + t.integer "notification_level", default: 3, null: false end - add_index "users_projects", ["project_access"], :name => "index_users_projects_on_project_access" - add_index "users_projects", ["project_id"], :name => "index_users_projects_on_project_id" - add_index "users_projects", ["user_id"], :name => "index_users_projects_on_user_id" + add_index "users_projects", ["project_access"], name: "index_users_projects_on_project_access", using: :btree + add_index "users_projects", ["project_id"], name: "index_users_projects_on_project_id", using: :btree + add_index "users_projects", ["user_id"], name: "index_users_projects_on_user_id", using: :btree - create_table "web_hooks", :force => true do |t| + create_table "web_hooks", force: true do |t| t.string "url" t.integer "project_id" - t.datetime "created_at" - t.datetime "updated_at" - 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" + add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree end diff --git a/doc/api/projects.md b/doc/api/projects.md index ca86b7db9f..b0e6e056fd 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -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", @@ -117,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", @@ -234,7 +237,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) ### Create project for user @@ -256,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 @@ -397,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" } ``` @@ -414,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 @@ -429,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 @@ -473,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/development/architecture.md b/doc/development/architecture.md new file mode 100644 index 0000000000..db22f0bda8 --- /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 be7bc0aad2..6016e97ede 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 feec1f4332..e6f877d2ac 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -1,6 +1,6 @@ # Select Version to Install Make sure you view this installation guide from the branch (version) of GitLab you would like to install. In most cases -this should be the highest numbered stable branch (example shown below). +this should be the highest numbered stable branch (example shown below). ![capture](https://f.cloud.github.com/assets/1192780/564911/2f9f3e1e-c5b7-11e2-9f89-98e527d1adec.png) @@ -105,7 +105,7 @@ Is the system packaged Git too old? Remove it and compile from source. mail server. By default, Debian is shipped with exim4 whereas Ubuntu does not ship with one. The recommended mail server is postfix and you can install it with: - sudo apt-get install -y postfix + sudo apt-get install -y postfix Then select 'Internet Site' and press enter to confirm the hostname. @@ -144,13 +144,10 @@ GitLab Shell is an ssh access and repository management software developed speci cd /home/git # Clone gitlab shell - sudo -u git -H git clone https://github.com/gitlabhq/gitlab-shell.git + sudo -u git -H git clone https://github.com/gitlabhq/gitlab-shell.git -b v1.8.0 cd gitlab-shell - # switch to right version - sudo -u git -H git checkout v1.7.9 - sudo -u git -H cp config.yml.example config.yml # Edit config and replace gitlab_url @@ -174,16 +171,13 @@ To setup the MySQL/PostgreSQL database and dependencies please see [`doc/install ## Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/subscribers/gitlab-ee.git gitlab + sudo -u git -H git clone https://gitlab.com/subscribers/gitlab-ee.git -b 6-4-stable-ee gitlab # Go to gitlab dir cd /home/git/gitlab - # Checkout to stable release - sudo -u git -H git checkout 6-3-stable-ee - **Note:** -You can change `6-3-stable-ee` to `master` if you want the *bleeding edge* version, but never install master on a production server! +You can change `6-4-stable-ee` to `master` if you want the *bleeding edge* version, but never install master on a production server! ## Configure it @@ -253,7 +247,7 @@ Make sure to edit both `gitlab.yml` and `unicorn.rb` to match your setup. # PostgreSQL sudo -u git cp config/database.yml.postgresql config/database.yml - + # Make config/database.yml readable to git only sudo -u git -H chmod o-rwx config/database.yml @@ -282,7 +276,12 @@ Make sure to edit both `gitlab.yml` and `unicorn.rb` to match your setup. Download the init script (will be /etc/init.d/gitlab): sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab - sudo chmod +x /etc/init.d/gitlab + +And if you are installing with a non-default folder or user copy and edit the defaults file: + + sudo cp lib/support/init.d/gitlab.default.example /etc/default/gitlab + +If you installed gitlab in another directory or as a user other than the default you should change these settings in /etc/default/gitlab. Do not edit /etc/init.d/gitlab as it will be changed on upgrade. Make GitLab start on boot: @@ -304,14 +303,10 @@ Check if GitLab and its environment are configured correctly: # or sudo /etc/init.d/gitlab restart -## Double-check Application Status -To make sure you didn't miss anything run a more thorough check with: +## Compile assets - sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production - -If all items are green, then congratulations on successfully installing GitLab! -However there are still a few steps left. + sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production # 7. Nginx @@ -343,7 +338,17 @@ Make sure to edit the config file to match your setup: # Done! -Visit YOUR_SERVER for your first GitLab login. +## Double-check Application Status + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations on successfully installing GitLab! + +## Initial Login + +Visit YOUR_SERVER in your web browser for your first GitLab login. The setup has created an admin account for you. You can use it to log in: admin@local.host @@ -370,7 +375,7 @@ a different host, you can configure its connection string via the # example production: redis://redis.example.tld:6379 -If you want to connect the Redis server via socket, then use the "unix:" URL scheme +If you want to connect the Redis server via socket, then use the "unix:" URL scheme and the path to the Redis socket file in the `config/resque.yml` file. # example @@ -406,7 +411,7 @@ These steps are fairly general and you will need to figure out the exact details * Add provider specific configuration options to your `config/gitlab.yml` (you can use the [auth providers section of the example config](https://github.com/gitlabhq/gitlabhq/blob/master/config/gitlab.yml.example) as a reference) * Add the gem to your [Gemfile](https://github.com/gitlabhq/gitlabhq/blob/master/Gemfile) - `gem "omniauth-your-auth-provider"` + `gem "omniauth-your-auth-provider"` * If you're using MySQL, install the new Omniauth provider gem by running the following command: `sudo -u git -H bundle install --without development test postgres --path vendor/bundle --no-deployment` @@ -422,5 +427,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/markdown/markdown.md b/doc/markdown/markdown.md index a84222f9fc..6b5308afc4 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -187,7 +187,7 @@ GFM will recognize the following: * !123 : for merge requests * $123 : for snippets * 1234567 : for commits -* [file](path/to/file) : for file references +* \[file\](path/to/file) : for file references diff --git a/doc/update/6.2-to-6.3.md b/doc/update/6.2-to-6.3.md index a590fc7824..2272ae8e1a 100644 --- a/doc/update/6.2-to-6.3.md +++ b/doc/update/6.2-to-6.3.md @@ -24,7 +24,7 @@ sudo -u git -H git fetch sudo -u git -H git checkout 6-3-stable ``` -### 3. Update gitlab-shell +### 3. Update gitlab-shell (and its config) ```bash cd /home/git/gitlab-shell @@ -32,14 +32,9 @@ sudo -u git -H git fetch sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulnerabilities ``` -### 4. Install additional packages +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 -```bash -# Add support for lograte for better log file handling -sudo apt-get install logrotate -``` - -### 5. Install libs, migrations, etc. +### 4. Install libs, migrations, etc. ```bash cd /home/git/gitlab @@ -47,52 +42,48 @@ cd /home/git/gitlab # MySQL sudo -u git -H bundle install --without development test postgres --deployment -#PostgreSQL +# 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 -sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production -sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production -sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production ``` -### 6. Update config files +### 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 -git diff 6-1-stable:config/gitlab.yml.example 6-3-stable:config/gitlab.yml.example # if you upgrading from 6-1 +git diff 6-2-stable-ee:config/gitlab.yml.example 6-3-stable-ee:config/gitlab.yml.example +git diff 6-1-stable-ee:config/gitlab.yml.example 6-3-stable-ee:config/gitlab.yml.example # if you upgrading from 6-1 ``` * 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 ```bash +# Copy rack attack middleware config +cd /home/git/gitlab sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb ``` -* Set up logrotate + +### 6. Update Init script ```bash -sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab -``` - -### 7. Update Init script - -```bash -sudo rm /etc/init.d/gitlab -sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/6-3-stable/lib/support/init.d/gitlab +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab sudo chmod +x /etc/init.d/gitlab ``` -### 8. Start application +### 7. Start application sudo service gitlab start sudo service nginx restart -### 9. Check application status +### 8. Check application status Check if GitLab and its environment are configured correctly: diff --git a/doc/update/6.3-to-6.4.md b/doc/update/6.3-to-6.4.md new file mode 100644 index 0000000000..f7c363f8d7 --- /dev/null +++ b/doc/update/6.3-to-6.4.md @@ -0,0 +1,77 @@ +# From 6.3 to 6.4 + +### 0. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get latest code + +```bash +cd /home/git/gitlab +sudo -u git -H git fetch +sudo -u git -H git checkout 6-4-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.8.0 +``` + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +# PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 5. Start application + + sudo service gitlab start + sudo service nginx restart + +### 8. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went south? Revert to previous version (6.3) + +### 1. Revert the code to the previous version +Follow the [`upgrade guide from 6.2 to 6.3`](6.2-to-6.3.md), except for the database migration +(The backup is already migrated to the previous version) + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md index 61542dbb51..b284ff4836 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.9 +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 0000000000..f6f2fd5856 --- /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/dashboard/archived_projects.feature b/features/dashboard/archived_projects.feature new file mode 100644 index 0000000000..399c9b53d8 --- /dev/null +++ b/features/dashboard/archived_projects.feature @@ -0,0 +1,16 @@ +Feature: Dashboard with archived projects + Background: + Given I sign in as a user + And I own project "Shop" + And I own project "Forum" + And project "Forum" is archived + And I visit dashboard page + + Scenario: I should see non-archived projects on dashboard + Then I should see "Shop" project link + And I should not see "Forum" project link + + Scenario: I should see all projects on projects page + And I visit dashboard projects page + Then I should see "Shop" project link + And I should see "Forum" project link diff --git a/features/profile/profile.feature b/features/profile/profile.feature index 6198fd2b30..6b0421a20b 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/archived_projects.feature b/features/project/archived_projects.feature new file mode 100644 index 0000000000..9aac29384b --- /dev/null +++ b/features/project/archived_projects.feature @@ -0,0 +1,39 @@ +Feature: Project Archived + Background: + Given I sign in as a user + And I own project "Shop" + And I own project "Forum" + + Scenario: I should not see archived on project page of not-archive project + And project "Forum" is archived + And I visit project "Shop" page + Then I should not see "Archived" + + Scenario: I should see archived on project page of archive project + And project "Forum" is archived + And I visit project "Forum" page + Then I should see "Archived" + + Scenario: I should not see archived on projects page with no archived projects + And I visit dashboard projects page + Then I should not see "Archived" + + Scenario: I should see archived on projects page with archived projects + And project "Forum" is archived + And I visit dashboard projects page + Then I should see "Archived" + + Scenario: I archive project + When project "Shop" has push event + And I visit project "Shop" page + And I visit edit project "Shop" page + And I set project archived + Then I should see "Archived" + + Scenario: I unarchive project + When project "Shop" has push event + And project "Shop" is archived + And I visit project "Shop" page + And I visit edit project "Shop" page + And I set project unarchived + Then I should not see "Archived" diff --git a/features/project/commits/commits.feature b/features/project/commits/commits.feature index fe470f5ac9..cbe8b32150 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 ceae08c107..22beb1c50b 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 0000000000..ce197912f6 --- /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 4805d2befb..46b983e8f9 100644 --- a/features/project/service.feature +++ b/features/project/service.feature @@ -30,3 +30,15 @@ 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 + + Scenario: Activate email on push service + When I visit project "Shop" services page + And I click email on push service link + And I fill email on push settings + Then I should see email on push service settings saved diff --git a/features/project/source/multiselect_blob.feature b/features/project/source/multiselect_blob.feature new file mode 100644 index 0000000000..3038c0814a --- /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 178a769194..d6574ca900 100644 --- a/features/public/public_projects.feature +++ b/features/public/public_projects.feature @@ -1,17 +1,63 @@ Feature: Public Projects Feature Background: Given public project "Community" + And internal project "Internal" And private project "Enterprise" Scenario: I visit public area When I visit the public projects area Then I should see project "Community" + And I should not see project "Internal" And I should not see project "Enterprise" Scenario: I visit public project page When I visit project "Community" page Then I should see project "Community" home page + Scenario: I visit internal project page + When I visit project "Internal" page + Then I should be redirected to sign in page + + Scenario: I visit private project page + When I visit project "Enterprise" page + Then I should be redirected to sign in page + + Scenario: I visit an empty public project page + Given public empty project "Empty Public Project" + When I visit empty project page + Then I should see empty public project details + And I should see empty public project details with http clone info + + Scenario: I visit an empty public project page as user + Given I sign in as a user + And public empty project "Empty Public Project" + When I visit empty project page + Then I should see empty public project details + And I should see empty public project details with ssh clone info + + Scenario: I visit public area as user + Given I sign in as a user + When I visit the public projects area + Then I should see project "Community" + And I should see project "Internal" + And I should not see project "Enterprise" + + Scenario: I visit internal project page as user + Given I sign in as a user + When I visit project "Internal" page + Then I should see project "Internal" home page + + Scenario: I visit public project page + When I visit project "Community" page + Then I should see project "Community" home page + And I should see an http link to the repository + + Scenario: I visit public project page as user + Given I sign in as a user + When I visit project "Community" page + Then I should see project "Community" home page + And I should see an ssh link to the repository + Scenario: I visit an empty public project page Given public empty project "Empty Public Project" When I visit empty project page diff --git a/features/steps/dashboard/dashboard_with_archived_projects.rb b/features/steps/dashboard/dashboard_with_archived_projects.rb new file mode 100644 index 0000000000..700f4b426c --- /dev/null +++ b/features/steps/dashboard/dashboard_with_archived_projects.rb @@ -0,0 +1,22 @@ +class DashboardWithArchivedProjects < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedProject + + When 'project "Forum" is archived' do + project = Project.find_by_name "Forum" + project.update_attribute(:archived, true) + end + + Then 'I should see "Shop" project link' do + page.should have_link "Shop" + end + + Then 'I should not see "Forum" project link' do + page.should_not have_link "Forum" + end + + Then 'I should see "Forum" project link' do + page.should have_link "Forum" + end +end diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 753e2c19bc..7bb4aebde2 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" @@ -62,11 +85,7 @@ class Profile < Spinach::FeatureSteps end step "I should see a password error message" do - page.should have_content "Password doesn't match confirmation" - end - - step 'I should be redirected to sign in page' do - current_path.should == new_user_session_path + page.should have_content "Password confirmation doesn't match" end step 'I reset my token' do diff --git a/features/steps/project/project_archived.rb b/features/steps/project/project_archived.rb new file mode 100644 index 0000000000..149d293cd0 --- /dev/null +++ b/features/steps/project/project_archived.rb @@ -0,0 +1,37 @@ +class ProjectArchived < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + When 'project "Forum" is archived' do + project = Project.find_by_name "Forum" + project.update_attribute(:archived, true) + end + + When 'project "Shop" is archived' do + project = Project.find_by_name "Shop" + project.update_attribute(:archived, true) + end + + When 'I visit project "Forum" page' do + project = Project.find_by_name "Forum" + visit project_path(project) + end + + Then 'I should not see "Archived"' do + page.should_not have_content "Archived" + end + + Then 'I should see "Archived"' do + page.should have_content "Archived" + end + + When 'I set project archived' do + click_link "Archive" + end + + When 'I set project unarchived' do + click_link "Unarchive" + end + +end \ No newline at end of file diff --git a/features/steps/project/project_browse_commits.rb b/features/steps/project/project_browse_commits.rb index 650bc3a16f..d667b58240 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 0000000000..3d330e837c --- /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 127adecf7e..4954db2d7b 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 70eafc875d..54b3f18e08 100644 --- a/features/steps/project/project_services.rb +++ b/features/steps/project/project_services.rb @@ -3,73 +3,101 @@ class ProjectServices < Spinach::FeatureSteps include SharedProject include SharedPaths - When 'I visit project "Shop" services page' do + step 'I visit project "Shop" services page' do visit project_services_path(@project) end - Then 'I should see list of available services' do - page.should have_content 'Services' + step 'I should see list of available services' do + page.should have_content 'Project services' page.should have_content 'Campfire' page.should have_content 'Hipchat' page.should have_content 'GitLab CI' + page.should have_content 'Assembla' end - And 'I click gitlab-ci service link' do + step 'I click gitlab-ci service link' do click_link 'GitLab CI' end - And 'I fill gitlab-ci settings' do + step 'I fill gitlab-ci settings' do check 'Active' fill_in 'Project url', with: 'http://ci.gitlab.org/projects/3' fill_in 'Token', with: 'verySecret' click_button 'Save' end - Then 'I should see service settings saved' do + step 'I should see service settings saved' do find_field('Project url').value.should == 'http://ci.gitlab.org/projects/3' end - And 'I click hipchat service link' do + step 'I click hipchat service link' do click_link 'Hipchat' end - And 'I fill hipchat settings' do + step 'I fill hipchat settings' do check 'Active' fill_in 'Room', with: 'gitlab' fill_in 'Token', with: 'verySecret' click_button 'Save' end - Then 'I should see hipchat service settings saved' do + step 'I should see hipchat service settings saved' do find_field('Room').value.should == 'gitlab' end - And 'I click pivotaltracker service link' do + step 'I click pivotaltracker service link' do click_link 'PivotalTracker' end - And 'I fill pivotaltracker settings' do + step 'I fill pivotaltracker settings' do check 'Active' fill_in 'Token', with: 'verySecret' click_button 'Save' end - Then 'I should see pivotaltracker service settings saved' do + step 'I should see pivotaltracker service settings saved' do find_field('Token').value.should == 'verySecret' end - And 'I click Flowdock service link' do + step 'I click Flowdock service link' do click_link 'Flowdock' end - And 'I fill Flowdock settings' do + step 'I fill Flowdock settings' do check 'Active' fill_in 'Token', with: 'verySecret' click_button 'Save' end - Then 'I should see Flowdock service settings saved' do + step 'I should see Flowdock service settings saved' do find_field('Token').value.should == 'verySecret' end + + step 'I click Assembla service link' do + click_link 'Assembla' + end + + step 'I fill Assembla settings' do + check 'Active' + fill_in 'Token', with: 'verySecret' + click_button 'Save' + end + + step 'I should see Assembla service settings saved' do + find_field('Token').value.should == 'verySecret' + end + + step 'I click email on push service link' do + click_link 'Emails on push' + end + + step 'I fill email on push settings' do + fill_in 'Recipients', with: 'qa@company.name' + click_button 'Save' + end + + step 'I should see email on push service settings saved' do + find_field('Recipients').value.should == 'qa@company.name' + end end diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb new file mode 100644 index 0000000000..4ac5307570 --- /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 e5292380c5..47e52f47d0 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 + 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 @@ -44,20 +46,66 @@ class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps page.should have_content 'Git global setup' end + step 'I should see empty public project details with http clone info' do + project = Project.find_by_name('Empty Public Project') + page.all(:css, '.git-empty .clone').each do |element| + element.text.should include(project.http_url_to_repo) + end + end + + step 'I should see empty public project details with ssh clone info' do + project = Project.find_by_name('Empty Public Project') + page.all(:css, '.git-empty .clone').each do |element| + element.text.should include(project.url_to_repo) + end + end + step 'private project "Enterprise"' do create :project, name: 'Enterprise' end + step 'I visit project "Enterprise" page' do + project = Project.find_by_name('Enterprise') + visit project_path(project) + end + step 'I should see project "Community" home page' do 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 - def project - @project ||= Project.find_by_name("Community") + step 'I should see project "Internal"' do + page.should have_content "Internal" + end + + step 'I should not see project "Internal"' do + page.should_not have_content "Internal" + end + + step 'I visit project "Internal" page' do + project = Project.find_by_name('Internal') + visit project_path(project) + end + + step 'I should see project "Internal" home page' do + within '.project-home-title' do + page.should have_content 'Internal' + end + end + + step 'I should see an http link to the repository' do + project = Project.find_by_name 'Community' + page.should have_field('project_clone', with: project.http_url_to_repo) + end + + step 'I should see an ssh link to the repository' do + project = Project.find_by_name 'Community' + page.should have_field('project_clone', with: project.url_to_repo) end end diff --git a/features/steps/shared/authentication.rb b/features/steps/shared/authentication.rb index 8c501bbc53..df05754c28 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/project.rb b/features/steps/shared/project.rb index cef66b038d..3dc4932a09 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -14,6 +14,13 @@ module SharedProject @project.team << [@user, :master] end + # Create another specific project called "Forum" + And 'I own project "Forum"' do + @project = Project.find_by_name "Forum" + @project ||= create(:project_with_code, name: "Forum", namespace: @user.namespace, path: 'forum_project') + @project.team << [@user, :master] + end + And 'project "Shop" has push event' do @project = Project.find_by_name("Shop") diff --git a/features/support/env.rb b/features/support/env.rb index d27a73edab..64cdc7f5bf 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -9,6 +9,7 @@ ENV['RAILS_ENV'] = 'test' require './config/environment' require 'rspec' +require 'rspec/expectations' require 'database_cleaner' require 'spinach/capybara' require 'sidekiq/testing/inline' diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 8b3645e8ac..06c47f9ca0 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 diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb index 3a9ab66957..f9f2ed90cc 100644 --- a/lib/api/namespaces.rb +++ b/lib/api/namespaces.rb @@ -12,7 +12,7 @@ module API # Example Request: # GET /namespaces get do - @namespaces = Namespace.scoped + @namespaces = Namespace.all @namespaces = @namespaces.search(params[:search]) if params[:search].present? @namespaces = paginate @namespaces diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index 738974955f..c271dd8b61 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 ada24d9140..a61d75531b 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 @@ -76,7 +83,8 @@ module API # 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 @@ -90,7 +98,9 @@ module API :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 @@ -114,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 @@ -128,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 @@ -313,7 +326,8 @@ module API # GET /projects/search/:query get "/search/:query" do ids = current_user.authorized_projects.map(&:id) - projects = Project.where("(id in (?) OR public = true) AND (name LIKE (?))", ids, "%#{params[:query]}%") + visibility_levels = [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ] + projects = Project.where("(id in (?) OR visibility_level in (?)) AND (name LIKE (?))", ids, visibility_levels, "%#{params[:query]}%") present paginate(projects), with: Entities::Project end end diff --git a/lib/api/users.rb b/lib/api/users.rb index 54d3aeecb7..475343a3ed 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -9,7 +9,7 @@ module API # Example Request: # GET /users get do - @users = User.scoped + @users = User.all @users = @users.active if params[:active].present? @users = @users.search(params[:search]) if params[:search].present? @users = paginate @users diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index e2349495b5..c629144118 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,7 +80,7 @@ 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' refs.each do |ref| action = if project.protected_branch?(ref) diff --git a/lib/gitlab/backend/grack_helpers.rb b/lib/gitlab/backend/grack_helpers.rb index 5ac9e9f325..cb747fe013 100644 --- a/lib/gitlab/backend/grack_helpers.rb +++ b/lib/gitlab/backend/grack_helpers.rb @@ -1,7 +1,7 @@ module Grack module Helpers def project_by_path(path) - if m = /^\/([\w\.\/-]+)\.git/.match(path).to_a + if m = /^([\w\.\/-]+)\.git/.match(path).to_a path_with_namespace = m.last path_with_namespace.gsub!(/\.wiki$/, '') diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index 5020fa1f99..7121c8e40d 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -10,7 +10,7 @@ module Gitlab # add_repository("gitlab/gitlab-ci") # def add_repository(name) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "add-project", "#{name}.git" + system "#{gitlab_shell_path}/bin/gitlab-projects", "add-project", "#{name}.git" end # Import repository @@ -21,7 +21,7 @@ module Gitlab # import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git") # def import_repository(name, url) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "import-project", "#{name}.git", url + system "#{gitlab_shell_path}/bin/gitlab-projects", "import-project", "#{name}.git", url end # Move repository @@ -33,7 +33,7 @@ module Gitlab # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git") # def mv_repository(path, new_path) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "mv-project", "#{path}.git", "#{new_path}.git" + system "#{gitlab_shell_path}/bin/gitlab-projects", "mv-project", "#{path}.git", "#{new_path}.git" end # Update HEAD for repository @@ -45,7 +45,7 @@ module Gitlab # update_repository_head("gitlab/gitlab-ci", "3-1-stable") # def update_repository_head(path, branch) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "update-head", "#{path}.git", branch + system "#{gitlab_shell_path}/bin/gitlab-projects", "update-head", "#{path}.git", branch end # Fork repository to new namespace @@ -57,7 +57,7 @@ module Gitlab # fork_repository("gitlab/gitlab-ci", "randx") # def fork_repository(path, fork_namespace) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "fork-project", "#{path}.git", fork_namespace + system "#{gitlab_shell_path}/bin/gitlab-projects", "fork-project", "#{path}.git", fork_namespace end # Remove repository from file system @@ -68,7 +68,7 @@ module Gitlab # remove_repository("gitlab/gitlab-ci") # def remove_repository(name) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "rm-project", "#{name}.git" + system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-project", "#{name}.git" end # Add repository branch from passed ref @@ -81,7 +81,7 @@ module Gitlab # add_branch("gitlab/gitlab-ci", "4-0-stable", "master") # def add_branch(path, branch_name, ref) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "create-branch", "#{path}.git", branch_name, ref + system "#{gitlab_shell_path}/bin/gitlab-projects", "create-branch", "#{path}.git", branch_name, ref end # Remove repository branch @@ -93,7 +93,7 @@ module Gitlab # rm_branch("gitlab/gitlab-ci", "4-0-stable") # def rm_branch(path, branch_name) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "rm-branch", "#{path}.git", branch_name + system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-branch", "#{path}.git", branch_name end # Add repository tag from passed ref @@ -106,7 +106,7 @@ module Gitlab # add_tag("gitlab/gitlab-ci", "v4.0", "master") # def add_tag(path, tag_name, ref) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "create-tag", "#{path}.git", tag_name, ref + system "#{gitlab_shell_path}/bin/gitlab-projects", "create-tag", "#{path}.git", tag_name, ref end # Remove repository tag @@ -118,7 +118,7 @@ module Gitlab # rm_tag("gitlab/gitlab-ci", "v4.0") # def rm_tag(path, tag_name) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "rm-tag", "#{path}.git", tag_name + system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-tag", "#{path}.git", tag_name end # Add new key to gitlab-shell @@ -127,7 +127,7 @@ module Gitlab # add_key("key-42", "sha-rsa ...") # def add_key(key_id, key_content) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-keys", "add-key", key_id, key_content + system "#{gitlab_shell_path}/bin/gitlab-keys", "add-key", key_id, key_content end # Remove ssh key from gitlab shell @@ -136,7 +136,7 @@ module Gitlab # remove_key("key-342", "sha-rsa ...") # def remove_key(key_id, key_content) - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-keys", "rm-key", key_id, key_content + system "#{gitlab_shell_path}/bin/gitlab-keys", "rm-key", key_id, key_content end # Remove all ssh keys from gitlab shell @@ -145,7 +145,7 @@ module Gitlab # remove_all_keys # def remove_all_keys - system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-keys", "clear" + system "#{gitlab_shell_path}/bin/gitlab-keys", "clear" end # Add empty directory for storing repositories @@ -198,7 +198,7 @@ module Gitlab # Return GitLab shell version def version - gitlab_shell_version_file = "#{gitlab_shell_user_home}/gitlab-shell/VERSION" + gitlab_shell_version_file = "#{gitlab_shell_path}/VERSION" if File.readable?(gitlab_shell_version_file) File.read(gitlab_shell_version_file) @@ -207,6 +207,10 @@ module Gitlab protected + def gitlab_shell_path + Gitlab.config.gitlab_shell.path + end + def gitlab_shell_user_home File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}") end diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb index 870ed9bd4f..bf83829ee7 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/oauth/user.rb b/lib/gitlab/oauth/user.rb index ea9badba2c..529753c401 100644 --- a/lib/gitlab/oauth/user.rb +++ b/lib/gitlab/oauth/user.rb @@ -28,8 +28,8 @@ module Gitlab } user = model.build_user(opts, as: :admin) + user.skip_confirmation! user.save! - user.confirm! log.info "(OAuth) Creating user #{email} from login with extern_uid => #{uid}" if Gitlab.config.omniauth['block_auto_created_users'] && !ldap? diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb new file mode 100644 index 0000000000..1de681a97f --- /dev/null +++ b/lib/gitlab/upgrader.rb @@ -0,0 +1,96 @@ +require_relative "version_info" + +module Gitlab + class Upgrader + def execute + puts "GitLab #{current_version.major} upgrade tool" + puts "Your version is #{current_version}" + puts "Latest available version for GitLab #{current_version.major} is #{latest_version}" + + if latest_version? + puts "You use latest GitLab version" + else + puts "Newer GitLab version is available" + answer = if ARGV.first == "-y" + "yes" + else + prompt("Do you want to upgrade (yes/no)? ", %w{yes no}) + end + + if answer == "yes" + upgrade + else + exit 0 + end + end + end + + def latest_version? + current_version >= latest_version + end + + def current_version + @current_version ||= Gitlab::VersionInfo.parse(current_version_raw) + end + + def latest_version + @latest_version ||= Gitlab::VersionInfo.parse(latest_version_raw) + end + + def current_version_raw + File.read(File.join(gitlab_path, "VERSION")).strip + end + + def latest_version_raw + git_tags = `git ls-remote --tags origin | grep tags\/v#{current_version.major}` + git_tags = git_tags.lines.to_a.select { |version| version =~ /v\d\.\d\.\d\Z/ } + last_tag = git_tags.last.match(/v\d\.\d\.\d/).to_s + end + + def update_commands + { + "Stash changed files" => "git stash", + "Get latest code" => "git fetch", + "Switch to new version" => "git checkout -b v#{latest_version}", + "Install gems" => "bundle", + "Migrate DB" => "bundle exec rake db:migrate RAILS_ENV=production", + "Recompile assets" => "bundle exec rake assets:clean assets:precompile RAILS_ENV=production", + "Clear cache" => "bundle exec rake cache:clear RAILS_ENV=production" + } + end + + def upgrade + update_commands.each do |title, cmd| + puts title + puts " -> #{cmd}" + if system(cmd) + puts " -> OK" + else + puts " -> FAILED" + puts "Failed to upgrade. Try to repeat task or proceed with upgrade manually " + exit 1 + end + end + + puts "Done" + end + + def gitlab_path + File.expand_path(File.join(File.dirname(__FILE__), '../..')) + end + + # Prompt the user to input something + # + # message - the message to display before input + # choices - array of strings of acceptable answers or nil for any answer + # + # Returns the user's answer + def prompt(message, choices = nil) + begin + print(message) + answer = STDIN.gets.chomp + end while !choices.include?(answer) + answer + end + end +end diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb new file mode 100644 index 0000000000..eada9bcddf --- /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/support/deploy/deploy.sh b/lib/support/deploy/deploy.sh index 0d2f8418bc..b96f73058b 100755 --- a/lib/support/deploy/deploy.sh +++ b/lib/support/deploy/deploy.sh @@ -28,17 +28,18 @@ sudo -u git -H git pull origin master echo 'Deploy: Bundle and migrate' # change it to your needs -sudo -u git -H bundle --without postgres +sudo -u git -H bundle --without aws development test postgres --deployment sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production +sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production +sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production # return stashed changes (if necessary) # sudo -u git -H git stash pop - echo 'Deploy: Starting GitLab server...' sudo service gitlab start -sleep 10 sudo -u git -H rm /home/git/gitlab/public/index.html -echo 'Deploy: Done' +echo 'Deploy: Done' \ No newline at end of file diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index fbb7380ac4..f1b94087b6 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -15,11 +15,20 @@ # Description: GitLab git repository management ### END INIT INFO + +### +# DO NOT EDIT THIS FILE! +# This file will be overwritten on update. +# Instead add/change your variables in /etc/default/gitlab +# An example defaults file can be found in lib/support/default/gitlab +### + + ### Environment variables RAILS_ENV="production" -# Script variable names should be lower-case not to conflict with internal -# /bin/sh variables such as PATH, EDITOR or SHELL. +# Script variable names should be lower-case not to conflict with +# internal /bin/sh variables such as PATH, EDITOR or SHELL. app_user="git" app_root="/home/$app_user/gitlab" pid_path="$app_root/tmp/pids" @@ -27,21 +36,20 @@ socket_path="$app_root/tmp/sockets" web_server_pid_path="$pid_path/unicorn.pid" sidekiq_pid_path="$pid_path/sidekiq.pid" - - -### Here ends user configuration ### - +# Read configuration variable file if it is present +test -f /etc/default/gitlab && . /etc/default/gitlab # Switch to the app_user if it is not he/she who is running the script. if [ "$USER" != "$app_user" ]; then sudo -u "$app_user" -H -i $0 "$@"; exit; fi -# Switch to the gitlab path, if it fails exit with an error. +# Switch to the gitlab path, exit on failure. if ! cd "$app_root" ; then echo "Failed to cd into $app_root, exiting!"; exit 1 fi + ### Init Script functions ## Gets the pids from the files diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example new file mode 100755 index 0000000000..4230783a9d --- /dev/null +++ b/lib/support/init.d/gitlab.default.example @@ -0,0 +1,14 @@ +# Copy this lib/support/init.d/gitlab.default.example file to +# /etc/default/gitlab in order for it to apply to your system. + +# RAILS_ENV defines the type of installation that is running. +# Normal values are "production", "test" and "development". +RAILS_ENV="production" + +# app_user defines the user that GitLab is run as. +# The default is "git". +app_user="git" + +# app_root defines the folder in which gitlab and it's components are installed. +# The default is "/home/$app_user/gitlab" +app_root="/home/$app_user/gitlab" diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 3e929c5299..9f5155b391 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; @@ -32,6 +35,7 @@ server { proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://gitlab; } diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index f2349ca8c1..20d5f03d6e 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} @@ -679,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 ########################## diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake index ac2c4577c7..c46d5855fa 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 057f74ac7a..bfb74960c4 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/script/upgrade.rb b/script/upgrade.rb new file mode 100644 index 0000000000..a5caecf852 --- /dev/null +++ b/script/upgrade.rb @@ -0,0 +1,3 @@ +require_relative "../lib/gitlab/upgrader" + +Gitlab::Upgrader.new.execute diff --git a/spec/contexts/fork_context_spec.rb b/spec/contexts/fork_context_spec.rb index ed51b0c3f8..70f650bc83 100644 --- a/spec/contexts/fork_context_spec.rb +++ b/spec/contexts/fork_context_spec.rb @@ -48,7 +48,7 @@ describe Projects::ForkContext do def fork_project(from_project, user, fork_success = true) context = Projects::ForkContext.new(from_project, user) - shell = mock("gitlab_shell") + shell = double("gitlab_shell") shell.stub(fork_repository: fork_success) context.stub(gitlab_shell: shell) context.execute diff --git a/spec/contexts/issues/list_context_spec.rb b/spec/contexts/issues/list_context_spec.rb new file mode 100644 index 0000000000..70ce956499 --- /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 8b2a49dbee..d5b1cb8351 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 0000000000..edcaf844e5 --- /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 58f747e872..c25743e003 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/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index d528d12c66..e1c0269b29 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -8,7 +8,7 @@ describe ApplicationController do it 'should redirect if the user is over their password expiry' do user.password_expires_at = Time.new(2002) user.ldap_user?.should be_false - controller.stub!(:current_user).and_return(user) + controller.stub(:current_user).and_return(user) controller.should_receive(:redirect_to) controller.should_receive(:new_profile_password_path) controller.send(:check_password_expiration) @@ -17,15 +17,15 @@ describe ApplicationController do it 'should not redirect if the user is under their password expiry' do user.password_expires_at = Time.now + 20010101 user.ldap_user?.should be_false - controller.stub!(:current_user).and_return(user) + controller.stub(:current_user).and_return(user) controller.should_not_receive(:redirect_to) controller.send(:check_password_expiration) end it 'should not redirect if the user is over their password expiry but they are an ldap user' do user.password_expires_at = Time.new(2002) - user.stub!(:ldap_user?).and_return(true) - controller.stub!(:current_user).and_return(user) + user.stub(:ldap_user?).and_return(true) + controller.stub(:current_user).and_return(user) controller.should_not_receive(:redirect_to) controller.send(:check_password_expiration) end diff --git a/spec/factories.rb b/spec/factories.rb index 3b0e305460..daf8417364 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 } diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 8cb984946b..bb0c4dbd5d 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 0000000000..5abccd259d --- /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 7f3f8c50f0..481d8cec41 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 267643fd8e..3f1016473f 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/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index d63a2de880..3644410e46 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe ApplicationHelper do describe 'current_controller?' do before do - controller.stub!(:controller_name).and_return('foo') + controller.stub(:controller_name).and_return('foo') end it "returns true when controller matches argument" do @@ -22,7 +22,7 @@ describe ApplicationHelper do describe 'current_action?' do before do - stub!(:action_name).and_return('foo') + allow(self).to receive(:action_name).and_return('foo') end it "returns true when action matches argument" do @@ -38,7 +38,7 @@ describe ApplicationHelper do current_action?(:baz, :bar, :foo).should be_true end end - + describe "avatar_icon" do avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png') @@ -52,7 +52,7 @@ describe ApplicationHelper do it "should call gravatar_icon when no avatar is present" do user = create(:user) user.save! - stub!(:gravatar_icon).and_return('gravatar_method_called') + allow(self).to receive(:gravatar_icon).and_return('gravatar_method_called') avatar_icon(user.email).to_s.should == "gravatar_method_called" end end @@ -62,41 +62,41 @@ describe ApplicationHelper do it "should return a generic avatar path when Gravatar is disabled" do Gitlab.config.gravatar.stub(:enabled).and_return(false) - gravatar_icon(user_email).should == 'no_avatar.png' + gravatar_icon(user_email).should == '/assets/no_avatar.png' end it "should return a generic avatar path when email is blank" do - gravatar_icon('').should == 'no_avatar.png' + gravatar_icon('').should == '/assets/no_avatar.png' end it "should return default gravatar url" do - stub!(:request).and_return(double(:ssl? => false)) + allow(self).to receive(:request).and_return(double(:ssl? => false)) gravatar_icon(user_email).should match('http://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118') end it "should use SSL when appropriate" do - stub!(:request).and_return(double(:ssl? => true)) + allow(self).to receive(:request).and_return(double(:ssl? => true)) gravatar_icon(user_email).should match('https://secure.gravatar.com') end it "should return custom gravatar path when gravatar_url is set" do - stub!(:request).and_return(double(:ssl? => false)) + allow(self).to receive(:request).and_return(double(:ssl? => false)) Gitlab.config.gravatar.stub(:plain_url).and_return('http://example.local/?s=%{size}&hash=%{hash}') gravatar_icon(user_email, 20).should == 'http://example.local/?s=20&hash=b58c6f14d292556214bd64909bcdb118' end it "should accept a custom size" do - stub!(:request).and_return(double(:ssl? => false)) + allow(self).to receive(:request).and_return(double(:ssl? => false)) gravatar_icon(user_email, 64).should match(/\?s=64/) end it "should use default size when size is wrong" do - stub!(:request).and_return(double(:ssl? => false)) + allow(self).to receive(:request).and_return(double(:ssl? => false)) gravatar_icon(user_email, nil).should match(/\?s=40/) end it "should be case insensitive" do - stub!(:request).and_return(double(:ssl? => false)) + allow(self).to receive(:request).and_return(double(:ssl? => false)) gravatar_icon(user_email).should == gravatar_icon(user_email.upcase + " ") end @@ -105,7 +105,7 @@ describe ApplicationHelper do describe "user_color_scheme_class" do context "with current_user is nil" do it "should return a string" do - stub!(:current_user).and_return(nil) + allow(self).to receive(:current_user).and_return(nil) user_color_scheme_class.should be_kind_of(String) end end @@ -115,7 +115,7 @@ describe ApplicationHelper do context "with color_scheme_id == #{color_scheme_id}" do it "should return a string" do current_user = double(:color_scheme_id => color_scheme_id) - stub!(:current_user).and_return(current_user) + allow(self).to receive(:current_user).and_return(current_user) user_color_scheme_class.should be_kind_of(String) end end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index a0bbc02642..33e69d4326 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("
some code from $#{snippet.id}\nhere too\n
") + target_html = "
some code from $#{snippet.id}"
 
-      helper.markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n").should include("
some code from $#{snippet.id}\nhere too\n
") + 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 @@ -392,7 +393,7 @@ describe GitlabMarkdownHelper do end it "should leave ref-like href of 'manual' links untouched" do - markdown("why not [inspect !#{merge_request.iid}](http://example.tld/#!#{merge_request.iid})").should == "

why not inspect !#{merge_request.iid}

\n" + markdown("why not [inspect !#{merge_request.iid}](http://example.tld/#!#{merge_request.iid})").should == "

why not inspect !#{merge_request.iid}

\n" end it "should leave ref-like src of images untouched" do @@ -434,7 +435,7 @@ describe GitlabMarkdownHelper do describe "#render_wiki_content" do before do - @wiki = stub('WikiPage') + @wiki = double('WikiPage') @wiki.stub(:content).and_return('wiki content') end @@ -448,7 +449,7 @@ describe GitlabMarkdownHelper do it "should use the Gollum renderer for all other file types" do @wiki.stub(:format).and_return(:rdoc) - formatted_content_stub = stub('formatted_content') + formatted_content_stub = double('formatted_content') formatted_content_stub.should_receive(:html_safe) @wiki.stub(:formatted_content).and_return(formatted_content_stub) diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb index 328f66237c..c1efc1fb2a 100644 --- a/spec/helpers/notifications_helper_spec.rb +++ b/spec/helpers/notifications_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe NotificationsHelper do describe 'notification_icon' do - let(:notification) { stub(disabled?: false, participating?: false, watch?: false) } + let(:notification) { double(disabled?: false, participating?: false, watch?: false) } context "disabled notification" do before { notification.stub(disabled?: true) } diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 8156bcdd99..114058e309 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -11,12 +11,12 @@ describe ProjectsHelper do it "returns the correct issues trackers available with current tracker 'gitlab' selected" do project_issues_trackers('gitlab').should == "\n" \ - "" + "" end it "returns the correct issues trackers available with current tracker 'redmine' selected" do project_issues_trackers('redmine').should == - "\n" \ + "\n" \ "" end end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index 55b6b6b4da..33ecb98020 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -8,7 +8,9 @@ describe SearchHelper do describe 'search_autocomplete_source' do context "with no current user" do - before { stub!(:current_user).and_return(nil) } + before do + allow(self).to receive(:current_user).and_return(nil) + end it "it returns nil" do search_autocomplete_source.should be_nil @@ -20,7 +22,7 @@ describe SearchHelper do let(:result) { JSON.parse(search_autocomplete_source) } before do - stub!(:current_user).and_return(user) + allow(self).to receive(:current_user).and_return(user) end it "includes Help sections" do diff --git a/spec/helpers/tab_helper_spec.rb b/spec/helpers/tab_helper_spec.rb index ef8e4cf637..fa8a3f554f 100644 --- a/spec/helpers/tab_helper_spec.rb +++ b/spec/helpers/tab_helper_spec.rb @@ -5,8 +5,8 @@ describe TabHelper do describe 'nav_link' do before do - controller.stub!(:controller_name).and_return('foo') - stub!(:action_name).and_return('foo') + controller.stub(:controller_name).and_return('foo') + allow(self).to receive(:action_name).and_return('foo') end it "captures block output" do diff --git a/spec/javascripts/stat_graph_contributors_graph_spec.js b/spec/javascripts/stat_graph_contributors_graph_spec.js index 8d2e2038a5..1090cb7f62 100644 --- a/spec/javascripts/stat_graph_contributors_graph_spec.js +++ b/spec/javascripts/stat_graph_contributors_graph_spec.js @@ -88,19 +88,20 @@ describe("ContributorsGraph", function () { describe("ContributorsMasterGraph", function () { - describe("#process_dates", function () { - it("gets and parses dates", function () { - var graph = new ContributorsMasterGraph() - var data = 'random data here' - spyOn(graph, 'parse_dates') - spyOn(graph, 'get_dates').andReturn("get") - spyOn(ContributorsGraph,'set_dates').andCallThrough() - graph.process_dates(data) - expect(graph.parse_dates).toHaveBeenCalledWith(data) - expect(graph.get_dates).toHaveBeenCalledWith(data) - expect(ContributorsGraph.set_dates).toHaveBeenCalledWith("get") - }) - }) + // TODO: fix or remove + //describe("#process_dates", function () { + //it("gets and parses dates", function () { + //var graph = new ContributorsMasterGraph() + //var data = 'random data here' + //spyOn(graph, 'parse_dates') + //spyOn(graph, 'get_dates').andReturn("get") + //spyOn(ContributorsGraph,'set_dates').andCallThrough() + //graph.process_dates(data) + //expect(graph.parse_dates).toHaveBeenCalledWith(data) + //expect(graph.get_dates).toHaveBeenCalledWith(data) + //expect(ContributorsGraph.set_dates).toHaveBeenCalledWith("get") + //}) + //}) describe("#get_dates", function () { it("plucks the date field from data collection", function () { diff --git a/spec/javascripts/stat_graph_contributors_util_spec.js b/spec/javascripts/stat_graph_contributors_util_spec.js index 2e52479ccb..9c1b588861 100644 --- a/spec/javascripts/stat_graph_contributors_util_spec.js +++ b/spec/javascripts/stat_graph_contributors_util_spec.js @@ -54,16 +54,17 @@ describe("ContributorsStatGraphUtil", function () { }) - describe("#store_commits", function () { - var fake_total = "fake_total" - var fake_by_author = "fake_by_author" + // TODO: fix or remove + //describe("#store_commits", function () { + //var fake_total = "fake_total" + //var fake_by_author = "fake_by_author" - it("calls #add twice with arguments fake_total and fake_by_author respectively", function () { - spyOn(ContributorsStatGraphUtil, 'add') - ContributorsStatGraphUtil.store_commits(fake_total, fake_by_author) - expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "commits", 1], ["fake_by_author", "commits", 1]]) - }) - }) + //it("calls #add twice with arguments fake_total and fake_by_author respectively", function () { + //spyOn(ContributorsStatGraphUtil, 'add') + //ContributorsStatGraphUtil.store_commits(fake_total, fake_by_author) + //expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "commits", 1], ["fake_by_author", "commits", 1]]) + //}) + //}) describe("#add", function () { it("adds 1 to current test_field in collection", function () { @@ -79,27 +80,29 @@ describe("ContributorsStatGraphUtil", function () { }) }) - describe("#store_additions", function () { - var fake_entry = {additions: 10} - var fake_total= "fake_total" - var fake_by_author = "fake_by_author" - it("calls #add twice with arguments fake_total and fake_by_author respectively", function () { - spyOn(ContributorsStatGraphUtil, 'add') - ContributorsStatGraphUtil.store_additions(fake_entry, fake_total, fake_by_author) - expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "additions", 10], ["fake_by_author", "additions", 10]]) - }) - }) + // TODO: fix or remove + //describe("#store_additions", function () { + //var fake_entry = {additions: 10} + //var fake_total= "fake_total" + //var fake_by_author = "fake_by_author" + //it("calls #add twice with arguments fake_total and fake_by_author respectively", function () { + //spyOn(ContributorsStatGraphUtil, 'add') + //ContributorsStatGraphUtil.store_additions(fake_entry, fake_total, fake_by_author) + //expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "additions", 10], ["fake_by_author", "additions", 10]]) + //}) + //}) - describe("#store_deletions", function () { - var fake_entry = {deletions: 10} - var fake_total= "fake_total" - var fake_by_author = "fake_by_author" - it("calls #add twice with arguments fake_total and fake_by_author respectively", function () { - spyOn(ContributorsStatGraphUtil, 'add') - ContributorsStatGraphUtil.store_deletions(fake_entry, fake_total, fake_by_author) - expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "deletions", 10], ["fake_by_author", "deletions", 10]]) - }) - }) + // TODO: fix or remove + //describe("#store_deletions", function () { + //var fake_entry = {deletions: 10} + //var fake_total= "fake_total" + //var fake_by_author = "fake_by_author" + //it("calls #add twice with arguments fake_total and fake_by_author respectively", function () { + //spyOn(ContributorsStatGraphUtil, 'add') + //ContributorsStatGraphUtil.store_deletions(fake_entry, fake_total, fake_by_author) + //expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "deletions", 10], ["fake_by_author", "deletions", 10]]) + //}) + //}) describe("#add_date", function () { it("adds a date field to the collection", function () { diff --git a/spec/javascripts/support/jasmine_helper.rb b/spec/javascripts/support/jasmine_helper.rb index 34b418a9ca..b4919802af 100644 --- a/spec/javascripts/support/jasmine_helper.rb +++ b/spec/javascripts/support/jasmine_helper.rb @@ -1,5 +1,11 @@ -WebMock.allow_net_connect! +#Use this file to set/override Jasmine configuration options +#You can remove it if you don't need it. +#This file is loaded *after* jasmine.yml is interpreted. +# +#Example: using a different boot file. +#Jasmine.configure do |config| +# config.boot_dir = '/absolute/path/to/boot_dir' +# config.boot_files = lambda { ['/absolute/path/to/boot_dir/file.js'] } +#end +# -Jasmine.configure do |config| - config.browser = :phantomjs -end diff --git a/spec/lib/auth_spec.rb b/spec/lib/auth_spec.rb index e05fde9573..073b811c3f 100644 --- a/spec/lib/auth_spec.rb +++ b/spec/lib/auth_spec.rb @@ -8,21 +8,21 @@ describe Gitlab::Auth do @user = create( :user, username: 'john', - password: '888777', - password_confirmation: '888777' + password: '88877711', + password_confirmation: '88877711' ) end it "should find user by valid login/password" do - gl_auth.find('john', '888777').should == @user + gl_auth.find('john', '88877711').should == @user end it "should not find user with invalid password" do - gl_auth.find('john', 'invalid').should_not == @user + gl_auth.find('john', 'invalid11').should_not == @user end it "should not find user with invalid login and password" do - gl_auth.find('jon', 'invalid').should_not == @user + gl_auth.find('jon', 'invalid11').should_not == @user end end end diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index aac72c63ea..7b3818ea5c 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -7,7 +7,7 @@ describe ExtractsPath do before do @project = project - project.stub(repository: stub(ref_names: ['master', 'foo/bar/baz', 'v1.0.0', 'v2.0.0'])) + project.stub(repository: double(ref_names: ['master', 'foo/bar/baz', 'v1.0.0', 'v2.0.0'])) project.stub(path_with_namespace: 'gitlab/gitlab-ci') end diff --git a/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb b/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb index df5252e461..eee8356221 100644 --- a/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb +++ b/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::LDAP do before do Gitlab.config.stub(omniauth: {}) - @info = mock( + @info = double( uid: '12djsak321', name: 'John', email: 'john@mail.com', @@ -16,7 +16,7 @@ describe Gitlab::LDAP do describe :find_for_ldap_auth do before do - @auth = mock( + @auth = double( uid: '12djsak321', info: @info, provider: 'ldap' diff --git a/spec/lib/gitlab/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb new file mode 100644 index 0000000000..2b254d6b3a --- /dev/null +++ b/spec/lib/gitlab/upgrader_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe Gitlab::Upgrader do + let(:upgrader) { Gitlab::Upgrader.new } + let(:current_version) { Gitlab::VERSION } + + describe 'current_version_raw' do + it { upgrader.current_version_raw.should == current_version } + end + + describe 'latest_version?' do + it 'should be true if newest version' do + upgrader.stub(latest_version_raw: current_version) + upgrader.latest_version?.should be_true + end + end + + describe 'latest_version_raw' do + it 'should be latest version for GitLab 5' do + upgrader.stub(current_version_raw: "5.3.0") + upgrader.latest_version_raw.should == "v5.4.2" + end + end +end diff --git a/spec/lib/oauth_spec.rb b/spec/lib/oauth_spec.rb index e21074554b..3dfe95a8e3 100644 --- a/spec/lib/oauth_spec.rb +++ b/spec/lib/oauth_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::OAuth::User do before do Gitlab.config.stub(omniauth: {}) - @info = mock( + @info = double( uid: '12djsak321', name: 'John', email: 'john@mail.com' @@ -15,7 +15,7 @@ describe Gitlab::OAuth::User do describe :create do it "should create user from LDAP" do - @auth = mock(info: @info, provider: 'ldap') + @auth = double(info: @info, provider: 'ldap') user = gl_auth.create(@auth) user.should be_valid @@ -24,7 +24,7 @@ describe Gitlab::OAuth::User do end it "should create user from Omniauth" do - @auth = mock(info: @info, provider: 'twitter') + @auth = double(info: @info, provider: 'twitter') user = gl_auth.create(@auth) user.should be_valid @@ -33,7 +33,7 @@ describe Gitlab::OAuth::User do end it "should apply defaults to user" do - @auth = mock(info: @info, provider: 'ldap') + @auth = double(info: @info, provider: 'ldap') user = gl_auth.create(@auth) user.should be_valid diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index d287239cfe..b1e5348681 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -391,4 +391,28 @@ describe Notify do should have_body_text /#{example_site_path}/ end end + + describe 'email on push' do + let(:example_site_path) { root_path } + let(:user) { create(:user) } + let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, 'cd5c4bac', 'b1e6a9db') } + + subject { Notify.repository_push_email(project.id, 'devs@company.name', user.id, 'master', compare) } + + it 'is sent to recipient' do + should deliver_to 'devs@company.name' + end + + it 'has the correct subject' do + should have_subject /New push to repository/ + end + + it 'includes commits list' do + should have_body_text /tree css fixes/ + end + + it 'includes diffs' do + should have_body_text /Checkout wiki pages for installation information/ + end + end end diff --git a/spec/models/assembla_service_spec.rb b/spec/models/assembla_service_spec.rb new file mode 100644 index 0000000000..0b961c81ac --- /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/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 852146ebae..0827e4f162 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -34,7 +34,7 @@ describe Issue, "Issuable" do let!(:searchable_issue) { create(:issue, title: "Searchable issue") } it "matches by title" do - described_class.search('able').all.should == [searchable_issue] + described_class.search('able').should == [searchable_issue] end end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 85bdf08ae6..53ede0d5ee 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -67,12 +67,12 @@ describe Event do end describe 'Team events' do - let(:user_project) { stub.as_null_object } + let(:user_project) { double.as_null_object } let(:observer) { UsersProjectObserver.instance } before { Event.should_receive :create - observer.stub(notification: stub.as_null_object) + observer.stub(notification: double.as_null_object) } describe "Joined project team" do diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb index 44b8c6155b..472ddf1b59 100644 --- a/spec/models/forked_project_link_spec.rb +++ b/spec/models/forked_project_link_spec.rb @@ -59,7 +59,7 @@ end def fork_project(from_project, user) context = Projects::ForkContext.new(from_project, user) - shell = mock("gitlab_shell") + shell = double("gitlab_shell") shell.stub(fork_repository: true) context.stub(gitlab_shell: shell) context.execute diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index fe65096fd1..039a0c087b 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -107,12 +107,12 @@ describe MergeRequest do describe 'detection of issues to be closed' do let(:issue0) { create :issue, project: subject.project } let(:issue1) { create :issue, project: subject.project } - let(:commit0) { mock('commit0', closes_issues: [issue0]) } - let(:commit1) { mock('commit1', closes_issues: [issue0]) } - let(:commit2) { mock('commit2', closes_issues: [issue1]) } + let(:commit0) { double('commit0', closes_issues: [issue0]) } + let(:commit1) { double('commit1', closes_issues: [issue0]) } + let(:commit2) { double('commit2', closes_issues: [issue1]) } before do - subject.stub(unmerged_commits: [commit0, commit1, commit2]) + subject.stub(commits: [commit0, commit1, commit2]) end it 'accesses the set of issues that will be closed on acceptance' do diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 42c405d8e5..55b264ce8c 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 d5803d8cec..8aa4c7fed1 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -14,13 +14,14 @@ # merge_requests_enabled :boolean default(TRUE), not null # wiki_enabled :boolean default(TRUE), not null # namespace_id :integer -# public :boolean default(FALSE), not null # issues_tracker :string(255) default("gitlab"), not null # issues_tracker_id :string(255) # snippets_enabled :boolean default(TRUE), not null # last_activity_at :datetime # imported :boolean default(FALSE), not null # import_url :string(255) +# visibility_level :integer default(0), not null +# archived :boolean default(FALSE), not null # require 'spec_helper' @@ -70,7 +71,7 @@ describe Project do it "should not allow new projects beyond user limits" do project2 = build(:project) - project2.stub(:creator).and_return(double(can_create_project?: false, projects_limit: 0)) + project2.stub(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object) project2.should_not be_valid project2.errors[:limit_reached].first.should match(/Your own projects limit is 0/) end diff --git a/spec/models/service_hook_spec.rb b/spec/models/service_hook_spec.rb index 0b0262c97f..40a5fbc71d 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 a9ed6a5fa7..6a0d99dcc5 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 8b9e0b3574..59f75ae552 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -90,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 2d9301732d..d603408101 100644 --- a/spec/models/web_hook_spec.rb +++ b/spec/models/web_hook_spec.rb @@ -2,13 +2,16 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# type :string(255) default("ProjectHook") -# service_id :integer +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null # require 'spec_helper' diff --git a/spec/observers/issue_observer_spec.rb b/spec/observers/issue_observer_spec.rb index 4155ff3119..9a0a2c4329 100644 --- a/spec/observers/issue_observer_spec.rb +++ b/spec/observers/issue_observer_spec.rb @@ -9,7 +9,7 @@ describe IssueObserver do before { subject.stub(:current_user).and_return(some_user) } before { subject.stub(:current_commit).and_return(nil) } - before { subject.stub(notification: mock('NotificationService').as_null_object) } + before { subject.stub(notification: double('NotificationService').as_null_object) } before { mock_issue.project.stub_chain(:repository, :commit).and_return(nil) } subject { IssueObserver.instance } diff --git a/spec/observers/merge_request_observer_spec.rb b/spec/observers/merge_request_observer_spec.rb index 3f5250a004..a450b4d518 100644 --- a/spec/observers/merge_request_observer_spec.rb +++ b/spec/observers/merge_request_observer_spec.rb @@ -4,14 +4,14 @@ 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)) } let(:closed_unassigned_mr) { create(:closed_merge_request, author: author, target_project: create(:project)) } before { subject.stub(:current_user).and_return(some_user) } - before { subject.stub(notification: mock('NotificationService').as_null_object) } + before { subject.stub(notification: double('NotificationService').as_null_object) } before { mr_mock.stub(:author_id) } before { mr_mock.stub(:target_project) } before { mr_mock.stub(:source_project) } diff --git a/spec/observers/note_observer_spec.rb b/spec/observers/note_observer_spec.rb index f9b96c255c..f8693355b2 100644 --- a/spec/observers/note_observer_spec.rb +++ b/spec/observers/note_observer_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe NoteObserver do subject { NoteObserver.instance } - before { subject.stub(notification: mock('NotificationService').as_null_object) } + before { subject.stub(notification: double('NotificationService').as_null_object) } let(:team_without_author) { (1..2).map { |n| double :user, id: n } } let(:note) { double(:note).as_null_object } diff --git a/spec/observers/user_observer_spec.rb b/spec/observers/user_observer_spec.rb index b74fceb98b..9aeade535f 100644 --- a/spec/observers/user_observer_spec.rb +++ b/spec/observers/user_observer_spec.rb @@ -4,7 +4,7 @@ describe UserObserver do before(:each) { enable_observers } after(:each) {disable_observers} subject { UserObserver.instance } - before { subject.stub(notification: mock('NotificationService').as_null_object) } + before { subject.stub(notification: double('NotificationService').as_null_object) } it 'calls #after_create when new users are created' do new_user = build(:user) diff --git a/spec/observers/users_group_observer_spec.rb b/spec/observers/users_group_observer_spec.rb index 65484806b1..2ab99c33b7 100644 --- a/spec/observers/users_group_observer_spec.rb +++ b/spec/observers/users_group_observer_spec.rb @@ -5,7 +5,7 @@ describe UsersGroupObserver do after(:each) { disable_observers } subject { UsersGroupObserver.instance } - before { subject.stub(notification: mock('NotificationService').as_null_object) } + before { subject.stub(notification: double('NotificationService').as_null_object) } describe "#after_create" do it "should send email to user" do diff --git a/spec/observers/users_project_observer_spec.rb b/spec/observers/users_project_observer_spec.rb index e7c624fce5..dea90b2bfa 100644 --- a/spec/observers/users_project_observer_spec.rb +++ b/spec/observers/users_project_observer_spec.rb @@ -7,27 +7,7 @@ describe UsersProjectObserver do let(:user) { create(:user) } let(:project) { create(:project) } subject { UsersProjectObserver.instance } - before { subject.stub(notification: mock('NotificationService').as_null_object) } - - describe "#after_commit" do - it "should called when UsersProject created" do - subject.should_receive(:after_commit) - create(:users_project) - end - - it "should send email to user" do - subject.should_receive(:notification) - Event.stub(create: true) - - create(:users_project) - end - - it "should create new event" do - Event.should_receive(:create) - - create(:users_project) - end - end + before { subject.stub(notification: double('NotificationService').as_null_object) } describe "#after_update" do before do @@ -35,7 +15,7 @@ describe UsersProjectObserver do end it "should called when UsersProject updated" do - subject.should_receive(:after_commit) + subject.should_receive(:after_update) @users_project.update_attribute(:project_access, UsersProject::MASTER) end @@ -45,7 +25,7 @@ describe UsersProjectObserver do end it "should not called after UsersProject destroyed" do - subject.should_not_receive(:after_commit) + subject.should_not_receive(:after_update) @users_project.destroy end end @@ -90,5 +70,18 @@ describe UsersProjectObserver do it { File.exists?(@path).should be_false } end end + + it "should send email to user" do + subject.should_receive(:notification) + Event.stub(create: true) + + create(:users_project) + end + + it "should create new event" do + Event.should_receive(:create) + + create(:users_project) + end end -end \ No newline at end of file +end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index d6acc80617..31feb6db91 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -149,7 +149,7 @@ describe API::API do describe "POST /groups/:id/projects/:project_id" do let(:project) { create(:project) } before(:each) do - project.stub!(:transfer).and_return(true) + project.stub(:transfer).and_return(true) Project.stub(:find).and_return(project) end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index e8870f4d5d..5f6dff92c0 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -103,6 +103,33 @@ describe API::API do end end + context "archived project" do + let(:personal_project) { create(:project, namespace: user.namespace) } + + before do + project.team << [user, :developer] + project.archive! + end + + context "git pull" do + it do + pull(key, project) + + response.status.should == 200 + response.body.should == 'true' + end + end + + context "git push" do + it do + push(key, project) + + response.status.should == 200 + response.body.should == 'false' + end + end + end + context "deploy key" do let(:key) { create(:deploy_key) } diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb new file mode 100644 index 0000000000..beccd61866 --- /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 24f7ef580b..9c3b5e9a63 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) } @@ -132,15 +131,45 @@ 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 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 @@ -183,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 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 @@ -382,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) @@ -649,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) @@ -752,8 +693,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 @@ -767,7 +710,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 @@ -777,8 +720,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 0fd90c567e..668007dc29 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 1b1d19d26b..1af052d873 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 2870f59195..b46022fb2d 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 025534a900..cc0ec2f4e3 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -16,7 +16,7 @@ module LoginHelpers def login_with(user) visit new_user_session_path fill_in "user_login", with: user.email - fill_in "user_password", with: "123456" + fill_in "user_password", with: "12345678" click_button "Sign in" Thread.current[:current_user] = user end diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index a7f189777c..948fff27b8 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -12,7 +12,7 @@ def common_mentionable_setup let(:mentioned_issue) { create :issue, project: mproject } let(:other_issue) { create :issue, project: mproject } let(:mentioned_mr) { create :merge_request, target_project: mproject, source_branch: 'different' } - let(:mentioned_commit) { mock('commit', sha: '1234567890abcdef').as_null_object } + let(:mentioned_commit) { double('commit', sha: '1234567890abcdef').as_null_object } # Override to add known commits to the repository stub. let(:extra_commits) { [] } @@ -30,7 +30,7 @@ def common_mentionable_setup commitmap = { '123456' => mentioned_commit } extra_commits.each { |c| commitmap[c.sha[0..5]] = c } - repo = mock('repository') + repo = double('repository') repo.stub(:commit) { |sha| commitmap[sha] } mproject.stub(repository: repo) diff --git a/vendor/assets/javascripts/ace-src-noconflict/mode-diff.js b/vendor/assets/javascripts/ace-src-noconflict/mode-diff.js index 75e26cc705..8d1e7cee09 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",