diff --git a/.gitignore b/.gitignore index 683e6c45a2..084edd30df 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ Vagrantfile config/gitlab.yml config/database.yml config/initializers/omniauth.rb +config/initializers/rack_attack.rb config/unicorn.rb config/resque.yml config/aws.yml diff --git a/.travis.yml b/.travis.yml index d7fbbc7bbc..f0fc2fb882 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,6 @@ branches: only: - 'master' rvm: - - 1.9.3-p392 - 2.0.0 services: - mysql @@ -16,7 +15,4 @@ services: before_script: - "cp config/database.yml.$DB config/database.yml" - "cp config/gitlab.yml.example config/gitlab.yml" - - "bundle exec rake db:setup RAILS_ENV=test" - - "bundle exec rake db:seed_fu RAILS_ENV=test" - - "sh -e /etc/init.d/xvfb start" -script: "bundle exec rake travis --trace" +script: "bundle exec rake gitlab:test --trace" diff --git a/CHANGELOG b/CHANGELOG index e955925138..6dae128b79 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,26 @@ +v 6.2.0 + - Public project pages are now visible to everyone (files, issues, wik, etc.) + THIS MEANS YOUR ISSUES AND WIKI FOR PUBLIC PROJECTS ARE PUBLICLY VISIBLE AFTER THE UPGRADE + - Add group access to permissions page + - Require current password to change one + - Group owner or admin can remove other group owners + - Remove group transfer since we have multiple owners + - Respect authorization in Repository API + - Improve UI for Project#files page + - Add more security specs + - Added search for projects by name to api (Izaak Alpert) + - Make default user theme configurable (Izaak Alpert) + - Update logic for validates_merge_request for tree of MR (Andrew Kumanyaev) + - Rake tasks for web hooks management (Jonhnny Weslley) + - Extended User API to expose admin and can_create_group for user creation/updating (Boyan Tabakov) + - API: Remove group + - API: Remove project + - Avatar upload on profile page with a maximum of 200KB (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 changed email + v 6.1.0 - Project specific IDs for issues, mr, milestones Above items will get a new id and for example all bookmarked issue urls will change. diff --git a/Gemfile b/Gemfile index 7c8f8d1bad..864172cd78 100644 --- a/Gemfile +++ b/Gemfile @@ -16,6 +16,7 @@ gem "pg", group: :postgres # Auth gem "devise", '~> 2.2' +gem "devise-async" gem 'omniauth', "~> 1.1.3" gem 'omniauth-google-oauth2' gem 'omniauth-twitter' @@ -23,7 +24,7 @@ gem 'omniauth-github' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '2.3.1' +gem "gitlab_git", "~> 3.0.0.rc2" # Ruby/Rack Git Smart-HTTP Server Handler gem 'gitlab-grack', '~> 1.0.1', require: 'grack' @@ -112,6 +113,9 @@ gem 'tinder', '~> 1.9.2' # HipChat integration gem "hipchat", "~> 0.9.0" +# Flowdock integration +gem "gitlab-flowdock-git-hook", "~> 0.4.2" + # d3 gem "d3_rails", "~> 3.1.4" @@ -121,6 +125,9 @@ gem "underscore-rails", "~> 1.4.4" # Sanitize user input gem "sanitize" +# Protect against bruteforcing +gem "rack-attack" + group :assets do gem "sass-rails" gem "coffee-rails" @@ -143,10 +150,11 @@ group :assets do end group :development do - gem "annotate", git: "https://github.com/ctran/annotate_models.git" + gem "annotate", "~> 2.6.0.beta2" gem "letter_opener" gem 'quiet_assets', '~> 1.0.1' gem 'rack-mini-profiler' + # Better errors handler gem 'better_errors' gem 'binding_of_caller' diff --git a/Gemfile.lock b/Gemfile.lock index fbf72ca42a..35db180a4e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,11 +1,3 @@ -GIT - remote: https://github.com/ctran/annotate_models.git - revision: 18a4e2eb77c8f3ef695b563e4a7ca45dfede819b - specs: - annotate (2.6.0.beta2) - activerecord (>= 2.3.0) - rake (>= 0.8.7) - GEM remote: https://rubygems.org/ specs: @@ -39,17 +31,20 @@ GEM acts-as-taggable-on (2.4.1) rails (>= 3, < 5) addressable (2.3.4) + annotate (2.6.0.beta2) + activerecord (>= 2.3.0) + rake (>= 0.8.7) arel (3.0.2) asciidoctor (0.1.3) - awesome_print (1.1.0) + awesome_print (1.2.0) backports (3.3.2) bcrypt-ruby (3.1.1) - better_errors (0.9.0) + better_errors (1.0.1) coderay (>= 1.0.0) erubis (>= 2.6.6) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) - bootstrap-sass (2.3.2.0) + bootstrap-sass (2.3.2.2) sass (~> 3.2) builder (3.0.4) capybara (2.1.0) @@ -73,7 +68,7 @@ GEM sass-rails (>= 3.2) chunky_png (1.2.8) cliver (0.2.1) - code_analyzer (0.3.2) + code_analyzer (0.4.3) sexp_processor coderay (1.0.9) coffee-rails (3.2.2) @@ -92,11 +87,11 @@ GEM compass-rails (1.0.3) compass (>= 0.12.2, < 0.14) connection_pool (1.1.0) - coveralls (0.6.7) - colorize + coveralls (0.7.0) multi_json (~> 1.3) rest-client simplecov (>= 0.7) + term-ansicolor thor crack (0.4.0) safe_yaml (~> 0.9.0) @@ -111,6 +106,8 @@ GEM orm_adapter (~> 0.1) railties (~> 3.1) warden (~> 1.2.1) + devise-async (0.8.0) + devise (>= 2.2, < 3.2) diff-lcs (1.2.4) dotenv (0.8.0) email_spec (1.4.0) @@ -133,7 +130,7 @@ GEM multipart-post (~> 1.1) faraday_middleware (0.9.0) faraday (>= 0.7.4, < 0.9) - ffaker (1.16.1) + ffaker (1.18.0) ffi (1.9.0) fog (1.3.1) builder @@ -145,7 +142,7 @@ GEM net-ssh (>= 2.1.3) nokogiri (~> 1.5.0) ruby-hmac - font-awesome-rails (3.2.1.2) + font-awesome-rails (3.2.1.3) railties (>= 3.2, < 5.0) foreman (0.63.0) dotenv (>= 0.7) @@ -161,6 +158,9 @@ GEM pygments.rb (>= 0.2.13) github-markdown (0.5.3) 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) github-markdown (~> 0.5.3) github-markup (>= 0.7.5, < 1.0.0) @@ -171,7 +171,7 @@ GEM stringex (~> 1.5.1) gitlab-grack (1.0.1) rack (~> 1.4.1) - gitlab-grit (2.6.0) + gitlab-grit (2.6.1) charlock_holmes (~> 0.6.9) diff-lcs (~> 1.1) mime-types (~> 1.15) @@ -179,10 +179,10 @@ GEM gitlab-pygments.rb (0.3.2) posix-spawn (~> 0.3.6) yajl-ruby (~> 1.1.0) - gitlab_git (2.3.1) + gitlab_git (3.0.0.rc2) activesupport (~> 3.2.13) github-linguist (~> 2.3.4) - gitlab-grit (~> 2.6.0) + gitlab-grit (~> 2.6.1) gitlab_meta (6.0) gitlab_omniauth-ldap (1.0.3) net-ldap (~> 0.3.1) @@ -278,7 +278,7 @@ GEM minitest (4.7.4) modernizr (2.6.2) sprockets (~> 2.0) - multi_json (1.7.9) + multi_json (1.8.0) multi_xml (0.5.4) multipart-post (1.2.0) mysql2 (0.3.11) @@ -334,9 +334,11 @@ GEM rack (1.4.5) rack-accept (0.4.5) rack (>= 0.4) + rack-attack (2.2.1) + rack rack-cache (1.2) rack (>= 0.4) - rack-mini-profiler (0.1.26) + rack-mini-profiler (0.1.31) rack (>= 1.1.3) rack-mount (0.8.3) rack (>= 1.0.0) @@ -357,13 +359,14 @@ GEM rails-dev-tweaks (0.6.1) actionpack (~> 3.1) railties (~> 3.1) - rails_best_practices (1.13.8) + rails_best_practices (1.14.4) activesupport awesome_print - code_analyzer + code_analyzer (>= 0.4.3) colored erubis i18n + require_all ruby-progressbar railties (3.2.13) actionpack (= 3.2.13) @@ -403,6 +406,7 @@ GEM redis-store (1.1.4) redis (>= 2.2) ref (1.0.5) + require_all (1.3.1) rest-client (1.6.7) mime-types (>= 1.16) rspec (2.13.0) @@ -427,7 +431,7 @@ GEM safe_yaml (0.9.3) sanitize (2.0.3) nokogiri (>= 1.4.4, < 1.6) - sass (3.2.9) + sass (3.2.11) sass-rails (3.2.6) railties (~> 3.2.0) sass (>= 3.1.10) @@ -447,7 +451,7 @@ GEM rubyzip websocket (~> 1.0.4) settingslogic (2.0.9) - sexp_processor (4.2.1) + sexp_processor (4.3.0) shoulda-matchers (2.1.0) activesupport (>= 3.0.0) sidekiq (2.14.0) @@ -487,7 +491,9 @@ GEM state_machine (1.2.0) stringex (1.5.1) temple (0.6.5) - test_after_commit (0.2.0) + term-ansicolor (1.2.2) + tins (~> 0.8) + test_after_commit (0.2.1) therubyracer (0.11.4) libv8 (~> 3.11.8.12) ref @@ -507,6 +513,7 @@ GEM mime-types (~> 1.19) multi_json (~> 1.5) twitter-stream (~> 0.1) + tins (0.11.0) treetop (1.4.14) polyglot polyglot (>= 0.3.1) @@ -544,7 +551,7 @@ PLATFORMS DEPENDENCIES acts-as-taggable-on - annotate! + annotate (~> 2.6.0.beta2) asciidoctor awesome_print better_errors @@ -559,6 +566,7 @@ DEPENDENCIES d3_rails (~> 3.1.4) database_cleaner devise (~> 2.2) + devise-async email_spec enumerize factory_girl_rails @@ -569,10 +577,11 @@ DEPENDENCIES gemoji (~> 1.2.1) github-linguist github-markup (~> 0.7.4) + gitlab-flowdock-git-hook (~> 0.4.2) gitlab-gollum-lib (~> 1.0.1) gitlab-grack (~> 1.0.1) gitlab-pygments.rb (~> 0.3.2) - gitlab_git (= 2.3.1) + gitlab_git (~> 3.0.0.rc2) gitlab_meta (= 6.0) gitlab_omniauth-ldap (= 1.0.3) gon @@ -604,6 +613,7 @@ DEPENDENCIES poltergeist (~> 1.4.1) pry quiet_assets (~> 1.0.1) + rack-attack rack-mini-profiler rails (= 3.2.13) rails-dev-tweaks diff --git a/README.md b/README.md index ce0f4a8a1c..b1c109950a 100644 --- a/README.md +++ b/README.md @@ -127,14 +127,17 @@ or start each component separately ### GitLab interfaces -* [GitLab API](doc/api/README.md) +* [GitLab API doc](doc/api/README.md) or see the [GitLab API website](http://api.gitlab.org/) -* [Rake tasks](doc/raketasks) +* [Rake tasks](doc/raketasks) including a [backup and restore procedure](doc/raketasks/backup_restore.md) * [Directory structure](doc/install/structure.md) -* [Databases](doc/install/databases.md) +* [Database installation](doc/install/databases.md) +* [Markdown specification](doc/markdown/markdown.md) + +* [Security guide](doc/security/rack_attack.md) to throttle abusive requests ### Getting help @@ -157,10 +160,8 @@ or start each component separately ### Getting in touch -* [Core team](https://github.com/gitlabhq?tab=members) +* [Core team](http://gitlab.org/team/) -* [Contributors](https://github.com/gitlabhq/gitlabhq/graphs/contributors) +* [Contributors](http://contributors.gitlab.org/) -* [Leader](https://github.com/randx) - -* [Contact page](http://gitlab.org/contact/) +* [Community](http://gitlab.org/community/) diff --git a/VERSION b/VERSION index bad4faad51..f8a9abe525 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.1.0-ee +6.2.0-ee diff --git a/app/assets/images/login-logo.png b/app/assets/images/login-logo.png deleted file mode 100644 index a61c41303c..0000000000 Binary files a/app/assets/images/login-logo.png and /dev/null differ diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee index e7974611cb..744f3086d5 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile.js.coffee @@ -16,3 +16,13 @@ $ -> $('.update-notifications').on 'ajax:complete', -> $(this).find('.btn-save').enableButton() + + + $('.js-choose-user-avatar-button').bind "click", -> + form = $(this).closest("form") + form.find(".js-user-avatar-input").click() + + $('.js-user-avatar-input').bind "change", -> + form = $(this).closest("form") + filename = $(this).val().replace(/^.*[\\\/]/, '') + form.find(".js-avatar-filename").text(filename) diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 8286ca2f0c..327667fede 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -1,9 +1,11 @@ $ -> userFormatResult = (user) -> - avatar = gon.gravatar_url - avatar = avatar.replace('%{hash}', md5(user.email)) - avatar = avatar.replace('%{size}', '24') - + if user.avatar + avatar = user.avatar.url + else + avatar = gon.gravatar_url + avatar = avatar.replace('%{hash}', md5(user.email)) + avatar = avatar.replace('%{size}', '24') markup = "
" markup += "
" markup += "
" + user.name + "
" diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss index 5b1088a051..99a46d8541 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -140,27 +140,6 @@ p.time { border-bottom: 2px solid #F90; } -.status_info { - font-size: 14px; - padding: 5px 15px; - line-height: 26px; - text-align: center; - float: right; - position: relative; - top: -5px; - @include border-radius(4px); - - &.success { - background: #4A4; - color: #FFF; - } - - &.error { - background: #DA4E49; - color: #FFF; - } -} - .thin_area{ height: 150px; } @@ -270,27 +249,6 @@ li.note { } } -.oauth_select_holder { - padding: 20px; - img { - padding: 5px; - margin-right: 10px; - } - .active { - img { - border: 1px solid #ccc; - background: $hover; - @include border-radius(5px); - } - } -} - -.btn-build-token { - float: left; - padding: 6px 20px; - margin-right: 12px; -} - .gitlab-promo { a { color: #aaa; @@ -386,3 +344,8 @@ table { width: 50px; min-height: 100px; } + +.navbar-gitlab .navbar-inner .nav > li .btn-sign-in { + @extend .btn-new; + padding: 5px 15px; +} diff --git a/app/assets/stylesheets/gitlab_bootstrap.scss b/app/assets/stylesheets/gitlab_bootstrap.scss index 1b24683fd9..faf36b702c 100644 --- a/app/assets/stylesheets/gitlab_bootstrap.scss +++ b/app/assets/stylesheets/gitlab_bootstrap.scss @@ -62,6 +62,5 @@ $baseLineHeight: 18px !default; @import "gitlab_bootstrap/buttons.scss"; @import "gitlab_bootstrap/blocks.scss"; @import "gitlab_bootstrap/files.scss"; -@import "gitlab_bootstrap/tables.scss"; @import "gitlab_bootstrap/lists.scss"; @import "gitlab_bootstrap/forms.scss"; diff --git a/app/assets/stylesheets/gitlab_bootstrap/avatar.scss b/app/assets/stylesheets/gitlab_bootstrap/avatar.scss index c23970c13e..98fbbd64e9 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/avatar.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/avatar.scss @@ -19,4 +19,5 @@ &.s32 { width: 32px; height: 32px; margin-right: 10px; } &.s60 { width: 60px; height: 60px; margin-right: 12px; } &.s90 { width: 90px; height: 90px; margin-right: 15px; } + &.s160 { width: 160px; height: 160px; margin-right: 20px; } } diff --git a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss index fcf1159cd3..8235fb6010 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss @@ -12,7 +12,7 @@ .ui-box { background: #FFF; margin-bottom: 20px; - border: 1px solid #CCC; + border: 1px solid #DDD; word-wrap: break-word; &.small-box { @@ -32,8 +32,12 @@ } &.ui-box-show { + text-shadow: 0 1px 1px #fff; + color: #666; margin:20px 0; background: #FFF; + box-shadow: inset 0 1px 0 #fff, 0 1px 5px #f1f1f1; + @include linear-gradient(#fafafa, #f1f1f1); .control-group { margin-bottom: 0; @@ -69,7 +73,6 @@ .ui-box-head { .box-title { - color: $style_color; font-size: 18px; font-weight: normal; line-height: 28px; diff --git a/app/assets/stylesheets/gitlab_bootstrap/common.scss b/app/assets/stylesheets/gitlab_bootstrap/common.scss index bc6c786da5..0d9adcf6d9 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/common.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/common.scss @@ -87,3 +87,25 @@ pre.well-pre { font-weight: bold; @include box-shadow(inset 0 2px 4px rgba(0,0,0,.15)); } + +/** Big Labels **/ +.state-label { + font-size: 14px; + padding: 5px 15px; + text-align: center; + float: right; + position: relative; + top: -5px; + @include border-radius(4px); + text-shadow: none; + + &.state-label-green { + background: #4A4; + color: #FFF; + } + + &.state-label-red { + background: #DA4E49; + color: #FFF; + } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/files.scss b/app/assets/stylesheets/gitlab_bootstrap/files.scss index a0d6682eb8..a286e530cd 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/files.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/files.scss @@ -6,6 +6,10 @@ border: 1px solid #CCC; margin-bottom: 1em; + table { + @extend .table; + } + .file-title { border-bottom: 1px solid #bbb; @include bg-dark-gray-gradient; @@ -65,6 +69,12 @@ } + &.blob-no-preview { + background: #eee; + text-shadow: 0 1px 2px #FFF; + padding: 100px 0; + } + /** * Blame file */ diff --git a/app/assets/stylesheets/gitlab_bootstrap/mixins.scss b/app/assets/stylesheets/gitlab_bootstrap/mixins.scss index e7daf8f43b..8b975a12cf 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/mixins.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/mixins.scss @@ -95,6 +95,14 @@ font-size: 14px; line-height: 1.5; } + + table { + @extend .table; + @extend .table-bordered; + th { + background: #EEE; + } + } } @mixin page-title { diff --git a/app/assets/stylesheets/gitlab_bootstrap/tables.scss b/app/assets/stylesheets/gitlab_bootstrap/tables.scss deleted file mode 100644 index a1a5139cda..0000000000 --- a/app/assets/stylesheets/gitlab_bootstrap/tables.scss +++ /dev/null @@ -1,68 +0,0 @@ -table { - @extend .table; - @extend .table-striped; - border: 1px solid #CCC; - width: 100%; - - &.low { - td { - line-height: 18px; - } - } - - &.headless { - tr:first-child td{ - border-top: 1px solid #CCC; - } - } - - th { - font-weight: bold; - vertical-align: middle; - border-bottom: 1px solid #CCC; - text-shadow: 0 1px 1px #fff; - @include bg-dark-gray-gradient; - - ul.nav { - text-shadow: none; - margin: 0; - } - } - - th, td { - padding: 10px; - line-height: 18px; - text-align: left; - } - - td { - border-color: #f1f1f1; - line-height: 28px; - - .s16 { - margin-top: 5px; - margin-right: 5px; - } - - &:first-child { - border-left: 1px solid #CCC; - } - - &:last-child { - border-right: 1px solid #CCC; - } - } - - &.bordered { - @extend .table-bordered; - } - - &.lite { - border: none; - box-shadow: none; - tr, td { - border: none; - background:none !important; - } - } -} diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss index b0cf8b1626..45a83cb608 100644 --- a/app/assets/stylesheets/sections/commits.scss +++ b/app/assets/stylesheets/sections/commits.scss @@ -67,6 +67,7 @@ } table { + width: 100%; font-family: $monospace_font; border: none; margin: 0px; @@ -418,7 +419,6 @@ .commit-title { margin: 0; font-size: 20px; - font-weight: bold; } .commit-description { diff --git a/app/assets/stylesheets/sections/dashboard.scss b/app/assets/stylesheets/sections/dashboard.scss index 61331cee41..04df7656b0 100644 --- a/app/assets/stylesheets/sections/dashboard.scss +++ b/app/assets/stylesheets/sections/dashboard.scss @@ -60,7 +60,7 @@ } a { - border-color: #CCC !important; + border-color: #DDD !important; } } } @@ -85,12 +85,18 @@ color: #666; } - .last-activity, .owner-info { + .last-activity { color: #AAA; display: block; margin-top: 5px; - .date, .owner { + .date { color: #777; } } } + +.group-row { + .arrow { + padding: 2px 5px; + } +} diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/sections/events.scss index fd5c80f988..39b2ad7a09 100644 --- a/app/assets/stylesheets/sections/events.scss +++ b/app/assets/stylesheets/sections/events.scss @@ -46,8 +46,8 @@ border-bottom: 1px solid #eee; .event-title { color: #333; - font-weight: bold; - font-size: 14px; + font-weight: normal; + font-size: 15px; .author_name { color: #333; } diff --git a/app/assets/stylesheets/sections/login.scss b/app/assets/stylesheets/sections/login.scss index 8d9fd037bc..33bef59c08 100644 --- a/app/assets/stylesheets/sections/login.scss +++ b/app/assets/stylesheets/sections/login.scss @@ -1,7 +1,8 @@ /* Login Page */ body.login-page{ - background: #474D57; - .container .content { padding-top: 4%; } + .container > .content { + padding-top: 20px; + } } .login-box{ diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss index bae1ac3aa9..94b9ca3b18 100644 --- a/app/assets/stylesheets/sections/notes.scss +++ b/app/assets/stylesheets/sections/notes.scss @@ -135,11 +135,15 @@ ul.notes { background-color: $white; border-width: 1px 0; padding-top: 0; + + li { + padding: 5px; + } } } .reply-btn { - margin-top: 8px; + margin: 5px; } } diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss index 5c7516ce6f..21f4ed0577 100644 --- a/app/assets/stylesheets/sections/profile.scss +++ b/app/assets/stylesheets/sections/profile.scss @@ -4,3 +4,41 @@ margin-bottom: 0; } } + +.account-page { + fieldset { + margin-bottom: 15px; + border-bottom: 1px dashed #ddd; + padding-bottom: 15px; + + &:last-child { + border: none; + } + + legend { + border: none; + margin: 0; + } + } +} + +.oauth_select_holder { + img { + padding: 2px; + margin-right: 10px; + } + .active { + img { + border: 1px solid #4BD; + background: $hover; + @include border-radius(5px); + } + } +} + +.btn-build-token { + float: left; + padding: 6px 20px; + margin-right: 12px; +} + diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss index f2707f6237..0491b68db5 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/sections/projects.scss @@ -79,21 +79,6 @@ ul.nav.nav-projects-tabs { margin: 0px; } -.public-projects { - li { - .project-title { - font-size: 14px; - line-height: 2; - font-weight: normal; - } - - .description { - margin-left: 15px; - color: #aaa; - } - } -} - .my-projects { li { .project-title { @@ -110,7 +95,6 @@ ul.nav.nav-projects-tabs { } } - .public-clone { background: #333; color: #f5f5f5; @@ -123,3 +107,11 @@ ul.nav.nav-projects-tabs { position: relative; top: -5px; } + +.public-projects .repo-info { + color: #777; + + a { + color: #777; + } +} diff --git a/app/assets/stylesheets/sections/tree.scss b/app/assets/stylesheets/sections/tree.scss index 850ed831df..96f2a5711f 100644 --- a/app/assets/stylesheets/sections/tree.scss +++ b/app/assets/stylesheets/sections/tree.scss @@ -13,13 +13,21 @@ } .tree-table { + @extend .table; @include border-radius(0); - .tree-item { - td { + + tr { + td, th { padding: 8px 10px; - strong { - font-weight: normal; - } + line-height: 20px; + } + th { + font-weight: normal; + font-size: 15px; + border-bottom: 1px solid #CCC; + } + td { + border-color: #F1F1F1; } &:hover { td { @@ -29,12 +37,11 @@ } cursor: pointer; } - &.selected { td { - background: $hover; - border-top: 1px solid #ADF; - border-bottom: 1px solid #ADF; + background: #f5f5f5; + border-top: 1px solid #EEE; + border-bottom: 1px solid #EEE; } } } @@ -56,24 +63,8 @@ } } - .tree-table { - th .btn { - margin: -2px -1px; - padding: 2px 10px; - } - td { - line-height: 20px; - background: #fafafa; - } - } - .tree_author { padding-right: 8px; - - img.avatar { - margin-top: 0; - width: 16px; - } } .tree_commit { diff --git a/app/contexts/search_context.rb b/app/contexts/search_context.rb index 0817d52cd0..48def0784f 100644 --- a/app/contexts/search_context.rb +++ b/app/contexts/search_context.rb @@ -13,7 +13,7 @@ class SearchContext projects = Project.where(id: project_ids) result[:projects] = projects.search(query).limit(20) - # Search inside singe project + # Search inside single project project = projects.first if projects.length == 1 if params[:search_code].present? diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index a2201f732e..89b395786b 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -20,9 +20,9 @@ class Admin::GroupsController < Admin::ApplicationController def create @group = Group.new(params[:group]) @group.path = @group.name.dup.parameterize if @group.name - @group.owner = current_user if @group.save + @group.add_owner(current_user) redirect_to [:admin, @group], notice: 'Group was successfully created.' else render "new" @@ -30,14 +30,7 @@ class Admin::GroupsController < Admin::ApplicationController end def update - group_params = params[:group].dup - owner_id =group_params.delete(:owner_id) - - if owner_id - @group.change_owner(User.find(owner_id)) - end - - if @group.update_attributes(group_params) + if @group.update_attributes(params[:group]) redirect_to [:admin, @group], notice: 'Group was successfully updated.' else render "edit" diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 70bbe30656..c9875b9632 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -47,6 +47,8 @@ class Admin::UsersController < Admin::ApplicationController @user = User.build_user(params[:user].merge(opts), as: :admin) @user.admin = (admin && admin.to_i > 0) @user.created_by_id = current_user.id + @user.generate_password + @user.confirm! respond_to do |format| if @user.save @@ -71,6 +73,7 @@ class Admin::UsersController < Admin::ApplicationController respond_to do |format| if user.update_attributes(params[:user], as: :admin) + user.confirm! format.html { redirect_to [:admin, user], notice: 'User was successfully updated.' } format.json { head :ok } else diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index da84e1b0d3..ccab2fc5c6 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,7 +2,7 @@ class ApplicationController < ActionController::Base before_filter :authenticate_user! before_filter :reject_blocked! before_filter :check_password_expiration - before_filter :set_current_user_for_thread + around_filter :set_current_user_for_thread before_filter :add_abilities before_filter :ldap_security_check before_filter :dev_tools if Rails.env == 'development' @@ -51,6 +51,11 @@ class ApplicationController < ActionController::Base def set_current_user_for_thread Thread.current[:current_user] = current_user + begin + yield + ensure + Thread.current[:current_user] = nil + end end def abilities @@ -64,6 +69,15 @@ class ApplicationController < ActionController::Base def project id = params[:project_id] || params[:id] + # Redirect from + # localhost/group/project.git + # to + # localhost/group/project + # + if id =~ /\.git\Z/ + redirect_to request.original_url.gsub(/\.git\Z/, '') and return + end + @project = Project.find_with_namespace(id) if @project and can?(current_user, :read_project, @project) diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 33b2a5cd7e..ac31938443 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -5,10 +5,14 @@ class DashboardController < ApplicationController before_filter :event_filter, only: :show def show + # Fetch only 30 projects. + # If user needs more - point to Dashboard#projects page + @projects_limit = 30 + @groups = current_user.authorized_groups.sort_by(&:human_name) @has_authorized_projects = @projects.count > 0 @projects_count = @projects.count - @projects = @projects.limit(20) + @projects = @projects.limit(@projects_limit) @events = Event.in_projects(current_user.authorized_projects.pluck(:id)) @events = @event_filter.apply_filter(@events) diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 97bd7d6f29..61361da3df 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -21,9 +21,9 @@ class GroupsController < ApplicationController def create @group = Group.new(params[:group]) @group.path = @group.name.dup.parameterize if @group.name - @group.owner = current_user if @group.save + @group.add_owner(current_user) redirect_to @group, notice: 'Group was successfully created.' else render action: "new" @@ -75,15 +75,7 @@ class GroupsController < ApplicationController end def update - group_params = params[:group].dup - owner_id = group_params.delete(:owner_id) - - if owner_id - @group.owner = User.find(owner_id) - @group.save - end - - if @group.update_attributes(group_params) + if @group.update_attributes(params[:group]) redirect_to @group, notice: 'Group was successfully updated.' else render action: "edit" diff --git a/app/controllers/profiles/accounts_controller.rb b/app/controllers/profiles/accounts_controller.rb new file mode 100644 index 0000000000..fe121691a1 --- /dev/null +++ b/app/controllers/profiles/accounts_controller.rb @@ -0,0 +1,7 @@ +class Profiles::AccountsController < ApplicationController + layout "profile" + + def show + @user = current_user + end +end diff --git a/app/controllers/profiles/groups_controller.rb b/app/controllers/profiles/groups_controller.rb index e276d07b48..378ff6bcf3 100644 --- a/app/controllers/profiles/groups_controller.rb +++ b/app/controllers/profiles/groups_controller.rb @@ -8,8 +8,8 @@ class Profiles::GroupsController < ApplicationController def leave @users_group = group.users_groups.where(user_id: current_user.id).first - if group.owner == current_user - redirect_to(profile_groups_path, alert: "You can't leave group. You must transfer it to another owner before leaving.") + if group.last_owner?(current_user) + redirect_to(profile_groups_path, alert: "You can't leave group. You must add at least one more owner to it.") else @users_group.destroy redirect_to(profile_groups_path, info: "You left #{group.name} group.") diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb index 432899f857..df6954554e 100644 --- a/app/controllers/profiles/passwords_controller.rb +++ b/app/controllers/profiles/passwords_controller.rb @@ -1,10 +1,11 @@ class Profiles::PasswordsController < ApplicationController - layout 'navless' + layout :determine_layout - skip_before_filter :check_password_expiration + skip_before_filter :check_password_expiration, only: [:new, :create] before_filter :set_user before_filter :set_title + before_filter :authorize_change_password! def new end @@ -26,6 +27,32 @@ class Profiles::PasswordsController < ApplicationController end end + def edit + end + + def update + password_attributes = params[:user].select do |key, value| + %w(password password_confirmation).include?(key.to_s) + end + + unless @user.valid_password?(params[:user][:current_password]) + redirect_to edit_profile_password_path, alert: 'You must provide a valid current password' + return + end + + if @user.update_attributes(password_attributes) + flash[:notice] = "Password was successfully updated. Please login with it" + redirect_to new_user_session_path + else + render 'edit' + end + end + + def reset + current_user.send_reset_password_instructions + redirect_to edit_profile_password_path, notice: 'We sent you an email with reset password instructions' + end + private def set_user @@ -35,4 +62,16 @@ class Profiles::PasswordsController < ApplicationController def set_title @title = "New password" end + + def determine_layout + if [:new, :create].include?(action_name.to_sym) + 'navless' + else + 'profile' + end + end + + def authorize_change_password! + return render_404 if @user.ldap_user? + end end diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 6fa635d0e3..47cfc5e63f 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -2,7 +2,6 @@ class ProfilesController < ApplicationController include ActionView::Helpers::SanitizeHelper before_filter :user - before_filter :authorize_change_password!, only: :update_password before_filter :authorize_change_username!, only: :update_username layout 'profile' @@ -13,9 +12,6 @@ class ProfilesController < ApplicationController def design end - def account - end - def update if @user.update_attributes(params[:user]) flash[:notice] = "Profile was successfully updated" @@ -29,26 +25,12 @@ class ProfilesController < ApplicationController end end - def token - end - - def update_password - params[:user].reject!{ |k, v| k != "password" && k != "password_confirmation"} - - if @user.update_attributes(params[:user]) - flash[:notice] = "Password was successfully updated. Please login with it" - redirect_to new_user_session_path - else - render 'account' - end - end - def reset_private_token if current_user.reset_authentication_token! flash[:notice] = "Token was successfully updated" end - redirect_to account_profile_path + redirect_to profile_account_path end def history @@ -69,10 +51,6 @@ class ProfilesController < ApplicationController @user = current_user end - def authorize_change_password! - return render_404 if @user.ldap_user? - end - def authorize_change_username! return render_404 unless @user.can_change_username? end diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 1f2a75175c..8fd4565f36 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -1,5 +1,26 @@ class Projects::ApplicationController < ApplicationController before_filter :project before_filter :repository - layout 'projects' + layout :determine_layout + + def authenticate_user! + # Restrict access to Projects area only + # for non-signed users + if !current_user + id = params[:project_id] || params[:id] + @project = Project.find_with_namespace(id) + + return if @project && @project.public + end + + super + end + + def determine_layout + if current_user + 'projects' + else + 'public_projects' + end + end end diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb index e58b450720..a3c4130167 100644 --- a/app/controllers/projects/blame_controller.rb +++ b/app/controllers/projects/blame_controller.rb @@ -8,7 +8,7 @@ class Projects::BlameController < Projects::ApplicationController before_filter :require_non_empty_project def show - @blob = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, @path) + @blob = @repository.blob_at(@commit.id, @path) @blame = Gitlab::Git::Blame.new(project.repository, @commit.id, @path) end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index b1329c01ce..ba466251b2 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -8,6 +8,8 @@ class Projects::BlobController < Projects::ApplicationController before_filter :require_non_empty_project def show - @blob = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, @path) + @blob = @repository.blob_at(@commit.id, @path) + + not_found! unless @blob end end diff --git a/app/controllers/projects/edit_tree_controller.rb b/app/controllers/projects/edit_tree_controller.rb index 3b945fc712..5d05c585ec 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::ApplicationController before_filter :edit_requirements, only: [:show, :update] def show - @last_commit = Gitlab::Git::Commit.last_for_path(@project.repository, @ref, @path).sha + @last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha end def update @@ -32,9 +32,9 @@ class Projects::EditTreeController < Projects::ApplicationController private def edit_requirements - @blob = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, @path) + @blob = @repository.blob_at(@commit.id, @path) - unless @blob.exists? && @blob.text? + unless @blob redirect_to project_blob_path(@project, @id), notice: "You can only edit text files" end diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index 3367ddb5d1..1a94dbab5e 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -1,7 +1,6 @@ class Projects::HooksController < Projects::ApplicationController # Authorize - before_filter :authorize_read_project! - before_filter :authorize_admin_project!, only: [:new, :create, :destroy] + before_filter :authorize_admin_project! respond_to :html diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 3bc50b0418..0cc09caf1d 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -157,12 +157,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def validates_merge_request - # Show git not found page if target branch doesn't exist - return invalid_mr unless @merge_request.target_project.repository.branch_names.include?(@merge_request.target_branch) + # 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) - # Show git not found page if source branch doesn't exist - # and there is no saved commits between source & target branch - return invalid_mr if !@merge_request.source_project.repository.branch_names.include?(@merge_request.source_branch) && @merge_request.commits.blank? + # or if source branch doesn't exist + return invalid_mr unless @merge_request.source_project.repository.branch_names.include?(@merge_request.source_branch) + end end def define_show_vars diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index 39cd579cce..3e88656cdf 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -14,7 +14,7 @@ class Projects::MilestonesController < Projects::ApplicationController @milestones = case params[:f] when 'all'; @project.milestones.order("state, due_date DESC") when 'closed'; @project.milestones.closed.order("due_date DESC") - else @project.milestones.active.order("due_date DESC") + else @project.milestones.active.order("due_date ASC") end @milestones = @milestones.includes(:project) diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb index 0c23d411f4..18ace028b0 100644 --- a/app/controllers/projects/raw_controller.rb +++ b/app/controllers/projects/raw_controller.rb @@ -8,9 +8,9 @@ class Projects::RawController < Projects::ApplicationController before_filter :require_non_empty_project def show - @blob = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, @path) + @blob = @repository.blob_at(@commit.id, @path) - if @blob.exists? + if @blob type = if @blob.mime_type =~ /html|javascript/ 'text/plain; charset=utf-8' else diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index e5c090e1f4..16621c0371 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -24,13 +24,14 @@ class Projects::RefsController < Projects::ApplicationController format.js do @ref = params[:ref] define_tree_vars + tree render "tree" end end end def logs_tree - contents = @tree.entries + contents = tree.entries @logs = contents.map do |content| file = params[:path] ? File.join(params[:path], content.name) : content.name last_commit = @repo.commits(@commit.id, file, 1).last diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 59063103ec..dd0c1a5708 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -14,8 +14,6 @@ class Projects::SnippetsController < Projects::ApplicationController # Allow destroy snippet before_filter :authorize_admin_project_snippet!, only: [:destroy] - layout 'projects' - respond_to :html def index diff --git a/app/controllers/projects/team_members_controller.rb b/app/controllers/projects/team_members_controller.rb index 6fee770cae..b4b318fa59 100644 --- a/app/controllers/projects/team_members_controller.rb +++ b/app/controllers/projects/team_members_controller.rb @@ -1,7 +1,6 @@ class Projects::TeamMembersController < Projects::ApplicationController # Authorize - before_filter :authorize_read_project! - before_filter :authorize_admin_project!, except: [:index, :show] + before_filter :authorize_admin_project! layout "project_settings" diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index 5d543f3566..1150efbea9 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -8,6 +8,8 @@ class Projects::TreeController < Projects::ApplicationController before_filter :require_non_empty_project def show + return not_found! if tree.entries.empty? + respond_to do |format| format.html # Disable cache so browser history works diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 23b54ec44a..7264128691 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -1,6 +1,7 @@ -class ProjectsController < Projects::ApplicationController - skip_before_filter :project, only: [:new, :create] - skip_before_filter :repository, only: [:new, :create] +class ProjectsController < ApplicationController + skip_before_filter :authenticate_user!, only: [:show] + before_filter :project, except: [:new, :create] + before_filter :repository, except: [:new, :create] # Authorize before_filter :authorize_read_project!, except: [:index, :new, :create] @@ -54,8 +55,9 @@ class ProjectsController < Projects::ApplicationController end def show - limit = (params[:limit] || 20).to_i + return authenticate_user! unless @project.public || current_user + limit = (params[:limit] || 20).to_i @events = @project.events.recent @events = event_filter.apply_filter(@events) @events = @events.limit(limit).offset(params[:offset] || 0) @@ -67,10 +69,12 @@ class ProjectsController < Projects::ApplicationController respond_to do |format| format.html do if @project.empty_repo? - render "projects/empty" + render "projects/empty", layout: user_layout else - @last_push = current_user.recent_push(@project.id) - render :show + if current_user + @last_push = current_user.recent_push(@project.id) + end + render :show, layout: user_layout end end format.js @@ -121,4 +125,8 @@ class ProjectsController < Projects::ApplicationController def set_title @title = 'New Project' end + + def user_layout + current_user ? "projects" : "public_projects" + end end diff --git a/app/controllers/public/projects_controller.rb b/app/controllers/public/projects_controller.rb index 85216cd327..87e903a1d2 100644 --- a/app/controllers/public/projects_controller.rb +++ b/app/controllers/public/projects_controller.rb @@ -1,7 +1,7 @@ class Public::ProjectsController < ApplicationController skip_before_filter :authenticate_user!, - :reject_blocked, :set_current_user_for_observers, - :add_abilities + :reject_blocked, :set_current_user_for_observers, + :add_abilities layout 'public' @@ -10,15 +10,4 @@ class Public::ProjectsController < ApplicationController @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 - - def show - @project = Project.public_only.find_with_namespace(params[:id]) - render_404 and return unless @project - - @repository = @project.repository - @recent_tags = @repository.tags.first(10) - - @commit = @repository.commit(params[:ref]) - @tree = Tree.new(@repository, @commit.id) - end end diff --git a/app/controllers/users_groups_controller.rb b/app/controllers/users_groups_controller.rb index df13b86fdc..749da1e141 100644 --- a/app/controllers/users_groups_controller.rb +++ b/app/controllers/users_groups_controller.rb @@ -19,7 +19,7 @@ class UsersGroupsController < ApplicationController def destroy @users_group = @group.users_groups.find(params[:id]) - @users_group.destroy unless @users_group.user == @group.owner + @users_group.destroy respond_to do |format| format.html { redirect_to members_group_path(@group), notice: 'User was successfully removed from group.' } diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 4209b081bf..e2d9790141 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -49,6 +49,15 @@ module ApplicationHelper args.any? { |v| v.to_s.downcase == action_name } end + def avatar_icon(user_email = '', size = nil) + user = User.find_by_email(user_email) + if user && user.avatar.present? + user.avatar.url + else + gravatar_icon(user_email, size) + end + end + def gravatar_icon(user_email = '', size = nil) size = 40 if size.nil? || size <= 0 @@ -89,49 +98,6 @@ module ApplicationHelper grouped_options_for_select(options, @ref || @project.default_branch) end - def search_autocomplete_source - projects = current_user.authorized_projects.map { |p| { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) } } - groups = current_user.authorized_groups.map { |group| { label: "group: #{simple_sanitize(group.name)}", url: group_path(group) } } - - default_nav = [ - { label: "My Profile", url: profile_path }, - { label: "My SSH Keys", url: profile_keys_path }, - { label: "My Dashboard", url: root_path }, - { label: "Admin Section", url: admin_root_path }, - ] - - help_nav = [ - { label: "help: API Help", url: help_api_path }, - { label: "help: Markdown Help", url: help_markdown_path }, - { label: "help: Permissions Help", url: help_permissions_path }, - { label: "help: Public Access Help", url: help_public_access_path }, - { label: "help: Rake Tasks Help", url: help_raketasks_path }, - { label: "help: SSH Keys Help", url: help_ssh_path }, - { label: "help: System Hooks Help", url: help_system_hooks_path }, - { label: "help: Web Hooks Help", url: help_web_hooks_path }, - { label: "help: Workflow Help", url: help_workflow_path }, - ] - - project_nav = [] - if @project && @project.repository.exists? && @project.repository.root_ref - project_nav = [ - { label: "#{simple_sanitize(@project.name_with_namespace)} - Files", url: project_tree_path(@project, @ref || @project.repository.root_ref) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Commits", url: project_commits_path(@project, @ref || @project.repository.root_ref) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Network", url: project_network_path(@project, @ref || @project.repository.root_ref) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Graph", url: project_graph_path(@project, @ref || @project.repository.root_ref) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Issues", url: project_issues_path(@project) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Merge Requests", url: project_merge_requests_path(@project) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Milestones", url: project_milestones_path(@project) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Snippets", url: project_snippets_path(@project) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Team", url: project_team_index_path(@project) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Wall", url: project_wall_path(@project) }, - { label: "#{simple_sanitize(@project.name_with_namespace)} - Wiki", url: project_wikis_path(@project) }, - ] - end - - [groups, projects, default_nav, project_nav, help_nav].flatten.to_json - end - def emoji_autocomplete_source # should be an array of strings # so to_s can be called, because it is sufficient and to_json is too slow @@ -181,7 +147,7 @@ module ApplicationHelper alt: "Sign in with #{provider.to_s.titleize}") end - def simple_sanitize str + def simple_sanitize(str) sanitize(str, tags: %w(a span)) end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index f8f84ff8b6..7e24cbd3e5 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -108,7 +108,7 @@ module CommitsHelper source_name = commit.send "#{options[:source]}_name".to_sym source_email = commit.send "#{options[:source]}_email".to_sym text = if options[:avatar] - avatar = image_tag(gravatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "") + avatar = image_tag(avatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "") %Q{#{avatar} #{source_name}} else source_name diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 375f8861da..b34f703353 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -34,7 +34,8 @@ module GitlabMarkdownHelper # see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch- filter_html: true, with_toc_data: true, - hard_wrap: true) + hard_wrap: true, + safe_links_only: true) @markdown = Redcarpet::Markdown.new(gitlab_renderer, # see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use no_intra_emphasis: true, @@ -57,4 +58,104 @@ module GitlabMarkdownHelper wiki_page.formatted_content.html_safe end end + + # text - whole text from a markdown file + # project_path_with_namespace - namespace/projectname, eg. gitlabhq/gitlabhq + # ref - name of the branch or reference, eg. stable + # requested_path - path of request, eg. doc/api/README.md, used in special case when path is pointing to the .md file were the original request is coming from + # wiki - whether the markdown is from wiki or not + def create_relative_links(text, project_path_with_namespace, ref, requested_path, wiki = false) + paths = extract_paths(text) + paths.each do |file_path| + new_path = rebuild_path(project_path_with_namespace, file_path, requested_path, ref) + # Replacing old string with a new one with brackets ]() to prevent replacing occurence of a word + # e.g. If we have a markdown like [test](test) this will replace ](test) and not the word test + text.gsub!("](#{file_path})", "](/#{new_path})") + end + text + end + + def extract_paths(markdown_text) + all_markdown_paths = pick_out_paths(markdown_text) + paths = remove_empty(all_markdown_paths) + select_relative(paths) + end + + # Split the markdown text to each line and find all paths, this will match anything with - ]("some_text") + def pick_out_paths(markdown_text) + markdown_text.split("\n").map { |text| text.scan(/\]\(([^(]+)\)/) } + end + + # Removes any empty result produced by not matching the regexp + def remove_empty(paths) + paths.reject{|l| l.empty? }.flatten + end + + # Reject any path that contains ignored protocol + # eg. reject "https://gitlab.org} but accept "doc/api/README.md" + def select_relative(paths) + paths.reject{|path| ignored_protocols.map{|protocol| path.include?(protocol)}.any?} + end + + def ignored_protocols + ["http://","https://", "ftp://", "mailto:"] + end + + def rebuild_path(path_with_namespace, path, requested_path, ref) + file_path = relative_file_path(path, requested_path) + [ + path_with_namespace, + path_with_ref(file_path, ref), + file_path + ].compact.join("/") + end + + # Checks if the path exists in the repo + # eg. checks if doc/README.md exists, if it doesn't then it is a wiki link + def path_with_ref(path, ref) + if file_exists?(path) + "#{local_path(path)}/#{correct_ref(ref)}" + else + "wikis" + end + end + + def relative_file_path(path, requested_path) + nested_path = build_nested_path(path, requested_path) + return nested_path if file_exists?(nested_path) + path + end + + # Covering a special case, when the link is referencing file in the same directory eg: + # If we are at doc/api/README.md and the README.md contains relative links like [Users](users.md) + # this takes the request path(doc/api/README.md), and replaces the README.md with users.md so the path looks like doc/api/users.md + # If we are at doc/api and the README.md shown in below the tree view + # this takes the rquest path(doc/api) and adds users.md so the path looks like doc/api/users.md + def build_nested_path(path, request_path) + return path unless request_path + if local_path(request_path) == "tree" + base = request_path.split("/").push(path) + base.join("/") + else + base = request_path.split("/") + base.pop + base.push(path).join("/") + end + end + + def file_exists?(path) + return false if path.nil? || path.empty? + File.exists?(Rails.root.join(path)) + end + + # Check if the path is pointing to a directory(tree) or a file(blob) + # eg. doc/api is directory and doc/README.md is file + def local_path(path) + File.directory?(Rails.root.join(path)) ? "tree" : "blob" + end + + # We will assume that if no ref exists we can point to master + def correct_ref(ref) + ref ? ref : "master" + end end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 5977c9cbae..2221583912 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -16,7 +16,7 @@ module IssuesHelper def url_for_project_issues return "" if @project.nil? - if @project.used_default_issues_tracker? + if @project.used_default_issues_tracker? || !external_issues_tracker_enabled? project_issues_path(@project) else url = Gitlab.config.issues_tracker[@project.issues_tracker]["project_url"] @@ -28,7 +28,7 @@ module IssuesHelper def url_for_new_issue return "" if @project.nil? - if @project.used_default_issues_tracker? + if @project.used_default_issues_tracker? || !external_issues_tracker_enabled? url = new_project_issue_path project_id: @project else url = Gitlab.config.issues_tracker[@project.issues_tracker]["new_issue_url"] @@ -40,7 +40,7 @@ module IssuesHelper def url_for_issue(issue_iid) return "" if @project.nil? - if @project.used_default_issues_tracker? + if @project.used_default_issues_tracker? || !external_issues_tracker_enabled? url = project_issue_url project_id: @project, id: issue_iid else url = Gitlab.config.issues_tracker[@project.issues_tracker]["issues_url"] @@ -59,4 +59,13 @@ module IssuesHelper "" end end + + # Checks if issues_tracker setting exists in gitlab.yml + def external_issues_tracker_enabled? + if Gitlab.config.issues_tracker && Gitlab.config.issues_tracker.values.any? + true + else + false + end + end end diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb index 88d9f184d0..dd9e03d95a 100644 --- a/app/helpers/profile_helper.rb +++ b/app/helpers/profile_helper.rb @@ -14,6 +14,6 @@ module ProfileHelper end def show_profile_remove_tab? - Gitlab.config.gitlab.signup_enabled && !current_user.ldap_user? + gitlab_config.signup_enabled && !current_user.ldap_user? end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 3a1cf59fd1..864f6c9af9 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -5,10 +5,10 @@ module ProjectsHelper def link_to_project project link_to project do - title = content_tag(:strong, project.name) + title = content_tag(:span, project.name, class: 'projet-name') if project.namespace - namespace = content_tag(:span, "#{project.namespace.human_name} / ", class: 'tiny') + namespace = content_tag(:span, "#{project.namespace.human_name} / ", class: 'namespace-name') title = namespace + title end @@ -25,7 +25,7 @@ module ProjectsHelper author_html = "" # Build avatar image tag - author_html << image_tag(gravatar_icon(author.try(:email), opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar] + author_html << image_tag(avatar_icon(author.try(:email), opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar] # Build name span tag author_html << content_tag(:span, sanitize(author.name), class: 'author') if opts[:name] @@ -77,7 +77,19 @@ module ProjectsHelper end def project_active_milestones - @project.milestones.active.order("id desc").all + @project.milestones.active.order("due_date, title ASC").all + end + + def project_issues_trackers + values = Project.issues_tracker.values.map do |tracker_key| + if tracker_key.to_sym == :gitlab + ['GitLab', tracker_key] + else + [Gitlab.config.issues_tracker[tracker_key]['title'] || tracker_key, tracker_key] + end + end + + options_for_select(values) end private @@ -103,4 +115,29 @@ module ProjectsHelper nav_tabs.flatten end + + def git_user_name + if current_user + current_user.name + else + "Your name" + end + end + + def git_user_email + if current_user + current_user.email + else + "your@email.com" + end + end + + def repository_size + "#{@project.repository.size} MB" + rescue + # In order to prevent 500 error + # when application cannot allocate memory + # to calculate repo size - just show 'Unknown' + 'unknown' + end end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb new file mode 100644 index 0000000000..33c5e4fb9d --- /dev/null +++ b/app/helpers/search_helper.rb @@ -0,0 +1,78 @@ +module SearchHelper + def search_autocomplete_source + return unless current_user + + [ + groups_autocomplete, + projects_autocomplete, + default_autocomplete, + project_autocomplete, + help_autocomplete + ].flatten.to_json + end + + private + + # Autocomplete results for various settings pages + def default_autocomplete + [ + { label: "My Profile settings", url: profile_path }, + { label: "My SSH Keys", url: profile_keys_path }, + { label: "My Dashboard", url: root_path }, + { label: "Admin Section", url: admin_root_path }, + ] + end + + # Autocomplete results for internal help pages + def help_autocomplete + [ + { label: "help: API Help", url: help_api_path }, + { label: "help: Markdown Help", url: help_markdown_path }, + { label: "help: Permissions Help", url: help_permissions_path }, + { label: "help: Public Access Help", url: help_public_access_path }, + { label: "help: Rake Tasks Help", url: help_raketasks_path }, + { label: "help: SSH Keys Help", url: help_ssh_path }, + { label: "help: System Hooks Help", url: help_system_hooks_path }, + { label: "help: Web Hooks Help", url: help_web_hooks_path }, + { label: "help: Workflow Help", url: help_workflow_path }, + ] + end + + # Autocomplete results for the current project, if it's defined + def project_autocomplete + if @project && @project.repository.exists? && @project.repository.root_ref + prefix = simple_sanitize(@project.name_with_namespace) + ref = @ref || @project.repository.root_ref + + [ + { label: "#{prefix} - Files", url: project_tree_path(@project, ref) }, + { label: "#{prefix} - Commits", url: project_commits_path(@project, ref) }, + { label: "#{prefix} - Network", url: project_network_path(@project, ref) }, + { label: "#{prefix} - Graph", url: project_graph_path(@project, ref) }, + { label: "#{prefix} - Issues", url: project_issues_path(@project) }, + { label: "#{prefix} - Merge Requests", url: project_merge_requests_path(@project) }, + { label: "#{prefix} - Milestones", url: project_milestones_path(@project) }, + { label: "#{prefix} - Snippets", url: project_snippets_path(@project) }, + { label: "#{prefix} - Team", url: project_team_index_path(@project) }, + { label: "#{prefix} - Wall", url: project_wall_path(@project) }, + { label: "#{prefix} - Wiki", url: project_wikis_path(@project) }, + ] + else + [] + end + end + + # Autocomplete results for the current user's groups + def groups_autocomplete + current_user.authorized_groups.map do |group| + { label: "group: #{simple_sanitize(group.name)}", url: group_path(group) } + end + end + + # Autocomplete results for the current user's projects + def projects_autocomplete + current_user.authorized_projects.map do |p| + { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) } + end + end +end diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index 73d36d0801..2dbc1cffb1 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -67,9 +67,9 @@ module TreeHelper end def tree_breadcrumbs(tree, max_links = 2) - if tree.path + if @path.present? part_path = "" - parts = tree.path.split("\/") + parts = @path.split("\/") yield('..', nil) if parts.count > max_links @@ -78,14 +78,14 @@ module TreeHelper part_path = part if part_path.empty? next unless parts.last(2).include?(part) if parts.count > max_links - yield(part, tree_join(tree.ref, part_path)) + yield(part, tree_join(@ref, part_path)) end end end def up_dir_path tree - file = File.join(tree.path, "..") - tree_join(tree.ref, file) + file = File.join(@path, "..") + tree_join(@ref, file) end def leave_edit_message diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index 761b4c8161..ba4f0dd862 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -18,7 +18,7 @@ module Emails @note = Note.find(note_id) @merge_request = @note.noteable @project = @note.project - mail(to: recipient(recipient_id), subject: subject("note for merge request !#{@merge_request.iid}")) + mail(to: recipient(recipient_id), subject: subject("note for merge request ##{@merge_request.iid}")) end def note_wall_email(recipient_id, note_id) diff --git a/app/models/ability.rb b/app/models/ability.rb index 8335829f91..8547608914 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -1,6 +1,7 @@ class Ability class << self def allowed(user, subject) + return not_auth_abilities(user, subject) if user.nil? return [] unless user.kind_of?(User) return [] if user.blocked? @@ -17,6 +18,34 @@ class Ability end.concat(global_abilities(user)) end + # List of possible abilities + # for non-authenticated user + def not_auth_abilities(user, subject) + project = if subject.kind_of?(Project) + subject + elsif subject.respond_to?(:project) + subject.project + else + nil + end + + if project && project.public + [ + :read_project, + :read_wiki, + :read_issue, + :read_milestone, + :read_project_snippet, + :read_team_member, + :read_merge_request, + :read_note, + :download_code + ] + else + [] + end + end + def global_abilities(user) rules = [] rules << :create_group if user.can_create_group @@ -50,7 +79,7 @@ class Ability rules << project_admin_rules end - if project.group && project.group.owners.include?(user) + if project.group && project.group.has_owner?(user) rules << project_admin_rules end @@ -58,19 +87,9 @@ class Ability end def public_project_rules - [ + project_guest_rules + [ :download_code, :fork_project, - :read_project, - :read_wiki, - :read_issue, - :read_milestone, - :read_project_snippet, - :read_team_member, - :read_merge_request, - :read_note, - :write_issue, - :write_note ] end @@ -135,12 +154,12 @@ class Ability def group_abilities user, group rules = [] - if group.users.include?(user) + if group.users.include?(user) || user.admin? rules << :read_group end # Only group owner and administrators can manage group - if group.owners.include?(user) || user.admin? + if group.has_owner?(user) || user.admin? rules << [ :manage_group, :manage_namespace diff --git a/app/models/flowdock_service.rb b/app/models/flowdock_service.rb new file mode 100644 index 0000000000..6ec431d4a1 --- /dev/null +++ b/app/models/flowdock_service.rb @@ -0,0 +1,52 @@ +# == 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) +# + +require "flowdock-git-hook" + +class FlowdockService < Service + validates :token, presence: true, if: :activated? + + def title + 'Flowdock' + end + + def description + 'Flowdock is a collaboration web app for technical teams.' + end + + def to_param + 'flowdock' + end + + def fields + [ + { type: 'text', name: 'token', placeholder: '' } + ] + end + + def execute(push_data) + repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git") + Flowdock::Git.post( + push_data[:ref], + push_data[:before], + push_data[:after], + token: token, + repo: repo_path, + repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}", + commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s", + diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s", + ) + end +end diff --git a/app/models/gollum_wiki.rb b/app/models/gollum_wiki.rb index d1edbab453..05fc2a745b 100644 --- a/app/models/gollum_wiki.rb +++ b/app/models/gollum_wiki.rb @@ -45,6 +45,10 @@ class GollumWiki end end + def empty? + pages.empty? + end + # Returns an Array of Gitlab WikiPage instances or an # empty Array if this Wiki has no pages. def pages diff --git a/app/models/group.rb b/app/models/group.rb index df52e0af3f..ba9461c42c 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -5,7 +5,7 @@ # id :integer not null, primary key # name :string(255) not null # path :string(255) not null -# owner_id :integer not null +# owner_id :integer # created_at :datetime not null # updated_at :datetime not null # type :string(255) @@ -26,36 +26,38 @@ class Group < Namespace presence: true, if: ->(group) { group.ldap_cn.present? } - after_create :add_owner - def human_name name end def owners - @owners ||= (users_groups.owners.map(&:user) << owner).uniq + @owners ||= users_groups.owners.map(&:user) end def add_users(user_ids, group_access) user_ids.compact.each do |user_id| - self.users_groups.create(user_id: user_id, group_access: group_access) + user = self.users_groups.find_or_initialize_by_user_id(user_id) + user.update_attributes(group_access: group_access) end end - def change_owner(user) - self.owner = user - membership = users_groups.where(user_id: user.id).first - - if membership - membership.update_attributes(group_access: UsersGroup::OWNER) - else - add_owner - end + def add_user(user, group_access) + self.users_groups.create(user_id: user.id, group_access: group_access) end - private + def add_owner(user) + self.add_user(user, UsersGroup::OWNER) + end - def add_owner - self.add_users([owner.id], UsersGroup::OWNER) + def has_owner?(user) + owners.include?(user) + end + + def last_owner?(user) + has_owner?(user) && owners.size == 1 + end + + def members + users_groups end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 7f367588b2..b164ea1107 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -18,6 +18,7 @@ # merge_status :string(255) # target_project_id :integer not null # iid :integer +# description :text # require Rails.root.join("app/models/commit") @@ -221,7 +222,11 @@ class MergeRequest < ActiveRecord::Base def mr_and_commit_notes commit_ids = commits.map(&:id) - Note.where("(noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR (noteable_type = 'Commit' AND commit_id IN (:commit_ids))", mr_id: id, commit_ids: commit_ids) + project.notes.where( + "(noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR (noteable_type = 'Commit' AND commit_id IN (:commit_ids))", + mr_id: id, + commit_ids: commit_ids + ) end # Returns the raw diff for this merge request diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 1895916693..fde06649c7 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -5,7 +5,7 @@ # id :integer not null, primary key # name :string(255) not null # path :string(255) not null -# owner_id :integer not null +# owner_id :integer # created_at :datetime not null # updated_at :datetime not null # type :string(255) @@ -20,7 +20,7 @@ class Namespace < ActiveRecord::Base has_many :projects, dependent: :destroy belongs_to :owner, class_name: "User" - validates :owner, presence: true + validates :owner, presence: true, unless: ->(n) { n.type == "Group" } validates :name, presence: true, uniqueness: true, length: { within: 0..255 }, format: { with: Gitlab::Regex.name_regex, diff --git a/app/models/network/commit.rb b/app/models/network/commit.rb index e31adcebbe..8417f200e3 100644 --- a/app/models/network/commit.rb +++ b/app/models/network/commit.rb @@ -1,5 +1,3 @@ -require "grit" - module Network class Commit include ActionView::Helpers::TagHelper diff --git a/app/models/note.rb b/app/models/note.rb index e819a5516b..7e7387abed 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -14,6 +14,7 @@ # commit_id :string(255) # noteable_id :integer # st_diff :text +# system :boolean default(FALSE), not null # require 'carrierwave/orm/activerecord' diff --git a/app/models/pivotaltracker_service.rb b/app/models/pivotaltracker_service.rb index f28e142da7..c5b1b9ab8d 100644 --- a/app/models/pivotaltracker_service.rb +++ b/app/models/pivotaltracker_service.rb @@ -10,6 +10,9 @@ # 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 PivotaltrackerService < Service diff --git a/app/models/project.rb b/app/models/project.rb index 9426747218..4988cc8bc1 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -24,8 +24,6 @@ # import_url :string(255) # -require "grit" - class Project < ActiveRecord::Base include Gitlab::ShellAdapter extend Enumerize @@ -48,12 +46,14 @@ class Project < ActiveRecord::Base has_one :campfire_service, dependent: :destroy has_one :pivotaltracker_service, dependent: :destroy has_one :hipchat_service, dependent: :destroy + has_one :flowdock_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 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 @@ -224,7 +224,7 @@ class Project < ActiveRecord::Base end def available_services_names - %w(gitlab_ci campfire hipchat pivotaltracker) + %w(gitlab_ci campfire hipchat pivotaltracker flowdock) end def gitlab_ci? @@ -252,10 +252,10 @@ class Project < ActiveRecord::Base end def owner - if namespace - namespace_owner + if group + group else - creator + namespace.try(:owner) end end @@ -279,10 +279,6 @@ class Project < ActiveRecord::Base end end - def namespace_owner - namespace.try(:owner) - end - def path_with_namespace if namespace namespace.path + '/' + path @@ -320,8 +316,11 @@ class Project < ActiveRecord::Base branch_name = ref.gsub("refs/heads/", "") c_ids = self.repository.commits_between(oldrev, newrev).map(&:id) - # Update code for merge requests + # Update code for merge requests into project between project branches mrs = self.merge_requests.opened.by_branch(branch_name).all + # Update code for merge requests between project and project fork + mrs += self.fork_merge_requests.opened.by_branch(branch_name).all + mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked } # Close merge requests diff --git a/app/models/repository.rb b/app/models/repository.rb index aeec48ee5c..97b4330092 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1,14 +1,19 @@ class Repository include Gitlab::ShellAdapter - attr_accessor :raw_repository + attr_accessor :raw_repository, :path_with_namespace def initialize(path_with_namespace, default_branch) - @raw_repository = Gitlab::Git::Repository.new(path_with_namespace, default_branch) + @path_with_namespace = path_with_namespace + @raw_repository = Gitlab::Git::Repository.new(path_to_repo) if path_with_namespace rescue Gitlab::Git::Repository::NoRepository nil end + def path_to_repo + @path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, path_with_namespace + ".git") + end + def exists? raw_repository end @@ -150,4 +155,8 @@ class Repository super end + + def blob_at(sha, path) + Gitlab::Git::Blob.find(self, sha, path) + end end diff --git a/app/models/tree.rb b/app/models/tree.rb index 042050527c..ed06cb1a12 100644 --- a/app/models/tree.rb +++ b/app/models/tree.rb @@ -1,17 +1,26 @@ class Tree - attr_accessor :raw + attr_accessor :entries, :readme - def initialize(repository, sha, ref = nil, path = nil) - @raw = Gitlab::Git::Tree.new(repository, sha, ref, path) + def initialize(repository, sha, path = '/') + path = '/' if path.blank? + git_repo = repository.raw_repository + @entries = Gitlab::Git::Tree.where(git_repo, sha, path) + + if readme_tree = @entries.find(&:readme?) + readme_path = path == '/' ? readme_tree.name : File.join(path, readme_tree.name) + @readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path) + end end - def method_missing(m, *args, &block) - @raw.send(m, *args, &block) + def trees + @entries.select(&:dir?) end - def respond_to?(method) - return true if @raw.respond_to?(method) + def blobs + @entries.select(&:file?) + end - super + def submodules + @entries.select(&:submodule?) end end diff --git a/app/models/user.rb b/app/models/user.rb index f4361987de..aeb3fe2def 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -38,13 +38,16 @@ # created_by_id :integer # +require 'carrierwave/orm/activerecord' +require 'file_size_validator' + class User < ActiveRecord::Base - devise :database_authenticatable, :token_authenticatable, :lockable, - :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :registerable + devise :database_authenticatable, :token_authenticatable, :lockable, :async, + :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :confirmable, :registerable attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username, :skype, :linkedin, :twitter, :color_scheme_id, :theme_id, :force_random_password, - :extern_uid, :provider, :password_expires_at, + :extern_uid, :provider, :password_expires_at, :avatar, as: [:default, :admin] attr_accessible :projects_limit, :can_create_group, @@ -113,6 +116,8 @@ class User < ActiveRecord::Base validate :namespace_uniq, if: ->(user) { user.username_changed? } + validates :avatar, file_size: { maximum: 100.kilobytes.to_i } + before_validation :generate_password, on: :create before_validation :sanitize_attrs @@ -135,7 +140,7 @@ class User < ActiveRecord::Base # Remove user from all groups user.users_groups.find_each do |membership| # skip owned resources - next if membership.group.owners.include?(user) + next if membership.group.last_owner?(user) return false unless membership.destroy end @@ -150,6 +155,8 @@ class User < ActiveRecord::Base end end + mount_uploader :avatar, AttachmentUploader + # Scopes scope :admins, -> { where(admin: true) } scope :blocked, -> { with_state(:blocked) } @@ -167,7 +174,7 @@ class User < ActiveRecord::Base # Class methods # class << self - # Devise method overridden to allow sing in with email or username + # Devise method overridden to allow sign in with email or username def find_for_database_authentication(warden_conditions) conditions = warden_conditions.dup if login = conditions.delete(:login) @@ -192,11 +199,7 @@ class User < ActiveRecord::Base end def by_username_or_id(name_or_id) - if (name_or_id.is_a?(Integer)) - User.find_by_id(name_or_id) - else - User.find_by_username(name_or_id) - end + where('username = ? OR id = ?', name_or_id, name_or_id).first end def build_user(attrs = {}, options= {}) @@ -211,7 +214,7 @@ class User < ActiveRecord::Base { projects_limit: Gitlab.config.gitlab.default_projects_limit, can_create_group: Gitlab.config.gitlab.default_can_create_group, - theme_id: Gitlab::Theme::MARS + theme_id: Gitlab.config.gitlab.default_theme } end end diff --git a/app/observers/project_observer.rb b/app/observers/project_observer.rb index acbb719b69..f301f30645 100644 --- a/app/observers/project_observer.rb +++ b/app/observers/project_observer.rb @@ -14,6 +14,17 @@ class ProjectObserver < BaseObserver log_info("#{project.owner.name} created a new project \"#{project.name_with_namespace}\"") end + + if project.wiki_enabled? + begin + # force the creation of a wiki, + GollumWiki.new(project, project.owner).wiki + rescue GollumWiki::CouldNotCreateWikiError => ex + # Prevent project observer crash + # if failed to create wiki + nil + end + end end def after_update(project) diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index f9d43e60de..3ad41de397 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -112,6 +112,7 @@ class GitPushService # ref: String, # user_id: String, # user_name: String, + # project_id: String, # repository: { # name: String, # url: String, @@ -136,6 +137,7 @@ class GitPushService ref: ref, user_id: user.id, user_name: user.name, + project_id: project.id, repository: { name: project.name, url: project.url_to_repo, diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index c872b27ba3..de4fa9b79e 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -23,18 +23,21 @@ class SystemHooksService case model when Project + owner = model.owner + data.merge!({ name: model.name, path: model.path, path_with_namespace: model.path_with_namespace, project_id: model.id, - owner_name: model.owner.name, - owner_email: model.owner.email + owner_name: owner.name, + owner_email: owner.respond_to?(:email) ? owner.email : nil }) when User data.merge!({ name: model.name, - email: model.email + email: model.email, + user_id: model.id }) when UsersProject data.merge!({ diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml index 2d4ffc10d5..122e952d87 100644 --- a/app/views/admin/background_jobs/show.html.haml +++ b/app/views/admin/background_jobs/show.html.haml @@ -2,3 +2,37 @@ %br .ui-box %iframe{src: sidekiq_path, width: '100%', height: 900, style: "border: none"} +%h4 Sidekiq running processes +- sidekiq_processes = `ps -eo euser,pid,pcpu,pmem,stat,start,command | grep sidekiq | grep -v grep` +- if sidekiq_processes.empty? + %b There are no running sidekiq processes + %b Please restart GitLab +- else + .ui-box + %table.zebra-striped + %thead + %th USER + %th + %th PID + %th + %th CPU + %th + %th MEM + %th + %th STATE + %th + %th START + %th + %th COMMAND + %th + - sidekiq_processes.split("\n").each do |process| + - next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/) + - data = process.gsub!(/\s+/m, '|').strip.split('|') + %tr + - 6.times do + %td= data.shift + %td + %td= data.join(" ") + %b If '[25 of 25 busy]' is shown, restart GitLab with 'sudo service gitlab reload'. + %br + %b If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u git -f sidekiq) and restart GitLab. diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 9ed788d0d9..3064763b99 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -1,30 +1,35 @@ +%h3.page-title + Admin area +%p.light + You can manage projects, users and other GitLab data from here. +%hr .admin_dash.row .span4 - .ui-box - .title Projects - .data.padded + .light-well + %h4 Projects + .data = link_to admin_projects_path do %h1= Project.count %hr - = link_to 'New Project', new_project_path, class: "btn btn-small" + = link_to 'New Project', new_project_path, class: "btn btn-new" .span4 - .ui-box - .title Users - .data.padded + .light-well + %h4 Users + .data = link_to admin_users_path do %h1= User.count %hr - = link_to 'New User', new_admin_user_path, class: "btn btn-small" + = link_to 'New User', new_admin_user_path, class: "btn btn-new" .span4 - .ui-box - .title Groups - .data.padded + .light-well + %h4 Groups + .data = link_to admin_groups_path do %h1= Group.count %hr - = link_to 'New Group', new_admin_group_path, class: "btn btn-small" + = link_to 'New Group', new_admin_group_path, class: "btn btn-new" -.row +.row.prepend-top-10 .span4 %h4 Latest projects %hr diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index 8e45dc76ec..c9d7c24f20 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -31,11 +31,8 @@ .clearfix.light.append-bottom-10 %span - %b Owner: - - if group.owner - = link_to group.owner_name, admin_user_path(group.owner) - - else - (deleted) + %b Members: + %span.badge= group.members.size \| %span %b Projects: diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 5509811bae..1566c34580 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -24,26 +24,6 @@ %strong = @group.description - %li - %span.light Owned by: - %strong - - if @group.owner - = link_to @group.owner_name, admin_user_path(@group.owner) - - else - (deleted) - .pull-right - = link_to "#", class: "btn btn-small change-owner-link" do - %i.icon-edit - Change owner - %li.change-owner-holder.hide.bgred - .form-holder - %strong.cred New Owner: - = form_for [:admin, @group] do |f| - = users_select_tag(:"group[owner_id]") - .prepend-top-10 - = f.submit 'Change Owner', class: "btn btn-remove" - = link_to "Cancel", "#", class: "btn change-owner-cancel-link" - %li %span.light Created at: %strong @@ -92,4 +72,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 + %i.icon-minus.icon-white diff --git a/app/views/admin/hooks/_data_ex.html.erb b/app/views/admin/hooks/_data_ex.html.erb index b69aa92716..5b16dfb398 100644 --- a/app/views/admin/hooks/_data_ex.html.erb +++ b/app/views/admin/hooks/_data_ex.html.erb @@ -52,7 +52,8 @@ "created_at": "2012-07-21T07:44:07Z", "email": "js@gitlabhq.com", "event_name": "user_create", - "name": "John Smith" + "name": "John Smith", + "user_id": 41 } 6. User removed: @@ -60,7 +61,8 @@ "created_at": "2012-07-21T07:44:07Z", "email": "js@gitlabhq.com", "event_name": "user_destroy", - "name": "John Smith" + "name": "John Smith", + "user_id": 41 } eos diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index eb6570af30..1bd7b8eac6 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -1,9 +1,12 @@ -.alert.alert-info - %span - Post-receive hooks for binding events. - %br - Read more about system hooks - %strong #{link_to "here", help_system_hooks_path, class: "vlink"} +%h3.page-title + System Hooks + +%p.light + #{link_to "System hooks ", help_system_hooks_path, class: "vlink"} can be + used for binding events when GitLab creates a User or Project. + +%hr + = form_for @hook, as: :hook, url: admin_hooks_path, html: { class: 'form-inline' } do |f| -if @hook.errors.any? diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index cecc84fe09..c8a8732820 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -25,7 +25,7 @@ %span.light Owned by: %strong - if @project.owner - = link_to @project.owner_name, admin_user_path(@project.owner) + = link_to @project.owner_name, [:admin, @project.owner] - else (deleted) diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 3df9903e40..655ed5f7f7 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -1,5 +1,5 @@ %h3.page-title - User: + %span.cgray User: = @user.name - if @user.blocked? %span.cred (Blocked) @@ -10,8 +10,6 @@ = link_to edit_admin_user_path(@user), class: "btn grouped" do %i.icon-edit Edit - - if @user.blocked? - = link_to 'Unblock', unblock_admin_user_path(@user), method: :put, class: "btn grouped success" %hr .row @@ -20,7 +18,7 @@ .title Account: .pull-right - = image_tag gravatar_icon(@user.email, 32), class: "avatar s32" + = image_tag avatar_icon(@user.email, 32), class: "avatar s32" %ul.well-list %li %span.light Name: @@ -67,17 +65,30 @@ = link_to @user.created_by.name, [:admin, @user.created_by] - unless @user == current_user - .alert - %h4 Block user - %br - %p Blocking user has the following effects: - %ul - %li User will not be able to login - %li User will not be able to access git repositories - %li User will be removed from joined projects and groups - %li Personal projects will be left - %li Owned groups will be left - = link_to 'Block user', block_admin_user_path(@user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-remove" + - if @user.blocked? + .alert.alert-info + %h4 This user is blocked + %br + %p Blocking user has the following effects: + %ul + %li User will not be able to login + %li User will not be able to access git repositories + %li User will be removed from joined projects and groups + %li Personal projects will be left + %li Owned groups will be left + = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-new", confirm: 'Are you sure?' + - else + .alert + %h4 Block this user + %br + %p Blocking user has the following effects: + %ul + %li User will not be able to login + %li User will not be able to access git repositories + %li User will be removed from joined projects and groups + %li Personal projects will be left + %li Owned groups will be left + = link_to 'Block user', block_admin_user_path(@user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-remove" .alert.alert-error %h4 @@ -117,11 +128,7 @@ - tm = project.team.find_tm(@user.id) %li.users_project = link_to admin_project_path(project), class: dom_class(project) do - - if project.namespace - = project.namespace.human_name - \/ - %strong.well-title - = truncate(project.name, length: 45) + = project.name_with_namespace - if tm .pull-right diff --git a/app/views/dashboard/_groups.html.haml b/app/views/dashboard/_groups.html.haml index 8b32c5642b..b4f3866228 100644 --- a/app/views/dashboard/_groups.html.haml +++ b/app/views/dashboard/_groups.html.haml @@ -14,9 +14,6 @@ = truncate(group.name, length: 35) %span.arrow %i.icon-angle-right - %span.owner-info - %span Owner: - %span.owner= group.owner_name - if groups.blank? %li %h3.nothing_here_message You have no groups yet. diff --git a/app/views/dashboard/_project.html.haml b/app/views/dashboard/_project.html.haml new file mode 100644 index 0000000000..50b833f3d8 --- /dev/null +++ b/app/views/dashboard/_project.html.haml @@ -0,0 +1,12 @@ += link_to project_path(project), class: dom_class(project) do + %span.namespace-name + - if project.namespace + = project.namespace.human_name + \/ + %span.project-name.filter-title + = truncate(project.name, length: 25) + %span.arrow + %i.icon-angle-right + %span.last-activity + %span Last activity: + %span.date= project_last_activity(project) diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml index 90b843de01..b79b27fc95 100644 --- a/app/views/dashboard/_projects.html.haml +++ b/app/views/dashboard/_projects.html.haml @@ -10,21 +10,16 @@ %ul.well-list.dash-list - projects.each do |project| %li.project-row - = link_to project_path(project), class: dom_class(project) do - %span.namespace-name - - if project.namespace - = project.namespace.human_name - \/ - %span.project-name.filter-title - = truncate(project.name, length: 25) - %span.arrow - %i.icon-angle-right - %span.last-activity - %span Last activity: - %span.date= project_last_activity(project) + = render "project", project: project + - if projects.blank? %li %h3.nothing_here_message There are no projects here. - - if @projects_count > 20 + - if @projects_count > @projects_limit %li.bottom - %strong= link_to "show all projects", projects_dashboard_path + %span.light + #{@projects_limit} of #{pluralize(@projects_count, 'project')} displayed. + .pull-right.append-right-10 + = link_to projects_dashboard_path do + Show all + %i.icon-angle-right diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder index 0f0f3466e9..f541355778 100644 --- a/app/views/dashboard/issues.atom.builder +++ b/app/views/dashboard/issues.atom.builder @@ -12,7 +12,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.link :href => project_issue_url(issue.project, issue) xml.title truncate(issue.title, :length => 80) xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(issue.author_email) + xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email) xml.author do |author| xml.name issue.author_name xml.email issue.author_email diff --git a/app/views/dashboard/show.atom.builder b/app/views/dashboard/show.atom.builder index a913df9229..f4cf24ccd9 100644 --- a/app/views/dashboard/show.atom.builder +++ b/app/views/dashboard/show.atom.builder @@ -17,7 +17,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.link :href => event_link xml.title truncate(event_title, :length => 80) xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(event.author_email) + xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email) xml.author do |author| xml.name event.author_name xml.email event.author_email diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index c6e1d8db57..15854effbf 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -18,7 +18,7 @@ = render 'devise/sessions/oauth_providers' if devise_mapping.omniauthable? - - if Gitlab.config.gitlab.signup_enabled + - if gitlab_config.signup_enabled %hr %div Don't have an account? diff --git a/app/views/devise/shared/_links.erb b/app/views/devise/shared/_links.erb index a47b5ff1ec..49e99e25c1 100644 --- a/app/views/devise/shared/_links.erb +++ b/app/views/devise/shared/_links.erb @@ -2,7 +2,7 @@ <%= link_to "Sign in", new_session_path(resource_name), class: "btn" %>
<% end -%> -<%- if devise_mapping.registerable? && controller_name != 'registrations' %> +<%- if devise_mapping.registerable? && controller_name != 'registrations' && gitlab_config.signup_enabled %> <%= link_to "Sign up", new_registration_path(resource_name) %>
<% end -%> diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index b3543460d6..892dacafa6 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -4,7 +4,7 @@ #{time_ago_in_words(event.created_at)} ago. = cache event do - = image_tag gravatar_icon(event.author_email), class: "avatar s24", alt:'' + = image_tag avatar_icon(event.author_email), class: "avatar s24", alt:'' - if event.push? = render "events/event/push", event: event diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml index adba9a5f61..f181df23eb 100644 --- a/app/views/events/event/_push.html.haml +++ b/app/views/events/event/_push.html.haml @@ -7,7 +7,7 @@ = link_to project_commits_path(event.project, event.ref_name) do %strong= truncate(event.ref_name, length: 30) at - %strong= link_to_project event.project + = link_to_project event.project - if event.push_with_commits? - project = event.project diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 3b1d633af1..f76a9d9586 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -11,8 +11,6 @@ Projects %li = link_to 'LDAP', '#tab-ldap', 'data-toggle' => 'tab' - %li - = link_to 'Transfer', '#tab-transfer', 'data-toggle' => 'tab' %li = link_to 'Remove', '#tab-remove', 'data-toggle' => 'tab' @@ -87,17 +85,6 @@ .form-actions = f.submit 'Save group', class: "btn btn-save" - .tab-pane#tab-transfer - .ui-box.ui-box-danger - .title Transfer group - .ui-box-body - %p - Transferring group will cause loss of admin control over group and all child projects - = form_for @group do |f| - = users_select_tag(:'group[owner_id]') - %hr - = f.submit 'Transfer group', class: "btn btn-small btn-remove" - .tab-pane#tab-remove .ui-box.ui-box-danger .title Remove group diff --git a/app/views/groups/issues.atom.builder b/app/views/groups/issues.atom.builder index 701747bdbc..f2005193f8 100644 --- a/app/views/groups/issues.atom.builder +++ b/app/views/groups/issues.atom.builder @@ -12,7 +12,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.link :href => project_issue_url(issue.project, issue) xml.title truncate(issue.title, :length => 80) xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(issue.author_email) + xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email) xml.author do |author| xml.name issue.author_name xml.email issue.author_email diff --git a/app/views/groups/members.html.haml b/app/views/groups/members.html.haml index 77bab11b33..124560e478 100644 --- a/app/views/groups/members.html.haml +++ b/app/views/groups/members.html.haml @@ -2,6 +2,9 @@ Group members %p.light Members of group have access to all group projects. + Read more about permissions + %strong= link_to "here", help_permissions_path, class: "vlink" + %hr - can_manage_group = current_user.can? :manage_group, @group .ui-box diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder index edf03642d8..e07bb7d2fb 100644 --- a/app/views/groups/show.atom.builder +++ b/app/views/groups/show.atom.builder @@ -16,7 +16,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.link :href => event_link xml.title truncate(event_title, :length => 80) xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(event.author_email) + xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email) xml.author do |author| xml.name event.author_name xml.email event.author_email diff --git a/app/views/help/_layout.html.haml b/app/views/help/_layout.html.haml index ac8660dcbb..da917888ee 100644 --- a/app/views/help/_layout.html.haml +++ b/app/views/help/_layout.html.haml @@ -30,5 +30,8 @@ %li %strong= link_to "Public Access", help_public_access_path + %li + %strong= link_to "Security", help_security_path + .span9.pull-right = yield diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index 9b18d03408..7df53c6d38 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -56,7 +56,7 @@ %p Get familiar with GitLab's permission levels. %li - %strong= link_to "API", help_api_path + %strong= link_to "API", help_api_file_path(category: 'README') %p Explore how you can access GitLab via a simple and powerful API. %li @@ -80,3 +80,7 @@ %li %strong= link_to "Public Access", help_public_access_path %p Learn how you can allow public access to a project. + + %li + %strong= link_to "Security", help_security_path + %p Learn what you can do to secure your GitLab instance. diff --git a/app/views/help/permissions.html.haml b/app/views/help/permissions.html.haml index da5210bad3..df35f41fc9 100644 --- a/app/views/help/permissions.html.haml +++ b/app/views/help/permissions.html.haml @@ -1,6 +1,11 @@ = render layout: 'help/layout' do %h3.page-title Permissions + %p.light Users have different abilities depending on the access level they have in particular group or project. + %p.light If a user is both in a project group and in the project itself the highest permission level is used. + %p.light If a user is a GitLab administrator they receive all permissions. + %hr + %h4 Project: %table.table %thead %tr @@ -158,3 +163,50 @@ %td %td %td.permission-x ✓ + + %h4 Group + %table.table + %thead + %tr + %th Action + %th Guest + %th Reporter + %th Developer + %th Master + %th Owner + %tbody + %tr + %td Browse group + %td.permission-x ✓ + %td.permission-x ✓ + %td.permission-x ✓ + %td.permission-x ✓ + %td.permission-x ✓ + %tr + %td Edit group + %td + %td + %td + %td + %td.permission-x ✓ + %tr + %td Create project in group + %td + %td + %td + %td + %td.permission-x ✓ + %tr + %td Manage group members + %td + %td + %td + %td + %td.permission-x ✓ + %tr + %td Remove group + %td + %td + %td + %td + %td.permission-x ✓ diff --git a/app/views/help/security.html.haml b/app/views/help/security.html.haml new file mode 100644 index 0000000000..72f21e9f63 --- /dev/null +++ b/app/views/help/security.html.haml @@ -0,0 +1,15 @@ += render layout: 'help/layout' do + %h3.page-title Security + + %p.slead + If your GitLab instance is visible from the internet chances are it will be 'tested' by bots sooner or later. + %br + %br + %br + .file-holder + .file-title + %i.icon-file + Dealing with bruteforcing + .file-content.wiki + = preserve do + = markdown File.read(Rails.root.join("doc", "security", "rack_attack.md")) diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml index 6492c122ba..80c42fe638 100644 --- a/app/views/layouts/_head_panel.html.haml +++ b/app/views/layouts/_head_panel.html.haml @@ -30,11 +30,11 @@ = link_to new_project_path, title: "New project", class: 'has_bottom_tooltip', 'data-original-title' => 'New project' do %i.icon-plus %li - = link_to profile_path, title: "My profile", class: 'has_bottom_tooltip', 'data-original-title' => 'My profile' do + = link_to profile_path, title: "Profile settings", class: 'has_bottom_tooltip', 'data-original-title' => 'Profile settings"' do %i.icon-user %li = link_to destroy_user_session_path, class: "logout", method: :delete, title: "Logout", class: 'has_bottom_tooltip', 'data-original-title' => 'Logout' do %i.icon-signout %li = link_to current_user, class: "profile-pic", id: 'profile-pic' do - = image_tag gravatar_icon(current_user.email, 26), alt: '' + = image_tag avatar_icon(current_user.email, 26), alt: 'User activity' diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml new file mode 100644 index 0000000000..3c4bd857c2 --- /dev/null +++ b/app/views/layouts/_public_head_panel.html.haml @@ -0,0 +1,22 @@ +%header.navbar.navbar-static-top.navbar-gitlab + .navbar-inner + .container + %div.app_logo + %span.separator + = link_to public_root_path, class: "home" do + %h1 GITLAB + %span.separator + %h1.project_name + - if @project + = project_title(@project) + - else + Public Projects + + %ul.nav + %li + %a + %div.hide.turbolink-spinner + %i.icon-refresh.icon-spin + Loading... + %li + = link_to "Sign in", new_session_path(:user), class: 'btn btn-sign-in' diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index fb20e8acf9..165a1487a7 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -6,7 +6,12 @@ .container .content %center - = image_tag image_path "login-logo.png" + %h1 GitLab + %p.light + 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 = yield %center %h5.cwhite Enterprise Edition diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index 7c3acfc398..d44cb975ea 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -2,8 +2,11 @@ = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to profile_path, title: "Profile" do %i.icon-home - = nav_link(path: 'profiles#account') do - = link_to "Account", account_profile_path + = nav_link(controller: :accounts) do + = link_to "Account", profile_account_path + - unless current_user.ldap_user? + = nav_link(controller: :passwords) do + = link_to "Password", edit_profile_password_path = nav_link(controller: :notifications) do = link_to "Notifications", profile_notifications_path = nav_link(controller: :keys) do diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml index 7dce0cbeae..f922dcc420 100644 --- a/app/views/layouts/public.html.haml +++ b/app/views/layouts/public.html.haml @@ -1,28 +1,11 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "Public Projects" - %body{class: "#{app_theme} application", :'data-page' => body_data_page} + %body{class: "ui_mars application", :'data-page' => body_data_page} - if current_user = render "layouts/head_panel", title: "Public Projects" - else - %header.navbar.navbar-static-top.navbar-gitlab - .navbar-inner - .container - %div.app_logo - %span.separator - = link_to public_root_path, class: "home" do - %h1 GITLAB - %span.separator - %h1.project_name Public Projects - %ul.nav - %li - %a - %div.hide.turbolink-spinner - %i.icon-refresh.icon-spin - Loading... - %li - = link_to "Sign in", new_session_path(:user) + = render "layouts/public_head_panel" .container.navless-container - .content - = yield + .content= yield diff --git a/app/views/layouts/public_projects.html.haml b/app/views/layouts/public_projects.html.haml new file mode 100644 index 0000000000..cfe6a63055 --- /dev/null +++ b/app/views/layouts/public_projects.html.haml @@ -0,0 +1,9 @@ +!!! 5 +%html{ lang: "en"} + = render "layouts/head", title: @project.name_with_namespace + %body{class: "ui_mars application", :'data-page' => body_data_page} + = render "layouts/public_head_panel" + %nav.main-nav + .container= render 'layouts/nav/project' + .container + .content= yield diff --git a/app/views/profiles/account.html.haml b/app/views/profiles/account.html.haml deleted file mode 100644 index 8f43db6624..0000000000 --- a/app/views/profiles/account.html.haml +++ /dev/null @@ -1,132 +0,0 @@ -%h3.page-title - Account settings -%p.light - You can change your password, username and private token here. - - if current_user.ldap_user? - Some options are unavailable for LDAP accounts -%hr - - -.row - .span2 - %ul.nav.nav-pills.nav-stacked.nav-stacked-menu - %li.active - = link_to '#tab-token', 'data-toggle' => 'tab' do - Private Token - %li - = link_to '#tab-password', 'data-toggle' => 'tab' do - Password - - - if show_profile_social_tab? - %li - = link_to '#tab-social', 'data-toggle' => 'tab' do - Social Accounts - - - if show_profile_username_tab? - %li - = link_to '#tab-username', 'data-toggle' => 'tab' do - Change Username - - - if show_profile_remove_tab? - %li - = link_to '#tab-remove', 'data-toggle' => 'tab' do - Remove Account - .span10 - .tab-content - .tab-pane.active#tab-token - %fieldset.update-token - %legend - Private token - %span.cred.pull-right - keep it secret! - %div - = form_for @user, url: reset_private_token_profile_path, method: :put do |f| - .data - %p.slead - Your private token is used to access application resources without authentication. - %br - It can be used for atom feeds or the API. - %p.cgray - - if current_user.private_token - = text_field_tag "token", current_user.private_token, class: "input-xxlarge large_text input-xpadding" - = f.submit 'Reset', confirm: "Are you sure?", class: "btn btn-primary btn-build-token" - - else - %span You don`t have one yet. Click generate to fix it. - = f.submit 'Generate', class: "btn success btn-build-token" - - .tab-pane#tab-password - %fieldset.update-password - %legend Password - = form_for @user, url: update_password_profile_path, method: :put do |f| - %div - %p.slead After a successful password update you will be redirected to login page where you should login with your new password - -if @user.errors.any? - .alert.alert-error - %ul - - @user.errors.full_messages.each do |msg| - %li= msg - .control-group - = f.label :password - .controls= f.password_field :password, required: true - .control-group - = f.label :password_confirmation - .controls - = f.password_field :password_confirmation, required: true - .control-group - .controls - = f.submit 'Save password', class: "btn btn-save" - - - if show_profile_social_tab? - .tab-pane#tab-social - %fieldset - %legend Social Accounts - .oauth_select_holder - %p.hint Tip: Click on icon to activate signin with one of the following services - - enabled_social_providers.each do |provider| - %span{class: oauth_active_class(provider) } - = link_to authbutton(provider, 32), omniauth_authorize_path(User, provider) - - - if show_profile_username_tab? - .tab-pane#tab-username - %fieldset.update-username - %legend - Username - %small.cred.pull-right - Changing your username can have unintended side effects! - = form_for @user, url: update_username_profile_path, method: :put, remote: true do |f| - %div - .control-group - = f.label :username - .controls - = f.text_field :username, required: true -   - %span.loading-gif.hide= image_tag "ajax_loader.gif" - %span.update-success.cgreen.hide - %i.icon-ok - Saved - %span.update-failed.cred.hide - %i.icon-remove - Failed - %ul.cred - %li This will change the web URL for personal projects. - %li This will change the git path to repositories for personal projects. - .controls - = f.submit 'Save username', class: "btn btn-save" - - - if show_profile_remove_tab? - .tab-pane#tab-remove - %fieldset.remove-account - %legend - Remove account - %div - %p Deleting an account has the following effects: - %ul - %li All user content like authored issues, snippets, comments will be removed - - rp = current_user.personal_projects.count - - unless rp.zero? - %li #{pluralize rp, 'personal project'} will be removed and cannot be restored - - if current_user.solo_owned_groups.present? - %li - Next groups will be abandoned. You should transfer or remove them: - %strong #{current_user.solo_owned_groups.map(&:name).join(', ')} - = link_to 'Delete account', user_registration_path, confirm: "REMOVE #{current_user.name}? Are you sure?", method: :delete, class: "btn btn-remove" diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml new file mode 100644 index 0000000000..453ef8b60d --- /dev/null +++ b/app/views/profiles/accounts/show.html.haml @@ -0,0 +1,73 @@ +%h3.page-title + Account settings +%p.light + You can change your username and private token here. + - if current_user.ldap_user? + Some options are unavailable for LDAP accounts +%hr + + +.account-page + %fieldset.update-token + %legend + Private token + %div + = form_for @user, url: reset_private_token_profile_path, method: :put do |f| + .data + %p + Your private token is used to access application resources without authentication. + %br + It can be used for atom feeds or the API. + %span.cred + Keep it secret! + + %p.cgray + - if current_user.private_token + = text_field_tag "token", current_user.private_token, class: "input-xlarge input-xpadding pull-left" + = f.submit 'Reset', 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" + + + - if show_profile_social_tab? + %fieldset + %legend Social Accounts + .oauth_select_holder.append-bottom-10 + %p Click on icon to activate signin with one of the following services + - enabled_social_providers.each do |provider| + %span{class: oauth_active_class(provider) } + = link_to authbutton(provider, 32), omniauth_authorize_path(User, provider) + + - if show_profile_username_tab? + %fieldset.update-username + %legend + Username + = form_for @user, url: update_username_profile_path, method: :put, remote: true do |f| + %p + Changing your username will change path to all personl projects! + %div + = f.text_field :username, required: true, class: 'input-xlarge input-xpadding' +   + %span.loading-gif.hide= image_tag "ajax_loader.gif" + %p.light + = user_url(@user) + %div + = f.submit 'Save username', class: "btn btn-save" + + - if show_profile_remove_tab? + %fieldset.remove-account + %legend + Remove account + %div + %p Deleting an account has the following effects: + %ul + %li All user content like authored issues, snippets, comments will be removed + - rp = current_user.personal_projects.count + - unless rp.zero? + %li #{pluralize rp, 'personal project'} will be removed and cannot be restored + - if current_user.solo_owned_groups.present? + %li + Next groups will be abandoned. You should transfer or remove them: + %strong #{current_user.solo_owned_groups.map(&:name).join(', ')} + = link_to 'Delete account', user_registration_path, confirm: "REMOVE #{current_user.name}? Are you sure?", method: :delete, class: "btn btn-remove" diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml new file mode 100644 index 0000000000..a5fa6e7f18 --- /dev/null +++ b/app/views/profiles/passwords/edit.html.haml @@ -0,0 +1,32 @@ +%h3.page-title Password +%p.light + Change your password or recover your current one. +%hr +.update-password + = form_for @user, url: profile_password_path, method: :put do |f| + %div + %p.slead + You must provide current password in order to change it. + %br + After a successful password update you will be redirected to login page where you should login with your new password + -if @user.errors.any? + .alert.alert-error + %ul + - @user.errors.full_messages.each do |msg| + %li= msg + .control-group + = f.label :current_password + .controls + = f.password_field :current_password, required: true + %div + = link_to "Forgot your password?", reset_profile_password_path, method: :put + + .control-group + = f.label :password, 'New password' + .controls= f.password_field :password, required: true + .control-group + = f.label :password_confirmation + .controls + = f.password_field :password_confirmation, required: true + .form-actions + = f.submit 'Save password', class: "btn btn-save" diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 25bf7912f1..2dce690c8d 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -1,18 +1,14 @@ -= image_tag gravatar_icon(@user.email, 60), alt: '', class: 'avatar s60' %h3.page-title - = @user.name - %br - %small - = @user.email - - .pull-right - = link_to destroy_user_session_path, class: "logout", method: :delete do - %small - %i.icon-signout - Logout + Profile settings +%p.light + This information appears on your profile. + - if current_user.ldap_user? + Some options are unavailable for LDAP accounts %hr -= form_for @user, url: profile_path, method: :put, html: { class: "edit_user form-horizontal" } do |f| + + += form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit_user form-horizontal" } do |f| -if @user.errors.any? %div.alert.alert-error %ul @@ -29,7 +25,12 @@ = f.label :email, class: "control-label" .controls = f.text_field :email, class: "input-xlarge", required: true - %span.help-block We also use email for avatar detection. + - 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" @@ -46,45 +47,23 @@ %span.help-block Tell us about yourself in fewer than 250 characters. .span5.pull-right - %fieldset.tips - %legend Tips: - %ul - %li - %p You can change your password on the Account page - - if Gitlab.config.gravatar.enabled - %li - %p You can change your avatar at #{link_to "gravatar.com", "http://gravatar.com"} + .light-well + = image_tag avatar_icon(@user.email, 160), alt: '', class: 'avatar s160' - - if Gitlab.config.omniauth.enabled && @user.provider? - %li - %p - You can login through #{@user.provider.titleize}! - = link_to "click here to change", account_profile_path - - if current_user.can_create_group? - %li - %p - Need a group for several dependent projects? - = link_to new_group_path, class: "btn btn-tiny" do - Create a group - - unless current_user.projects_limit_left > 100 - %fieldset - %legend - Personal projects: - %small.pull-right - %span= current_user.personal_projects.count - of - %span= current_user.projects_limit - .padded - .progress - .bar{style: "width: #{current_user.projects_limit_percent}%;"} - - %fieldset - %legend - SSH public keys: - %span.pull-right - = link_to pluralize(current_user.keys.count, 'key'), profile_keys_path - .padded - = link_to "Add Public Key", new_profile_key_path, class: "btn btn-small" + .clearfix + .profile-avatar-form-option + %p.light + You can upload an avatar here + %br + or change it at #{link_to "gravatar.com", "http://gravatar.com"} + %hr + %a.choose-btn.btn.btn-small.js-choose-user-avatar-button + %i.icon-paper-clip + %span Choose File ... +   + %span.file_name.js-avatar-filename File name... + = f.file_field :avatar, class: "js-user-avatar-input hide" + %span.help-block The maximum file size allowed is 200KB. .form-actions = f.submit 'Save changes', class: "btn btn-save" diff --git a/app/views/profiles/update_username.js.haml b/app/views/profiles/update_username.js.haml index abd90269c9..249680bcab 100644 --- a/app/views/profiles/update_username.js.haml +++ b/app/views/profiles/update_username.js.haml @@ -1,6 +1,6 @@ - if @user.valid? :plain - $('.update-username .update-success').show(); + new Flash("Username sucessfully changed", "notice") - else :plain - $('.update-username .update-failed').show(); + new Flash("Username change failed - #{@user.errors.full_messages.first}", "alert") diff --git a/app/views/projects/_clone_panel.html.haml b/app/views/projects/_clone_panel.html.haml index 7228c760d2..c2f85e8ebe 100644 --- a/app/views/projects/_clone_panel.html.haml +++ b/app/views/projects/_clone_panel.html.haml @@ -5,7 +5,7 @@ .span3.pull-right .pull-right - unless @project.empty_repo? - - if can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace + - if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace - if current_user.already_forked?(@project) = link_to project_path(current_user.fork_of(@project)), class: 'btn grouped disabled' do %i.icon-code-fork @@ -19,37 +19,38 @@ %i.icon-download-alt %span.only-wide Download - .dropdown.pull-right - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %i.icon-plus-sign-alt - %span.only-wide New - %b.caret - %ul.dropdown-menu - - if @project.issues_enabled && can?(current_user, :write_issue, @project) - %li - = link_to url_for_new_issue, title: "New Issue" do - Issue - - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project) - %li - = link_to new_project_merge_request_path(@project), title: "New Merge Request" do - Merge Request - - if @project.snippets_enabled && can?(current_user, :write_snippet, @project) - %li - = link_to new_project_snippet_path(@project), title: "New Snippet" do - Snippet - - if can? current_user, :push_code, @project - %li.divider - %li - = link_to new_project_branch_path(@project) do - %i.icon-code-fork - Git branch - %li - = link_to new_project_tag_path(@project) do - %i.icon-tag - Git tag + - if current_user + .dropdown.pull-right + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %i.icon-plus-sign-alt + %span.only-wide New + %b.caret + %ul.dropdown-menu + - if @project.issues_enabled && can?(current_user, :write_issue, @project) + %li + = link_to url_for_new_issue, title: "New Issue" do + Issue + - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project) + %li + = link_to new_project_merge_request_path(@project), title: "New Merge Request" do + Merge Request + - if @project.snippets_enabled && can?(current_user, :write_snippet, @project) + %li + = link_to new_project_snippet_path(@project), title: "New Snippet" do + Snippet + - if can? current_user, :push_code, @project + %li.divider + %li + = link_to new_project_branch_path(@project) do + %i.icon-code-fork + Git branch + %li + = link_to new_project_tag_path(@project) do + %i.icon-tag + Git tag - - if can?(current_user, :admin_team_member, @project) - %li.divider - %li - = link_to new_project_team_member_path(@project), title: "New project member" do - Project member + - if can?(current_user, :admin_team_member, @project) + %li.divider + %li + = link_to new_project_team_member_path(@project), title: "New project member" do + Project member diff --git a/app/views/projects/blob/_download.html.haml b/app/views/projects/blob/_download.html.haml index f3da1a2a21..ff317f9020 100644 --- a/app/views/projects/blob/_download.html.haml +++ b/app/views/projects/blob/_download.html.haml @@ -1,8 +1,7 @@ -.file-content.blob_file +.file-content.blob_file.blob-no-preview %center = link_to project_raw_path(@project, @id) do - %div.padded - %h4 - %i.icon-download-alt - %br - Download (#{number_to_human_size blob.size}) + %h1.light + %i.icon-download-alt + %h4 + Download (#{number_to_human_size blob.size}) diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index b839252579..4372647a41 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -24,7 +24,7 @@ %p = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do = commit.short_id - = image_tag gravatar_icon(commit.author_email), class: "avatar s16", alt: '' + = image_tag avatar_icon(commit.author_email), class: "avatar s16", alt: '' %span.light = gfm escape_once(truncate(commit.title, length: 40)) %span diff --git a/app/views/projects/commits/_diffs.html.haml b/app/views/projects/commits/_diffs.html.haml index c51f1b6eff..2e61b9ece1 100644 --- a/app/views/projects/commits/_diffs.html.haml +++ b/app/views/projects/commits/_diffs.html.haml @@ -37,9 +37,9 @@ - unless @suppress_diff - diffs.each_with_index do |diff, i| - next if diff.diff.empty? - - file = Gitlab::Git::Blob.new(project.repository, @commit.id, @ref, diff.new_path) - - file = Gitlab::Git::Blob.new(project.repository, @commit.parent_id, @ref, diff.old_path) unless file.exists? - - next unless file.exists? + - file = project.repository.blob_at(@commit.id, diff.new_path) + - file = project.repository.blob_at(@commit.parent_id, diff.old_path) unless file + - next unless file .file{id: "diff-#{i}"} .header - if diff.deleted_file @@ -64,7 +64,7 @@ - if file.text? = render "projects/commits/text_file", diff: diff, index: i - elsif file.image? - - old_file = Gitlab::Git::Blob.new(project.repository, @commit.parent_id, @ref, diff.old_path) if @commit.parent_id + - old_file = project.repository.blob_at(@commit.parent_id, diff.old_path) if @commit.parent_id = render "projects/commits/image", diff: diff, old_file: old_file, file: file, index: i - else %p.nothing_here_message No preview for this file type diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index 624604142b..c2da9f273b 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -21,7 +21,7 @@ Stats - - if current_controller?(:commits) && current_user.private_token + - if current_user && current_controller?(:commits) && current_user.private_token %li.pull-right = link_to project_commits_path(@project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Feed" do %i.icon-rss diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder index 46f9838e84..27c8fa6da7 100644 --- a/app/views/projects/commits/show.atom.builder +++ b/app/views/projects/commits/show.atom.builder @@ -12,7 +12,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.link :href => project_commit_url(@project, :id => commit.id) xml.title truncate(commit.title, :length => 80) xml.updated commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(commit.author_email) + xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(commit.author_email) xml.author do |author| xml.name commit.author_name xml.email commit.author_email diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 117ee13140..03cd3cbd65 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -67,7 +67,7 @@ - if Project.issues_tracker.values.count > 1 .control-group = f.label :issues_tracker, "Issues tracker", class: 'control-label' - .controls= f.select(:issues_tracker, Project.issues_tracker.values, {}, { disabled: !@project.issues_enabled }) + .controls= f.select(:issues_tracker, project_issues_trackers, {}, { disabled: !@project.issues_enabled }) .control-group = f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label' diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 001857cefd..b5dffb772a 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -6,7 +6,7 @@ = image_tag "ajax_loader.gif" %h3 Importing repository. %p.monospace git clone --bare #{@project.import_url} - %p Please wait until we import repository for you. Refresh at will. + %p Please wait while we import the repository for you. Refresh at will. :javascript new ProjectImport(); @@ -16,8 +16,8 @@ %legend Git global setup: %pre.dark :preserve - git config --global user.name "#{current_user.name}" - git config --global user.email "#{current_user.email}" + git config --global user.name "#{git_user_name}" + git config --global user.email "#{git_user_email}" %fieldset %legend Create Repository diff --git a/app/views/projects/hooks/_data_ex.html.erb b/app/views/projects/hooks/_data_ex.html.erb index b4281fa18c..5092aaf675 100644 --- a/app/views/projects/hooks/_data_ex.html.erb +++ b/app/views/projects/hooks/_data_ex.html.erb @@ -5,6 +5,7 @@ "ref": "refs/heads/master", "user_id": 4, "user_name": "John Smith", + "project_id": 15, "repository": { "name": "Diaspora", "url": "git@localhost:diaspora.git", diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index 6acad9134d..05cfb2788b 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -1,5 +1,5 @@ %div.issue-form-holder - %h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.id}" + %h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.iid}" = form_for [@project, @issue] do |f| -if @issue.errors.any? .alert.alert-error diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml index 44d14d5cdf..438cc02b47 100644 --- a/app/views/projects/issues/_head.html.haml +++ b/app/views/projects/issues/_head.html.haml @@ -5,6 +5,7 @@ = link_to 'Milestones', project_milestones_path(@project), class: "tab" = nav_link(controller: :labels) do = link_to 'Labels', project_labels_path(@project), class: "tab" - %li.pull-right - = link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do - %i.icon-rss + - if current_user + %li.pull-right + = link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do + %i.icon-rss diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index b9a2c18efd..effad718c8 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -6,6 +6,9 @@ .issue-title %span.light= "##{issue.iid}" = link_to_gfm truncate(issue.title, length: 100), project_issue_path(issue.project, issue), class: "row_title" + - if issue.closed? + %small.pull-right + = "CLOSED" .issue-info - if issue.assignee diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index 539c45edd9..427d653313 100644 --- a/app/views/projects/issues/_issues.html.haml +++ b/app/views/projects/issues/_issues.html.haml @@ -52,7 +52,7 @@ - @project.team.members.sort_by(&:name).each do |user| %li = link_to project_filter_path(assignee_id: user.id) do - = image_tag gravatar_icon(user.email), class: "avatar s16", alt: '' + = image_tag avatar_icon(user.email), class: "avatar s16", alt: '' = user.name .dropdown.inline.prepend-left-10 diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder index 00ddd4bf70..012ba23595 100644 --- a/app/views/projects/issues/index.atom.builder +++ b/app/views/projects/issues/index.atom.builder @@ -12,7 +12,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.link :href => project_issue_url(@project, issue) xml.title truncate(issue.title, :length => 80) xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(issue.author_email) + xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email) xml.author do |author| xml.name issue.author_name xml.email issue.author_email diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index d36a831432..d08a8af2bb 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -6,9 +6,10 @@ = @issue.created_at.stamp("Aug 21, 2011") %span.pull-right - = link_to new_project_issue_path(@project), class: "btn grouped", title: "New Issue", id: "new_issue_link" do - %i.icon-plus - New Issue + - if can?(current_user, :write_issue, @project) + = link_to new_project_issue_path(@project), class: "btn grouped", title: "New Issue", id: "new_issue_link" do + %i.icon-plus + New Issue - if can?(current_user, :modify_issue, @issue) - if @issue.closed? = link_to 'Reopen', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn grouped reopen_issue" @@ -31,7 +32,7 @@ .ui-box-head %h4.box-title - if @issue.closed? - .error.status_info Closed + .state-label.state-label-red Closed = gfm escape_once(@issue.title) .ui-box-body diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml index 67a1541d9b..839c63986a 100644 --- a/app/views/projects/merge_requests/edit.html.haml +++ b/app/views/projects/merge_requests/edit.html.haml @@ -1,4 +1,4 @@ %h3.page-title - = "Edit merge request ##{@merge_request.id}" + = "Edit merge request ##{@merge_request.iid}" %hr = render 'form' diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 2bd5a027a0..e256ee2153 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -35,7 +35,7 @@ - @project.team.members.sort_by(&:name).each do |user| %li = link_to project_filter_path(assignee_id: user.id) do - = image_tag gravatar_icon(user.email), class: "avatar s16", alt: '' + = image_tag avatar_icon(user.email), class: "avatar s16", alt: '' = user.name .dropdown.inline.prepend-left-10 diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml index 1f750e22c6..93ae602a69 100644 --- a/app/views/projects/merge_requests/show/_mr_box.html.haml +++ b/app/views/projects/merge_requests/show/_mr_box.html.haml @@ -3,11 +3,12 @@ %h4.box-title = gfm escape_once(@merge_request.title) - if @merge_request.merged? - .success.status_info + .state-label.state-label-green %i.icon-ok Merged - elsif @merge_request.closed? - .error.status_info Closed + .state-label.state-label-red + Closed .ui-box-body %div @@ -32,7 +33,7 @@ %span %i.icon-remove Closed by #{link_to_member(@project, @merge_request.closed_event.author)} - %small #{time_ago_in_words(@merge_request.closed_event.created_at)} ago. + %span #{time_ago_in_words(@merge_request.closed_event.created_at)} ago. - if @merge_request.merged? .ui-box-bottom.alert-success %span diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index 78e4cd2243..b2fd1a9284 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -1,4 +1,4 @@ -%h3.page-title= @milestone.new_record? ? "New Milestone" : "Edit Milestone ##{@milestone.id}" +%h3.page-title= @milestone.new_record? ? "New Milestone" : "Edit Milestone ##{@milestone.iid}" .back-link = link_to project_milestones_path(@project) do ← To milestones diff --git a/app/views/projects/milestones/_issues.html.haml b/app/views/projects/milestones/_issues.html.haml index bf81cfda45..21939ad013 100644 --- a/app/views/projects/milestones/_issues.html.haml +++ b/app/views/projects/milestones/_issues.html.haml @@ -8,4 +8,4 @@ = link_to_gfm truncate(issue.title, length: 40), [@project, issue] - if issue.assignee .pull-right - = image_tag gravatar_icon(issue.assignee.email, 16), class: "avatar s16" + = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16" diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml index 7f81589406..8e30a42a60 100644 --- a/app/views/projects/milestones/_merge_request.html.haml +++ b/app/views/projects/milestones/_merge_request.html.haml @@ -1,5 +1,5 @@ %li = link_to [@project, merge_request] do - %span.badge.badge-info ##{merge_request.id} + %span.badge.badge-info ##{merge_request.iid} – = link_to_gfm truncate(merge_request.title, length: 60), [@project, merge_request] diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index b755a81341..a5bd7a2901 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -1,6 +1,6 @@ = render "projects/issues/head" %h3.page-title - Milestone ##{@milestone.id} + Milestone ##{@milestone.iid} %small = @milestone.expires_at .pull-right @@ -26,9 +26,9 @@ .ui-box-head %h4.box-title - if @milestone.closed? - .error.status_info Closed + .state-label.state-label-red Closed - elsif @milestone.expired? - .error.status_info Expired + .state-label.state-label-red Expired = gfm escape_once(@milestone.title) @@ -99,7 +99,7 @@ - @users.each do |user| %li = link_to user, title: user.name, class: "dark" do - = image_tag gravatar_icon(user.email, 32), class: "avatar s32" + = image_tag avatar_icon(user.email, 32), class: "avatar s32" %strong= truncate(user.name, lenght: 40) %br %small.cgray= user.username diff --git a/app/views/projects/network/show.json.erb b/app/views/projects/network/show.json.erb index f0bedcf2d3..dc82adcb2c 100644 --- a/app/views/projects/network/show.json.erb +++ b/app/views/projects/network/show.json.erb @@ -9,7 +9,7 @@ author: { name: c.author_name, email: c.author_email, - icon: gravatar_icon(c.author_email, 20) + icon: avatar_icon(c.author_email, 20) }, time: c.time, space: c.spaces.first, diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 0213576927..980b3783a1 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -51,7 +51,7 @@ = f.label :public do %span Public project .controls - = f.check_box :public, { checked: Gitlab.config.gitlab.default_projects_features.public }, true, false + = f.check_box :public, { checked: gitlab_config.default_projects_features.public }, true, false %span.help-inline Make project visible to everyone .form-actions diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml index a964d86a8d..e0aad0cc95 100644 --- a/app/views/projects/notes/_discussion.html.haml +++ b/app/views/projects/notes/_discussion.html.haml @@ -8,7 +8,7 @@ = link_to "javascript:;", class: "js-details-target turn-off js-toggler-target" do %i.icon-eye-open Show discussion - = image_tag gravatar_icon(note.author_email), class: "avatar s32" + = image_tag avatar_icon(note.author_email), class: "avatar s32" %div = link_to_member(@project, note.author, avatar: false) - if note.for_merge_request? diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index fbc924c4e1..e56e9153c2 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -5,7 +5,7 @@ %i.icon-link Link here   - - if(note.author_id == current_user.id) || can?(current_user, :admin_note, @project) + - if(note.author_id == current_user.try(:id)) || can?(current_user, :admin_note, @project) = link_to "#", title: "Edit comment", class: "js-note-edit" do %i.icon-edit Edit @@ -13,7 +13,7 @@ = link_to project_note_path(@project, note), title: "Remove comment", method: :delete, confirm: 'Are you sure you want to remove this comment?', remote: true, class: "danger js-note-delete" do %i.icon-trash.cred Remove - = image_tag gravatar_icon(note.author_email), class: "avatar s32" + = image_tag avatar_icon(note.author_email), class: "avatar s32" = link_to_member(@project, note.author, avatar: false) %span.note-last-update = note_timestamp(note) diff --git a/app/views/projects/repositories/_feed.html.haml b/app/views/projects/repositories/_feed.html.haml index faa3ed1746..6e537d2959 100644 --- a/app/views/projects/repositories/_feed.html.haml +++ b/app/views/projects/repositories/_feed.html.haml @@ -11,7 +11,7 @@ %div = link_to project_commits_path(@project, commit.id) do %code= commit.short_id - = image_tag gravatar_icon(commit.author_email), class: "", width: 16, alt: '' + = image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: '' = gfm escape_once(truncate(commit.title, length: 40)) %td %span.pull-right.cgray diff --git a/app/views/projects/repositories/stats.html.haml b/app/views/projects/repositories/stats.html.haml index 454296e82f..679b4211cd 100644 --- a/app/views/projects/repositories/stats.html.haml +++ b/app/views/projects/repositories/stats.html.haml @@ -19,7 +19,7 @@ %ol.styled - @stats.authors[0...50].each do |author| %li - = image_tag gravatar_icon(author.email, 16), class: 'avatar s16', alt: '' + = image_tag avatar_icon(author.email, 16), class: 'avatar s16', alt: '' = author.name %small.light= author.email .pull-right diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 06ca5169df..aefcd83395 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -17,7 +17,7 @@ %p %p %span.light Repo size is - #{@project.repository.size} MB + = repository_size %p %span.light Created at #{@project.created_at.stamp('Aug 22, 2013')} diff --git a/app/views/projects/snippets/_form.html.haml b/app/views/projects/snippets/_form.html.haml index e83f5d0d65..d414ee2d1e 100644 --- a/app/views/projects/snippets/_form.html.haml +++ b/app/views/projects/snippets/_form.html.haml @@ -31,10 +31,10 @@ = f.submit 'Create snippet', class: "btn-create btn" - else = f.submit 'Save', class: "btn-save btn" - = link_to "Cancel", project_snippets_path(@project), class: " btn btn-cancel" - - unless @snippet.new_record? - .pull-right= link_to 'Destroy', project_snippet_path(@project, @snippet), confirm: 'Are you sure?', method: :delete, class: "btn pull-right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}" + = link_to "Cancel", project_snippets_path(@project), class: "btn btn-cancel" + - unless @snippet.new_record? + = link_to 'Remove snippet', project_snippet_path(@project, @snippet), confirm: 'Are you sure?', method: :delete, class: "btn pull-right btn-remove delete-snippet prepend-left-10", id: "destroy_snippet_#{@snippet.id}" :javascript var editor = ace.edit("editor"); diff --git a/app/views/projects/snippets/_snippet.html.haml b/app/views/projects/snippets/_snippet.html.haml index fc1c0893b0..6185e35cf6 100644 --- a/app/views/projects/snippets/_snippet.html.haml +++ b/app/views/projects/snippets/_snippet.html.haml @@ -16,6 +16,6 @@ = "##{snippet.id}" %span by - = image_tag gravatar_icon(snippet.author_email), class: "avatar avatar-inline s16" + = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16" = snippet.author_name %span.light #{time_ago_in_words(snippet.created_at)} ago diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml index 4a07ebf7fd..ac32f4866b 100644 --- a/app/views/projects/snippets/show.html.haml +++ b/app/views/projects/snippets/show.html.haml @@ -5,7 +5,7 @@ = "##{@snippet.id}" %span.light by - = image_tag gravatar_icon(@snippet.author_email), class: "avatar avatar-inline s16" + = image_tag avatar_icon(@snippet.author_email), class: "avatar avatar-inline s16" = @snippet.author_name %div= render 'projects/snippets/blob' %div#notes= render "projects/notes/notes_with_form" diff --git a/app/views/projects/team_members/_team_member.html.haml b/app/views/projects/team_members/_team_member.html.haml index 916cf2e7a8..5c93e6e3ea 100644 --- a/app/views/projects/team_members/_team_member.html.haml +++ b/app/views/projects/team_members/_team_member.html.haml @@ -9,7 +9,7 @@   = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do %i.icon-minus.icon-white - = image_tag gravatar_icon(user.email, 32), class: "avatar s32" + = image_tag avatar_icon(user.email, 32), class: "avatar s32" %p %strong= user.name %span.cgray= user.username diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml index ec15b608f8..b179ad7d24 100644 --- a/app/views/projects/tree/_blob_item.html.haml +++ b/app/views/projects/tree/_blob_item.html.haml @@ -1,7 +1,7 @@ %tr{ class: "tree-item #{tree_hex_class(blob_item)}" } %td.tree-item-file-name = tree_icon(type) - %strong= link_to truncate(blob_item.name, length: 40), project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)) + %span= link_to truncate(blob_item.name, length: 40), project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)) %td.tree_time_ago.cgray %span.log_loading.hide Loading commit data... diff --git a/app/views/projects/tree/_submodule_item.html.haml b/app/views/projects/tree/_submodule_item.html.haml index 092a024afb..badc7d992b 100644 --- a/app/views/projects/tree/_submodule_item.html.haml +++ b/app/views/projects/tree/_submodule_item.html.haml @@ -1,11 +1,10 @@ -- url = submodule_item.url(@ref) rescue '' -- name = submodule_item.basename -- return '' unless url -%tr{ class: "tree-item", url: url } +%tr{ class: "tree-item" } %td.tree-item-file-name = image_tag "submodule.png" - %strong= truncate(name, length: 40) + %span + = link_to truncate(submodule_item.name, length: 40), submodule_item.submodule_url + @ + %span.monospace #{submodule_item.id[0..10]} + %td + %td %td - %code= submodule_item.id[0..10] - %td{ colspan: 2 } - = link_to truncate(url, length: 40), url diff --git a/app/views/projects/tree/_tree.html.haml b/app/views/projects/tree/_tree.html.haml index 0b1148b3e7..eadfd33bd3 100644 --- a/app/views/projects/tree/_tree.html.haml +++ b/app/views/projects/tree/_tree.html.haml @@ -26,9 +26,9 @@ = link_to @commit.short_id, project_commit_path(@project, @commit) – = truncate(@commit.title, length: 50) - %th= link_to "history", project_commits_path(@project, @id), class: "btn btn-tiny pull-right" + %th= link_to "history", project_commits_path(@project, @id), class: "pull-right" - - if tree.up_dir? + - if @path.present? %tr.tree-item %td.tree-item-file-name = image_tag "file_empty.png", size: '16x16' diff --git a/app/views/projects/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml index 7ae2582c13..67b5b2b96e 100644 --- a/app/views/projects/tree/_tree_commit_column.html.haml +++ b/app/views/projects/tree/_tree_commit_column.html.haml @@ -1,2 +1,2 @@ -%span.tree_author= commit_author_link(commit, avatar: true) +%span.tree_author= commit_author_link(commit, avatar: true, size: 16) = link_to_gfm truncate(commit.title, length: 80), project_commit_path(@project, commit.id), class: "tree-commit-link" diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml index 0a76d5c21b..f8856afc86 100644 --- a/app/views/projects/tree/_tree_item.html.haml +++ b/app/views/projects/tree/_tree_item.html.haml @@ -1,7 +1,7 @@ %tr{ class: "tree-item #{tree_hex_class(tree_item)}" } %td.tree-item-file-name = tree_icon(type) - %strong= link_to truncate(tree_item.name, length: 40), project_tree_path(@project, tree_join(@id || @commit.id, tree_item.name)) + %span= link_to truncate(tree_item.name, length: 40), project_tree_path(@project, tree_join(@id || @commit.id, tree_item.name)) %td.tree_time_ago.cgray %span.log_loading.hide Loading commit data... diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml index dd01bb9904..57ad4c93ad 100644 --- a/app/views/projects/wikis/git_access.html.haml +++ b/app/views/projects/wikis/git_access.html.haml @@ -11,7 +11,7 @@ .form-horizontal .input-prepend.project_clone_holder %button{class: "btn active", :"data-clone" => @gollum_wiki.ssh_url_to_repo} SSH - %button{class: "btn", :"data-clone" => @gollum_wiki.http_url_to_repo}= Gitlab.config.gitlab.protocol.upcase + %button{class: "btn", :"data-clone" => @gollum_wiki.http_url_to_repo}= gitlab_config.protocol.upcase = text_field_tag :project_clone, @gollum_wiki.url_to_repo, class: "one_click_select input-xxlarge", readonly: true .git-empty %fieldset diff --git a/app/views/public/projects/_tree.html.haml b/app/views/public/projects/_tree.html.haml deleted file mode 100644 index bd09c236a0..0000000000 --- a/app/views/public/projects/_tree.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -- if tree.readme - = render "projects/tree/readme", readme: tree.readme -- else - .alert - %h3.nothing_here_message This project does not have README file diff --git a/app/views/public/projects/index.html.haml b/app/views/public/projects/index.html.haml index 7dbe560e7f..21aee64457 100644 --- a/app/views/public/projects/index.html.haml +++ b/app/views/public/projects/index.html.haml @@ -2,29 +2,40 @@ .span6 %h3.page-title Projects (#{@projects.total_count}) - %small with read-only access + .light + You can browse public projects in read-only mode until signed in. + .span6 .pull-right = form_tag public_projects_path, method: :get, class: 'form-inline' do |f| .search-holder - .controls - = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "span3 search-text-input", id: "projects_search" - = submit_tag 'Search', class: "btn btn-primary wide" - + = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "span3 search-text-input", id: "projects_search" + = submit_tag 'Search', class: "btn btn-primary wide" +%hr .public-projects - %ul.bordered-list + %ul.bordered-list.top-list - @projects.each do |project| %li - .project-title - %i.icon-share.cgray - = link_to public_project_path(project) do - %strong= project.name_with_namespace + %h4 + = link_to project_path(project) do + = project.name_with_namespace .pull-right %pre.public-clone git clone #{project.http_url_to_repo} - if project.description.present? - %div.description + %p = project.description + + .repo-info + - unless project.empty_repo? + = link_to pluralize(project.repository.round_commit_count, 'commit'), project_commits_path(project, project.default_branch) + · + = link_to pluralize(project.repository.branch_names.count, 'branch'), project_branches_path(project) + · + = link_to pluralize(project.repository.tag_names.count, 'tag'), project_tags_path(project) + - else + %i.icon-warning-sign + Empty repository - unless @projects.present? %h3.nothing_here_message No public projects diff --git a/app/views/public/projects/show.html.haml b/app/views/public/projects/show.html.haml deleted file mode 100644 index c4d8a4f5a5..0000000000 --- a/app/views/public/projects/show.html.haml +++ /dev/null @@ -1,46 +0,0 @@ -%h3.page-title - = @project.name_with_namespace - .pull-right - %pre.public-clone git clone #{@project.http_url_to_repo} - .pull-right - - if current_user - = link_to 'Browse project', @project, class: 'btn btn-create append-right-10' - - -%div - = link_to public_root_path do - ← To projects list - .pull-right - %span.light= @project.description - -%br -.row - .span9 - = render 'tree', tree: @tree - .span3 - %h5 Repository: - %div - %p - %span.light Bare size is - #{@project.repository.size} MB - - %p - = pluralize(@repository.round_commit_count, 'commit') - %p - = pluralize(@repository.branch_names.count, 'branch') - %p - = pluralize(@repository.tag_names.count, 'tag') - - - if @recent_tags.present? - %hr - %h5 Most Recent Tags: - %ul.unstyled - - @recent_tags.each do |tag| - %li - %p - %i.icon-tag - %strong= tag.name - %small.light.pull-right - %i.icon-calendar - = time_ago_in_words(tag.commit.committed_date) - ago diff --git a/app/views/shared/_project_filter.html.haml b/app/views/shared/_project_filter.html.haml index 5e0f503c81..f3d032ef98 100644 --- a/app/views/shared/_project_filter.html.haml +++ b/app/views/shared/_project_filter.html.haml @@ -1,15 +1,16 @@ = form_tag project_entities_path, method: 'get' do %fieldset - %ul.nav.nav-pills.nav-stacked - %li{class: ("active" if params[:scope].blank?)} - = link_to project_filter_path(scope: nil) do - Everyone's - %li{class: ("active" if params[:scope] == 'assigned-to-me')} - = link_to project_filter_path(scope: 'assigned-to-me') do - Assigned to me - %li{class: ("active" if params[:scope] == 'created-by-me')} - = link_to project_filter_path(scope: 'created-by-me') do - Created by me + - if current_user + %ul.nav.nav-pills.nav-stacked + %li{class: ("active" if params[:scope].blank?)} + = link_to project_filter_path(scope: nil) do + Everyone's + %li{class: ("active" if params[:scope] == 'assigned-to-me')} + = link_to project_filter_path(scope: 'assigned-to-me') do + Assigned to me + %li{class: ("active" if params[:scope] == 'created-by-me')} + = link_to project_filter_path(scope: 'created-by-me') do + Created by me %ul.nav.nav-pills.nav-stacked %li{class: ("active" if params[:state].blank?)} diff --git a/app/views/snippets/_snippet.html.haml b/app/views/snippets/_snippet.html.haml index 9689c9c4d3..8514bc3ddd 100644 --- a/app/views/snippets/_snippet.html.haml +++ b/app/views/snippets/_snippet.html.haml @@ -18,6 +18,6 @@ %span by = link_to user_snippets_path(snippet.author) do - = image_tag gravatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: '' + = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: '' = snippet.author_name %span.light #{time_ago_in_words(snippet.created_at)} ago diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index 37f9e7576f..a680e5eb5b 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -17,7 +17,7 @@ %span.light by = link_to user_snippets_path(@snippet.author) do - = image_tag gravatar_icon(@snippet.author_email), class: "avatar avatar-inline s16" + = image_tag avatar_icon(@snippet.author_email), class: "avatar avatar-inline s16" = @snippet.author_name .back-link diff --git a/app/views/snippets/user_index.html.haml b/app/views/snippets/user_index.html.haml index 49636c3f5f..1cb53ec6a2 100644 --- a/app/views/snippets/user_index.html.haml +++ b/app/views/snippets/user_index.html.haml @@ -1,5 +1,5 @@ %h3.page-title - = image_tag gravatar_icon(@user.email), class: "avatar s24" + = image_tag avatar_icon(@user.email), class: "avatar s24" = @user.name %span \/ diff --git a/app/views/users/_projects.html.haml b/app/views/users/_projects.html.haml index f1b2c8dd7f..a61c6ba5b8 100644 --- a/app/views/users/_projects.html.haml +++ b/app/views/users/_projects.html.haml @@ -3,9 +3,4 @@ %ul.well-list - @projects.each do |project| %li - = link_to project_path(project), class: dom_class(project) do - - if project.namespace - = project.namespace.human_name - \/ - %strong.well-title - = truncate(project.name, length: 45) + = link_to_project project diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 743ab0949a..53a0a9232a 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -1,13 +1,13 @@ .row .span8 %h3.page-title - = image_tag gravatar_icon(@user.email, 90), class: "avatar s90", alt: '' + = image_tag avatar_icon(@user.email, 90), class: "avatar s90", alt: '' = @user.name - if @user == current_user .pull-right = link_to profile_path, class: 'btn' do %i.icon-edit - Edit Profile + Edit Profile settings %br %small #{@user.username} %br diff --git a/app/views/users_groups/_users_group.html.haml b/app/views/users_groups/_users_group.html.haml index c8d306838f..5934ff72e8 100644 --- a/app/views/users_groups/_users_group.html.haml +++ b/app/views/users_groups/_users_group.html.haml @@ -1,7 +1,7 @@ - user = member.user - return unless user %li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)} - = image_tag gravatar_icon(user.email, 16), class: "avatar s16" + = image_tag avatar_icon(user.email, 16), class: "avatar s16" %strong= user.name %span.cgray= user.username - if user == current_user @@ -10,7 +10,7 @@ %span.pull-right %strong= member.human_access - - if show_controls && user != @group.owner && user != current_user + - if show_controls && can?(current_user, :manage_group, @group) && current_user != user = link_to '#', class: "btn-tiny btn js-toggle-button", title: 'Edit access level' do %i.icon-edit = link_to group_users_group_path(@group, member), confirm: remove_user_from_group_message(@group, user), method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do @@ -20,4 +20,4 @@ = form_for [@group, member], remote: true do |f| .alert.prepend-top-20 = f.select :group_access, options_for_select(UsersGroup.group_access_roles, member.group_access) - = f.submit 'Save', class: 'btn btn-save' + = f.submit 'Save', class: 'btn btn-save btn-small' diff --git a/config/application.rb b/config/application.rb index 8ac07ef337..d85bcab788 100644 --- a/config/application.rb +++ b/config/application.rb @@ -70,12 +70,15 @@ module Gitlab config.assets.version = '1.0' # Uncomment and customize the last line to run in a non-root path - # WARNING: This feature is no longer supported + # WARNING: This feature is known to work, but unsupported # Note that three settings need to be changed for this to work. # 1) In your application.rb file: config.relative_url_root = "/gitlab" # 2) In your gitlab.yml file: relative_url_root: /gitlab - # 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] + # 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab" # # config.relative_url_root = "/gitlab" + + # Uncomment to enable rack attack middleware + # config.middleware.use Rack::Attack end end diff --git a/config/database.yml.mysql b/config/database.yml.mysql index a3eff1a74f..e7a9227e41 100644 --- a/config/database.yml.mysql +++ b/config/database.yml.mysql @@ -7,7 +7,7 @@ production: reconnect: false database: gitlabhq_production pool: 10 - username: root + username: gitlab password: "secure password" # host: localhost # socket: /tmp/mysql.sock diff --git a/config/database.yml.postgresql b/config/database.yml.postgresql index 4b74f3348f..66960551cf 100644 --- a/config/database.yml.postgresql +++ b/config/database.yml.postgresql @@ -6,8 +6,8 @@ production: encoding: unicode database: gitlabhq_production pool: 10 - username: git - password: + # username: git + # password: # host: localhost # port: 5432 # socket: /tmp/postgresql.sock diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index bc1ac5319a..5dbef84d1c 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -20,11 +20,11 @@ production: &base https: false # Uncomment and customize the last line to run in a non-root path - # WARNING: This feature is no longer supported + # WARNING: This feature is known to work, but unsupported # Note that three settings need to be changed for this to work. # 1) In your application.rb file: config.relative_url_root = "/gitlab" # 2) In your gitlab.yml file: relative_url_root: /gitlab - # 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] + # 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab" # # relative_url_root: /gitlab @@ -42,14 +42,24 @@ production: &base default_projects_limit: 10 # default_can_create_group: false # default: true # username_changing_enabled: false # default: true - User can change her username/namespace + ## Default theme + ## BASIC = 1 + ## MARS = 2 + ## MODERN = 3 + ## GRAY = 4 + ## COLOR = 5 + # default_theme: 2 # default: 2 + ## Users management - # signup_enabled: true # default: false - Account passwords are not sent via the email if signup is enabled. + # default: false - Account passwords are not sent via the email if signup is enabled. + # signup_enabled: true ## Automatic issue closing - # If a commit message matches this regular express, all issues referenced from the matched text will be closed - # if it's pushed to a project's default branch. - # issue_closing_pattern: ^([Cc]loses|[Ff]ixes) +#\d+ + # If a commit message matches this regular expression, all issues referenced from the matched text will be closed. + # This happends when the commit is pushed or merged into the default branch of a project. + # When not specified the default issue_closing_pattern as specified below will be used. + # issue_closing_pattern: ([Cc]loses|[Ff]ixes) +#\d+ ## Default project features settings default_projects_features: @@ -63,6 +73,7 @@ production: &base ## External issues trackers issues_tracker: # redmine: + # title: "Redmine" # ## If not nil, link 'Issues' on project page will be replaced with this # ## Use placeholders: # ## :project_id - GitLab project identifier @@ -83,6 +94,7 @@ production: &base # new_issue_url: "http://redmine.sample/projects/:issues_tracker_id/issues/new" # # jira: + # title: "Atlassian Jira" # project_url: "http://jira.sample/issues/?jql=project=:issues_tracker_id" # issues_url: "http://jira.sample/browse/:id" # new_issue_url: "http://jira.sample/secure/CreateIssue.jspa" @@ -215,6 +227,7 @@ test: <<: *base issues_tracker: redmine: + title: "Redmine" project_url: "http://redmine/projects/:issues_tracker_id" issues_url: "http://redmine/:project_id/:issues_tracker_id/:id" new_issue_url: "http://redmine/projects/:issues_tracker_id/issues/new" diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index da7a83a62a..942b77ffd2 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -52,6 +52,7 @@ Settings['issues_tracker'] ||= {} Settings['gitlab'] ||= Settingslogic.new({}) Settings.gitlab['default_projects_limit'] ||= 10 Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil? +Settings.gitlab['default_theme'] = Gitlab::Theme::MARS if Settings.gitlab['default_theme'].nil? Settings.gitlab['host'] ||= 'localhost' Settings.gitlab['https'] = false if Settings.gitlab['https'].nil? Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80 @@ -68,7 +69,7 @@ rescue ArgumentError # no user configured end Settings.gitlab['signup_enabled'] ||= false Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil? -Settings.gitlab['issue_closing_pattern'] = '^([Cc]loses|[Ff]ixes) #(\d+)' if Settings.gitlab['issue_closing_pattern'].nil? +Settings.gitlab['issue_closing_pattern'] = '([Cc]loses|[Ff]ixes) #(\d+)' if Settings.gitlab['issue_closing_pattern'].nil? Settings.gitlab['default_projects_features'] ||= {} Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil? Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.gitlab.default_projects_features['merge_requests'].nil? diff --git a/config/initializers/5_backend.rb b/config/initializers/5_backend.rb index e60d9559c9..7c2e7f3900 100644 --- a/config/initializers/5_backend.rb +++ b/config/initializers/5_backend.rb @@ -6,6 +6,3 @@ require Rails.root.join("lib", "gitlab", "backend", "shell") # GitLab shell adapter require Rails.root.join("lib", "gitlab", "backend", "shell_adapter") - -# Gitlab Git repos path -Gitlab::Git::Repository.repos_path = Gitlab.config.gitlab_shell.repos_path diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 39c1b7c235..b7cb808d2e 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -54,6 +54,8 @@ Devise.setup do |config| # The realm used in Http Basic Authentication. "Application" by default. # config.http_authentication_realm = "Application" + config.reconfirmable = true + # It will change confirmation, password recovery and other workflows # to behave the same regardless if the e-mail provided was right or wrong. # Does not affect registerable. diff --git a/config/initializers/devise_async.rb b/config/initializers/devise_async.rb new file mode 100644 index 0000000000..05a1852cdb --- /dev/null +++ b/config/initializers/devise_async.rb @@ -0,0 +1 @@ +Devise::Async.backend = :sidekiq diff --git a/config/initializers/rack_attack.rb.example b/config/initializers/rack_attack.rb.example new file mode 100644 index 0000000000..76fa7ad282 --- /dev/null +++ b/config/initializers/rack_attack.rb.example @@ -0,0 +1,16 @@ +# To enable rack-attack for your GitLab instance do the following: +# 1. In config/application.rb find and uncomment the following line: +# config.middleware.use Rack::Attack +# 2. Rename this file to rack_attack.rb +# 3. Review the paths_to_be_protected and add any other path you need protecting +# 4. Restart GitLab instance +# + +paths_to_be_protected = [ + "#{Rails.application.config.relative_url_root}/users/password", + "#{Rails.application.config.relative_url_root}/users/sign_in", + "#{Rails.application.config.relative_url_root}/users" +] +Rack::Attack.throttle('protected paths', limit: 6, period: 60.seconds) do |req| + req.ip if paths_to_be_protected.include?(req.path) && req.post? +end diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index 52a099c3e1..501cad4a83 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -1,11 +1,9 @@ # Be sure to restart your server when you modify this file. -Gitlab::Application.config.session_store :cookie_store, key: '_gitlab_session', - secure: Gitlab::Application.config.force_ssl, - httponly: true, - path: (Rails.application.config.relative_url_root.nil?) ? '/' : Rails.application.config.relative_url_root - -# Use the database for sessions instead of the cookie-based default, -# which shouldn't be used to store highly confidential information -# (create the session table with "rails generate session_migration") -# Gitlab::Application.config.session_store :active_record_store +Gitlab::Application.config.session_store( + :redis_store, # Using the cookie_store would enable session replay attacks. + key: '_gitlab_session', + secure: Gitlab::Application.config.force_ssl, + httponly: true, + path: (Rails.application.config.relative_url_root.nil?) ? '/' : Rails.application.config.relative_url_root +) diff --git a/config/initializers/smtp_settings.rb.sample b/config/initializers/smtp_settings.rb.sample index e62ad0f4b7..9ebb1dde6f 100644 --- a/config/initializers/smtp_settings.rb.sample +++ b/config/initializers/smtp_settings.rb.sample @@ -1,11 +1,11 @@ # To enable smtp email delivery for your GitLab instance do next: -# 1. Change config/environments/production.rb to use smtp -# config.action_mailer.delivery_method = :smtp -# 2. Rename this file to smtp_settings.rb -# 3. Edit settings inside this file -# 4. Restart GitLab instance +# 1. Rename this file to smtp_settings.rb +# 2. Edit settings inside this file +# 3. Restart GitLab instance # -if Gitlab::Application.config.action_mailer.delivery_method == :smtp +if Rails.env.production? do + Gitlab::Application.config.action_mailer.delivery_method = :smtp + ActionMailer::Base.smtp_settings = { address: "email.server.com", port: 456, diff --git a/config/routes.rb b/config/routes.rb index 37dc26d36c..3a4b1c12b6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -39,6 +39,7 @@ Gitlab::Application.routes.draw do get 'help/web_hooks' => 'help#web_hooks' get 'help/workflow' => 'help#workflow' get 'help/shortcuts' + get 'help/security' # # Global snippets @@ -55,8 +56,6 @@ Gitlab::Application.routes.draw do # namespace :public do resources :projects, only: [:index] - resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, only: [:show] - root to: "projects#index" end @@ -100,19 +99,21 @@ Gitlab::Application.routes.draw do # resource :profile, only: [:show, :update] do member do - get :account get :history - get :token get :design - put :update_password put :reset_private_token put :update_username end scope module: :profiles do + resource :account, only: [:show, :update] resource :notifications, only: [:show, :update] - resource :password, only: [:new, :create] + resource :password, only: [:new, :create, :edit, :update] do + member do + put :reset + end + end resources :keys resources :groups, only: [:index] do member do diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example index 5c933df073..e4e1342683 100644 --- a/config/unicorn.rb.example +++ b/config/unicorn.rb.example @@ -8,6 +8,15 @@ # See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete # documentation. +# Uncomment and customize the last line to run in a non-root path +# WARNING: This feature is known to work, but unsupported +# Note that three settings need to be changed for this to work. +# 1) In your application.rb file: config.relative_url_root = "/gitlab" +# 2) In your gitlab.yml file: relative_url_root: /gitlab +# 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab" +# +# ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab" + # Use at least one worker per core if you're on a dedicated server, # more will usually help for _short_ waits on databases/caches. worker_processes 2 diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb index ecea821139..3e76d76e83 100644 --- a/db/fixtures/development/01_admin.rb +++ b/db/fixtures/development/01_admin.rb @@ -11,3 +11,5 @@ User.seed(:id, [ theme_id: Gitlab::Theme::MARS } ]) + +User.find(1).confirm! diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb index 43178dee25..6fdb163c27 100644 --- a/db/fixtures/development/04_project.rb +++ b/db/fixtures/development/04_project.rb @@ -26,6 +26,7 @@ project_urls.each_with_index do |url, i| name: group_path.titleize, path: group_path ) + group.description = Faker::Lorem.sentence group.owner = User.first group.save end @@ -35,7 +36,8 @@ project_urls.each_with_index do |url, i| params = { import_url: url, namespace_id: group.id, - name: project_path.titleize + name: project_path.titleize, + description: Faker::Lorem.sentence } project = Projects::CreateContext.new(User.first, params).execute diff --git a/db/fixtures/development/09_issues.rb b/db/fixtures/development/09_issues.rb index 31ba77254a..85e309b633 100644 --- a/db/fixtures/development/09_issues.rb +++ b/db/fixtures/development/09_issues.rb @@ -11,17 +11,23 @@ Gitlab::Seeder.quiet do next unless user user_id = user.id - Thread.current[:current_user] = user - Issue.seed(:id, [{ - id: i, - project_id: project.id, - author_id: user_id, - assignee_id: user_id, - state: ['opened', 'closed'].sample, - milestone: project.milestones.sample, - title: Faker::Lorem.sentence(6) - }]) + begin + Thread.current[:current_user] = user + + Issue.seed(:id, [{ + id: i, + project_id: project.id, + author_id: user_id, + assignee_id: user_id, + state: ['opened', 'closed'].sample, + milestone: project.milestones.sample, + title: Faker::Lorem.sentence(6), + description: Faker::Lorem.sentence + }]) + ensure + Thread.current[:current_user] = nil + end print('.') end diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb index 1e61ea2863..75f6e56985 100644 --- a/db/fixtures/development/10_merge_requests.rb +++ b/db/fixtures/development/10_merge_requests.rb @@ -17,19 +17,23 @@ Gitlab::Seeder.quiet do next if branches.uniq.size < 2 user_id = user.id - Thread.current[:current_user] = user + begin + Thread.current[:current_user] = user - MergeRequest.seed(:id, [{ - id: i, - source_branch: branches.first, - target_branch: branches.last, - source_project_id: project.id, - target_project_id: project.id, - author_id: user_id, - assignee_id: user_id, - milestone: project.milestones.sample, - title: Faker::Lorem.sentence(6) - }]) + MergeRequest.seed(:id, [{ + id: i, + source_branch: branches.first, + target_branch: branches.last, + source_project_id: project.id, + target_project_id: project.id, + author_id: user_id, + assignee_id: user_id, + milestone: project.milestones.sample, + title: Faker::Lorem.sentence(6) + }]) + ensure + Thread.current[:current_user] = nil + end print('.') end end diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb index 1b77d94905..a919fad704 100644 --- a/db/fixtures/production/001_admin.rb +++ b/db/fixtures/production/001_admin.rb @@ -12,6 +12,7 @@ admin = User.create( admin.projects_limit = 10000 admin.admin = true admin.save! +admin.confirm! if admin.valid? puts %q[ diff --git a/db/migrate/20130926081215_change_owner_id_for_group.rb b/db/migrate/20130926081215_change_owner_id_for_group.rb new file mode 100644 index 0000000000..8f1992c37a --- /dev/null +++ b/db/migrate/20130926081215_change_owner_id_for_group.rb @@ -0,0 +1,9 @@ +class ChangeOwnerIdForGroup < ActiveRecord::Migration + def up + change_column :namespaces, :owner_id, :integer, null: true + end + + def down + change_column :namespaces, :owner_id, :integer, null: false + end +end diff --git a/db/migrate/20131005191208_add_avatar_to_users.rb b/db/migrate/20131005191208_add_avatar_to_users.rb new file mode 100644 index 0000000000..7b4de37ad7 --- /dev/null +++ b/db/migrate/20131005191208_add_avatar_to_users.rb @@ -0,0 +1,5 @@ +class AddAvatarToUsers < ActiveRecord::Migration + def change + add_column :users, :avatar, :string + end +end diff --git a/db/migrate/20131009115346_add_confirmable_to_users.rb b/db/migrate/20131009115346_add_confirmable_to_users.rb new file mode 100644 index 0000000000..249cbe704e --- /dev/null +++ b/db/migrate/20131009115346_add_confirmable_to_users.rb @@ -0,0 +1,15 @@ +class AddConfirmableToUsers < ActiveRecord::Migration + def self.up + add_column :users, :confirmation_token, :string + add_column :users, :confirmed_at, :datetime + add_column :users, :confirmation_sent_at, :datetime + add_column :users, :unconfirmed_email, :string + add_index :users, :confirmation_token, unique: true + User.update_all(confirmed_at: Time.now) + end + + def self.down + remove_column :users, :confirmation_token, :confirmed_at, :confirmation_sent_at + remove_column :users, :unconfirmed_email + end +end diff --git a/db/schema.rb b/db/schema.rb index 71e8432cca..843a58efae 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130909132950) do +ActiveRecord::Schema.define(:version => 20131009115346) do create_table "deploy_keys_projects", :force => true do |t| t.integer "deploy_key_id", :null => false @@ -129,7 +129,7 @@ ActiveRecord::Schema.define(:version => 20130909132950) do create_table "namespaces", :force => true do |t| t.string "name", :null => false t.string "path", :null => false - t.integer "owner_id", :null => false + t.integer "owner_id" t.datetime "created_at", :null => false t.datetime "updated_at", :null => false t.string "type" @@ -291,10 +291,16 @@ ActiveRecord::Schema.define(:version => 20130909132950) do t.datetime "password_expires_at" t.integer "created_by_id" t.datetime "last_credential_check_at" + t.string "avatar" + t.string "confirmation_token" + t.datetime "confirmed_at" + t.datetime "confirmation_sent_at" + t.string "unconfirmed_email" end add_index "users", ["admin"], :name => "index_users_on_admin" add_index "users", ["authentication_token"], :name => "index_users_on_authentication_token", :unique => true + add_index "users", ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true add_index "users", ["email"], :name => "index_users_on_email", :unique => true add_index "users", ["name"], :name => "index_users_on_name" add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true diff --git a/doc/api/README.md b/doc/api/README.md index 6971e08f01..517a9fae6f 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -96,13 +96,30 @@ curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" --header "SUDO: username" "h curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" --header "SUDO: 23" "http://example.com/api/v3/projects" ``` -#### Pagination +## Pagination When listing resources you can pass the following parameters: + `page` (default: `1`) - page number + `per_page` (default: `20`, max: `100`) - number of items to list per page +## id vs iid + +When you work with API you may notice two similar fields in api entites: id and iid. +The main difference between them is scope. Example: + +Issue + id: 46 + iid: 5 + +* id - is uniq across all Issues table. It used for any api calls. +* iid - is uniq only in scope of single project. When you browse issues or merge requests with Web UI - you see iid. + +So if you want to get issue with api you use `http://host/api/v3/.../issues/:id.json` +But when you want to create a link to web page - use `http:://host/project/issues/:iid.json` + + + ## Contents + [Users](users.md) @@ -117,7 +134,7 @@ When listing resources you can pass the following parameters: + [Deploy Keys](deploy_keys.md) + [System Hooks](system_hooks.md) + [Groups](groups.md) -+ [User Teams](user_teams.md) + ## Clients diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md index fbb1e45bcc..50d2b82c36 100644 --- a/doc/api/deploy_keys.md +++ b/doc/api/deploy_keys.md @@ -20,13 +20,15 @@ Parameters: "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4 soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "created_at":"2013-10-02T10:12:29Z" }, { "id": 3, "title" : "Another Public key", "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4 - soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" + soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "created_at":"2013-10-02T11:12:29Z" } ] ``` @@ -51,7 +53,8 @@ Parameters: "title" : "Public key", "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4 - soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" + soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "created_at":"2013-10-02T10:12:29Z" } ``` diff --git a/doc/api/groups.md b/doc/api/groups.md index cf5a961a88..e3a7d11a6e 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -12,7 +12,6 @@ GET /groups "id": 1, "name": "Foobar Group", "path": "foo-bar", - "owner_id": 18, "ldap_cn": "ldap-group-name", "ldap_access": 30, } @@ -58,11 +57,37 @@ POST /groups/:id/projects/:project_id Parameters: + `id` (required) - The ID of a group -+ `project_id (required) - The ID of a project ++ `project_id` (required) - The ID of a project + + +## Remove group + +Removes group with all projects inside. + +``` +DELETE /groups/:id +``` + +Parameters: + ++ `id` (required) - The ID of a user group ## Group members + +**Group access levels** + +The group access levels are defined in the `Gitlab::Access` module. Currently, these levels are recognized: + +``` + GUEST = 10 + REPORTER = 20 + DEVELOPER = 30 + MASTER = 40 + OWNER = 50 +``` + ### List group members Get a list of group members viewable by the authenticated user. @@ -74,22 +99,22 @@ GET /groups/:id/members ```json [ { - id: 1, - username: "raymond_smith", - email: "ray@smith.org", - name: "Raymond Smith", - state: "active", - created_at: "2012-10-22T14:13:35Z", - access_level: 30 + "id": 1, + "username": "raymond_smith", + "email": "ray@smith.org", + "name": "Raymond Smith", + "state": "active", + "created_at": "2012-10-22T14:13:35Z", + "access_level": 30 }, { - id: 2, - username: "john_doe", - email: "joh@doe.org", - name: "John Doe", - state: "active", - created_at: "2012-10-22T14:13:35Z", - access_level: 30 + "id": 2, + "username": "john_doe", + "email": "joh@doe.org", + "name": "John Doe", + "state": "active", + "created_at": "2012-10-22T14:13:35Z", + "access_level": 30 } ] ``` diff --git a/doc/api/issues.md b/doc/api/issues.md index 723c8acf38..ef37940faf 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -11,6 +11,7 @@ GET /issues [ { "id": 43, + "iid": 3, "project_id": 8, "title": "4xx/5xx pages", "description": "", @@ -22,15 +23,16 @@ GET /issues "username": "john_smith", "email": "john@example.com", "name": "John Smith", - "blocked": false, + "state": "active", "created_at": "2012-05-23T08:00:58Z" }, - "state": 'closed', + "state": "closed", "updated_at": "2012-07-02T17:53:12Z", "created_at": "2012-07-02T17:53:12Z" }, { "id": 42, + "iid": 4, "project_id": 8, "title": "Add user settings", "description": "", @@ -42,7 +44,7 @@ GET /issues "title": "v1.0", "description": "", "due_date": "2012-07-20", - "state": 'reopenend', + "state": "reopenend", "updated_at": "2012-07-04T13:42:48Z", "created_at": "2012-07-04T13:42:48Z" }, @@ -51,7 +53,7 @@ GET /issues "username": "jack_smith", "email": "jack@example.com", "name": "Jack Smith", - "blocked": false, + "state": "active", "created_at": "2012-05-23T08:01:01Z" }, "author": { @@ -59,10 +61,10 @@ GET /issues "username": "john_smith", "email": "john@example.com", "name": "John Smith", - "blocked": false, + "state": "active", "created_at": "2012-05-23T08:00:58Z" }, - "state": 'opened', + "state": "opened", "updated_at": "2012-07-12T13:43:19Z", "created_at": "2012-06-28T12:58:06Z" } @@ -100,6 +102,7 @@ Parameters: ```json { "id": 42, + "iid": 3, "project_id": 8, "title": "Add user settings", "description": "", @@ -111,7 +114,7 @@ Parameters: "title": "v1.0", "description": "", "due_date": "2012-07-20", - "state": 'closed', + "state": "closed", "updated_at": "2012-07-04T13:42:48Z", "created_at": "2012-07-04T13:42:48Z" }, @@ -120,7 +123,7 @@ Parameters: "username": "jack_smith", "email": "jack@example.com", "name": "Jack Smith", - "blocked": false, + "state": "active", "created_at": "2012-05-23T08:01:01Z" }, "author": { @@ -128,10 +131,10 @@ Parameters: "username": "john_smith", "email": "john@example.com", "name": "John Smith", - "blocked": false, + "state": "active", "created_at": "2012-05-23T08:00:58Z" }, - "state": 'opened', + "state": "opened", "updated_at": "2012-07-12T13:43:19Z", "created_at": "2012-06-28T12:58:06Z" } @@ -190,4 +193,3 @@ Parameters: + `id` (required) - The project ID + `issue_id` (required) - The ID of the issue - diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 111c52112e..2ddaea5a58 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -15,18 +15,20 @@ Parameters: [ { "id":1, + "iid":1, "target_branch":"master", "source_branch":"test1", "project_id":3, "title":"test1", - "closed":true, - "merged":false, + "state":"opened", + "upvotes":0, + "downvotes":0, "author":{ "id":1, "username": "admin", "email":"admin@local.host", "name":"Administrator", - "blocked":false, + "state":"active", "created_at":"2012-04-29T08:46:00Z" }, "assignee":{ @@ -34,7 +36,7 @@ Parameters: "username": "admin", "email":"admin@local.host", "name":"Administrator", - "blocked":false, + "state":"active", "created_at":"2012-04-29T08:46:00Z" } } @@ -58,18 +60,20 @@ Parameters: ```json { "id":1, + "iid":1, "target_branch":"master", "source_branch":"test1", "project_id":3, "title":"test1", - "closed":true, - "merged":false, + "state":"merged", + "upvotes":0, + "downvotes":0, "author":{ "id":1, "username": "admin", "email":"admin@local.host", "name":"Administrator", - "blocked":false, + "state":"active", "created_at":"2012-04-29T08:46:00Z" }, "assignee":{ @@ -77,7 +81,7 @@ Parameters: "username": "admin", "email":"admin@local.host", "name":"Administrator", - "blocked":false, + "state":"active", "created_at":"2012-04-29T08:46:00Z" } } @@ -97,7 +101,7 @@ Parameters: + `id` (required) - The ID of a project + `source_branch` (required) - The source branch + `target_branch` (required) - The target branch -+ `assignee_id` - Assignee user ID ++ `assignee_id` (optional) - Assignee user ID + `title` (required) - Title of MR ```json @@ -107,14 +111,15 @@ Parameters: "source_branch":"test1", "project_id":3, "title":"test1", - "closed":true, - "merged":false, + "state":"opened", + "upvotes":0, + "downvotes":0, "author":{ "id":1, "username": "admin", "email":"admin@local.host", "name":"Administrator", - "blocked":false, + "state":"active", "created_at":"2012-04-29T08:46:00Z" }, "assignee":{ @@ -122,7 +127,7 @@ Parameters: "username": "admin", "email":"admin@local.host", "name":"Administrator", - "blocked":false, + "state":"active", "created_at":"2012-04-29T08:46:00Z" } } @@ -145,24 +150,24 @@ Parameters: + `target_branch` - The target branch + `assignee_id` - Assignee user ID + `title` - Title of MR -+ `closed` - Status of MR. true - closed - ```json + { "id":1, "target_branch":"master", "source_branch":"test1", "project_id":3, "title":"test1", - "closed":true, - "merged":false, + "state":"opened", + "upvotes":0, + "downvotes":0, "author":{ "id":1, "username": "admin", "email":"admin@local.host", "name":"Administrator", - "blocked":false, + "state":"active", "created_at":"2012-04-29T08:46:00Z" }, "assignee":{ @@ -170,7 +175,7 @@ Parameters: "username": "admin", "email":"admin@local.host", "name":"Administrator", - "blocked":false, + "state":"active", "created_at":"2012-04-29T08:46:00Z" } } diff --git a/doc/api/milestones.md b/doc/api/milestones.md index aa8f1bf5e0..e899e28d21 100644 --- a/doc/api/milestones.md +++ b/doc/api/milestones.md @@ -6,6 +6,22 @@ Returns a list of project milestones. GET /projects/:id/milestones ``` +```json +[ + { + "id":12, + "iid":3, + "project_id":16, + "title":"10.0", + "description":"Version", + "due_date":"2013-11-29", + "state":"active", + "updated_at":"2013-10-02T09:24:18Z", + "created_at":"2013-10-02T09:24:18Z" + } +] +``` + Parameters: + `id` (required) - The ID of a project diff --git a/doc/api/notes.md b/doc/api/notes.md index 4b57f636a0..397aa87aad 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -8,17 +8,22 @@ Get a list of project wall notes. GET /projects/:id/notes ``` +Parameters: + ++ `id` (required) - The ID of a project + ```json [ { "id": 522, "body": "The solution is rather tricky", + "attachment":null, "author": { "id": 1, "username": "john_smith", "email": "john@example.com", "name": "John Smith", - "blocked": false, + "state": "active", "created_at": "2012-05-23T08:00:58Z" }, "created_at": "2012-11-27T19:16:44Z" @@ -26,11 +31,6 @@ GET /projects/:id/notes ] ``` -Parameters: - -+ `id` (required) - The ID of a project - - ### Get single wall note Returns a single wall note. @@ -74,6 +74,38 @@ Parameters: + `id` (required) - The ID of a project + `issue_id` (required) - The ID of an issue +```json +[ + { + "id":302, + "body":"_Status changed to closed_", + "attachment":null, + "author":{ + "id":1, + "username":"pipin", + "email":"admin@example.com", + "name":"Pip", + "state":"active", + "created_at":"2013-09-30T13:46:01Z" + }, + "created_at":"2013-10-02T09:22:45Z" + }, + { + "id":305, + "body":"Text of the comment\r\n", + "attachment":null, + "author":{ + "id":1, + "username":"pipin", + "email":"admin@example.com", + "name":"Pip", + "state":"active", + "created_at":"2013-09-30T13:46:01Z" + }, + "created_at":"2013-10-02T09:56:03Z" + } +] +``` ### Get single issue note @@ -135,6 +167,24 @@ Parameters: + `snippet_id` (required) - The ID of a project snippet + `note_id` (required) - The ID of an snippet note +```json +{ + "id":52, + "title":"Snippet", + "file_name":"snippet.rb", + "author":{ + "id":1, + "username":"pipin", + "email":"admin@example.com", + "name":"Pip", + "state":"active", + "created_at":"2013-09-30T13:46:01Z" + }, + "expires_at":null, + "updated_at":"2013-10-02T07:34:20Z", + "created_at":"2013-10-02T07:34:20Z" +} +``` ### Create new snippet note @@ -181,6 +231,22 @@ Parameters: + `merge_request_id` (required) - The ID of a project merge request + `note_id` (required) - The ID of a merge request note +```json +{ + "id":301, + "body":"Comment for MR", + "attachment":null, + "author":{ + "id":1, + "username":"pipin", + "email":"admin@example.com", + "name":"Pip", + "state":"active", + "created_at":"2013-09-30T13:46:01Z" + }, + "created_at":"2013-10-02T08:57:14Z" +} +``` ### Create new merge request note diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md index 04ea367d51..f7b7fc8fbb 100644 --- a/doc/api/project_snippets.md +++ b/doc/api/project_snippets.md @@ -34,7 +34,7 @@ Parameters: "username": "john_smith", "email": "john@example.com", "name": "John Smith", - "blocked": false, + "state": "active", "created_at": "2012-05-23T08:00:58Z" }, "expires_at": null, diff --git a/doc/api/projects.md b/doc/api/projects.md index 9fbc9a3eb3..90dd39231b 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -11,51 +11,72 @@ GET /projects ```json [ { - "id": 3, - "name": "rails", + "id": 4, "description": null, "default_branch": "master", + "public": false, + "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git", + "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git", + "web_url": "http://example.com/diaspora/diaspora-client", "owner": { - "id": 1, - "username": "john_smith", - "email": "john@example.com", - "name": "John Smith", - "blocked": false, - "created_at": "2012-05-23T08:00:58Z" + "id": 3, + "name": "Diaspora", + "created_at": "2013-09-30T13: 46: 02Z" }, - "public": true, - "path": "rails", - "path_with_namespace": "rails/rails", - "issues_enabled": false, - "merge_requests_enabled": false, - "wall_enabled": true, - "wiki_enabled": true, - "created_at": "2012-05-23T08:05:02Z", - "last_activity_at": "2012-05-23T08:05:02Z" - }, - { - "id": 5, - "name": "gitlab", - "description": null, - "default_branch": "api", - "owner": { - "id": 1, - "username": "john_smith", - "email": "john@example.com", - "name": "John Smith", - "blocked": false, - "created_at": "2012-05-23T08:00:58Z" - }, - "public": true, - "path": "gitlab", - "path_with_namespace": "randx/gitlab", + "name": "Diaspora Client", + "name_with_namespace": "Diaspora / Diaspora Client", + "path": "diaspora-client", + "path_with_namespace": "diaspora/diaspora-client", "issues_enabled": true, "merge_requests_enabled": true, - "wall_enabled": true, + "wall_enabled": false, "wiki_enabled": true, - "snippets_enabled": true, - "created_at": "2012-05-30T12:49:20Z", - "last_activity_at": "2012-05-23T08:05:02Z" + "snippets_enabled": false, + "created_at": "2013-09-30T13: 46: 02Z", + "last_activity_at": "2013-09-30T13: 46: 02Z", + "namespace": { + "created_at": "2013-09-30T13: 46: 02Z", + "description": "", + "id": 3, + "name": "Diaspora", + "owner_id": 1, + "path": "diaspora", + "updated_at": "2013-09-30T13: 46: 02Z" + } + }, + { + "id": 6, + "description": null, + "default_branch": "master", + "public": false, + "ssh_url_to_repo": "git@example.com:brightbox/puppet.git", + "http_url_to_repo": "http://example.com/brightbox/puppet.git", + "web_url": "http://example.com/brightbox/puppet", + "owner": { + "id": 4, + "name": "Brightbox", + "created_at": "2013-09-30T13:46:02Z" + }, + "name": "Puppet", + "name_with_namespace": "Brightbox / Puppet", + "path": "puppet", + "path_with_namespace": "brightbox/puppet", + "issues_enabled": true, + "merge_requests_enabled": true, + "wall_enabled": false, + "wiki_enabled": true, + "snippets_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", + "namespace": { + "created_at": "2013-09-30T13:46:02Z", + "description": "", + "id": 4, + "name": "Brightbox", + "owner_id": 1, + "path": "brightbox", + "updated_at": "2013-09-30T13:46:02Z" + } } ] ``` @@ -63,8 +84,8 @@ GET /projects ### Get single project -Get a specific project, identified by project ID or NAME, which is owned by the authentication user. -Currently namespaced projects cannot retrieved by name. +Get a specific project, identified by project ID or NAMESPACE/PROJECT_NAME , which is owned by the authentication user. +If using namespaced projects call make sure that the NAMESPACE/PROJECT_NAME is URL-encoded, eg. `/api/v3/projects/diaspora%2Fdiaspora` (where `/` is represented by `%2F`). ``` GET /projects/:id @@ -72,33 +93,42 @@ GET /projects/:id Parameters: -+ `id` (required) - The ID or NAME of a project ++ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project ```json { - "id": 5, - "name": "gitlab", - "name_with_namespace": "GitLab / gitlabhq", + "id": 3, "description": null, - "default_branch": "api", + "default_branch": "master", + "public": false, + "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", + "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", + "web_url": "http://example.com/diaspora/diaspora-project-site", "owner": { - "id": 1, - "username": "john_smith", - "email": "john@example.com", - "name": "John Smith", - "blocked": false, - "created_at": "2012-05-23T08:00:58Z" + "id": 3, + "name": "Diaspora", + "created_at": "2013-09-30T13: 46: 02Z" }, - "public": true, - "path": "gitlab", - "path_with_namespace": "randx/gitlab", + "name": "Diaspora Project Site", + "name_with_namespace": "Diaspora / Diaspora Project Site", + "path": "diaspora-project-site", + "path_with_namespace": "diaspora/diaspora-project-site", "issues_enabled": true, "merge_requests_enabled": true, - "wall_enabled": true, + "wall_enabled": false, "wiki_enabled": true, - "snippets_enabled": true, - "created_at": "2012-05-30T12:49:20Z", - "last_activity_at": "2012-05-23T08:05:02Z" + "snippets_enabled": false, + "created_at": "2013-09-30T13: 46: 02Z", + "last_activity_at": "2013-09-30T13: 46: 02Z", + "namespace": { + "created_at": "2013-09-30T13: 46: 02Z", + "description": "", + "id": 3, + "name": "Diaspora", + "owner_id": 1, + "path": "diaspora", + "updated_at": "2013-09-30T13: 46: 02Z" + } } ``` @@ -191,17 +221,6 @@ Parameters: + `snippets_enabled` (optional) + `public` (optional) -**Project access levels** - -The project access levels are defined in the `user_project.rb` class. Currently, these levels are recognized: - -``` - GUEST = 10 - REPORTER = 20 - DEVELOPER = 30 - MASTER = 40 -``` - ### Create project for user @@ -225,6 +244,18 @@ Parameters: + `public` (optional) +## Remove project + +Removes project with all resources(issues, merge requests etc) + +``` +DELETE /projects/:id +``` + +Parameters: + ++ `id` (required) - The ID of a project + ## Team members @@ -261,7 +292,7 @@ Parameters: "username": "john_smith", "email": "john@example.com", "name": "John Smith", - "blocked": false, + "state": "active", "created_at": "2012-05-23T08:00:58Z", "access_level": 40 } @@ -417,6 +448,55 @@ Parameters: + `id` (required) - The ID of the project +```json +[ + { + "name":"async", + "commit": { + "id":"a2b702edecdf41f07b42653eb1abe30ce98b9fca", + "parents": [{ + "id":"3f94fc7c85061973edc9906ae170cc269b07ca55" + }], + "tree": "c68537c6534a02cc2b176ca1549f4ffa190b58ee", + "message":"give caolan his credit where it's due (up top)", + "author": { + "name":"Jeremy Ashkenas", + "email":"jashkenas@example.com" + }, + "committer": { + "name":"Jeremy Ashkenas", + "email":"jashkenas@example.com" + }, + "authored_date":"2010-12-08T21:28:50+00:00", + "committed_date":"2010-12-08T21:28:50+00:00" + }, + "protected":false + }, + { + "name": "gh-pages", + "commit": { + "id": "101c10a60019fe870d21868835f65c25d64968fc", + "parents": [{ + "id": "9c15d2e26945a665131af5d7b6d30a06ba338aaa" + }], + "tree": "fb5cc9d45da3014b17a876ad539976a0fb9b352a", + "message": "Underscore.js 1.5.2", + "author": { + "name": "Jeremy Ashkenas", + "email": "jashkenas@example.com" + }, + "committer": { + "name": "Jeremy Ashkenas", + "email": "jashkenas@example.com" + }, + "authored_date": "2013-09-07T12: 58: 21+00: 00", + "committed_date": "2013-09-07T12: 58: 21+00: 00" + }, + "protected": false + } +] + +``` ### List single branch @@ -495,9 +575,22 @@ You must be a master or owner of project in order to perform action ``` POST /projects/:id/share ``` - Parameters: + `id` (required) - The ID of a project + `group_id` (required) - The ID of a group to share with + `group_access` (required) - Group access level + +## Search for projects by name + +Search for projects by name which are public or the calling user has access to + +``` +GET /projects/search/:query +``` + +Parameters: + ++ query (required) - A string contained in the project name ++ per_page (optional) - number of projects to return per page ++ page (optional) - the page to retrieve diff --git a/doc/api/repositories.md b/doc/api/repositories.md index cb0626972e..2769c22d6a 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -356,3 +356,16 @@ Parameters: + `id` (required) - The ID of a project + `sha` (required) - The commit or branch name + `filepath` (required) - The path the file + + +## Get file archive + +Get a an archive of the repository + +``` +GET /projects/:id/repository/archive +``` + +Parameters: ++ `id` (required) - The ID of a project ++ `sha` (optional) - The commit sha to download defaults to the tip of the default branch \ No newline at end of file diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md index dca22c43f8..355ce31c12 100644 --- a/doc/api/system_hooks.md +++ b/doc/api/system_hooks.md @@ -1,5 +1,7 @@ All methods require admin authorization. +The url endpoint of the system hooks can be configured in [the admin area under hooks](/admin/hooks). + ## List system hooks Get list of system hooks @@ -12,6 +14,15 @@ Parameters: + **none** +```json +[ + { + "id":3, + "url":"http://example.com/hook", + "created_at":"2013-10-02T10:15:31Z" + } +] +``` ## Add new system hook hook @@ -34,6 +45,16 @@ Parameters: + `id` (required) - The ID of hook +```json +{ + "event_name":"project_create", + "name":"Ruby", + "path":"ruby", + "project_id":1, + "owner_name":"Someone", + "owner_email":"example@gitlabhq.com" +} +``` ## Delete system hook diff --git a/doc/api/user_teams.md b/doc/api/user_teams.md deleted file mode 100644 index cf467a5466..0000000000 --- a/doc/api/user_teams.md +++ /dev/null @@ -1,209 +0,0 @@ -## User teams - -### List user teams - -Get a list of user teams viewable by the authenticated user. - -``` -GET /user_teams -``` - -```json -[ - { - id: 1, - name: "User team 1", - path: "user_team1", - owner_id: 1 - }, - { - id: 2, - name: "User team 2", - path: "user_team2", - owner_id: 1 - } -] -``` - - -### Get single user team - -Get a specific user team, identified by user team ID, which is viewable by the authenticated user. - -``` -GET /user_teams/:id -``` - -Parameters: - -+ `id` (required) - The ID of a user_team - -```json -{ - id: 1, - name: "User team 1", - path: "user_team1", - owner_id: 1 -} -``` - - -### Create user team - -Creates new user team owned by user. Available only for admins. - -``` -POST /user_teams -``` - -Parameters: - -+ `name` (required) - new user team name -+ `path` (required) - new user team internal name - - - -## User team members - -### List user team members - -Get a list of project team members. - -``` -GET /user_teams/:id/members -``` - -Parameters: - -+ `id` (required) - The ID of a user_team - - -### Get user team member - -Gets a user team member. - -``` -GET /user_teams/:id/members/:user_id -``` - -Parameters: - -+ `id` (required) - The ID of a user_team -+ `user_id` (required) - The ID of a user - -```json -{ - id: 2, - username: "john_doe", - email: "joh@doe.org", - name: "John Doe", - state: "active", - created_at: "2012-10-22T14:13:35Z", - access_level: 30 -} -``` - - -### Add user team member - -Adds a user to a user team. - -``` -POST /user_teams/:id/members -``` - -Parameters: - -+ `id` (required) - The ID of a user team -+ `user_id` (required) - The ID of a user to add -+ `access_level` (required) - Project access level - - -### Remove user team member - -Removes user from user team. - -``` -DELETE /user_teams/:id/members/:user_id -``` - -Parameters: - -+ `id` (required) - The ID of a user team -+ `user_id` (required) - The ID of a team member - -## User team projects - -### List user team projects - -Get a list of project team projects. - -``` -GET /user_teams/:id/projects -``` - -Parameters: - -+ `id` (required) - The ID of a user_team - - -### Get user team project - -Gets a user team project. - -``` -GET /user_teams/:id/projects/:project_id -``` - -Parameters: - -+ `id` (required) - The ID of a user_team -+ `project_id` (required) - The ID of a user - -```json -{ - id: 12, - name: "project1", - description: null, - default_branch: "develop", - public: false, - path: "project1", - path_with_namespace: "group1/project1", - issues_enabled: false, - merge_requests_enabled: true, - wall_enabled: true, - wiki_enabled: false, - created_at: "2013-03-11T12:59:08Z", - greatest_access_level: 30 -} -``` - - -### Add user team project - -Adds a project to a user team. - -``` -POST /user_teams/:id/projects -``` - -Parameters: - -+ `id` (required) - The ID of a user team -+ `project_id` (required) - The ID of a project to add -+ `greatest_access_level` (required) - Maximum project access level - - -### Remove user team project - -Removes project from user team. - -``` -DELETE /user_teams/:id/projects/:project_id -``` - -Parameters: - -+ `id` (required) - The ID of a user team -+ `project_id` (required) - The ID of a team project - diff --git a/doc/api/users.md b/doc/api/users.md index 49afbab8c6..16479ea6e0 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -23,7 +23,9 @@ GET /users "extern_uid": "john.smith", "provider": "provider_name", "theme_id": 1, - "color_scheme_id": 2 + "color_scheme_id": 2, + "is_admin": false, + "can_create_group": true }, { "id": 2, @@ -39,7 +41,10 @@ GET /users "extern_uid": "jack.smith", "provider": "provider_name", "theme_id": 1, - "color_scheme_id": 3 + "color_scheme_id": 3, + "is_admin": false, + "can_create_group": true, + "can_create_project": true } ] ``` @@ -72,7 +77,10 @@ Parameters: "extern_uid": "john.smith", "provider": "provider_name", "theme_id": 1, - "color_scheme_id": 2 + "color_scheme_id": 2, + "is_admin": false, + "can_create_group": true, + "can_create_project": true } ``` @@ -87,17 +95,19 @@ POST /users Parameters: -+ `email` (required) - Email -+ `password` (required) - Password -+ `username` (required) - Username -+ `name` (required) - Name -+ `skype` (optional) - Skype ID -+ `linkedin` (optional) - Linkedin -+ `twitter` (optional) - Twitter account -+ `projects_limit` (optional) - Number of projects user can create -+ `extern_uid` (optional) - External UID -+ `provider` (optional) - External provider name -+ `bio` (optional) - User's bio ++ `email` (required) - Email ++ `password` (required) - Password ++ `username` (required) - Username ++ `name` (required) - Name ++ `skype` (optional) - Skype ID ++ `linkedin` (optional) - Linkedin ++ `twitter` (optional) - Twitter account ++ `projects_limit` (optional) - Number of projects user can create ++ `extern_uid` (optional) - External UID ++ `provider` (optional) - External provider name ++ `bio` (optional) - User's bio ++ `admin` (optional) - User is admin - true or false (default) ++ `can_create_group` (optional) - User can create groups - true or false ## User modification @@ -121,6 +131,8 @@ Parameters: + `extern_uid` - External UID + `provider` - External provider name + `bio` - User's bio ++ `admin` (optional) - User is admin - true or false (default) ++ `can_create_group` (optional) - User can create groups - true or false Note, at the moment this method does only return a 404 error, even in cases where a 409 (Conflict) would be more appropriate, e.g. when renaming the email address to some existing one. @@ -166,7 +178,6 @@ GET /user "color_scheme_id": 2, "is_admin": false, "can_create_group" : true, - "can_create_team" : true, "can_create_project" : true } ``` diff --git a/doc/install/databases.md b/doc/install/databases.md index 5ec1d0c652..be7bc0aad2 100644 --- a/doc/install/databases.md +++ b/doc/install/databases.md @@ -14,6 +14,9 @@ GitLab supports the following databases: # Pick a database root password (can be anything), type it and press enter # Retype the database root password and press enter + # Secure your installation. + sudo mysql_secure_installation + # Login to MySQL mysql -u root -p @@ -55,7 +58,7 @@ GitLab supports the following databases: sudo -u postgres psql -d template1 # Create a user for GitLab. (change $password to a real password) - template1=# CREATE USER git WITH PASSWORD '$password'; + template1=# CREATE USER git; # Create the GitLab production database & grant all privileges on database template1=# CREATE DATABASE gitlabhq_production OWNER git; diff --git a/doc/install/installation.md b/doc/install/installation.md index 1034656ff7..389886d76d 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -52,7 +52,7 @@ If you are not familiar with vim please skip this and keep using the default edi Install the required packages: - sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl git-core openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev + sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl git-core openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate Make sure you have the right version of Python installed. @@ -63,7 +63,7 @@ Make sure you have the right version of Python installed. python --version # If it's Python 3 you might need to install Python 2 separately - sudo apt-get install python2.7 + sudo apt-get install -y python2.7 # Make sure you can access Python via python2 python2 --version @@ -72,7 +72,7 @@ Make sure you have the right version of Python installed. sudo ln -s /usr/bin/python /usr/bin/python2 # For reStructuredText markup language support install required package: - sudo apt-get install python-docutils + sudo apt-get install -y python-docutils **Note:** In order to receive mail notifications, make sure to install a mail server. By default, Debian is shipped with exim4 whereas Ubuntu @@ -86,14 +86,14 @@ Then select 'Internet Site' and press enter to confirm the hostname. Remove the old Ruby 1.8 if present - sudo apt-get remove -y ruby1.8 + sudo apt-get remove ruby1.8 Download Ruby and compile it: mkdir /tmp/ruby && cd /tmp/ruby curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.0/ruby-2.0.0-p247.tar.gz | tar xz cd ruby-2.0.0-p247 - ./configure + ./configure --disable-install-rdoc make sudo make install @@ -153,10 +153,10 @@ To setup the MySQL/PostgreSQL database and dependencies please see [`doc/install cd /home/git/gitlab # Checkout to stable release - sudo -u git -H git checkout 6-1-stable-ee + sudo -u git -H git checkout 6-2-stable-ee **Note:** -You can change `6-1-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +You can change `6-2-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ## Configure it @@ -195,6 +195,13 @@ You can change `6-1-stable` to `master` if you want the *bleeding edge* version, # Ex. change amount of workers to 3 for 2GB RAM server sudo -u git -H editor config/unicorn.rb + # Copy the example Rack attack config + sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb + + # Enable rack attack middleware + # Find and uncomment the line 'config.middleware.use Rack::Attack' + sudo -u git -H editor config/application.rb + # Configure Git global settings for git user, useful when editing via web # Edit user.email according to what is set in gitlab.yml sudo -u git -H git config --global user.name "GitLab" @@ -209,18 +216,18 @@ Make sure to edit both `gitlab.yml` and `unicorn.rb` to match your setup. # Mysql sudo -u git cp config/database.yml.mysql config/database.yml + # Make sure to update username/password in config/database.yml. + # You only need to adapt the production settings (first part). + # If you followed the database guide then please do as follows: + # Change 'secure password' with the value you have given to $password + # You can keep the double quotes around the password + sudo -u git -H editor config/database.yml + or # PostgreSQL sudo -u git cp config/database.yml.postgresql config/database.yml - # Make sure to update username/password in config/database.yml. - # You only need to adapt the production settings (first part). - # If you followed the database guide then please do as follows: - # Change 'root' to 'gitlab' - # Change 'secure password' with the value you have given to $password - # You can keep the double quotes around the password - sudo -u git -H editor config/database.yml # Make config/database.yml readable to git only sudo -u git -H chmod o-rwx config/database.yml @@ -258,6 +265,9 @@ Make GitLab start on boot: sudo update-rc.d gitlab defaults 21 +## Set up logrotate + + sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab ## Check Application Status @@ -337,6 +347,12 @@ a different host, you can configure its connection string via the # example production: redis://redis.example.tld:6379 +If you want to connect the Redis server via socket, then use the "unix:" URL scheme +and the path to the Redis socket file in the `config/resque.yml` file. + + # example + production: unix:/path/to/redis/socket + ## Custom SSH Connection If you are running SSH on a non-standard port, you must change the gitlab user's SSH config. diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 1dba04f423..30a9564674 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -41,24 +41,31 @@ some work on your part. ## CPU -We recommend a processor with **4 cores**. At a minimum you need a processor with 2 cores to responsively run an unmodified installation. +- 1 core works for under 100 users but the responsiveness might suffer +- **2 cores** is the **recommended** number of cores and supports up to 100 users +- 4 cores supports about 1,000 users +- 8 cores supports up to 10,000 users ## Memory - 512MB is too little memory, GitLab will be very slow and you will need 250MB of swap -- 768MB is the minimal memory size and supports up to 100 users -- **1GB** is the **recommended** memory size and supports up to 1,000 users -- 1.5GB supports up to 10,000 users +- 768MB is the minimal memory size but we advise against this +- 1GB supports up to 100 users if you do not have individual repo's over 250MB +- **2GB** is the **recommended** memory size and supports up to 1,000 users +- 4GB supports up to 10,000 users ## Storage The necessary hard drive space largely depends on the size of the repos you want to store in GitLab. But as a *rule of thumb* you should have at least twice as much -free space as your all repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo. Apart from a local hard drive you can also mount a volume that supports the network file system (NFS) protocol. This volume might be located on a file server, a network attached storage (NAS) device, a storage area network (SAN) or on an Amazon Web Services (AWS) Elastic Block Store (EBS) volume. +free space as your all repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo. + +If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them. + +Apart from a local hard drive you can also mount a volume that supports the network file system (NFS) protocol. This volume might be located on a file server, a network attached storage (NAS) device, a storage area network (SAN) or on an Amazon Web Services (AWS) Elastic Block Store (EBS) volume. If you have enough RAM memory and a recent CPU the speed of GitLab is mainly limited by hard drive seek times. Having a fast drive (7200 RPM and up) or a solid state drive (SSD) will improve the responsiveness of GitLab. - # Installation troubles and reporting success or failure If you have troubles installing GitLab following the [official installation guide](installation.md) diff --git a/doc/make_release.md b/doc/make_release.md index 5be0d5980a..7f19a17da6 100644 --- a/doc/make_release.md +++ b/doc/make_release.md @@ -1,5 +1,6 @@ # Things to do when creating new release -NOTE: This is a developer guide. If you are trying to install GitLab see the latest stable [installation guide](install/installation.md) and if you are trying to upgrade, see the [upgrade guides](update). +NOTE: This is a guide for GitLab developers. If you are trying to install GitLab see the latest stable [installation guide](install/installation.md) and if you are trying to upgrade, see the [upgrade guides](update). + ## Install guide up to date? * References correct GitLab branch `x-x-stable` and correct GitLab shell tag? @@ -41,7 +42,7 @@ Check if changed since last release (~22nd of last month depending on when last #### 10. Check application status -## Make sure code status is good +## Make sure the code quality indicatiors are good * [![build status](http://ci.gitlab.org/projects/1/status.png?ref=master)](http://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch) @@ -53,4 +54,18 @@ Check if changed since last release (~22nd of last month depending on when last * [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq) -## Make release branch +## Make a release branch + +After making the release branch new commits are cherry-picked from master. When the release gets closer we get more selective what is cherry-picked. + +- 5 days before release: feature freeze +- 3 days before release: UI freeze +- 1 day before release: code freeze + +## Last actions + +1. Write a blog post (mention what GitLab is on the first line, select a MVP) +1. Update VERSION and CHANGELOG +1. Create a git tag vX.X.X +1. Publish the blog post +1. Tweet about the release diff --git a/doc/raketasks/web_hooks.md b/doc/raketasks/web_hooks.md new file mode 100644 index 0000000000..1ca5bacb9d --- /dev/null +++ b/doc/raketasks/web_hooks.md @@ -0,0 +1,31 @@ +### Add a web hook for **ALL** projects: + + RAILS_ENV=production bundle exec rake gitlab:web_hook:add URL="http://example.com/hook" + + +### Add a web hook for projects in a given **NAMESPACE**: + + RAILS_ENV=production bundle exec rake gitlab:web_hook:add URL="http://example.com/hook" NAMESPACE=acme + + +### Remove a web hook from **ALL** projects using: + + RAILS_ENV=production bundle exec rake gitlab:web_hook:rm URL="http://example.com/hook" + + +### Remove a web hook from projects in a given **NAMESPACE**: + + RAILS_ENV=production bundle exec rake gitlab:web_hook:rm URL="http://example.com/hook" NAMESPACE=acme + + +### List **ALL** web hooks: + + RAILS_ENV=production bundle exec rake gitlab:web_hook:list + + +### List the web hooks from projects in a given **NAMESPACE**: + + RAILS_ENV=production bundle exec rake gitlab:web_hook:list NAMESPACE=/ + +> Note: `/` is the global namespace. + diff --git a/doc/security/rack_attack.md b/doc/security/rack_attack.md new file mode 100644 index 0000000000..a0d02b1650 --- /dev/null +++ b/doc/security/rack_attack.md @@ -0,0 +1,19 @@ +To prevent abusive clients doing damage GitLab uses rack-attack gem. +If you installed or upgraded GitLab by following the official guides this should be enabled by default. +If you are missing `config/initializers/rack_attack.rb` the following steps need to be taken in order to enable protection for your GitLab instance: + +1. In config/application.rb find and uncomment the following line: + config.middleware.use Rack::Attack +2. Rename config/initializers/rack_attack.rb.example to config/initializers/rack_attack.rb +3. Review the paths_to_be_protected and add any other path you need protecting +4. Restart GitLab instance + +By default, user sign-in, user sign-up(if enabled) and user password reset is limited to 6 requests per minute. +After trying for 6 times, client will have to wait for the next minute to be able to try again. +These settings can be found in `config/initializers/rack_attack.rb` + +If you want more restrictive/relaxed throttle rule change the `limit` or `period` values. For example, more relaxed throttle rule will be if you set limit: 3 and period: 1.second(this will allow 3 requests per second). You can also add other paths to the protected list by adding to `paths_to_be_protected` variable. If you change any of these settings do not forget to restart your GitLab instance. + +In case you find throttling is not enough to protect you against abusive clients, rack-attack gem offers IP whitelisting, blacklisting, Fail2ban style filter and tracking. + +For more information on how to use these options check out [rack-attack README](https://github.com/kickstarter/rack-attack/blob/master/README.md). \ No newline at end of file diff --git a/doc/update/4.1-to-4.2.md b/doc/update/4.1-to-4.2.md index 536f22415e..3aaf17b772 100644 --- a/doc/update/4.1-to-4.2.md +++ b/doc/update/4.1-to-4.2.md @@ -12,15 +12,15 @@ cd /home/gitlab/gitlab/ # Get latest code -sudo -u gitlab git fetch +sudo -u gitlab -H git fetch -sudo -u gitlab git checkout 4-2-stable +sudo -u gitlab -H git checkout 4-2-stable # Install libs -sudo -u gitlab bundle install --without development test postgres --deployment +sudo -u gitlab -H bundle install --without development test postgres --deployment # update db -sudo -u gitlab bundle exec rake db:migrate RAILS_ENV=production +sudo -u gitlab -H bundle exec rake db:migrate RAILS_ENV=production ``` diff --git a/doc/update/4.2-to-5.0.md b/doc/update/4.2-to-5.0.md index 053a50ebc8..64b89ee93c 100644 --- a/doc/update/4.2-to-5.0.md +++ b/doc/update/4.2-to-5.0.md @@ -85,7 +85,7 @@ sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:shell:setup RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:shell:build_missing_projects RAILS_ENV=production -sudo -u git -H mkdir /home/git/gitlab-satellites +sudo -u git -H mkdir -p /home/git/gitlab-satellites sudo -u git -H bundle exec rake gitlab:satellites:create RAILS_ENV=production # migrate wiki to git @@ -101,7 +101,7 @@ sudo chown -R git /home/git/gitlab/log/ sudo chown -R git /home/git/gitlab/tmp/ sudo chmod -R u+rwX /home/git/gitlab/log/ sudo chmod -R u+rwX /home/git/gitlab/tmp/ -sudo -u git -H mkdir /home/git/gitlab/tmp/pids/ +sudo -u git -H mkdir -p /home/git/gitlab/tmp/pids/ sudo chmod -R u+rwX /home/git/gitlab/tmp/pids ``` diff --git a/doc/update/5.1-to-5.2.md b/doc/update/5.1-to-5.2.md index 8599c4323e..27f992ecfe 100644 --- a/doc/update/5.1-to-5.2.md +++ b/doc/update/5.1-to-5.2.md @@ -7,7 +7,7 @@ It's useful to make a backup just in case things go south: ```bash cd /home/git/gitlab -sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` ### 1. Stop server @@ -95,5 +95,5 @@ Follow the [`upgrade guide from 5.0 to 5.1`](5.0-to-5.1.md), except for the data ```bash cd /home/git/gitlab -sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:restore +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production ``` diff --git a/doc/update/5.2-to-5.3.md b/doc/update/5.2-to-5.3.md index e00dfa3951..a8bb530902 100644 --- a/doc/update/5.2-to-5.3.md +++ b/doc/update/5.2-to-5.3.md @@ -7,7 +7,7 @@ It's useful to make a backup just in case things go south: ```bash cd /home/git/gitlab -sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` ### 1. Stop server @@ -78,5 +78,5 @@ Follow the [`upgrade guide from 5.1 to 5.2`](5.1-to-5.2.md), except for the data ```bash cd /home/git/gitlab -sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:restore +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production ``` diff --git a/doc/update/5.3-to-5.4.md b/doc/update/5.3-to-5.4.md index 5fba0e26af..315bf03a6d 100644 --- a/doc/update/5.3-to-5.4.md +++ b/doc/update/5.3-to-5.4.md @@ -7,7 +7,7 @@ It's useful to make a backup just in case things go south: ```bash cd /home/git/gitlab -sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` ### 1. Stop server @@ -86,5 +86,5 @@ Follow the [`upgrade guide from 5.2 to 5.3`](5.2-to-5.3.md), except for the data ```bash cd /home/git/gitlab -sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:restore +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production ``` diff --git a/doc/update/5.4-to-6.0.md b/doc/update/5.4-to-6.0.md index 3b1d987820..fc010cd818 100644 --- a/doc/update/5.4-to-6.0.md +++ b/doc/update/5.4-to-6.0.md @@ -24,7 +24,7 @@ It's useful to make a backup just in case things go south: ```bash cd /home/git/gitlab -sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` ### 1. Stop server @@ -91,7 +91,7 @@ Note: We switched from Puma in GitLab 5.4 to unicorn in GitLab 6.0. ```bash sudo rm /etc/init.d/gitlab -sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/master/lib/support/init.d/gitlab +sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/6-0-stable/lib/support/init.d/gitlab sudo chmod +x /etc/init.d/gitlab ``` @@ -111,3 +111,21 @@ To make sure you didn't miss anything run a more thorough check with: sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production If all items are green, then congratulations upgrade complete! + +### Troubleshooting +The migrations in this update are very sensitive to incomplete or inconsistent data. If you have a long-running GitLab installation and some of the previous upgrades did not work out 100% correct this may bite you now. The following commands can be run in the rails console to look for 'bad' data. + +All project owners should have an owner +``` +Project.all.select { |project| project.owner.blank? } +``` + +Every user should have a namespace +``` +User.all.select { |u| u.namespace.blank? } +``` + +Projects in the global namespace should not conflict with projects in the owner namespace +``` +Project.where(namespace_id: nil).select { |p| Project.where(path: p.path, namespace_id: p.owner.try(:namespace).try(:id)).present? } +``` diff --git a/doc/update/6.0-to-6.1.md b/doc/update/6.0-to-6.1.md index ba80603499..8fe703309e 100644 --- a/doc/update/6.0-to-6.1.md +++ b/doc/update/6.0-to-6.1.md @@ -53,6 +53,7 @@ sudo -u git -H bundle install --without development test mysql --deployment sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production ``` @@ -79,6 +80,7 @@ sudo chmod +x /etc/init.d/gitlab Check if GitLab and its environment are configured correctly: + cd /home/git/gitlab sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production To make sure you didn't miss anything run a more thorough check with: diff --git a/doc/update/6.1-to-6.2.md b/doc/update/6.1-to-6.2.md new file mode 100644 index 0000000000..e7f6c2a5ba --- /dev/null +++ b/doc/update/6.1-to-6.2.md @@ -0,0 +1,102 @@ +# From 6.1 to 6.2 + +# You should update to 6.1 before installing 6.2 so all the necessary conversions are run. + +### 0. Backup + +It's useful to make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + + sudo service gitlab stop + +### 2. Get latest code + +```bash +cd /home/git/gitlab +sudo -u git -H git fetch +sudo -u git -H git checkout 6-2-stable +``` + +### 3. Install additional packages + +```bash +# Add support for lograte for better log file handling +sudo apt-get install logrotate +``` + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL +sudo -u git -H bundle install --without development test postgres --deployment + +#PostgreSQL +sudo -u git -H bundle install --without development test mysql --deployment + + +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production +sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production +sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production +``` + +### 5. Update config files + +* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/6-2-stable/config/gitlab.yml.example but with your settings. +* Make `/home/git/gitlab/config/unicorn.rb` same as https://github.com/gitlabhq/gitlabhq/blob/6-2-stable/config/unicorn.rb.example but with your settings. +* Copy rack attack middleware config +```bash +sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb +``` +* Uncomment `config.middleware.use Rack::Attack` in `/home/git/gitlab/config/application.rb` +* Set up logrotate +```bash +sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab +``` + +### 6. Update Init script + +```bash +sudo rm /etc/init.d/gitlab +sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/6-2-stable/lib/support/init.d/gitlab +sudo chmod +x /etc/init.d/gitlab +``` + +### 7. Start application + + sudo service gitlab start + sudo service nginx restart + +### 8. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## Things went south? Revert to previous version (6.1) + +### 1. Revert the code to the previous version +Follow the [`upgrade guide from 6.0 to 6.1`](6.0-to-6.1.md), except for the database migration +(The backup is already migrated to the previous version) + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` diff --git a/features/profile/profile.feature b/features/profile/profile.feature index 3b61552a73..6198fd2b30 100644 --- a/features/profile/profile.feature +++ b/features/profile/profile.feature @@ -11,21 +11,32 @@ Feature: Profile Then I change my contact info And I should see new contact info + Scenario: I change my password without old one + Given I visit profile password page + When I try change my password w/o old one + Then I should see a missing password error message + And I should be redirected to password page + Scenario: I change my password - Given I visit profile account page + Given I visit profile password page Then I change my password And I should be redirected to sign in page + Scenario: I edit my avatar + Given I visit profile page + Then I change my avatar + And I should see new avatar + Scenario: My password is expired Given my password is expired And I am not an ldap user - And I visit profile account page + Given I visit profile password page Then I redirected to expired password page And I submit new password And I redirected to sign in page Scenario: I unsuccessfully change my password - Given I visit profile account page + Given I visit profile password page When I unsuccessfully change my password Then I should see a password error message diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature index 022188f825..67986784bc 100644 --- a/features/project/issues/issues.feature +++ b/features/project/issues/issues.feature @@ -3,6 +3,7 @@ Feature: Project Issues Given I sign in as a user And I own project "Shop" And project "Shop" have "Release 0.4" open issue + And project "Shop" have "Tweet control" open issue And project "Shop" have "Release 0.3" closed issue And I visit project "Shop" issues page @@ -37,20 +38,20 @@ Feature: Project Issues @javascript Scenario: I search issue - Given I fill in issue search with "Release" + Given I fill in issue search with "Re" Then I should see "Release 0.4" in issues And I should not see "Release 0.3" in issues + And I should not see "Tweet control" in issues @javascript Scenario: I search issue that not exist - Given I fill in issue search with "Bug" + Given I fill in issue search with "Bu" Then I should not see "Release 0.4" in issues And I should not see "Release 0.3" in issues - @javascript Scenario: I search all issues Given I click link "All" - And I fill in issue search with "0.3" + And I fill in issue search with ".3" Then I should see "Release 0.3" in issues And I should not see "Release 0.4" in issues diff --git a/features/project/service.feature b/features/project/service.feature index e685c385d1..4805d2befb 100644 --- a/features/project/service.feature +++ b/features/project/service.feature @@ -24,3 +24,9 @@ Feature: Project Services And I click pivotaltracker service link And I fill pivotaltracker settings Then I should see pivotaltracker service settings saved + + Scenario: Activate Flowdock service + When I visit project "Shop" services page + And I click Flowdock service link + And I fill Flowdock settings + Then I should see Flowdock service settings saved diff --git a/features/project/snippets.feature b/features/project/snippets.feature index a26c8dc847..dfaa02663a 100644 --- a/features/project/snippets.feature +++ b/features/project/snippets.feature @@ -31,5 +31,5 @@ Feature: Project Snippets Scenario: I destroy "Snippet one" Given I visit snippet page "Snippet one" And I click link "Edit" - And I click link "Destroy" + And I click link "Remove Snippet" Then I should not see "Snippet one" in snippets diff --git a/features/project/source/markdown_render.feature b/features/project/source/markdown_render.feature new file mode 100644 index 0000000000..8b4b89e85a --- /dev/null +++ b/features/project/source/markdown_render.feature @@ -0,0 +1,80 @@ +Feature: Project markdown render + Background: + Given I sign in as a user + And I own project "Delta" + Given I visit project source page + + Scenario: I browse files from master branch + Then I should see files from repository in master + And I should see rendered README which contains correct links + And I click on Gitlab API in README + Then I should see correct document rendered + + Scenario: I view README in master branch + Then I should see files from repository in master + And I should see rendered README which contains correct links + And I click on Rake tasks in README + Then I should see correct directory rendered + + Scenario: I navigate to doc directory to view documentation in master + And I navigate to the doc/api/README + And I see correct file rendered + And I click on users in doc/api/README + Then I should see the correct document file + + Scenario: I navigate to doc directory to view user doc in master + And I navigate to the doc/api/README + And I see correct file rendered + And I click on raketasks in doc/api/README + Then I should see correct directory rendered + + Scenario: I browse files from markdown branch + When I visit markdown branch + Then I should see files from repository in markdown branch + And I should see rendered README which contains correct links + And I click on Gitlab API in README + Then I should see correct document rendered for markdown branch + + Scenario: I browse directory from markdown branch + When I visit markdown branch + Then I should see files from repository in markdown branch + And I should see rendered README which contains correct links + And I click on Rake tasks in README + Then I should see correct directory rendered for markdown branch + + Scenario: I navigate to doc directory to view documentation in markdown branch + When I visit markdown branch + And I navigate to the doc/api/README + And I see correct file rendered in markdown branch + And I click on users in doc/api/README + Then I should see the users document file in markdown branch + + Scenario: I navigate to doc directory to view user doc in markdown branch + When I visit markdown branch + And I navigate to the doc/api/README + And I see correct file rendered in markdown branch + And I click on raketasks in doc/api/README + Then I should see correct directory rendered for markdown branch + + Scenario: I create a wiki page with different links + Given I go to wiki page + And I add various links to the wiki page + Then Wiki page should have added links + And I click on test link + Then I see new wiki page named test + When I go back to wiki page home + And I click on GitLab API doc link + Then I see Gitlab API document + When I go back to wiki page home + And I click on Rake tasks link + Then I see Rake tasks directory + + Scenario: I visit the help page with markdown + Given I visit to the help page + And I select a page with markdown + Then I should see a help page with markdown + + Scenario: Tree view should have correct links in README + Given I go directory which contains README file + And I click on a relative link in README + Then I should see the correct markdown diff --git a/features/public/public_projects.feature b/features/public/public_projects.feature index c4f1b6203e..178a769194 100644 --- a/features/public/public_projects.feature +++ b/features/public/public_projects.feature @@ -9,6 +9,10 @@ Feature: Public Projects Feature And I should not see project "Enterprise" Scenario: I visit public project page - When I visit public page for "Community" project - Then I should see public project details - And I should see project readme + When I visit project "Community" page + Then I should see project "Community" home page + + Scenario: I visit an empty public project page + Given public empty project "Empty Public Project" + When I visit empty project page + Then I should see empty public project details diff --git a/features/steps/group/group.rb b/features/steps/group/group.rb index b3d557ddcd..0ad1746bed 100644 --- a/features/steps/group/group.rb +++ b/features/steps/group/group.rb @@ -10,7 +10,8 @@ class Groups < Spinach::FeatureSteps end And 'I have group with projects' do - @group = create(:group, owner: current_user) + @group = create(:group) + @group.add_owner(current_user) @project = create(:project, namespace: @group) @event = create(:closed_issue_event, project: @project) diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 6944977c3f..753e2c19bc 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -3,9 +3,7 @@ class Profile < Spinach::FeatureSteps include SharedPaths step 'I should see my profile info' do - page.should have_content "Profile" - page.should have_content @user.name - page.should have_content @user.email + page.should have_content "Profile settings" end step 'I change my contact info' do @@ -22,8 +20,28 @@ class Profile < Spinach::FeatureSteps @user.twitter.should == 'testtwitter' end + step 'I change my avatar' do + attach_file(:user_avatar, File.join(Rails.root, 'public', 'gitlab_logo.png')) + click_button "Save changes" + @user.reload + end + + step 'I should see new avatar' do + @user.avatar.should be_instance_of AttachmentUploader + @user.avatar.url.should == "/uploads/user/avatar/#{ @user.id }/gitlab_logo.png" + end + + step 'I try change my password w/o old one' do + within '.update-password' do + fill_in "user_password", with: "222333" + fill_in "user_password_confirmation", with: "222333" + click_button "Save" + end + end + step 'I change my password' do within '.update-password' do + fill_in "user_current_password", with: "123456" fill_in "user_password", with: "222333" fill_in "user_password_confirmation", with: "222333" click_button "Save" @@ -32,12 +50,17 @@ class Profile < Spinach::FeatureSteps step 'I unsuccessfully change my password' do within '.update-password' do + fill_in "user_current_password", with: "123456" fill_in "user_password", with: "password" fill_in "user_password_confirmation", with: "confirmation" click_button "Save" end end + step "I should see a missing password error message" do + page.should have_content "You must provide a valid current password" + end + step "I should see a password error message" do page.should have_content "Password doesn't match confirmation" end @@ -110,6 +133,14 @@ class Profile < Spinach::FeatureSteps current_path.should == new_user_session_path end + step 'I should be redirected to password page' do + current_path.should == edit_profile_password_path + end + + step 'I should be redirected to account page' do + current_path.should == profile_account_path + end + step 'I click on my profile picture' do click_link 'profile-pic' end diff --git a/features/steps/project/project_issues.rb b/features/steps/project/project_issues.rb index 6c18177498..801fff78a5 100644 --- a/features/steps/project/project_issues.rb +++ b/features/steps/project/project_issues.rb @@ -12,6 +12,10 @@ class ProjectIssues < Spinach::FeatureSteps page.should_not have_content "Release 0.3" end + And 'I should not see "Tweet control" in issues' do + page.should_not have_content "Tweet control" + end + Given 'I click link "Closed"' do click_link "Closed" end @@ -56,16 +60,16 @@ class ProjectIssues < Spinach::FeatureSteps page.should have_content issue.project.name end - Given 'I fill in issue search with "Release"' do - fill_in 'issue_search', with: "Release" + Given 'I fill in issue search with "Re"' do + fill_in 'issue_search', with: "Re" end - Given 'I fill in issue search with "Bug"' do - fill_in 'issue_search', with: "Bug" + Given 'I fill in issue search with "Bu"' do + fill_in 'issue_search', with: "Bu" end - And 'I fill in issue search with "0.3"' do - fill_in 'issue_search', with: "0.3" + And 'I fill in issue search with ".3"' do + fill_in 'issue_search', with: ".3" end And 'I fill in issue search with "Something"' do @@ -120,6 +124,14 @@ class ProjectIssues < Spinach::FeatureSteps author: project.users.first) end + And 'project "Shop" have "Tweet control" open issue' do + project = Project.find_by_name("Shop") + create(:issue, + title: "Tweet control", + project: project, + author: project.users.first) + end + And 'project "Shop" have "Release 0.3" closed issue' do project = Project.find_by_name("Shop") create(:closed_issue, diff --git a/features/steps/project/project_markdown_render.rb b/features/steps/project/project_markdown_render.rb new file mode 100644 index 0000000000..95d73c9362 --- /dev/null +++ b/features/steps/project/project_markdown_render.rb @@ -0,0 +1,179 @@ +class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + + And 'I own project "Delta"' do + @project = Project.find_by_name "Delta" + @project ||= create(:project_with_code, name: "Delta", namespace: @user.namespace) + @project.team << [@user, :master] + end + + Then 'I should see files from repository in master' do + current_path.should == project_tree_path(@project, "master") + page.should have_content "Gemfile" + page.should have_content "app" + page.should have_content "README" + end + + And 'I should see rendered README which contains correct links' do + page.should have_content "Welcome to GitLab GitLab is a free project and repository management application" + page.should have_link "GitLab API doc" + page.should have_link "GitLab API website" + page.should have_link "Rake tasks" + page.should have_link "backup and restore procedure" + end + + And 'I click on Gitlab API in README' do + click_link "GitLab API doc" + end + + Then 'I should see correct document rendered' do + current_path.should == project_blob_path(@project, "master/doc/api/README.md") + page.should have_content "All API requests require authentication" + end + + And 'I click on Rake tasks in README' do + click_link "Rake tasks" + end + + Then 'I should see correct directory rendered' do + current_path.should == project_tree_path(@project, "master/doc/raketasks") + page.should have_content "backup_restore.md" + page.should have_content "maintenance.md" + end + + And 'I navigate to the doc/api/README' do + click_link "doc" + click_link "api" + click_link "README.md" + end + + And 'I see correct file rendered' do + current_path.should == project_blob_path(@project, "master/doc/api/README.md") + page.should have_content "Contents" + page.should have_link "Users" + page.should have_link "Rake tasks" + end + + And 'I click on users in doc/api/README' do + click_link "Users" + end + + Then 'I should see the correct document file' do + current_path.should == project_blob_path(@project, "master/doc/api/users.md") + page.should have_content "Get a list of users." + end + + And 'I click on raketasks in doc/api/README' do + click_link "Rake tasks" + end + + When 'I visit markdown branch' do + visit project_tree_path(@project, "markdown") + end + + Then 'I should see files from repository in markdown branch' do + current_path.should == project_tree_path(@project, "markdown") + page.should have_content "Gemfile" + page.should have_content "app" + page.should have_content "README" + end + + And 'I see correct file rendered in markdown branch' do + current_path.should == project_blob_path(@project, "markdown/doc/api/README.md") + page.should have_content "Contents" + page.should have_link "Users" + page.should have_link "Rake tasks" + end + + Then 'I should see correct document rendered for markdown branch' do + current_path.should == project_blob_path(@project, "markdown/doc/api/README.md") + page.should have_content "All API requests require authentication" + end + + Then 'I should see correct directory rendered for markdown branch' do + current_path.should == project_tree_path(@project, "markdown/doc/raketasks") + page.should have_content "backup_restore.md" + page.should have_content "maintenance.md" + end + + Then 'I should see the users document file in markdown branch' do + current_path.should == project_blob_path(@project, "markdown/doc/api/users.md") + page.should have_content "Get a list of users." + end + + Given 'I go to wiki page' do + click_link "Wiki" + current_path.should == project_wiki_path(@project, "home") + end + + And 'I add various links to the wiki page' do + fill_in "wiki[content]", with: "[test](test)\n[GitLab API doc](doc/api/README.md)\n[Rake tasks](doc/raketasks)\n" + fill_in "wiki[message]", with: "Adding links to wiki" + click_button "Create page" + end + + Then 'Wiki page should have added links' do + current_path.should == project_wiki_path(@project, "home") + page.should have_content "test GitLab API doc Rake tasks" + end + + And 'I click on test link' do + click_link "test" + end + + Then 'I see new wiki page named test' do + current_path.should == project_wiki_path(@project, "test") + page.should have_content "Editing page" + end + + When 'I go back to wiki page home' do + visit project_wiki_path(@project, "home") + current_path.should == project_wiki_path(@project, "home") + end + + And 'I click on GitLab API doc link' do + click_link "GitLab API" + end + + Then 'I see Gitlab API document' do + current_path.should == project_blob_path(@project, "master/doc/api/README.md") + page.should have_content "Status codes" + end + + And 'I click on Rake tasks link' do + click_link "Rake tasks" + end + + Then 'I see Rake tasks directory' do + current_path.should == project_tree_path(@project, "master/doc/raketasks") + page.should have_content "backup_restore.md" + page.should have_content "maintenance.md" + end + + Given 'I visit to the help page' do + visit help_path + end + + And 'I select a page with markdown' do + click_link "Rake Tasks" + end + + Then 'I should see a help page with markdown' do + page.should have_content "GitLab provides some specific rake tasks to enable special features or perform maintenance tasks" + end + + Given 'I go directory which contains README file' do + visit project_tree_path(@project, "master/doc/api") + current_path.should == project_tree_path(@project, "master/doc/api") + end + + And 'I click on a relative link in README' do + click_link "Users" + end + + Then 'I should see the correct markdown' do + current_path.should == project_blob_path(@project, "master/doc/api/users.md") + page.should have_content "List users" + end +end diff --git a/features/steps/project/project_merge_requests.rb b/features/steps/project/project_merge_requests.rb index 7c70482deb..6c794ef8f1 100644 --- a/features/steps/project/project_merge_requests.rb +++ b/features/steps/project/project_merge_requests.rb @@ -21,7 +21,9 @@ class ProjectMergeRequests < Spinach::FeatureSteps end Then 'I should see merge request "Wiki Feature"' do - page.should have_content "Wiki Feature" + within '.merge-request' do + page.should have_content "Wiki Feature" + end end Then 'I should see closed merge request "Bug NS-04"' do @@ -56,16 +58,20 @@ class ProjectMergeRequests < Spinach::FeatureSteps end And 'I submit new merge request "Wiki Feature"' do - #this must come first, so that the target branch is set by the time the "select" for "notes_refactoring" is executed - select project.path_with_namespace, :from => "merge_request_target_project_id" - fill_in "merge_request_title", :with => "Wiki Feature" - select "master", :from => "merge_request_source_branch" + fill_in "merge_request_title", with: "Wiki Feature" + + # this must come first, so that the target branch is set + # by the time the "select" for "notes_refactoring" is executed + select project.path_with_namespace, from: "merge_request_target_project_id" + select "master", from: "merge_request_source_branch" + find(:select, "merge_request_target_project_id", {}).value.should == project.id.to_s find(:select, "merge_request_source_project_id", {}).value.should == project.id.to_s - #using "notes_refactoring" because "Bug NS-04" uses master/stable, this will fail merge_request validation if the branches are the same + # using "notes_refactoring" because "Bug NS-04" uses master/stable, + # this will fail merge_request validation if the branches are the same find(:select, "merge_request_target_branch", {}).find(:option, "notes_refactoring", {}).value.should == "notes_refactoring" - select "notes_refactoring", :from => "merge_request_target_branch" + select "notes_refactoring", from: "merge_request_target_branch" click_button "Submit merge request" end diff --git a/features/steps/project/project_services.rb b/features/steps/project/project_services.rb index a24100ff8c..70eafc875d 100644 --- a/features/steps/project/project_services.rb +++ b/features/steps/project/project_services.rb @@ -58,4 +58,18 @@ class ProjectServices < Spinach::FeatureSteps Then 'I should see pivotaltracker service settings saved' do find_field('Token').value.should == 'verySecret' end + + And 'I click Flowdock service link' do + click_link 'Flowdock' + end + + And 'I fill Flowdock settings' do + check 'Active' + fill_in 'Token', with: 'verySecret' + click_button 'Save' + end + + Then 'I should see Flowdock service settings saved' do + find_field('Token').value.should == 'verySecret' + end end diff --git a/features/steps/project/project_snippets.rb b/features/steps/project/project_snippets.rb index 86c0685256..5082b31198 100644 --- a/features/steps/project/project_snippets.rb +++ b/features/steps/project/project_snippets.rb @@ -47,8 +47,8 @@ class ProjectSnippets < Spinach::FeatureSteps end end - And 'I click link "Destroy"' do - click_link "Destroy" + And 'I click link "Remove Snippet"' do + click_link "Remove snippet" end And 'I submit new snippet "Snippet three"' do diff --git a/features/steps/public/projects_feature.rb b/features/steps/public/projects_feature.rb index 8d612498fb..e9a4d56e36 100644 --- a/features/steps/public/projects_feature.rb +++ b/features/steps/public/projects_feature.rb @@ -9,6 +9,10 @@ class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps page.should_not have_content "Enterprise" end + step 'I should see project "Empty Public Project"' do + page.should have_content "Empty Public Project" + end + step 'I should see public project details' do page.should have_content '32 branches' page.should have_content '16 tags' @@ -19,13 +23,35 @@ class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps end step 'public project "Community"' do - create :project_with_code, name: 'Community', public: true + create :project_with_code, name: 'Community', public: true, default_branch: 'master' + end + + step 'public empty project "Empty Public Project"' do + create :project, name: 'Empty Public Project', public: true + end + + step 'I visit empty project page' do + project = Project.find_by_name('Empty Public Project') + visit project_path(project) + end + + step 'I visit project "Community" page' do + project = Project.find_by_name('Community') + visit project_path(project) + end + + step 'I should see empty public project details' do + page.should have_content 'Git global setup' end step 'private project "Enterprise"' do create :project, name: 'Enterprise' end + step 'I should see project "Community" home page' do + page.should have_content 'Repo size is' + end + private def project diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index c30eccce1c..156fa5bab4 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -65,8 +65,12 @@ module SharedPaths visit profile_path end + step 'I visit profile password page' do + visit edit_profile_password_path + end + step 'I visit profile account page' do - visit account_profile_path + visit profile_account_path end step 'I visit profile SSH keys page' do diff --git a/features/support/env.rb b/features/support/env.rb index 8798e62ea7..d27a73edab 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -51,4 +51,6 @@ Spinach.hooks.before_run do RSpec::Mocks::setup self include FactoryGirl::Syntax::Methods + MergeRequestObserver.any_instance.stub(current_user: create(:user)) end + diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb index 55c947eb17..218b3d8eee 100644 --- a/lib/api/deploy_keys.rb +++ b/lib/api/deploy_keys.rb @@ -2,6 +2,7 @@ module API # Projects API class DeployKeys < Grape::API before { authenticate! } + before { authorize_admin_project } resource :projects do helpers do diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 7e8a928d47..5efd971cfb 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -3,6 +3,9 @@ module API class User < Grape::Entity expose :id, :username, :email, :name, :bio, :skype, :linkedin, :twitter, :theme_id, :color_scheme_id, :state, :created_at, :extern_uid, :provider + expose :is_admin?, as: :is_admin + expose :can_create_group?, as: :can_create_group + expose :can_create_project?, as: :can_create_project end class UserSafe < Grape::Entity @@ -15,10 +18,6 @@ module API class UserLogin < User expose :private_token - expose :is_admin?, as: :is_admin - expose :can_create_group?, as: :can_create_group - expose :can_create_project?, as: :can_create_project - expose :can_create_team?, as: :can_create_team end class Hook < Grape::Entity @@ -92,15 +91,16 @@ module API expose :expires_at, :updated_at, :created_at end - class Milestone < Grape::Entity - expose :id - expose (:project_id) {|milestone| milestone.project.id} + class ProjectEntity < Grape::Entity + expose :id, :iid + expose (:project_id) { |entity| entity.project.id } + end + + class Milestone < ProjectEntity expose :title, :description, :due_date, :state, :updated_at, :created_at end - class Issue < Grape::Entity - expose :id - expose (:project_id) {|issue| issue.project.id} + class Issue < ProjectEntity expose :title, :description expose :label_list, as: :labels expose :milestone, using: Entities::Milestone @@ -108,14 +108,14 @@ module API expose :state, :updated_at, :created_at end - class SSHKey < Grape::Entity - expose :id, :title, :key, :created_at + class MergeRequest < ProjectEntity + expose :target_branch, :source_branch, :title, :state, :upvotes, :downvotes + expose :author, :assignee, using: Entities::UserBasic + expose :source_project_id, :target_project_id end - class MergeRequest < Grape::Entity - expose :id, :target_branch, :source_branch, :title, :state - expose :target_project_id, as: :project_id - expose :author, :assignee, using: Entities::UserBasic + class SSHKey < Grape::Entity + expose :id, :title, :key, :created_at end class Note < Grape::Entity diff --git a/lib/api/groups.rb b/lib/api/groups.rb index d6ebb74bc1..55aa484435 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -7,12 +7,14 @@ module API helpers do def find_group(id) group = Group.find(id) - if current_user.admin or current_user.groups.include? group + + if can?(current_user, :read_group, group) group else render_api_error!("403 Forbidden - #{current_user.username} lacks sufficient access to #{group.name}", 403) end end + def validate_access_level?(level) Gitlab::Access.options_with_owner.values.include? level.to_i end @@ -64,6 +66,18 @@ module API present group, with: Entities::GroupDetail end + # Remove group + # + # Parameters: + # id (required) - The ID of a group + # Example Request: + # DELETE /groups/:id + delete ":id" do + group = find_group(params[:id]) + authorize! :manage_group, group + group.destroy + end + # Transfer a project to the Group namespace # # Parameters: @@ -132,7 +146,6 @@ module API member.destroy end end - end end end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 4f189f3519..edc662eaaa 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -11,12 +11,8 @@ module API # If the sudo is the current user do nothing if (identifier && !(@current_user.id == identifier || @current_user.username == identifier)) render_api_error!('403 Forbidden: Must be admin to use sudo', 403) unless @current_user.is_admin? - begin - @current_user = User.by_username_or_id(identifier) - rescue => ex - not_found!("No user id or username for: #{identifier}") - end - not_found!("No user id or username for: #{identifier}") if current_user.nil? + @current_user = User.by_username_or_id(identifier) + not_found!("No user id or username for: #{identifier}") if @current_user.nil? end @current_user end @@ -31,6 +27,15 @@ module API end end + def set_current_user_for_thread + Thread.current[:current_user] = current_user + begin + yield + ensure + Thread.current[:current_user] = nil + end + end + def user_project @project ||= find_project(params[:id]) @project || not_found! @@ -64,6 +69,10 @@ module API end end + def authorize_admin_project + authorize! :admin_project, user_project + end + def can?(object, action, subject) abilities.allowed?(object, action, subject) end @@ -82,7 +91,7 @@ module API def attributes_for_keys(keys) attrs = {} keys.each do |key| - attrs[key] = params[key] if params[key].present? + attrs[key] = params[key] if params[key].present? or (params.has_key?(key) and params[key] == false) end attrs end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 79f8eb3a54..ed6b50c3a6 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -35,6 +35,7 @@ module API user = key.user return false if user.blocked? + return false if user.ldap_user? && Gitlab::LDAP::User.blocked?(user.extern_uid) action = case git_cmd when *DOWNLOAD_COMMANDS diff --git a/lib/api/issues.rb b/lib/api/issues.rb index a15203d156..3d15c35b8c 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -2,7 +2,6 @@ module API # Issues API class Issues < Grape::API before { authenticate! } - before { Thread.current[:current_user] = current_user } resource :issues do # Get currently authenticated user's issues @@ -49,15 +48,17 @@ module API # Example Request: # POST /projects/:id/issues post ":id/issues" do - required_attributes! [:title] - attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id] - attrs[:label_list] = params[:labels] if params[:labels].present? - @issue = user_project.issues.new attrs - @issue.author = current_user - if @issue.save - present @issue, with: Entities::Issue - else - not_found! + set_current_user_for_thread do + required_attributes! [:title] + attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id] + attrs[:label_list] = params[:labels] if params[:labels].present? + @issue = user_project.issues.new attrs + @issue.author = current_user + if @issue.save + present @issue, with: Entities::Issue + else + not_found! + end end end @@ -75,16 +76,18 @@ module API # Example Request: # PUT /projects/:id/issues/:issue_id put ":id/issues/:issue_id" do - @issue = user_project.issues.find(params[:issue_id]) - authorize! :modify_issue, @issue + set_current_user_for_thread do + @issue = user_project.issues.find(params[:issue_id]) + authorize! :modify_issue, @issue - attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event] - attrs[:label_list] = params[:labels] if params[:labels].present? + attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event] + attrs[:label_list] = params[:labels] if params[:labels].present? - if @issue.update_attributes attrs - present @issue, with: Entities::Issue - else - not_found! + if @issue.update_attributes attrs + present @issue, with: Entities::Issue + else + not_found! + end end end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index d690f1d07e..3f4bec895b 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -2,7 +2,6 @@ module API # MergeRequest API class MergeRequests < Grape::API before { authenticate! } - before { Thread.current[:current_user] = current_user } resource :projects do helpers do @@ -70,28 +69,30 @@ module API # POST /projects/:id/merge_requests # post ":id/merge_requests" do - authorize! :write_merge_request, user_project - required_attributes! [:source_branch, :target_branch, :title] - attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id] - merge_request = user_project.merge_requests.new(attrs) - merge_request.author = current_user - merge_request.source_project = user_project - target_project_id = attrs[:target_project_id] - if not_fork?(target_project_id, user_project) - merge_request.target_project = user_project - else - if target_matches_fork(target_project_id,user_project) - merge_request.target_project = Project.find_by_id(attrs[:target_project_id]) + set_current_user_for_thread do + authorize! :write_merge_request, user_project + required_attributes! [:source_branch, :target_branch, :title] + attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id] + merge_request = user_project.merge_requests.new(attrs) + merge_request.author = current_user + merge_request.source_project = user_project + target_project_id = attrs[:target_project_id] + if not_fork?(target_project_id, user_project) + merge_request.target_project = user_project else - render_api_error!('(Bad Request) Specified target project that is not the source project, or the source fork of the project.', 400) + if target_matches_fork(target_project_id,user_project) + merge_request.target_project = Project.find_by_id(attrs[:target_project_id]) + else + render_api_error!('(Bad Request) Specified target project that is not the source project, or the source fork of the project.', 400) + end end - end - if merge_request.save - merge_request.reload_code - present merge_request, with: Entities::MergeRequest - else - handle_merge_request_errors! merge_request.errors + if merge_request.save + merge_request.reload_code + present merge_request, with: Entities::MergeRequest + else + handle_merge_request_errors! merge_request.errors + end end end @@ -109,17 +110,19 @@ module API # PUT /projects/:id/merge_request/:merge_request_id # put ":id/merge_request/:merge_request_id" do - attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event] - merge_request = user_project.merge_requests.find(params[:merge_request_id]) + set_current_user_for_thread do + attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event] + merge_request = user_project.merge_requests.find(params[:merge_request_id]) - authorize! :modify_merge_request, merge_request + authorize! :modify_merge_request, merge_request - if merge_request.update_attributes attrs - merge_request.reload_code - merge_request.mark_as_unchecked - present merge_request, with: Entities::MergeRequest - else - handle_merge_request_errors! merge_request.errors + if merge_request.update_attributes attrs + merge_request.reload_code + merge_request.mark_as_unchecked + present merge_request, with: Entities::MergeRequest + else + handle_merge_request_errors! merge_request.errors + end end end @@ -133,16 +136,18 @@ module API # POST /projects/:id/merge_request/:merge_request_id/comments # post ":id/merge_request/:merge_request_id/comments" do - required_attributes! [:note] + set_current_user_for_thread do + required_attributes! [:note] - merge_request = user_project.merge_requests.find(params[:merge_request_id]) - note = merge_request.notes.new(note: params[:note], project_id: user_project.id) - note.author = current_user + merge_request = user_project.merge_requests.find(params[:merge_request_id]) + note = merge_request.notes.new(note: params[:note], project_id: user_project.id) + note.author = current_user - if note.save - present note, with: Entities::MRNote - else - not_found! + if note.save + present note, with: Entities::MRNote + else + not_found! + end end end diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index aee12e7dc4..f7e63b2309 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -40,15 +40,17 @@ module API # Example Request: # POST /projects/:id/milestones post ":id/milestones" do - authorize! :admin_milestone, user_project - required_attributes! [:title] + set_current_user_for_thread do + authorize! :admin_milestone, user_project + required_attributes! [:title] - attrs = attributes_for_keys [:title, :description, :due_date] - @milestone = user_project.milestones.new attrs - if @milestone.save - present @milestone, with: Entities::Milestone - else - not_found! + attrs = attributes_for_keys [:title, :description, :due_date] + @milestone = user_project.milestones.new attrs + if @milestone.save + present @milestone, with: Entities::Milestone + else + not_found! + end end end @@ -64,14 +66,16 @@ module API # Example Request: # PUT /projects/:id/milestones/:milestone_id put ":id/milestones/:milestone_id" do - authorize! :admin_milestone, user_project + set_current_user_for_thread do + authorize! :admin_milestone, user_project - @milestone = user_project.milestones.find(params[:milestone_id]) - attrs = attributes_for_keys [:title, :description, :due_date, :state_event] - if @milestone.update_attributes attrs - present @milestone, with: Entities::Milestone - else - not_found! + @milestone = user_project.milestones.find(params[:milestone_id]) + attrs = attributes_for_keys [:title, :description, :due_date, :state_event] + if @milestone.update_attributes attrs + present @milestone, with: Entities::Milestone + else + not_found! + end end end end diff --git a/lib/api/notes.rb b/lib/api/notes.rb index cb2bc76447..f21907b1ff 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -41,17 +41,19 @@ module API # Example Request: # POST /projects/:id/notes post ":id/notes" do - required_attributes! [:body] + set_current_user_for_thread do + required_attributes! [:body] - @note = user_project.notes.new(note: params[:body]) - @note.author = current_user + @note = user_project.notes.new(note: params[:body]) + @note.author = current_user - if @note.save - present @note, with: Entities::Note - else - # :note is exposed as :body, but :note is set on error - bad_request!(:note) if @note.errors[:note].any? - not_found! + if @note.save + present @note, with: Entities::Note + else + # :note is exposed as :body, but :note is set on error + bad_request!(:note) if @note.errors[:note].any? + not_found! + end end end @@ -97,17 +99,19 @@ module API # POST /projects/:id/issues/:noteable_id/notes # POST /projects/:id/snippets/:noteable_id/notes post ":id/#{noteables_str}/:#{noteable_id_str}/notes" do - required_attributes! [:body] + set_current_user_for_thread do + required_attributes! [:body] - @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"]) - @note = @noteable.notes.new(note: params[:body]) - @note.author = current_user - @note.project = user_project + @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"]) + @note = @noteable.notes.new(note: params[:body]) + @note.author = current_user + @note.project = user_project - if @note.save - present @note, with: Entities::Note - else - not_found! + if @note.save + present @note, with: Entities::Note + else + not_found! + end end end end diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index 2850125679..738974955f 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -2,6 +2,7 @@ module API # Projects API class ProjectHooks < Grape::API before { authenticate! } + before { authorize_admin_project } resource :projects do helpers do @@ -20,7 +21,6 @@ module API # Example Request: # GET /projects/:id/hooks get ":id/hooks" do - authorize! :admin_project, user_project @hooks = paginate user_project.hooks present @hooks, with: Entities::Hook end @@ -33,7 +33,6 @@ module API # Example Request: # GET /projects/:id/hooks/:hook_id get ":id/hooks/:hook_id" do - authorize! :admin_project, user_project @hook = user_project.hooks.find(params[:hook_id]) present @hook, with: Entities::Hook end @@ -47,7 +46,6 @@ module API # Example Request: # POST /projects/:id/hooks post ":id/hooks" do - authorize! :admin_project, user_project required_attributes! [:url] @hook = user_project.hooks.new({"url" => params[:url]}) @@ -71,7 +69,6 @@ module API # PUT /projects/:id/hooks/:hook_id put ":id/hooks/:hook_id" do @hook = user_project.hooks.find(params[:hook_id]) - authorize! :admin_project, user_project required_attributes! [:url] attrs = attributes_for_keys [:url] @@ -93,7 +90,6 @@ module API # Example Request: # DELETE /projects/:id/hooks/:hook_id delete ":id/hooks/:hook_id" do - authorize! :admin_project, user_project required_attributes! [:hook_id] begin diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 4b9dac9107..14cafe1f95 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -73,16 +73,16 @@ module API post do required_attributes! [:name] attrs = attributes_for_keys [:name, - :path, - :description, - :default_branch, - :issues_enabled, - :wall_enabled, - :merge_requests_enabled, - :wiki_enabled, - :snippets_enabled, - :namespace_id, - :public] + :path, + :description, + :default_branch, + :issues_enabled, + :wall_enabled, + :merge_requests_enabled, + :wiki_enabled, + :snippets_enabled, + :namespace_id, + :public] @project = ::Projects::CreateContext.new(current_user, attrs).execute if @project.saved? present @project, with: Entities::Project @@ -113,14 +113,14 @@ module API authenticated_as_admin! user = User.find(params[:user_id]) attrs = attributes_for_keys [:name, - :description, - :default_branch, - :issues_enabled, - :wall_enabled, - :merge_requests_enabled, - :wiki_enabled, - :snippets_enabled, - :public] + :description, + :default_branch, + :issues_enabled, + :wall_enabled, + :merge_requests_enabled, + :wiki_enabled, + :snippets_enabled, + :public] @project = ::Projects::CreateContext.new(user, attrs).execute if @project.saved? present @project, with: Entities::Project @@ -129,6 +129,16 @@ module API end end + # Remove project + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # DELETE /projects/:id + delete ":id" do + authorize! :remove_project, user_project + user_project.destroy + end # Mark this project as forked from another # @@ -165,7 +175,6 @@ module API end end - # Get a project team members # # Parameters: @@ -285,6 +294,20 @@ module API render_api_error!(link.errors.full_messages.first, 409) end end + + # search for projects current_user has access to + # + # Parameters: + # query (required) - A string contained in the project name + # per_page (optional) - number of projects to return per page + # page (optional) - the page to retrieve + # Example Request: + # GET /projects/search/:query + get "/search/:query" do + ids = current_user.authorized_projects.map(&:id) + projects = Project.where("(id in (?) OR public = true) AND (name LIKE (?))", ids, "%#{params[:query]}%") + present paginate(projects), with: Entities::Project + end end end end diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index fef32d3a2f..c9422fdb16 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -2,6 +2,7 @@ module API # Projects API class Repositories < Grape::API before { authenticate! } + before { authorize! :download_code, user_project } resource :projects do helpers do @@ -44,13 +45,12 @@ module API # Example Request: # PUT /projects/:id/repository/branches/:branch/protect put ":id/repository/branches/:branch/protect" do - @branch = user_project.repo.heads.find { |item| item.name == params[:branch] } - not_found! unless @branch - protected = user_project.protected_branches.find_by_name(@branch.name) + authorize_admin_project - unless protected - user_project.protected_branches.create(name: @branch.name) - end + @branch = user_project.repository.find_branch(params[:branch]) + not_found! unless @branch + protected_branch = user_project.protected_branches.find_by_name(@branch.name) + user_project.protected_branches.create(name: @branch.name) unless protected_branch present @branch, with: Entities::RepoObject, project: user_project end @@ -63,13 +63,12 @@ module API # Example Request: # PUT /projects/:id/repository/branches/:branch/unprotect put ":id/repository/branches/:branch/unprotect" do - @branch = user_project.repo.heads.find { |item| item.name == params[:branch] } - not_found! unless @branch - protected = user_project.protected_branches.find_by_name(@branch.name) + authorize_admin_project - if protected - protected.destroy - end + @branch = user_project.repository.find_branch(params[:branch]) + not_found! unless @branch + protected_branch = user_project.protected_branches.find_by_name(@branch.name) + protected_branch.destroy if protected_branch present @branch, with: Entities::RepoObject, project: user_project end @@ -92,8 +91,6 @@ module API # Example Request: # GET /projects/:id/repository/commits get ":id/repository/commits" do - authorize! :download_code, user_project - page = (params[:page] || 0).to_i per_page = (params[:per_page] || 20).to_i ref = params[:ref_name] || user_project.try(:default_branch) || 'master' @@ -110,7 +107,6 @@ module API # Example Request: # GET /projects/:id/repository/commits/:sha get ":id/repository/commits/:sha" do - authorize! :download_code, user_project sha = params[:sha] commit = user_project.repository.commit(sha) not_found! "Commit" unless commit @@ -125,7 +121,6 @@ module API # Example Request: # GET /projects/:id/repository/commits/:sha/diff get ":id/repository/commits/:sha/diff" do - authorize! :download_code, user_project sha = params[:sha] result = CommitLoadContext.new(user_project, current_user, {id: sha}).execute not_found! "Commit" unless result[:commit] @@ -140,18 +135,16 @@ module API # Example Request: # GET /projects/:id/repository/tree get ":id/repository/tree" do - authorize! :download_code, user_project - ref = params[:ref_name] || user_project.try(:default_branch) || 'master' path = params[:path] || nil commit = user_project.repository.commit(ref) - tree = Tree.new(user_project.repository, commit.id, ref, path) + tree = Tree.new(user_project.repository, commit.id, path) trees = [] %w(trees blobs submodules).each do |type| - trees += tree.send(type).map { |t| { name: t.name, type: type.singularize, mode: t.mode, id: t.id } } + trees += tree.send(type).map { |t| {name: t.name, type: type.singularize, mode: t.mode, id: t.id} } end trees @@ -166,7 +159,6 @@ module API # Example Request: # GET /projects/:id/repository/blobs/:sha get [ ":id/repository/blobs/:sha", ":id/repository/commits/:sha/blob" ] do - authorize! :download_code, user_project required_attributes! [:filepath] ref = params[:sha] @@ -176,14 +168,42 @@ module API commit = repo.commit(ref) not_found! "Commit" unless commit - blob = Gitlab::Git::Blob.new(repo, commit.id, ref, params[:filepath]) - not_found! "File" unless blob.exists? + blob = Gitlab::Git::Blob.find(repo, commit.id, params[:filepath]) + not_found! "File" unless blob env['api.format'] = :txt content_type blob.mime_type present blob.data end + + # Get a an archive of the repository + # + # Parameters: + # id (required) - The ID of a project + # sha (optional) - the commit sha to download defaults to the tip of the default branch + # Example Request: + # GET /projects/:id/repository/archive + get ":id/repository/archive" do + authorize! :download_code, user_project + repo = user_project.repository + ref = params[:sha] + storage_path = Rails.root.join("tmp", "repositories") + + file_path = repo.archive_repo(ref, storage_path) + if file_path && File.exists?(file_path) + data = File.open(file_path, 'rb').read + + header "Content-Disposition:", " infile; filename=\"#{File.basename(file_path)}\"" + content_type 'application/x-gzip' + + env['api.format'] = :binary + + present data + else + not_found! + end + end end end end diff --git a/lib/api/users.rb b/lib/api/users.rb index 00dc2311ff..54d3aeecb7 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -40,13 +40,17 @@ module API # extern_uid - External authentication provider UID # provider - External provider # bio - Bio + # admin - User is admin - true or false (default) + # can_create_group - User can create groups - true or false # Example Request: # POST /users post do authenticated_as_admin! required_attributes! [:email, :password, :name, :username] - attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio] + attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin] user = User.build_user(attrs, as: :admin) + admin = attrs.delete(:admin) + user.admin = admin unless admin.nil? if user.save present user, with: Entities::User else @@ -67,16 +71,20 @@ module API # extern_uid - External authentication provider UID # provider - External provider # bio - Bio + # admin - User is admin - true or false (default) + # can_create_group - User can create groups - true or false # Example Request: # PUT /users/:id put ":id" do authenticated_as_admin! - attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio] + attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin] user = User.find(params[:id]) not_found!("User not found") unless user - if user.update_attributes(attrs) + admin = attrs.delete(:admin) + user.admin = admin unless admin.nil? + if user.update_attributes(attrs, as: :admin) present user, with: Entities::User else not_found! diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index c5e3d049fd..252201f11b 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -28,7 +28,9 @@ module Backup if File.exists?(path_to_repo(wiki)) print " * #{wiki.path_with_namespace} ... " - if system("cd #{path_to_repo(wiki)} > /dev/null 2>&1 && git bundle create #{path_to_bundle(wiki)} --all > /dev/null 2>&1") + if wiki.empty? + puts " [SKIPPED]".cyan + elsif system("cd #{path_to_repo(wiki)} > /dev/null 2>&1 && git bundle create #{path_to_bundle(wiki)} --all > /dev/null 2>&1") puts " [DONE]".green else puts " [FAILED]".red diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb index 53bc079296..6e7872dcd0 100644 --- a/lib/extracts_path.rb +++ b/lib/extracts_path.rb @@ -86,7 +86,6 @@ module ExtractsPath # - @ref - A string representing the ref (e.g., the branch, tag, or commit SHA) # - @path - A string representing the filesystem path # - @commit - A Commit representing the commit from the given ref - # - @tree - A Tree representing the tree at the given ref/path # # If the :id parameter appears to be requesting a specific response format, # that will be handled as well. @@ -107,15 +106,20 @@ module ExtractsPath else @commit = @repo.commit(@options[:extended_sha1]) end - @tree = Tree.new(@repo, @commit.id, @ref, @path) + + raise InvalidPathError unless @commit + @hex_path = Digest::SHA1.hexdigest(@path) @logs_path = logs_file_project_ref_path(@project, @ref, @path) - raise InvalidPathError unless @tree.exists? rescue RuntimeError, NoMethodError, InvalidPathError not_found! end + def tree + @tree ||= Tree.new(@repo, @commit.id, @path) + end + private def get_id diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb index 06c1e37063..4c5b63261a 100644 --- a/lib/gitlab/ldap/user.rb +++ b/lib/gitlab/ldap/user.rb @@ -26,7 +26,7 @@ module Gitlab # * When user already has account and need to link his LDAP account. # * LDAP uid changed for user with same email and we need to update his uid # - user = model.find_by_email(email) + user = find_user(email) if user user.update_attributes(extern_uid: uid, provider: provider) @@ -43,6 +43,19 @@ module Gitlab user end + def find_user(email) + user = model.find_by_email(email) + + # If no user found and allow_username_or_email_login is true + # we look for user by extracting part of his email + if !user && email && ldap_conf['allow_username_or_email_login'] + uname = email.partition('@').first + user = model.find_by_username(uname) + end + + user + end + def authenticate(login, password) # Check user against LDAP backend if user is not authenticated # Only check with valid login and password to prevent anonymous bind results @@ -58,6 +71,16 @@ module Gitlab find_by_uid(ldap_user.dn) if ldap_user end + # Check LDAP user existance by dn. User in git over ssh check + # + # It covers 2 cases: + # * when ldap account was removed + # * when ldap account was deactivated by change of OU membership in 'dn' + def blocked?(dn) + ldap = OmniAuth::LDAP::Adaptor.new(ldap_conf) + ldap.connection.search(base: dn, size: 1).blank? + end + private def find_by_uid(uid) diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb index 1b32b99f4b..ea9badba2c 100644 --- a/lib/gitlab/oauth/user.rb +++ b/lib/gitlab/oauth/user.rb @@ -29,6 +29,7 @@ module Gitlab user = model.build_user(opts, as: :admin) user.save! + user.confirm! log.info "(OAuth) Creating user #{email} from login with extern_uid => #{uid}" if Gitlab.config.omniauth['block_auto_created_users'] && !ldap? diff --git a/lib/gitlab/satellite/merge_action.rb b/lib/gitlab/satellite/merge_action.rb index 156483be8d..d74d4194ff 100644 --- a/lib/gitlab/satellite/merge_action.rb +++ b/lib/gitlab/satellite/merge_action.rb @@ -146,10 +146,8 @@ module Gitlab repo.remote_fetch('source') repo.git.checkout(default_options({b: true}), merge_request.target_branch, "origin/#{merge_request.target_branch}") else - # We can't trust the input here being branch names, we can't always check it out because it could be a relative ref i.e. HEAD~3 - # we could actually remove the if true, because it should never ever happen (as long as the satellite has been prepared) repo.git.checkout(default_options, "#{merge_request.source_branch}") - repo.git.checkout(default_options, "#{merge_request.target_branch}") + repo.git.checkout(default_options({t: true}), "origin/#{merge_request.target_branch}") end rescue Grit::Git::CommandFailed => ex handle_exception(ex) diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb index d9c2d3b626..b84c005524 100644 --- a/lib/redcarpet/render/gitlab_html.rb +++ b/lib/redcarpet/render/gitlab_html.rb @@ -6,6 +6,8 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML def initialize(template, options = {}) @template = template @project = @template.instance_variable_get("@project") + @ref = @template.instance_variable_get("@ref") + @request_path = @template.instance_variable_get("@path") super options end @@ -32,7 +34,19 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML h.link_to_gfm(content, link, title: title) end + def preprocess(full_document) + if @project + h.create_relative_links(full_document, @project.path_with_namespace, @ref, @request_path, is_wiki?) + else + full_document + end + end + def postprocess(full_document) h.gfm(full_document) end + + def is_wiki? + @template.instance_variable_get("@wiki") + end end diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index dbdf7db114..26ca50c22f 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -22,7 +22,6 @@ RAILS_ENV="production" # /bin/sh variables such as PATH, EDITOR or SHELL. app_root="/home/git/gitlab" app_user="git" -unicorn_conf="$app_root/config/unicorn.rb" pid_path="$app_root/tmp/pids" socket_path="$app_root/tmp/sockets" web_server_pid_path="$pid_path/unicorn.pid" @@ -129,7 +128,7 @@ start() { # Remove old socket if it exists rm -f "$socket_path"/gitlab.socket 2>/dev/null # Start the webserver - bundle exec unicorn_rails -D -c "$unicorn_conf" -E "$RAILS_ENV" + RAILS_ENV=$RAILS_ENV script/web start fi # If sidekiq is already running, don't start it again. @@ -137,7 +136,7 @@ start() { echo "The Sidekiq job dispatcher is already running with pid $spid, not restarting" else echo "Starting the GitLab Sidekiq event dispatcher..." - RAILS_ENV=$RAILS_ENV bundle exec rake sidekiq:start + RAILS_ENV=$RAILS_ENV script/background_jobs start # We are sleeping a bit here because sidekiq is slow at writing it's pid sleep 2 fi @@ -151,7 +150,7 @@ stop() { exit_if_not_running # If the Unicorn web server is running, tell it to stop; if [ "$web_status" = "0" ]; then - kill -QUIT "$wpid" & + RAILS_ENV=$RAILS_ENV script/web stop echo "Stopping the GitLab Unicorn web server..." stopping=true else @@ -160,7 +159,7 @@ stop() { # And do the same thing for the Sidekiq. if [ "$sidekiq_status" = "0" ]; then printf "Stopping Sidekiq job dispatcher." - RAILS_ENV=$RAILS_ENV bundle exec rake sidekiq:stop & + RAILS_ENV=$RAILS_ENV script/background_jobs stop stopping=true else echo "The Sidekiq was not running, must have run out of breath." @@ -204,7 +203,7 @@ status() { printf "The GitLab Sidekiq job dispatcher is \033[31mnot running\033[0m.\n" fi if [ "$web_status" = "0" -a "$sidekiq_status" = "0" ]; then - printf "GitLab and all it's components are \033[32mup and running\033[0m.\n" + printf "GitLab and all its components are \033[32mup and running\033[0m.\n" fi } @@ -215,12 +214,10 @@ reload(){ exit 1 fi printf "Reloading GitLab Unicorn configuration... " - kill -USR2 "$wpid" + RAILS_ENV=$RAILS_ENV script/web reload echo "Done." echo "Restarting GitLab Sidekiq since it isn't capable of reloading its config..." - RAILS_ENV=$RAILS_ENV bundle exec rake sidekiq:stop - echo "Starting Sidekiq..." - RAILS_ENV=$RAILS_ENV bundle exec rake sidekiq:start + RAILS_ENV=$RAILS_ENV script/background_jobs restart # Waiting 2 seconds for sidekiq to write it. sleep 2 status diff --git a/lib/support/logrotate/gitlab b/lib/support/logrotate/gitlab new file mode 100644 index 0000000000..df9398d079 --- /dev/null +++ b/lib/support/logrotate/gitlab @@ -0,0 +1,22 @@ +# GitLab logrotate settings +# based on: http://stackoverflow.com/a/4883967 + +/home/git/gitlab/log/*.log { + weekly + missingok + rotate 52 + compress + delaycompress + notifempty + copytruncate +} + +/home/git/gitlab-shell/gitlab-shell.log { + weekly + missingok + rotate 52 + compress + delaycompress + notifempty + copytruncate +} diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index c445c0fbdd..e01f3b23d0 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -289,7 +289,6 @@ namespace :gitlab do ######################## def check_gitlab_git_config - gitlab_user = Gitlab.config.gitlab.user print "Git configured for #{gitlab_user} user? ... " options = { @@ -377,6 +376,7 @@ namespace :gitlab do check_repo_base_permissions check_update_hook_is_up_to_date check_repos_update_hooks_is_link + check_gitlab_shell_self_test finished_checking "GitLab Shell" end @@ -392,14 +392,20 @@ namespace :gitlab do hook_file = "update" gitlab_shell_hooks_path = Gitlab.config.gitlab_shell.hooks_path gitlab_shell_hook_file = File.join(gitlab_shell_hooks_path, hook_file) - gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user - unless File.exists?(gitlab_shell_hook_file) - puts "can't check because of previous errors".magenta - return + if File.exists?(gitlab_shell_hook_file) + puts "yes".green + else + puts "no".red + puts "Could not find #{gitlab_shell_hook_file}" + try_fixing_it( + 'Check the hooks_path in config/gitlab.yml', + 'Check your gitlab-shell installation' + ) + for_more_information( + see_installation_guide_section "GitLab Shell" + ) end - - puts "yes".green end def check_repo_base_exists @@ -553,6 +559,23 @@ namespace :gitlab do end end + def check_gitlab_shell_self_test + gitlab_shell_repo_base = File.expand_path('gitlab-shell', gitlab_shell_user_home) + check_cmd = File.expand_path('bin/check', gitlab_shell_repo_base) + puts "Running #{check_cmd}" + if system(check_cmd, chdir: gitlab_shell_repo_base) + puts 'gitlab-shell self-check successful'.green + else + puts 'gitlab-shell self-check failed'.red + try_fixing_it( + 'Make sure GitLab is running;', + 'Check the gitlab-shell configuration file:', + sudo_gitlab("editor #{File.expand_path('config.yml', gitlab_shell_repo_base)}") + ) + fix_and_rerun + end + end + def check_projects_have_namespace print "projects have namespace: ... " @@ -608,6 +631,7 @@ namespace :gitlab do start_checking "Sidekiq" check_sidekiq_running + only_one_sidekiq_running finished_checking "Sidekiq" end @@ -619,7 +643,7 @@ namespace :gitlab do def check_sidekiq_running print "Running? ... " - if run_and_match("ps aux | grep -i sidekiq", /sidekiq \d+\.\d+\.\d+.+$/) + if sidekiq_process_count > 0 puts "yes".green else puts "no".red @@ -633,6 +657,29 @@ namespace :gitlab do fix_and_rerun end end + + def only_one_sidekiq_running + process_count = sidekiq_process_count + return if process_count.zero? + + print 'Number of Sidekiq processes ... ' + if process_count == 1 + puts '1'.green + else + puts "#{process_count}".red + try_fixing_it( + 'sudo service gitlab stop', + "sudo pkill -u #{gitlab_user} -f sidekiq", + "sleep 10 && sudo pkill -9 -u #{gitlab_user} -f sidekiq", + 'sudo service gitlab start' + ) + fix_and_rerun + end + end + + def sidekiq_process_count + `ps ux`.scan(/sidekiq \d+\.\d+\.\d+/).count + end end @@ -667,10 +714,13 @@ namespace :gitlab do end def sudo_gitlab(command) - gitlab_user = Gitlab.config.gitlab.user "sudo -u #{gitlab_user} -H #{command}" end + def gitlab_user + Gitlab.config.gitlab.user + end + def start_checking(component) puts "Checking #{component.yellow} ..." puts "" diff --git a/lib/tasks/gitlab/test.rake b/lib/tasks/gitlab/test.rake index 03b3fc5ea2..011748c971 100644 --- a/lib/tasks/gitlab/test.rake +++ b/lib/tasks/gitlab/test.rake @@ -1,4 +1,18 @@ namespace :gitlab do - desc "GITLAB | Run both spinach and rspec" - task test: ['spinach', 'spec'] + desc "GITLAB | Run all tests" + task :test do + cmds = [ + "rake db:setup", + "rake db:seed_fu", + "rake spinach", + "rake spec", + "rake jasmine:ci" + ] + + cmds.each do |cmd| + system(cmd + " RAILS_ENV=test") + + raise "#{cmd} failed!" unless $?.exitstatus.zero? + end + end end diff --git a/lib/tasks/gitlab/web_hook.rake b/lib/tasks/gitlab/web_hook.rake new file mode 100644 index 0000000000..f9f586db93 --- /dev/null +++ b/lib/tasks/gitlab/web_hook.rake @@ -0,0 +1,65 @@ +namespace :gitlab do + namespace :web_hook do + desc "GITLAB | Adds a web hook to the projects" + task :add => :environment do + web_hook_url = ENV['URL'] + namespace_path = ENV['NAMESPACE'] + + projects = find_projects(namespace_path) + + puts "Adding web hook '#{web_hook_url}' to:" + projects.find_each(batch_size: 1000) do |project| + print "- #{project.name} ... " + web_hook = project.hooks.new(url: web_hook_url) + if web_hook.save + puts "added".green + else + print "failed".red + puts " [#{web_hook.errors.full_messages.to_sentence}]" + end + end + end + + desc "GITLAB | Remove a web hook from the projects" + task :rm => :environment do + web_hook_url = ENV['URL'] + namespace_path = ENV['NAMESPACE'] + + projects = find_projects(namespace_path) + projects_ids = projects.pluck(:id) + + puts "Removing web hooks with the url '#{web_hook_url}' ... " + count = WebHook.where(url: web_hook_url, project_id: projects_ids, type: 'ProjectHook').delete_all + puts "#{count} web hooks were removed." + end + + desc "GITLAB | List web hooks" + task :list => :environment do + namespace_path = ENV['NAMESPACE'] + + projects = find_projects(namespace_path) + web_hooks = projects.all.map(&:hooks).flatten + web_hooks.each do |hook| + puts "#{hook.project.name.truncate(20).ljust(20)} -> #{hook.url}" + end + + puts "\n#{web_hooks.size} web hooks found." + end + end + + def find_projects(namespace_path) + if namespace_path.blank? + Project + elsif namespace_path == '/' + Project.where(namespace_id: nil) + else + namespace = Namespace.where(path: namespace_path).first + if namespace + Project.where(namespace_id: namespace.id) + else + puts "Namespace not found: #{namespace_path}".red + exit 2 + end + end + end +end diff --git a/lib/tasks/sidekiq.rake b/lib/tasks/sidekiq.rake index d0e9dfe46a..23d41f8ed2 100644 --- a/lib/tasks/sidekiq.rake +++ b/lib/tasks/sidekiq.rake @@ -1,20 +1,19 @@ namespace :sidekiq do desc "GITLAB | Stop sidekiq" task :stop do - system "bundle exec sidekiqctl stop #{pidfile}" + system "script/background_jobs stop" end - desc "GITLAB | Start sidekiq" - task :start do - system "nohup bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1 &" + desc "GITLAB | Start sidekiq" do + system "script/background_jobs start" + end + + desc 'GitLab | Restart sidekiq' do + system "script/background_jobs restart" end desc "GITLAB | Start sidekiq with launchd on Mac OS X" task :launchd do - system "bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1" - end - - def pidfile - Rails.root.join("tmp", "pids", "sidekiq.pid") + system "script/background_jobs start_no_deamonize" end end diff --git a/lib/tasks/travis.rake b/lib/tasks/travis.rake deleted file mode 100644 index bc1b8aadbc..0000000000 --- a/lib/tasks/travis.rake +++ /dev/null @@ -1,5 +0,0 @@ -desc "Travis run tests" -task travis: [ - :spinach, - :spec -] diff --git a/script/background_jobs b/script/background_jobs new file mode 100755 index 0000000000..623e26a283 --- /dev/null +++ b/script/background_jobs @@ -0,0 +1,56 @@ +#!/bin/bash + +cd $(dirname $0)/.. +app_root=$(pwd) +sidekiq_pidfile="$app_root/tmp/pids/sidekiq.pid" +sidekiq_logfile="$app_root/log/sidekiq.log" +gitlab_user=$(ls -l config.ru | awk '{print $3}') + +function stop +{ + bundle exec sidekiqctl stop $sidekiq_pidfile >> $sidekiq_logfile 2>&1 +} + +function killall +{ + pkill -u $gitlab_user -f sidekiq +} + +function restart +{ + if [ -f $sidekiq_pidfile ]; then + stop + fi + killall + start_sidekiq -d -L $sidekiq_logfile +} + +function start_no_deamonize +{ + start_sidekiq +} + +function start_sidekiq +{ + bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1 +} + +case "$1" in + stop) + stop + ;; + start) + restart + ;; + start_no_deamonize) + start_no_deamonize + ;; + restart) + restart + ;; + killall) + killall + ;; + *) + echo "Usage: RAILS_ENV=your_env $0 {stop|start|start_no_deamonize|restart|killall}" +esac diff --git a/script/web b/script/web new file mode 100755 index 0000000000..5464ed040a --- /dev/null +++ b/script/web @@ -0,0 +1,49 @@ +#!/bin/bash + +cd $(dirname $0)/.. +app_root=$(pwd) + +unicorn_pidfile="$app_root/tmp/pids/unicorn.pid" +unicorn_config="$app_root/config/unicorn.rb" + +function get_unicorn_pid +{ + local pid=$(cat $unicorn_pidfile) + if [ -z $pid ] ; then + echo "Could not find a PID in $unicorn_pidfile" + exit 1 + fi + unicorn_pid=$pid +} + +function start +{ + bundle exec unicorn_rails -D -c $unicorn_config -E $RAILS_ENV +} + +function stop +{ + get_unicorn_pid + kill -QUIT $unicorn_pid +} + +function reload +{ + get_unicorn_pid + kill -USR2 $unicorn_pid +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + reload) + reload + ;; + *) + echo "Usage: RAILS_ENV=your_env $0 {start|stop|reload}" + ;; +esac diff --git a/spec/contexts/projects_create_context_spec.rb b/spec/contexts/projects_create_context_spec.rb index 7b5fa530a8..8b2a49dbee 100644 --- a/spec/contexts/projects_create_context_spec.rb +++ b/spec/contexts/projects_create_context_spec.rb @@ -25,13 +25,15 @@ describe Projects::CreateContext do context 'group namespace' do before do - @group = create :group, owner: @user + @group = create :group + @group.add_owner(@user) + @opts.merge!(namespace_id: @group.id) @project = create_project(@user, @opts) end it { @project.should be_valid } - it { @project.owner.should == @user } + it { @project.owner.should == @group } it { @project.namespace.should == @group } end diff --git a/spec/factories.rb b/spec/factories.rb index 4c5e687ac3..624cb0f765 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -17,6 +17,8 @@ FactoryGirl.define do sequence(:username) { |n| "#{Faker::Internet.user_name}#{n}" } password "123456" password_confirmation { password } + confirmed_at { Time.now } + confirmation_token { nil } trait :admin do admin true @@ -71,7 +73,6 @@ FactoryGirl.define do factory :group do sequence(:name) { |n| "group#{n}" } path { name.downcase.gsub(/\s/, '_') } - owner type 'Group' end diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 6fff59f036..8cb984946b 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -24,7 +24,7 @@ describe "Issues" do end it "should open new issue popup" do - page.should have_content("Issue ##{issue.id}") + page.should have_content("Issue ##{issue.iid}") end describe "fill in" do @@ -45,40 +45,6 @@ describe "Issues" do end end - describe "Search issue", js: true do - before do - ['foobar', 'foobar2', 'gitlab'].each do |title| - create(:issue, - author: @user, - assignee: @user, - project: project, - title: title) - end - end - - it "should be able to search on different statuses" do - issue = Issue.first # with title 'foobar' - issue.close - - visit project_issues_path(project) - click_link 'Closed' - fill_in 'issue_search', with: 'foobar' - - page.should have_content 'foobar' - page.should_not have_content 'foobar2' - page.should_not have_content 'gitlab' - end - - it "should search for term and return the correct results" do - visit project_issues_path(project) - fill_in 'issue_search', with: 'foobar' - - page.should have_content 'foobar' - page.should have_content 'foobar2' - page.should_not have_content 'gitlab' - end - end - describe "Filter issue" do before do ['foobar', 'barbaz', 'gitlab'].each do |title| diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index ba580d9484..d29bed4dea 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -216,12 +216,6 @@ describe "On a merge request diff", js: true, focus: true do end end - it do - within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .js-temp-notes-holder") do - should have_no_css(".js-temp-notes-holder") - end - end - it 'should be added as discussion' do should have_content("Another comment on line 17") should have_css(".notes_holder") diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb index 80c9f5d7f1..b67ce3c67f 100644 --- a/spec/features/profile_spec.rb +++ b/spec/features/profile_spec.rb @@ -12,7 +12,7 @@ describe "Profile account page" do describe "when signup is enabled" do before do Gitlab.config.gitlab.stub(:signup_enabled).and_return(true) - visit account_profile_path + visit profile_account_path end it { page.should have_content("Remove account") } @@ -26,12 +26,12 @@ describe "Profile account page" do describe "when signup is disabled" do before do Gitlab.config.gitlab.stub(:signup_enabled).and_return(false) - visit account_profile_path + visit profile_account_path end it "should not have option to remove account" do page.should_not have_content("Remove account") - current_path.should == account_profile_path + current_path.should == profile_account_path end end end diff --git a/spec/features/security/dashboard_access_spec.rb b/spec/features/security/dashboard_access_spec.rb new file mode 100644 index 0000000000..adec5926c6 --- /dev/null +++ b/spec/features/security/dashboard_access_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe "Dashboard access" do + describe "GET /dashboard" do + subject { dashboard_path } + + it { should be_allowed_for :admin } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /dashboard/issues" do + subject { issues_dashboard_path } + + it { should be_allowed_for :admin } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /dashboard/merge_requests" do + subject { merge_requests_dashboard_path } + + it { should be_allowed_for :admin } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /dashboard/projects" do + subject { projects_dashboard_path } + + it { should be_allowed_for :admin } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /help" do + subject { help_path } + + it { should be_allowed_for :admin } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /projects/new" do + it { new_project_path.should be_allowed_for :admin } + it { new_project_path.should be_allowed_for :user } + it { new_project_path.should be_denied_for :visitor } + end + + describe "GET /groups/new" do + it { new_group_path.should be_allowed_for :admin } + it { new_group_path.should be_allowed_for :user } + it { new_group_path.should be_denied_for :visitor } + end +end diff --git a/spec/features/security/group_access_spec.rb b/spec/features/security/group_access_spec.rb new file mode 100644 index 0000000000..dea957962a --- /dev/null +++ b/spec/features/security/group_access_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper' + +describe "Group access" do + describe "GET /projects/new" do + it { new_group_path.should be_allowed_for :admin } + it { new_group_path.should be_allowed_for :user } + it { new_group_path.should be_denied_for :visitor } + end + + describe "Group" do + let(:group) { create(:group) } + + let(:owner) { create(:owner) } + let(:master) { create(:user) } + let(:reporter) { create(:user) } + let(:guest) { create(:user) } + + before do + group.add_user(owner, Gitlab::Access::OWNER) + group.add_user(master, Gitlab::Access::MASTER) + group.add_user(reporter, Gitlab::Access::REPORTER) + group.add_user(guest, Gitlab::Access::GUEST) + end + + describe "GET /groups/:path" do + subject { group_path(group) } + + it { should be_allowed_for owner } + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /groups/:path/issues" do + subject { issues_group_path(group) } + + it { should be_allowed_for owner } + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /groups/:path/merge_requests" do + subject { merge_requests_group_path(group) } + + it { should be_allowed_for owner } + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /groups/:path/members" do + subject { members_group_path(group) } + + it { should be_allowed_for owner } + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /groups/:path/edit" do + subject { edit_group_path(group) } + + it { should be_allowed_for owner } + it { should be_denied_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + end +end diff --git a/spec/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb index 52130b3f8c..078c257538 100644 --- a/spec/features/security/profile_access_spec.rb +++ b/spec/features/security/profile_access_spec.rb @@ -29,7 +29,7 @@ describe "Users Security" do end describe "GET /profile/account" do - subject { account_profile_path } + subject { profile_account_path } it { should be_allowed_for @u1 } it { should be_allowed_for :admin } @@ -45,5 +45,32 @@ describe "Users Security" do it { should be_allowed_for :user } it { should be_denied_for :visitor } end + + describe "GET /profile/history" do + subject { history_profile_path } + + it { should be_allowed_for @u1 } + it { should be_allowed_for :admin } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /profile/notifications" do + subject { profile_notifications_path } + + it { should be_allowed_for @u1 } + it { should be_allowed_for :admin } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /profile/groups" do + subject { profile_groups_path } + + it { should be_allowed_for @u1 } + it { should be_allowed_for :admin } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end end end diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb new file mode 100644 index 0000000000..7f3f8c50f0 --- /dev/null +++ b/spec/features/security/project/private_access_spec.rb @@ -0,0 +1,218 @@ +require 'spec_helper' + +describe "Private Project Access" do + let(:project) { create(:project_with_code) } + + let(:master) { create(:user) } + let(:guest) { create(:user) } + let(:reporter) { create(:user) } + + before do + # full access + project.team << [master, :master] + + # readonly + project.team << [reporter, :reporter] + end + + describe "GET /:project_path" do + subject { project_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/tree/master" do + subject { project_tree_path(project, project.repository.root_ref) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/commits/master" do + subject { project_commits_path(project, project.repository.root_ref, limit: 1) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/commit/:sha" do + subject { project_commit_path(project, project.repository.commit) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/compare" do + subject { project_compare_index_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/team" do + subject { project_team_index_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/wall" do + subject { project_wall_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/blob" do + before do + commit = project.repository.commit + path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name + @blob_path = project_blob_path(project, File.join(commit.id, path)) + end + + it { @blob_path.should be_allowed_for master } + it { @blob_path.should be_allowed_for reporter } + it { @blob_path.should be_allowed_for :admin } + it { @blob_path.should be_denied_for guest } + it { @blob_path.should be_denied_for :user } + it { @blob_path.should be_denied_for :visitor } + end + + describe "GET /:project_path/edit" do + subject { edit_project_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/deploy_keys" do + subject { project_deploy_keys_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/issues" do + subject { project_issues_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/snippets" do + subject { project_snippets_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/merge_requests" do + subject { project_merge_requests_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/branches/recent" do + subject { recent_project_branches_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/branches" do + subject { project_branches_path(project) } + + before do + # Speed increase + Project.any_instance.stub(:branches).and_return([]) + end + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/tags" do + subject { project_tags_path(project) } + + before do + # Speed increase + Project.any_instance.stub(:tags).and_return([]) + end + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/hooks" do + subject { project_hooks_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end +end diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb new file mode 100644 index 0000000000..267643fd8e --- /dev/null +++ b/spec/features/security/project/public_access_spec.rb @@ -0,0 +1,251 @@ +require 'spec_helper' + +describe "Public Project Access" do + let(:project) { create(:project_with_code) } + + let(:master) { create(:user) } + let(:guest) { create(:user) } + let(:reporter) { create(:user) } + + before do + # public project + project.public = true + project.save! + + # full access + project.team << [master, :master] + + # readonly + project.team << [reporter, :reporter] + + end + + describe "Project should be public" do + subject { project } + + its(:public?) { should be_true } + end + + describe "GET /:project_path" do + subject { project_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /:project_path/tree/master" do + subject { project_tree_path(project, project.repository.root_ref) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /:project_path/commits/master" do + subject { project_commits_path(project, project.repository.root_ref, limit: 1) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /:project_path/commit/:sha" do + subject { project_commit_path(project, project.repository.commit) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /:project_path/compare" do + subject { project_compare_index_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /:project_path/team" do + subject { project_team_index_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/wall" do + subject { project_wall_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /:project_path/blob" do + before do + commit = project.repository.commit + path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name + @blob_path = project_blob_path(project, File.join(commit.id, path)) + end + + it { @blob_path.should be_allowed_for master } + it { @blob_path.should be_allowed_for reporter } + it { @blob_path.should be_allowed_for :admin } + it { @blob_path.should be_allowed_for guest } + it { @blob_path.should be_allowed_for :user } + it { @blob_path.should be_allowed_for :visitor } + end + + describe "GET /:project_path/edit" do + subject { edit_project_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/deploy_keys" do + subject { project_deploy_keys_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/issues" do + subject { project_issues_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /:project_path/snippets" do + subject { project_snippets_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /:project_path/snippets/new" do + subject { new_project_snippet_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/merge_requests" do + subject { project_merge_requests_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /:project_path/merge_requests/new" do + subject { new_project_merge_request_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/branches/recent" do + subject { recent_project_branches_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /:project_path/branches" do + subject { project_branches_path(project) } + + before do + # Speed increase + Project.any_instance.stub(:branches).and_return([]) + end + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /:project_path/tags" do + subject { project_tags_path(project) } + + before do + # Speed increase + Project.any_instance.stub(:tags).and_return([]) + end + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /:project_path/hooks" do + subject { project_hooks_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end +end diff --git a/spec/features/security/project_access_spec.rb b/spec/features/security/project_access_spec.rb deleted file mode 100644 index d0964a947d..0000000000 --- a/spec/features/security/project_access_spec.rb +++ /dev/null @@ -1,474 +0,0 @@ -require 'spec_helper' - -describe "Application access" do - describe "GET /" do - it { root_path.should be_allowed_for :admin } - it { root_path.should be_allowed_for :user } - it { root_path.should be_denied_for :visitor } - end - - describe "GET /projects/new" do - it { new_project_path.should be_allowed_for :admin } - it { new_project_path.should be_allowed_for :user } - it { new_project_path.should be_denied_for :visitor } - end - - describe "Project" do - let(:project) { create(:project_with_code) } - - let(:master) { create(:user) } - let(:guest) { create(:user) } - let(:reporter) { create(:user) } - - before do - # full access - project.team << [master, :master] - - # readonly - project.team << [reporter, :reporter] - end - - describe "GET /project_code" do - subject { project_path(project) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/tree/master" do - subject { project_tree_path(project, project.repository.root_ref) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/commits/master" do - subject { project_commits_path(project, project.repository.root_ref, limit: 1) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/commit/:sha" do - subject { project_commit_path(project, project.repository.commit) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/compare" do - subject { project_compare_index_path(project) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/team" do - subject { project_team_index_path(project) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/wall" do - subject { project_wall_path(project) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/blob" do - before do - commit = project.repository.commit - path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name - @blob_path = project_blob_path(project, File.join(commit.id, path)) - end - - it { @blob_path.should be_allowed_for master } - it { @blob_path.should be_allowed_for reporter } - it { @blob_path.should be_allowed_for :admin } - it { @blob_path.should be_denied_for guest } - it { @blob_path.should be_denied_for :user } - it { @blob_path.should be_denied_for :visitor } - end - - describe "GET /project_code/edit" do - subject { edit_project_path(project) } - - it { should be_allowed_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/deploy_keys" do - subject { project_deploy_keys_path(project) } - - it { should be_allowed_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/issues" do - subject { project_issues_path(project) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/snippets" do - subject { project_snippets_path(project) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/merge_requests" do - subject { project_merge_requests_path(project) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/branches/recent" do - subject { recent_project_branches_path(project) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/branches" do - subject { project_branches_path(project) } - - before do - # Speed increase - Project.any_instance.stub(:branches).and_return([]) - end - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/tags" do - subject { project_tags_path(project) } - - before do - # Speed increase - Project.any_instance.stub(:tags).and_return([]) - end - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/hooks" do - subject { project_hooks_path(project) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } - end - end - - - describe "PublicProject" do - let(:project) { create(:project_with_code) } - - let(:master) { create(:user) } - let(:guest) { create(:user) } - let(:reporter) { create(:user) } - - let(:admin) { create(:user) } - - before do - # public project - project.public = true - project.save! - - # full access - project.team << [master, :master] - - # readonly - project.team << [reporter, :reporter] - - end - - describe "Project should be public" do - subject { project } - - its(:public?) { should be_true } - end - - describe "GET /project_code" do - subject { project_path(project) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/tree/master" do - subject { project_tree_path(project, project.repository.root_ref) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/commits/master" do - subject { project_commits_path(project, project.repository.root_ref, limit: 1) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/commit/:sha" do - subject { project_commit_path(project, project.repository.commit) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/compare" do - subject { project_compare_index_path(project) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/team" do - subject { project_team_index_path(project) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/wall" do - subject { project_wall_path(project) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/blob" do - before do - commit = project.repository.commit - path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name - @blob_path = project_blob_path(project, File.join(commit.id, path)) - end - - it { @blob_path.should be_allowed_for master } - it { @blob_path.should be_allowed_for reporter } - it { @blob_path.should be_allowed_for :admin } - it { @blob_path.should be_allowed_for guest } - it { @blob_path.should be_allowed_for :user } - it { @blob_path.should be_denied_for :visitor } - end - - describe "GET /project_code/edit" do - subject { edit_project_path(project) } - - it { should be_allowed_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/deploy_keys" do - subject { project_deploy_keys_path(project) } - - it { should be_allowed_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/issues" do - subject { project_issues_path(project) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/snippets" do - subject { project_snippets_path(project) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/snippets/new" do - subject { new_project_snippet_path(project) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/merge_requests" do - subject { project_merge_requests_path(project) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/branches/recent" do - subject { recent_project_branches_path(project) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/branches" do - subject { project_branches_path(project) } - - before do - # Speed increase - Project.any_instance.stub(:branches).and_return([]) - end - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/tags" do - subject { project_tags_path(project) } - - before do - # Speed increase - Project.any_instance.stub(:tags).and_return([]) - end - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /project_code/hooks" do - subject { project_hooks_path(project) } - - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end - end -end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 229f49659c..d63a2de880 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -38,6 +38,24 @@ describe ApplicationHelper do current_action?(:baz, :bar, :foo).should be_true end end + + describe "avatar_icon" do + avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png') + + it "should return an url for the avatar" do + user = create(:user) + user.avatar = File.open(avatar_file_path) + user.save! + avatar_icon(user.email).to_s.should == "/uploads/user/avatar/#{ user.id }/gitlab_logo.png" + end + + it "should call gravatar_icon when no avatar is present" do + user = create(:user) + user.save! + stub!(:gravatar_icon).and_return('gravatar_method_called') + avatar_icon(user.email).to_s.should == "gravatar_method_called" + end + end describe "gravatar_icon" do let(:user_email) { 'user@email.com' } @@ -105,4 +123,21 @@ describe ApplicationHelper do end end + describe "simple_sanitize" do + let(:a_tag) { 'Foo' } + + it "allows the a tag" do + simple_sanitize(a_tag).should == a_tag + end + + it "allows the span tag" do + input = 'Bar' + simple_sanitize(input).should == input + end + + it "disallows other tags" do + input = "#{a_tag}" + simple_sanitize(input).should == a_tag + end + end end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index d49247accc..a0bbc02642 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -406,6 +406,30 @@ describe GitlabMarkdownHelper do it "should generate absolute urls for emoji" do markdown(":smile:").should include("src=\"#{url_to_image("emoji/smile")}") end + + it "should handle relative urls for a file in master" do + actual = "[GitLab API doc](doc/api/README.md)\n" + expected = "

GitLab API doc

\n" + markdown(actual).should match(expected) + end + + it "should handle relative urls for a directory in master" do + actual = "[GitLab API doc](doc/api)\n" + expected = "

GitLab API doc

\n" + markdown(actual).should match(expected) + end + + it "should handle absolute urls" do + actual = "[GitLab](https://www.gitlab.com)\n" + expected = "

GitLab

\n" + markdown(actual).should match(expected) + end + + it "should handle wiki urls" do + actual = "[Link](test/link)\n" + expected = "

Link

\n" + markdown(actual).should match(expected) + end end describe "#render_wiki_content" do diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 3595af3243..9c95bc044f 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -47,6 +47,17 @@ describe IssuesHelper do url_for_project_issues.should eq "" end + + describe "when external tracker was enabled and then config removed" do + before do + @project = ext_project + Gitlab.config.stub(:issues_tracker).and_return(nil) + end + + it "should return path to internal tracker" do + url_for_project_issues.should match(polymorphic_path([@project])) + end + end end describe :url_for_issue do @@ -75,6 +86,17 @@ describe IssuesHelper do url_for_issue(issue.iid).should eq "" end + + describe "when external tracker was enabled and then config removed" do + before do + @project = ext_project + Gitlab.config.stub(:issues_tracker).and_return(nil) + end + + it "should return internal path" do + url_for_issue(issue.iid).should match(polymorphic_path([@project, issue])) + end + end end describe :url_for_new_issue do @@ -101,6 +123,17 @@ describe IssuesHelper do url_for_new_issue.should eq "" end + + describe "when external tracker was enabled and then config removed" do + before do + @project = ext_project + Gitlab.config.stub(:issues_tracker).and_return(nil) + end + + it "should return internal path" do + url_for_new_issue.should match(new_project_issue_path(@project)) + end + end end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb new file mode 100644 index 0000000000..62f88dd522 --- /dev/null +++ b/spec/helpers/projects_helper_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe ProjectsHelper do + describe '#project_issues_trackers' do + it "returns the correct issues trackers available" do + project_issues_trackers.should == + "\n" \ + "" + end + end +end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb new file mode 100644 index 0000000000..55b6b6b4da --- /dev/null +++ b/spec/helpers/search_helper_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' + +describe SearchHelper do + # Override simple_sanitize for our testing purposes + def simple_sanitize(str) + str + end + + describe 'search_autocomplete_source' do + context "with no current user" do + before { stub!(:current_user).and_return(nil) } + + it "it returns nil" do + search_autocomplete_source.should be_nil + end + end + + context "with a user" do + let(:user) { create(:user) } + let(:result) { JSON.parse(search_autocomplete_source) } + + before do + stub!(:current_user).and_return(user) + end + + it "includes Help sections" do + result.select { |h| h['label'] =~ /^help:/ }.length.should == 9 + end + + it "includes default sections" do + result.count { |h| h['label'] =~ /^(My|Admin)\s/ }.should == 4 + end + + it "includes the user's groups" do + create(:group).add_owner(user) + result.count { |h| h['label'] =~ /^group:/ }.should == 1 + end + + it "includes the user's projects" do + create(:project, namespace: create(:namespace, owner: user)) + result.count { |h| h['label'] =~ /^project:/ }.should == 1 + end + + context "with a current project" do + before { @project = create(:project_with_code) } + + it "includes project-specific sections" do + result.count { |h| h['label'] =~ /^#{@project.name_with_namespace} - / }.should == 11 + end + + it "uses @ref in urls if defined" do + @ref = "foo_bar" + result.count { |h| h['url'] == project_tree_path(@project, @ref) }.should == 1 + end + end + + context "with no current project" do + it "does not include project-specific sections" do + result.count { |h| h['label'] =~ /Files$/ }.should == 0 + end + end + end + end +end diff --git a/spec/javascripts/stat_graph_contributors_util_spec.js b/spec/javascripts/stat_graph_contributors_util_spec.js index 367f0af05f..2e52479ccb 100644 --- a/spec/javascripts/stat_graph_contributors_util_spec.js +++ b/spec/javascripts/stat_graph_contributors_util_spec.js @@ -3,10 +3,10 @@ describe("ContributorsStatGraphUtil", function () { describe("#parse_log", function () { it("returns a correctly parsed log", function () { var fake_log = [ - {author: "Karlo Soriano", date: "2013-05-09", additions: 471}, - {author: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 6, deletions: 1}, - {author: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 19, deletions: 3}, - {author: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 29, deletions: 3}] + {author_email: "karlo@email.com", author_name: "Karlo Soriano", date: "2013-05-09", additions: 471}, + {author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 6, deletions: 1}, + {author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 19, deletions: 3}, + {author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 29, deletions: 3}] var correct_parsed_log = { total: [ @@ -15,11 +15,11 @@ describe("ContributorsStatGraphUtil", function () { by_author: [ { - author: "Karlo Soriano", + author_name: "Karlo Soriano", author_email: "karlo@email.com", "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1} }, { - author: "Dmitriy Zaporozhets", + author_name: "Dmitriy Zaporozhets",author_email: "dzaporozhets@email.com", "2013-05-08": {date: "2013-05-08", additions: 54, deletions: 7, commits: 3} } ] @@ -112,10 +112,10 @@ describe("ContributorsStatGraphUtil", function () { describe("#add_author", function () { it("adds an author field to the collection", function () { - var fake_author = "Author" + var fake_author = { author_name: "Author", author_email: 'fake@email.com' } var fake_collection = {} ContributorsStatGraphUtil.add_author(fake_author, fake_collection) - expect(fake_collection[fake_author].author).toEqual("Author") + expect(fake_collection[fake_author.author_name].author_name).toEqual("Author") }) }) @@ -153,30 +153,35 @@ describe("ContributorsStatGraphUtil", function () { describe("#get_author_data", function () { it("returns the log by author sorted by specified field", function () { var fake_parsed_log = { - total: [{date: "2013-05-09", additions: 471, deletions: 0, commits: 1}, - {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}], - by_author:[ - { - author: "Karlo Soriano", - "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1} - }, - { - author: "Dmitriy Zaporozhets", - "2013-05-08": {date: "2013-05-08", additions: 54, deletions: 7, commits: 3} + total: [ + {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}, + {date: "2013-05-08", additions: 54, deletions: 7, commits: 3} + ], + by_author: [ + { + author_name: "Karlo Soriano", author_email: "karlo@email.com", + "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1} + }, + { + author_name: "Dmitriy Zaporozhets", author_email: "dzaporozhets@email.com", + "2013-05-08": {date: "2013-05-08", additions: 54, deletions: 7, commits: 3} + } + ] } - ]} - var correct_author_data = [{author:"Dmitriy Zaporozhets",dates:{"2013-05-08":3},deletions:7,additions:54,"commits":3}, - {author:"Karlo Soriano",dates:{"2013-05-09":1},deletions:0,additions:471,commits:1}] + var correct_author_data = [ + {author_name:"Dmitriy Zaporozhets",author_email:"dzaporozhets@email.com",dates:{"2013-05-08":3},deletions:7,additions:54,"commits":3}, + {author_name:"Karlo Soriano",author_email:"karlo@email.com",dates:{"2013-05-09":1},deletions:0,additions:471,commits:1} + ] expect(ContributorsStatGraphUtil.get_author_data(fake_parsed_log, "commits")).toEqual(correct_author_data) }) }) describe("#parse_log_entry", function () { it("adds the corresponding info from the log entry to the author", function () { - var fake_log_entry = { author: "Karlo Soriano", + var fake_log_entry = { author_name: "Karlo Soriano", author_email: "karlo@email.com", "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1} } - var correct_parsed_log = {author:"Karlo Soriano",dates:{"2013-05-09":1},deletions:0,additions:471,commits:1} + var correct_parsed_log = {author_name:"Karlo Soriano",author_email:"karlo@email.com",dates:{"2013-05-09":1},deletions:0,additions:471,commits:1} expect(ContributorsStatGraphUtil.parse_log_entry(fake_log_entry, 'commits', null)).toEqual(correct_parsed_log) }) }) @@ -197,4 +202,4 @@ describe("ContributorsStatGraphUtil", function () { }) -}) \ No newline at end of file +}) diff --git a/spec/javascripts/support/jasmine_helper.rb b/spec/javascripts/support/jasmine_helper.rb index 986a4c16f3..34b418a9ca 100644 --- a/spec/javascripts/support/jasmine_helper.rb +++ b/spec/javascripts/support/jasmine_helper.rb @@ -1,11 +1,5 @@ -#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 -# +WebMock.allow_net_connect! +Jasmine.configure do |config| + config.browser = :phantomjs +end diff --git a/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb b/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb new file mode 100644 index 0000000000..df5252e461 --- /dev/null +++ b/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +describe Gitlab::LDAP do + let(:gl_auth) { Gitlab::LDAP::User } + + before do + Gitlab.config.stub(omniauth: {}) + + @info = mock( + uid: '12djsak321', + name: 'John', + email: 'john@mail.com', + nickname: 'john' + ) + end + + describe :find_for_ldap_auth do + before do + @auth = mock( + uid: '12djsak321', + info: @info, + provider: 'ldap' + ) + end + + it "should update credentials by email if missing uid" do + user = double('User') + User.stub find_by_extern_uid_and_provider: nil + User.stub find_by_email: user + user.should_receive :update_attributes + gl_auth.find_or_create(@auth) + end + + it "should update credentials by username if missing uid and Gitlab.config.ldap.allow_username_or_email_login is true" do + user = double('User') + value = Gitlab.config.ldap.allow_username_or_email_login + Gitlab.config.ldap['allow_username_or_email_login'] = true + User.stub find_by_extern_uid_and_provider: nil + User.stub find_by_email: nil + User.stub find_by_username: user + user.should_receive :update_attributes + gl_auth.find_or_create(@auth) + Gitlab.config.ldap['allow_username_or_email_login'] = value + end + + it "should not update credentials by username if missing uid and Gitlab.config.ldap.allow_username_or_email_login is false" do + user = double('User') + value = Gitlab.config.ldap.allow_username_or_email_login + Gitlab.config.ldap['allow_username_or_email_login'] = false + User.stub find_by_extern_uid_and_provider: nil + User.stub find_by_email: nil + User.stub find_by_username: user + user.should_not_receive :update_attributes + gl_auth.find_or_create(@auth) + Gitlab.config.ldap['allow_username_or_email_login'] = value + end + end +end diff --git a/spec/lib/gitlab/satellite/merge_action_spec.rb b/spec/lib/gitlab/satellite/merge_action_spec.rb index 3be14383e0..e40ff73b7f 100644 --- a/spec/lib/gitlab/satellite/merge_action_spec.rb +++ b/spec/lib/gitlab/satellite/merge_action_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Gitlab::Satellite::MergeAction' do before(:each) do # TestEnv.init(mailer: false, init_repos: true, repos: true) - @master = ['master', 'bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a'] + @master = ['master', 'b1e6a9dbf1c85e6616497a5e7bad9143a4bd0828'] @one_after_stable = ['stable', '6ea87c47f0f8a24ae031c3fff17bc913889ecd00'] #this commit sha is one after stable @wiki_branch = ['wiki', '635d3e09b72232b6e92a38de6cc184147e5bcb41'] #this is the commit sha where the wiki branch goes off from master @conflicting_metior = ['metior', '313d96e42b313a0af5ab50fa233bf43e27118b3f'] #this branch conflicts with the wiki branch diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 0787bdbea6..666c6cceff 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -320,7 +320,7 @@ describe Notify do it_behaves_like 'a note email' it 'has the correct subject' do - should have_subject /note for merge request !#{merge_request.iid}/ + should have_subject /note for merge request ##{merge_request.iid}/ end it 'contains a link to the merge request note' do diff --git a/spec/models/flowdock_service_spec.rb b/spec/models/flowdock_service_spec.rb new file mode 100644 index 0000000000..b22193c9e9 --- /dev/null +++ b/spec/models/flowdock_service_spec.rb @@ -0,0 +1,48 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# token :string(255) +# project_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean default(FALSE), not null +# project_url :string(255) +# + +require 'spec_helper' + +describe FlowdockService do + describe "Associations" do + it { should belong_to :project } + it { should have_one :service_hook } + end + + describe "Execute" do + let(:user) { create(:user) } + let(:project) { create(:project_with_code) } + + before do + @flowdock_service = FlowdockService.new + @flowdock_service.stub( + project_id: project.id, + project: project, + service_hook: true, + token: 'verySecret' + ) + @sample_data = GitPushService.new.sample_data(project, user) + @api_url = 'https://api.flowdock.com/v1/git/verySecret' + WebMock.stub_request(:post, @api_url) + end + + it "should call FlowDock API" do + @flowdock_service.execute(@sample_data) + WebMock.should have_requested(:post, @api_url).with( + body: /#{@sample_data[:before]}.*#{@sample_data[:after]}.*#{project.path}/ + ).once + end + end +end diff --git a/spec/models/gollum_wiki_spec.rb b/spec/models/gollum_wiki_spec.rb index aa850dfd0a..9e07d9ee19 100644 --- a/spec/models/gollum_wiki_spec.rb +++ b/spec/models/gollum_wiki_spec.rb @@ -86,6 +86,27 @@ describe GollumWiki do end end + describe "#empty?" do + context "when the wiki repository is empty" do + before do + Gitlab::Shell.any_instance.stub(:add_repository) do + create_temp_repo("#{Rails.root}/tmp/test-git-base-path/non-existant.wiki.git") + end + project.stub(:path_with_namespace).and_return("non-existant") + end + + its(:empty?) { should be_true } + end + + context "when the wiki has pages" do + before do + create_page("index", "This is an awesome new Gollum Wiki") + end + + its(:empty?) { should be_false } + end + end + describe "#pages" do before do create_page("index", "This is an awesome new Gollum Wiki") diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 78c39548eb..12b84700eb 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -5,7 +5,7 @@ # id :integer not null, primary key # name :string(255) not null # path :string(255) not null -# owner_id :integer not null +# owner_id :integer # created_at :datetime not null # updated_at :datetime not null # type :string(255) @@ -26,10 +26,10 @@ describe Group do it { should validate_uniqueness_of(:name) } it { should validate_presence_of :path } it { should validate_uniqueness_of(:path) } - it { should validate_presence_of :owner } + it { should_not validate_presence_of :owner } describe :users do - it { group.users.should == [group.owner] } + it { group.users.should == group.owners } end describe :human_name do @@ -38,8 +38,20 @@ describe Group do describe :add_users do let(:user) { create(:user) } - before { group.add_users([user.id], UsersGroup::MASTER) } + before { group.add_user(user, UsersGroup::MASTER) } it { group.users_groups.masters.map(&:user).should include(user) } end + + describe :add_users do + let(:user) { create(:user) } + before { group.add_users([user.id], UsersGroup::GUEST) } + + it "should update the group permission" do + group.users_groups.guests.map(&:user).should include(user) + group.add_users([user.id], UsersGroup::DEVELOPER) + group.users_groups.developers.map(&:user).should include(user) + group.users_groups.guests.map(&:user).should_not include(user) + end + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 18c32d4fb7..b17183a281 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -18,6 +18,7 @@ # merge_status :string(255) # target_project_id :integer not null # iid :integer +# description :text # require 'spec_helper' @@ -48,8 +49,8 @@ describe MergeRequest do before do merge_request.stub(:commits) { [merge_request.source_project.repository.commit] } - create(:note, commit_id: merge_request.commits.first.id, noteable_type: 'Commit') - create(:note, noteable: merge_request) + create(:note, commit_id: merge_request.commits.first.id, noteable_type: 'Commit', project: merge_request.project) + create(:note, noteable: merge_request, project: merge_request.project) end it "should include notes for commits" do diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 0a0509bcd2..c38554e2f7 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -5,7 +5,7 @@ # id :integer not null, primary key # name :string(255) not null # path :string(255) not null -# owner_id :integer not null +# owner_id :integer # created_at :datetime not null # updated_at :datetime not null # type :string(255) diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index ef143debcc..42c405d8e5 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -14,6 +14,7 @@ # commit_id :string(255) # noteable_id :integer # st_diff :text +# system :boolean default(FALSE), not null # require 'spec_helper' diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index c3d2bf5d4a..88ea692679 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -27,8 +27,8 @@ require 'spec_helper' describe Project do - before(:each) { enable_observers } - after(:each) { disable_observers } + before { enable_observers } + after { disable_observers } describe "Associations" do it { should belong_to(:group) } @@ -85,7 +85,6 @@ describe Project do it { should respond_to(:execute_hooks) } it { should respond_to(:transfer) } it { should respond_to(:name_with_namespace) } - it { should respond_to(:namespace_owner) } it { should respond_to(:owner) } it { should respond_to(:path_with_namespace) } end @@ -133,17 +132,17 @@ describe Project do it "should close merge request if last commit from source branch was pushed to target branch" do @merge_request.reloaded_commits - @merge_request.last_commit.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" - project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a", "refs/heads/stable", @key.user) + @merge_request.last_commit.id.should == "b1e6a9dbf1c85e6616497a5e7bad9143a4bd0828" + project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "b1e6a9dbf1c85e6616497a5e7bad9143a4bd0828", "refs/heads/stable", @key.user) @merge_request.reload @merge_request.merged?.should be_true end it "should update merge request commits with new one if pushed to source branch" do @merge_request.last_commit.should == nil - project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a", "refs/heads/master", @key.user) + project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "b1e6a9dbf1c85e6616497a5e7bad9143a4bd0828", "refs/heads/master", @key.user) @merge_request.reload - @merge_request.last_commit.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" + @merge_request.last_commit.id.should == "b1e6a9dbf1c85e6616497a5e7bad9143a4bd0828" end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 79915daa46..f6c9f82c4e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -130,11 +130,12 @@ describe User do before do ActiveRecord::Base.observers.enable(:user_observer) @user = create :user - @group = create :group, owner: @user + @group = create :group + @group.add_owner(@user) end it { @user.several_namespaces?.should be_true } - it { @user.namespaces.should include(@user.namespace, @group) } + it { @user.namespaces.should include(@user.namespace) } it { @user.authorized_groups.should == [@group] } it { @user.owned_groups.should == [@group] } end @@ -144,9 +145,10 @@ describe User do ActiveRecord::Base.observers.enable(:user_observer) @user = create :user @user2 = create :user - @group = create :group, owner: @user + @group = create :group + @group.add_owner(@user) - @group.add_users([@user2.id], UsersGroup::OWNER) + @group.add_user(@user2, UsersGroup::OWNER) end it { @user2.several_namespaces?.should be_true } @@ -221,9 +223,9 @@ describe User do let(:user) { User.build_user({}, as: :admin) } it "should apply defaults to user" do - user.projects_limit.should == 42 - user.can_create_group.should be_false - user.theme_id.should == Gitlab::Theme::MARS + user.projects_limit.should == Gitlab.config.gitlab.default_projects_limit + user.can_create_group.should == Gitlab.config.gitlab.default_can_create_group + user.theme_id.should == Gitlab.config.gitlab.default_theme end end @@ -231,6 +233,9 @@ describe User do let(:user) { User.build_user({projects_limit: 123, can_create_group: true, can_create_team: true, theme_id: Gitlab::Theme::BASIC}, as: :admin) } it "should apply defaults to user" do + Gitlab.config.gitlab.default_projects_limit.should_not == 123 + Gitlab.config.gitlab.default_can_create_group.should_not be_true + Gitlab.config.gitlab.default_theme.should_not == Gitlab::Theme::BASIC user.projects_limit.should == 123 user.can_create_group.should be_true user.theme_id.should == Gitlab::Theme::BASIC @@ -243,9 +248,9 @@ describe User do let(:user) { User.build_user } it "should apply defaults to user" do - user.projects_limit.should == 42 - user.can_create_group.should be_false - user.theme_id.should == Gitlab::Theme::MARS + user.projects_limit.should == Gitlab.config.gitlab.default_projects_limit + user.can_create_group.should == Gitlab.config.gitlab.default_can_create_group + user.theme_id.should == Gitlab.config.gitlab.default_theme end end @@ -253,9 +258,9 @@ describe User do let(:user) { User.build_user(projects_limit: 123, can_create_group: true, theme_id: Gitlab::Theme::BASIC) } it "should apply defaults to user" do - user.projects_limit.should == 42 - user.can_create_group.should be_false - user.theme_id.should == Gitlab::Theme::MARS + user.projects_limit.should == Gitlab.config.gitlab.default_projects_limit + user.can_create_group.should == Gitlab.config.gitlab.default_can_create_group + user.theme_id.should == Gitlab.config.gitlab.default_theme end end end diff --git a/spec/observers/users_project_observer_spec.rb b/spec/observers/users_project_observer_spec.rb index e33d8cc50f..e7c624fce5 100644 --- a/spec/observers/users_project_observer_spec.rb +++ b/spec/observers/users_project_observer_spec.rb @@ -65,4 +65,30 @@ describe UsersProjectObserver do @users_project.destroy end end -end + + describe "#after_create" do + context 'wiki_enabled creates repository directory' do + context 'wiki_enabled true creates wiki repository directory' do + before do + @project = create(:project, wiki_enabled: true) + @path = GollumWiki.new(@project, user).send(:path_to_repo) + end + + after do + FileUtils.rm_rf(@path) + end + + it { File.exists?(@path).should be_true } + end + + context 'wiki_enabled false does not create wiki repository directory' do + before do + @project = create(:project, wiki_enabled: false) + @path = GollumWiki.new(@project, user).send(:path_to_repo) + end + + it { File.exists?(@path).should be_false } + end + end + end +end \ No newline at end of file diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 1df2746cb5..d6acc80617 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -6,8 +6,13 @@ describe API::API do let(:user1) { create(:user) } let(:user2) { create(:user) } let(:admin) { create(:admin) } - let!(:group1) { create(:group, owner: user1, ldap_cn: "ldap-group", ldap_access: Gitlab::Access::MASTER ) } - let!(:group2) { create(:group, owner: user2) } + let!(:group1) { create(:group, ldap_cn: "ldap-group", ldap_access: Gitlab::Access::MASTER ) } + let!(:group2) { create(:group) } + + before do + group1.add_owner(user1) + group2.add_owner(user2) + end describe "GET /groups" do context "when unauthenticated" do @@ -103,6 +108,44 @@ describe API::API do end end + describe "DELETE /groups/:id" do + context "when authenticated as user" do + it "should remove group" do + delete api("/groups/#{group1.id}", user1) + response.status.should == 200 + end + + it "should not remove a group if not an owner" do + user3 = create(:user) + group1.add_user(user3, Gitlab::Access::MASTER) + delete api("/groups/#{group1.id}", user3) + response.status.should == 403 + end + + it "should not remove a non existing group" do + delete api("/groups/1328", user1) + response.status.should == 404 + end + + it "should not remove a group not attached to user1" do + delete api("/groups/#{group2.id}", user1) + response.status.should == 403 + end + end + + context "when authenticated as admin" do + it "should remove any existing group" do + delete api("/groups/#{group2.id}", admin) + response.status.should == 200 + end + + it "should not remove a non existing group" do + delete api("/groups/1328", admin) + response.status.should == 404 + end + end + end + describe "POST /groups/:id/projects/:project_id" do let(:project) { create(:project) } before(:each) do @@ -132,14 +175,19 @@ describe API::API do let(:master) { create(:user) } let(:guest) { create(:user) } let!(:group_with_members) do - group = create(:group, owner: owner) + group = create(:group) group.add_users([reporter.id], UsersGroup::REPORTER) group.add_users([developer.id], UsersGroup::DEVELOPER) group.add_users([master.id], UsersGroup::MASTER) group.add_users([guest.id], UsersGroup::GUEST) group end - let!(:group_no_members) { create(:group, owner: owner) } + let!(:group_no_members) { create(:group) } + + before do + group_with_members.add_owner owner + group_no_members.add_owner owner + end describe "GET /groups/:id/members" do context "when authenticated as user that is part or the group" do diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index a97d6a282a..c55025d72b 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -42,6 +42,7 @@ describe API::API do get api("/projects/#{project.id}/issues/#{issue.id}", user) response.status.should == 200 json_response['title'].should == issue.title + json_response['iid'].should == issue.iid end it "should return 404 if issue id not found" do @@ -99,4 +100,16 @@ describe API::API do response.status.should == 405 end end + + describe "PUT /projects/:id/issues/:issue_id to test observer on close" do + before { enable_observers } + after { disable_observers } + + it "should create an activity event when an issue is closed" do + Event.should_receive(:create) + + put api("/projects/#{project.id}/issues/#{issue.id}", user), + state_event: "close" + end + end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 2f11f562aa..f31b4da90c 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -34,6 +34,7 @@ describe API::API do get api("/projects/#{project.id}/merge_request/#{merge_request.id}", user) response.status.should == 200 json_response['title'].should == merge_request.title + json_response['iid'].should == merge_request.iid end it "should return a 404 error if merge_request_id not found" do diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index 246fe262ce..febfc63921 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -30,6 +30,7 @@ describe API::API do get api("/projects/#{project.id}/milestones/#{milestone.id}", user) response.status.should == 200 json_response['title'].should == milestone.title + json_response['iid'].should == milestone.iid end it "should return 401 error if user not authenticated" do @@ -89,4 +90,16 @@ describe API::API do json_response['state'].should == 'closed' end end + + describe "PUT /projects/:id/milestones/:milestone_id to test observer on close" do + before { enable_observers } + after { disable_observers } + + it "should create an activity event when an milestone is closed" do + Event.should_receive(:create) + + put api("/projects/#{project.id}/milestones/#{milestone.id}", user), + state_event: 'close' + end + end end diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index ba18b12303..6ed96eb97f 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -176,4 +176,16 @@ describe API::API do end end end + + describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do + before { enable_observers } + after { disable_observers } + + it "should create an activity event when an issue note is created" do + Event.should_receive(:create) + + post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!' + end + end + end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index de4f01b785..cf939ce76d 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -723,4 +723,80 @@ describe API::API do json_response['message'].should == 'Group access is not included in the list' end end + + describe "GET /projects/search/:query" do + let!(:query) { 'query'} + let!(:search) { create(:project, name: query, creator_id: user.id, namespace: user.namespace) } + let!(:pre) { create(:project, name: "pre_#{query}", creator_id: user.id, namespace: user.namespace) } + let!(:post) { create(:project, name: "#{query}_post", creator_id: user.id, namespace: user.namespace) } + let!(:pre_post) { create(:project, name: "pre_#{query}_post", creator_id: user.id, namespace: user.namespace) } + let!(:unfound) { create(:project, name: 'unfound', creator_id: user.id, namespace: user.namespace) } + let!(:public) { create(:project, name: "another #{query}",public: true) } + let!(:unfound_public) { create(:project, name: 'unfound public', public: true) } + + context "when unauthenticated" do + it "should return authentication error" do + get api("/projects/search/#{query}") + response.status.should == 401 + end + end + + context "when authenticated" do + it "should return an array of projects" do + get api("/projects/search/#{query}",user) + response.status.should == 200 + json_response.should be_an Array + json_response.size.should == 5 + json_response.each {|project| project['name'].should =~ /.*query.*/} + end + end + + context "when authenticated as a different user" do + it "should return matching public projects" 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}" + end + end + end + + describe "DELETE /projects/:id" do + context "when authenticated as user" do + it "should remove project" do + delete api("/projects/#{project.id}", user) + response.status.should == 200 + end + + it "should not remove a project if not an owner" do + user3 = create(:user) + project.team << [user3, :developer] + delete api("/projects/#{project.id}", user3) + response.status.should == 403 + end + + it "should not remove a non existing project" do + delete api("/projects/1328", user) + response.status.should == 404 + end + + it "should not remove a project not attached to user" do + delete api("/projects/#{project.id}", user2) + response.status.should == 404 + end + end + + context "when authenticated as admin" do + it "should remove any existing project" do + delete api("/projects/#{project.id}", admin) + response.status.should == 200 + end + + it "should not remove a non existing project" do + delete api("/projects/1328", admin) + response.status.should == 404 + end + end + end end diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index f15abdd358..9649c4d09c 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -8,7 +8,8 @@ describe API::API do let(:user) { create(:user) } let(:user2) { create(:user) } let!(:project) { create(:project_with_code, creator_id: user.id) } - let!(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) } + let!(:master) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) } + let!(:guest) { create(:users_project, user: user2, project: project, project_access: UsersProject::GUEST) } before { project.team << [user, :reporter] } @@ -32,6 +33,11 @@ describe API::API do json_response['protected'].should == false end + it "should return a 403 error if guest" do + get api("/projects/#{project.id}/repository/branches", user2) + response.status.should == 403 + end + it "should return a 404 error if branch is not available" do get api("/projects/#{project.id}/repository/branches/unknown", user) response.status.should == 404 @@ -53,6 +59,11 @@ describe API::API do response.status.should == 404 end + it "should return a 403 error if guest" do + put api("/projects/#{project.id}/repository/branches/new_design/protect", user2) + response.status.should == 403 + end + it "should return success when protect branch again" do put api("/projects/#{project.id}/repository/branches/new_design/protect", user) put api("/projects/#{project.id}/repository/branches/new_design/protect", user) @@ -214,4 +225,16 @@ describe API::API do end end + describe "GET /projects/:id/repository/archive/:sha" do + it "should get the archive" do + get api("/projects/#{project.id}/repository/archive", user) + response.status.should == 200 + response.content_type.should == 'application/x-gzip' + end + + it "should return 404 for invalid sha" do + get api("/projects/#{project.id}/repository/archive/?sha=xxx", user) + response.status.should == 404 + end + end end diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb index 88c17f26a6..0fd90c567e 100644 --- a/spec/requests/api/session_spec.rb +++ b/spec/requests/api/session_spec.rb @@ -14,7 +14,6 @@ describe API::API do json_response['email'].should == user.email json_response['private_token'].should == user.private_token json_response['is_admin'].should == user.is_admin? - json_response['can_create_team'].should == user.can_create_team? json_response['can_create_project'].should == user.can_create_project? json_response['can_create_group'].should == user.can_create_group? end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 2fced3ec94..4ef78b8e5d 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -52,6 +52,35 @@ describe API::API do }.to change { User.count }.by(1) end + it "should create user with correct attributes" do + post api('/users', admin), attributes_for(:user, admin: true, can_create_group: true) + response.status.should == 201 + user_id = json_response['id'] + new_user = User.find(user_id) + new_user.should_not == nil + new_user.admin.should == true + new_user.can_create_group.should == true + end + + it "should create non-admin user" do + post api('/users', admin), attributes_for(:user, admin: false, can_create_group: false) + response.status.should == 201 + user_id = json_response['id'] + new_user = User.find(user_id) + new_user.should_not == nil + new_user.admin.should == false + new_user.can_create_group.should == false + end + + it "should create non-admin users by default" do + post api('/users', admin), attributes_for(:user) + response.status.should == 201 + user_id = json_response['id'] + new_user = User.find(user_id) + new_user.should_not == nil + new_user.admin.should == false + end + it "should return 201 Created on success" do post api("/users", admin), attributes_for(:user, projects_limit: 3) response.status.should == 201 @@ -135,6 +164,8 @@ describe API::API do end describe "PUT /users/:id" do + let!(:admin_user) { create(:admin) } + before { admin } it "should update user with new bio" do @@ -144,6 +175,21 @@ describe API::API do user.reload.bio.should == 'new test bio' end + it "should update admin status" do + put api("/users/#{user.id}", admin), {admin: true} + response.status.should == 200 + json_response['is_admin'].should == true + user.reload.admin.should == true + end + + it "should not update admin status" do + put api("/users/#{admin_user.id}", admin), {can_create_group: false} + response.status.should == 200 + json_response['is_admin'].should == true + admin_user.reload.admin.should == true + admin_user.can_create_group.should == false + end + it "should not allow invalid update" do put api("/users/#{user.id}", admin), {email: 'invalid email'} response.status.should == 404 @@ -228,7 +274,6 @@ describe API::API do response.status.should == 200 json_response['email'].should == user.email json_response['is_admin'].should == user.is_admin? - json_response['can_create_team'].should == user.can_create_team? json_response['can_create_project'].should == user.can_create_project? json_response['can_create_group'].should == user.can_create_group? end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 946ef7c28c..1b1d19d26b 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -128,7 +128,7 @@ end # profile_update PUT /profile/update(.:format) profile#update describe ProfilesController, "routing" do it "to #account" do - get("/profile/account").should route_to('profiles#account') + get("/profile/account").should route_to('profiles/accounts#show') end it "to #history" do diff --git a/spec/seed_project.tar.gz b/spec/seed_project.tar.gz index 3ffafed18d..7abb51ebdf 100644 Binary files a/spec/seed_project.tar.gz and b/spec/seed_project.tar.gz differ diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index e3c25fa046..2870f59195 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -26,6 +26,7 @@ describe GitPushService do it { should include(ref: @ref) } it { should include(user_id: user.id) } it { should include(user_name: user.name) } + it { should include(project_id: project.id) } context "with repository data" do subject { @push_data[:repository] } diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index 7f1590f559..ebc1ed51d2 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -5,37 +5,29 @@ describe SystemHooksService do let (:project) { create :project } let (:users_project) { create :users_project } - context 'it should build event data' do - it 'should build event data for user' do - SystemHooksService.build_event_data(user, :create).should include(:event_name, :name, :created_at, :email) - end - - it 'should build event data for project' do - SystemHooksService.build_event_data(project, :create).should include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email) - end - - it 'should build event data for users project' do - SystemHooksService.build_event_data(users_project, :create).should include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :project_access) - end + context 'event data' do + it { event_data(user, :create).should include(:event_name, :name, :created_at, :email, :user_id) } + it { event_data(user, :destroy).should include(:event_name, :name, :created_at, :email, :user_id) } + it { event_data(project, :create).should include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email) } + it { event_data(project, :destroy).should include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email) } + it { event_data(users_project, :create).should include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :project_access) } + it { event_data(users_project, :destroy).should include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :project_access) } end - context 'it should build event names' do - it 'should build event names for user' do - SystemHooksService.build_event_name(user, :create).should eq "user_create" + context 'event names' do + it { event_name(user, :create).should eq "user_create" } + it { event_name(user, :destroy).should eq "user_destroy" } + it { event_name(project, :create).should eq "project_create" } + it { event_name(project, :destroy).should eq "project_destroy" } + it { event_name(users_project, :create).should eq "user_add_to_team" } + it { event_name(users_project, :destroy).should eq "user_remove_from_team" } + end - SystemHooksService.build_event_name(user, :destroy).should eq "user_destroy" - end + def event_data(*args) + SystemHooksService.build_event_data(*args) + end - it 'should build event names for project' do - SystemHooksService.build_event_name(project, :create).should eq "project_create" - - SystemHooksService.build_event_name(project, :destroy).should eq "project_destroy" - end - - it 'should build event names for users project' do - SystemHooksService.build_event_name(users_project, :create).should eq "user_add_to_team" - - SystemHooksService.build_event_name(users_project, :destroy).should eq "user_remove_from_team" - end + def event_name(*args) + SystemHooksService.build_event_name(*args) end end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 203e661f51..16e10b1a62 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -84,6 +84,10 @@ module TestEnv Repository.any_instance.stub( size: 12.45 ) + + ActivityObserver.any_instance.stub( + current_user: double("current_user", id: 1) + ) end def clear_repo_dir(namespace, name)