From b80dd3d2422b59a1b241ccdae32140ca19f33dc3 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Sun, 2 Sep 2012 18:31:16 +0200 Subject: [PATCH] Non-interactive AWS install by running a single script. Merge branch 'master' into non-interactive-aws-install Conflicts: doc/installation.md Fix merge mess in installation.md --- .rails_footnotes | 3 - CHANGELOG | 24 + Gemfile | 10 +- Gemfile.lock | 23 +- README.md | 1 + VERSION | 2 +- app/assets/images/file_dir.png | Bin 517 -> 1648 bytes app/assets/images/merge.png | Bin 0 -> 593 bytes app/assets/javascripts/application.js | 22 +- app/assets/javascripts/issues.js | 2 + app/assets/javascripts/note.js | 239 ++--- app/assets/javascripts/projects.js | 4 +- app/assets/stylesheets/common.scss | 334 +++---- app/assets/stylesheets/gitlab_bootstrap.scss | 818 ------------------ .../stylesheets/gitlab_bootstrap/blocks.scss | 145 ++++ .../stylesheets/gitlab_bootstrap/buttons.scss | 105 +++ .../stylesheets/gitlab_bootstrap/common.scss | 52 ++ .../stylesheets/gitlab_bootstrap/files.scss | 156 ++++ .../stylesheets/gitlab_bootstrap/lists.scss | 30 + .../stylesheets/gitlab_bootstrap/tables.scss | 41 + .../gitlab_bootstrap/typography.scss | 71 ++ app/assets/stylesheets/main.scss | 45 +- app/assets/stylesheets/projects.css.scss | 385 --------- app/assets/stylesheets/ref_select.scss | 4 +- app/assets/stylesheets/sections/commits.scss | 20 + app/assets/stylesheets/sections/graph.scss | 6 +- app/assets/stylesheets/sections/issues.scss | 13 + .../stylesheets/sections/merge_requests.scss | 57 +- app/assets/stylesheets/sections/nav.scss | 10 +- app/assets/stylesheets/sections/notes.scss | 73 +- app/assets/stylesheets/sections/projects.scss | 75 +- app/assets/stylesheets/sections/tree.scss | 6 +- app/assets/stylesheets/themes/ui_mars.scss | 8 + app/contexts/merge_requests_load.rb | 8 +- app/controllers/application_controller.rb | 4 + app/controllers/issues_controller.rb | 16 +- app/controllers/labels_controller.rb | 25 + app/controllers/merge_requests_controller.rb | 2 + app/controllers/milestones_controller.rb | 4 +- .../omniauth_callbacks_controller.rb | 3 +- app/controllers/team_members_controller.rb | 1 + app/decorators/application_decorator.rb | 2 +- app/helpers/application_helper.rb | 26 +- app/helpers/gitlab_markdown_helper.rb | 52 +- app/mailers/notify.rb | 77 +- app/models/key.rb | 8 +- app/models/merge_request.rb | 7 +- app/models/milestone.rb | 14 +- app/models/project.rb | 10 +- app/models/protected_branch.rb | 4 +- app/models/user.rb | 34 +- app/models/users_project.rb | 6 +- app/observers/issue_observer.rb | 12 +- app/observers/key_observer.rb | 7 +- app/observers/users_project_observer.rb | 9 + app/roles/git_host.rb | 5 + app/roles/git_merge.rb | 2 - .../{project_push.rb => push_observer.rb} | 2 +- app/roles/repository.rb | 31 +- app/roles/ssh_key.rb | 18 - app/views/admin/dashboard/index.html.haml | 8 +- app/views/admin/hooks/index.html.haml | 2 +- app/views/admin/projects/_form.html.haml | 20 +- app/views/admin/projects/_new_form.html.haml | 29 + app/views/admin/projects/index.html.haml | 4 +- app/views/admin/projects/new.html.haml | 15 +- app/views/admin/users/_form.html.haml | 124 +-- app/views/admin/users/edit.html.haml | 2 +- app/views/admin/users/index.html.haml | 4 +- app/views/admin/users/new.html.haml | 4 +- app/views/commits/_commit.html.haml | 4 +- app/views/commits/compare.html.haml | 2 +- app/views/commits/show.html.haml | 10 +- app/views/dashboard/index.html.haml | 28 +- app/views/errors/gitolite.html.haml | 6 +- app/views/errors/invalid_ssh_key.html.haml | 3 + app/views/events/_event_last_push.html.haml | 2 +- app/views/help/api.html.haml | 22 + app/views/help/index.html.haml | 3 + app/views/help/markdown.html.haml | 114 ++- app/views/help/permissions.html.haml | 4 +- app/views/help/ssh.html.haml | 25 + app/views/help/system_hooks.html.haml | 4 +- app/views/help/web_hooks.html.haml | 8 +- app/views/help/workflow.html.haml | 13 +- app/views/hooks/_data_ex.html.erb | 2 +- app/views/hooks/index.html.haml | 2 +- app/views/issues/_form.html.haml | 13 +- app/views/issues/_head.html.haml | 3 + app/views/issues/index.html.haml | 2 +- app/views/keys/_form.html.haml | 14 +- app/views/keys/index.html.haml | 2 +- app/views/keys/new.html.haml | 2 +- app/views/labels/_label.html.haml | 4 + app/views/labels/index.html.haml | 14 + app/views/merge_requests/_form.html.haml | 19 +- app/views/merge_requests/index.html.haml | 18 +- .../show/_how_to_merge.html.haml | 11 +- .../merge_requests/show/_mr_title.html.haml | 4 +- app/views/milestones/_form.html.haml | 16 +- app/views/milestones/index.html.haml | 8 +- app/views/notes/_create_common.js.haml | 9 +- app/views/notes/_create_line.js.haml | 2 +- app/views/notes/_form.html.haml | 69 +- app/views/notes/_per_line_form.html.haml | 51 +- .../issue_status_changed_email.html.haml | 16 + .../project_access_granted_email.html.haml | 14 + app/views/profile/password.html.haml | 2 +- app/views/profile/show.html.haml | 9 +- app/views/projects/_clone_panel.html.haml | 21 + app/views/projects/_form.html.haml | 13 +- app/views/projects/_new_form.html.haml | 6 +- app/views/projects/empty.html.haml | 88 +- app/views/projects/new.html.haml | 6 +- app/views/projects/show.html.haml | 29 +- app/views/refs/_tree_item.html.haml | 2 +- app/views/search/show.html.haml | 4 +- app/views/shared/_no_ssh.html.haml | 8 + app/views/team_members/_show.html.haml | 2 +- app/views/team_members/show.html.haml | 2 +- app/views/wikis/_form.html.haml | 9 +- config/application.rb | 2 +- config/environment.rb | 2 - config/gitlab.yml.example | 14 +- config/initializers/1_settings.rb | 8 + config/initializers/5_backend.rb | 5 + config/initializers/rails_footnotes.rb | 3 - config/routes.rb | 3 + db/fixtures/test/001_repo.rb | 32 +- ...31232_add_extern_auth_provider_to_users.rb | 8 + db/pkey.example | 3 - db/schema.rb | 5 +- doc/api/README.md | 2 + doc/api/milestones.md | 57 ++ doc/api/projects.md | 102 --- doc/api/snippets.md | 100 +++ doc/development.md | 45 + doc/installation.md | 103 +-- features/profile/ssh_keys.feature | 4 +- features/projects/issues/labels.feature | 13 + features/projects/network.feature | 4 +- features/step_definitions/dashboard_steps.rb | 24 +- .../profile/profile_keys_steps.rb | 2 +- .../project/project_issues_steps.rb | 21 +- .../project/projects_steps.rb | 11 +- features/support/env.rb | 23 +- gitlab | 75 ++ lib/api.rb | 1 + lib/api/issues.rb | 5 +- lib/api/milestones.rb | 80 ++ lib/gitlab/{ => backend}/gitolite.rb | 115 ++- .../gitlab/backend}/grack_auth.rb | 10 +- lib/gitlab/git_host.rb | 17 - lib/gitlab/markdown.rb | 19 +- lib/{post-receive-hook => hooks/post-receive} | 0 lib/support/init-gitlab | 54 ++ lib/support/nginx-gitlab | 33 + lib/tasks/bulk_add_permission.rake | 26 + lib/tasks/gitlab/backup.rake | 3 +- lib/tasks/gitlab/enable_automerge.rake | 4 +- lib/tasks/gitlab/gitolite_rebuild.rake | 2 +- lib/tasks/gitlab/setup.rake | 6 +- lib/tasks/gitlab/status.rake | 22 +- lib/tasks/gitlab/update_hooks.rake | 19 - lib/tasks/gitlab/write_hook.rake | 23 + resque_dev.sh | 3 +- spec/factories.rb | 188 ++-- spec/factories_spec.rb | 91 ++ spec/factory.rb | 29 - spec/helpers/application_helper_spec.rb | 26 + spec/helpers/gitlab_flavored_markdown_spec.rb | 4 +- spec/mailers/notify_spec.rb | 49 +- spec/models/event_spec.rb | 27 +- spec/models/issue_spec.rb | 91 +- spec/models/key_spec.rb | 36 +- spec/models/merge_request_spec.rb | 81 +- spec/models/milestone_spec.rb | 48 +- spec/models/note_spec.rb | 39 +- spec/models/project_spec.rb | 70 +- spec/models/protected_branch_spec.rb | 31 +- spec/models/snippet_spec.rb | 26 +- spec/models/system_hook_spec.rb | 11 +- spec/models/user_spec.rb | 69 +- spec/models/users_project_spec.rb | 16 +- spec/models/web_hook_spec.rb | 11 - spec/models/wiki_spec.rb | 18 +- spec/monkeypatch.rb | 51 -- .../activity_observer_spec.rb | 0 .../issue_observer_spec.rb | 69 +- spec/observers/key_observer_spec.rb | 34 + .../user_observer_spec.rb | 0 spec/observers/users_project_observer_spec.rb | 40 + spec/requests/admin/admin_projects_spec.rb | 2 +- spec/requests/admin/security_spec.rb | 24 +- spec/{ => requests}/api/issues_spec.rb | 19 +- spec/requests/api/milestones_spec.rb | 47 + spec/{ => requests}/api/projects_spec.rb | 37 +- spec/{ => requests}/api/users_spec.rb | 10 +- spec/requests/atom/dashboard_issues_spec.rb | 8 +- spec/requests/projects_deploy_keys_spec.rb | 8 +- spec/requests/security/profile_access_spec.rb | 30 +- spec/requests/security/project_access_spec.rb | 230 ++--- spec/roles/issue_commonality_spec.rb | 69 ++ spec/roles/upvote_spec.rb | 27 + spec/spec_helper.rb | 45 +- spec/support/api.rb | 7 - spec/support/api_helpers.rb | 34 + spec/support/db_cleaner.rb | 18 + spec/support/gitolite_stub.rb | 35 + spec/support/js_patch.rb | 6 - spec/support/login.rb | 30 - spec/support/login_helpers.rb | 23 + spec/support/matchers.rb | 24 + spec/support/shared_examples.rb | 16 - spec/support/stubbed_repository.rb | 31 + 215 files changed, 3829 insertions(+), 3348 deletions(-) delete mode 100644 .rails_footnotes create mode 100644 app/assets/images/merge.png delete mode 100644 app/assets/stylesheets/gitlab_bootstrap.scss create mode 100644 app/assets/stylesheets/gitlab_bootstrap/blocks.scss create mode 100644 app/assets/stylesheets/gitlab_bootstrap/buttons.scss create mode 100644 app/assets/stylesheets/gitlab_bootstrap/common.scss create mode 100644 app/assets/stylesheets/gitlab_bootstrap/files.scss create mode 100644 app/assets/stylesheets/gitlab_bootstrap/lists.scss create mode 100644 app/assets/stylesheets/gitlab_bootstrap/tables.scss create mode 100644 app/assets/stylesheets/gitlab_bootstrap/typography.scss delete mode 100644 app/assets/stylesheets/projects.css.scss create mode 100644 app/controllers/labels_controller.rb create mode 100644 app/observers/users_project_observer.rb create mode 100644 app/roles/git_host.rb delete mode 100644 app/roles/git_merge.rb rename app/roles/{project_push.rb => push_observer.rb} (99%) delete mode 100644 app/roles/ssh_key.rb create mode 100644 app/views/admin/projects/_new_form.html.haml create mode 100644 app/views/errors/invalid_ssh_key.html.haml create mode 100644 app/views/help/ssh.html.haml create mode 100644 app/views/labels/_label.html.haml create mode 100644 app/views/labels/index.html.haml create mode 100644 app/views/notify/issue_status_changed_email.html.haml create mode 100644 app/views/notify/project_access_granted_email.html.haml create mode 100644 app/views/projects/_clone_panel.html.haml create mode 100644 app/views/shared/_no_ssh.html.haml create mode 100644 config/initializers/5_backend.rb delete mode 100644 config/initializers/rails_footnotes.rb create mode 100644 db/migrate/20120729131232_add_extern_auth_provider_to_users.rb delete mode 100644 db/pkey.example create mode 100644 doc/api/milestones.md create mode 100644 doc/api/snippets.md create mode 100644 doc/development.md create mode 100644 features/projects/issues/labels.feature create mode 100755 gitlab create mode 100644 lib/api/milestones.rb rename lib/gitlab/{ => backend}/gitolite.rb (72%) rename {config/initializers => lib/gitlab/backend}/grack_auth.rb (87%) delete mode 100644 lib/gitlab/git_host.rb rename lib/{post-receive-hook => hooks/post-receive} (100%) create mode 100644 lib/support/init-gitlab create mode 100644 lib/support/nginx-gitlab create mode 100644 lib/tasks/bulk_add_permission.rake delete mode 100644 lib/tasks/gitlab/update_hooks.rake create mode 100644 lib/tasks/gitlab/write_hook.rake create mode 100644 spec/factories_spec.rb delete mode 100644 spec/factory.rb create mode 100644 spec/helpers/application_helper_spec.rb delete mode 100644 spec/monkeypatch.rb rename spec/{models => observers}/activity_observer_spec.rb (100%) rename spec/{models => observers}/issue_observer_spec.rb (58%) create mode 100644 spec/observers/key_observer_spec.rb rename spec/{models => observers}/user_observer_spec.rb (100%) create mode 100644 spec/observers/users_project_observer_spec.rb rename spec/{ => requests}/api/issues_spec.rb (73%) create mode 100644 spec/requests/api/milestones_spec.rb rename spec/{ => requests}/api/projects_spec.rb (67%) rename spec/{ => requests}/api/users_spec.rb (76%) create mode 100644 spec/roles/issue_commonality_spec.rb create mode 100644 spec/roles/upvote_spec.rb delete mode 100644 spec/support/api.rb create mode 100644 spec/support/api_helpers.rb create mode 100644 spec/support/db_cleaner.rb create mode 100644 spec/support/gitolite_stub.rb delete mode 100644 spec/support/js_patch.rb delete mode 100644 spec/support/login.rb create mode 100644 spec/support/login_helpers.rb delete mode 100644 spec/support/shared_examples.rb create mode 100644 spec/support/stubbed_repository.rb diff --git a/.rails_footnotes b/.rails_footnotes deleted file mode 100644 index 1019a70a..00000000 --- a/.rails_footnotes +++ /dev/null @@ -1,3 +0,0 @@ -#this code temporarily disables notes for all controllers -# Footnotes::Filter.notes = [] - diff --git a/CHANGELOG b/CHANGELOG index 80b68b0a..7322efbf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,8 +1,32 @@ +v 2.9.0 + - fixed inline notes bugs + - refactored rspecs + - refactored gitolite backend + - added factory_girl + - restyled projects list on dashboard + - ssh keys validation to prevent gitolite crash + - send notifications if changed premission in project + - scss refactoring. gitlab_bootstrap/ dir + - fix git push http body bigger than 112k problem + - list of labels page under issues tab + - API for milestones + - restyled buttons + +v 2.8.1 + - ability to disable gravatars + - improved MR diff logic + - ssh key help page + v 2.8.0 - Gitlab Flavored Markdown - Bulk issues update - Issues API - Cucumber coverage increased + - Post-receive files fixed + - UI improved + - Application cleanup + - more cucumber + - capybara-webkit + headless v 2.7.0 - Issue Labels diff --git a/Gemfile b/Gemfile index e8b0b244..b0724fad 100644 --- a/Gemfile +++ b/Gemfile @@ -54,7 +54,7 @@ gem "unicorn" gem "acts-as-taggable-on", "2.3.1" # Decorators -gem "drapper" +gem "draper" # Background jobs gem "resque", "~> 1.20.0" @@ -92,7 +92,6 @@ end group :development do gem "letter_opener" - gem "rails-footnotes" gem "annotate", :git => "https://github.com/ctran/annotate_models.git" gem 'rack-mini-profiler' end @@ -108,15 +107,18 @@ group :development, :test do gem "awesome_print" gem "database_cleaner" gem "launchy" + gem 'factory_girl_rails' end group :test do gem 'cucumber-rails', :require => false - gem 'minitest', ">= 2.10" - gem "turn", :require => false gem "simplecov", :require => false gem "shoulda-matchers" gem 'email_spec' gem 'resque_spec' gem "webmock" end + +group :production do + gem "gitlab_meta", '2.9' +end diff --git a/Gemfile.lock b/Gemfile.lock index b23bc47c..f226c931 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -99,7 +99,6 @@ GEM acts-as-taggable-on (2.3.1) rails (~> 3.0) addressable (2.2.8) - ansi (1.4.2) arel (3.0.2) autotest (4.4.6) ZenTest (>= 4.4.1) @@ -156,7 +155,9 @@ GEM railties (~> 3.1) warden (~> 1.2.1) diff-lcs (1.1.3) - drapper (0.8.4) + draper (0.17.0) + actionpack (~> 3.2) + activesupport (~> 3.2) email_spec (1.2.1) mail (~> 2.2) rspec (~> 2.0) @@ -165,6 +166,11 @@ GEM eventmachine (0.12.10) execjs (1.4.0) multi_json (~> 1.0) + factory_girl (4.0.0) + activesupport (>= 3.0.0) + factory_girl_rails (4.0.0) + factory_girl (~> 4.0.0) + railties (>= 3.0.0) ffaker (1.14.0) ffi (1.0.11) foreman (0.47.0) @@ -172,6 +178,7 @@ GEM gherkin (2.11.0) json (>= 1.4.6) git (1.2.5) + gitlab_meta (2.9) grape (0.2.1) hashie (~> 1.2) multi_json @@ -218,7 +225,6 @@ GEM treetop (~> 1.4.8) method_source (0.7.1) mime-types (1.19) - minitest (3.1.0) modernizr (2.5.3) sprockets (~> 2.0) multi_json (1.3.6) @@ -258,8 +264,6 @@ GEM activesupport (= 3.2.8) bundler (~> 1.0) railties (= 3.2.8) - rails-footnotes (3.7.8) - rails (>= 3.0.0) railties (3.2.8) actionpack (= 3.2.8) activesupport (= 3.2.8) @@ -349,8 +353,6 @@ GEM treetop (1.4.10) polyglot polyglot (>= 0.3.1) - turn (0.9.5) - ansi tzinfo (0.3.33) uglifier (1.0.3) execjs (>= 0.3.0) @@ -389,11 +391,13 @@ DEPENDENCIES cucumber-rails database_cleaner devise (~> 2.1.0) - drapper + draper email_spec + factory_girl_rails ffaker foreman git + gitlab_meta (= 2.9) gitolite! grack! grape (~> 0.2.1) @@ -407,7 +411,6 @@ DEPENDENCIES launchy letter_opener linguist (~> 1.0.0)! - minitest (>= 2.10) modernizr (= 2.5.3) mysql2 omniauth-ldap! @@ -415,7 +418,6 @@ DEPENDENCIES pygments.rb! rack-mini-profiler rails (= 3.2.8) - rails-footnotes raphael-rails (= 1.5.2) redcarpet (~> 2.1.1) resque (~> 1.20.0) @@ -432,7 +434,6 @@ DEPENDENCIES stamp therubyracer thin - turn uglifier (= 1.0.3) unicorn webmock diff --git a/README.md b/README.md index 26ed209e..122cd984 100644 --- a/README.md +++ b/README.md @@ -39,5 +39,6 @@ Email ## Contribute +[Development Tips](https://github.com/gitlabhq/gitlabhq/blob/master/doc/development.md) Want to help - send a pull request. We'll accept good pull requests. diff --git a/VERSION b/VERSION index 3f80e494..a564e653 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.8.0pre +2.9.0pre diff --git a/app/assets/images/file_dir.png b/app/assets/images/file_dir.png index 97b0539393d29c78a596f565087b9282df0f0291..ea277bb14dba38fa633a32675ee58b88b88f80a4 100644 GIT binary patch delta 1613 zcmV-T2D15u1n>-yBYy-WX+uL$Nkc;*P;zf(X>4Tx05}naRo`#hR1`jmZ&IWdKOk5~ zhl<6oRa0BJ8yc;~21%2p?MfD<>DVeH9(p*dx19w`~g7O0}n_%Aq@s% zd)fBDv`JHkDym6Hd+5XuAtvnwRpGmKVkc9?T=n|PIo~$m6N9yl%7qB(zDb{1JtYOPA!qk%@Qub75C2T;T?(y%k7cw{!aEIzj}+@(TO5Y zz-|>CeHr)+QOwk9wo4Sh2mAPGw+7tv9PraN1Olr9mwlWPc+tlvJx(D%369Vz>lLjC zyaqhH*k&)cVSir(7|4_1Rc;%)B4&}Zt&(AK&*3kRaknx54=$T^^z2=OSo@r%xnsBw ziQX&rRKAHn*R}K<@Jp~?uenKKe+BrD`qF#?cpp*psgiwk-seYOU#%402HpjG(Qu~= zP5WwfF?$>svC;J<>zK$RIM;P&60@WifbUlLwAf?lk$1u(mFRYq89Q?S8HyrsX^MVnpsf`{W6(5UXREr zZM&7ilpo6ux^|%%%N9z;(dj0Ct!8>>5!%=VyPg-?ke}Vs>~sbgdDuU^Ixpr19NS&g zQew8Fz<)>RL_{M_7A<1csY*MfP@XcB#Jxr~JS8&7goVS)VKE|4(h_Xlc{z{c$ApZs z7riZ_QKdV_uW-M~u~8PF3UX~a6)MwxDE0Ha zPjyrlI!;jX{6Kvuh*8ej?;85ekN$?5uuCiSBY#iOF*IB46Cei@XmvNzKBsGIKkN=X z$_Bgm&h5TyYjpf^^Nrn$ZO1mSZpOC4_}H(pE&Od#0y|)Oik6|u@OHupcq1J>TmjQ1 z4eM{2+Oeah>iL1tBPPK=$Ue-{x#7^N{-XY=Zm3_V7u4T7YJ?d(rX+nTeJlMa zeSar?Fa1o4bXmG0{UlwLzL72kIlJ$@pC#CPuU}yk-HKgk)oY(MCsR(!d*m18l>EH> zvOF8G6uDm>lc(gp@azfp%?$k=y%=-~vuaJXdoA$<7&H$h2Au}i##F_Gt8^Aw+nR zkw6!J0rE*iK~y*qrBbnKRY4G)-McTT6G=piAc84kDWVTT6hg{?rPydCf<-+!r#lj$ng=H+oN&>+I6T-{e-P!S+-TUGz1Q8eJ?#|AfIcH|CGc)<06$l<&UENr@ zwf1Pb(3elUJL&6(x6^ljI@baOKYn=s0TZZy$H89H5&3HYw|wvEvxWJ^yX$vuU-6F6 zw%+Z2{KnJo8+szXZBnrB9~`_7i*s|s=i6JuN?n6lTo+_xc=J-y3`r&lvZ7@v*`!#h zySN#7@#gS85TvQ6ckVs#qLpndV=Phf1WC&rX~-X4J_b) zs{kBPOmXL6Q~;mF6kp9LpjwHz>N4bdBvA)I=E#N_00iX#T8$Zi%qbX#0vyo^P&k}_#3Z4lNe4Z+V(J+q#Jlu=L@(bus&a$C+M_c( zoW+_(AG53jtUpcHnEBpRK=b&J4&)M?6^rAM49W;YX##ta#)&EfwI%Xox*Gs-L!r2nwN*UoS2#&-?;(lO*~KF&Bai00000 LNkvXXu0mjfR5}Yv delta 474 zcmV<00VV$M421-cBOL$%b5ch_0Itp)=>Px#24YJ`L;(K){{a7>y{D6tQ3Mx%00(qQ zO+^RX2OSA87wSK)9RL6Ug-Jv~R5;7ElQD`GK@f$%*VFzk;vvKXh?ypaf@cuSOiet6 zVq#=ux@cfxWMC$Sf?#Co0TxA9cXp<$oHh71YY)y75WthyAKuhqxU+wlw&xZPLDfK}X%WnkgiS=Y z$Mk;f+6i2wS|xDysGHzLcODn4Uxu}$c`78TNB$yb&mX&2~x+myO0-ufpjnKdv9Y# z8sV>XoZ@>OdQcKzjBvUqb`=uHOuD{0RpgI4&h5CgvUM%eY9g6^wN3VfzNcdtj_b+P zPKwS1`YkZ&rEoo4b0#$u4Pe^*ZHIJYLrI!j}Zc0q;$ zAmJq-$()<~lGPaGSXpPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iyo9 z5;+7dV2sKD00GfSL_t(Y$IX?oiq%jMhQB{v1e-0i5o8;|NARQ;1sg$01PfUkZL|;z zD~p5#Ec5}k5g#D|J5ji5B}rjrsd7QAw^M{=e~Tn|mv!$Yl0^qXrbysMY4b@Bv07XKNF;C(uqe+UDkkJ6i?kjm{cp0jE z)+EJ*5*gbd$&xNhei7ICNq|sg2NR0x{5Y@$Yyw^#+`B?o5$A`7AY^!teXC8&PjQve zq~$)q2JqTA2(4={a;Fm@u<8C0_$%mIlN299mC^Q)sLy(DUGOf7KtsT;BZF_V3A_qb z7KY%V&i>{U9R|n1DT%jnoo@lONy%)o36#{UcmnY(RM{Dcr_uDYE-+FDoUnpUB3Xca zuT9G5lLBl@(huw_ZCcKUxrhMS){w%g2YWf&eZHg};~Q;S&WF{}rX@4*uB4AkCt+11 z_kgp&>1A0k-;8gzY56X$yZtoS4D=niZbldc|`v!EONs7gvmfVs=qX+63FbA$TX|WUPj5aAJy_fIARlaeg fm;Pb9|DV_|IBUXuSip0M00000NkvXXu0mjfn+gJJ literal 0 HcmV?d00001 diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index d9cbd5d6..f69fd6f9 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -72,7 +72,7 @@ $(document).ready(function(){ * Note markdown preview * */ - $('#preview-link').on('click', function(e) { + $(document).on('click', '#preview-link', function(e) { $('#preview-note').text('Loading...'); var previewLinkText = ($(this).text() == 'Preview' ? 'Edit' : 'Preview'); @@ -128,3 +128,23 @@ function showDiff(link) { function ajaxGet(url) { $.ajax({type: "GET", url: url, dataType: "script"}); } + +/** + * Disable button if text field is empty + */ +function disableButtonIfEmtpyField(field_selector, button_selector) { + field = $(field_selector); + if(field.val() == "") { + field.closest("form").find(button_selector).attr("disabled", "disabled").addClass("disabled"); + } + + field.on('keyup', function(){ + var field = $(this); + var closest_submit = field.closest("form").find(button_selector); + if(field.val() == "") { + closest_submit.attr("disabled", "disabled").addClass("disabled"); + } else { + closest_submit.removeAttr("disabled").removeClass("disabled"); + } + }) +} diff --git a/app/assets/javascripts/issues.js b/app/assets/javascripts/issues.js index bc056965..aae818de 100644 --- a/app/assets/javascripts/issues.js +++ b/app/assets/javascripts/issues.js @@ -5,6 +5,7 @@ function switchToNewIssue(form){ $('select#issue_milestone_id').chosen(); $("#new_issue_dialog").show("fade", { direction: "right" }, 150); $('.top-tabs .add_new').hide(); + disableButtonIfEmtpyField("#issue_title", ".save-btn"); }); } @@ -15,6 +16,7 @@ function switchToEditIssue(form){ $('select#issue_milestone_id').chosen(); $("#edit_issue_dialog").show("fade", { direction: "right" }, 150); $('.add_new').hide(); + disableButtonIfEmtpyField("#issue_title", ".save-btn"); }); } diff --git a/app/assets/javascripts/note.js b/app/assets/javascripts/note.js index d9ae45d9..9cd3e36e 100644 --- a/app/assets/javascripts/note.js +++ b/app/assets/javascripts/note.js @@ -1,161 +1,160 @@ var NoteList = { -notes_path: null, -target_params: null, -target_id: 0, -target_type: null, -first_id: 0, -last_id: 0, -disable:false, + notes_path: null, + target_params: null, + target_id: 0, + target_type: null, + first_id: 0, + last_id: 0, + disable:false, -init: - function(tid, tt, path) { - this.notes_path = path + ".js"; - this.target_id = tid; - this.target_type = tt; - this.target_params = "&target_type=" + this.target_type + "&target_id=" + this.target_id; + init: + function(tid, tt, path) { + this.notes_path = path + ".js"; + this.target_id = tid; + this.target_type = tt; + this.target_params = "&target_type=" + this.target_type + "&target_id=" + this.target_id; - // get notes - this.getContent(); + // get notes + this.getContent(); - // get new notes every n seconds - this.initRefresh(); + // get new notes every n seconds + this.initRefresh(); - $('.delete-note').live('ajax:success', function() { - $(this).closest('li').fadeOut(); }); + $('.delete-note').live('ajax:success', function() { + $(this).closest('li').fadeOut(); }); - $("#new_note").live("ajax:before", function(){ - $(".submit_note").attr("disabled", "disabled"); - }) + $(".note-form-holder").live("ajax:before", function(){ + $(".submit_note").attr("disabled", "disabled"); + }) - $("#new_note").live("ajax:complete", function(){ - $(".submit_note").removeAttr("disabled"); - }) + $(".note-form-holder").live("ajax:complete", function(){ + $(".submit_note").removeAttr("disabled"); + }) - $("#note_note").live("focus", function(){ - $(this).css("height", "80px"); - $('.note_advanced_opts').show(); - }); + disableButtonIfEmtpyField(".note-text", ".submit_note"); - $("#note_attachment").change(function(e){ + $(".note-text").live("focus", function(){ + $(this).css("height", "80px"); + $('.note_advanced_opts').show(); + }); + + $("#note_attachment").change(function(e){ var val = $('.input-file').val(); var filename = val.replace(/^.*[\\\/]/, ''); $(".file_name").text(filename); - }); + }); - }, + }, -/** - * Load new notes to fresh list called 'new_notes_list': - * - Replace 'new_notes_list' with new list every n seconds - * - Append new notes to this list after submit - */ + /** + * Load new notes to fresh list called 'new_notes_list': + * - Replace 'new_notes_list' with new list every n seconds + * - Append new notes to this list after submit + */ -initRefresh: - function() { - // init timer - var intNew = setInterval("NoteList.getNew()", 10000); - }, + initRefresh: + function() { + // init timer + var intNew = setInterval("NoteList.getNew()", 10000); + }, -replace: - function(html) { - $("#new_notes_list").html(html); - }, + replace: + function(html) { + $("#new_notes_list").html(html); + }, -prepend: - function(id, html) { - if(id != this.last_id) { - $("#new_notes_list").prepend(html); - } - }, + prepend: + function(id, html) { + if(id != this.last_id) { + $("#new_notes_list").prepend(html); + } + }, -getNew: - function() { - // refersh notes list - $.ajax({ - type: "GET", + getNew: + function() { + // refersh notes list + $.ajax({ + type: "GET", url: this.notes_path, data: "last_id=" + this.last_id + this.target_params, dataType: "script"}); - }, + }, -refresh: - function() { - // refersh notes list - $.ajax({ - type: "GET", + refresh: + function() { + // refersh notes list + $.ajax({ + type: "GET", url: this.notes_path, data: "first_id=" + this.first_id + "&last_id=" + this.last_id + this.target_params, dataType: "script"}); - }, + }, -/** - * Init load of notes: - * 1. Get content with ajax call - * 2. Set content of notes list with loaded one - */ + /** + * Init load of notes: + * 1. Get content with ajax call + * 2. Set content of notes list with loaded one + */ -getContent: - function() { - $.ajax({ - type: "GET", + getContent: + function() { + $.ajax({ + type: "GET", url: this.notes_path, data: "?" + this.target_params, complete: function(){ $('.status').removeClass("loading")}, beforeSend: function() { $('.status').addClass("loading") }, dataType: "script"}); - }, + }, -setContent: - function(fid, lid, html) { + setContent: + function(fid, lid, html) { this.last_id = lid; this.first_id = fid; $("#notes-list").html(html); // Init infinite scrolling this.initLoadMore(); - }, + }, -/** - * Paging for old notes when scroll to bottom: - * 1. Init scroll events with 'initLoadMore' - * 2. Load onlder notes with 'getOld' method - * 3. append old notes to bottom of list with 'append' - * - */ + /** + * Paging for old notes when scroll to bottom: + * 1. Init scroll events with 'initLoadMore' + * 2. Load onlder notes with 'getOld' method + * 3. append old notes to bottom of list with 'append' + * + */ + getOld: + function() { + $('.loading').show(); + $.ajax({ + type: "GET", + url: this.notes_path, + data: "first_id=" + this.first_id + this.target_params, + complete: function(){ $('.status').removeClass("loading")}, + beforeSend: function() { $('.status').addClass("loading") }, + dataType: "script"}); + }, + append: + function(id, html) { + if(this.first_id == id) { + this.disable = true; + } else { + this.first_id = id; + $("#notes-list").append(html); + } + }, -getOld: - function() { - $('.loading').show(); - $.ajax({ - type: "GET", - url: this.notes_path, - data: "first_id=" + this.first_id + this.target_params, - complete: function(){ $('.status').removeClass("loading")}, - beforeSend: function() { $('.status').addClass("loading") }, - dataType: "script"}); - }, - -append: - function(id, html) { - if(this.first_id == id) { - this.disable = true; - } else { - this.first_id = id; - $("#notes-list").append(html); - } - }, - - -initLoadMore: - function() { - $(document).endlessScroll({ - bottomPixels: 400, + initLoadMore: + function() { + $(document).endlessScroll({ + bottomPixels: 400, fireDelay: 1000, fireOnce:true, ceaseFire: function() { @@ -164,6 +163,20 @@ initLoadMore: callback: function(i) { NoteList.getOld(); } - }); - } + }); + } +}; + +var PerLineNotes = { + init: + function() { + $(".line_note_link, .line_note_reply_link").live("click", function(e) { + var form = $(".per_line_form"); + $(this).closest("tr").after(form); + form.find("#note_line_code").val($(this).attr("line_code")); + form.show(); + return false; + }); + disableButtonIfEmtpyField(".line-note-text", ".submit_inline_note"); + } } diff --git a/app/assets/javascripts/projects.js b/app/assets/javascripts/projects.js index 84272698..be1b75b9 100644 --- a/app/assets/javascripts/projects.js +++ b/app/assets/javascripts/projects.js @@ -7,8 +7,10 @@ function Projects() { $('.new_project, .edit_project').live('ajax:before', function() { $('.project_new_holder, .project_edit_holder').hide(); - $('.ajax_loader').show(); + $('.save-project-loader').show(); }); $('form #project_default_branch').chosen(); + + disableButtonIfEmtpyField("#project_name", ".project-submit") } diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss index 6103d05c..aa27a280 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -1,11 +1,9 @@ -.diff_file_header a, -.file_stats a { - color:$style_color; -} - - /** LAYOUT **/ +body { + margin-bottom:20px; +} + .container { padding-top:0; z-index:5; @@ -40,30 +38,6 @@ color: $link_color; } -.widget { - @include shade; - padding:20px; - margin-bottom:20px; - border: 1px solid #DDD; - border-radius: 5px; - background:#fafafa; - - .link_holder { - background:#eee; - position:relative; - left:-20px; - top:20px; - padding:10px 20px; - width:100%; - border-top:1px solid #ccc; - - a { - font-size:14px; - color:#666; - } - } -} - .help li { color:#111 } .back_link { @@ -88,16 +62,6 @@ padding-left:20px; } -.number { - border-radius: 4px; - text-shadow: none; - background: rgba(0,0,0,.12); - text-align: center; - padding: 2px 4px; - line-height:18px; - margin-left:2px; -} - table a code { position: relative; top: -2px; @@ -129,26 +93,18 @@ table a code { border-bottom:1px solid #ccc; h4 { - color:#444; - font-size:22px; + color:#666; + font-size:18px; + line-height:38px; padding-top:5px; margin:2px; + font-weight:normal; } } .git_url_wrapper { margin-right:50px } -.file_stats { - span { - img { - width:14px; - float:left; - margin-right:6px; - padding:2px 0; - } - } -} .handle:hover { cursor:move; @@ -172,10 +128,6 @@ span.update-author { display:block; } /** END UPDATE ITEM **/ -.ajax-tab-loading { - padding:40px; - display:none; -} .dashboard-loader { float:left; margin:10px; @@ -186,15 +138,110 @@ span.update-author { font-weight:bold; } -a.project-update.titled { - position:relative; - padding-left:35% !important; - .title-block { - padding:10px; - width:35%; - position:absolute; - left:0; - top:0; +.neib { + margin-right:10px; +} + +.label { + background-color: #474D57; + + &.label-issue { + background-color: #eee; + border: 1px solid #ccc; + padding:4px 6px; + color:#444; + text-shadow:0 0 1px #fff; + + &.grouped { + float: left; + margin-right: 6px; + padding: 6px; + } + } +} + +.event_label { + @extend .label; + background-color: #999; + + &.pushed { + background-color: #4A97BD; + } + + &.opened { + background-color: #469847; + } + + &.closed { + background-color: #B94A48; + } + + &.merged { + background-color: #2A2; + } +} + +form { + @extend .form-horizontal; + + .actions { + @extend .form-actions; + } + + .clearfix { + @extend .control-group; + } + + .input { + @extend .controls; + } + + label { + @extend .control-label; + } + .xlarge { + @extend .input-xlarge; + } + .xxlarge { + @extend .input-xxlarge; + } +} + + +.field_with_errors { + display:inline; +} + +ul.breadcrumb { + background:white; + border:none; + li { + display: inline; + text-shadow: 0 1px 0 white + } + + a { + color:#474D57; + font-weight:bold; + font-size:14px; + } + + .arrow { + background: url("images.png") no-repeat -85px -77px; + width: 19px; + height: 16px; + float: left; + position: relative; + left: -10px; + padding:0; + margin:0; + } +} + +input[type=text] { + &.large_text { + padding:6px; + font-size:16px; } } @@ -270,40 +317,6 @@ p.time { } -/** - * Dashboard page - * - */ -.dashboard_category { - margin-bottom:30px; - h3 a { - color:#474D57; - &:hover { - text-decoration:underline; - } - } - - .dashboard_block { - .dash_project_item { - margin-bottom:10px; - border:none; - padding:0px 5px; - .project_link { - color:#888; - &:hover { - color:#111; - .ico.project { - background-position:-209px -21px; - } - } - } - h4 { - color:#666; - } - } - } -} - .styled_image { border:2px solid #ddd; } @@ -393,39 +406,6 @@ p.time { } } -.btn { - &.very_small { - font-size:11px; - padding:2px 6px; - margin:2px; - } - - &.grouped { - margin-right:7px; - float:left; - } - - &.padded { - margin-right:3px; - padding:4px 10px 4px; - } -} - - -.prettyprint { - background-color: #fefbf3; - padding: 9px; - border: 1px solid rgba(0,0,0,.2); - -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.1); - -moz-box-shadow: 0 1px 2px rgba(0,0,0,.1); - box-shadow: 0 1px 2px rgba(0,0,0,.1); -} - -.hint { - font-style: italic; - color: #999; -} - .upvotes { font-size: 14px; font-weight: bold; @@ -549,14 +529,6 @@ li.note { } -/** - * Milestones list - * - */ - -.milestone { - @extend .wll; -} /** * Admin area @@ -603,11 +575,10 @@ li.note { * */ .event_lp { - @extend .alert-info; + @extend .ui-box; + color:#777; margin-bottom:20px; padding:8px; - border-style: solid; - border-width: 1px; @include border-radius(4px); min-height:22px; @@ -621,88 +592,19 @@ li.note { cursor:pointer; } -/** - * Issues, MRs legend - * - */ - -.list_legend { - float:left; - margin-right:20px; - .icon { - width:12px; - height:12px; - float:left; - margin-right:5px; - margin-top: 2px; - @include border-radius(4px); - &.today{ - background: #ADA; - border:1px solid #8B8; - } - &.closed { - background: #DDD; - border:1px solid #BBB; - } - &.yours { - background: #AAD; - border:1px solid #88B; - } - &.merged { - background: #DAD; - border:1px solid #B8B; - } - } - .text { - padding-bottom: 10px; - float:left; - } -} - .merge_request, .issue { - .list_legend { - margin-right: 5px; - margin-top: 14px; - .icon { - width:8px; - height:8px; - float:left; - margin-right:5px; - @include border-radius(4px); - border:1px solid #ddd; - } - } - &.today{ background: #EFE; border-color:#CEC; - .icon { - background: #ADA; - border:1px solid #8B8; - } } &.closed { background: #F5f5f5; border-color:#E5E5E5; - .icon { - background: #DDD; - border:1px solid #BBB; - } - } - &.yours { - .icon { - background: #AAD; - border:1px solid #88B; - } } &.merged { background: #F5f5f5; border-color:#E5E5E5; - .icon { - background: #DAD; - border:1px solid #B8B; - } } } @@ -735,3 +637,11 @@ li.note { font-size: 12px; } } + +.error_message { + @extend .cred; + border-bottom: 1px solid #D21; + padding-bottom:20px; + text-align:center; + margin-bottom:10px; +} diff --git a/app/assets/stylesheets/gitlab_bootstrap.scss b/app/assets/stylesheets/gitlab_bootstrap.scss deleted file mode 100644 index 550046d0..00000000 --- a/app/assets/stylesheets/gitlab_bootstrap.scss +++ /dev/null @@ -1,818 +0,0 @@ -body { - margin-bottom:20px; -} -a { - outline: none; - color: $link_color; - &:hover { - text-decoration:none; - color: $blue_link; - } - - &.btn { - color: $style_color; - } - - &.dark { - color: $style_color; - } - - &.lined { - text-decoration:underline; - &:hover { text-decoration:underline; } - } - - &.gray { - color:gray; - } - - &.supp_diff_link { - text-align:center; - padding:20px 0; - background:#f1f1f1; - width:100%; - float:left; - } - - &.neib { - margin-right:15px; - } -} - -.neib { - margin-right:10px; -} - -.alert-message { - @extend .alert; - - &.success { - @extend .alert-success; - } - - &.error { - @extend .alert-error; - } -} - -.alert { - &.alert-well { - background:#ddd; - border:1px solid #ccc; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #ddd), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#ddd 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#ddd 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#ddd 6.6%, #dfdfdf); - color:#111; - } -} - -h3, h4, h5, h6 { - line-height: 36px; -} - -h5 { - font-size:14px; -} - - -table { - width:100%; - th { - padding-top: 9px; - font-weight: bold; - vertical-align: middle; - } - th, td { - padding: 10px 10px 9px; - line-height: 18px; - text-align: left; - } - - &.bordered-table { - border: 1px solid #DDD; - border-collapse: separate; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - } - - &.zebra-striped { - @extend .table-striped; - } -} - -.btn { - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f1f1f1), color-stop(25%, #f1f1f1), to(#e6e6e6)); - background-image: -webkit-linear-gradient(#f1f1f1, #f1f1f1 25%, #e6e6e6); - background-image: -moz-linear-gradient(top, #f1f1f1, #f1f1f1 25%, #e6e6e6); - background-image: -ms-linear-gradient(#f1f1f1, #f1f1f1 25%, #e6e6e6); - background-image: -o-linear-gradient(#f1f1f1, #f1f1f1 25%, #e6e6e6); - background-image: linear-gradient(#f1f1f1, #f1f1f1 25%, #e6e6e6); - - &:hover { - } - - &.btn-primary { - background:$link_color; - border-color: #2A79A3; - &:hover { - background:$blue_link; - } - } - &.primary { - @extend .btn-primary; - } - - &.success { - color: #fff; - text-shadow: 0 0 1px #111; - background: #5bb75b;; - font-weight: bold; - - &:hover { - background-color: #51a351; - color: #fff; - } - } - - &.danger, - &.btn-danger { - color:#fff; - background: #DA4E49; - border-color: #BD362F; - - &:hover { - color:#fff; - background: #EE4E49; - } - } - - &.danger { - @extend .btn-danger; - } - - &.small { - @extend .btn-small; - } - - &.active { - border-color:#aaa; - background-color:#ccc; - } -} - -a:focus { - outline: none; -} - -.nav-pills a:hover { - background-color:#888; -} - -.nav-pills .active a { - background-color: $style_color; -} - -.label { - background-color: #474D57; - &.label-important { - background-color: #B94A48; - } - - &.label-issue { - background-color: #eee; - border: 1px solid #ccc; - padding:4px 6px; - color:#444; - text-shadow:0 0 1px #fff; - - &.grouped { - float: left; - margin-right: 6px; - padding: 6px; - } - } -} - -.nav-tabs > li > a, .nav-pills > li > a { - color:$style_color; -} - -.nav-tabs > .active > a { - font-weight:bold; -} - -/** COLORS **/ -.cgray { color:gray; } -.cred { color:#D12F19; } -.cgreen { color:#44aa22; } -.cblack { color:#111; } -.cdark { color:#444 } -.cwhite { color:#fff !important } -.bgred { background: #F2DEDE !important} - -/** COMMON STYLES **/ -.left { - float:left; -} -.right { - float:right !important; -} -.width-50p{ - width:50%; -} -.width-49p{ - width:49%; -} -.width-30p{ - width:30%; -} -.width-65p{ - width:65%; -} -.width-100p{ - width:100%; -} -.append-bottom-10 { - margin-bottom:10px; -} -.append-bottom-20 { - margin-bottom:20px; -} -.prepend-top-10 { - margin-top:10px; -} - -.prepend-top-20 { - margin-top:20px; -} - -.padded { - padding:20px; -} - -.ipadded { - padding:20px !important; -} -.lborder { - border-left:1px solid #eee; -} - -.borders { - border: 1px solid #ccc; - @include shade; -} -.no-borders { - border:none; -} -table.no-borders { - border:none; - tr, td { border:none } -} -.no-padding { - padding:0 !important; -} -.underlined { - border-bottom: 1px solid $border_color; -} -.vlink { - color: $link_color !important; -} - -.pretty_label { - @include round-borders-all(4px); - padding:2px 4px; - background-image: -webkit-gradient(linear, 0 0, 0 26, color-stop(0.076, #fefefe), to(#F6F7F8)); - background-image: -webkit-linear-gradient(#fefefe 7.6%, #F6F7F8); - background-image: -moz-linear-gradient(#fefefe 7.6%, #F6F7F8); - background-image: -o-linear-gradient(#fefefe 7.6%, #F6F7F8); - color: #777; - border: 1px solid #DEDFE1; - - &.branch { - border:none; - font-size:13px; - background: #474D57; - color:#fff; - font-weight:bold; - font-family: monospace; - } -} - -.event_label { - @extend .label; - background-color: #999; - - &.pushed { - background-color: #3A87AD; - } - - &.opened { - background-color: #468847; - } - - &.closed { - background-color: #B94A48; - } - - &.merged { - background-color: #2A2; - } -} - -img.avatar { - float:left; - margin-right:15px; - width:40px; - border:2px solid #ddd; - - &.s16 { - width:16px; - } - &.s24 { - width:24px; - } - &.s32 { - width:32px; - } -} - -img.lil_av { - padding-left: 4px; - padding-right:3px; -} - -form { - @extend .form-horizontal; - - .actions { - @extend .form-actions; - } - - .clearfix { - @extend .control-group; - } - - .input { - @extend .controls; - } - - label { - @extend .control-label; - } - .xlarge { - @extend .input-xlarge; - } - .xxlarge { - @extend .input-xxlarge; - } -} - -/** - * List li block element #1 - * - */ -.wll { - background-color: #FFF; - padding: 10px 5px; - min-height: 20px; - border-bottom: 1px solid #eee; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); - &.smoke { - background-color:#f5f5f5; - } - &:hover { - background:$hover; - } - &:last-child { border:none } - p { padding-top:5px; margin:0; color:$style_color;} - .author { color: #999; } - p { - color:#222; - margin-bottom: 0; - img { - position:relative; - top:3px; - } - } -} - - -/** - * Block element #2 - * - */ -.entry { - position: relative; - padding: 7px 15px; - margin-bottom: 18px; - color: #404040; - filter:none; - - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); - - background:#F1F1F1; - border: 1px solid #ccc; - - - p { - color:$style_color; - margin-bottom: 0; - img { - position:relative; - top:3px; - } - } -} - - -/** - * Big UI Block for show page content - * - */ -.ui-box { - background:#F9F9F9; - margin-bottom: 25px; - @include round-borders-all(4px); - border-color: #CCC; - @include solid_shade; - - ul { - margin:0; - } - - h5, .title { - padding: 0 10px; - @include round-borders-top(4px); - border-bottom: 1px solid #bbb; - background:#eee; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); - - &.small { - line-height: 28px; - font-size: 14px; - line-height:28px; - text-shadow: 0 1px 1px white; - } - - form { - padding:9px 0; - margin:0px; - } - - .nav-pills { - li { - padding:3px 0; - &.active a { background-color:$style_color; } - a { - border-radius:7px; - } - } - } - } - - .bottom { - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); - @include round-borders-bottom(4px); - border-bottom:none; - border-top: 1px solid #bbb; - } - - &.padded { - h5, .title { - margin: -20px; - margin-bottom: 0; - padding: 5px 20px; - } - .middle_title { - background:#f5f5f5; - margin:20px -20px; - padding: 0 20px; - border-top:1px solid #eee; - border-bottom:1px solid #eee; - font-size:14px; - color:#777; - } - } - .row_title { - font-weight:bold; - color:#444; - &:hover { - color:#444; - text-decoration:underline; - } - } - - li, .wll { - padding:10px; - &:first-child { - @include round-borders-top(4px); - border-top:none; - } - - &:last-child { - @include round-borders-bottom(4px); - border:none; - } - } - -} - -table.admin-table { - @extend .table-bordered; - @extend .zebra-striped; - @include solid_shade; - th { - border-color: #CCC; - border-bottom: 1px solid #bbb; - background:#eee; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); - } -} - -.field_with_errors { - display:inline; -} - -ul.breadcrumb { - background:white; - border:none; - li { - display: inline; - text-shadow: 0 1px 0 white - } - - a { - color:#474D57; - font-weight:bold; - font-size:14px; - } - - .arrow { - background: url("images.png") no-repeat -85px -77px; - width: 19px; - height: 16px; - float: left; - position: relative; - left: -10px; - padding:0; - margin:0; - } -} - -.nothing_here_message { - text-align:center; - padding:20px; - color:#777; -} - -/** - * UI box element - * contains top, middle, bottom blocks - * - */ -.main_box { - @extend .borders; - @extend .prepend-top-20; - @extend .append-bottom-20; - border-width:1px; - @include solid_shade; - - - img { max-width: 100%; } - - pre { - code { - background: none !important; - } - } - - .top_box_content, - .middle_box_content, - .bottom_box_content { - padding:15px; - - pre { - background: none !important; - margin:0; - border:none; - padding:0; - } - } - - .middle_box_content { - border-radius:0; - border:none; - font-size:12px; - background-color:#f5f5f5; - border:none; - border-top:1px solid #eee; - } - - .bottom_box_content { - border-top:1px solid #eee; - } -} - -input[type=text] { - &.large_text { - padding:6px; - font-size:16px; - } -} - -p { - &.slead { - color:#456; - font-size:16px; - margin-bottom: 12px; - font-weight: 200; - line-height: 24px; - } -} - -h3.page_title { - color:#456; - font-size:20px; - font-weight: normal; - line-height: 28px; -} - -/** - * File content holder - * - */ -.file_holder { - border:1px solid #CCC; - margin-bottom:1em; - @include solid_shade; - - .file_title { - border-bottom: 1px solid #bbb; - background:#eee; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); - margin: 0; - font-weight: normal; - font-weight: bold; - text-align: left; - color: #666; - padding: 9px 10px; - height:18px; - - .options { - float:right; - margin-top: -5px; - } - - .file_name { - color:$style_color; - font-size:14px; - text-shadow: 0 1px 1px #fff; - small { - color:#999; - font-size:13px; - } - } - } - .file_content { - background:#fff; - font-size: 11px; - - &.wiki { - font-size: 13px; - code { - padding:0 4px; - } - padding:20px; - h1, h2 { - line-height: 46px; - } - h3, h4 { - line-height: 40px; - } - } - - &.image_file { - background:#eee; - text-align:center; - img { - padding:100px; - max-width:300px; - } - } - - &.blob_file { - - } - - /** - * Blame file - */ - &.blame { - tr { - border-bottom: 1px solid #eee; - } - td { - padding:5px; - } - .author, - .blame_commit { - background:#f5f5f5; - vertical-align:top; - } - .lines { - pre { - padding:0; - margin:0; - background:none; - border:none; - } - } - } - - &.logs { - background:#eee; - max-height: 700px; - overflow-y: auto; - - ol { - margin-left:40px; - padding: 10px 0; - border-left: 1px solid #CCC; - margin-bottom:0; - background: white; - li { - color:#888; - p { - margin:0; - color:#333; - line-height:24px; - padding-left: 10px; - } - - &:hover { - background:$hover; - } - } - } - } - - /** - * Code file - */ - &.code { - padding:0; - td.code { - width: 100%; - .highlight { - margin-left: 55px; - overflow:auto; - overflow-y:hidden; - } - } - .highlight pre { - white-space: pre; - word-wrap:normal; - } - - table.highlighttable { - border: none; - } - body.project-page table.highlighttable td { border: none } - table.highlighttable tr:hover { background:none;} - - table.highlighttable pre{ - line-height:16px !important; - font-size:12px !important; - } - - table.highlighttable .linenodiv pre { - text-align: right; - padding-right: 4px; - color:#666; - } - } - } -} diff --git a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss new file mode 100644 index 00000000..894cb304 --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss @@ -0,0 +1,145 @@ +/** + * =================================== + * Contain 3 main UI block elements: + * .main_box - for show pages + * .ui-box - for simple block & widgets + * =================================== + */ + +/** + * UI box element + * contains top, middle, bottom blocks + * + */ +.main_box { + @extend .borders; + @extend .prepend-top-20; + @extend .append-bottom-20; + border-width:1px; + @include solid_shade; + + + img { max-width: 100%; } + + pre { + code { + background: none !important; + } + } + + .top_box_content, + .middle_box_content, + .bottom_box_content { + padding:15px; + + pre { + background: none !important; + margin:0; + border:none; + padding:0; + } + } + + .middle_box_content { + border-radius:0; + border:none; + font-size:12px; + background-color:#f5f5f5; + border:none; + border-top:1px solid #eee; + } + + .bottom_box_content { + border-top:1px solid #eee; + } +} + +/** + * Big UI Block for show page content + * + */ +.ui-box { + background:#F9F9F9; + margin-bottom: 25px; + @include round-borders-all(4px); + border-color: #CCC; + @include solid_shade; + + ul { + margin:0; + } + + h5, .title { + padding: 0 10px; + @include round-borders-top(4px); + @include bg-gray-gradient; + border-bottom: 1px solid #bbb; + + &.small { + line-height: 28px; + font-size: 14px; + line-height:28px; + text-shadow: 0 1px 1px white; + } + + form { + padding:9px 0; + margin:0px; + } + + .nav-pills { + li { + padding:3px 0; + &.active a { background-color:$style_color; } + a { + border-radius:7px; + } + } + } + } + + .bottom { + @include bg-gray-gradient; + @include round-borders-bottom(4px); + border-bottom:none; + border-top: 1px solid #bbb; + } + + &.padded { + h5, .title { + margin: -20px; + margin-bottom: 0; + padding: 5px 20px; + } + .middle_title { + background:#f5f5f5; + margin:20px -20px; + padding: 0 20px; + border-top:1px solid #eee; + border-bottom:1px solid #eee; + font-size:14px; + color:#777; + } + } + .row_title { + font-weight:bold; + color:#444; + &:hover { + color:#444; + text-decoration:underline; + } + } + + li, .wll { + padding:10px; + &:first-child { + @include round-borders-top(4px); + border-top:none; + } + + &:last-child { + @include round-borders-bottom(4px); + border:none; + } + } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/buttons.scss b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss new file mode 100644 index 00000000..c838f3b2 --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss @@ -0,0 +1,105 @@ +.btn { + background-image: -webkit-gradient(linear, 0 0, 0 26, color-stop(0.076, #f7f7f7), to(#d5d5d5)); + background-image: -webkit-linear-gradient(#f7f7f7 7.6%, #d5d5d5); + background-image: -moz-linear-gradient(#f7f7f7 7.6%, #d5d5d5); + background-image: -o-linear-gradient(#f7f7f7 7.6%, #d5d5d5); + border-color:#aaa; + &:hover { + @include bg-gray-gradient; + border-color:#bbb; + color:#333; + } + + &.primary { + background:#2a79A3; + border-color: #2A79A3; + background-image: -webkit-linear-gradient(#47A7b7 7.6%, #2585b5); + background-image: -moz-linear-gradient(#47A7b7 7.6%, #2585b5); + background-image: -o-linear-gradient(#47A7b7 7.6%, #2585b5); + color:#fff; + text-shadow: 0 1px 1px #268; + &:hover { + background:$blue_link; + color:#fff; + } + + &.disabled { + color:#fff; + background:#29B; + } + } + + &.success { + border-color: #4A4; + background-image: -webkit-linear-gradient(#82D482 7.6%, #22B442); + background-image: -moz-linear-gradient(#82D482 7.6%, #22B442); + background-image: -o-linear-gradient(#82D482 7.6%, #22B442); + color: #fff; + text-shadow: 0 1px 1px #141; + + &:hover { + background: #6C6; + color: #fff; + } + + &.disabled { + color:#fff; + background:#2b2; + } + } + + &.save-btn { + @extend .wide; + @extend .primary; + } + + &.cancel-btn { + float:right; + } + + &.wide { + padding-left:30px; + padding-right:30px; + } + + &.danger, + &.btn-danger { + color:#fff; + background: #DA4E49; + border-color: #BD362F; + + &:hover { + color:#fff; + background: #EE4E49; + } + } + + &.danger { + @extend .btn-danger; + } + + &.small { + @extend .btn-small; + } + + &.active { + border-color:#aaa; + background-color:#ccc; + } + + &.very_small { + font-size:11px; + padding:2px 6px; + margin:2px; + } + + &.grouped { + margin-right:7px; + float:left; + } + + &.padded { + margin-right:3px; + padding:4px 10px 4px; + } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/common.scss b/app/assets/stylesheets/gitlab_bootstrap/common.scss new file mode 100644 index 00000000..cd7145c9 --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/common.scss @@ -0,0 +1,52 @@ +/** COLORS **/ +.cgray { color:gray } +.cred { color:#D12F19 } +.cgreen { color:#4a2 } +.cblack { color:#111 } +.cdark { color:#444 } +.cwhite { color:#fff!important } +.bgred { background:#F2DEDE!important } + +/** COMMON CLASSES **/ +.left { float:left } +.right { float:right!important } +.width-50p { width:50% } +.width-49p { width:49% } +.width-30p { width:30% } +.width-65p { width:65% } +.width-100p { width:100% } +.append-bottom-10 { margin-bottom:10px } +.append-bottom-20 { margin-bottom:20px } +.prepend-top-10 { margin-top:10px } +.prepend-top-20 { margin-top:20px } +.padded { padding:20px } +.ipadded { padding:20px!important } +.lborder { border-left:1px solid #eee } +.no-padding { padding:0 !important; } +.underlined { border-bottom: 1px solid #CCC; } +.no-borders { border:none; } +.vlink { color: $link_color !important; } +.borders { border: 1px solid #ccc; @include shade; } +.hint { font-style: italic; color: #999; } + +/** PILLS & TABS**/ +.nav-pills a:hover { background-color:#888; } +.nav-pills .active a { background-color: $style_color; } +.nav-tabs > li > a, .nav-pills > li > a { color:$style_color; } +.nav-tabs > .active > a { font-weight:bold; } + +/** ALERT MESSAGES **/ +.alert-message { @extend .alert; } +.alert-messag.success { @extend .alert-success; } +.alert-message.error { @extend .alert-error; } + +/** AVATARS **/ +img.avatar { float:left; margin-right:15px; width:40px; border:1px solid #ddd; padding:1px; } +img.avatar.s16 { width:16px; height:16px; } +img.avatar.s24 { width:24px; height:24px; } +img.avatar.s32 { width:32px; height:32px; } +img.lil_av { padding-left: 4px; padding-right:3px; } + +/** HELPERS **/ +.nothing_here_message { text-align:center; padding:20px; color:#777; } +p.slead { color:#456; font-size:16px; margin-bottom: 12px; font-weight: 200; line-height: 24px; } diff --git a/app/assets/stylesheets/gitlab_bootstrap/files.scss b/app/assets/stylesheets/gitlab_bootstrap/files.scss new file mode 100644 index 00000000..4ea5ed02 --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/files.scss @@ -0,0 +1,156 @@ +/** + * File content holder + * + */ +.file_holder { + border:1px solid #CCC; + margin-bottom:1em; + @include solid_shade; + + .file_title { + border-bottom: 1px solid #bbb; + @include bg-gray-gradient; + margin: 0; + font-weight: normal; + font-weight: bold; + text-align: left; + color: #666; + padding: 9px 10px; + height:18px; + + .options { + float:right; + margin-top: -5px; + } + + .file_name { + color:$style_color; + font-size:14px; + text-shadow: 0 1px 1px #fff; + small { + color:#999; + font-size:13px; + } + } + } + .file_content { + background:#fff; + font-size: 11px; + + &.wiki { + font-size: 13px; + code { + padding:0 4px; + } + padding:20px; + h1, h2 { + line-height: 46px; + } + h3, h4 { + line-height: 40px; + } + } + + &.image_file { + background:#eee; + text-align:center; + img { + padding:100px; + max-width:300px; + } + } + + &.blob_file { + + } + + /** + * Blame file + */ + &.blame { + tr { + border-bottom: 1px solid #eee; + } + td { + padding:5px; + } + .author, + .blame_commit { + background:#f5f5f5; + vertical-align:top; + } + .lines { + pre { + padding:0; + margin:0; + background:none; + border:none; + } + } + } + + &.logs { + background:#eee; + max-height: 700px; + overflow-y: auto; + + ol { + margin-left:40px; + padding: 10px 0; + border-left: 1px solid #CCC; + margin-bottom:0; + background: white; + li { + color:#888; + p { + margin:0; + color:#333; + line-height:24px; + padding-left: 10px; + } + + &:hover { + background:$hover; + } + } + } + } + + /** + * Code file + */ + &.code { + padding:0; + td.code { + width: 100%; + .highlight { + margin-left: 55px; + overflow:auto; + overflow-y:hidden; + } + } + .highlight pre { + white-space: pre; + word-wrap:normal; + } + + table.highlighttable { + border: none; + } + body.project-page table.highlighttable td { border: none } + table.highlighttable tr:hover { background:none;} + + table.highlighttable pre{ + line-height:16px !important; + font-size:12px !important; + } + + table.highlighttable .linenodiv pre { + text-align: right; + padding-right: 4px; + color:#666; + } + } + } +} + diff --git a/app/assets/stylesheets/gitlab_bootstrap/lists.scss b/app/assets/stylesheets/gitlab_bootstrap/lists.scss new file mode 100644 index 00000000..402ba04b --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/lists.scss @@ -0,0 +1,30 @@ +/** LISTS **/ + +ul { + /** + * List li block element #1 + * + */ + .wll { + background-color: #FFF; + padding: 10px 5px; + min-height: 20px; + border-bottom: 1px solid #eee; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + + &.smoke { background-color:#f5f5f5; } + &:hover { background:$hover; } + &:last-child { border:none } + .author { color: #999; } + + p { + padding-top:5px; + margin:0; + color:#222; + img { + position:relative; + top:3px; + } + } + } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/tables.scss b/app/assets/stylesheets/gitlab_bootstrap/tables.scss new file mode 100644 index 00000000..f78b1dee --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/tables.scss @@ -0,0 +1,41 @@ +table { + width:100%; + th { + padding-top: 9px; + font-weight: bold; + vertical-align: middle; + } + th, td { + padding: 10px 10px 9px; + line-height: 18px; + text-align: left; + } + + &.bordered-table { + border: 1px solid #DDD; + border-collapse: separate; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + } + + &.zebra-striped { + @extend .table-striped; + } +} + +table.admin-table { + @extend .table-bordered; + @extend .zebra-striped; + @include solid_shade; + th { + border-color: #CCC; + border-bottom: 1px solid #bbb; + @include bg-gray-gradient; + } +} + +table.no-borders { + border:none; + tr, td { border:none } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/typography.scss b/app/assets/stylesheets/gitlab_bootstrap/typography.scss new file mode 100644 index 00000000..97e85492 --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/typography.scss @@ -0,0 +1,71 @@ +/** + * Headers + * + */ +h3, h4, h5, h6 { line-height: 36px; } +h5 { font-size:14px; } +h3.page_title { + color:#456; + font-size:20px; + font-weight: normal; + line-height: 28px; +} + +/** CODE **/ +pre { + font-family:'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; + + &.dark { + background: #333; + color:#f5f5f5; + } +} + +/** + * Links + * + */ +a { + outline: none; + color: $link_color; + &:hover { + text-decoration:none; + color: $blue_link; + } + + &.btn { + color: $style_color; + &:hover { + color: $style_color; + } + } + + &.dark { + color: $style_color; + } + + &.lined { + text-decoration:underline; + &:hover { text-decoration:underline; } + } + + &.gray { + color:gray; + } + + &.supp_diff_link { + text-align:center; + padding:20px 0; + background:#f1f1f1; + width:100%; + float:left; + } + + &.neib { + margin-right:15px; + } +} + +a:focus { + outline: none; +} diff --git a/app/assets/stylesheets/main.scss b/app/assets/stylesheets/main.scss index ad8be0bc..be27d754 100644 --- a/app/assets/stylesheets/main.scss +++ b/app/assets/stylesheets/main.scss @@ -2,29 +2,13 @@ @import "bootstrap-responsive"; /** GITLAB colors **/ -$text_color:#222; -$lite_text_color: #666; -$link_color:#2A79A3; -$active_link_color:#2FA0BB; -$active_bg_color:#79C3E0; -$active_bd_color: #2FA0BB; -$border_color:#CCC; -$lite_border_color:#EEE; -$min_app_width:980px; -$max_app_width:980px; -$app_padding:20px; -$bg_color: #FFF; -$styled_border_color: #2FA0BB; -$color: "#4BB8D2"; +$link_color:#3A89A3; $blue_link: #2fa0bb; - - -/** Style colors **/ -$style_color: #474D57; -$hover: #FDF5D9; +$style_color: #474d57; +$hover: #fdf5d9; /** GITLAB Fonts **/ -@font-face { font-family: Korolev; src: url('korolev-medium-compressed.otf'); } +@font-face { font-family: Korolev; src: url('korolev-medium-compressed.otf'); } /** MIXINS **/ @mixin shade { @@ -72,7 +56,20 @@ $hover: #FDF5D9; border-radius: $radius; } +@mixin bg-gray-gradient { + background:#eee; + background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); + background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); + background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); + background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); +} +@mixin bg-dark-gray-gradient { + background:#eee; + background-image: -webkit-linear-gradient(#e9e9e9, #d7d7d7); + background-image: -moz-linear-gradient(#e9e9e9, #d7d7d7); + background-image: -o-linear-gradient(#e9e9e9, #d7d7d7); +} /** * Header of application. @@ -113,7 +110,13 @@ $hover: #FDF5D9; * Overrides some styles of twitter bootstrap. * Also give some common classes for gitlab app */ -@import "gitlab_bootstrap.scss"; +@import "gitlab_bootstrap/common.scss"; +@import "gitlab_bootstrap/typography.scss"; +@import "gitlab_bootstrap/buttons.scss"; +@import "gitlab_bootstrap/blocks.scss"; +@import "gitlab_bootstrap/files.scss"; +@import "gitlab_bootstrap/tables.scss"; +@import "gitlab_bootstrap/lists.scss"; /** diff --git a/app/assets/stylesheets/projects.css.scss b/app/assets/stylesheets/projects.css.scss deleted file mode 100644 index 4bdf5dee..00000000 --- a/app/assets/stylesheets/projects.css.scss +++ /dev/null @@ -1,385 +0,0 @@ -.git_url_wrapper { margin-right:50px } - -.sidebar aside a{ - display: block; - position: relative; - padding: 15px 10px; - margin: 10px 0 0 0; - - font-size:13px; - font-weight:bold; - color:#333; - - &.current { - color: white; - background: $active_bg_color; - border: 1px solid $active_bd_color; - border-radius:5px; - - -webkit-border-top-right-radius: 0; - -webkit-border-bottom-right-radius: 0; - -moz-border-radius-topright: 0px; - -moz-border-radius-bottomright: 0px; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - margin-right: -1px; - } -} - -body table .commit a{color: #{$blue_link}} -body table th, body table td{ border-bottom: 1px solid #DEE2E3;} -body .fixed{position: fixed; } - -/** File stat **/ -.file_stats { - span { - img { - width:14px; - float:left; - margin-right: 6px; - padding:2px 0; - } - } -} - -.round-borders { - @include round-borders-all(4px); - padding: 4px 0px; -} - -table.round-borders { - float:left; - text-align: left; -} - - - -/** PROJECTS **/ -input.ssh_project_url { - padding:5px; - margin:0px; - float:right; - width:400px; - text-align:center; -} - -#projects-list .project { - height:50px; -} - -#tree-slider .tree-item, -#projects-list .project, -#snippets-table .snippet, -#issues-table .issue{ - cursor:pointer; -} - -.clear { - clear: both; -} - - - -#user_projects_limit{ - width: 60px; -} - -.handle:hover{ - cursor: move; -} - -.project-refs-form { - span { - background: none !important; - position:static !important; - width:auto !important; - height: auto !important; - } -} - -.project-refs-select { - width:200px; -} - -.filter .left { margin-right:15px; } - -body table .commit { - a.tree-commit-link { - color:#444; - &:hover { - text-decoration:underline; - } - } -} - -/** NEW PROJECT **/ -.new-project-hodler { - .icon span { background-position: -31px -70px; } - td { border-bottom: 1px solid #DEE2E3; } -} - -/** Feed entry **/ -.commit, -.snippet, -.message { - .title { - color:#666; - a { color:#666 !important; } - p { margin-top:0px; } - } - .author { color: #999 } -} - -/** JQuery UI **/ -.ui-autocomplete { @include round-borders-all(5px); } -.ui-menu-item { cursor: pointer } -.ui-selectmenu{ - @include round-borders-all(4px); - margin-right:10px; - font-size:1.5em; - height:auto; - font-weight:bold; - .ui-selectmenu-status { - padding:3px 10px; - } -} - -#holder { - background:#FAFAFA; - border: 1px solid #EEE; - cursor: move; - height: 70%; - overflow: hidden; -} - -/* Project Dashboard Page */ -html, body { height: 100%; } - -.news-feed h2{float: left;} -.news-feed .project-updates {margin-bottom: 20px; display: block; width: 100%;} -.news-feed .project-updates .data{ padding: 0} -.news-feed .project-updates a.project-update {padding: 10px; border-bottom: 1px solid #eee; overflow: hidden; display: block;} -.news-feed .project-updates a.project-update:last-child{border-bottom: 0} -.news-feed .project-updates a.project-update img{float: left; margin-right: 10px;} -.news-feed .project-updates a.project-update span.update-title, .dashboard-page .news-feed .project-updates li a span.update-author{display: block;} -.news-feed .project-updates a.project-update span.update-title{margin-bottom: 10px} -.news-feed .project-updates a.project-update span.update-author{color: #999; font-weight: normal; font-style: italic;} -.news-feed .project-updates a.project-update span.update-author strong{font-weight: bold; font-style: normal;} -/* eo Dashboard Page */ - - -/** Update entry **/ -.update-data { padding: 0 } -.update-data { width:100%; } -.update-data.ui-box .data { padding:0; } -a.update-item {padding: 10px; border-bottom: 1px solid #eee; overflow: hidden; display: block;} -a.update-item:last-child{border-bottom: 0} -a.update-item img{float: left; margin-right: 10px;} -a.update-item span.update-title, .dashboard-page .news-feed .project-updates li a span.update-author{display: block;} -a.update-item span.update-title{margin-bottom: 10px} -a.update-item span.update-author{color: #999; font-weight: normal; font-style: italic;} -a.update-item span.update-author strong{font-weight: bold; font-style: normal;} - - -body .team_member_new .span-6, .team_member_edit .span-6{ padding:10px 0; } - -body.projects-page input.text.git-url.project_list_url { width:165px; } - - -body table.no-borders th { - background:none; - border-bottom:1px solid #CCC; - color:#333; -} - -body table.no-borders tr, -body table.no-borders td{ - border:none; -} - -.ajax-tab-loading { - padding:40px; - display:none; -} - -#tree-content-holder { float:left; width:100%; } - -#tree-readme-holder { - float:left; - width:100%; - - .readme { - @include round-borders-all(4px); - padding: 4px 15px; - background:#F7F7F7; - } -} - - - -/* Commit Page */ -.entity-info {float: right;} -.entity-button{ - background-image: -webkit-gradient(linear, 0 0, 0 26, color-stop(0.192, #fff), to(#f4f4f4)); - background-image: -webkit-linear-gradient(#fff 19.2%, #f4f4f4); - background-image: -moz-linear-gradient(#fff 19.2%, #f4f4f4); - background-image: -o-linear-gradient(#fff 19.2%, #f4f4f4); - box-shadow: 0 -1px 0 white inset; - display: block; - border: 1px solid #eee; - border-radius: 5px; - margin-bottom: 2px; - position: relative; - padding: 4px 10px; - font-size: 11px; - padding-right: 20px; -} - -.entity-button i{ - background: url('images.png') no-repeat -138px -27px; - width: 6px; - height: 9px; - float: right; - position: absolute; - top: 6px; - right: 5px; -} -.box-arrow{float: right; background: #E3E5EA; padding: 10px; border-radius: 5px; margin-top: 2px; text-shadow: none; color: #999; margin: 1.5em 0;} - -h4.dash-tabs { - margin: 0; - border-bottom: 1px solid #ccc; - padding: 10px 10px; - font-size: 11px; - padding-left:20px; - font-weight: bold; text-transform: uppercase; - background: #F7F7F7; - margin-bottom:20px; - height:13px; - -} - -.dash-button { - border-right: 1px solid #ddd; - background:none; - padding: 10px 15px; - float:left; - position:relative; - top:-10px; - left:0px; - height:13px; - - &:first-child { - border-left: 1px solid #ddd; - } - &.active { - background: #eaeaea; - } -} - - -.dashboard-loader { - float:right; - margin-right:30px; - display:none; -} - - -.merge-tabs { - margin: 0; - border: 1px solid #ccc; - padding: 5px; - font-size: 12px; - background: #F7F7F7; - margin-bottom:20px; - height:26px; - - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; - - .tab { - font-weight: bold; - border-right: 1px solid #ddd; - background:none; - padding: 10px; - min-width:60px; - float:left; - position:relative; - top:-5px; - left:-5px; - height:16px; - padding-left:34px; - - span { - width: 20px; - height: 20px; - display: inline-block; - position: absolute; - left: 8px; - top: 8px; - } - - &.active { - background: #eaeaea; - } - } -} -.merge-tabs.repository .tab span{ background: url("images.png") no-repeat -38px -77px; } -.activities-tab span { background: url("images.png") no-repeat -161px -1px; } -.stat-tab span, -.team-tab span, -.snippets-tab span { background: url("images.png") no-repeat -38px -77px; } -.files-tab span { background: url("images.png") no-repeat -112px -23px; } - -.merge-notes-tab span { background: url("images.png") no-repeat -161px -1px; } -.merge-commits-tab span { background: url("images.png") no-repeat -86px 1px; } -.merge-diffs-tab span { background: url("images.png") no-repeat -118px 1px; } -.merge-tabs .dashboard-loader { padding:8px; } - -.user-mention { - color: #2FA0BB; - font-weight: bold; -} - -.author { - color: #999; -} - - - - -.dark_scheme_box { - padding:20px 0; - - label { - float:left; - box-shadow: 0 0px 5px rgba(0,0,0,.3); - - img { - } - } -} - -a.project-update.titled { - position: relative; - padding-left: 235px !important; - - .title-block { - padding: 10px; - width: 205px; - position: absolute; - left: 0; - top: 0; - } -} - -.add_new { - float: right; - background: #A6B807; - color: white; - padding: 4px 10px; - @include round-borders-all(4px); - font-size:11px; - margin: 10px 0; -} diff --git a/app/assets/stylesheets/ref_select.scss b/app/assets/stylesheets/ref_select.scss index 6f6a1bc9..5b52e11b 100644 --- a/app/assets/stylesheets/ref_select.scss +++ b/app/assets/stylesheets/ref_select.scss @@ -33,9 +33,7 @@ } .chzn-single { - background:#ddd; - //border:none; - //box-shadow:none; + @include bg-gray-gradient; div { background:transparent; diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss index e2db701d..75e38aee 100644 --- a/app/assets/stylesheets/sections/commits.scss +++ b/app/assets/stylesheets/sections/commits.scss @@ -206,4 +206,24 @@ min-width:65px; font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; } + + .commit-author-name { + color: #777; + } +} + +.diff_file_header a, +.file_stats a { + color:$style_color; +} + +.file_stats { + span { + img { + width:14px; + float:left; + margin-right:6px; + padding:2px 0; + } + } } diff --git a/app/assets/stylesheets/sections/graph.scss b/app/assets/stylesheets/sections/graph.scss index 33d91de5..2aa4463e 100644 --- a/app/assets/stylesheets/sections/graph.scss +++ b/app/assets/stylesheets/sections/graph.scss @@ -6,11 +6,7 @@ h4 { padding:0 10px; border-bottom: 1px solid #bbb; - background:#eee; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); + @include bg-gray-gradient; } .graph { diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss index 1b61ec3f..230a7aea 100644 --- a/app/assets/stylesheets/sections/issues.scss +++ b/app/assets/stylesheets/sections/issues.scss @@ -65,6 +65,11 @@ input.check_all_issues { } } +@media (min-width: 800px) { .issues_filters select { width:160px; } } +@media (min-width: 1000px) { .issues_filters select { width:200px; } } +@media (min-width: 1200px) { .issues_filters select { width:220px; } } + + #issues-table-holder { .issues_filters { form { @@ -99,3 +104,11 @@ input.check_all_issues { #update_status { width:100px; } + +/** + * Milestones list + * + */ +.milestone { + @extend .wll; +} diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss index ec84a64e..73171915 100644 --- a/app/assets/stylesheets/sections/merge_requests.scss +++ b/app/assets/stylesheets/sections/merge_requests.scss @@ -11,23 +11,6 @@ background:#f1f1f1; } - .commit { - margin:0; - padding:0; - padding: 5px; - margin-bottom: 5px; - - .committed_ago { - display:none; - } - .browse_code_link_holder { - display:none; - } - list-style:none; - &:hover { - background:none; - } - } } /** @@ -55,6 +38,7 @@ background: #CEB; .accept_merge_request { + font-size:13px; float:left; } .remove_branch_holder { @@ -99,3 +83,42 @@ li.merge_request { @extend .padded; @extend .append-bottom-10; } + +.label_branch { + @include round-borders-all(4px); + padding:2px 4px; + border:none; + font-size:13px; + background: #474D57; + color:#fff; + font-weight:bold; + font-family: monospace; +} + +.mr_source_commit, +.mr_target_commit { + .commit { + margin:0; + padding:0; + padding: 5px; + margin-bottom: 5px; + .avatar { position:relative } + .row_title { + color:#444; + } + .commit-author-name, + .dash, + .committed_ago, + .browse_code_link_holder { + display:none; + } + list-style:none; + &:hover { + background:none; + } + } +} + +.mr_direction_tip { + margin-top:40px +} diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss index 4a773644..097e8197 100644 --- a/app/assets/stylesheets/sections/nav.scss +++ b/app/assets/stylesheets/sections/nav.scss @@ -6,13 +6,9 @@ ul.main_menu { border-radius: 4px; margin: auto; margin:30px 0; - background:#eee; - border:1px solid #bbb; + border:1px solid #AAA; height:37px; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); + @include bg-gray-gradient; position:relative; overflow:hidden; @include shade; @@ -89,7 +85,7 @@ ul.main_menu { line-height:36px; color: $style_color; text-shadow:0 1px 1px white; - + padding:0 10px; } } /* diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss index 30587ef5..6a965fa4 100644 --- a/app/assets/stylesheets/sections/notes.scss +++ b/app/assets/stylesheets/sections/notes.scss @@ -2,7 +2,7 @@ * Notes * */ -#notes-list, +#notes-list, #new_notes_list { display:block; list-style:none; @@ -10,7 +10,7 @@ padding:0px; } -#new_notes_list li:last-child{ +#new_notes_list li:last-child{ border-bottom:1px solid #aaa; } @@ -30,16 +30,24 @@ } #new_note { - #note_note { - height:25px; + .note-text { + height:40px; } .attach_holder { display:none; } } -.note { - padding: 8px 0; +.preview_note { + margin: 2px; + border: 1px solid #ddd; + padding: 10px; + min-height: 60px; + background:#f5f5f5; +} + +.note { + padding: 8px 0; border-bottom: 1px solid #eee; overflow: hidden; display: block; @@ -49,16 +57,16 @@ .note-author { color: $style_color;} .note-title { margin-left:45px; padding-top: 5px;} - .avatar { + .avatar { margin-top:3px; } - .delete-note { - display:none; + .delete-note { + display:none; float:right; } - &:hover { + &:hover { .delete-note { display:block; } } } @@ -72,18 +80,18 @@ p.notify_controls span{ font-weight: 700; } -tr.line_notes_row { +tr.line_notes_row { border-bottom:1px solid #DDD; border-left: 7px solid #2A79A3; - &.reply { + &.reply { background:#eee; border-left: 7px solid #2A79A3; border-top:1px solid #ddd; - td { + td { padding:7px 10px; } - a.line_note_reply_link { + a.line_note_reply_link { @include round-borders-all(4px); padding: 3px 10px; margin-left:5px; @@ -92,9 +100,9 @@ tr.line_notes_row { border-color: #2A79A3; } } - ul { + ul { margin:0; - li { + li { padding:0; border:none; } @@ -103,26 +111,26 @@ tr.line_notes_row { .line_notes_row, .per_line_form { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } -.per_line_form { +.per_line_form { background:#f5f5f5; border-top:1px solid #eee; form { margin: 0; } - td { + td { border-bottom:1px solid #ddd; } - .note_actions { + .note_actions { margin:0; padding-top: 10px; - .buttons { + .buttons { float:left; width:300px; } - .options { - .labels { + .options { + .labels { float:left; padding-left:10px; - label { + label { padding: 6px 0; margin: 0; width:120px; @@ -132,7 +140,7 @@ tr.line_notes_row { } } -td .line_note_link { +td .line_note_link { position:absolute; margin-left:-70px; margin-top:-10px; @@ -144,14 +152,14 @@ td .line_note_link { opacity: 0.0; filter: alpha(opacity=0); - &:hover { + &:hover { opacity: 1.0; filter: alpha(opacity=100); } } .diff_file_content tr.line_holder:hover > td { background: $hover !important; } -.diff_file_content tr.line_holder:hover > td .line_note_link { +.diff_file_content tr.line_holder:hover > td .line_note_link { opacity: 1.0; filter: alpha(opacity=100); } @@ -169,8 +177,8 @@ td .line_note_link { margin: 0; } - .note_advanced_opts { - h6 { + .note_advanced_opts { + h6 { line-height: 32px; padding-right: 15px; } @@ -183,7 +191,7 @@ td .line_note_link { overflow:hidden; margin:0 0 5px !important; - .input_file { + .input_file { .file_upload { position: absolute; right:14px; @@ -196,7 +204,7 @@ td .line_note_link { height:28px; overflow:hidden; } - .input-file { + .input-file { width: 260px; height: 41px; float: right; @@ -204,3 +212,8 @@ td .line_note_link { } } } + +.note-text { + border: 1px solid #aaa; + box-shadow:none; +} diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss index 8c79e45e..721b569d 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/sections/projects.scss @@ -1,17 +1,44 @@ -.projects { +.projects { @extend .row; .activities { } - .side { + .side { @extend .span4; @extend .right; - .projects_box { - h5 { + .projects_box { + h5 { color:$style_color; font-size:16px; text-shadow: 0 1px 1px #fff; + padding: 2px 10px; + } + ul { + li { + padding:0; + a { + display:block; + .project_name { + color:#4fa2bd; + font-size:14px; + line-height:18px; + } + .arrow { + float:right; + padding:10px; + margin:0; + } + .last_activity { + padding-top:5px; + display:block; + span, strong { + font-size:12px; + color:#666; + } + } + } + } } @extend .leftbar; @extend .ui-box; @@ -19,21 +46,47 @@ } } -.new_project, -.edit_project { - .project_name_holder { +.new_project, +.edit_project { + .project_name_holder { input, - label { + label { font-size:16px; line-height:20px; padding:8px; } - label { + label { color:#888; } - .btn { - padding:6px; + .btn { + padding:6px 10px; margin-left:10px; + margin-bottom:8px; } } + .adv_settings { + h6 { margin-left:40px; } + } +} + +.project_clone_panel { + @include border-radius(4px); + @include bg-gray-gradient; + padding: 4px 7px; + border: 1px solid #CCC; + margin-bottom:5px; + input[type=text] { + border: 1px solid #BBB; + } +} + +.save-project-loader { + img { + margin-top:50px; + margin-bottom:50px; + } + h3 { + @extend .page_title; + } + } diff --git a/app/assets/stylesheets/sections/tree.scss b/app/assets/stylesheets/sections/tree.scss index 2663fc9a..891f5e20 100644 --- a/app/assets/stylesheets/sections/tree.scss +++ b/app/assets/stylesheets/sections/tree.scss @@ -72,11 +72,7 @@ th { border-color: #CCC; border-bottom: 1px solid #bbb; - background:#eee; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); + @include bg-gray-gradient; } } diff --git a/app/assets/stylesheets/themes/ui_mars.scss b/app/assets/stylesheets/themes/ui_mars.scss index 2808ad32..c630f388 100644 --- a/app/assets/stylesheets/themes/ui_mars.scss +++ b/app/assets/stylesheets/themes/ui_mars.scss @@ -20,6 +20,10 @@ .fbtn { .btn { + i { + position: relative; + top: 1px; + } margin-left:8px; background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #595D63), to(#31363E)); background-image: -webkit-linear-gradient(#595D63 6.6%, #31363E); @@ -32,6 +36,10 @@ background-image: -moz-linear-gradient(#595D63 6.6%, #202227); background-image: -o-linear-gradient(#595D63 6.6%, #202227); background-position:0 0; + color:#fff; + i { + @extend .icon-white; + } } border: 1px solid #31363E; diff --git a/app/contexts/merge_requests_load.rb b/app/contexts/merge_requests_load.rb index 6778db3b..e2f68e38 100644 --- a/app/contexts/merge_requests_load.rb +++ b/app/contexts/merge_requests_load.rb @@ -1,13 +1,13 @@ class MergeRequestsLoad < BaseContext def execute - type = params[:f].to_i + type = params[:f] merge_requests = project.merge_requests merge_requests = case type - when 1 then merge_requests - when 2 then merge_requests.closed - when 3 then merge_requests.opened.assigned(current_user) + when 'all' then merge_requests + when 'closed' then merge_requests.closed + when 'assigned-to-me' then merge_requests.opened.assigned(current_user) else merge_requests.opened end.page(params[:page]).per(20) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7c1941ec..9aab250d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -14,6 +14,10 @@ class ApplicationController < ActionController::Base render "errors/gitolite", layout: "error" end + rescue_from Gitlab::Gitolite::InvalidKey do |exception| + render "errors/invalid_ssh_key", layout: "error" + end + rescue_from Encoding::CompatibilityError do |exception| render "errors/encoding", layout: "error", status: 404 end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 889a7d98..a47b3843 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -60,7 +60,13 @@ class IssuesController < ApplicationController @issue.save respond_to do |format| - format.html { redirect_to project_issue_path(@project, @issue) } + format.html do + if @issue.valid? + redirect_to project_issue_path(@project, @issue) + else + render :new + end + end format.js end end @@ -162,10 +168,10 @@ class IssuesController < ApplicationController def issues_filter { - all: "1", - closed: "2", - to_me: "3", - open: "0" + all: "all", + closed: "closed", + to_me: "assigned-to-me", + open: "open" } end end diff --git a/app/controllers/labels_controller.rb b/app/controllers/labels_controller.rb new file mode 100644 index 00000000..e703f822 --- /dev/null +++ b/app/controllers/labels_controller.rb @@ -0,0 +1,25 @@ +class LabelsController < ApplicationController + before_filter :authenticate_user! + before_filter :project + before_filter :module_enabled + + layout "project" + + # Authorize + before_filter :add_project_abilities + + # Allow read any issue + before_filter :authorize_read_issue! + + respond_to :js, :html + + def index + @labels = @project.issues.tag_counts_on(:labels).order('count DESC') + end + + protected + + def module_enabled + return render_404 unless @project.issues_enabled + end +end diff --git a/app/controllers/merge_requests_controller.rb b/app/controllers/merge_requests_controller.rb index 4938a294..187bb407 100644 --- a/app/controllers/merge_requests_controller.rb +++ b/app/controllers/merge_requests_controller.rb @@ -103,10 +103,12 @@ class MergeRequestsController < ApplicationController def branch_from @commit = project.commit(params[:ref]) + @commit = CommitDecorator.decorate(@commit) end def branch_to @commit = project.commit(params[:ref]) + @commit = CommitDecorator.decorate(@commit) end protected diff --git a/app/controllers/milestones_controller.rb b/app/controllers/milestones_controller.rb index 7acb3781..10f089f1 100644 --- a/app/controllers/milestones_controller.rb +++ b/app/controllers/milestones_controller.rb @@ -17,8 +17,8 @@ class MilestonesController < ApplicationController respond_to :html def index - @milestones = case params[:f].to_i - when 1; @project.milestones + @milestones = case params[:f] + when 'all'; @project.milestones else @project.milestones.active end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index d19931e9..d472936b 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -12,8 +12,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController def ldap # We only find ourselves here if the authentication to LDAP was successful. - info = request.env["omniauth.auth"]["info"] - @user = User.find_for_ldap_auth(info) + @user = User.find_for_ldap_auth(request.env["omniauth.auth"], current_user) if @user.persisted? @user.remember_me = true end diff --git a/app/controllers/team_members_controller.rb b/app/controllers/team_members_controller.rb index 0cc58c3e..0846f096 100644 --- a/app/controllers/team_members_controller.rb +++ b/app/controllers/team_members_controller.rb @@ -9,6 +9,7 @@ class TeamMembersController < ApplicationController def show @team_member = project.users_projects.find(params[:id]) + @events = @team_member.user.recent_events.where(:project_id => @project.id).limit(7) end def new diff --git a/app/decorators/application_decorator.rb b/app/decorators/application_decorator.rb index 7bc88648..3023699e 100644 --- a/app/decorators/application_decorator.rb +++ b/app/decorators/application_decorator.rb @@ -1,4 +1,4 @@ -class ApplicationDecorator < Drapper::Base +class ApplicationDecorator < Draper::Base # Lazy Helpers # PRO: Call Rails helpers without the h. proxy # ex: number_to_currency(model.price) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 51569b06..3dafb753 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -2,10 +2,13 @@ require 'digest/md5' module ApplicationHelper def gravatar_icon(user_email = '', size = 40) - return unless user_email - gravatar_host = request.ssl? ? "https://secure.gravatar.com" : "http://www.gravatar.com" - user_email.strip! - "#{gravatar_host}/avatar/#{Digest::MD5.hexdigest(user_email.downcase)}?s=#{size}&d=identicon" + if Gitlab.config.disable_gravatar? || user_email.blank? + 'no_avatar.png' + else + gravatar_prefix = request.ssl? ? "https://secure" : "http://www" + user_email.strip! + "#{gravatar_prefix}.gravatar.com/avatar/#{Digest::MD5.hexdigest(user_email.downcase)}?s=#{size}&d=identicon" + end end def request_protocol @@ -75,16 +78,16 @@ module ApplicationHelper end def show_last_push_widget?(event) - event && + event && event.last_push_to_non_root? && !event.rm_ref? && - event.project && + event.project && event.project.merge_requests_enabled end def tab_class(tab_key) active = case tab_key - + # Project Area when :wall; wall_tab? when :wiki; controller.controller_name == "wikis" @@ -123,4 +126,13 @@ module ApplicationHelper def hexdigest(string) Digest::SHA1.hexdigest string end + + def project_last_activity project + activity = project.last_activity + if activity && activity.created_at + time_ago_in_words(activity.created_at) + " ago" + else + "Never" + end + end end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 24bc3e85..9da695b5 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -1,9 +1,18 @@ module GitlabMarkdownHelper + # Replaces references (i.e. @abc, #123, !456, ...) in the text with links to + # the appropriate items in Gitlab. + # + # text - the source text + # html_options - extra options for the reference links as given to link_to + # + # note: reference links will only be generated if @project is set + # + # see Gitlab::Markdown for details on the supported syntax def gfm(text, html_options = {}) return text if text.nil? return text if @project.nil? - # Extract pre blocks + # Extract pre blocks so they are not altered # from http://github.github.com/github-flavored-markdown/ extractions = {} text.gsub!(%r{
.*?
|.*?}m) do |match| @@ -22,10 +31,18 @@ module GitlabMarkdownHelper extractions[$1] end - text.html_safe + sanitize text.html_safe, attributes: ActionView::Base.sanitized_allowed_attributes + %w(id class ) end - # circumvents nesting links, which will behave bad in browsers + # Use this in places where you would normally use link_to(gfm(...), ...). + # + # It solves a problem occurring with nested links (i.e. + # "outer text gfm ref more outer text"). This will not be + # interpreted as intended. Browsers will parse something like + # "outer text gfm ref more outer text" (notice the last part is + # not linked any more). link_to_gfm corrects that. It wraps all parts to + # explicitly produce the correct linking behavior (i.e. + # "outer text gfm ref more outer text"). def link_to_gfm(body, url, html_options = {}) gfm_body = gfm(body, html_options) @@ -37,17 +54,24 @@ module GitlabMarkdownHelper end def markdown(text) - @__renderer ||= Redcarpet::Markdown.new(Redcarpet::Render::GitlabHTML.new(self, filter_html: true, with_toc_data: true), { - no_intra_emphasis: true, - tables: true, - fenced_code_blocks: true, - autolink: true, - strikethrough: true, - lax_html_blocks: true, - space_after_headers: true, - superscript: true - }) + unless @markdown + gitlab_renderer = Redcarpet::Render::GitlabHTML.new(self, + # 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) + @markdown ||= Redcarpet::Markdown.new(gitlab_renderer, + # see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use + no_intra_emphasis: true, + tables: true, + fenced_code_blocks: true, + autolink: true, + strikethrough: true, + lax_html_blocks: true, + space_after_headers: true, + superscript: true) + end - @__renderer.render(text).html_safe + @markdown.render(text).html_safe end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 9563fdbc..91136fee 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -12,74 +12,117 @@ class Notify < ActionMailer::Base def new_user_email(user_id, password) @user = User.find(user_id) @password = password - mail(to: @user.email, subject: "gitlab | Account was created for you") + mail(to: @user.email, subject: subject("Account was created for you")) end def new_issue_email(issue_id) @issue = Issue.find(issue_id) @project = @issue.project - mail(to: @issue.assignee_email, subject: "gitlab | new issue ##{@issue.id} | #{@issue.title} | #{@project.name}") + mail(to: @issue.assignee_email, subject: subject("new issue ##{@issue.id}", @issue.title)) end def note_wall_email(recipient_id, note_id) - recipient = User.find(recipient_id) @note = Note.find(note_id) @project = @note.project - mail(to: recipient.email, subject: "gitlab | #{@project.name}") + mail(to: recipient(recipient_id), subject: subject) end def note_commit_email(recipient_id, note_id) - recipient = User.find(recipient_id) @note = Note.find(note_id) @commit = @note.target @commit = CommitDecorator.decorate(@commit) @project = @note.project - mail(to: recipient.email, subject: "gitlab | note for commit #{@commit.short_id} | #{@commit.title} | #{@project.name}") + mail(to: recipient(recipient_id), subject: subject("note for commit #{@commit.short_id}", @commit.title)) end def note_merge_request_email(recipient_id, note_id) - recipient = User.find(recipient_id) @note = Note.find(note_id) @merge_request = @note.noteable @project = @note.project - mail(to: recipient.email, subject: "gitlab | note for merge request !#{@merge_request.id} | #{@project.name}") + mail(to: recipient(recipient_id), subject: subject("note for merge request !#{@merge_request.id}")) end def note_issue_email(recipient_id, note_id) - recipient = User.find(recipient_id) @note = Note.find(note_id) @issue = @note.noteable @project = @note.project - mail(to: recipient.email, subject: "gitlab | note for issue ##{@issue.id} | #{@project.name}") + mail(to: recipient(recipient_id), subject: subject("note for issue ##{@issue.id}")) end def note_wiki_email(recipient_id, note_id) - recipient = User.find(recipient_id) @note = Note.find(note_id) @wiki = @note.noteable @project = @note.project - mail(to: recipient.email, subject: "gitlab | note for wiki | #{@project.name}") + mail(to: recipient(recipient_id), subject: subject("note for wiki")) end def new_merge_request_email(merge_request_id) @merge_request = MergeRequest.find(merge_request_id) @project = @merge_request.project - mail(to: @merge_request.assignee_email, subject: "gitlab | new merge request !#{@merge_request.id} | #{@merge_request.title} | #{@project.name}") + mail(to: @merge_request.assignee_email, subject: subject("new merge request !#{@merge_request.id}", @merge_request.title)) end def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id) - recipient = User.find(recipient_id) @merge_request = MergeRequest.find(merge_request_id) @previous_assignee ||= User.find(previous_assignee_id) @project = @merge_request.project - mail(to: recipient.email, subject: "gitlab | changed merge request !#{@merge_request.id} | #{@merge_request.title} | #{@project.name}") + mail(to: recipient(recipient_id), subject: subject("changed merge request !#{@merge_request.id}", @merge_request.title)) end def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id) - recipient = User.find(recipient_id) @issue = Issue.find(issue_id) @previous_assignee ||= User.find(previous_assignee_id) @project = @issue.project - mail(to: recipient.email, subject: "gitlab | changed issue ##{@issue.id} | #{@issue.title} | #{@project.name}") + mail(to: recipient(recipient_id), subject: subject("changed issue ##{@issue.id}", @issue.title)) + end + + def project_access_granted_email(user_project_id) + @users_project = UsersProject.find user_project_id + @project = @users_project.project + mail(to: @users_project.user.email, + subject: subject("access to project was granted")) + end + + def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id) + @issue = Issue.find issue_id + @issue_status = status + @updated_by = User.find updated_by_user_id + mail(to: recipient(recipient_id), + subject: subject("changed issue ##{@issue.id}", @issue.title)) + end + + private + + # Look up a User by their ID and return their email address + # + # recipient_id - User ID + # + # Returns a String containing the User's email address. + def recipient(recipient_id) + if recipient = User.find(recipient_id) + recipient.email + end + end + + # Formats arguments into a String suitable for use as an email subject + # + # extra - Extra Strings to be inserted into the subject + # + # Examples + # + # >> subject('Lorem ipsum') + # => "gitlab | Lorem ipsum" + # + # # Automatically inserts Project name when @project is set + # >> @project = Project.last + # => # + # >> subject('Lorem ipsum') + # => "gitlab | Lorem ipsum | Ruby on Rails" + # + # # Accepts multiple arguments + # >> subject('Lorem ipsum', 'Dolor sit amet') + # => "gitlab | Lorem ipsum | Dolor sit amet" + def subject(*extra) + "gitlab | " << extra.join(' | ') << (@project ? " | #{@project.name}" : "") end end diff --git a/app/models/key.rb b/app/models/key.rb index cfcb1f63..a39a4a16 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -1,16 +1,18 @@ require 'digest/md5' class Key < ActiveRecord::Base - include SshKey belongs_to :user belongs_to :project + attr_protected :user_id + validates :title, presence: true, length: { within: 0..255 } validates :key, presence: true, + format: { :with => /ssh-.{3} / }, length: { within: 0..5000 } before_save :set_identifier @@ -50,6 +52,10 @@ class Key < ActiveRecord::Base user.projects end end + + def last_deploy? + Key.where(identifier: identifier).count == 0 + end end # == Schema Information # diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 47966d66..542817b0 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -88,8 +88,11 @@ class MergeRequest < ActiveRecord::Base end def unmerged_diffs - commits = project.repo.commits_between(target_branch, source_branch).map {|c| Commit.new(c)} - diffs = project.repo.diff(commits.first.prev_commit.id, commits.last.id) rescue [] + # Only show what is new in the source branch compared to the target branch, not the other way around. + # The linex below with merge_base is equivalent to diff with three dots (git diff branch1...branch2) + # From the git documentation: "git diff A...B" is equivalent to "git diff $(git-merge-base A B) B" + common_commit = project.repo.git.native(:merge_base, {}, [target_branch, source_branch]).strip + diffs = project.repo.diff(common_commit, source_branch) end def last_commit diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 592c57f9..d416fb63 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -28,17 +28,9 @@ class Milestone < ActiveRecord::Base end def percent_complete - @percent_complete ||= begin - total_i = self.issues.count - closed_i = self.issues.closed.count - if total_i > 0 - (closed_i * 100) / total_i - else - 100 - end - rescue => ex - 0 - end + ((self.issues.closed.count * 100) / self.issues.count).abs + rescue ZeroDivisionError + 100 end def expires_at diff --git a/app/models/project.rb b/app/models/project.rb index 714953c6..a7735a42 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2,13 +2,13 @@ require "grit" class Project < ActiveRecord::Base include Repository - include ProjectPush + include PushObserver include Authority include Team # # Relations - # + # belongs_to :owner, class_name: "User" has_many :users, through: :users_projects has_many :events, dependent: :destroy @@ -25,12 +25,12 @@ class Project < ActiveRecord::Base attr_accessor :error_code - # + # # Protected attributes # attr_protected :private_flag, :owner_id - # + # # Scopes # scope :public_only, where(private_flag: false) @@ -158,7 +158,7 @@ class Project < ActiveRecord::Base end def last_activity - events.last || nil + events.order("created_at ASC").last end def last_activity_date diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index 4da1432c..7c30f7a0 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -1,4 +1,6 @@ class ProtectedBranch < ActiveRecord::Base + include GitHost + belongs_to :project validates_presence_of :project_id validates_presence_of :name @@ -7,7 +9,7 @@ class ProtectedBranch < ActiveRecord::Base after_destroy :update_repository def update_repository - Gitlab::GitHost.system.update_project(project.path, project) + git_host.update_repository(project) end def commit diff --git a/app/models/user.rb b/app/models/user.rb index 92c81c83..ad6af6a6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -7,7 +7,7 @@ class User < ActiveRecord::Base attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :projects_limit, :skype, :linkedin, :twitter, :dark_scheme, - :theme_id, :force_random_password + :theme_id, :force_random_password, :extern_uid, :provider attr_accessor :force_random_password @@ -54,6 +54,8 @@ class User < ActiveRecord::Base validates :bio, length: { within: 0..255 } + validates :extern_uid, :allow_blank => true, :uniqueness => {:scope => :provider} + before_save :ensure_authentication_token alias_attribute :private_token, :authentication_token @@ -84,21 +86,31 @@ class User < ActiveRecord::Base where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)') end - def self.find_for_ldap_auth(omniauth_info) - name = omniauth_info.name.force_encoding("utf-8") - email = omniauth_info.email.downcase unless omniauth_info.email.nil? - raise OmniAuth::Error, "LDAP accounts must provide an email address" if email.nil? + def self.find_for_ldap_auth(auth, signed_in_resource=nil) + uid = auth.info.uid + provider = auth.provider + name = auth.info.name.force_encoding("utf-8") + email = auth.info.email.downcase unless auth.info.email.nil? + raise OmniAuth::Error, "LDAP accounts must provide an uid and email address" if uid.nil? or email.nil? - if @user = User.find_by_email(email) + if @user = User.find_by_extern_uid_and_provider(uid, provider) + @user + # workaround for backward compatibility + elsif @user = User.find_by_email(email) + logger.info "Updating legacy LDAP user #{email} with extern_uid => #{uid}" + @user.update_attributes(:extern_uid => uid, :provider => provider) @user else + logger.info "Creating user from LDAP login {uid => #{uid}, name => #{name}, email => #{email}}" password = Devise.friendly_token[0, 8].downcase @user = User.create( - name: name, - email: email, - password: password, - password_confirmation: password, - projects_limit: Gitlab.config.default_projects_limit + :extern_uid => uid, + :provider => provider, + :name => name, + :email => email, + :password => password, + :password_confirmation => password, + :projects_limit => Gitlab.config.default_projects_limit ) end end diff --git a/app/models/users_project.rb b/app/models/users_project.rb index 36e6d904..7d172934 100644 --- a/app/models/users_project.rb +++ b/app/models/users_project.rb @@ -1,4 +1,6 @@ class UsersProject < ActiveRecord::Base + include GitHost + GUEST = 10 REPORTER = 20 DEVELOPER = 30 @@ -58,9 +60,7 @@ class UsersProject < ActiveRecord::Base end def update_repository - Gitlab::GitHost.system.new.configure do |c| - c.update_project(project.path, project) - end + git_host.update_repository(project) end def project_access_human diff --git a/app/observers/issue_observer.rb b/app/observers/issue_observer.rb index 92b0f836..62fd9bf8 100644 --- a/app/observers/issue_observer.rb +++ b/app/observers/issue_observer.rb @@ -9,8 +9,16 @@ class IssueObserver < ActiveRecord::Observer def after_update(issue) send_reassigned_email(issue) if issue.is_being_reassigned? - Note.create_status_change_note(issue, current_user, 'closed') if issue.is_being_closed? - Note.create_status_change_note(issue, current_user, 'reopened') if issue.is_being_reopened? + + status = nil + status = 'closed' if issue.is_being_closed? + status = 'reopened' if issue.is_being_reopened? + if status + Note.create_status_change_note(issue, current_user, status) + [issue.author, issue.assignee].compact.each do |recipient| + Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) + end + end end protected diff --git a/app/observers/key_observer.rb b/app/observers/key_observer.rb index fac53a67..a3f17bde 100644 --- a/app/observers/key_observer.rb +++ b/app/observers/key_observer.rb @@ -1,9 +1,12 @@ class KeyObserver < ActiveRecord::Observer + include GitHost + def after_save(key) - key.update_repository + git_host.set_key(key.identifier, key.key, key.projects) end def after_destroy(key) - key.repository_delete_key + return if key.is_deploy_key && !key.last_deploy? + git_host.remove_key(key.identifier, key.projects) end end diff --git a/app/observers/users_project_observer.rb b/app/observers/users_project_observer.rb new file mode 100644 index 00000000..763b2c87 --- /dev/null +++ b/app/observers/users_project_observer.rb @@ -0,0 +1,9 @@ +class UsersProjectObserver < ActiveRecord::Observer + def after_create(users_project) + Notify.project_access_granted_email(users_project.id).deliver + end + + def after_update(users_project) + Notify.project_access_granted_email(users_project.id).deliver + end +end diff --git a/app/roles/git_host.rb b/app/roles/git_host.rb new file mode 100644 index 00000000..aa620f77 --- /dev/null +++ b/app/roles/git_host.rb @@ -0,0 +1,5 @@ +module GitHost + def git_host + Gitlab::Gitolite.new + end +end diff --git a/app/roles/git_merge.rb b/app/roles/git_merge.rb deleted file mode 100644 index 95e5942f..00000000 --- a/app/roles/git_merge.rb +++ /dev/null @@ -1,2 +0,0 @@ -module GitMerge -end diff --git a/app/roles/project_push.rb b/app/roles/push_observer.rb similarity index 99% rename from app/roles/project_push.rb rename to app/roles/push_observer.rb index 02025384..1067404d 100644 --- a/app/roles/project_push.rb +++ b/app/roles/push_observer.rb @@ -1,4 +1,4 @@ -module ProjectPush +module PushObserver def observe_push(oldrev, newrev, ref, user) data = post_receive_data(oldrev, newrev, ref, user) diff --git a/app/roles/repository.rb b/app/roles/repository.rb index 8d5b018d..5fa950db 100644 --- a/app/roles/repository.rb +++ b/app/roles/repository.rb @@ -1,4 +1,6 @@ module Repository + include GitHost + def valid_repo? repo rescue @@ -30,26 +32,10 @@ module Repository Commit.commits_between(repo, from, to) end - def write_hooks - %w(post-receive).each do |hook| - write_hook(hook, File.read(File.join(Rails.root, 'lib', "#{hook}-hook"))) - end - end - def satellite @satellite ||= Gitlab::Satellite.new(self) end - def write_hook(name, content) - hook_file = File.join(path_to_repo, 'hooks', name) - - File.open(hook_file, 'w') do |f| - f.write(content) - end - - File.chmod(0775, hook_file) - end - def has_post_receive_file? hook_file = File.join(path_to_repo, 'hooks', 'post-receive') File.exists?(hook_file) @@ -64,7 +50,7 @@ module Repository end def url_to_repo - Gitlab::GitHost.url_to_repo(path) + git_host.url_to_repo(path) end def path_to_repo @@ -72,13 +58,11 @@ module Repository end def update_repository - Gitlab::GitHost.system.update_project(path, self) - - write_hooks if File.exists?(path_to_repo) + git_host.update_repository(self) end def destroy_repository - Gitlab::GitHost.system.destroy_project(self) + git_host.remove_repository(self) end def repo_exists? @@ -133,10 +117,13 @@ module Repository storage_path = File.join(Rails.root, "tmp", "repositories", self.code) file_path = File.join(storage_path, file_name) + # Put files into a directory before archiving + prefix = self.code + "/" + # Create file if not exists unless File.exists?(file_path) FileUtils.mkdir_p storage_path - file = self.repo.archive_to_file(ref, nil, file_path) + file = self.repo.archive_to_file(ref, prefix, file_path) end file_path diff --git a/app/roles/ssh_key.rb b/app/roles/ssh_key.rb deleted file mode 100644 index 5e1d2c23..00000000 --- a/app/roles/ssh_key.rb +++ /dev/null @@ -1,18 +0,0 @@ -module SshKey - def update_repository - Gitlab::GitHost.system.new.configure do |c| - c.update_keys(identifier, key) - c.update_projects(projects) - end - end - - def repository_delete_key - Gitlab::GitHost.system.new.configure do |c| - #delete key file is there is no identically deploy keys - if !is_deploy_key || Key.where(identifier: identifier).count() == 0 - c.delete_key(identifier) - end - c.update_projects(projects) - end - end -end diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 2e67abfc..3e26f566 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -35,11 +35,13 @@ %h3 Latest projects %hr - @projects.each do |project| - %h5 + %p = link_to project.name, [:admin, project] .span6 %h3 Latest users %hr - @users.each do |user| - %h5 - = link_to user.name, [:admin, user] + %p + = link_to [:admin, user] do + = user.name + %small= user.email diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index d34acffe..43288424 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -5,7 +5,7 @@ Read more about system hooks %strong #{link_to "here", help_system_hooks_path, class: "vlink"} -= form_for @hook, as: :hook, url: admin_hooks_path do |f| += form_for @hook, as: :hook, url: admin_hooks_path, html: { class: 'form-inline' } do |f| -if @hook.errors.any? .alert-message.block-message.error - @hook.errors.full_messages.each do |msg| diff --git a/app/views/admin/projects/_form.html.haml b/app/views/admin/projects/_form.html.haml index 7cebddf2..87d212e5 100644 --- a/app/views/admin/projects/_form.html.haml +++ b/app/views/admin/projects/_form.html.haml @@ -10,19 +10,17 @@ Project name is .input = f.text_field :name, placeholder: "Example Project", class: "xxlarge" - = f.submit project.new_record? ? 'Create project' : 'Save Project', class: "btn primary" %hr - .alert.alert-info - %h5 Advanced settings: + .adv_settings + %h6 Advanced settings: .clearfix = f.label :path do - Git Clone + Path .input .input-prepend - %span.add-on= Gitlab.config.ssh_path - = f.text_field :path, placeholder: "example_project", disabled: !!project.id - %span.add-on= ".git" + %strong + = text_field_tag :ppath, @admin_project.path_to_repo, class: "xlarge", disabled: true .clearfix = f.label :code do URL @@ -42,8 +40,9 @@ .input= f.select(:default_branch, project.heads.map(&:name), {}, style: "width:210px;") - unless project.new_record? - .alert.alert-info - %h5 Features: + %hr + .adv_settings + %h6 Features: .clearfix = f.label :issues_enabled, "Issues" @@ -63,7 +62,8 @@ - unless project.new_record? .actions - = f.submit 'Save Project', class: "btn primary" + = f.submit 'Save Project', class: "btn save-btn" + = link_to 'Cancel', admin_projects_path, class: "btn cancel-btn" diff --git a/app/views/admin/projects/_new_form.html.haml b/app/views/admin/projects/_new_form.html.haml new file mode 100644 index 00000000..d793b6f3 --- /dev/null +++ b/app/views/admin/projects/_new_form.html.haml @@ -0,0 +1,29 @@ += form_for [:admin, @admin_project] do |f| + - if @admin_project.errors.any? + .alert-message.block-message.error + %span= @admin_project.errors.full_messages.first + .clearfix.project_name_holder + = f.label :name do + Project name is + .input + = f.text_field :name, placeholder: "Example Project", class: "xxlarge" + = f.submit 'Create project', class: "btn primary project-submit" + + %hr + %div.adv_settings + %h6 Advanced settings: + .clearfix + = f.label :path do + Git Clone + .input + .input-prepend + %span.add-on= Gitlab.config.ssh_path + = f.text_field :path, placeholder: "example_project", disabled: !@admin_project.new_record? + %span.add-on= ".git" + .clearfix + = f.label :code do + URL + .input + .input-prepend + %span.add-on= web_app_url + = f.text_field :code, placeholder: "example" diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 4512bb7e..f7dd486d 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -1,8 +1,8 @@ -%h3 +%h3.page_title Projects = link_to 'New Project', new_admin_project_path, class: "btn small right" %br -= form_tag admin_projects_path, method: :get do += form_tag admin_projects_path, method: :get, class: 'form-inline' do = text_field_tag :name, params[:name], class: "xlarge" = submit_tag "Search", class: "btn submit primary" diff --git a/app/views/admin/projects/new.html.haml b/app/views/admin/projects/new.html.haml index ac6526bf..933cb671 100644 --- a/app/views/admin/projects/new.html.haml +++ b/app/views/admin/projects/new.html.haml @@ -1,3 +1,12 @@ -%h3.page_title New project -%hr -= render 'form', project: @admin_project +.project_new_holder + %h3.page_title + New Project + %hr + = render 'new_form' +%div.save-project-loader.hide + %center + = image_tag "ajax_loader.gif" + %h3 Creating project & repository. Please wait a few minutes + +:javascript + $(function(){ new Projects(); }); diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index 5e68a23f..7010c272 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -2,71 +2,79 @@ = form_for [:admin, @admin_user] do |f| -if @admin_user.errors.any? #error_explanation - %ul + %ul.unstyled.alert.alert-error - @admin_user.errors.full_messages.each do |msg| %li= msg .row - .span6 - .clearfix - = f.label :name - .input - = f.text_field :name - %span.help-inline * required - .clearfix - = f.label :email - .input - = f.text_field :email - %span.help-inline * required - %hr + .span7 + .ui-box + %br + .clearfix + = f.label :name + .input + = f.text_field :name + %span.help-inline * required + .clearfix + = f.label :email + .input + = f.text_field :email + %span.help-inline * required + %hr + -if f.object.new_record? + .clearfix + = f.label :force_random_password do + %span Generate random password + .input= f.check_box :force_random_password, {}, true, nil + + %div.password-fields + .clearfix + = f.label :password + .input= f.password_field :password, disabled: f.object.force_random_password + .clearfix + = f.label :password_confirmation + .input= f.password_field :password_confirmation, disabled: f.object.force_random_password + %hr + .clearfix + = f.label :skype + .input= f.text_field :skype + .clearfix + = f.label :linkedin + .input= f.text_field :linkedin + .clearfix + = f.label :twitter + .input= f.text_field :twitter + .span5 + .ui-box + %br + .clearfix + = f.label :projects_limit + .input= f.number_field :projects_limit - -if f.object.new_record? - .clearfix - = f.label :admin, class: "checkbox" do - = f.check_box :force_random_password, {}, true, nil - %span Generate random password - - %div.password-fields .clearfix - = f.label :password - .input= f.password_field :password, disabled: f.object.force_random_password - .clearfix - = f.label :password_confirmation - .input= f.password_field :password_confirmation, disabled: f.object.force_random_password - %hr - .clearfix - = f.label :skype - .input= f.text_field :skype - .clearfix - = f.label :linkedin - .input= f.text_field :linkedin - .clearfix - = f.label :twitter - .input= f.text_field :twitter - .span6 - .clearfix - = f.label :projects_limit - .input= f.text_field :projects_limit, class: "small_input" + = f.label :admin do + %strong.cred Administrator + .input= f.check_box :admin + - unless @admin_user.new_record? + %hr + .padded.cred + - if @admin_user.blocked + %span + This user is blocked and is not able to login to GitLab + .clearfix + = link_to 'Unblock User', unblock_admin_user_path(@admin_user), method: :put, class: "btn small right" + - else + %span + Blocked users will be removed from all projects & will not be able to login to GitLab. + .clearfix + = link_to 'Block User', block_admin_user_path(@admin_user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small right danger" - .alert - .clearfix - %p Make the user a GitLab administrator. - = f.label :admin, class: "checkbox" do - = f.check_box :admin - %span Administrator - - unless @admin_user.new_record? - .alert.alert-error - - if @admin_user.blocked - %span - = link_to 'Unblock', unblock_admin_user_path(@admin_user), method: :put, class: "btn small" - This user is blocked and is not able to login to GitLab - - else - %span - = link_to 'Block', block_admin_user_path(@admin_user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small danger" - Blocked users will be removed from all projects & will not be able to login to GitLab. + .row + .span6 + .span6 .actions - = f.submit 'Save', class: "btn primary" + = f.submit 'Save', class: "btn save-btn" - if @admin_user.new_record? - = link_to 'Cancel', admin_users_path, class: "btn" + = link_to 'Cancel', admin_users_path, class: "btn cancel-btn" - else - = link_to 'Cancel', admin_user_path(@admin_user), class: "btn" + = link_to 'Cancel', admin_user_path(@admin_user), class: "btn cancel-btn" diff --git a/app/views/admin/users/edit.html.haml b/app/views/admin/users/edit.html.haml index 0e94be9e..032e3cfa 100644 --- a/app/views/admin/users/edit.html.haml +++ b/app/views/admin/users/edit.html.haml @@ -1,3 +1,3 @@ -%h3= @admin_user.name +%h3.page_title #{@admin_user.name} → Edit user %hr = render 'form' diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 5edca312..f21baabf 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -1,9 +1,9 @@ -%h3 +%h3.page_title Users = link_to 'New User', new_admin_user_path, class: "btn small right" %br -= form_tag admin_users_path, method: :get do += form_tag admin_users_path, method: :get, class: 'form-inline' do = text_field_tag :name, params[:name], class: "xlarge" = submit_tag "Search", class: "btn submit primary" %ul.nav.nav-pills diff --git a/app/views/admin/users/new.html.haml b/app/views/admin/users/new.html.haml index 87d6b0f2..70ead0d3 100644 --- a/app/views/admin/users/new.html.haml +++ b/app/views/admin/users/new.html.haml @@ -1,3 +1,3 @@ -%h2 New user -%hr +%h3.page_title New user +%br = render 'form' diff --git a/app/views/commits/_commit.html.haml b/app/views/commits/_commit.html.haml index 686a4337..61371d14 100644 --- a/app/views/commits/_commit.html.haml +++ b/app/views/commits/_commit.html.haml @@ -4,8 +4,8 @@ %strong= link_to "Browse Code »", tree_project_ref_path(@project, commit.id), class: "right" %p = link_to commit.short_id(8), project_commit_path(@project, id: commit.id), class: "commit_short_id" - %strong.cgray= commit.author_name - – + %strong.commit-author-name= commit.author_name + %span.dash – = image_tag gravatar_icon(commit.author_email), class: "avatar", width: 16 = link_to_gfm truncate(commit.title, length: 50), project_commit_path(@project, id: commit.id), class: "row_title" diff --git a/app/views/commits/compare.html.haml b/app/views/commits/compare.html.haml index be915cd1..7dab1f5c 100644 --- a/app/views/commits/compare.html.haml +++ b/app/views/commits/compare.html.haml @@ -20,7 +20,7 @@ = "..." = text_field_tag :to, params[:to], placeholder: "aa8b4ef", class: "xlarge" .actions - = submit_tag "Compare", class: "btn btn-primary" + = submit_tag "Compare", class: "btn primary" - unless @commits.empty? diff --git a/app/views/commits/show.html.haml b/app/views/commits/show.html.haml index 9a483aa2..e01f8ea5 100644 --- a/app/views/commits/show.html.haml +++ b/app/views/commits/show.html.haml @@ -5,12 +5,6 @@ :javascript - $(document).ready(function(){ - $(".line_note_link, .line_note_reply_link").live("click", function(e) { - var form = $(".per_line_form"); - $(this).parent().parent().after(form); - form.find("#note_line_code").val($(this).attr("line_code")); - form.show(); - return false; - }); + $(function(){ + PerLineNotes.init(); }); diff --git a/app/views/dashboard/index.html.haml b/app/views/dashboard/index.html.haml index 9fea5acb..e13640fb 100644 --- a/app/views/dashboard/index.html.haml +++ b/app/views/dashboard/index.html.haml @@ -1,14 +1,7 @@ - if @projects.any? .projects .activities.span8 - - if current_user.require_ssh_key? - .alert.alert-error.padded - %span - You wont be able to pull/push project code unless you - %strong - = link_to new_key_path, class: "vlink" do - add new key - to your profile + = render 'shared/no_ssh' - if @events.any? .content_list= render @events - else @@ -26,13 +19,16 @@ = link_to new_project_path, class: "btn very_small info" do %i.icon-plus New Project - - @projects.each do |project| - = link_to project_path(project), class: dom_class(project) do - %h4 - %span.ico.project - = truncate(project.name, length: 25) - %span.right - → + %ul.unstyled + - @projects.each do |project| + %li.wll + = link_to project_path(project), class: dom_class(project) do + %strong.project_name= truncate(project.name, length: 25) + %span.arrow + → + %span.last_activity + %strong Last activity: + %span= project_last_activity(project) .bottom= paginate @projects, theme: "gitlab" %hr @@ -57,5 +53,5 @@ If you will be added to project - it will be displayed here -:javascript +:javascript $(function(){ Pager.init(20); }); diff --git a/app/views/errors/gitolite.html.haml b/app/views/errors/gitolite.html.haml index 4788c2e5..d5f51951 100644 --- a/app/views/errors/gitolite.html.haml +++ b/app/views/errors/gitolite.html.haml @@ -21,7 +21,5 @@ Permissions: %pre = preserve do - sudo chmod -R 770 /home/git/repositories/ - sudo chown -R git:git /home/git/repositories/ - sudo chown gitlab:gitlab /home/git/repositories/**/hooks/post-receive - + sudo chmod -R 770 #{Gitlab.config.git_base_path} + sudo chown -R git:git #{Gitlab.config.git_base_path} diff --git a/app/views/errors/invalid_ssh_key.html.haml b/app/views/errors/invalid_ssh_key.html.haml new file mode 100644 index 00000000..fb7922b0 --- /dev/null +++ b/app/views/errors/invalid_ssh_key.html.haml @@ -0,0 +1,3 @@ +%h1 Git Error +%hr +%p Seems like SSH Key you provided is not a valid SSH key. diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml index 4ef92749..66e14936 100644 --- a/app/views/events/_event_last_push.html.haml +++ b/app/views/events/_event_last_push.html.haml @@ -9,5 +9,5 @@ at %strong= link_to event.project.name, event.project - = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn very_small primary" do + = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn very_small" do Create Merge Request diff --git a/app/views/help/api.html.haml b/app/views/help/api.html.haml index 344f8ade..d7d7aed4 100644 --- a/app/views/help/api.html.haml +++ b/app/views/help/api.html.haml @@ -9,6 +9,8 @@ %a{href: "#README"} README %li %a{href: "#projects"} Projects + %li + %a{href: "#snippets"} Snippets %li %a{href: "#users"} Users %li @@ -34,6 +36,16 @@ %br +.file_holder#snippets + .file_title + %i.icon-file + Projects Snippets + .file_content.wiki + = preserve do + = markdown File.read(Rails.root.join("doc", "api", "snippets.md")) + +%br + .file_holder#users .file_title %i.icon-file @@ -51,3 +63,13 @@ .file_content.wiki = preserve do = markdown File.read(Rails.root.join("doc", "api", "issues.md")) + +%br + +.file_holder#milestones + .file_title + %i.icon-file + Milestones + .file_content.wiki + = preserve do + = markdown File.read(Rails.root.join("doc", "api", "milestones.md")) diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index 66f7c722..02549577 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -31,3 +31,6 @@ %li %h5= link_to "Gitlab Markdown", help_markdown_path + + %li + %h5= link_to "SSH keys", help_ssh_path diff --git a/app/views/help/markdown.html.haml b/app/views/help/markdown.html.haml index 8d6fb2a5..6a4bbb02 100644 --- a/app/views/help/markdown.html.haml +++ b/app/views/help/markdown.html.haml @@ -1,25 +1,105 @@ -- bash_lexer = Pygments::Lexer[:bash] -%h3.page_title Gitlab Markdown +%h3.page_title Gitlab Flavored Markdown .back_link = link_to help_path do ← to index %hr -%p.slead We extend Markdown with some GITLAB specific syntax. It allows you to link to: +.row + .span8 + %p + For Gitlab we developed something we call "Gitlab Flavored Markdown" (GFM). + It extends the standard Markdown in a few significant ways adds some useful functionality. -%ul - %li issues (#123) - %li merge request (!123) - %li commits (1234567) - %li team members (@foo) - %li snippets ($123) + %p You can use GFM in: + %ul + %li commit messages + %li comments + %li wall posts + %li issues + %li merge requests + %li milestones + %li wiki pages -%p.slead in + %h3 Differences from traditional Markdown -%ul - %li commit messages - %li notes/comments/wall posts - %li issues - %li merge requests - %li milestones - %li wiki pages + %h4 Newlines + + %p + The biggest difference that GFM introduces is in the handling of linebreaks. + With traditional Markdown you can hard wrap paragraphs of text and they will be combined into a single paragraph. We find this to be the cause of a huge number of unintentional formatting errors. + GFM treats newlines in paragraph-like content as real line breaks, which is probably what you intended. + + + %p The next paragraph contains two phrases separated by a single newline character: + %pre= "Roses are red\nViolets are blue" + %p becomes + = markdown "Roses are red\nViolets are blue" + + %h4 Multiple underscores in words + + %p + It is not reasonable to italicize just part of a word, especially when you're dealing with code and names often appear with multiple underscores. + Therefore, GFM ignores multiple underscores in words. + + %pre= "perform_complicated_task\ndo_this_and_do_that_and_another_thing" + %p becomes + = markdown "perform_complicated_task\ndo_this_and_do_that_and_another_thing" + + %h4 URL autolinking + + %p + GFM will autolink standard URLs you copy and paste into your text. + So if you want to link to a URL (instead of a textual link), you can simply put the URL in verbatim and it will be turned into a link to that URL. + + %h4 Fenced code blocks + + %p + Markdown converts text with four spaces at the front of each line to code blocks. + GFM supports that, but we also support fenced blocks. + Just wrap your code blocks in ``` and you won't need to indent manually to trigger a code block. + + %pre= %Q{```ruby\nrequire 'redcarpet'\nmarkdown = Redcarpet.new("Hello World!")\nputs markdown.to_html\n```} + %p becomes + = markdown %Q{```ruby\nrequire 'redcarpet'\nmarkdown = Redcarpet.new("Hello World!")\nputs markdown.to_html\n```} + + %h4 Special Gitlab references + + %p + GFM recognizes special references. + You can easily reference e.g. a team member, an issue or a commit within a project. + GFM will turn that reference into a link so you can navigate between them easily. + + %p GFM will recognize the following references: + %ul + %li + %code @foo + for team members + %li + %code #123 + for issues + %li + %code !123 + for merge request + %li + %code $123 + for snippets + %li + %code 1234567 + for commits + + -# this example will only be shown if the user has a project with at least one issue + - if @project = current_user.projects.first + - if issue = @project.issues.first + %p For example in your #{link_to @project.name, project_path(@project)} project something like + %pre= "This is related to ##{issue.id}. @#{current_user.name} is working on solving it." + %p becomes + = markdown "This is related to ##{issue.id}. @#{current_user.name} is working on solving it." + + + + .span4.right + .alert.alert-info + %p + If you're not already familiar with Markdown, you should spend 15 minutes and go over the excellent + %strong= link_to "Markdown Syntax Guide", "http://daringfireball.net/projects/markdown/syntax" + at Daring Fireball. diff --git a/app/views/help/permissions.html.haml b/app/views/help/permissions.html.haml index 7511d15d..f9287fa0 100644 --- a/app/views/help/permissions.html.haml +++ b/app/views/help/permissions.html.haml @@ -1,6 +1,6 @@ -%h3 Permissions +%h3.page_title Permissions .back_link - = link_to help_path do + = link_to help_path do ← to index %hr diff --git a/app/views/help/ssh.html.haml b/app/views/help/ssh.html.haml new file mode 100644 index 00000000..6a581204 --- /dev/null +++ b/app/views/help/ssh.html.haml @@ -0,0 +1,25 @@ +%h3.page_title SSH Keys +.back_link + = link_to help_path do + ← to index +%hr + +%p.slead + SSH key allows you to establish a secure connection between your computer and Gitlab + +%p.slead + To generate a new SSH key just open your terminal and use code below. + +%pre.dark + ssh-keygen -t rsa -C "#{current_user.email}" + + \# Creates a new ssh key using the provided email + \# Generating public/private rsa key pair... + +%p.slead + Next just use code below to dump your public key and add to GITLAB SSH Keys + +%pre.dark + cat ~/.ssh/id_rsa.pub + + \# ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6eNtGpNGwstc.... diff --git a/app/views/help/system_hooks.html.haml b/app/views/help/system_hooks.html.haml index 2088208a..9fc8cbab 100644 --- a/app/views/help/system_hooks.html.haml +++ b/app/views/help/system_hooks.html.haml @@ -1,10 +1,10 @@ %h3 System hooks .back_link - = link_to :back do + = link_to :back do ← back %hr -%p.slead +%p.slead Your Gitlab instance can perform HTTP POST request on next event: create_project, delete_project, create_user, delete_user, change_team_member. %br System Hooks can be used for logging or change information in LDAP server. diff --git a/app/views/help/web_hooks.html.haml b/app/views/help/web_hooks.html.haml index 3acea62c..263eadf6 100644 --- a/app/views/help/web_hooks.html.haml +++ b/app/views/help/web_hooks.html.haml @@ -1,11 +1,11 @@ -%h3 Web hooks +%h3.page_title Web hooks .back_link - = link_to help_path do + = link_to help_path do ← to index %hr -%p.slead - Every Gitlab project can trigger a web server whenever the repo is pushed to. +%p.slead + Every Gitlab project can trigger a web server whenever the repo is pushed to. %br Web Hooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server. %br diff --git a/app/views/help/workflow.html.haml b/app/views/help/workflow.html.haml index 7db8133b..a3fe3b01 100644 --- a/app/views/help/workflow.html.haml +++ b/app/views/help/workflow.html.haml @@ -1,7 +1,6 @@ -- bash_lexer = Pygments::Lexer[:bash] -%h3 Workflow +%h3.page_title Workflow .back_link - = link_to help_path do + = link_to help_path do ← to index %hr @@ -9,25 +8,25 @@ %li %p Clone project .bash - %pre + %pre.dark git clone git@example.com:project-name.git %li %p Create branch with your feature .bash - %pre + %pre.dark git checkout -b $feature_name %li %p Write code. Commit changes .bash - %pre + %pre.dark git commit -am "My feature is ready" %li %p Push your branch to gitlabhq .bash - %pre + %pre.dark git push origin $feature_name %li diff --git a/app/views/hooks/_data_ex.html.erb b/app/views/hooks/_data_ex.html.erb index 8d3de3f0..e43714e9 100644 --- a/app/views/hooks/_data_ex.html.erb +++ b/app/views/hooks/_data_ex.html.erb @@ -37,7 +37,7 @@ } } ], - total_commits_count => 3 + total_commits_count => 4 } eos %> diff --git a/app/views/hooks/index.html.haml b/app/views/hooks/index.html.haml index 4e15dc50..3d2a381e 100644 --- a/app/views/hooks/index.html.haml +++ b/app/views/hooks/index.html.haml @@ -8,7 +8,7 @@ Read more about web hooks %strong #{link_to "here", help_web_hooks_path, class: "vlink"} -= form_for [@project, @hook], as: :hook, url: project_hooks_path(@project) do |f| += form_for [@project, @hook], as: :hook, url: project_hooks_path(@project), html: { class: 'form-inline' } do |f| -if @hook.errors.any? .alert-message.block-message.error - @hook.errors.full_messages.each do |msg| diff --git a/app/views/issues/_form.html.haml b/app/views/issues/_form.html.haml index 1b67eabd..db7920b9 100644 --- a/app/views/issues/_form.html.haml +++ b/app/views/issues/_form.html.haml @@ -38,19 +38,20 @@ = f.label :description, "Details" .input = f.text_area :description, maxlength: 2000, class: "xxlarge", rows: 14 - %p.hint Markdown is enabled. + %p.hint Issues are parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}. .actions - if @issue.new_record? - = f.submit 'Submit new issue', class: "primary btn" + = f.submit 'Submit new issue', class: "btn save-btn" -else - = f.submit 'Save changes', class: "primary btn" + = f.submit 'Save changes', class: "save-btn btn" + - cancel_class = 'btn cancel-btn' - if request.xhr? - = link_to "Cancel", "#back", onclick: "backToIssues();", class: "btn" + = link_to "Cancel", "#back", onclick: "backToIssues();", class: cancel_class - else - if @issue.new_record? - = link_to "Cancel", project_issues_path(@project), class: "btn" + = link_to "Cancel", project_issues_path(@project), class: cancel_class - else - = link_to "Cancel", project_issue_path(@project, @issue), class: "btn" + = link_to "Cancel", project_issue_path(@project, @issue), class: cancel_class diff --git a/app/views/issues/_head.html.haml b/app/views/issues/_head.html.haml index 1f6e7d7f..8ebe3e05 100644 --- a/app/views/issues/_head.html.haml +++ b/app/views/issues/_head.html.haml @@ -5,6 +5,9 @@ %li{class: "#{'active' if current_page?(project_milestones_path(@project))}"} = link_to project_milestones_path(@project), class: "tab" do Milestones + %li{class: "#{'active' if current_page?(project_labels_path(@project))}"} + = link_to project_labels_path(@project), class: "tab" do + Labels %li.right %span.rss-icon = link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do diff --git a/app/views/issues/index.html.haml b/app/views/issues/index.html.haml index a6836fd4..010b8856 100644 --- a/app/views/issues/index.html.haml +++ b/app/views/issues/index.html.haml @@ -6,7 +6,7 @@ .right .span5 - if can? current_user, :write_issue, @project - = link_to new_project_issue_path(@project), class: "right btn small", title: "New Issue", remote: true do + = link_to new_project_issue_path(@project), class: "right btn", title: "New Issue", remote: true do %i.icon-plus New Issue = form_tag search_project_issues_path(@project), method: :get, remote: true, id: "issue_search_form", class: :right do diff --git a/app/views/keys/_form.html.haml b/app/views/keys/_form.html.haml index ee2eafdd..26700803 100644 --- a/app/views/keys/_form.html.haml +++ b/app/views/keys/_form.html.haml @@ -11,8 +11,14 @@ .input= f.text_field :title .clearfix = f.label :key - .input= f.text_area :key, class: [:xxlarge, :thin_area] - .actions - = f.submit 'Save', class: "primary btn" - = link_to "Cancel", keys_path, class: "btn" + .input + = f.text_area :key, class: [:xxlarge, :thin_area] + %p.hint + Paste your public key here. Read more about how generate it + = link_to "here", help_ssh_path + + + .actions + = f.submit 'Save', class: "btn save-btn" + = link_to "Cancel", keys_path, class: "btn cancel-btn" diff --git a/app/views/keys/index.html.haml b/app/views/keys/index.html.haml index 04e9e4cb..9b5663ed 100644 --- a/app/views/keys/index.html.haml +++ b/app/views/keys/index.html.haml @@ -1,6 +1,6 @@ %h3.page_title SSH Keys - = link_to "Add new", new_key_path, class: "btn small right" + = link_to "Add new", new_key_path, class: "btn right" %hr %p.slead diff --git a/app/views/keys/new.html.haml b/app/views/keys/new.html.haml index 02e782b9..fff38058 100644 --- a/app/views/keys/new.html.haml +++ b/app/views/keys/new.html.haml @@ -1,4 +1,4 @@ -%h3.page_title New key +%h3.page_title Add an SSH Key %hr = render 'form' diff --git a/app/views/labels/_label.html.haml b/app/views/labels/_label.html.haml new file mode 100644 index 00000000..32158c20 --- /dev/null +++ b/app/views/labels/_label.html.haml @@ -0,0 +1,4 @@ +%li.wll + %strong= label.name + .right + %span= pluralize label.count, 'issue' diff --git a/app/views/labels/index.html.haml b/app/views/labels/index.html.haml new file mode 100644 index 00000000..4e41d375 --- /dev/null +++ b/app/views/labels/index.html.haml @@ -0,0 +1,14 @@ += render "issues/head" + +%h3.page_title + Labels +%br +%div.ui-box + %ul.unstyled.labels-table + - @labels.each do |label| + = render 'label', label: label + + - unless @labels.present? + %li + %h3.nothing_here_message Nothing to show here + diff --git a/app/views/merge_requests/_form.html.haml b/app/views/merge_requests/_form.html.haml index b6c12397..b554c051 100644 --- a/app/views/merge_requests/_form.html.haml +++ b/app/views/merge_requests/_form.html.haml @@ -9,7 +9,7 @@ %br .row - .span6 + .span5 .mr_branch_box %h5 From (Head Branch) .body @@ -17,10 +17,11 @@ = f.label :source_branch, "From", class: "control-label" .controls = f.select(:source_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px") - .bottom_commit - .mr_source_commit + .mr_source_commit - .span6 + .span2 + %center= image_tag "merge.png", class: 'mr_direction_tip' + .span5 .mr_branch_box %h5 To (Base Branch) .body @@ -28,8 +29,7 @@ = f.label :target_branch, "To", class: "control-label" .controls = f.select(:target_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px") - .bottom_commit - .mr_target_commit + .mr_target_commit %h4.cdark 2. Fill info @@ -48,18 +48,19 @@ .control-group .form-actions - = f.submit 'Save', class: "btn-primary btn" + = f.submit 'Save', class: "btn save-btn" - if @merge_request.new_record? - = link_to project_merge_requests_path(@project), class: "btn" do + = link_to project_merge_requests_path(@project), class: "btn cancel-btn" do Cancel - else - = link_to project_merge_request_path(@project, @merge_request), class: "btn" do + = link_to project_merge_request_path(@project, @merge_request), class: "btn cancel-btn" do Cancel :javascript $(function(){ + disableButtonIfEmtpyField("#merge_request_title", ".save-btn"); $('select#merge_request_assignee_id').chosen(); $('select#merge_request_source_branch').chosen(); $('select#merge_request_target_branch').chosen(); diff --git a/app/views/merge_requests/index.html.haml b/app/views/merge_requests/index.html.haml index 4ad6e5c1..bbf35dc7 100644 --- a/app/views/merge_requests/index.html.haml +++ b/app/views/merge_requests/index.html.haml @@ -1,7 +1,7 @@ %h3.page_title Merge Requests - if can? current_user, :write_issue, @project - = link_to new_project_merge_request_path(@project), class: "right btn small", title: "New Merge Request" do + = link_to new_project_merge_request_path(@project), class: "right btn", title: "New Merge Request" do New Merge Request %br @@ -10,17 +10,17 @@ .ui-box .title %ul.nav.nav-pills - %li{class: ("active" if (params[:f] == "0" || !params[:f]))} - = link_to project_merge_requests_path(@project, f: 0) do + %li{class: ("active" if (params[:f] == 'open' || !params[:f]))} + = link_to project_merge_requests_path(@project, f: 'open') do Open - %li{class: ("active" if params[:f] == "2")} - = link_to project_merge_requests_path(@project, f: 2) do + %li{class: ("active" if params[:f] == "closed")} + = link_to project_merge_requests_path(@project, f: "closed") do Closed - %li{class: ("active" if params[:f] == "3")} - = link_to project_merge_requests_path(@project, f: 3) do + %li{class: ("active" if params[:f] == 'assigned-to-me')} + = link_to project_merge_requests_path(@project, f: 'assigned-to-me') do To Me - %li{class: ("active" if params[:f] == "1")} - = link_to project_merge_requests_path(@project, f: 1) do + %li{class: ("active" if params[:f] == 'all')} + = link_to project_merge_requests_path(@project, f: 'all') do All %ul.unstyled diff --git a/app/views/merge_requests/show/_how_to_merge.html.haml b/app/views/merge_requests/show/_how_to_merge.html.haml index c21f2727..69881d43 100644 --- a/app/views/merge_requests/show/_how_to_merge.html.haml +++ b/app/views/merge_requests/show/_how_to_merge.html.haml @@ -3,13 +3,12 @@ %a.close{href: "#"} × %h3 How To Merge .modal-body - %pre + %pre.dark = preserve do - :erb - git checkout <%= @merge_request.target_branch %> - git fetch origin - git merge origin/<%= @merge_request.source_branch %> - git push origin <%= @merge_request.target_branch %> + git checkout #{@merge_request.target_branch} + git fetch origin + git merge origin/#{@merge_request.source_branch} + git push origin #{@merge_request.target_branch} :javascript diff --git a/app/views/merge_requests/show/_mr_title.html.haml b/app/views/merge_requests/show/_mr_title.html.haml index 31fa0779..3ae1050d 100644 --- a/app/views/merge_requests/show/_mr_title.html.haml +++ b/app/views/merge_requests/show/_mr_title.html.haml @@ -1,9 +1,9 @@ %h3.page_title = "Merge Request ##{@merge_request.id}:"   - %span.pretty_label.branch= @merge_request.source_branch + %span.label_branch= @merge_request.source_branch → - %span.pretty_label.branch= @merge_request.target_branch + %span.label_branch= @merge_request.target_branch %span.right - if @merge_request.merged? diff --git a/app/views/milestones/_form.html.haml b/app/views/milestones/_form.html.haml index 1cd08ac3..41cbd6ab 100644 --- a/app/views/milestones/_form.html.haml +++ b/app/views/milestones/_form.html.haml @@ -22,7 +22,7 @@ = f.label :description, "Description", class: "control-label" .controls = f.text_area :description, maxlength: 2000, class: "input-xlarge", rows: 10 - %p.hint Markdown is enabled. + %p.hint Milestones are parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}. .span6 .control-group = f.label :due_date, "Due Date", class: "control-label" @@ -32,20 +32,16 @@ .form-actions - if @milestone.new_record? - = f.submit 'Create milestone', class: "primary btn" + = f.submit 'Create milestone', class: "save-btn btn" + = link_to "Cancel", project_milestones_path(@project), class: "btn cancel-btn" -else - = f.submit 'Save changes', class: "primary btn" + = f.submit 'Save changes', class: "save-btn btn" + = link_to "Cancel", project_milestone_path(@project, @milestone), class: "btn cancel-btn" - - if request.xhr? - = link_to "Cancel", "#back", onclick: "backToIssues();", class: "btn" - - else - - if @milestone.new_record? - = link_to "Cancel", project_milestones_path(@project), class: "btn" - - else - = link_to "Cancel", project_milestone_path(@project, @milestone), class: "btn" :javascript $(function() { + disableButtonIfEmtpyField("#milestone_title", ".save-btn"); $( ".datepicker" ).datepicker({ dateFormat: "yy-mm-dd", onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } diff --git a/app/views/milestones/index.html.haml b/app/views/milestones/index.html.haml index ecb008dc..c5333b08 100644 --- a/app/views/milestones/index.html.haml +++ b/app/views/milestones/index.html.haml @@ -8,11 +8,11 @@ %div.ui-box .title %ul.nav.nav-pills - %li{class: ("active" if (params[:f] == "0" || !params[:f]))} - = link_to project_milestones_path(@project, f: 0) do + %li{class: ("active" if (params[:f] == "active" || !params[:f]))} + = link_to project_milestones_path(@project, f: "active") do Active - %li{class: ("active" if params[:f] == "1")} - = link_to project_milestones_path(@project, f: 1) do + %li{class: ("active" if params[:f] == "all")} + = link_to project_milestones_path(@project, f: "all") do All %ul.unstyled diff --git a/app/views/notes/_create_common.js.haml b/app/views/notes/_create_common.js.haml index 847ff383..e80eccb1 100644 --- a/app/views/notes/_create_common.js.haml +++ b/app/views/notes/_create_common.js.haml @@ -1,9 +1,12 @@ - if note.valid? :plain - $("#new_note .errors").remove(); - $('#new_note textarea').val(""); + $(".note-form-holder .error").remove(); + $('.note-form-holder textarea').val(""); + $('.note-form-holder #preview-link').text('Preview'); + $('.note-form-holder #preview-note').hide(); + $('.note-form-holder').show(); NoteList.prepend(#{note.id}, "#{escape_javascript(render partial: "notes/show", locals: {note: note})}"); - else :plain - $("#new_note").replaceWith("#{escape_javascript(render('form'))}"); + $(".note-form-holder").replaceWith("#{escape_javascript(render('form'))}"); diff --git a/app/views/notes/_create_line.js.haml b/app/views/notes/_create_line.js.haml index 13809bec..662909f7 100644 --- a/app/views/notes/_create_line.js.haml +++ b/app/views/notes/_create_line.js.haml @@ -1,7 +1,7 @@ - if note.valid? :plain $(".per_line_form").hide(); - $('#new_note textarea').val(""); + $('.line-note-form-holder textarea').val(""); $("a.line_note_reply_link[line_code='#{note.line_code}']").closest("tr").remove(); var trEl = $(".#{note.line_code}").parent(); trEl.after("#{escape_javascript(render partial: "notes/per_line_show", locals: {note: note})}"); diff --git a/app/views/notes/_form.html.haml b/app/views/notes/_form.html.haml index dac026bd..7211a0ae 100644 --- a/app/views/notes/_form.html.haml +++ b/app/views/notes/_form.html.haml @@ -1,38 +1,39 @@ -= form_for [@project, @note], remote: "true", multipart: true do |f| - %h3.page_title Leave a comment - -if @note.errors.any? - .alert-message.block-message.error - - @note.errors.full_messages.each do |msg| - %div= msg +.note-form-holder + = form_for [@project, @note], remote: "true", multipart: true do |f| + %h3.page_title Leave a comment + -if @note.errors.any? + .alert-message.block-message.error + - @note.errors.full_messages.each do |msg| + %div= msg - = f.hidden_field :noteable_id - = f.hidden_field :noteable_type - = f.text_area :note, size: 255 - #preview-note.well.hide - %p.hint - = link_to "Gitlab Markdown", help_markdown_path, target: '_blank' - is enabled. - = link_to 'Preview', preview_project_notes_path(@project), id: 'preview-link' + = f.hidden_field :noteable_id + = f.hidden_field :noteable_type + = f.text_area :note, size: 255, class: 'note-text' + #preview-note.preview_note.hide + .hint + .right Comments are parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}. + .clearfix - .row.note_advanced_opts.hide - .span2 - = f.submit 'Add Comment', class: "btn primary submit_note", id: "submit_note" - .span4.notify_opts - %h6.left Notify via email: - = label_tag :notify do - = check_box_tag :notify, 1, @note.noteable_type != "Commit" - %span Project team + .row.note_advanced_opts.hide + .span3 + = f.submit 'Add Comment', class: "btn success submit_note grouped", id: "submit_note" + = link_to 'Preview', preview_project_notes_path(@project), class: 'btn grouped', id: 'preview-link' + .span4.notify_opts + %h6.left Notify via email: + = label_tag :notify do + = check_box_tag :notify, 1, @note.noteable_type != "Commit" + %span Project team - - if @note.notify_only_author?(current_user) - = label_tag :notify_author do - = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit" - %span Commit author - .span6.attachments - %h6.left Attachment: - %span.file_name File name... - - .input.input_file - %a.file_upload.btn.small Upload File - = f.file_field :attachment, class: "input-file" - %span.hint Any file less than 10 MB + - if @note.notify_only_author?(current_user) + = label_tag :notify_author do + = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit" + %span Commit author + .span5.attachments + %h6.left Attachment: + %span.file_name File name... + + .input.input_file + %a.file_upload.btn.small Upload File + = f.file_field :attachment, class: "input-file" + %span.hint Any file less than 10 MB diff --git a/app/views/notes/_per_line_form.html.haml b/app/views/notes/_per_line_form.html.haml index afb0b30d..8e31b59e 100644 --- a/app/views/notes/_per_line_form.html.haml +++ b/app/views/notes/_per_line_form.html.haml @@ -1,33 +1,34 @@ %table{style: "display:none;"} %tr.per_line_form %td{colspan: 3 } - = form_for [@project, @note], remote: "true", multipart: true do |f| - %h3.page_title Leave a note - %div.span10 - -if @note.errors.any? - .alert-message.block-message.error - - @note.errors.full_messages.each do |msg| - %div= msg + .line-note-form-holder + = form_for [@project, @note], remote: "true", multipart: true do |f| + %h3.page_title Leave a note + %div.span10 + -if @note.errors.any? + .alert-message.block-message.error + - @note.errors.full_messages.each do |msg| + %div= msg - = f.hidden_field :noteable_id - = f.hidden_field :noteable_type - = f.hidden_field :line_code - = f.text_area :note, size: 255 - .note_actions - .buttons - = f.submit 'Add note', class: "btn primary submit_note", id: "submit_note" - = link_to "Cancel", "#", class: "btn hide-button" - .options - %h6.left Notify via email: - .labels - = label_tag :notify do - = check_box_tag :notify, 1, @note.noteable_type != "Commit" - %span Project team + = f.hidden_field :noteable_id + = f.hidden_field :noteable_type + = f.hidden_field :line_code + = f.text_area :note, size: 255, class: 'line-note-text' + .note_actions + .buttons + = f.submit 'Add note', class: "btn save-btn submit_note submit_inline_note", id: "submit_note" + = link_to "Cancel", "#", class: "btn hide-button" + .options + %h6.left Notify via email: + .labels + = label_tag :notify do + = check_box_tag :notify, 1, @note.noteable_type != "Commit" + %span Project team - - if @note.notify_only_author?(current_user) - = label_tag :notify_author do - = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit" - %span Commit author + - if @note.notify_only_author?(current_user) + = label_tag :notify_author do + = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit" + %span Commit author :javascript $(function(){ diff --git a/app/views/notify/issue_status_changed_email.html.haml b/app/views/notify/issue_status_changed_email.html.haml new file mode 100644 index 00000000..59130f79 --- /dev/null +++ b/app/views/notify/issue_status_changed_email.html.haml @@ -0,0 +1,16 @@ +%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"} + %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"} + %tr + %td{style: "font-size: 1px; line-height: 1px;", width: "21"} + %td{align: "left", style: "padding: 20px 0 0;"} + %h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "} + = "Issue was #{@issue_status} by #{@updated_by.name}" + %td{style: "font-size: 1px; line-height: 1px;", width: "21"} + %tr + %td{style: "font-size: 1px; line-height: 1px;", width: "21"} + %td{align: "left", style: "padding: 20px 0 0;"} + %h2{style: "color:#646464 !important; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "} + = "Issue ##{@issue.id}" + = link_to_gfm truncate(@issue.title, length: 45), project_issue_url(@issue.project, @issue), title: @issue.title + %br + diff --git a/app/views/notify/project_access_granted_email.html.haml b/app/views/notify/project_access_granted_email.html.haml new file mode 100644 index 00000000..154c2aaa --- /dev/null +++ b/app/views/notify/project_access_granted_email.html.haml @@ -0,0 +1,14 @@ +%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"} + %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"} + %tr + %td{style: "font-size: 1px; line-height: 1px;", width: "21"} + %td{align: "left", style: "padding: 20px 0 0;"} + %h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "} + = "You got granted #{@users_project.project_access_human} access to project" + %td{style: "font-size: 1px; line-height: 1px;", width: "21"} + %tr + %td{style: "font-size: 1px; line-height: 1px;", width: "21"} + %td{align: "left", style: "padding: 20px 0 0;"} + %h2{style: "color:#646464 !important; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "} + = link_to_gfm truncate(@project.name, length: 45), project_url(@project), title: @project.name + %br diff --git a/app/views/profile/password.html.haml b/app/views/profile/password.html.haml index 257dacb1..d0aee7ac 100644 --- a/app/views/profile/password.html.haml +++ b/app/views/profile/password.html.haml @@ -16,4 +16,4 @@ = f.label :password_confirmation .input= f.password_field :password_confirmation .actions - = f.submit 'Save', class: "btn primary" + = f.submit 'Save', class: "btn save-btn" diff --git a/app/views/profile/show.html.haml b/app/views/profile/show.html.haml index 95cce2bb..22e840a0 100644 --- a/app/views/profile/show.html.haml +++ b/app/views/profile/show.html.haml @@ -45,9 +45,10 @@ %span.help-block Tell us about yourself in fewer than 250 characters. .span5.right - %p.alert.alert-info - %strong Tip: - You can change your avatar at gravatar.com + -unless Gitlab.config.disable_gravatar? + %p.alert.alert-info + %strong Tip: + You can change your avatar at gravatar.com %h4 Personal projects: @@ -66,4 +67,4 @@ = link_to "Add Public Key", new_key_path, class: "btn small right" .form-actions - = f.submit 'Save', class: "btn-primary btn" + = f.submit 'Save', class: "btn save-btn" diff --git a/app/views/projects/_clone_panel.html.haml b/app/views/projects/_clone_panel.html.haml new file mode 100644 index 00000000..839a98a0 --- /dev/null +++ b/app/views/projects/_clone_panel.html.haml @@ -0,0 +1,21 @@ +.project_clone_panel + .row + .span7 + .form-horizontal + .input-prepend.project_clone_holder + = link_to "SSH", "#", class: "btn small active", :"data-clone" => @project.ssh_url_to_repo + = link_to "HTTP", "#", class: "btn small", :"data-clone" => @project.http_url_to_repo + = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span5" + .span4.right + .right + - if can? current_user, :download_code, @project + = link_to archive_project_repository_path(@project), class: "btn small grouped" do + %i.icon-download-alt + Download + - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project) + = link_to new_project_merge_request_path(@project), title: "New Merge Request", class: "btn small grouped" do + Merge Request + - if @project.issues_enabled && can?(current_user, :write_issue, @project) + = link_to new_project_issue_path(@project), title: "New Issue", class: "btn small grouped" do + Issue + diff --git a/app/views/projects/_form.html.haml b/app/views/projects/_form.html.haml index ce66b2cf..8bdeda1c 100644 --- a/app/views/projects/_form.html.haml +++ b/app/views/projects/_form.html.haml @@ -10,9 +10,9 @@ .input = f.text_field :name, placeholder: "Example Project", class: "xxlarge" - %h5.page_title - .alert.alert-info - %h5 Advanced settings: + %hr + .adv_settings + %h6 Advanced settings: .clearfix = f.label :path do Path @@ -34,8 +34,9 @@ .input= f.select(:default_branch, @project.heads.map(&:name), {}, style: "width:210px;") - unless @project.new_record? - .alert.alert-info - %h5 Features: + %hr + .adv_settings + %h6 Features: .clearfix = f.label :issues_enabled, "Issues" @@ -56,7 +57,7 @@ %br .actions - = f.submit 'Save', class: "btn primary" + = f.submit 'Save', class: "btn save-btn" = link_to 'Cancel', @project, class: "btn" - unless @project.new_record? .right diff --git a/app/views/projects/_new_form.html.haml b/app/views/projects/_new_form.html.haml index 5104df83..e6d5e93f 100644 --- a/app/views/projects/_new_form.html.haml +++ b/app/views/projects/_new_form.html.haml @@ -7,11 +7,11 @@ Project name is .input = f.text_field :name, placeholder: "Example Project", class: "xxlarge" - = f.submit 'Create project', class: "btn primary" + = f.submit 'Create project', class: "btn primary project-submit" %hr - .alert.alert-info - %h5 Advanced settings: + %div.adv_settings + %h6 Advanced settings: .clearfix = f.label :path do Git Clone diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index b8d0dad9..d408c0a6 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -1,47 +1,51 @@ -- if current_user.require_ssh_key? - .alert-message.block-message.error - %ul - %li You have no ssh keys added to your profile. - %li You wont be able to pull/push repository. - %li Visit profile → keys and add public key of every machine you want to use for work with gitlabhq. - -.alert-message.block-message.error - %ul.unstyled.alert_holder - %li You should push repository to proceed. - %li After push you will be able to browse code, commits etc. - -- bash_lexer = Pygments::Lexer[:bash] += render 'shared/no_ssh' +.project_clone_panel + .row + .span7 + .form-horizontal + .input-prepend.project_clone_holder + = link_to "SSH", "#", class: "btn small active", :"data-clone" => @project.ssh_url_to_repo + = link_to "HTTP", "#", class: "btn small", :"data-clone" => @project.http_url_to_repo + = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span5" %div.git-empty - %h3 Git global setup: - - setup_str = ["git config --global user.name \"#{current_user.name}\"", - "git config --global user.email \"#{current_user.email}\""].join("\n") - = preserve do - = raw bash_lexer.highlight(setup_str, lexer: 'bash', options: {encoding: 'utf-8'}) + %h4 Git global setup: + %pre.dark + = preserve do + git config --global user.name "#{current_user.name}" + git config --global user.email "#{current_user.email}" - %br - %br - %h3 Create Repository - - repo_setup_str = ["mkdir #{@project.path}", - "cd #{@project.path}", - "git init", - "touch README", - "git add README", - "git commit -m 'first commit'", - "git remote add origin #{@project.url_to_repo}", - "git push -u origin master"].join("\n") + %h4.prepend-top-20 Create Repository + %pre.dark + = preserve do + mkdir #{@project.path} + cd #{@project.path} + git init + touch README + git add README + git commit -m 'first commit' + git remote add origin #{@project.url_to_repo} + git push -u origin master - = preserve do - = raw bash_lexer.highlight(repo_setup_str) - - %br - %br - %h3 Existing Git Repo? - - exist_repo_setup_str = ["cd existing_git_repo", - "git remote add origin #{@project.url_to_repo}", - "git push -u origin master"].join("\n") - = preserve do - = raw bash_lexer.highlight(exist_repo_setup_str) + %h4.prepend-top-20 Existing Git Repo? + %pre.dark + = preserve do + cd existing_git_repo + git remote add origin #{@project.url_to_repo} + git push -u origin master - if can? current_user, :admin_project, @project - .alert-message.block-message.error.prepend-top-20 - = link_to 'Remove project', @project, confirm: 'Are you sure?', method: :delete, class: "btn danger" + .prepend-top-20 + = link_to 'Remove project', @project, confirm: 'Are you sure?', method: :delete, class: "btn danger right" + + + +:javascript + $(function(){ + var link_sel = ".project_clone_holder a"; + $(link_sel).bind("click", function() { + $(link_sel).removeClass("active"); + $(this).addClass("active"); + $("#project_clone").val($(this).attr("data-clone")); + }) + }) + diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 703e558a..933cb671 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -3,10 +3,10 @@ New Project %hr = render 'new_form' -%div.ajax_loader.hide +%div.save-project-loader.hide %center - %div.padded= image_tag "ajax_loader.gif" - %h3.prepend-top Creating project & repository. Please wait a few minutes + = image_tag "ajax_loader.gif" + %h3 Creating project & repository. Please wait a few minutes :javascript $(function(){ new Projects(); }); diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index ebd2c8e4..77a0ef1a 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,35 +1,12 @@ = render "project_head" - -.entry - .row - .span7 - .form-horizontal - .input-prepend.project_clone_holder - - %span.add-on git clone - = link_to "SSH", "#", class: "btn small active", :"data-clone" => @project.ssh_url_to_repo - = link_to "HTTP", "#", class: "btn small", :"data-clone" => @project.http_url_to_repo - = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span5" - .span4.right - .right - - if can? current_user, :download_code, @project - = link_to archive_project_repository_path(@project), class: "btn small grouped" do - %i.icon-download-alt - Download - - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project) - = link_to new_project_merge_request_path(@project), title: "New Merge Request", class: "btn small grouped" do - Merge Request - - if @project.issues_enabled && can?(current_user, :write_issue, @project) - = link_to new_project_issue_path(@project), title: "New Issue", class: "btn small grouped" do - Issue - += render 'clone_panel' = render "events/event_last_push", event: @last_push .content_list= render @events -:javascript +:javascript $(function(){ var link_sel = ".project_clone_holder a"; - $(link_sel).bind("click", function() { + $(link_sel).bind("click", function() { $(link_sel).removeClass("active"); $(this).addClass("active"); $("#project_clone").val($(this).attr("data-clone")); diff --git a/app/views/refs/_tree_item.html.haml b/app/views/refs/_tree_item.html.haml index 2e6bbf62..d4c4ee8d 100644 --- a/app/views/refs/_tree_item.html.haml +++ b/app/views/refs/_tree_item.html.haml @@ -2,7 +2,7 @@ %tr{ class: "tree-item #{tree_hex_class(content)}", url: tree_file_project_ref_path(@project, @ref, file) } %td.tree-item-file-name = tree_icon(content) - = link_to truncate(content.name, length: 40), tree_file_project_ref_path(@project, @ref || @commit.id, file), remote: :true + %strong= link_to truncate(content.name, length: 40), tree_file_project_ref_path(@project, @ref || @commit.id, file), remote: :true %td.tree_time_ago.cgray - if index == 1 %span.log_loading diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index ed9da1f0..d37ef670 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -1,10 +1,10 @@ -= form_tag search_path, method: :get do |f| += form_tag search_path, method: :get, class: 'form-inline' do |f| .padded = label_tag :search do %strong Looking for .input = text_field_tag :search, params[:search], placeholder: "issue 143", class: "input-xxlarge", id: "dashboard_search" - = submit_tag 'Search', class: "btn btn-primary" + = submit_tag 'Search', class: "btn primary" - if params[:search].present? %br %h3 diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml new file mode 100644 index 00000000..b6ab666b --- /dev/null +++ b/app/views/shared/_no_ssh.html.haml @@ -0,0 +1,8 @@ +- if current_user.require_ssh_key? + %h6.error_message + %span + You wont be able to pull/push project code unless you + %strong + = link_to new_key_path, class: "vlink" do + add SSH key + to your profile diff --git a/app/views/team_members/_show.html.haml b/app/views/team_members/_show.html.haml index f47554c1..2dc4fb65 100644 --- a/app/views/team_members/_show.html.haml +++ b/app/views/team_members/_show.html.haml @@ -9,7 +9,7 @@ %span.label Blocked = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do - = image_tag gravatar_icon(user.email, 40), class: "avatar" + = image_tag gravatar_icon(user.email, 40), class: "avatar s32" = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do %strong= truncate(user.name, lenght: 40) %br diff --git a/app/views/team_members/show.html.haml b/app/views/team_members/show.html.haml index d7e09bce..6cb357cd 100644 --- a/app/views/team_members/show.html.haml +++ b/app/views/team_members/show.html.haml @@ -51,7 +51,7 @@ = form_for(@team_member, as: :team_member, url: project_team_member_path(@project, @team_member)) do |f| = f.select :project_access, options_for_select(Project.access_options, @team_member.project_access), {}, class: "project-access-select", disabled: !allow_admin %hr - = render user.recent_events.limit(5) + = render @events :javascript $(function(){ $('.repo-access-select, .project-access-select').live("change", function() { diff --git a/app/views/wikis/_form.html.haml b/app/views/wikis/_form.html.haml index 6b6411be..12b57e03 100644 --- a/app/views/wikis/_form.html.haml +++ b/app/views/wikis/_form.html.haml @@ -14,13 +14,14 @@ .middle_box_content .input %span.cgray - Wiki content is parsed with #{link_to "Markdown", "http://en.wikipedia.org/wiki/Markdown"}. - To add link to new page you can just type + Wiki content is parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}. + To link to a (new) page you can just type %code [Link Title](page-slug) + \. .bottom_box_content = f.label :content .input= f.text_area :content, class: 'span8' .actions - = f.submit 'Save', class: "primary btn" - = link_to "Cancel", project_wiki_path(@project, :index), class: "btn" + = f.submit 'Save', class: "save-btn btn" + = link_to "Cancel", project_wiki_path(@project, :index), class: "btn cancel-btn" diff --git a/config/application.rb b/config/application.rb index ecd88b15..ad41f196 100644 --- a/config/application.rb +++ b/config/application.rb @@ -23,7 +23,7 @@ module Gitlab # config.plugins = [ :exception_notification, :ssl_requirement, :all ] # Activate observers that should always be running. - config.active_record.observers = :mailer_observer, :activity_observer, :project_observer, :key_observer, :issue_observer, :user_observer, :system_hook_observer + config.active_record.observers = :mailer_observer, :activity_observer, :project_observer, :key_observer, :issue_observer, :user_observer, :system_hook_observer, :users_project_observer # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. diff --git a/config/environment.rb b/config/environment.rb index c880a7ad..3b186a9d 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -3,5 +3,3 @@ require File.expand_path('../application', __FILE__) # Initialize the rails application Gitlab::Application.initialize! - -require File.join(Rails.root, "lib", "gitlab", "git_host") diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 1818f2c0..d05cc1be 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -1,4 +1,4 @@ -# # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # Gitlab application config file # # # # # # # # # # # # # # # # # # # @@ -19,27 +19,27 @@ email: # Application specific settings # Like default project limit for user etc -app: - default_projects_limit: 10 +app: + default_projects_limit: 10 # backup_path: "/vol/backups" # default: Rails.root + backups/ # backup_keep_time: 604800 # default: 0 (forever) (in seconds) + # disable_gravatar: true # default: false - Disable user avatars from Gravatar.com - -# -# 2. Advanced settings: +# +# 2. Advanced settings: # ========================== # Git Hosting configuration git_host: admin_uri: git@localhost:gitolite-admin base_path: /home/git/repositories/ + # hooks_path: /var/lib/gitolite/.gitolite/hooks/ # only needed when gitolite is not installed according the manual # host: localhost git_user: git upload_pack: true receive_pack: true # port: 22 - # Git settings # Use default values unless you understand it git: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 5c5987a8..27c5bc22 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -66,6 +66,10 @@ class Settings < Settingslogic git_host['base_path'] || '/home/git/repositories/' end + def git_hooks_path + git_host['hooks_path'] || '/home/git/share/gitolite/hooks/' + end + def git_upload_pack if git_host['upload_pack'] != false true @@ -111,5 +115,9 @@ class Settings < Settingslogic def backup_keep_time app['backup_keep_time'] || 0 end + + def disable_gravatar? + app['disable_gravatar'] || false + end end end diff --git a/config/initializers/5_backend.rb b/config/initializers/5_backend.rb new file mode 100644 index 00000000..85f747ac --- /dev/null +++ b/config/initializers/5_backend.rb @@ -0,0 +1,5 @@ +# GIT over HTTP +require Rails.root.join("lib", "gitlab", "backend", "grack_auth") + +# GITOLITE backend +require Rails.root.join("lib", "gitlab", "backend", "gitolite") diff --git a/config/initializers/rails_footnotes.rb b/config/initializers/rails_footnotes.rb deleted file mode 100644 index afe6f3ad..00000000 --- a/config/initializers/rails_footnotes.rb +++ /dev/null @@ -1,3 +0,0 @@ -#if defined?(Footnotes) && Rails.env.development? - #Footnotes.run! # first of all -#end diff --git a/config/routes.rb b/config/routes.rb index 04e13bc4..f895478f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -30,6 +30,7 @@ Gitlab::Application.routes.draw do get 'help/web_hooks' => 'help#web_hooks' get 'help/system_hooks' => 'help#system_hooks' get 'help/markdown' => 'help#markdown' + get 'help/ssh' => 'help#ssh' # # Admin Area @@ -196,7 +197,9 @@ Gitlab::Application.routes.draw do end resources :team_members resources :milestones + resources :labels, :only => [:index] resources :issues do + collection do post :sort post :bulk_update diff --git a/db/fixtures/test/001_repo.rb b/db/fixtures/test/001_repo.rb index ebf005a1..67d4e7bf 100644 --- a/db/fixtures/test/001_repo.rb +++ b/db/fixtures/test/001_repo.rb @@ -1,15 +1,23 @@ -# create tmp dir if not exist -tmp_dir = File.join(Rails.root, "tmp") -Dir.mkdir(tmp_dir) unless File.exists?(tmp_dir) +require 'fileutils' -# Create dir for test repo -repo_dir = File.join(Rails.root, "tmp", "tests") -Dir.mkdir(repo_dir) unless File.exists?(repo_dir) +print "Unpacking seed repository..." -`cp spec/seed_project.tar.gz tmp/tests/` -Dir.chdir(repo_dir) -`tar -xf seed_project.tar.gz` -3.times do |i| -`cp -r gitlabhq/ gitlabhq_#{i}/` -puts "Unpacked seed repo - tmp/tests/gitlabhq_#{i}" +SEED_REPO = 'seed_project.tar.gz' +REPO_PATH = File.join(Rails.root, 'tmp', 'repositories') + +# Make whatever directories we need to make +FileUtils.mkdir_p(REPO_PATH) + +# Copy the archive to the repo path +FileUtils.cp(File.join(Rails.root, 'spec', SEED_REPO), REPO_PATH) + +# chdir to the repo path +FileUtils.cd(REPO_PATH) do + # Extract the archive + `tar -xf #{SEED_REPO}` + + # Remove the copy + FileUtils.rm(SEED_REPO) end + +puts ' done.' diff --git a/db/migrate/20120729131232_add_extern_auth_provider_to_users.rb b/db/migrate/20120729131232_add_extern_auth_provider_to_users.rb new file mode 100644 index 00000000..d5e66ba4 --- /dev/null +++ b/db/migrate/20120729131232_add_extern_auth_provider_to_users.rb @@ -0,0 +1,8 @@ +class AddExternAuthProviderToUsers < ActiveRecord::Migration + def change + add_column :users, :extern_uid, :string + add_column :users, :provider, :string + + add_index :users, [:extern_uid, :provider], :unique => true + end +end diff --git a/db/pkey.example b/db/pkey.example deleted file mode 100644 index ae045772..00000000 --- a/db/pkey.example +++ /dev/null @@ -1,3 +0,0 @@ -AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4 -596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4 -soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0= diff --git a/db/schema.rb b/db/schema.rb index c4c54f56..46461e44 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 => 20120712080407) do +ActiveRecord::Schema.define(:version => 20120729131232) do create_table "events", :force => true do |t| t.string "target_type" @@ -171,9 +171,12 @@ ActiveRecord::Schema.define(:version => 20120712080407) do t.boolean "blocked", :default => false, :null => false t.integer "failed_attempts", :default => 0 t.datetime "locked_at" + t.string "extern_uid" + t.string "provider" end add_index "users", ["email"], :name => "index_users_on_email", :unique => true + add_index "users", ["extern_uid", "provider"], :name => "index_users_on_extern_uid_and_provider", :unique => true add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true create_table "users_projects", :force => true do |t| diff --git a/doc/api/README.md b/doc/api/README.md index e0111966..53b4983e 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -27,4 +27,6 @@ The API uses JSON to serialize data. You don't need to specify `.json` at the en + [Users](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/users.md) + [Projects](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/projects.md) ++ [Snippets](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/snippets.md) + [Issues](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/issues.md) ++ [Milestones](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/milestones.md) diff --git a/doc/api/milestones.md b/doc/api/milestones.md new file mode 100644 index 00000000..f68d8eb7 --- /dev/null +++ b/doc/api/milestones.md @@ -0,0 +1,57 @@ +## List project milestones + +Get a list of project milestones. + +``` +GET /projects/:id/milestones +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project + +## Single milestone + +Get a single project milestone. + +``` +GET /projects/:id/milestones/:milestone_id +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `milestone_id` (required) - The ID of a project milestone + +## New milestone + +Create a new project milestone. + +``` +POST /projects/:id/milestones +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `milestone_id` (required) - The ID of a project milestone ++ `title` (required) - The title of an milestone ++ `description` (optional) - The description of the milestone ++ `due_date` (optional) - The due date of the milestone + +## Edit milestone + +Update an existing project milestone. + +``` +PUT /projects/:id/milestones/:milestone_id +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `milestone_id` (required) - The ID of a project milestone ++ `title` (optional) - The title of a milestone ++ `description` (optional) - The description of a milestone ++ `due_date` (optional) - The due date of the milestone ++ `closed` (optional) - The status of the milestone diff --git a/doc/api/projects.md b/doc/api/projects.md index ead31003..d680b5d8 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -204,108 +204,6 @@ Parameters: ] ``` -# Project Snippets - -## List snippets - -Not implemented. - -## Single snippet - -Get a project snippet. - -``` -GET /projects/:id/snippets/:snippet_id -``` - -Parameters: - -+ `id` (required) - The ID or code name of a project -+ `snippet_id` (required) - The ID of a project's snippet - -```json -{ - "id": 1, - "title": "test", - "file_name": "add.rb", - "author": { - "id": 1, - "email": "john@example.com", - "name": "John Smith", - "blocked": false, - "created_at": "2012-05-23T08:00:58Z" - }, - "expires_at": null, - "updated_at": "2012-06-28T10:52:04Z", - "created_at": "2012-06-28T10:52:04Z" -} -``` - -## Snippet content - -Get a raw project snippet. - -``` -GET /projects/:id/snippets/:snippet_id/raw -``` - -Parameters: - -+ `id` (required) - The ID or code name of a project -+ `snippet_id` (required) - The ID of a project's snippet - -## New snippet - -Create a new project snippet. - -``` -POST /projects/:id/snippets -``` - -Parameters: - -+ `id` (required) - The ID or code name of a project -+ `title` (required) - The title of a snippet -+ `file_name` (required) - The name of a snippet file -+ `lifetime` (optional) - The expiration date of a snippet -+ `code` (required) - The content of a snippet - -Will return created snippet with status `201 Created` on success, or `404 Not found` on fail. - -## Edit snippet - -Update an existing project snippet. - -``` -PUT /projects/:id/snippets/:snippet_id -``` - -Parameters: - -+ `id` (required) - The ID or code name of a project -+ `snippet_id` (required) - The ID of a project's snippet -+ `title` (optional) - The title of a snippet -+ `file_name` (optional) - The name of a snippet file -+ `lifetime` (optional) - The expiration date of a snippet -+ `code` (optional) - The content of a snippet - -Will return updated snippet with status `200 OK` on success, or `404 Not found` on fail. - -## Delete snippet - -Delete existing project snippet. - -``` -DELETE /projects/:id/snippets/:snippet_id -``` - -Parameters: - -+ `id` (required) - The ID or code name of a project -+ `snippet_id` (required) - The ID of a project's snippet - -Status code `200` will be returned on success. - ## Raw blob content Get the raw file contents for a file. diff --git a/doc/api/snippets.md b/doc/api/snippets.md new file mode 100644 index 00000000..0cd29ce5 --- /dev/null +++ b/doc/api/snippets.md @@ -0,0 +1,100 @@ +## List snippets + +Not implemented. + +## Single snippet + +Get a project snippet. + +``` +GET /projects/:id/snippets/:snippet_id +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `snippet_id` (required) - The ID of a project's snippet + +```json +{ + "id": 1, + "title": "test", + "file_name": "add.rb", + "author": { + "id": 1, + "email": "john@example.com", + "name": "John Smith", + "blocked": false, + "created_at": "2012-05-23T08:00:58Z" + }, + "expires_at": null, + "updated_at": "2012-06-28T10:52:04Z", + "created_at": "2012-06-28T10:52:04Z" +} +``` + +## Snippet content + +Get a raw project snippet. + +``` +GET /projects/:id/snippets/:snippet_id/raw +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `snippet_id` (required) - The ID of a project's snippet + +## New snippet + +Create a new project snippet. + +``` +POST /projects/:id/snippets +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `title` (required) - The title of a snippet ++ `file_name` (required) - The name of a snippet file ++ `lifetime` (optional) - The expiration date of a snippet ++ `code` (required) - The content of a snippet + +Will return created snippet with status `201 Created` on success, or `404 Not found` on fail. + +## Edit snippet + +Update an existing project snippet. + +``` +PUT /projects/:id/snippets/:snippet_id +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `snippet_id` (required) - The ID of a project's snippet ++ `title` (optional) - The title of a snippet ++ `file_name` (optional) - The name of a snippet file ++ `lifetime` (optional) - The expiration date of a snippet ++ `code` (optional) - The content of a snippet + +Will return updated snippet with status `200 OK` on success, or `404 Not found` on fail. + +## Delete snippet + +Delete existing project snippet. + +``` +DELETE /projects/:id/snippets/:snippet_id +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `snippet_id` (required) - The ID of a project's snippet + +Status code `200` will be returned on success. + diff --git a/doc/development.md b/doc/development.md new file mode 100644 index 00000000..55be2bc3 --- /dev/null +++ b/doc/development.md @@ -0,0 +1,45 @@ +## Development tips: + +### Start application in development mode + +#### 1. Via foreman + + bundle exec foreman -p 3000 + +#### 2. Via gitlab cli + + ./gitlab start + +#### 3. Manually + + bundle exec rails s + bundle exec rake environment resque:work QUEUE=* VVERBOSE=1 + + +### Run tests: + +#### 1. Packages + + # ubuntu + sudo apt-get install libqt4-dev libqtwebkit-dev + sudo apt-get install xvfb + + # Mac + brew install qt + brew install xvfb + +#### 2. DB & seeds + + bundle exec rake db:setup RAILS_ENV=test + bundle exec rake db:seed_fu RAILS_ENV=test + +### 3. Run Tests + + # All in one + bundle exec gitlab:test + + # Rspec + bundle exec rake spec + + # Cucumber + bundle exec rake cucumber diff --git a/doc/installation.md b/doc/installation.md index b6f1869b..b3fe92bf 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -119,7 +119,6 @@ Permissions: sudo chmod -R g+rwX /home/git/repositories/ sudo chown -R git:git /home/git/repositories/ - sudo chown gitlab:gitlab /home/git/repositories/**/hooks/post-receive #### CHECK: Logout & login again to apply git group to your user @@ -178,6 +177,11 @@ Permissions: sudo -u gitlab bundle exec rake gitlab:app:setup RAILS_ENV=production +#### Setup gitlab hooks + + sudo cp ./lib/hooks/post-receive /home/git/share/gitolite/hooks/common/post-receive + sudo chown git:git /home/git/share/gitolite/hooks/common/post-receive + Checking status: sudo -u gitlab bundle exec rake gitlab:app:status RAILS_ENV=production @@ -196,6 +200,7 @@ Checking status: Resolving deltas: 100% (174/174), done. Can clone gitolite-admin?............YES UMASK for .gitolite.rc is 0007? ............YES + /home/git/share/gitolite/hooks/common/post-receive exists? ............YES If you got all YES - congrats! You can go to next step. @@ -239,42 +244,15 @@ You can login via web using admin generated with setup: sudo -u gitlab cp config/unicorn.rb.orig config/unicorn.rb sudo -u gitlab bundle exec unicorn_rails -c config/unicorn.rb -E production -D -Edit /etc/nginx/nginx.conf. In the *http* section add the following section of code or replace it completely with https://raw.github.com/dosire/gitlabhq/master/aws/nginx.conf +Add gitlab to nginx sites & change with your host specific settings - upstream gitlab { - server unix:/home/gitlab/gitlab/tmp/sockets/gitlab.socket; - } + sudo cp /home/gitlab/gitlab/lib/support/nginx-gitlab /etc/nginx/sites-available/gitlab + sudo ln -s /etc/nginx/sites-available/gitlab /etc/nginx/sites-enabled/gitlab - server { - listen YOUR_SERVER_IP:80; # e.g., listen 192.168.1.1:80; - server_name YOUR_SERVER_FQDN; # e.g., server_name source.example.com; - root /home/gitlab/gitlab/public; - - # individual nginx logs for this gitlab vhost - access_log /var/log/nginx/gitlab_access.log; - error_log /var/log/nginx/gitlab_error.log; - - location / { - # serve static files from defined root folder;. - # @gitlab is a named location for the upstream fallback, see below - try_files $uri $uri/index.html $uri.html @gitlab; - } - - # if a file, which is not found in the root folder is requested, - # then the proxy pass the request to the upsteam (gitlab unicorn) - location @gitlab { - proxy_redirect off; - - # you need to change this to "https", if you set "ssl" directive to "on" - proxy_set_header X-FORWARDED_PROTO http; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - - proxy_pass http://gitlab; - } - } - -Change **YOUR_SERVER_IP** and **YOUR_SERVER_FQDN** to the IP address and fully-qualified domain name of the host serving GitLab. + # Change **YOUR_SERVER_IP** and **YOUR_SERVER_FQDN** + # to the IP address and fully-qualified domain name + # of the host serving GitLab. + sudo vim /etc/nginx/sites-enabled/gitlab Restart nginx: @@ -282,60 +260,7 @@ Restart nginx: Create init script in /etc/init.d/gitlab: - #! /bin/bash - ### BEGIN INIT INFO - # Provides: gitlab - # Required-Start: $local_fs $remote_fs $network $syslog redis-server - # Required-Stop: $local_fs $remote_fs $network $syslog - # Default-Start: 2 3 4 5 - # Default-Stop: 0 1 6 - # Short-Description: GitLab git repository management - # Description: GitLab git repository management - ### END INIT INFO - - DAEMON_OPTS="-c /home/gitlab/gitlab/config/unicorn.rb -E production -D" - NAME=unicorn - DESC="Gitlab service" - PID=/home/gitlab/gitlab/tmp/pids/unicorn.pid - RESQUE_PID=/home/gitlab/gitlab/tmp/pids/resque_worker.pid - - case "$1" in - start) - CD_TO_APP_DIR="cd /home/gitlab/gitlab" - START_DAEMON_PROCESS="bundle exec unicorn_rails $DAEMON_OPTS" - START_RESQUE_PROCESS="./resque.sh" - - echo -n "Starting $DESC: " - if [ `whoami` = root ]; then - sudo -u gitlab sh -l -c "$CD_TO_APP_DIR > /dev/null 2>&1 && $START_DAEMON_PROCESS && $START_RESQUE_PROCESS" - else - $CD_TO_APP_DIR > /dev/null 2>&1 && $START_DAEMON_PROCESS && $START_RESQUE_PROCESS - fi - echo "$NAME." - ;; - stop) - echo -n "Stopping $DESC: " - kill -QUIT `cat $PID` - kill -QUIT `cat $RESQUE_PID` - echo "$NAME." - ;; - restart) - echo -n "Restarting $DESC: " - kill -USR2 `cat $PID` - echo "$NAME." - ;; - reload) - echo -n "Reloading $DESC configuration: " - kill -HUP `cat $PID` - echo "$NAME." - ;; - *) - echo "Usage: $NAME {start|stop|restart|reload}" >&2 - exit 1 - ;; - esac - - exit 0 + cp /home/gitlab/gitlab/lib/support/init-gitlab /etc/init.d/gitlab Adding permission: diff --git a/features/profile/ssh_keys.feature b/features/profile/ssh_keys.feature index c3a92f30..c81503ed 100644 --- a/features/profile/ssh_keys.feature +++ b/features/profile/ssh_keys.feature @@ -3,8 +3,8 @@ Feature: SSH Keys Given I signin as a user And I have ssh keys: | title | - | Work | - | Home | + | ssh-rsa Work | + | ssh-rsa Home | And I visit profile keys page Scenario: I should see SSH keys diff --git a/features/projects/issues/labels.feature b/features/projects/issues/labels.feature new file mode 100644 index 00000000..5a20bfd6 --- /dev/null +++ b/features/projects/issues/labels.feature @@ -0,0 +1,13 @@ +Feature: Labels + Background: + Given I signin as a user + And I own project "Shop" + And project "Shop" have issues tags: + | name | + | bug | + | feature | + Given I visit project "Shop" labels page + + Scenario: I should see active milestones + Then I should see label "bug" + And I should see label "feature" diff --git a/features/projects/network.feature b/features/projects/network.feature index 9655184c..61c05eb3 100644 --- a/features/projects/network.feature +++ b/features/projects/network.feature @@ -4,9 +4,7 @@ Feature: Project Network Graph Background: Given I signin as a user And I own project "Shop" - And I visit project "Shop" network page + And I visit project "Shop" network page Scenario: I should see project network Then page should have network graph - - diff --git a/features/step_definitions/dashboard_steps.rb b/features/step_definitions/dashboard_steps.rb index 90c3a69c..a4edd224 100644 --- a/features/step_definitions/dashboard_steps.rb +++ b/features/step_definitions/dashboard_steps.rb @@ -91,36 +91,24 @@ Then /^I should see my merge requests$/ do end Given /^I have assigned issues$/ do - project1 = Factory :project, - :path => "project1", - :code => "gitlabhq_1" - - project2 = Factory :project, - :path => "project2", - :code => "gitlabhq_2" - - project1.add_access(@user, :read, :write) - project2.add_access(@user, :read, :write) + project = Factory :project + project.add_access(@user, :read, :write) issue1 = Factory :issue, :author => @user, :assignee => @user, - :project => project1 + :project => project issue2 = Factory :issue, :author => @user, :assignee => @user, - :project => project2 + :project => project end Given /^I have authored merge requests$/ do - project1 = Factory :project, - :path => "project1", - :code => "gitlabhq_1" + project1 = Factory :project - project2 = Factory :project, - :path => "project2", - :code => "gitlabhq_2" + project2 = Factory :project project1.add_access(@user, :read, :write) project2.add_access(@user, :read, :write) diff --git a/features/step_definitions/profile/profile_keys_steps.rb b/features/step_definitions/profile/profile_keys_steps.rb index 5ab7e048..25926c53 100644 --- a/features/step_definitions/profile/profile_keys_steps.rb +++ b/features/step_definitions/profile/profile_keys_steps.rb @@ -16,7 +16,7 @@ end Given /^I submit new ssh key "(.*?)"$/ do |arg1| fill_in "key_title", :with => arg1 - fill_in "key_key", :with => "publickey234=" + fill_in "key_key", :with => "ssh-rsa publickey234=" click_button "Save" end diff --git a/features/step_definitions/project/project_issues_steps.rb b/features/step_definitions/project/project_issues_steps.rb index 00a1721f..27de03d5 100644 --- a/features/step_definitions/project/project_issues_steps.rb +++ b/features/step_definitions/project/project_issues_steps.rb @@ -33,6 +33,25 @@ Given /^I visit issue page "(.*?)"$/ do |arg1| end Given /^I submit new issue "(.*?)"$/ do |arg1| - fill_in "issue_title", :with => arg1 + fill_in "issue_title", with: arg1 click_button "Submit new issue" end + +Given /^project "(.*?)" have issues tags:$/ do |arg1, table| + project = Project.find_by_name(arg1) + table.hashes.each do |hash| + Factory :issue, + project: project, + label_list: [hash[:name]] + end +end + +Given /^I visit project "(.*?)" labels page$/ do |arg1| + visit project_labels_path(Project.find_by_name(arg1)) +end + +Then /^I should see label "(.*?)"$/ do |arg1| + within ".labels-table" do + page.should have_content arg1 + end +end diff --git a/features/step_definitions/project/projects_steps.rb b/features/step_definitions/project/projects_steps.rb index c9af346e..d981e1f3 100644 --- a/features/step_definitions/project/projects_steps.rb +++ b/features/step_definitions/project/projects_steps.rb @@ -1,4 +1,4 @@ -include LoginMacros +include LoginHelpers Given /^I signin as a user$/ do login_as :user @@ -57,6 +57,11 @@ end Given /^I visit project "(.*?)" network page$/ do |arg1| project = Project.find_by_name(arg1) + + # Stub out find_all to speed this up (10 commits vs. 650) + commits = Grit::Commit.find_all(project.repo, nil, {max_count: 10}) + Grit::Commit.stub(:find_all).and_return(commits) + visit graph_project_path(project) end @@ -67,8 +72,8 @@ end Given /^page should have network graph$/ do page.should have_content "Project Network Graph" within ".graph" do - page.should have_content "stable" - page.should have_content "notes_refacto..." + page.should have_content "master" + page.should have_content "scss_refactor..." end end diff --git a/features/support/env.rb b/features/support/env.rb index 496f23f9..53578152 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -1,13 +1,16 @@ -require 'simplecov' -SimpleCov.start 'rails' +unless ENV['CI'] + require 'simplecov' + SimpleCov.start 'rails' +end require 'cucumber/rails' require 'webmock/cucumber' + WebMock.allow_net_connect! -require Rails.root.join 'spec/monkeypatch' -require Rails.root.join 'spec/factories' -require Rails.root.join 'spec/support/login' +require Rails.root.join 'spec/support/gitolite_stub' +require Rails.root.join 'spec/support/stubbed_repository' +require Rails.root.join 'spec/support/login_helpers' require Rails.root.join 'spec/support/valid_commit' Capybara.default_selector = :css @@ -44,3 +47,13 @@ require 'headless' headless = Headless.new headless.start + +require 'cucumber/rspec/doubles' + +include GitoliteStub + +Before do + stub_gitolite! +end + +World(FactoryGirl::Syntax::Methods) diff --git a/gitlab b/gitlab new file mode 100755 index 00000000..acafb3f1 --- /dev/null +++ b/gitlab @@ -0,0 +1,75 @@ +#!/usr/bin/env ruby + +class GitlabCli + def initialize + @path = File.dirname(__FILE__) + @command = ARGV.shift + @mode = ARGV.shift + end + + def execute + case @command + when 'start' then start + when 'stop' then stop + else + puts "-- Usage gitlab start production or gitlab stop development" + end + end + + private + + def start + case @mode + when 'production'; + system(unicorn_start_cmd) + system(resque_start_cmd) + else + system(rails_start_cmd) + system(resque_dev_start_cmd) + end + end + + def stop + case @mode + when 'production'; + system(unicorn_stop_cmd) + else + system(rails_stop_cmd) + end + system(resque_stop_cmd) + end + + def rails_start_cmd + "bundle exec rails s -d" + end + + def rails_stop_cmd + pid = File.join(@path, "tmp/pids/server.pid") + "kill -QUIT `cat #{pid}`" + end + + def unicorn_start_cmd + unicorn_conf = File.join(@path, "config/unicorn.rb") + "bundle exec unicorn_rails -c #{unicorn_conf} -E production -D" + end + + def unicorn_stop_cmd + pid = File.join(@path, "/tmp/pids/unicorn.pid") + "kill -QUIT `cat #{pid}`" + end + + def resque_dev_start_cmd + "./resque_dev.sh > /dev/null 2>&1" + end + + def resque_start_cmd + "./resque.sh > /dev/null 2>&1" + end + + def resque_stop_cmd + pid = File.join(@path, "tmp/pids/resque_worker.pid") + "kill -QUIT `cat #{pid}`" + end +end + +GitlabCli.new.execute diff --git a/lib/api.rb b/lib/api.rb index 3ff3b383..be04701c 100644 --- a/lib/api.rb +++ b/lib/api.rb @@ -16,5 +16,6 @@ module Gitlab mount Users mount Projects mount Issues + mount Milestones end end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 2abc20ad..836c2818 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -95,7 +95,7 @@ module Gitlab end end - # Delete a project issue + # Delete a project issue (deprecated) # # Parameters: # id (required) - The ID or code name of a project @@ -103,8 +103,7 @@ module Gitlab # Example Request: # DELETE /projects/:id/issues/:issue_id delete ":id/issues/:issue_id" do - @issue = user_project.issues.find(params[:issue_id]) - @issue.destroy + error!({'message' => 'method not allowed'}, 405) end end end diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb new file mode 100644 index 00000000..f537b8e5 --- /dev/null +++ b/lib/api/milestones.rb @@ -0,0 +1,80 @@ +module Gitlab + # Milestones API + class Milestones < Grape::API + before { authenticate! } + + resource :projects do + # Get a list of project milestones + # + # Parameters: + # id (required) - The ID or code name of a project + # Example Request: + # GET /projects/:id/milestones + get ":id/milestones" do + present user_project.milestones, with: Entities::Milestone + end + + # Get a single project milestone + # + # Parameters: + # id (required) - The ID or code name of a project + # milestone_id (required) - The ID of a project milestone + # Example Request: + # GET /projects/:id/milestones/:milestone_id + get ":id/milestones/:milestone_id" do + @milestone = user_project.milestones.find(params[:milestone_id]) + present @milestone, with: Entities::Milestone + end + + # Create a new project milestone + # + # Parameters: + # id (required) - The ID or code name of the project + # title (required) - The title of the milestone + # description (optional) - The description of the milestone + # due_date (optional) - The due date of the milestone + # Example Request: + # POST /projects/:id/milestones + post ":id/milestones" do + @milestone = user_project.milestones.new( + title: params[:title], + description: params[:description], + due_date: params[:due_date] + ) + + if @milestone.save + present @milestone, with: Entities::Milestone + else + error!({'message' => '404 Not found'}, 404) + end + end + + # Update an existing project milestone + # + # Parameters: + # id (required) - The ID or code name of a project + # milestone_id (required) - The ID of a project milestone + # title (optional) - The title of a milestone + # description (optional) - The description of a milestone + # due_date (optional) - The due date of a milestone + # closed (optional) - The status of the milestone + # Example Request: + # PUT /projects/:id/milestones/:milestone_id + put ":id/milestones/:milestone_id" do + @milestone = user_project.milestones.find(params[:milestone_id]) + parameters = { + title: (params[:title] || @milestone.title), + description: (params[:description] || @milestone.description), + due_date: (params[:due_date] || @milestone.due_date), + closed: (params[:closed] || @milestone.closed) + } + + if @milestone.update_attributes(parameters) + present @milestone, with: Entities::Milestone + else + error!({'message' => '404 Not found'}, 404) + end + end + end + end +end diff --git a/lib/gitlab/gitolite.rb b/lib/gitlab/backend/gitolite.rb similarity index 72% rename from lib/gitlab/gitolite.rb rename to lib/gitlab/backend/gitolite.rb index e82f9e62..b69f4663 100644 --- a/lib/gitlab/gitolite.rb +++ b/lib/gitlab/backend/gitolite.rb @@ -2,54 +2,57 @@ require 'gitolite' require 'timeout' require 'fileutils' +# TODO: refactor & cleanup module Gitlab class Gitolite class AccessDenied < StandardError; end + class InvalidKey < StandardError; end - def self.update_project(path, project) - self.new.configure { |git| git.update_project(path, project) } + def set_key key_id, key_content, projects + configure do |c| + c.update_keys(key_id, key_content) + c.update_projects(projects) + end end - def self.destroy_project(project) - self.new.configure { |git| git.destroy_project(project) } + def remove_key key_id, projects + configure do |c| + c.delete_key(key_id) + c.update_projects(projects) + end end - def pull + def update_repository project + configure do |c| + c.update_project(project.path, project) + end + end + + alias_method :create_repository, :update_repository + + def remove_repository project + configure do |c| + c.destroy_project(project) + end + end + + def url_to_repo path + Gitlab.config.ssh_path + "#{path}.git" + end + + def initialize # create tmp dir @local_dir = File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}") - Dir.mkdir @local_dir - - `git clone #{GitHost.admin_uri} #{@local_dir}/gitolite` end - def push - Dir.chdir(File.join(@local_dir, "gitolite")) - `git add -A` - `git commit -am "Gitlab"` - `git push` - Dir.chdir(Rails.root) - - FileUtils.rm_rf(@local_dir) - end - - def configure - Timeout::timeout(30) do - File.open(File.join(Rails.root, 'tmp', "gitlabhq-gitolite.lock"), "w+") do |f| - begin - f.flock(File::LOCK_EX) - pull - yield(self) - push - ensure - f.flock(File::LOCK_UN) - end - end + def enable_automerge + configure do |git| + git.admin_all_repo end - rescue Exception => ex - Gitlab::Logger.error(ex.message) - raise Gitolite::AccessDenied.new("gitolite timeout") end + protected + def destroy_project(project) FileUtils.rm_rf(project.path_to_repo) @@ -106,13 +109,13 @@ module Gitlab name_writers = project.repository_writers name_masters = project.repository_masters - pr_br = project.protected_branches.map(&:name).join(" ") + pr_br = project.protected_branches.map(&:name).join("$ ") repo.clean_permissions # Deny access to protected branches for writers unless name_writers.blank? || pr_br.blank? - repo.add_permission("-", pr_br, name_writers) + repo.add_permission("-", pr_br.strip + "$ ", name_writers) end # Add read permissions @@ -153,5 +156,47 @@ module Gitlab conf.add_repo(repo, true) ga_repo.save end + + private + + def pull + # create tmp dir + @local_dir = File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}") + Dir.mkdir @local_dir + + `git clone #{Gitlab.config.gitolite_admin_uri} #{@local_dir}/gitolite` + end + + def push + Dir.chdir(File.join(@local_dir, "gitolite")) + `git add -A` + `git commit -am "Gitlab"` + `git push` + Dir.chdir(Rails.root) + + FileUtils.rm_rf(@local_dir) + end + + def configure + Timeout::timeout(30) do + File.open(File.join(Rails.root, 'tmp', "gitlabhq-gitolite.lock"), "w+") do |f| + begin + f.flock(File::LOCK_EX) + pull + yield(self) + push + ensure + f.flock(File::LOCK_UN) + end + end + end + rescue Exception => ex + if ex.message =~ /is not a valid SSH key string/ + raise Gitolite::InvalidKey.new("ssh key is not valid") + else + Gitlab::Logger.error(ex.message) + raise Gitolite::AccessDenied.new("gitolite timeout") + end + end end end diff --git a/config/initializers/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb similarity index 87% rename from config/initializers/grack_auth.rb rename to lib/gitlab/backend/grack_auth.rb index 5995b873..4f77c327 100644 --- a/config/initializers/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -42,13 +42,13 @@ module Grack def current_ref if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/ - input = Zlib::GzipReader.new(@request.body).string + input = Zlib::GzipReader.new(@request.body).read else - input = @request.body.string + input = @request.body.read end - - oldrev, newrev, ref = input.split(' ') - /refs\/heads\/([\w-]+)/.match(ref).to_a.last + # Need to reset seek point + @request.body.rewind + /refs\/heads\/([\w-]+)/.match(input).to_a.first end end# Auth end# Grack diff --git a/lib/gitlab/git_host.rb b/lib/gitlab/git_host.rb deleted file mode 100644 index 76b2c7b1..00000000 --- a/lib/gitlab/git_host.rb +++ /dev/null @@ -1,17 +0,0 @@ -require File.join(Rails.root, "lib", "gitlab", "gitolite") - -module Gitlab - class GitHost - def self.system - Gitlab::Gitolite - end - - def self.admin_uri - Gitlab.config.git_host.admin_uri - end - - def self.url_to_repo(path) - Gitlab.config.ssh_path + "#{path}.git" - end - end -end diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index d3daed91..75fa835d 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -1,5 +1,14 @@ module Gitlab - # Custom parsing for Gitlab-flavored Markdown + # Custom parser for Gitlab-flavored Markdown + # + # It replaces references in the text with links to the appropriate items in Gitlab. + # + # Supported reference formats are: + # * @foo for team members + # * #123 for issues + # * !123 for merge requests + # * $123 for snippets + # * 123456 for commits # # Examples # @@ -67,25 +76,25 @@ module Gitlab def reference_user(identifier) if user = @project.users.where(name: identifier).first member = @project.users_projects.where(user_id: user).first - link_to("@#{user.name}", project_team_member_path(@project, member), html_options.merge(class: "gfm gfm-team_member #{html_options[:class]}")) if member + link_to("@#{identifier}", project_team_member_path(@project, member), html_options.merge(class: "gfm gfm-team_member #{html_options[:class]}")) if member end end def reference_issue(identifier) if issue = @project.issues.where(id: identifier).first - link_to("##{issue.id}", project_issue_path(@project, issue), html_options.merge(title: "Issue: #{issue.title}", class: "gfm gfm-issue #{html_options[:class]}")) + link_to("##{identifier}", project_issue_path(@project, issue), html_options.merge(title: "Issue: #{issue.title}", class: "gfm gfm-issue #{html_options[:class]}")) end end def reference_merge_request(identifier) if merge_request = @project.merge_requests.where(id: identifier).first - link_to("!#{merge_request.id}", project_merge_request_path(@project, merge_request), html_options.merge(title: "Merge Request: #{merge_request.title}", class: "gfm gfm-merge_request #{html_options[:class]}")) + link_to("!#{identifier}", project_merge_request_path(@project, merge_request), html_options.merge(title: "Merge Request: #{merge_request.title}", class: "gfm gfm-merge_request #{html_options[:class]}")) end end def reference_snippet(identifier) if snippet = @project.snippets.where(id: identifier).first - link_to("$#{snippet.id}", project_snippet_path(@project, snippet), html_options.merge(title: "Snippet: #{snippet.title}", class: "gfm gfm-snippet #{html_options[:class]}")) + link_to("$#{identifier}", project_snippet_path(@project, snippet), html_options.merge(title: "Snippet: #{snippet.title}", class: "gfm gfm-snippet #{html_options[:class]}")) end end diff --git a/lib/post-receive-hook b/lib/hooks/post-receive similarity index 100% rename from lib/post-receive-hook rename to lib/hooks/post-receive diff --git a/lib/support/init-gitlab b/lib/support/init-gitlab new file mode 100644 index 00000000..f146e80f --- /dev/null +++ b/lib/support/init-gitlab @@ -0,0 +1,54 @@ +#! /bin/bash +### BEGIN INIT INFO +# Provides: gitlab +# Required-Start: $local_fs $remote_fs $network $syslog redis-server +# Required-Stop: $local_fs $remote_fs $network $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: GitLab git repository management +# Description: GitLab git repository management +### END INIT INFO + +DAEMON_OPTS="-c /home/gitlab/gitlab/config/unicorn.rb -E production -D" +NAME=unicorn +DESC="Gitlab service" +PID=/home/gitlab/gitlab/tmp/pids/unicorn.pid +RESQUE_PID=/home/gitlab/gitlab/tmp/pids/resque_worker.pid + +case "$1" in + start) + CD_TO_APP_DIR="cd /home/gitlab/gitlab" + START_DAEMON_PROCESS="bundle exec unicorn_rails $DAEMON_OPTS" + START_RESQUE_PROCESS="./resque.sh" + + echo -n "Starting $DESC: " + if [ `whoami` = root ]; then + sudo -u gitlab sh -l -c "$CD_TO_APP_DIR > /dev/null 2>&1 && $START_DAEMON_PROCESS && $START_RESQUE_PROCESS" + else + $CD_TO_APP_DIR > /dev/null 2>&1 && $START_DAEMON_PROCESS && $START_RESQUE_PROCESS + fi + echo "$NAME." + ;; + stop) + echo -n "Stopping $DESC: " + kill -QUIT `cat $PID` + kill -QUIT `cat $RESQUE_PID` + echo "$NAME." + ;; + restart) + echo -n "Restarting $DESC: " + kill -USR2 `cat $PID` + echo "$NAME." + ;; + reload) + echo -n "Reloading $DESC configuration: " + kill -HUP `cat $PID` + echo "$NAME." + ;; + *) + echo "Usage: $NAME {start|stop|restart|reload}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/lib/support/nginx-gitlab b/lib/support/nginx-gitlab new file mode 100644 index 00000000..fa15d201 --- /dev/null +++ b/lib/support/nginx-gitlab @@ -0,0 +1,33 @@ +upstream gitlab { + server unix:/home/gitlab/gitlab/tmp/sockets/gitlab.socket; +} + +server { + listen YOUR_SERVER_IP:80; # e.g., listen 192.168.1.1:80; + server_name YOUR_SERVER_FQDN; # e.g., server_name source.example.com; + root /home/gitlab/gitlab/public; + + # individual nginx logs for this gitlab vhost + access_log /var/log/nginx/gitlab_access.log; + error_log /var/log/nginx/gitlab_error.log; + + location / { + # serve static files from defined root folder;. + # @gitlab is a named location for the upstream fallback, see below + try_files $uri $uri/index.html $uri.html @gitlab; + } + + # if a file, which is not found in the root folder is requested, + # then the proxy pass the request to the upsteam (gitlab unicorn) + location @gitlab { + proxy_redirect off; + + # you need to change this to "https", if you set "ssl" directive to "on" + proxy_set_header X-FORWARDED_PROTO http; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + + proxy_pass http://gitlab; + } +} + diff --git a/lib/tasks/bulk_add_permission.rake b/lib/tasks/bulk_add_permission.rake new file mode 100644 index 00000000..55797825 --- /dev/null +++ b/lib/tasks/bulk_add_permission.rake @@ -0,0 +1,26 @@ +desc "Add all users to all projects, system administratos are added as masters" +task :add_users_to_project_teams => :environment do |t, args| + users = User.find_all_by_admin(false, :select => 'id').map(&:id) + admins = User.find_all_by_admin(true, :select => 'id').map(&:id) + + users.each do |user| + puts "#{user}" + end + + Project.all.each do |project| + puts "Importing #{users.length} users into #{project.path}" + UsersProject.bulk_import(project, users, UsersProject::DEVELOPER) + puts "Importing #{admins.length} admins into #{project.path}" + UsersProject.bulk_import(project, admins, UsersProject::MASTER) + end +end + +desc "Add user to as a developer to all projects" +task :add_user_to_project_teams, [:email] => :environment do |t, args| + user_email = args.email + user = User.find_by_email(user_email) + + project_ids = Project.all.map(&:id) + + UsersProject.user_bulk_import(user,project_ids,UsersProject::DEVELOPER) +end diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index d9053c23..04d240f6 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -144,8 +144,7 @@ namespace :gitlab do if Kernel.system("cd #{File.dirname(project.second)} > /dev/null 2>&1 && git clone --bare #{backup_path_repo}/#{project.first}.bundle #{project.first}.git > /dev/null 2>&1") permission_commands = [ "sudo chmod -R g+rwX #{Gitlab.config.git_base_path}", - "sudo chown -R #{Gitlab.config.ssh_user}:#{Gitlab.config.ssh_user} #{Gitlab.config.git_base_path}", - "sudo chown gitlab:gitlab /home/git/repositories/**/hooks/post-receive" + "sudo chown -R #{Gitlab.config.ssh_user}:#{Gitlab.config.ssh_user} #{Gitlab.config.git_base_path}" ] permission_commands.each { |command| Kernel.system(command) } puts "[DONE]".green diff --git a/lib/tasks/gitlab/enable_automerge.rake b/lib/tasks/gitlab/enable_automerge.rake index 07f80586..0a1a0fa7 100644 --- a/lib/tasks/gitlab/enable_automerge.rake +++ b/lib/tasks/gitlab/enable_automerge.rake @@ -2,9 +2,7 @@ namespace :gitlab do namespace :app do desc "GITLAB | Enable auto merge" task :enable_automerge => :environment do - Gitlab::GitHost.system.new.configure do |git| - git.admin_all_repo - end + Gitlab::Gitolite.new.enable_automerge Project.find_each do |project| if project.repo_exists? && !project.satellite.exists? diff --git a/lib/tasks/gitlab/gitolite_rebuild.rake b/lib/tasks/gitlab/gitolite_rebuild.rake index 5ab17606..534aa315 100644 --- a/lib/tasks/gitlab/gitolite_rebuild.rake +++ b/lib/tasks/gitlab/gitolite_rebuild.rake @@ -16,7 +16,7 @@ namespace :gitlab do task :update_keys => :environment do puts "Starting Key" Key.find_each(:batch_size => 100) do |key| - key.update_repository + Gitlab::Gitolite.new.set_key(key.identifier, key.key, key.projects) print '.' end puts "Done with keys" diff --git a/lib/tasks/gitlab/setup.rake b/lib/tasks/gitlab/setup.rake index d60e73e9..49c86461 100644 --- a/lib/tasks/gitlab/setup.rake +++ b/lib/tasks/gitlab/setup.rake @@ -1,7 +1,11 @@ namespace :gitlab do namespace :app do desc "GITLAB | Setup production application" - task :setup => ['db:setup', 'db:seed_fu', 'gitlab:app:enable_automerge'] + task :setup => [ + 'db:setup', + 'db:seed_fu', + 'gitlab:app:enable_automerge' + ] end end diff --git a/lib/tasks/gitlab/status.rake b/lib/tasks/gitlab/status.rake index bc4e86ea..e5b5e122 100644 --- a/lib/tasks/gitlab/status.rake +++ b/lib/tasks/gitlab/status.rake @@ -56,6 +56,20 @@ namespace :gitlab do return end + gitolite_hooks_path = File.join(Gitlab.config.git_hooks_path, "common") + gitlab_hook_files = ['post-receive'] + gitlab_hook_files.each do |file_name| + dest = File.join(gitolite_hooks_path, file_name) + print "#{dest} exists? ............" + if File.exists?(dest) + puts "YES".green + else + puts "NO".red + return + end + end + + if Project.count > 0 puts "Validating projects repositories:".yellow Project.find_each(:batch_size => 100) do |project| @@ -67,13 +81,7 @@ namespace :gitlab do next end - - unless File.owned?(hook_file) - puts "post-receive file is not owner by gitlab".red - next - end - - puts "post-reveice file ok".green + puts "post-receive file ok".green end end diff --git a/lib/tasks/gitlab/update_hooks.rake b/lib/tasks/gitlab/update_hooks.rake deleted file mode 100644 index 44e1617e..00000000 --- a/lib/tasks/gitlab/update_hooks.rake +++ /dev/null @@ -1,19 +0,0 @@ -namespace :gitlab do - namespace :gitolite do - desc "GITLAB | Rewrite hooks for repos" - task :update_hooks => :environment do - puts "Starting Projects" - Project.find_each(:batch_size => 100) do |project| - begin - if project.commit - project.write_hooks - print ".".green - end - rescue Exception => e - print e.message.red - end - end - puts "\nDone with projects" - end - end -end diff --git a/lib/tasks/gitlab/write_hook.rake b/lib/tasks/gitlab/write_hook.rake new file mode 100644 index 00000000..9ec9c838 --- /dev/null +++ b/lib/tasks/gitlab/write_hook.rake @@ -0,0 +1,23 @@ +namespace :gitlab do + namespace :gitolite do + desc "GITLAB | Write GITLAB hook for gitolite" + task :write_hooks => :environment do + gitolite_hooks_path = File.join(Gitlab.config.git_hooks_path, "common") + gitlab_hooks_path = Rails.root.join("lib", "hooks") + + gitlab_hook_files = ['post-receive'] + + gitlab_hook_files.each do |file_name| + source = File.join(gitlab_hooks_path, file_name) + dest = File.join(gitolite_hooks_path, file_name) + + puts "sudo -u root cp #{source} #{dest}".yellow + `sudo -u root cp #{source} #{dest}` + + puts "sudo -u root chown git:git #{dest}".yellow + `sudo -u root chown git:git #{dest}` + end + end + end +end + diff --git a/resque_dev.sh b/resque_dev.sh index b09cfd9e..0f1d6edb 100755 --- a/resque_dev.sh +++ b/resque_dev.sh @@ -1 +1,2 @@ -bundle exec rake environment resque:work QUEUE=post_receive,mailer,system_hook VVERBOSE=1 +mkdir -p tmp/pids +bundle exec rake environment resque:work QUEUE=post_receive,mailer,system_hook VVERBOSE=1 PIDFILE=tmp/pids/resque_worker.pid RAILS_ENV=development BACKGROUND=yes diff --git a/spec/factories.rb b/spec/factories.rb index ab2ca468..2e4acf39 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -1,92 +1,130 @@ -require File.join(Rails.root, 'spec', 'factory') - -Factory.add(:project, Project) do |obj| - obj.name = Faker::Internet.user_name - obj.path = 'gitlabhq' - obj.owner = Factory(:user) - obj.code = 'LGT' +# Backwards compatibility with the old method +def Factory(type, *args) + FactoryGirl.create(type, *args) end -Factory.add(:project_without_owner, Project) do |obj| - obj.name = Faker::Internet.user_name - obj.path = 'gitlabhq' - obj.code = 'LGT' +module Factory + def self.create(type, *args) + FactoryGirl.create(type, *args) + end + + def self.new(type, *args) + FactoryGirl.build(type, *args) + end end -Factory.add(:public_project, Project) do |obj| - obj.name = Faker::Internet.user_name - obj.path = 'gitlabhq' - obj.private_flag = false - obj.owner = Factory(:user) - obj.code = 'LGT' -end +FactoryGirl.define do + sequence :sentence, aliases: [:title, :content] do + Faker::Lorem.sentence + end -Factory.add(:user, User) do |obj| - obj.email = Faker::Internet.email - obj.password = "123456" - obj.name = Faker::Name.name - obj.password_confirmation = "123456" -end + sequence :name, aliases: [:file_name] do + Faker::Name.name + end -Factory.add(:admin, User) do |obj| - obj.email = Faker::Internet.email - obj.password = "123456" - obj.name = Faker::Name.name - obj.password_confirmation = "123456" - obj.admin = true -end + sequence(:url) { Faker::Internet.uri('http') } -Factory.add(:issue, Issue) do |obj| - obj.title = Faker::Lorem.sentence - obj.author = Factory :user - obj.assignee = Factory :user -end + factory :user, aliases: [:author, :assignee, :owner] do + email { Faker::Internet.email } + name + password "123456" + password_confirmation "123456" -Factory.add(:merge_request, MergeRequest) do |obj| - obj.title = Faker::Lorem.sentence - obj.author = Factory :user - obj.assignee = Factory :user - obj.source_branch = "master" - obj.target_branch = "stable" - obj.closed = false -end + trait :admin do + admin true + end -Factory.add(:snippet, Snippet) do |obj| - obj.title = Faker::Lorem.sentence - obj.file_name = Faker::Lorem.sentence - obj.content = Faker::Lorem.sentences -end + factory :admin, traits: [:admin] + end -Factory.add(:note, Note) do |obj| - obj.note = Faker::Lorem.sentence -end + factory :project do + sequence(:name) { |n| "project#{n}" } + path { name } + code { name } + owner + end -Factory.add(:key, Key) do |obj| - obj.title = "Example key" - obj.key = File.read(File.join(Rails.root, "db", "pkey.example")) -end + factory :users_project do + user + project + end -Factory.add(:project_hook, ProjectHook) do |obj| - obj.url = Faker::Internet.uri("http") -end + factory :issue do + title + author + project -Factory.add(:system_hook, SystemHook) do |obj| - obj.url = Faker::Internet.uri("http") -end + trait :closed do + closed true + end -Factory.add(:wiki, Wiki) do |obj| - obj.title = Faker::Lorem.sentence - obj.content = Faker::Lorem.sentence - obj.user = Factory(:user) - obj.project = Factory(:project) -end + factory :closed_issue, traits: [:closed] + end -Factory.add(:event, Event) do |obj| - obj.title = Faker::Lorem.sentence - obj.project = Factory(:project) -end + factory :merge_request do + title + author + project + source_branch "master" + target_branch "stable" + end -Factory.add(:milestone, Milestone) do |obj| - obj.title = Faker::Lorem.sentence - obj.due_date = Date.today + 1.month + factory :note do + project + note "Note" + end + + factory :event do + end + + factory :key do + title + key do + """ + ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4 + 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4 + soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0= + """ + end + + factory :deploy_key do + project + end + + factory :personal_key do + user + end + end + + factory :milestone do + title + project + end + + factory :system_hook do + url + end + + factory :project_hook do + url + end + + factory :wiki do + title + content + user + end + + factory :snippet do + project + author + title + content + file_name + end + + factory :protected_branch do + name + project + end end diff --git a/spec/factories_spec.rb b/spec/factories_spec.rb new file mode 100644 index 00000000..5760aad4 --- /dev/null +++ b/spec/factories_spec.rb @@ -0,0 +1,91 @@ +require 'spec_helper' + +describe "Factories" do + describe 'User' do + it "builds a valid instance" do + build(:user).should be_valid + end + + it "builds a valid admin instance" do + build(:admin).should be_valid + end + end + + describe 'Project' do + it "builds a valid instance" do + build(:project).should be_valid + end + end + + describe 'Issue' do + it "builds a valid instance" do + build(:issue).should be_valid + end + + it "builds a valid closed instance" do + build(:closed_issue).should be_valid + end + end + + describe 'MergeRequest' do + it "builds a valid instance" do + build(:merge_request).should be_valid + end + end + + describe 'Note' do + it "builds a valid instance" do + build(:note).should be_valid + end + end + + describe 'Event' do + it "builds a valid instance" do + build(:event).should be_valid + end + end + + describe 'Key' do + it "builds a valid instance" do + build(:key).should be_valid + end + + it "builds a valid deploy key instance" do + build(:deploy_key).should be_valid + end + + it "builds a valid personal key instance" do + build(:personal_key).should be_valid + end + end + + describe 'Milestone' do + it "builds a valid instance" do + build(:milestone).should be_valid + end + end + + describe 'SystemHook' do + it "builds a valid instance" do + build(:system_hook).should be_valid + end + end + + describe 'ProjectHook' do + it "builds a valid instance" do + build(:project_hook).should be_valid + end + end + + describe 'Wiki' do + it "builds a valid instance" do + build(:wiki).should be_valid + end + end + + describe 'Snippet' do + it "builds a valid instance" do + build(:snippet).should be_valid + end + end +end diff --git a/spec/factory.rb b/spec/factory.rb deleted file mode 100644 index 1758b4d6..00000000 --- a/spec/factory.rb +++ /dev/null @@ -1,29 +0,0 @@ -class Factory - @factories = {} - - class << self - def add(name, klass, &block) - @factories[name] = [klass, block] - end - - def create(name, opts = {}) - new(name, opts).tap(&:save!) - end - - def new(name, opts = {}) - factory= @factories[name] - factory[0].new.tap do |obj| - factory[1].call(obj) - end.tap do |obj| - opts.each do |k, opt| - obj.send("#{k}=", opt) - end - end - end - end -end - -def Factory(name, opts={}) - Factory.create name, opts -end - diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb new file mode 100644 index 00000000..9a2df314 --- /dev/null +++ b/spec/helpers/application_helper_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe ApplicationHelper do + describe "gravatar_icon" do + let(:user_email) { 'user@email.com' } + + it "should return a generic avatar path when Gravatar is disabled" do + Gitlab.config.stub(:disable_gravatar?).and_return(true) + gravatar_icon(user_email).should == 'no_avatar.png' + end + + it "should return a generic avatar path when email is blank" do + gravatar_icon('').should == 'no_avatar.png' + end + + it "should use SSL when appropriate" do + stub!(:request).and_return(double(:ssl? => true)) + gravatar_icon(user_email).should match('https://secure.gravatar.com') + end + + it "should accept a custom size" do + stub!(:request).and_return(double(:ssl? => false)) + gravatar_icon(user_email, 64).should match(/\?s=64/) + end + end +end diff --git a/spec/helpers/gitlab_flavored_markdown_spec.rb b/spec/helpers/gitlab_flavored_markdown_spec.rb index e147cb39..28bd46ec 100644 --- a/spec/helpers/gitlab_flavored_markdown_spec.rb +++ b/spec/helpers/gitlab_flavored_markdown_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" describe GitlabMarkdownHelper do before do - @project = Project.find_by_path("gitlabhq") || Factory(:project) + @project = Factory(:project) @commit = @project.repo.commits.first.parents.first @commit = CommitDecorator.decorate(Commit.new(@commit)) @other_project = Factory :project, path: "OtherPath", code: "OtherCode" @@ -157,7 +157,7 @@ describe GitlabMarkdownHelper do gfm("Let @#{user.name} fix the *mess* in #{@commit.id}").should == "Let #{link_to "@#{user.name}", project_team_member_path(@project, member), class: "gfm gfm-team_member "} fix the *mess* in #{link_to @commit.id, project_commit_path(@project, id: @commit.id), title: "Commit: #{@commit.author_name} - #{@commit.title}", class: "gfm gfm-commit "}" end - it "should not trip over other stuff", focus: true do + it "should not trip over other stuff" do gfm("_Please_ *stop* 'helping' and all the other b*$#%' you do.").should == "_Please_ *stop* 'helping' and all the other b*$#%' you do." end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 27af1e38..cf50b429 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -24,7 +24,7 @@ describe Notify do end it 'has the correct subject' do - should have_subject /Account was created for you/ + should have_subject /^gitlab \| Account was created for you$/ end it 'contains the new user\'s login name' do @@ -60,7 +60,7 @@ describe Notify do it_behaves_like 'an assignee email' it 'has the correct subject' do - should have_subject /new issue ##{issue.id}/ + should have_subject /new issue ##{issue.id} \| #{issue.title} \| #{project.name}/ end it 'contains a link to the new issue' do @@ -76,7 +76,7 @@ describe Notify do it_behaves_like 'a multiple recipients email' it 'has the correct subject' do - should have_subject /changed issue/ + should have_subject /changed issue ##{issue.id} \| #{issue.title}/ end it 'contains the name of the previous assignee' do @@ -91,6 +91,29 @@ describe Notify do should have_body_text /#{project_issue_path project, issue}/ end end + + describe 'status changed' do + let(:current_user) { Factory.create :user, email: "current@email.com" } + let(:status) { 'closed' } + subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) } + + it 'has the correct subject' do + should have_subject /changed issue ##{issue.id} \| #{issue.title}/i + end + + it 'contains the new status' do + should have_body_text /#{status}/i + end + + it 'contains the user name' do + should have_body_text /#{current_user.name}/i + end + + it 'contains a link to the issue' do + should have_body_text /#{project_issue_path project, issue}/ + end + end + end context 'for merge requests' do @@ -145,6 +168,26 @@ describe Notify do end end + describe 'project access changed' do + let(:project) { Factory.create(:project, + path: "Fuu", + code: "Fuu") } + let(:user) { Factory.create :user } + let(:users_project) { Factory.create(:users_project, + project: project, + user: user) } + subject { Notify.project_access_granted_email(users_project.id) } + it 'has the correct subject' do + should have_subject /access to project was granted/ + end + it 'contains name of project' do + should have_body_text /#{project.name}/ + end + it 'contains new user role' do + should have_body_text /#{users_project.project_access_human}/ + end + end + context 'items that are noteable, the email for a note' do let(:note_author) { Factory.create(:user, name: 'author_name') } let(:note) { Factory.create(:note, project: project, author: note_author) } diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 188f0997..aaffda31 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -1,24 +1,9 @@ -# == Schema Information -# -# Table name: events -# -# id :integer(4) not null, primary key -# target_type :string(255) -# target_id :integer(4) -# title :string(255) -# data :text -# project_id :integer(4) -# created_at :datetime not null -# updated_at :datetime not null -# action :integer(4) -# author_id :integer(4) -# - require 'spec_helper' describe Event do describe "Associations" do it { should belong_to(:project) } + it { should belong_to(:target) } end describe "Respond to" do @@ -29,16 +14,6 @@ describe Event do it { should respond_to(:commits) } end - describe "Creation" do - before do - @event = Factory :event - end - - it "should create a valid event" do - @event.should be_valid - end - end - describe "Push event" do before do project = Factory :project diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index e9cbd725..69829a4d 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -2,28 +2,19 @@ require 'spec_helper' describe Issue do describe "Associations" do - it { should belong_to(:project) } - it { should belong_to(:author) } - it { should belong_to(:assignee) } it { should belong_to(:milestone) } end describe "Validation" do - it { should validate_presence_of(:title) } - it { should validate_presence_of(:author_id) } - it { should validate_presence_of(:project_id) } + it { should ensure_length_of(:description).is_within(0..2000) } end - describe "Scope" do - it { Issue.should respond_to :closed } - it { Issue.should respond_to :opened } + describe 'modules' do + it { should include_module(IssueCommonality) } + it { should include_module(Upvote) } end - subject { Factory.create(:issue, - author: Factory(:user), - assignee: Factory(:user), - project: Factory.create(:project)) } - it { should be_valid } + subject { Factory.create(:issue) } describe '#is_being_reassigned?' do it 'returns true if the issue assignee has changed' do @@ -41,11 +32,7 @@ describe Issue do subject.is_being_closed?.should be_true end it 'returns false if the closed attribute has changed and is now false' do - issue = Factory.create(:issue, - closed: true, - author: Factory(:user), - assignee: Factory(:user), - project: Factory.create(:project)) + issue = Factory.create(:closed_issue) issue.closed = false issue.is_being_closed?.should be_false end @@ -57,11 +44,7 @@ describe Issue do describe '#is_being_reopened?' do it 'returns true if the closed attribute has changed and is now false' do - issue = Factory.create(:issue, - closed: true, - author: Factory(:user), - assignee: Factory(:user), - project: Factory.create(:project)) + issue = Factory.create(:closed_issue) issue.closed = false issue.is_being_reopened?.should be_true end @@ -73,64 +56,4 @@ describe Issue do subject.is_being_reopened?.should be_false end end - - describe "plus 1" do - let(:project) { Factory(:project) } - subject { - Factory.create(:issue, - author: Factory(:user), - assignee: Factory(:user), - project: project) - } - - it "with no notes has a 0/0 score" do - subject.upvotes.should == 0 - end - - it "should recognize non-+1 notes" do - subject.notes << Factory(:note, note: "No +1 here", project: Factory(:project, path: 'plusone', code: 'plusone')) - subject.should have(1).note - subject.notes.first.upvote?.should be_false - subject.upvotes.should == 0 - end - - it "should recognize a single +1 note" do - subject.notes << Factory(:note, note: "+1 This is awesome", project: Factory(:project, path: 'plusone', code: 'plusone')) - subject.upvotes.should == 1 - end - - it "should recognize a multiple +1 notes" do - subject.notes << Factory(:note, note: "+1 This is awesome", project: Factory(:project, path: 'plusone', code: 'plusone')) - subject.notes << Factory(:note, note: "+1 I want this", project: Factory(:project, path: 'plustwo', code: 'plustwo')) - subject.upvotes.should == 2 - end - end - - describe ".search" do - let!(:issue) { Factory.create(:issue, title: "Searchable issue", - project: Factory.create(:project)) } - - it "matches by title" do - Issue.search('able').all.should == [issue] - end - end end -# == Schema Information -# -# Table name: issues -# -# id :integer(4) not null, primary key -# title :string(255) -# assignee_id :integer(4) -# author_id :integer(4) -# project_id :integer(4) -# created_at :datetime not null -# updated_at :datetime not null -# closed :boolean(1) default(FALSE), not null -# position :integer(4) default(0) -# critical :boolean(1) default(FALSE), not null -# branch_name :string(255) -# description :text -# milestone_id :integer(4) -# - diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index 0f9b3177..85cd291d 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -2,12 +2,15 @@ require 'spec_helper' describe Key do describe "Associations" do - it { should belong_to(:user) or belong_to(:project) } + it { should belong_to(:user) } + it { should belong_to(:project) } end describe "Validation" do it { should validate_presence_of(:title) } it { should validate_presence_of(:key) } + it { should ensure_length_of(:title).is_within(0..255) } + it { should ensure_length_of(:key).is_within(0..5000) } end describe "Methods" do @@ -17,20 +20,15 @@ describe Key do context "validation of uniqueness" do context "as a deploy key" do - let(:project) { Factory.create(:project, path: 'alpha', code: 'alpha') } - let(:another_project) { Factory.create(:project, path: 'beta', code: 'beta') } - - before do - deploy_key = Factory.create(:key, project: project) - end + let!(:deploy_key) { create(:deploy_key) } it "does not accept the same key twice for a project" do - key = Factory.new(:key, project: project) + key = build(:key, project: deploy_key.project) key.should_not be_valid end it "does accept the same key for another project" do - key = Factory.new(:key, project: another_project) + key = build(:key, project_id: 0) key.should be_valid end end @@ -39,27 +37,13 @@ describe Key do let(:user) { Factory.create(:user) } it "accepts the key once" do - Factory.new(:key, user: user).should be_valid + build(:key, user: user).should be_valid end it "does not accepts the key twice" do - Factory.create(:key, user: user) - Factory.new(:key, user: user).should_not be_valid + create(:key, user: user) + build(:key, user: user).should_not be_valid end end end end -# == Schema Information -# -# Table name: keys -# -# id :integer(4) not null, primary key -# user_id :integer(4) -# created_at :datetime not null -# updated_at :datetime not null -# key :text -# title :string(255) -# identifier :string(255) -# project_id :integer(4) -# - diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index c7ad08a1..d1253b35 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1,88 +1,13 @@ require 'spec_helper' describe MergeRequest do - describe "Associations" do - it { should belong_to(:project) } - it { should belong_to(:author) } - it { should belong_to(:assignee) } - end - describe "Validation" do it { should validate_presence_of(:target_branch) } it { should validate_presence_of(:source_branch) } - it { should validate_presence_of(:title) } - it { should validate_presence_of(:author_id) } - it { should validate_presence_of(:project_id) } end - describe "Scope" do - it { MergeRequest.should respond_to :closed } - it { MergeRequest.should respond_to :opened } - end - - it { Factory.create(:merge_request, - author: Factory(:user), - assignee: Factory(:user), - project: Factory.create(:project)).should be_valid } - - describe "plus 1" do - let(:project) { Factory(:project) } - subject { - Factory.create(:merge_request, - author: Factory(:user), - assignee: Factory(:user), - project: project) - } - - it "with no notes has a 0/0 score" do - subject.upvotes.should == 0 - end - - it "should recognize non-+1 notes" do - subject.notes << Factory(:note, note: "No +1 here", project: Factory(:project, path: 'plusone', code: 'plusone')) - subject.should have(1).note - subject.notes.first.upvote?.should be_false - subject.upvotes.should == 0 - end - - it "should recognize a single +1 note" do - subject.notes << Factory(:note, note: "+1 This is awesome", project: Factory(:project, path: 'plusone', code: 'plusone')) - subject.upvotes.should == 1 - end - - it "should recognize a multiple +1 notes" do - subject.notes << Factory(:note, note: "+1 This is awesome", project: Factory(:project, path: 'plusone', code: 'plusone')) - subject.notes << Factory(:note, note: "+1 I want this", project: Factory(:project, path: 'plustwo', code: 'plustwo')) - subject.upvotes.should == 2 - end - end - - describe ".search" do - let!(:issue) { Factory.create(:issue, title: "Searchable issue", - project: Factory.create(:project)) } - - it "matches by title" do - Issue.search('able').all.should == [issue] - end + describe 'modules' do + it { should include_module(IssueCommonality) } + it { should include_module(Upvote) } end end -# == Schema Information -# -# Table name: merge_requests -# -# id :integer(4) not null, primary key -# target_branch :string(255) not null -# source_branch :string(255) not null -# project_id :integer(4) not null -# author_id :integer(4) -# assignee_id :integer(4) -# title :string(255) -# closed :boolean(1) default(FALSE), not null -# created_at :datetime not null -# updated_at :datetime not null -# st_commits :text(2147483647 -# st_diffs :text(2147483647 -# merged :boolean(1) default(FALSE), not null -# state :integer(4) default(1), not null -# - diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 880d3f30..fa15fc8f 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -1,17 +1,3 @@ -# == Schema Information -# -# Table name: milestones -# -# id :integer(4) not null, primary key -# title :string(255) not null -# project_id :integer(4) not null -# description :text -# due_date :date -# closed :boolean(1) default(FALSE), not null -# created_at :datetime not null -# updated_at :datetime not null -# - require 'spec_helper' describe Milestone do @@ -25,30 +11,36 @@ describe Milestone do it { should validate_presence_of(:project_id) } end - let(:project) { Factory :project } - let(:milestone) { Factory :milestone, project: project } - let(:issue) { Factory :issue, project: project } + let(:milestone) { Factory :milestone } + let(:issue) { Factory :issue } - it { milestone.should be_valid } - - describe "Issues" do - before do + describe "#percent_complete" do + it "should not count open issues" do milestone.issues << issue + milestone.percent_complete.should == 0 end - it { milestone.percent_complete.should == 0 } + it "should count closed issues" do + issue.update_attributes(closed: true) + milestone.issues << issue + milestone.percent_complete.should == 100 + end - it do - issue.update_attributes closed: true + it "should recover from dividing by zero" do + milestone.issues.should_receive(:count).and_return(0) milestone.percent_complete.should == 100 end end - describe :expires_at do - before do - milestone.update_attributes due_date: Date.today + 1.day + describe "#expires_at" do + it "should be nil when due_date is unset" do + milestone.update_attributes(due_date: nil) + milestone.expires_at.should be_nil end - it { milestone.expires_at.should_not be_nil } + it "should not be nil when due_date is set" do + milestone.update_attributes(due_date: Date.tomorrow) + milestone.expires_at.should be_present + end end end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index c97b23cb..ffaf442d 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -1,11 +1,10 @@ require 'spec_helper' describe Note do - let(:project) { Factory :project } - let!(:commit) { project.commit } - describe "Associations" do it { should belong_to(:project) } + it { should belong_to(:noteable) } + it { should belong_to(:author).class_name('User') } end describe "Validation" do @@ -13,8 +12,6 @@ describe Note do it { should validate_presence_of(:project) } end - it { Factory.create(:note, - project: project).should be_valid } describe "Scopes" do it "should have a today named scope that returns ..." do Note.today.where_values.should == ["created_at >= '#{Date.today}'"] @@ -25,26 +22,27 @@ describe Note do let(:project) { Factory(:project) } it "recognizes a neutral note" do - note = Factory(:note, project: project, note: "This is not a +1 note") + note = Factory(:note, note: "This is not a +1 note") note.should_not be_upvote end it "recognizes a +1 note" do - note = Factory(:note, project: project, note: "+1 for this") + note = Factory(:note, note: "+1 for this") note.should be_upvote end it "recognizes a -1 note as no vote" do - note = Factory(:note, project: project, note: "-1 for this") + note = Factory(:note, note: "-1 for this") note.should_not be_upvote end end - describe "Commit notes" do + let(:project) { create(:project) } + let(:commit) { project.commit } + describe "Commit notes" do before do @note = Factory :note, - project: project, noteable_id: commit.id, noteable_type: "Commit" end @@ -58,7 +56,6 @@ describe Note do describe "Pre-line commit notes" do before do @note = Factory :note, - project: project, noteable_id: commit.id, noteable_type: "Commit", line_code: "0_16_1" @@ -91,8 +88,8 @@ describe Note do describe :authorization do before do - @p1 = project - @p2 = Factory :project, code: "alien", path: "gitlabhq_1" + @p1 = create(:project) + @p2 = Factory :project @u1 = Factory :user @u2 = Factory :user @u3 = Factory :user @@ -135,19 +132,3 @@ describe Note do end end end -# == Schema Information -# -# Table name: notes -# -# id :integer(4) not null, primary key -# note :text -# noteable_id :string(255) -# noteable_type :string(255) -# author_id :integer(4) -# created_at :datetime not null -# updated_at :datetime not null -# project_id :integer(4) -# attachment :string(255) -# line_code :string(255) -# - diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 5bba4ff6..5add7ff8 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2,23 +2,52 @@ require 'spec_helper' describe Project do describe "Associations" do + it { should belong_to(:owner).class_name('User') } it { should have_many(:users) } - it { should have_many(:protected_branches).dependent(:destroy) } it { should have_many(:events).dependent(:destroy) } - it { should have_many(:wikis).dependent(:destroy) } it { should have_many(:merge_requests).dependent(:destroy) } - it { should have_many(:users_projects).dependent(:destroy) } it { should have_many(:issues).dependent(:destroy) } + it { should have_many(:milestones).dependent(:destroy) } + it { should have_many(:users_projects).dependent(:destroy) } it { should have_many(:notes).dependent(:destroy) } it { should have_many(:snippets).dependent(:destroy) } - it { should have_many(:hooks).dependent(:destroy) } it { should have_many(:deploy_keys).dependent(:destroy) } + it { should have_many(:hooks).dependent(:destroy) } + it { should have_many(:wikis).dependent(:destroy) } + it { should have_many(:protected_branches).dependent(:destroy) } end describe "Validation" do + let!(:project) { create(:project) } + it { should validate_presence_of(:name) } + it { should validate_uniqueness_of(:name) } + it { should ensure_length_of(:name).is_within(0..255) } + it { should validate_presence_of(:path) } + it { should validate_uniqueness_of(:path) } + it { should ensure_length_of(:path).is_within(0..255) } + # TODO: Formats + + it { should ensure_length_of(:description).is_within(0..2000) } + it { should validate_presence_of(:code) } + it { should validate_uniqueness_of(:code) } + it { should ensure_length_of(:code).is_within(1..255) } + # TODO: Formats + + it { should validate_presence_of(:owner) } + + it "should not allow new projects beyond user limits" do + project.stub(:owner).and_return(double(can_create_project?: false, projects_limit: 1)) + project.should_not be_valid + project.errors[:base].first.should match(/Your own projects limit is 1/) + end + + it "should not allow 'gitolite-admin' as repo name" do + should allow_value("blah").for(:path) + should_not allow_value("gitolite-admin").for(:path) + end end describe "Respond to" do @@ -40,7 +69,6 @@ describe Project do it { should respond_to(:commits_with_refs) } it { should respond_to(:commits_since) } it { should respond_to(:commits_between) } - it { should respond_to(:write_hooks) } it { should respond_to(:satellite) } it { should respond_to(:update_repository) } it { should respond_to(:destroy_repository) } @@ -74,9 +102,11 @@ describe Project do it { should respond_to(:trigger_post_receive) } end - it "should not allow 'gitolite-admin' as repo name" do - should allow_value("blah").for(:path) - should_not allow_value("gitolite-admin").for(:path) + describe 'modules' do + it { should include_module(Repository) } + it { should include_module(PushObserver) } + it { should include_module(Authority) } + it { should include_module(Team) } end it "should return valid url to repo" do @@ -86,7 +116,7 @@ describe Project do it "should return path to repo" do project = Project.new(path: "somewhere") - project.path_to_repo.should == File.join(Rails.root, "tmp", "tests", "somewhere") + project.path_to_repo.should == File.join(Rails.root, "tmp", "repositories", "somewhere") end it "returns the full web URL for this repo" do @@ -111,7 +141,7 @@ describe Project do let(:last_event) { double } before do - project.stub(:events).and_return( [ double, double, last_event ] ) + project.stub_chain(:events, :order).and_return( [ double, double, last_event ] ) end it { project.last_activity.should == last_event } @@ -237,23 +267,3 @@ describe Project do end end end -# == Schema Information -# -# Table name: projects -# -# id :integer(4) not null, primary key -# name :string(255) -# path :string(255) -# description :text -# created_at :datetime not null -# updated_at :datetime not null -# private_flag :boolean(1) default(TRUE), not null -# code :string(255) -# owner_id :integer(4) -# default_branch :string(255) default("master"), not null -# issues_enabled :boolean(1) default(TRUE), not null -# wall_enabled :boolean(1) default(TRUE), not null -# merge_requests_enabled :boolean(1) default(TRUE), not null -# wiki_enabled :boolean(1) default(TRUE), not null -# - diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb index 1654e3b6..9180bc3b 100644 --- a/spec/models/protected_branch_spec.rb +++ b/spec/models/protected_branch_spec.rb @@ -1,19 +1,6 @@ -# == Schema Information -# -# Table name: protected_branches -# -# id :integer(4) not null, primary key -# project_id :integer(4) not null -# name :string(255) not null -# created_at :datetime not null -# updated_at :datetime not null -# - require 'spec_helper' describe ProtectedBranch do - let(:project) { Factory(:project) } - describe 'Associations' do it { should belong_to(:project) } end @@ -24,26 +11,26 @@ describe ProtectedBranch do end describe 'Callbacks' do - subject { ProtectedBranch.new(project: project, name: 'branch_name') } + let(:branch) { build(:protected_branch) } it 'call update_repository after save' do - subject.should_receive(:update_repository) - subject.save + branch.should_receive(:update_repository) + branch.save end it 'call update_repository after destroy' do - subject.should_receive(:update_repository) - subject.destroy + branch.save + branch.should_receive(:update_repository) + branch.destroy end end describe '#commit' do - subject { ProtectedBranch.new(project: project, name: 'cant_touch_this') } + let(:branch) { create(:protected_branch) } it 'commits itself to its project' do - project.should_receive(:commit).with('cant_touch_this') - - subject.commit + branch.project.should_receive(:commit).with(branch.name) + branch.commit end end end diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 9b4aaa13..ffb861c4 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -3,29 +3,21 @@ require 'spec_helper' describe Snippet do describe "Associations" do it { should belong_to(:project) } - it { should belong_to(:author) } + it { should belong_to(:author).class_name('User') } + it { should have_many(:notes).dependent(:destroy) } end describe "Validation" do - it { should validate_presence_of(:title) } it { should validate_presence_of(:author_id) } it { should validate_presence_of(:project_id) } + + it { should validate_presence_of(:title) } + it { should ensure_length_of(:title).is_within(0..255) } + it { should validate_presence_of(:file_name) } + it { should ensure_length_of(:title).is_within(0..255) } + it { should validate_presence_of(:content) } + it { should ensure_length_of(:content).is_within(0..10_000) } end end -# == Schema Information -# -# Table name: snippets -# -# id :integer(4) not null, primary key -# title :string(255) -# content :text -# author_id :integer(4) not null -# project_id :integer(4) not null -# created_at :datetime not null -# updated_at :datetime not null -# file_name :string(255) -# expires_at :datetime -# - diff --git a/spec/models/system_hook_spec.rb b/spec/models/system_hook_spec.rb index 56d76ed0..fe2a5836 100644 --- a/spec/models/system_hook_spec.rb +++ b/spec/models/system_hook_spec.rb @@ -10,13 +10,12 @@ describe SystemHook do end it "project_create hook" do - user = Factory :user with_resque do - project = Factory :project_without_owner, owner: user + project = Factory :project end WebMock.should have_requested(:post, @system_hook.url).with(body: /project_create/).once end - + it "project_destroy hook" do project = Factory :project with_resque do @@ -31,7 +30,7 @@ describe SystemHook do end WebMock.should have_requested(:post, @system_hook.url).with(body: /user_create/).once end - + it "user_destroy hook" do user = Factory :user with_resque do @@ -39,7 +38,7 @@ describe SystemHook do end WebMock.should have_requested(:post, @system_hook.url).with(body: /user_destroy/).once end - + it "project_create hook" do user = Factory :user project = Factory :project @@ -48,7 +47,7 @@ describe SystemHook do end WebMock.should have_requested(:post, @system_hook.url).with(body: /user_add_to_team/).once end - + it "project_destroy hook" do user = Factory :user project = Factory :project diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 265dcef1..ca34f07d 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2,12 +2,26 @@ require 'spec_helper' describe User do describe "Associations" do + it { should have_many(:users_projects).dependent(:destroy) } it { should have_many(:projects) } - it { should have_many(:users_projects) } - it { should have_many(:issues) } - it { should have_many(:assigned_issues) } - it { should have_many(:merge_requests) } - it { should have_many(:assigned_merge_requests) } + it { should have_many(:my_own_projects).class_name('Project') } + it { should have_many(:keys).dependent(:destroy) } + it { should have_many(:events).class_name('Event').dependent(:destroy) } + it { should have_many(:recent_events).class_name('Event') } + it { should have_many(:issues).dependent(:destroy) } + it { should have_many(:notes).dependent(:destroy) } + it { should have_many(:assigned_issues).dependent(:destroy) } + it { should have_many(:merge_requests).dependent(:destroy) } + it { should have_many(:assigned_merge_requests).dependent(:destroy) } + end + + describe 'validations' do + it { should validate_presence_of(:projects_limit) } + it { should validate_numericality_of(:projects_limit) } + it { should allow_value(0).for(:projects_limit) } + it { should_not allow_value(-1).for(:projects_limit) } + + it { should ensure_length_of(:bio).is_within(0..255) } end describe "Respond to" do @@ -49,49 +63,4 @@ describe User do user = Factory(:user) user.authentication_token.should_not == "" end - - describe "dependent" do - before do - @user = Factory :user - @note = Factory :note, - author: @user, - project: Factory(:project) - end - - it "should destroy all notes with user" do - Note.find_by_id(@note.id).should_not be_nil - @user.destroy - Note.find_by_id(@note.id).should be_nil - end - end end -# == Schema Information -# -# Table name: users -# -# id :integer(4) not null, primary key -# email :string(255) default(""), not null -# encrypted_password :string(128) default(""), not null -# reset_password_token :string(255) -# reset_password_sent_at :datetime -# remember_created_at :datetime -# sign_in_count :integer(4) default(0) -# current_sign_in_at :datetime -# last_sign_in_at :datetime -# current_sign_in_ip :string(255) -# last_sign_in_ip :string(255) -# created_at :datetime not null -# updated_at :datetime not null -# name :string(255) -# admin :boolean(1) default(FALSE), not null -# projects_limit :integer(4) default(10) -# skype :string(255) default(""), not null -# linkedin :string(255) default(""), not null -# twitter :string(255) default(""), not null -# authentication_token :string(255) -# dark_scheme :boolean(1) default(FALSE), not null -# theme_id :integer(4) default(1), not null -# bio :string(255) -# blocked :boolean(1) default(FALSE), not null -# - diff --git a/spec/models/users_project_spec.rb b/spec/models/users_project_spec.rb index 87fbfbf2..3197ba6e 100644 --- a/spec/models/users_project_spec.rb +++ b/spec/models/users_project_spec.rb @@ -7,7 +7,11 @@ describe UsersProject do end describe "Validation" do + let!(:users_project) { create(:users_project) } + it { should validate_presence_of(:user_id) } + it { should validate_uniqueness_of(:user_id).scoped_to(:project_id) } + it { should validate_presence_of(:project_id) } end @@ -16,15 +20,3 @@ describe UsersProject do it { should respond_to(:user_email) } end end -# == Schema Information -# -# Table name: users_projects -# -# id :integer(4) not null, primary key -# user_id :integer(4) not null -# project_id :integer(4) not null -# created_at :datetime not null -# updated_at :datetime not null -# project_access :integer(4) default(0), not null -# - diff --git a/spec/models/web_hook_spec.rb b/spec/models/web_hook_spec.rb index 88594761..3cba5b64 100644 --- a/spec/models/web_hook_spec.rb +++ b/spec/models/web_hook_spec.rb @@ -52,14 +52,3 @@ describe ProjectHook do end end end -# == Schema Information -# -# Table name: web_hooks -# -# id :integer(4) not null, primary key -# url :string(255) -# project_id :integer(4) -# created_at :datetime not null -# updated_at :datetime not null -# - diff --git a/spec/models/wiki_spec.rb b/spec/models/wiki_spec.rb index 892d0e8f..de6ce426 100644 --- a/spec/models/wiki_spec.rb +++ b/spec/models/wiki_spec.rb @@ -4,27 +4,13 @@ describe Wiki do describe "Associations" do it { should belong_to(:project) } it { should belong_to(:user) } + it { should have_many(:notes).dependent(:destroy) } end describe "Validation" do it { should validate_presence_of(:title) } + it { should ensure_length_of(:title).is_within(1..250) } it { should validate_presence_of(:content) } it { should validate_presence_of(:user_id) } end - - it { Factory(:wiki).should be_valid } end -# == Schema Information -# -# Table name: wikis -# -# id :integer(4) not null, primary key -# title :string(255) -# content :text -# project_id :integer(4) -# created_at :datetime not null -# updated_at :datetime not null -# slug :string(255) -# user_id :integer(4) -# - diff --git a/spec/monkeypatch.rb b/spec/monkeypatch.rb deleted file mode 100644 index 855a31f0..00000000 --- a/spec/monkeypatch.rb +++ /dev/null @@ -1,51 +0,0 @@ -# Stubbing Project <-> git host path -# create project using Factory only -class Project - def update_repository - true - end - - def destroy_repository - true - end - - def path_to_repo - File.join(Rails.root, "tmp", "tests", path) - end - - def satellite - @satellite ||= FakeSatellite.new - end -end - -class Key - def update_repository - true - end - - def repository_delete_key - true - end -end - -class UsersProject - def update_repository - true - end -end - -class FakeSatellite - def exists? - true - end - - def create - true - end -end - -class ProtectedBranch - def update_repository - true - end -end diff --git a/spec/models/activity_observer_spec.rb b/spec/observers/activity_observer_spec.rb similarity index 100% rename from spec/models/activity_observer_spec.rb rename to spec/observers/activity_observer_spec.rb diff --git a/spec/models/issue_observer_spec.rb b/spec/observers/issue_observer_spec.rb similarity index 58% rename from spec/models/issue_observer_spec.rb rename to spec/observers/issue_observer_spec.rb index c6a405f1..b5943f2c 100644 --- a/spec/models/issue_observer_spec.rb +++ b/spec/observers/issue_observer_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' describe IssueObserver do let(:some_user) { double(:user, id: 1) } let(:assignee) { double(:user, id: 2) } - let(:issue) { double(:issue, id: 42, assignee: assignee) } + let(:author) { double(:user, id: 3) } + let(:issue) { double(:issue, id: 42, assignee: assignee, author: author) } before(:each) { subject.stub(:current_user).and_return(some_user) } @@ -67,36 +68,90 @@ describe IssueObserver do end end - context 'a status "closed" note' do - it 'is created if the issue is being closed' do + context 'a status "closed"' do + it 'note is created if the issue is being closed' do issue.should_receive(:is_being_closed?).and_return(true) Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed') subject.after_update(issue) end - it 'is not created if the issue is not being closed' do + it 'note is not created if the issue is not being closed' do issue.should_receive(:is_being_closed?).and_return(false) Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'closed') subject.after_update(issue) end + + it 'notification is delivered if the issue being closed' do + issue.stub(:is_being_closed?).and_return(true) + Notify.should_receive(:issue_status_changed_email).twice + Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed') + + subject.after_update(issue) + end + + it 'notification is not delivered if the issue not being closed' do + issue.stub(:is_being_closed?).and_return(false) + Notify.should_not_receive(:issue_status_changed_email) + Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'closed') + + subject.after_update(issue) + end + + it 'notification is delivered only to author if the issue being closed' do + issue_without_assignee = double(:issue, id: 42, author: author, assignee: nil) + issue_without_assignee.stub(:is_being_reassigned?).and_return(false) + issue_without_assignee.stub(:is_being_closed?).and_return(true) + issue_without_assignee.stub(:is_being_reopened?).and_return(false) + Notify.should_receive(:issue_status_changed_email).once + Note.should_receive(:create_status_change_note).with(issue_without_assignee, some_user, 'closed') + + subject.after_update(issue_without_assignee) + end end - context 'a status "reopened" note' do - it 'is created if the issue is being reopened' do + context 'a status "reopened"' do + it 'note is created if the issue is being reopened' do issue.should_receive(:is_being_reopened?).and_return(true) Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened') subject.after_update(issue) end - it 'is not created if the issue is not being reopened' do + it 'note is not created if the issue is not being reopened' do issue.should_receive(:is_being_reopened?).and_return(false) Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'reopened') subject.after_update(issue) end + + it 'notification is delivered if the issue being reopened' do + issue.stub(:is_being_reopened?).and_return(true) + Notify.should_receive(:issue_status_changed_email).twice + Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened') + + subject.after_update(issue) + end + + it 'notification is not delivered if the issue not being reopened' do + issue.stub(:is_being_reopened?).and_return(false) + Notify.should_not_receive(:issue_status_changed_email) + Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'reopened') + + subject.after_update(issue) + end + + it 'notification is delivered only to author if the issue being reopened' do + issue_without_assignee = double(:issue, id: 42, author: author, assignee: nil) + issue_without_assignee.stub(:is_being_reassigned?).and_return(false) + issue_without_assignee.stub(:is_being_closed?).and_return(false) + issue_without_assignee.stub(:is_being_reopened?).and_return(true) + Notify.should_receive(:issue_status_changed_email).once + Note.should_receive(:create_status_change_note).with(issue_without_assignee, some_user, 'reopened') + + subject.after_update(issue_without_assignee) + end end end diff --git a/spec/observers/key_observer_spec.rb b/spec/observers/key_observer_spec.rb new file mode 100644 index 00000000..7f2a76a3 --- /dev/null +++ b/spec/observers/key_observer_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe KeyObserver do + before do + @key = double('Key', + identifier: 'admin_654654', + key: '== a vaild ssh key', + projects: [], + is_deploy_key: false + ) + + @gitolite = double('Gitlab::Gitolite', + set_key: true, + remove_key: true + ) + + @observer = KeyObserver.instance + @observer.stub(:git_host => @gitolite) + end + + context :after_save do + it do + @gitolite.should_receive(:set_key).with(@key.identifier, @key.key, @key.projects) + @observer.after_save(@key) + end + end + + context :after_destroy do + it do + @gitolite.should_receive(:remove_key).with(@key.identifier, @key.projects) + @observer.after_destroy(@key) + end + end +end diff --git a/spec/models/user_observer_spec.rb b/spec/observers/user_observer_spec.rb similarity index 100% rename from spec/models/user_observer_spec.rb rename to spec/observers/user_observer_spec.rb diff --git a/spec/observers/users_project_observer_spec.rb b/spec/observers/users_project_observer_spec.rb new file mode 100644 index 00000000..3e392040 --- /dev/null +++ b/spec/observers/users_project_observer_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe UsersProjectObserver do + let(:user) { Factory.create :user } + let(:project) { Factory.create(:project, + code: "Fuu", + path: "Fuu" ) } + let(:users_project) { Factory.create(:users_project, + project: project, + user: user )} + subject { UsersProjectObserver.instance } + + describe "#after_create" do + it "should called when UsersProject created" do + subject.should_receive(:after_create) + UsersProject.observers.enable :users_project_observer do + Factory.create(:users_project, + project: project, + user: user) + end + end + it "should send email to user" do + Notify.should_receive(:project_access_granted_email).with(users_project.id).and_return(double(deliver: true)) + subject.after_create(users_project) + end + end + + describe "#after_update" do + it "should called when UsersProject updated" do + subject.should_receive(:after_update) + UsersProject.observers.enable :users_project_observer do + users_project.update_attribute(:project_access, 40) + end + end + it "should send email to user" do + Notify.should_receive(:project_access_granted_email).with(users_project.id).and_return(double(deliver: true)) + subject.after_update(users_project) + end + end +end diff --git a/spec/requests/admin/admin_projects_spec.rb b/spec/requests/admin/admin_projects_spec.rb index 0ce66f5f..2edfb592 100644 --- a/spec/requests/admin/admin_projects_spec.rb +++ b/spec/requests/admin/admin_projects_spec.rb @@ -87,7 +87,7 @@ describe "Admin::Projects" do visit new_admin_project_path fill_in 'project_name', with: 'NewProject' fill_in 'project_code', with: 'NPR' - fill_in 'project_path', with: 'gitlabhq_1' + fill_in 'project_path', with: 'newproject' expect { click_button "Create project" }.to change { Project.count }.by(1) @project = Project.last end diff --git a/spec/requests/admin/security_spec.rb b/spec/requests/admin/security_spec.rb index 0c369740..63068326 100644 --- a/spec/requests/admin/security_spec.rb +++ b/spec/requests/admin/security_spec.rb @@ -2,20 +2,26 @@ require 'spec_helper' describe "Admin::Projects" do describe "GET /admin/projects" do - it { admin_projects_path.should be_allowed_for :admin } - it { admin_projects_path.should be_denied_for :user } - it { admin_projects_path.should be_denied_for :visitor } + subject { admin_projects_path } + + it { should be_allowed_for :admin } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /admin/users" do - it { admin_users_path.should be_allowed_for :admin } - it { admin_users_path.should be_denied_for :user } - it { admin_users_path.should be_denied_for :visitor } + subject { admin_users_path } + + it { should be_allowed_for :admin } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /admin/hooks" do - it { admin_hooks_path.should be_allowed_for :admin } - it { admin_hooks_path.should be_denied_for :user } - it { admin_hooks_path.should be_denied_for :visitor } + subject { admin_hooks_path } + + it { should be_allowed_for :admin } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end end diff --git a/spec/api/issues_spec.rb b/spec/requests/api/issues_spec.rb similarity index 73% rename from spec/api/issues_spec.rb rename to spec/requests/api/issues_spec.rb index f6d8e379..293ea83a 100644 --- a/spec/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Gitlab::API do + include ApiHelpers + let(:user) { Factory :user } let!(:project) { Factory :project, owner: user } let!(:issue) { Factory :issue, author: user, assignee: user, project: project } @@ -8,13 +10,13 @@ describe Gitlab::API do describe "GET /issues" do it "should return authentication error" do - get "#{api_prefix}/issues" + get api("/issues") response.status.should == 401 end describe "authenticated GET /issues" do it "should return an array of issues" do - get "#{api_prefix}/issues?private_token=#{user.private_token}" + get api("/issues", user) response.status.should == 200 json_response.should be_an Array json_response.first['title'].should == issue.title @@ -24,7 +26,7 @@ describe Gitlab::API do describe "GET /projects/:id/issues" do it "should return project issues" do - get "#{api_prefix}/projects/#{project.code}/issues?private_token=#{user.private_token}" + get api("/projects/#{project.code}/issues", user) response.status.should == 200 json_response.should be_an Array json_response.first['title'].should == issue.title @@ -33,7 +35,7 @@ describe Gitlab::API do describe "GET /projects/:id/issues/:issue_id" do it "should return a project issue by id" do - get "#{api_prefix}/projects/#{project.code}/issues/#{issue.id}?private_token=#{user.private_token}" + get api("/projects/#{project.code}/issues/#{issue.id}", user) response.status.should == 200 json_response['title'].should == issue.title end @@ -41,7 +43,7 @@ describe Gitlab::API do describe "POST /projects/:id/issues" do it "should create a new project issue" do - post "#{api_prefix}/projects/#{project.code}/issues?private_token=#{user.private_token}", + post api("/projects/#{project.code}/issues", user), title: 'new issue', labels: 'label, label2' response.status.should == 201 json_response['title'].should == 'new issue' @@ -52,7 +54,7 @@ describe Gitlab::API do describe "PUT /projects/:id/issues/:issue_id" do it "should update a project issue" do - put "#{api_prefix}/projects/#{project.code}/issues/#{issue.id}?private_token=#{user.private_token}", + put api("/projects/#{project.code}/issues/#{issue.id}", user), title: 'updated title', labels: 'label2', closed: 1 response.status.should == 200 json_response['title'].should == 'updated title' @@ -63,9 +65,8 @@ describe Gitlab::API do describe "DELETE /projects/:id/issues/:issue_id" do it "should delete a project issue" do - expect { - delete "#{api_prefix}/projects/#{project.code}/issues/#{issue.id}?private_token=#{user.private_token}" - }.to change { Issue.count }.by(-1) + delete api("/projects/#{project.code}/issues/#{issue.id}", user) + response.status.should == 405 end end end diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb new file mode 100644 index 00000000..cf5f65f0 --- /dev/null +++ b/spec/requests/api/milestones_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe Gitlab::API do + include ApiHelpers + + let(:user) { Factory :user } + let!(:project) { Factory :project, owner: user } + let!(:milestone) { Factory :milestone, project: project } + + before { project.add_access(user, :read) } + + describe "GET /projects/:id/milestones" do + it "should return project milestones" do + get api("/projects/#{project.code}/milestones", user) + response.status.should == 200 + json_response.should be_an Array + json_response.first['title'].should == milestone.title + end + end + + describe "GET /projects/:id/milestones/:milestone_id" do + it "should return a project milestone by id" do + get api("/projects/#{project.code}/milestones/#{milestone.id}", user) + response.status.should == 200 + json_response['title'].should == milestone.title + end + end + + describe "POST /projects/:id/milestones" do + it "should create a new project milestone" do + post api("/projects/#{project.code}/milestones", user), + title: 'new milestone' + response.status.should == 201 + json_response['title'].should == 'new milestone' + json_response['description'].should be_nil + end + end + + describe "PUT /projects/:id/milestones/:milestone_id" do + it "should update a project milestone" do + put api("/projects/#{project.code}/milestones/#{milestone.id}", user), + title: 'updated title' + response.status.should == 200 + json_response['title'].should == 'updated title' + end + end +end diff --git a/spec/api/projects_spec.rb b/spec/requests/api/projects_spec.rb similarity index 67% rename from spec/api/projects_spec.rb rename to spec/requests/api/projects_spec.rb index ff45619e..0cbc12af 100644 --- a/spec/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Gitlab::API do + include ApiHelpers + let(:user) { Factory :user } let!(:project) { Factory :project, owner: user } let!(:snippet) { Factory :snippet, author: user, project: project, title: 'example' } @@ -8,13 +10,13 @@ describe Gitlab::API do describe "GET /projects" do it "should return authentication error" do - get "#{api_prefix}/projects" + get api("/projects") response.status.should == 401 end describe "authenticated GET /projects" do it "should return an array of projects" do - get "#{api_prefix}/projects?private_token=#{user.private_token}" + get api("/projects", user) response.status.should == 200 json_response.should be_an Array json_response.first['name'].should == project.name @@ -25,20 +27,20 @@ describe Gitlab::API do describe "GET /projects/:id" do it "should return a project by id" do - get "#{api_prefix}/projects/#{project.id}?private_token=#{user.private_token}" + get api("/projects/#{project.id}", user) response.status.should == 200 json_response['name'].should == project.name json_response['owner']['email'].should == user.email end it "should return a project by code name" do - get "#{api_prefix}/projects/#{project.code}?private_token=#{user.private_token}" + get api("/projects/#{project.code}", user) response.status.should == 200 json_response['name'].should == project.name end it "should return a 404 error if not found" do - get "#{api_prefix}/projects/42?private_token=#{user.private_token}" + get api("/projects/42", user) response.status.should == 404 json_response['message'].should == '404 Not found' end @@ -46,7 +48,7 @@ describe Gitlab::API do describe "GET /projects/:id/repository/branches" do it "should return an array of project branches" do - get "#{api_prefix}/projects/#{project.code}/repository/branches?private_token=#{user.private_token}" + get api("/projects/#{project.code}/repository/branches", user) response.status.should == 200 json_response.should be_an Array json_response.first['name'].should == project.repo.heads.sort_by(&:name).first.name @@ -55,7 +57,7 @@ describe Gitlab::API do describe "GET /projects/:id/repository/branches/:branch" do it "should return the branch information for a single branch" do - get "#{api_prefix}/projects/#{project.code}/repository/branches/new_design?private_token=#{user.private_token}" + get api("/projects/#{project.code}/repository/branches/new_design", user) response.status.should == 200 json_response['name'].should == 'new_design' @@ -65,7 +67,7 @@ describe Gitlab::API do describe "GET /projects/:id/repository/tags" do it "should return an array of project tags" do - get "#{api_prefix}/projects/#{project.code}/repository/tags?private_token=#{user.private_token}" + get api("/projects/#{project.code}/repository/tags", user) response.status.should == 200 json_response.should be_an Array json_response.first['name'].should == project.repo.tags.sort_by(&:name).reverse.first.name @@ -74,7 +76,7 @@ describe Gitlab::API do describe "GET /projects/:id/snippets/:snippet_id" do it "should return a project snippet" do - get "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}?private_token=#{user.private_token}" + get api("/projects/#{project.code}/snippets/#{snippet.id}", user) response.status.should == 200 json_response['title'].should == snippet.title end @@ -82,7 +84,7 @@ describe Gitlab::API do describe "POST /projects/:id/snippets" do it "should create a new project snippet" do - post "#{api_prefix}/projects/#{project.code}/snippets?private_token=#{user.private_token}", + post api("/projects/#{project.code}/snippets", user), title: 'api test', file_name: 'sample.rb', code: 'test' response.status.should == 201 json_response['title'].should == 'api test' @@ -91,7 +93,7 @@ describe Gitlab::API do describe "PUT /projects/:id/snippets" do it "should update an existing project snippet" do - put "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}?private_token=#{user.private_token}", + put api("/projects/#{project.code}/snippets/#{snippet.id}", user), code: 'updated code' response.status.should == 200 json_response['title'].should == 'example' @@ -102,34 +104,31 @@ describe Gitlab::API do describe "DELETE /projects/:id/snippets/:snippet_id" do it "should delete existing project snippet" do expect { - delete "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}?private_token=#{user.private_token}" + delete api("/projects/#{project.code}/snippets/#{snippet.id}", user) }.to change { Snippet.count }.by(-1) end end describe "GET /projects/:id/snippets/:snippet_id/raw" do it "should get a raw project snippet" do - get "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}/raw?private_token=#{user.private_token}" + get api("/projects/#{project.code}/snippets/#{snippet.id}/raw", user) response.status.should == 200 end end describe "GET /projects/:id/:sha/blob" do it "should get the raw file contents" do - get "#{api_prefix}/projects/#{project.code}/repository/commits/master/blob?filepath=README.md&private_token=#{user.private_token}" - + get api("/projects/#{project.code}/repository/commits/master/blob?filepath=README.md", user) response.status.should == 200 end it "should return 404 for invalid branch_name" do - get "#{api_prefix}/projects/#{project.code}/repository/commits/invalid_branch_name/blob?filepath=README.md&private_token=#{user.private_token}" - + get api("/projects/#{project.code}/repository/commits/invalid_branch_name/blob?filepath=README.md", user) response.status.should == 404 end it "should return 404 for invalid file" do - get "#{api_prefix}/projects/#{project.code}/repository/commits/master/blob?filepath=README.invalid&private_token=#{user.private_token}" - + get api("/projects/#{project.code}/repository/commits/master/blob?filepath=README.invalid", user) response.status.should == 404 end end diff --git a/spec/api/users_spec.rb b/spec/requests/api/users_spec.rb similarity index 76% rename from spec/api/users_spec.rb rename to spec/requests/api/users_spec.rb index 32b9379d..d791962a 100644 --- a/spec/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -1,17 +1,19 @@ require 'spec_helper' describe Gitlab::API do + include ApiHelpers + let(:user) { Factory :user } describe "GET /users" do it "should return authentication error" do - get "#{api_prefix}/users" + get api("/users") response.status.should == 401 end describe "authenticated GET /users" do it "should return an array of users" do - get "#{api_prefix}/users?private_token=#{user.private_token}" + get api("/users", user) response.status.should == 200 json_response.should be_an Array json_response.first['email'].should == user.email @@ -21,7 +23,7 @@ describe Gitlab::API do describe "GET /users/:id" do it "should return a user by id" do - get "#{api_prefix}/users/#{user.id}?private_token=#{user.private_token}" + get api("/users/#{user.id}", user) response.status.should == 200 json_response['email'].should == user.email end @@ -29,7 +31,7 @@ describe Gitlab::API do describe "GET /user" do it "should return current user" do - get "#{api_prefix}/user?private_token=#{user.private_token}" + get api("/user", user) response.status.should == 200 json_response['email'].should == user.email end diff --git a/spec/requests/atom/dashboard_issues_spec.rb b/spec/requests/atom/dashboard_issues_spec.rb index 9b4ffc0e..79a9b8ef 100644 --- a/spec/requests/atom/dashboard_issues_spec.rb +++ b/spec/requests/atom/dashboard_issues_spec.rb @@ -6,13 +6,9 @@ describe "User Issues Dashboard" do login_as :user - @project1 = Factory :project, - path: "project1", - code: "TEST1" + @project1 = Factory :project - @project2 = Factory :project, - path: "project2", - code: "TEST2" + @project2 = Factory :project @project1.add_access(@user, :read, :write) @project2.add_access(@user, :read, :write) diff --git a/spec/requests/projects_deploy_keys_spec.rb b/spec/requests/projects_deploy_keys_spec.rb index 0fea7b46..894aa6d3 100644 --- a/spec/requests/projects_deploy_keys_spec.rb +++ b/spec/requests/projects_deploy_keys_spec.rb @@ -42,7 +42,7 @@ describe "Projects", "DeployKeys" do describe "fill in" do before do fill_in "key_title", with: "laptop" - fill_in "key_key", with: "publickey234=" + fill_in "key_key", with: "ssh-rsa publickey234=" end it { expect { click_button "Save" }.to change {Key.count}.by(1) } @@ -55,12 +55,12 @@ describe "Projects", "DeployKeys" do end end - describe "Show page" do + describe "Show page" do before do @key = Factory :key, project: project - visit project_deploy_key_path(project, @key) + visit project_deploy_key_path(project, @key) end - + it { page.should have_content @key.title } it { page.should have_content @key.key[0..10] } end diff --git a/spec/requests/security/profile_access_spec.rb b/spec/requests/security/profile_access_spec.rb index b8ed27f0..9f6fe6a2 100644 --- a/spec/requests/security/profile_access_spec.rb +++ b/spec/requests/security/profile_access_spec.rb @@ -11,24 +11,30 @@ describe "Users Security" do end describe "GET /keys" do - it { keys_path.should be_allowed_for @u1 } - it { keys_path.should be_allowed_for :admin } - it { keys_path.should be_allowed_for :user } - it { keys_path.should be_denied_for :visitor } + subject { keys_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" do - it { profile_path.should be_allowed_for @u1 } - it { profile_path.should be_allowed_for :admin } - it { profile_path.should be_allowed_for :user } - it { profile_path.should be_denied_for :visitor } + subject { 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/password" do - it { profile_password_path.should be_allowed_for @u1 } - it { profile_password_path.should be_allowed_for :admin } - it { profile_password_path.should be_allowed_for :user } - it { profile_password_path.should be_denied_for :visitor } + subject { profile_password_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/requests/security/project_access_spec.rb b/spec/requests/security/project_access_spec.rb index d503cf85..0cdf43bf 100644 --- a/spec/requests/security/project_access_spec.rb +++ b/spec/requests/security/project_access_spec.rb @@ -26,64 +26,76 @@ describe "Application access" do end describe "GET /project_code" do - it { project_path(@project).should be_allowed_for @u1 } - it { project_path(@project).should be_allowed_for @u3 } - it { project_path(@project).should be_denied_for :admin } - it { project_path(@project).should be_denied_for @u2 } - it { project_path(@project).should be_denied_for :user } - it { project_path(@project).should be_denied_for :visitor } + subject { project_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/master/tree" do - it { tree_project_ref_path(@project, @project.root_ref).should be_allowed_for @u1 } - it { tree_project_ref_path(@project, @project.root_ref).should be_allowed_for @u3 } - it { tree_project_ref_path(@project, @project.root_ref).should be_denied_for :admin } - it { tree_project_ref_path(@project, @project.root_ref).should be_denied_for @u2 } - it { tree_project_ref_path(@project, @project.root_ref).should be_denied_for :user } - it { tree_project_ref_path(@project, @project.root_ref).should be_denied_for :visitor } + subject { tree_project_ref_path(@project, @project.root_ref) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/commits" do - it { project_commits_path(@project).should be_allowed_for @u1 } - it { project_commits_path(@project).should be_allowed_for @u3 } - it { project_commits_path(@project).should be_denied_for :admin } - it { project_commits_path(@project).should be_denied_for @u2 } - it { project_commits_path(@project).should be_denied_for :user } - it { project_commits_path(@project).should be_denied_for :visitor } + subject { project_commits_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/commit" do - it { project_commit_path(@project, @project.commit.id).should be_allowed_for @u1 } - it { project_commit_path(@project, @project.commit.id).should be_allowed_for @u3 } - it { project_commit_path(@project, @project.commit.id).should be_denied_for :admin } - it { project_commit_path(@project, @project.commit.id).should be_denied_for @u2 } - it { project_commit_path(@project, @project.commit.id).should be_denied_for :user } - it { project_commit_path(@project, @project.commit.id).should be_denied_for :visitor } + subject { project_commit_path(@project, @project.commit.id) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/team" do - it { team_project_path(@project).should be_allowed_for @u1 } - it { team_project_path(@project).should be_allowed_for @u3 } - it { team_project_path(@project).should be_denied_for :admin } - it { team_project_path(@project).should be_denied_for @u2 } - it { team_project_path(@project).should be_denied_for :user } - it { team_project_path(@project).should be_denied_for :visitor } + subject { team_project_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/wall" do - it { wall_project_path(@project).should be_allowed_for @u1 } - it { wall_project_path(@project).should be_allowed_for @u3 } - it { wall_project_path(@project).should be_denied_for :admin } - it { wall_project_path(@project).should be_denied_for @u2 } - it { wall_project_path(@project).should be_denied_for :user } - it { wall_project_path(@project).should be_denied_for :visitor } + subject { wall_project_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/blob" do before do - @commit = @project.commit - @path = @commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name - @blob_path = blob_project_ref_path(@project, @commit.id, path: @path) + commit = @project.commit + path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name + @blob_path = blob_project_ref_path(@project, commit.id, path: path) end it { @blob_path.should be_allowed_for @u1 } @@ -95,93 +107,113 @@ describe "Application access" do end describe "GET /project_code/edit" do - it { edit_project_path(@project).should be_allowed_for @u1 } - it { edit_project_path(@project).should be_denied_for @u3 } - it { edit_project_path(@project).should be_denied_for :admin } - it { edit_project_path(@project).should be_denied_for @u2 } - it { edit_project_path(@project).should be_denied_for :user } - it { edit_project_path(@project).should be_denied_for :visitor } + subject { edit_project_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_denied_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/deploy_keys" do - it { project_deploy_keys_path(@project).should be_allowed_for @u1 } - it { project_deploy_keys_path(@project).should be_denied_for @u3 } - it { project_deploy_keys_path(@project).should be_denied_for :admin } - it { project_deploy_keys_path(@project).should be_denied_for @u2 } - it { project_deploy_keys_path(@project).should be_denied_for :user } - it { project_deploy_keys_path(@project).should be_denied_for :visitor } + subject { project_deploy_keys_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_denied_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/issues" do - it { project_issues_path(@project).should be_allowed_for @u1 } - it { project_issues_path(@project).should be_allowed_for @u3 } - it { project_issues_path(@project).should be_denied_for :admin } - it { project_issues_path(@project).should be_denied_for @u2 } - it { project_issues_path(@project).should be_denied_for :user } - it { project_issues_path(@project).should be_denied_for :visitor } + subject { project_issues_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/snippets" do - it { project_snippets_path(@project).should be_allowed_for @u1 } - it { project_snippets_path(@project).should be_allowed_for @u3 } - it { project_snippets_path(@project).should be_denied_for :admin } - it { project_snippets_path(@project).should be_denied_for @u2 } - it { project_snippets_path(@project).should be_denied_for :user } - it { project_snippets_path(@project).should be_denied_for :visitor } + subject { project_snippets_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/merge_requests" do - it { project_merge_requests_path(@project).should be_allowed_for @u1 } - it { project_merge_requests_path(@project).should be_allowed_for @u3 } - it { project_merge_requests_path(@project).should be_denied_for :admin } - it { project_merge_requests_path(@project).should be_denied_for @u2 } - it { project_merge_requests_path(@project).should be_denied_for :user } - it { project_merge_requests_path(@project).should be_denied_for :visitor } + subject { project_merge_requests_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/repository" do - it { project_repository_path(@project).should be_allowed_for @u1 } - it { project_repository_path(@project).should be_allowed_for @u3 } - it { project_repository_path(@project).should be_denied_for :admin } - it { project_repository_path(@project).should be_denied_for @u2 } - it { project_repository_path(@project).should be_denied_for :user } - it { project_repository_path(@project).should be_denied_for :visitor } + subject { project_repository_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/repository/branches" do - it { branches_project_repository_path(@project).should be_allowed_for @u1 } - it { branches_project_repository_path(@project).should be_allowed_for @u3 } - it { branches_project_repository_path(@project).should be_denied_for :admin } - it { branches_project_repository_path(@project).should be_denied_for @u2 } - it { branches_project_repository_path(@project).should be_denied_for :user } - it { branches_project_repository_path(@project).should be_denied_for :visitor } + subject { branches_project_repository_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/repository/tags" do - it { tags_project_repository_path(@project).should be_allowed_for @u1 } - it { tags_project_repository_path(@project).should be_allowed_for @u3 } - it { tags_project_repository_path(@project).should be_denied_for :admin } - it { tags_project_repository_path(@project).should be_denied_for @u2 } - it { tags_project_repository_path(@project).should be_denied_for :user } - it { tags_project_repository_path(@project).should be_denied_for :visitor } + subject { tags_project_repository_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/hooks" do - it { project_hooks_path(@project).should be_allowed_for @u1 } - it { project_hooks_path(@project).should be_allowed_for @u3 } - it { project_hooks_path(@project).should be_denied_for :admin } - it { project_hooks_path(@project).should be_denied_for @u2 } - it { project_hooks_path(@project).should be_denied_for :user } - it { project_hooks_path(@project).should be_denied_for :visitor } + subject { project_hooks_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/files" do - it { files_project_path(@project).should be_allowed_for @u1 } - it { files_project_path(@project).should be_allowed_for @u3 } - it { files_project_path(@project).should be_denied_for :admin } - it { files_project_path(@project).should be_denied_for @u2 } - it { files_project_path(@project).should be_denied_for :user } - it { files_project_path(@project).should be_denied_for :visitor } + subject { files_project_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end end end diff --git a/spec/roles/issue_commonality_spec.rb b/spec/roles/issue_commonality_spec.rb new file mode 100644 index 00000000..77b98b46 --- /dev/null +++ b/spec/roles/issue_commonality_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe Issue, "IssueCommonality" do + let(:issue) { create(:issue) } + + describe "Associations" do + it { should belong_to(:project) } + it { should belong_to(:author) } + it { should belong_to(:assignee) } + it { should have_many(:notes).dependent(:destroy) } + end + + describe "Validation" do + it { should validate_presence_of(:project_id) } + it { should validate_presence_of(:author_id) } + it { should validate_presence_of(:title) } + it { should ensure_length_of(:title).is_at_least(0).is_at_most(255) } + end + + describe "Scope" do + it { described_class.should respond_to(:opened) } + it { described_class.should respond_to(:closed) } + it { described_class.should respond_to(:assigned) } + end + + it "has an :author_id_of_changes accessor" do + issue.should respond_to(:author_id_of_changes) + issue.should respond_to(:author_id_of_changes=) + end + + describe ".search" do + let!(:searchable_issue) { create(:issue, title: "Searchable issue") } + + it "matches by title" do + described_class.search('able').all.should == [searchable_issue] + end + end + + describe "#today?" do + it "returns true when created today" do + # Avoid timezone differences and just return exactly what we want + Date.stub(:today).and_return(issue.created_at.to_date) + issue.today?.should be_true + end + + it "returns false when not created today" do + Date.stub(:today).and_return(Date.yesterday) + issue.today?.should be_false + end + end + + describe "#new?" do + it "returns true when created today and record hasn't been updated" do + issue.stub(:today?).and_return(true) + issue.new?.should be_true + end + + it "returns false when not created today" do + issue.stub(:today?).and_return(false) + issue.new?.should be_false + end + + it "returns false when record has been updated" do + issue.stub(:today?).and_return(true) + issue.touch + issue.new?.should be_false + end + end +end diff --git a/spec/roles/upvote_spec.rb b/spec/roles/upvote_spec.rb new file mode 100644 index 00000000..24288ada --- /dev/null +++ b/spec/roles/upvote_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe Issue, "Upvote" do + let(:issue) { create(:issue) } + + it "with no notes has a 0/0 score" do + issue.upvotes.should == 0 + end + + it "should recognize non-+1 notes" do + issue.notes << create(:note, note: "No +1 here") + issue.should have(1).note + issue.notes.first.upvote?.should be_false + issue.upvotes.should == 0 + end + + it "should recognize a single +1 note" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.upvotes.should == 1 + end + + it "should recognize multiple +1 notes" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.notes << create(:note, note: "+1 I want this") + issue.upvotes.should == 2 + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5c0bb618..d381b3f1 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,7 @@ -require 'simplecov' -SimpleCov.start 'rails' +unless ENV['CI'] + require 'simplecov' + SimpleCov.start 'rails' +end # This file is copied to spec/ when you run 'rails generate rspec:install' ENV["RAILS_ENV"] ||= 'test' @@ -7,10 +9,7 @@ require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' require 'capybara/rails' require 'capybara/rspec' -require 'capybara/dsl' require 'webmock/rspec' -require 'factories' -require 'monkeypatch' require 'email_spec' require 'headless' @@ -21,10 +20,14 @@ Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} # Use capybara-webkit Capybara.javascript_driver = :webkit +WebMock.disable_net_connect!(allow_localhost: true) + RSpec.configure do |config| config.mock_with :rspec - config.include LoginMacros + config.include LoginHelpers, type: :request + config.include GitoliteStub + config.include FactoryGirl::Syntax::Methods # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false @@ -36,35 +39,11 @@ RSpec.configure do |config| headless.start end - config.before :each, type: :integration do - DeviseSessionMock.disable - end - config.before do - if example.metadata[:js] - DatabaseCleaner.strategy = :truncation - Capybara::Selenium::Driver::DEFAULT_OPTIONS[:resynchronize] = true - else - DatabaseCleaner.strategy = :transaction - end - - DatabaseCleaner.start - - WebMock.disable_net_connect!(allow_localhost: true) + stub_gitolite! # !!! Observers disabled by default in tests - # - # Use next code to enable observers - # before(:each) { ActiveRecord::Base.observers.enable(:all) } - # - ActiveRecord::Base.observers.disable :all + ActiveRecord::Base.observers.disable(:all) + # ActiveRecord::Base.observers.enable(:all) end - - config.after do - DatabaseCleaner.clean - end - - config.include RSpec::Rails::RequestExampleGroup, type: :request, example_group: { - file_path: /spec\/api/ - } end diff --git a/spec/support/api.rb b/spec/support/api.rb deleted file mode 100644 index d363d8b9..00000000 --- a/spec/support/api.rb +++ /dev/null @@ -1,7 +0,0 @@ -def api_prefix - "/api/#{Gitlab::API::VERSION}" -end - -def json_response - JSON.parse(response.body) -end diff --git a/spec/support/api_helpers.rb b/spec/support/api_helpers.rb new file mode 100644 index 00000000..7d901197 --- /dev/null +++ b/spec/support/api_helpers.rb @@ -0,0 +1,34 @@ +module ApiHelpers + # Public: Prepend a request path with the path to the API + # + # path - Path to append + # user - User object - If provided, automatically appends private_token query + # string for authenticated requests + # + # Examples + # + # >> api('/issues') + # => "/api/v2/issues" + # + # >> api('/issues', User.last) + # => "/api/v2/issues?private_token=..." + # + # >> api('/issues?foo=bar', User.last) + # => "/api/v2/issues?foo=bar&private_token=..." + # + # Returns the relative path to the requested API resource + def api(path, user = nil) + "/api/#{Gitlab::API::VERSION}#{path}" + + + # Normalize query string + (path.index('?') ? '' : '?') + + + # Append private_token if given a User object + (user.respond_to?(:private_token) ? + "&private_token=#{user.private_token}" : "") + end + + def json_response + JSON.parse(response.body) + end +end diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb new file mode 100644 index 00000000..f1e072aa --- /dev/null +++ b/spec/support/db_cleaner.rb @@ -0,0 +1,18 @@ +require 'database_cleaner' + +RSpec.configure do |config| + config.before do + if example.metadata[:js] + DatabaseCleaner.strategy = :truncation + Capybara::Selenium::Driver::DEFAULT_OPTIONS[:resynchronize] = true + else + DatabaseCleaner.strategy = :transaction + end + + DatabaseCleaner.start + end + + config.after do + DatabaseCleaner.clean + end +end diff --git a/spec/support/gitolite_stub.rb b/spec/support/gitolite_stub.rb new file mode 100644 index 00000000..2a907f99 --- /dev/null +++ b/spec/support/gitolite_stub.rb @@ -0,0 +1,35 @@ +module GitoliteStub + def stub_gitolite! + stub_gitlab_gitolite + stub_gitolite_admin + end + + def stub_gitolite_admin + gitolite_repo = mock( + clean_permissions: true, + add_permission: true + ) + + gitolite_config = mock( + add_repo: true, + get_repo: gitolite_repo, + has_repo?: true + ) + + gitolite_admin = double( + 'Gitolite::GitoliteAdmin', + config: gitolite_config, + save: true, + ) + + Gitolite::GitoliteAdmin.stub(new: gitolite_admin) + + end + + def stub_gitlab_gitolite + gitlab_gitolite = Gitlab::Gitolite.new + Gitlab::Gitolite.stub(new: gitlab_gitolite) + gitlab_gitolite.stub(configure: ->() { yield(self) }) + gitlab_gitolite.stub(update_keys: true) + end +end diff --git a/spec/support/js_patch.rb b/spec/support/js_patch.rb deleted file mode 100644 index 0d4ab264..00000000 --- a/spec/support/js_patch.rb +++ /dev/null @@ -1,6 +0,0 @@ -module JsPatch - def confirm_js_popup - page.evaluate_script("window.alert = function(msg) { return true; }") - page.evaluate_script("window.confirm = function(msg) { return true; }") - end -end diff --git a/spec/support/login.rb b/spec/support/login.rb deleted file mode 100644 index 78a907ba..00000000 --- a/spec/support/login.rb +++ /dev/null @@ -1,30 +0,0 @@ -module LoginMacros - def login_as role - @user = User.create(email: "user#{User.count}@mail.com", - name: "John Smith", - password: "123456", - password_confirmation: "123456", - skype: 'user_skype') - - if role == :admin - @user.admin = true - @user.save! - end - - visit new_user_session_path - fill_in "user_email", with: @user.email - fill_in "user_password", with: "123456" - click_button "Sign in" - end - - def login_with(user) - visit new_user_session_path - fill_in "user_email", with: user.email - fill_in "user_password", with: "123456" - click_button "Sign in" - end - - def logout - click_link "Logout" rescue nil - end -end diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb new file mode 100644 index 00000000..769034e2 --- /dev/null +++ b/spec/support/login_helpers.rb @@ -0,0 +1,23 @@ +module LoginHelpers + # Internal: Create and log in as a user of the specified role + # + # role - User role (e.g., :admin, :user) + def login_as(role) + @user = Factory(role) + login_with(@user) + end + + # Internal: Login as the specified user + # + # user - User instance to login with + def login_with(user) + visit new_user_session_path + fill_in "user_email", with: user.email + fill_in "user_password", with: "123456" + click_button "Sign in" + end + + def logout + click_link "Logout" rescue nil + end +end diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb index e0672166..cb1dcba3 100644 --- a/spec/support/matchers.rb +++ b/spec/support/matchers.rb @@ -28,6 +28,16 @@ RSpec::Matchers.define :be_404_for do |user| end end +RSpec::Matchers.define :include_module do |expected| + match do + described_class.included_modules.include?(expected) + end + + failure_message_for_should do + "expected #{described_class} to include the #{expected} module" + end +end + module UrlAccess def url_allowed?(user, url) emulate_user(user) @@ -57,3 +67,17 @@ module UrlAccess login_with(user) if user end end + +# Extend shoulda-matchers +module Shoulda::Matchers::ActiveModel + class EnsureLengthOfMatcher + # Shortcut for is_at_least and is_at_most + def is_within(range) + if range.exclude_end? + is_at_least(range.first) && is_at_most(range.last - 1) + else + is_at_least(range.first) && is_at_most(range.last) + end + end + end +end diff --git a/spec/support/shared_examples.rb b/spec/support/shared_examples.rb deleted file mode 100644 index 9fd207d0..00000000 --- a/spec/support/shared_examples.rb +++ /dev/null @@ -1,16 +0,0 @@ -shared_examples_for :project_side_pane do - subject { page } - it { should have_content((@project || project).name) } - it { should have_content("Commits") } - it { should have_content("Files") } -end - -shared_examples_for :tree_view do - subject { page } - - it "should have Tree View of project" do - should have_content("app") - should have_content("History") - should have_content("Gemfile") - end -end diff --git a/spec/support/stubbed_repository.rb b/spec/support/stubbed_repository.rb new file mode 100644 index 00000000..90491e43 --- /dev/null +++ b/spec/support/stubbed_repository.rb @@ -0,0 +1,31 @@ +# Stubs out all Git repository access done by models so that specs can run +# against fake repositories without Grit complaining that they don't exist. +module StubbedRepository + def path_to_repo + if new_record? || path == 'newproject' + # There are a couple Project specs and features that expect the Project's + # path to be in the returned path, so let's patronize them. + File.join(Rails.root, 'tmp', 'repositories', path) + else + # For everything else, just give it the path to one of our real seeded + # repos. + File.join(Rails.root, 'tmp', 'repositories', 'gitlabhq') + end + end + + def satellite + FakeSatellite.new + end + + class FakeSatellite + def exists? + true + end + + def create + true + end + end +end + +Project.send(:include, StubbedRepository)