summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rails_footnotes3
-rw-r--r--CHANGELOG24
-rw-r--r--Gemfile10
-rw-r--r--Gemfile.lock23
-rw-r--r--README.md1
-rw-r--r--VERSION2
-rw-r--r--app/assets/images/file_dir.pngbin517 -> 1648 bytes
-rw-r--r--app/assets/images/merge.pngbin0 -> 593 bytes
-rw-r--r--app/assets/javascripts/application.js22
-rw-r--r--app/assets/javascripts/issues.js2
-rw-r--r--app/assets/javascripts/note.js261
-rw-r--r--app/assets/javascripts/projects.js4
-rw-r--r--app/assets/stylesheets/common.scss334
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap.scss818
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/blocks.scss145
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/buttons.scss105
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/common.scss52
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/files.scss156
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/lists.scss30
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/tables.scss41
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/typography.scss71
-rw-r--r--app/assets/stylesheets/main.scss45
-rw-r--r--app/assets/stylesheets/projects.css.scss385
-rw-r--r--app/assets/stylesheets/ref_select.scss4
-rw-r--r--app/assets/stylesheets/sections/commits.scss20
-rw-r--r--app/assets/stylesheets/sections/graph.scss6
-rw-r--r--app/assets/stylesheets/sections/issues.scss13
-rw-r--r--app/assets/stylesheets/sections/merge_requests.scss57
-rw-r--r--app/assets/stylesheets/sections/nav.scss10
-rw-r--r--app/assets/stylesheets/sections/notes.scss73
-rw-r--r--app/assets/stylesheets/sections/projects.scss75
-rw-r--r--app/assets/stylesheets/sections/tree.scss6
-rw-r--r--app/assets/stylesheets/themes/ui_mars.scss8
-rw-r--r--app/contexts/merge_requests_load.rb8
-rw-r--r--app/controllers/application_controller.rb4
-rw-r--r--app/controllers/issues_controller.rb16
-rw-r--r--app/controllers/labels_controller.rb25
-rw-r--r--app/controllers/merge_requests_controller.rb2
-rw-r--r--app/controllers/milestones_controller.rb4
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb3
-rw-r--r--app/controllers/team_members_controller.rb1
-rw-r--r--app/decorators/application_decorator.rb2
-rw-r--r--app/helpers/application_helper.rb26
-rw-r--r--app/helpers/gitlab_markdown_helper.rb54
-rw-r--r--app/mailers/notify.rb77
-rw-r--r--app/models/key.rb8
-rw-r--r--app/models/merge_request.rb7
-rw-r--r--app/models/milestone.rb14
-rw-r--r--app/models/project.rb10
-rw-r--r--app/models/protected_branch.rb4
-rw-r--r--app/models/user.rb34
-rw-r--r--app/models/users_project.rb6
-rw-r--r--app/observers/issue_observer.rb12
-rw-r--r--app/observers/key_observer.rb7
-rw-r--r--app/observers/users_project_observer.rb9
-rw-r--r--app/roles/git_host.rb5
-rw-r--r--app/roles/git_merge.rb2
-rw-r--r--app/roles/push_observer.rb (renamed from app/roles/project_push.rb)2
-rw-r--r--app/roles/repository.rb31
-rw-r--r--app/roles/ssh_key.rb18
-rw-r--r--app/views/admin/dashboard/index.html.haml8
-rw-r--r--app/views/admin/hooks/index.html.haml2
-rw-r--r--app/views/admin/projects/_form.html.haml20
-rw-r--r--app/views/admin/projects/_new_form.html.haml29
-rw-r--r--app/views/admin/projects/index.html.haml4
-rw-r--r--app/views/admin/projects/new.html.haml15
-rw-r--r--app/views/admin/users/_form.html.haml122
-rw-r--r--app/views/admin/users/edit.html.haml2
-rw-r--r--app/views/admin/users/index.html.haml4
-rw-r--r--app/views/admin/users/new.html.haml4
-rw-r--r--app/views/commits/_commit.html.haml4
-rw-r--r--app/views/commits/compare.html.haml2
-rw-r--r--app/views/commits/show.html.haml10
-rw-r--r--app/views/dashboard/index.html.haml28
-rw-r--r--app/views/errors/gitolite.html.haml6
-rw-r--r--app/views/errors/invalid_ssh_key.html.haml3
-rw-r--r--app/views/events/_event_last_push.html.haml2
-rw-r--r--app/views/help/api.html.haml22
-rw-r--r--app/views/help/index.html.haml3
-rw-r--r--app/views/help/markdown.html.haml114
-rw-r--r--app/views/help/permissions.html.haml4
-rw-r--r--app/views/help/ssh.html.haml25
-rw-r--r--app/views/help/system_hooks.html.haml4
-rw-r--r--app/views/help/web_hooks.html.haml8
-rw-r--r--app/views/help/workflow.html.haml13
-rw-r--r--app/views/hooks/_data_ex.html.erb2
-rw-r--r--app/views/hooks/index.html.haml2
-rw-r--r--app/views/issues/_form.html.haml13
-rw-r--r--app/views/issues/_head.html.haml3
-rw-r--r--app/views/issues/index.html.haml2
-rw-r--r--app/views/keys/_form.html.haml12
-rw-r--r--app/views/keys/index.html.haml2
-rw-r--r--app/views/keys/new.html.haml2
-rw-r--r--app/views/labels/_label.html.haml4
-rw-r--r--app/views/labels/index.html.haml14
-rw-r--r--app/views/merge_requests/_form.html.haml19
-rw-r--r--app/views/merge_requests/index.html.haml18
-rw-r--r--app/views/merge_requests/show/_how_to_merge.html.haml11
-rw-r--r--app/views/merge_requests/show/_mr_title.html.haml4
-rw-r--r--app/views/milestones/_form.html.haml16
-rw-r--r--app/views/milestones/index.html.haml8
-rw-r--r--app/views/notes/_create_common.js.haml9
-rw-r--r--app/views/notes/_create_line.js.haml2
-rw-r--r--app/views/notes/_form.html.haml69
-rw-r--r--app/views/notes/_per_line_form.html.haml51
-rw-r--r--app/views/notify/issue_status_changed_email.html.haml16
-rw-r--r--app/views/notify/project_access_granted_email.html.haml14
-rw-r--r--app/views/profile/password.html.haml2
-rw-r--r--app/views/profile/show.html.haml9
-rw-r--r--app/views/projects/_clone_panel.html.haml21
-rw-r--r--app/views/projects/_form.html.haml13
-rw-r--r--app/views/projects/_new_form.html.haml6
-rw-r--r--app/views/projects/empty.html.haml86
-rw-r--r--app/views/projects/new.html.haml6
-rw-r--r--app/views/projects/show.html.haml29
-rw-r--r--app/views/refs/_tree_item.html.haml2
-rw-r--r--app/views/search/show.html.haml4
-rw-r--r--app/views/shared/_no_ssh.html.haml8
-rw-r--r--app/views/team_members/_show.html.haml2
-rw-r--r--app/views/team_members/show.html.haml2
-rw-r--r--app/views/wikis/_form.html.haml9
-rw-r--r--config/application.rb2
-rw-r--r--config/environment.rb2
-rw-r--r--config/gitlab.yml.example14
-rw-r--r--config/initializers/1_settings.rb8
-rw-r--r--config/initializers/5_backend.rb5
-rw-r--r--config/initializers/rails_footnotes.rb3
-rw-r--r--config/routes.rb3
-rw-r--r--db/fixtures/test/001_repo.rb36
-rw-r--r--db/migrate/20120729131232_add_extern_auth_provider_to_users.rb8
-rw-r--r--db/pkey.example3
-rw-r--r--db/schema.rb5
-rw-r--r--doc/api/README.md2
-rw-r--r--doc/api/milestones.md57
-rw-r--r--doc/api/projects.md102
-rw-r--r--doc/api/snippets.md100
-rw-r--r--doc/development.md45
-rw-r--r--doc/installation.md103
-rw-r--r--features/profile/ssh_keys.feature4
-rw-r--r--features/projects/issues/labels.feature13
-rw-r--r--features/projects/network.feature4
-rw-r--r--features/step_definitions/dashboard_steps.rb24
-rw-r--r--features/step_definitions/profile/profile_keys_steps.rb2
-rw-r--r--features/step_definitions/project/project_issues_steps.rb21
-rw-r--r--features/step_definitions/project/projects_steps.rb11
-rw-r--r--features/support/env.rb23
-rwxr-xr-xgitlab75
-rw-r--r--lib/api.rb1
-rw-r--r--lib/api/issues.rb5
-rw-r--r--lib/api/milestones.rb80
-rw-r--r--lib/gitlab/backend/gitolite.rb (renamed from lib/gitlab/gitolite.rb)111
-rw-r--r--lib/gitlab/backend/grack_auth.rb (renamed from config/initializers/grack_auth.rb)10
-rw-r--r--lib/gitlab/git_host.rb17
-rw-r--r--lib/gitlab/markdown.rb19
-rwxr-xr-xlib/hooks/post-receive (renamed from lib/post-receive-hook)0
-rw-r--r--lib/support/init-gitlab54
-rw-r--r--lib/support/nginx-gitlab33
-rw-r--r--lib/tasks/bulk_add_permission.rake26
-rw-r--r--lib/tasks/gitlab/backup.rake3
-rw-r--r--lib/tasks/gitlab/enable_automerge.rake4
-rw-r--r--lib/tasks/gitlab/gitolite_rebuild.rake2
-rw-r--r--lib/tasks/gitlab/setup.rake6
-rw-r--r--lib/tasks/gitlab/status.rake22
-rw-r--r--lib/tasks/gitlab/update_hooks.rake19
-rw-r--r--lib/tasks/gitlab/write_hook.rake23
-rwxr-xr-xresque_dev.sh3
-rw-r--r--spec/factories.rb188
-rw-r--r--spec/factories_spec.rb91
-rw-r--r--spec/factory.rb29
-rw-r--r--spec/helpers/application_helper_spec.rb26
-rw-r--r--spec/helpers/gitlab_flavored_markdown_spec.rb4
-rw-r--r--spec/mailers/notify_spec.rb49
-rw-r--r--spec/models/event_spec.rb27
-rw-r--r--spec/models/issue_spec.rb91
-rw-r--r--spec/models/key_spec.rb36
-rw-r--r--spec/models/merge_request_spec.rb81
-rw-r--r--spec/models/milestone_spec.rb48
-rw-r--r--spec/models/note_spec.rb39
-rw-r--r--spec/models/project_spec.rb70
-rw-r--r--spec/models/protected_branch_spec.rb31
-rw-r--r--spec/models/snippet_spec.rb26
-rw-r--r--spec/models/system_hook_spec.rb11
-rw-r--r--spec/models/user_spec.rb69
-rw-r--r--spec/models/users_project_spec.rb16
-rw-r--r--spec/models/web_hook_spec.rb11
-rw-r--r--spec/models/wiki_spec.rb18
-rw-r--r--spec/monkeypatch.rb51
-rw-r--r--spec/observers/activity_observer_spec.rb (renamed from spec/models/activity_observer_spec.rb)0
-rw-r--r--spec/observers/issue_observer_spec.rb (renamed from spec/models/issue_observer_spec.rb)69
-rw-r--r--spec/observers/key_observer_spec.rb34
-rw-r--r--spec/observers/user_observer_spec.rb (renamed from spec/models/user_observer_spec.rb)0
-rw-r--r--spec/observers/users_project_observer_spec.rb40
-rw-r--r--spec/requests/admin/admin_projects_spec.rb2
-rw-r--r--spec/requests/admin/security_spec.rb24
-rw-r--r--spec/requests/api/issues_spec.rb (renamed from spec/api/issues_spec.rb)19
-rw-r--r--spec/requests/api/milestones_spec.rb47
-rw-r--r--spec/requests/api/projects_spec.rb (renamed from spec/api/projects_spec.rb)37
-rw-r--r--spec/requests/api/users_spec.rb (renamed from spec/api/users_spec.rb)10
-rw-r--r--spec/requests/atom/dashboard_issues_spec.rb8
-rw-r--r--spec/requests/projects_deploy_keys_spec.rb8
-rw-r--r--spec/requests/security/profile_access_spec.rb30
-rw-r--r--spec/requests/security/project_access_spec.rb230
-rw-r--r--spec/roles/issue_commonality_spec.rb69
-rw-r--r--spec/roles/upvote_spec.rb27
-rw-r--r--spec/spec_helper.rb45
-rw-r--r--spec/support/api.rb7
-rw-r--r--spec/support/api_helpers.rb34
-rw-r--r--spec/support/db_cleaner.rb18
-rw-r--r--spec/support/gitolite_stub.rb35
-rw-r--r--spec/support/js_patch.rb6
-rw-r--r--spec/support/login.rb30
-rw-r--r--spec/support/login_helpers.rb23
-rw-r--r--spec/support/matchers.rb24
-rw-r--r--spec/support/shared_examples.rb16
-rw-r--r--spec/support/stubbed_repository.rb31
215 files changed, 3838 insertions, 3357 deletions
diff --git a/.rails_footnotes b/.rails_footnotes
deleted file mode 100644
index 1019a70aa1b..00000000000
--- 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 80b68b0ae21..7322efbf228 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 e8b0b244d1e..b0724fadf5b 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 b23bc47c1d2..f226c931bff 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 26ed209ef87..122cd984b4a 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 3f80e49444f..a564e65383b 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 97b0539393d..ea277bb14db 100644
--- a/app/assets/images/file_dir.png
+++ b/app/assets/images/file_dir.png
Binary files differ
diff --git a/app/assets/images/merge.png b/app/assets/images/merge.png
new file mode 100644
index 00000000000..4a6bb2e154d
--- /dev/null
+++ b/app/assets/images/merge.png
Binary files differ
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index d9cbd5d65b3..f69fd6f9a44 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 bc0569654e1..aae818deefc 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 d9ae45d93c7..9cd3e36e87b 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,
-
-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 new notes every n seconds
- this.initRefresh();
-
- $('.delete-note').live('ajax:success', function() {
- $(this).closest('li').fadeOut(); });
-
- $("#new_note").live("ajax:before", function(){
- $(".submit_note").attr("disabled", "disabled");
- })
-
- $("#new_note").live("ajax:complete", function(){
- $(".submit_note").removeAttr("disabled");
- })
-
- $("#note_note").live("focus", function(){
- $(this).css("height", "80px");
- $('.note_advanced_opts').show();
- });
-
- $("#note_attachment").change(function(e){
+ 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;
+
+ // get notes
+ this.getContent();
+
+ // get new notes every n seconds
+ this.initRefresh();
+
+ $('.delete-note').live('ajax:success', function() {
+ $(this).closest('li').fadeOut(); });
+
+ $(".note-form-holder").live("ajax:before", function(){
+ $(".submit_note").attr("disabled", "disabled");
+ })
+
+ $(".note-form-holder").live("ajax:complete", function(){
+ $(".submit_note").removeAttr("disabled");
+ })
+
+ disableButtonIfEmtpyField(".note-text", ".submit_note");
+
+ $(".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'
- *
- */
-
-
-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);
- }
- },
-
+ },
+
+
+ /**
+ * 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);
+ }
+ },
-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 842726981d3..be1b75b90a7 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 6103d05c3ce..aa27a280a18 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 550046d04fd..00000000000
--- 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 00000000000..894cb3044fa
--- /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 00000000000..c838f3b2368
--- /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 00000000000..cd7145c99c3
--- /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 00000000000..4ea5ed02ada
--- /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 00000000000..402ba04bd00
--- /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 00000000000..f78b1dee4fa
--- /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 00000000000..97e85492875
--- /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 ad8be0bc58d..be27d754dfa 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 4bdf5dee2f5..00000000000
--- 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 6f6a1bc983d..5b52e11b355 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 e2db701db71..75e38aeea93 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 33d91de5391..2aa4463e45e 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 1b61ec3fe85..230a7aea1da 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 ec84a64e23a..73171915fcc 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 4a7736443af..097e819732d 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 30587ef5b63..6a965fa47b9 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 8c79e45e703..721b569d446 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 2663fc9a3ce..891f5e20871 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 2808ad32397..c630f388945 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 6778db3bce5..e2f68e3805b 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 7c1941ec859..9aab250dbd4 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 889a7d98033..a47b38435f2 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 00000000000..e703f822982
--- /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 4938a294f60..187bb407b2d 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 7acb3781fbb..10f089f138b 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 d19931e93d7..d472936b4b4 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 0cc58c3ec16..0846f096554 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 7bc88648c77..3023699e700 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 51569b0635e..3dafb7534c3 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 24bc3e85b9a..9da695b5d23 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{<pre>.*?</pre>|<code>.*?</code>}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.
+ # "<a>outer text <a>gfm ref</a> more outer text</a>"). This will not be
+ # interpreted as intended. Browsers will parse something like
+ # "<a>outer text </a><a>gfm ref</a> 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.
+ # "<a>outer text </a><a>gfm ref</a><a> more outer text</a>").
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
- })
-
- @__renderer.render(text).html_safe
+ 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
+
+ @markdown.render(text).html_safe
end
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 9563fdbca22..91136fee95e 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
+ # => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
+ # >> 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 cfcb1f63c26..a39a4a16c22 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 47966d669f6..542817b0eea 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 592c57f948c..d416fb630c5 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 714953c64c7..a7735a42137 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 4da1432c1a9..7c30f7a0b6d 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 92c81c83d41..ad6af6a6dd0 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 36e6d9045b6..7d1729343bb 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 92b0f8368cb..62fd9bf8ac9 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 fac53a67925..a3f17bdec92 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 00000000000..763b2c87f63
--- /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 00000000000..aa620f77ea4
--- /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 95e5942fb00..00000000000
--- 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
index 02025384e48..1067404d5af 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 8d5b018de77..5fa950db6d6 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 5e1d2c23d15..00000000000
--- 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 2e67abfc16d..3e26f566a2e 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 d34acffe9e6..43288424e8e 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 7cebddf2890..87d212e5710 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 00000000000..d793b6f3aff
--- /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 4512bb7e493..f7dd486ddac 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 ac6526bfa4b..933cb671142 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 &amp; 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 5e68a23f494..7010c2727c6 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
-
- -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
+ .span7
+ .ui-box
+ %br
.clearfix
- = f.label :password
- .input= f.password_field :password, disabled: f.object.force_random_password
+ = f.label :name
+ .input
+ = f.text_field :name
+ %span.help-inline * required
.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 :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
- .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 &amp; will not be able to login to GitLab.
+ = 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 &amp; 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"
+
+ .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 0e94be9e12c..032e3cfaa99 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} &rarr; Edit user
%hr
= render 'form'
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index 5edca312cc5..f21baabf8eb 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 87d6b0f2757..70ead0d3f7d 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 686a4337b3a..61371d14c5f 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
- &ndash;
+ %strong.commit-author-name= commit.author_name
+ %span.dash &ndash;
= 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 be915cd1038..7dab1f5c0fa 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 9a483aa2a9a..e01f8ea5878 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 9fea5acb7cd..e13640fb835 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
- &rarr;
+ %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
+ &rarr;
+ %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 4788c2e5237..d5f51951d5e 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 00000000000..fb7922b0ea3
--- /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 4ef927495ae..66e149365f7 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 344f8ade755..d7d7aed4232 100644
--- a/app/views/help/api.html.haml
+++ b/app/views/help/api.html.haml
@@ -10,6 +10,8 @@
%li
%a{href: "#projects"} Projects
%li
+ %a{href: "#snippets"} Snippets
+ %li
%a{href: "#users"} Users
%li
%a{href: "#issues"} Issues
@@ -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 66f7c722d67..02549577282 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 8d6fb2a590f..6a4bbb02c6c 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
&larr; 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 <em>part</em> 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 <code>```</code> 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 7511d15d995..f9287fa0996 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
&larr; to index
%hr
diff --git a/app/views/help/ssh.html.haml b/app/views/help/ssh.html.haml
new file mode 100644
index 00000000000..6a5812040e7
--- /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
+ &larr; 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 2088208ad47..9fc8cbabf17 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
&larr; 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 3acea62cf90..263eadf6583 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
&larr; 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 7db8133b7f3..a3fe3b01d46 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
&larr; 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 8d3de3f0bf2..e43714e965b 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 4e15dc50ee8..3d2a381e746 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 1b67eabd5a5..db7920b993d 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 1f6e7d7f81f..8ebe3e057bc 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 a6836fd4fd1..010b8856d65 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 ee2eafddf3b..26700803e61 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]
+ .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: "primary btn"
- = link_to "Cancel", keys_path, class: "btn"
+ = 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 04e9e4cbc33..9b5663ed93e 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 02e782b9f85..fff3805890e 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 00000000000..32158c20adc
--- /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 00000000000..4e41d375d6a
--- /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 b6c12397d6c..b554c051964 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 4ad6e5c18ce..bbf35dc72d2 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 c21f2727a47..69881d4352f 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 31fa0779fd2..3ae1050d169 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}:"
&nbsp;
- %span.pretty_label.branch= @merge_request.source_branch
+ %span.label_branch= @merge_request.source_branch
&rarr;
- %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 1cd08ac3bcf..41cbd6abcad 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 ecb008dc144..c5333b08fdc 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 847ff383b7c..e80eccb1b4c 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 13809bec1b9..662909f7967 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 dac026bd23d..7211a0ae471 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 afb0b30dca3..8e31b59e9f0 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 00000000000..59130f79d6c
--- /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 00000000000..154c2aaa01e
--- /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 257dacb1ad3..d0aee7ac6b3 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 95cce2bb6b6..22e840a089f 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 00000000000..839a98a0d79
--- /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 ce66b2cf930..8bdeda1cafe 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 5104df83a2c..e6d5e93fca7 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 b8d0dad973c..d408c0a64ae 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 &rarr; keys and add public key of every machine you want to use for work with gitlabhq.
+= 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
+ %h4 Git global setup:
+ %pre.dark
+ = preserve do
+ git config --global user.name "#{current_user.name}"
+ git config --global user.email "#{current_user.email}"
-.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.
+ %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
-- bash_lexer = Pygments::Lexer[:bash]
-%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.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
- %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")
+ - if can? current_user, :admin_project, @project
+ .prepend-top-20
+ = link_to 'Remove project', @project, confirm: 'Are you sure?', method: :delete, class: "btn danger right"
- = 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)
- - 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"
+: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 703e558ae41..933cb671142 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 &amp; repository. Please wait a few minutes
+ = image_tag "ajax_loader.gif"
+ %h3 Creating project &amp; 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 ebd2c8e4495..77a0ef1ac4d 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 2e6bbf6221b..d4c4ee8de49 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 ed9da1f0753..d37ef670318 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 00000000000..b6ab666bc5d
--- /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 f47554c1dad..2dc4fb652dd 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 d7e09bced63..6cb357cddfc 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 6b6411be55b..12b57e032a4 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 ecd88b15d15..ad41f19657f 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 c880a7adc42..3b186a9d57a 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 1818f2c0d01..d05cc1bead6 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 5c5987a8857..27c5bc2270c 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 00000000000..85f747ac334
--- /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 afe6f3ad383..00000000000
--- 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 04e13bc4a86..f895478fb12 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 ebf005a1467..67d4e7bfbde 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)
-
-# Create dir for test repo
-repo_dir = File.join(Rails.root, "tmp", "tests")
-Dir.mkdir(repo_dir) unless File.exists?(repo_dir)
-
-`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}"
+require 'fileutils'
+
+print "Unpacking seed repository..."
+
+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 00000000000..d5e66ba4d3b
--- /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 ae045772430..00000000000
--- 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 c4c54f562a3..46461e44aad 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 e01119661f0..53b4983ef47 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 00000000000..f68d8eb7d58
--- /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 ead31003512..d680b5d8597 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 00000000000..0cd29ce530b
--- /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 00000000000..55be2bc32f2
--- /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 b6f1869b761..b3fe92bf4c6 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
-
- 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;
+Add gitlab to nginx sites & change with your host specific settings
- proxy_pass http://gitlab;
- }
- }
+ 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
-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 c3a92f30ee0..c81503ed2ba 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 00000000000..5a20bfd6d14
--- /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 9655184cfe1..61c05eb367e 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 90c3a69cfb8..a4edd224dc0 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 5ab7e0480ad..25926c53f97 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 00a1721f8de..27de03d5489 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 c9af346e7cf..d981e1f3802 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 496f23f9894..5357815201a 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 00000000000..acafb3f10c4
--- /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 3ff3b3836f4..be04701c25d 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 2abc20ad34e..836c2818544 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 00000000000..f537b8e5bf2
--- /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
index e82f9e62307..b69f4663c35 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
- # create tmp dir
- @local_dir = File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}")
- Dir.mkdir @local_dir
+ def update_repository project
+ configure do |c|
+ c.update_project(project.path, project)
+ end
+ end
+
+ alias_method :create_repository, :update_repository
- `git clone #{GitHost.admin_uri} #{@local_dir}/gitolite`
+ def remove_repository project
+ configure do |c|
+ c.destroy_project(project)
+ end
end
- def push
- Dir.chdir(File.join(@local_dir, "gitolite"))
- `git add -A`
- `git commit -am "Gitlab"`
- `git push`
- Dir.chdir(Rails.root)
+ def url_to_repo path
+ Gitlab.config.ssh_path + "#{path}.git"
+ end
- FileUtils.rm_rf(@local_dir)
+ def initialize
+ # create tmp dir
+ @local_dir = File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}")
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
index 5995b873de9..4f77c327373 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 76b2c7b19e4..00000000000
--- 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 d3daed91c32..75fa835d502 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
index d38bd13e19d..d38bd13e19d 100755
--- a/lib/post-receive-hook
+++ b/lib/hooks/post-receive
diff --git a/lib/support/init-gitlab b/lib/support/init-gitlab
new file mode 100644
index 00000000000..f146e80f4a8
--- /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 00000000000..fa15d2018c4
--- /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 00000000000..55797825e0c
--- /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 d9053c232cd..04d240f6b12 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 07f80586c60..0a1a0fa7350 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 5ab176060fb..534aa315631 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 d60e73e9ac3..49c86461c0b 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 bc4e86ea648..e5b5e122a18 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 44e1617e58f..00000000000
--- 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 00000000000..9ec9c8383e2
--- /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 b09cfd9e383..0f1d6edb41f 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 ab2ca4687da..2e4acf39461 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'
-end
+module Factory
+ def self.create(type, *args)
+ FactoryGirl.create(type, *args)
+ 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'
+ def self.new(type, *args)
+ FactoryGirl.build(type, *args)
+ end
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
+FactoryGirl.define do
+ sequence :sentence, aliases: [:title, :content] do
+ Faker::Lorem.sentence
+ 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 :name, aliases: [:file_name] do
+ Faker::Name.name
+ end
-Factory.add(:issue, Issue) do |obj|
- obj.title = Faker::Lorem.sentence
- obj.author = Factory :user
- obj.assignee = Factory :user
-end
+ sequence(:url) { Faker::Internet.uri('http') }
-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
+ factory :user, aliases: [:author, :assignee, :owner] do
+ email { Faker::Internet.email }
+ name
+ password "123456"
+ password_confirmation "123456"
-Factory.add(:snippet, Snippet) do |obj|
- obj.title = Faker::Lorem.sentence
- obj.file_name = Faker::Lorem.sentence
- obj.content = Faker::Lorem.sentences
-end
+ trait :admin do
+ admin true
+ end
-Factory.add(:note, Note) do |obj|
- obj.note = Faker::Lorem.sentence
-end
+ factory :admin, traits: [:admin]
+ end
-Factory.add(:key, Key) do |obj|
- obj.title = "Example key"
- obj.key = File.read(File.join(Rails.root, "db", "pkey.example"))
-end
+ factory :project do
+ sequence(:name) { |n| "project#{n}" }
+ path { name }
+ code { name }
+ owner
+ end
-Factory.add(:project_hook, ProjectHook) do |obj|
- obj.url = Faker::Internet.uri("http")
-end
+ factory :users_project do
+ user
+ project
+ end
-Factory.add(:system_hook, SystemHook) do |obj|
- obj.url = Faker::Internet.uri("http")
-end
+ factory :issue do
+ title
+ author
+ project
-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
+ trait :closed do
+ closed true
+ end
-Factory.add(:event, Event) do |obj|
- obj.title = Faker::Lorem.sentence
- obj.project = Factory(:project)
-end
+ factory :closed_issue, traits: [:closed]
+ end
+
+ factory :merge_request do
+ title
+ author
+ project
+ source_branch "master"
+ target_branch "stable"
+ end
+
+ 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.add(:milestone, Milestone) do |obj|
- obj.title = Faker::Lorem.sentence
- obj.due_date = Date.today + 1.month
+ 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 00000000000..5760aad423b
--- /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 1758b4d69d7..00000000000
--- 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 00000000000..9a2df31479c
--- /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 e147cb39375..28bd46ecb99 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 27af1e381c8..cf50b429f23 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 188f09978a7..aaffda3199e 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 e9cbd72589a..69829a4d13d 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 0f9b31778df..85cd291d681 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 c7ad08a1e06..d1253b35ae5 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 880d3f307a3..fa15fc8f560 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 }
-
- it { milestone.should be_valid }
+ let(:milestone) { Factory :milestone }
+ let(:issue) { Factory :issue }
- 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 c97b23cb4fa..ffaf442d9a4 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 5bba4ff6c96..5add7ff88a9 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 1654e3b6f56..9180bc3bca6 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 9b4aaa13f74..ffb861c4910 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 56d76ed08cf..fe2a5836fe7 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 265dcef1e77..ca34f07df7f 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 87fbfbf2a8c..3197ba6eb6b 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 885947614d7..3cba5b64ff0 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 892d0e8fe17..de6ce426331 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 855a31f06de..00000000000
--- 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
index 0db4a9985be..0db4a9985be 100644
--- a/spec/models/activity_observer_spec.rb
+++ b/spec/observers/activity_observer_spec.rb
diff --git a/spec/models/issue_observer_spec.rb b/spec/observers/issue_observer_spec.rb
index c6a405f1c1b..b5943f2c539 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 00000000000..7f2a76a320c
--- /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
index 23dac98bb74..23dac98bb74 100644
--- a/spec/models/user_observer_spec.rb
+++ b/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 00000000000..3e3920407cb
--- /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 0ce66f5f868..2edfb59231d 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 0c369740cff..6306832628b 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
index f6d8e3792c2..293ea83ae6c 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 00000000000..cf5f65f068c
--- /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
index ff45619e58a..0cbc12af53b 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
index 32b9379d212..d791962adc2 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 9b4ffc0e326..79a9b8ef996 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 0fea7b46ce2..894aa6d3a8d 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 b8ed27f031c..9f6fe6a2b50 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 d503cf857b5..0cdf43bf84e 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 00000000000..77b98b46ed9
--- /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 00000000000..24288ada0fe
--- /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 5c0bb618754..d381b3f1e2e 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
- end
-
- config.after do
- DatabaseCleaner.clean
+ ActiveRecord::Base.observers.disable(:all)
+ # ActiveRecord::Base.observers.enable(:all)
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 d363d8b9a57..00000000000
--- 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 00000000000..7d9011971dd
--- /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 00000000000..f1e072aa15f
--- /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 00000000000..2a907f99bc8
--- /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 0d4ab264e85..00000000000
--- 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 78a907bad0b..00000000000
--- 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 00000000000..769034e2286
--- /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 e0672166e92..cb1dcba3dd8 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 9fd207d0db2..00000000000
--- 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 00000000000..90491e430b4
--- /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)