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 @@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 = <some code from $#{snippet.id}\nhere too\nsome 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",