summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--CHANGELOG8
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock13
-rw-r--r--VERSION2
-rw-r--r--app/assets/images/ajax_loader_tree.gifbin0 -> 6531 bytes
-rw-r--r--app/assets/javascripts/application.js22
-rw-r--r--app/assets/javascripts/issues.js21
-rw-r--r--app/assets/javascripts/note.js4
-rw-r--r--app/assets/stylesheets/common.scss6
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap.scss202
-rw-r--r--app/assets/stylesheets/header.scss8
-rw-r--r--app/assets/stylesheets/main.scss8
-rw-r--r--app/assets/stylesheets/notes.scss15
-rw-r--r--app/assets/stylesheets/sections/commits.scss15
-rw-r--r--app/assets/stylesheets/sections/merge_requests.scss12
-rw-r--r--app/assets/stylesheets/sections/tree.scss96
-rw-r--r--app/assets/stylesheets/themes/ui_mars.scss3
-rw-r--r--app/assets/stylesheets/tree.scss232
-rw-r--r--app/contexts/base_context.rb8
-rw-r--r--app/contexts/commit_load.rb26
-rw-r--r--app/contexts/merge_requests_load.rb16
-rw-r--r--app/contexts/notes_load.rb30
-rw-r--r--app/controllers/admin/hooks_controller.rb44
-rw-r--r--app/controllers/admin/mailer_controller.rb45
-rw-r--r--app/controllers/admin/projects_controller.rb4
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/controllers/commits_controller.rb46
-rw-r--r--app/controllers/dashboard_controller.rb8
-rw-r--r--app/controllers/hooks_controller.rb12
-rw-r--r--app/controllers/merge_requests_controller.rb11
-rw-r--r--app/controllers/notes_controller.rb21
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb13
-rw-r--r--app/controllers/refs_controller.rb23
-rw-r--r--app/decorators/event_decorator.rb25
-rw-r--r--app/helpers/application_helper.rb4
-rw-r--r--app/helpers/tree_helper.rb27
-rw-r--r--app/models/commit.rb23
-rw-r--r--app/models/event.rb4
-rw-r--r--app/models/merge_request.rb4
-rw-r--r--app/models/project.rb4
-rw-r--r--app/models/project_hook.rb3
-rw-r--r--app/models/system_hook.rb13
-rw-r--r--app/models/user.rb13
-rw-r--r--app/models/users_project.rb2
-rw-r--r--app/models/web_hook.rb5
-rw-r--r--app/observers/mailer_observer.rb2
-rw-r--r--app/observers/system_hook_observer.rb67
-rw-r--r--app/roles/account.rb4
-rw-r--r--app/roles/git_push.rb6
-rw-r--r--app/views/admin/hooks/_data_ex.html.erb66
-rw-r--r--app/views/admin/hooks/index.html.haml39
-rw-r--r--app/views/admin/logs/show.html.haml15
-rw-r--r--app/views/admin/mailer/preview.html.haml28
-rw-r--r--app/views/admin/projects/index.html.haml6
-rw-r--r--app/views/admin/users/_form.html.haml6
-rw-r--r--app/views/admin/users/index.html.haml4
-rw-r--r--app/views/commits/_commits.html.haml4
-rw-r--r--app/views/commits/_diffs.html.haml10
-rw-r--r--app/views/commits/_head.html.haml4
-rw-r--r--app/views/commits/compare.html.haml2
-rw-r--r--app/views/dashboard/index.atom.builder13
-rw-r--r--app/views/dashboard/index.html.haml7
-rw-r--r--app/views/dashboard/index.js.haml2
-rw-r--r--app/views/events/_event_issue.html.haml4
-rw-r--r--app/views/events/_event_last_push.html.haml5
-rw-r--r--app/views/events/_event_merge_request.html.haml4
-rw-r--r--app/views/events/_event_push.html.haml2
-rw-r--r--app/views/help/api.html.haml41
-rw-r--r--app/views/help/index.html.haml6
-rw-r--r--app/views/help/system_hooks.html.haml13
-rw-r--r--app/views/issues/_issues.html.haml4
-rw-r--r--app/views/issues/_show.html.haml8
-rw-r--r--app/views/issues/index.html.haml4
-rw-r--r--app/views/keys/new.html.haml4
-rw-r--r--app/views/layouts/_project_menu.html.haml4
-rw-r--r--app/views/layouts/admin.html.haml2
-rw-r--r--app/views/layouts/devise_layout.html.haml (renamed from app/views/layouts/devise.html.haml)0
-rw-r--r--app/views/layouts/profile.html.haml9
-rw-r--r--app/views/merge_requests/_form.html.haml20
-rw-r--r--app/views/merge_requests/_merge_request.html.haml10
-rw-r--r--app/views/merge_requests/edit.html.haml2
-rw-r--r--app/views/merge_requests/new.html.haml2
-rw-r--r--app/views/merge_requests/show/_commits.html.haml4
-rw-r--r--app/views/merge_requests/show/_mr_box.html.haml7
-rw-r--r--app/views/notes/_form.html.haml2
-rw-r--r--app/views/notes/_per_line_form.html.haml2
-rw-r--r--app/views/notes/_reply_button.html.haml1
-rw-r--r--app/views/refs/_tree.html.haml29
-rw-r--r--app/views/refs/_tree_commit.html.haml3
-rw-r--r--app/views/refs/_tree_file.html.haml32
-rw-r--r--app/views/refs/_tree_item.html.haml30
-rw-r--r--app/views/refs/blame.html.haml11
-rw-r--r--app/views/refs/logs_tree.js.haml9
-rw-r--r--app/views/refs/tree.js.haml6
-rw-r--r--app/views/snippets/show.html.haml20
-rw-r--r--app/workers/system_hook_worker.rb7
-rw-r--r--config/application.rb2
-rw-r--r--config/gitlab.yml.example2
-rw-r--r--config/initializers/1_settings.rb12
-rw-r--r--config/initializers/devise.rb16
-rw-r--r--config/locales/devise.en.yml8
-rw-r--r--config/routes.rb16
-rw-r--r--db/migrate/20110913200833_devise_create_users.rb46
-rw-r--r--db/migrate/20120706065612_add_lockable_to_users.rb6
-rw-r--r--db/migrate/20120712080407_add_type_to_web_hook.rb5
-rw-r--r--db/schema.rb9
-rw-r--r--doc/installation.md26
-rw-r--r--lib/gitlab/logger.rb20
-rw-r--r--lib/tasks/gitlab/backup.rake190
-rwxr-xr-xresque.sh2
-rwxr-xr-xresque_dev.sh2
-rw-r--r--spec/api/projects_spec.rb2
-rw-r--r--spec/factories.rb12
-rw-r--r--spec/models/merge_request_spec.rb1
-rw-r--r--spec/models/project_hooks_spec.rb28
-rw-r--r--spec/models/project_spec.rb2
-rw-r--r--spec/models/system_hook_spec.rb63
-rw-r--r--spec/models/web_hook_spec.rb20
-rw-r--r--spec/requests/admin/admin_hooks_spec.rb53
-rw-r--r--spec/requests/admin/security_spec.rb8
-rw-r--r--spec/requests/hooks_spec.rb7
-rw-r--r--spec/workers/post_receive_spec.rb4
-rw-r--r--vendor/assets/javascripts/jquery.waitforimages.js144
124 files changed, 1746 insertions, 660 deletions
diff --git a/.gitignore b/.gitignore
index 8c626989bb8..725f289db55 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@ log/*.log
tmp/
.sass-cache/
coverage/*
+backups/*
*.swp
public/uploads/
.rvmrc
diff --git a/CHANGELOG b/CHANGELOG
index 172357603cb..ec5d2f220d3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,13 @@
v 2.7.0
- Issue Labels
+ - Inline diff
+ - Git HTTP
+ - API
+ - UI improved
+ - System hooks
+ - UI improved
+ - Dashboard events endless scroll
+ - Source perfomance increased
v 2.6.0
- UI polished
diff --git a/Gemfile b/Gemfile
index bf2320a80a4..76dc1856424 100644
--- a/Gemfile
+++ b/Gemfile
@@ -7,7 +7,7 @@ gem "sqlite3"
gem "mysql2"
# Auth
-gem "devise", "~> 1.5"
+gem "devise", "~> 2.1.0"
# GITLAB patched libs
gem "grit", :git => "https://github.com/gitlabhq/grit.git", :ref => "7f35cb98ff17d534a07e3ce6ec3d580f67402837"
@@ -71,7 +71,6 @@ group :development, :test do
gem "awesome_print"
gem "database_cleaner"
gem "launchy"
- gem "webmock"
end
group :test do
@@ -82,4 +81,5 @@ group :test do
gem "shoulda-matchers"
gem 'email_spec'
gem 'resque_spec'
+ gem "webmock"
end
diff --git a/Gemfile.lock b/Gemfile.lock
index e6a488f2753..e4c06fed229 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -148,10 +148,11 @@ GEM
nokogiri (>= 1.5.0)
daemons (1.1.8)
database_cleaner (0.8.0)
- devise (1.5.3)
+ devise (2.1.2)
bcrypt-ruby (~> 3.0)
- orm_adapter (~> 0.0.3)
- warden (~> 1.1)
+ orm_adapter (~> 0.1)
+ railties (~> 3.1)
+ warden (~> 1.2.1)
diff-lcs (1.1.3)
drapper (0.8.4)
email_spec (1.2.1)
@@ -225,7 +226,7 @@ GEM
omniauth (1.1.0)
hashie (~> 1.2)
rack
- orm_adapter (0.0.7)
+ orm_adapter (0.3.0)
polyglot (0.3.3)
posix-spawn (0.3.6)
pry (0.9.9.6)
@@ -356,7 +357,7 @@ GEM
raindrops (~> 0.7)
vegas (0.1.11)
rack (>= 1.0.0)
- warden (1.2.0)
+ warden (1.2.1)
rack (>= 1.0)
webmock (1.8.7)
addressable (>= 2.2.7)
@@ -383,7 +384,7 @@ DEPENDENCIES
colored
cucumber-rails
database_cleaner
- devise (~> 1.5)
+ devise (~> 2.1.0)
drapper
email_spec
ffaker
diff --git a/VERSION b/VERSION
index 1daf7c48e6d..24ba9a38de6 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.7.0pre
+2.7.0
diff --git a/app/assets/images/ajax_loader_tree.gif b/app/assets/images/ajax_loader_tree.gif
new file mode 100644
index 00000000000..99d5a0f37f3
--- /dev/null
+++ b/app/assets/images/ajax_loader_tree.gif
Binary files differ
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 42290f8e154..527b5c795e1 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -12,6 +12,7 @@
//= require jquery.cookie
//= require jquery.endless-scroll
//= require jquery.highlight
+//= require jquery.waitforimages
//= require bootstrap-modal
//= require modernizr
//= require chosen-jquery
@@ -20,10 +21,26 @@
//= require_tree .
$(document).ready(function(){
+
$(".one_click_select").live("click", function(){
$(this).select();
});
+
+ $('body').on('ajax:complete, ajax:beforeSend, submit', 'form', function(e){
+ var buttons = $('[type="submit"]', this);
+ switch( e.type ){
+ case 'ajax:beforeSend':
+ case 'submit':
+ buttons.attr('disabled', 'disabled');
+ break;
+ case ' ajax:complete':
+ default:
+ buttons.removeAttr('disabled');
+ break;
+ }
+ })
+
$(".account-box").mouseenter(showMenu);
$(".account-box").mouseleave(resetMenu);
@@ -97,3 +114,8 @@ function showDiff(link) {
return _chosen.apply(this, [default_options]);
}})
})(jQuery);
+
+
+function ajaxGet(url) {
+ $.ajax({type: "GET", url: url, dataType: "script"});
+}
diff --git a/app/assets/javascripts/issues.js b/app/assets/javascripts/issues.js
index 49936e3f0ee..0acf9ec8aef 100644
--- a/app/assets/javascripts/issues.js
+++ b/app/assets/javascripts/issues.js
@@ -73,4 +73,25 @@ function issuesPage(){
$("#milestone_id, #assignee_id, #label_name").on("change", function(){
$(this).closest("form").submit();
});
+
+ $('body').on('ajax:success', '.close_issue, .reopen_issue, #new_issue', function(){
+ var t = $(this),
+ totalIssues,
+ reopen = t.hasClass('reopen_issue'),
+ newIssue = false;
+ if( this.id == 'new_issue' ){
+ newIssue = true;
+ }
+ $('.issue_counter, #new_issue').each(function(){
+ var issue = $(this);
+ totalIssues = parseInt( $(this).html(), 10 );
+
+ if( newIssue || ( reopen && issue.closest('.main_menu').length ) ){
+ $(this).html( totalIssues+1 );
+ }else {
+ $(this).html( totalIssues-1 );
+ }
+ });
+
+ });
}
diff --git a/app/assets/javascripts/note.js b/app/assets/javascripts/note.js
index 4d97ffefdce..c45a45d2fcb 100644
--- a/app/assets/javascripts/note.js
+++ b/app/assets/javascripts/note.js
@@ -25,11 +25,11 @@ init:
$(this).closest('li').fadeOut(); });
$("#new_note").live("ajax:before", function(){
- $("#submit_note").attr("disabled", "disabled");
+ $(".submit_note").attr("disabled", "disabled");
})
$("#new_note").live("ajax:complete", function(){
- $("#submit_note").removeAttr("disabled");
+ $(".submit_note").removeAttr("disabled");
})
$("#note_note").live("focus", function(){
diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss
index 19092073c17..0db803aae23 100644
--- a/app/assets/stylesheets/common.scss
+++ b/app/assets/stylesheets/common.scss
@@ -604,7 +604,11 @@ li.note {
border-style: solid;
border-width: 1px;
@include border-radius(4px);
- min-height:42px;
+ min-height:22px;
+
+ .avatar {
+ width:24px;
+ }
}
.supp_diff_link,
diff --git a/app/assets/stylesheets/gitlab_bootstrap.scss b/app/assets/stylesheets/gitlab_bootstrap.scss
index 1b86cddee39..39e5998305a 100644
--- a/app/assets/stylesheets/gitlab_bootstrap.scss
+++ b/app/assets/stylesheets/gitlab_bootstrap.scss
@@ -202,6 +202,10 @@ a:focus {
color:$style_color;
}
+.nav-tabs > .active > a {
+ font-weight:bold;
+}
+
/** COLORS **/
.cgray { color:gray; }
.cred { color:#D12F19; }
@@ -209,6 +213,7 @@ a:focus {
.cblack { color:#111; }
.cdark { color:#444 }
.cwhite { color:#fff !important }
+.bgred { background: #F2DEDE !important}
/** COMMON STYLES **/
.left {
@@ -299,9 +304,24 @@ table.no-borders {
}
.event_label {
- background: #FCEEC1;
- padding: 2px 2px 0;
- font-family: monospace;
+ @extend .label;
+ background-color: #999;
+
+ &.pushed {
+ background-color: #3A87AD;
+ }
+
+ &.opened {
+ background-color: #468847;
+ }
+
+ &.closed {
+ background-color: #B94A48;
+ }
+
+ &.merged {
+ background-color: #2A2;
+ }
}
img.avatar {
@@ -425,9 +445,10 @@ form {
*/
.ui-box {
background:#F9F9F9;
- margin-bottom: 40px;
+ margin-bottom: 25px;
@include round-borders-all(4px);
border-color: #CCC;
+ @include solid_shade;
ul {
margin:0;
@@ -443,6 +464,13 @@ form {
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;
@@ -511,6 +539,7 @@ form {
table.admin-table {
@extend .table-bordered;
@extend .zebra-striped;
+ @include solid_shade;
th {
border-color: #CCC;
border-bottom: 1px solid #bbb;
@@ -568,6 +597,8 @@ ul.breadcrumb {
@extend .prepend-top-20;
@extend .append-bottom-20;
border-width:1px;
+ @include solid_shade;
+
img { max-width: 100%; }
@@ -624,13 +655,166 @@ p {
h3.page_title {
color:#456;
font-size:20px;
- font-weight: 600;
+ font-weight: normal;
line-height: 28px;
}
-pre.logs {
- .log {
- font-size:12px;
- line-height:18px;
+/**
+ * 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/header.scss b/app/assets/stylesheets/header.scss
index 5e2e410071b..07eba39b275 100644
--- a/app/assets/stylesheets/header.scss
+++ b/app/assets/stylesheets/header.scss
@@ -96,7 +96,7 @@ header {
*/
.search {
float: right;
- margin-right: 55px;
+ margin-right: 50px;
.search-input {
@extend .span2;
@@ -126,10 +126,10 @@ header {
cursor: pointer;
img {
border-radius: 4px;
- right: 0px;
+ right: 5px;
position: absolute;
- width: 33px;
- height: 33px;
+ width: 31px;
+ height: 31px;
display: block;
top: 0;
&:after {
diff --git a/app/assets/stylesheets/main.scss b/app/assets/stylesheets/main.scss
index bff24dd68b9..7d60cd258e3 100644
--- a/app/assets/stylesheets/main.scss
+++ b/app/assets/stylesheets/main.scss
@@ -31,6 +31,12 @@ $hover: #FDF5D9;
box-shadow: 0 0 3px #ddd;
}
+@mixin solid_shade {
+ -moz-box-shadow: 0 0 0 3px #eee;
+ -webkit-box-shadow: 0 0 0 3px #eee;
+ box-shadow: 0 0 0 3px #eee;
+}
+
@mixin border-radius($radius) {
-moz-border-radius: $radius;
-webkit-border-radius: $radius;
@@ -136,7 +142,7 @@ $hover: #FDF5D9;
/**
* Code (files list) styles. Browsing project files there
*/
-@import "tree.scss";
+@import "sections/tree.scss";
/**
* This file represent notes(comments) styles
diff --git a/app/assets/stylesheets/notes.scss b/app/assets/stylesheets/notes.scss
index 70a31dbb0ff..39db704b1a9 100644
--- a/app/assets/stylesheets/notes.scss
+++ b/app/assets/stylesheets/notes.scss
@@ -63,18 +63,22 @@ p.notify_controls span{
tr.line_notes_row {
border-bottom:1px solid #DDD;
+ border-left: 7px solid #2A79A3;
+
&.reply {
background:#eee;
-
+ border-left: 7px solid #2A79A3;
+ border-top:1px solid #ddd;
td {
padding:7px 10px;
}
a.line_note_reply_link {
@include round-borders-all(4px);
- border-color:#aaa;
- background: #bbb;
- padding: 3px 20px;
+ padding: 3px 10px;
+ margin-left:5px;
color: white;
+ background: #2A79A3;
+ border-color: #2A79A3;
}
}
ul {
@@ -95,6 +99,9 @@ tr.line_notes_row {
td {
border-bottom:1px solid #ddd;
}
+ .actions {
+ margin:0;
+ }
}
td .line_note_link {
diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss
index acab785ac71..6052ec3fabb 100644
--- a/app/assets/stylesheets/sections/commits.scss
+++ b/app/assets/stylesheets/sections/commits.scss
@@ -101,18 +101,21 @@
margin:50px;
padding:1px;
max-width:400px;
- }
- &.diff_image_removed {
- img {
+
+ &.diff_image_removed {
border: 1px solid #C00;
}
- }
- &.diff_image_added {
- img {
+ &.diff_image_added {
border: 1px solid #0C0;;
}
}
+
+ &.img_compared {
+ img {
+ max-width:300px;
+ }
+ }
}
}
diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss
index ad496238a13..34f43acf839 100644
--- a/app/assets/stylesheets/sections/merge_requests.scss
+++ b/app/assets/stylesheets/sections/merge_requests.scss
@@ -82,3 +82,15 @@
}
}
}
+
+li.merge_request {
+ padding:7px 10px;
+ img.avatar {
+ width: 32px;
+ margin-top: 4px;
+ }
+ p {
+ padding: 0px;
+ padding-bottom: 2px;
+ }
+}
diff --git a/app/assets/stylesheets/sections/tree.scss b/app/assets/stylesheets/sections/tree.scss
new file mode 100644
index 00000000000..c915e0a96fb
--- /dev/null
+++ b/app/assets/stylesheets/sections/tree.scss
@@ -0,0 +1,96 @@
+#tree-holder {
+ #tree-content-holder {
+ float:left;
+ width:100%;
+ }
+ #tree-readme-holder {
+ float:left;
+ width:100%;
+ .readme {
+ border:1px solid #ccc;
+ padding:12px;
+ background: #F7F7F7;
+
+ pre {
+ overflow: auto;
+ }
+ }
+ }
+
+ .tree_progress {
+ display:none;
+ margin:20px;
+ &.loading {
+ display:block;
+ }
+ }
+
+ #tree-slider {
+ @include border-radius(0);
+ .tree-item {
+ &:hover {
+ td { background: $hover; }
+ cursor:pointer;
+ }
+ }
+ }
+
+ .tree-item {
+ .tree-item-file-name {
+ vertical-align:middle;
+ font-weight:bold;
+ a {
+ color:$style_color;
+ &:hover {
+ color:$blue_link;
+ }
+ }
+
+ img {
+ position: relative;
+ top:-1px;
+ }
+ }
+ }
+
+
+ #tree-slider {
+ @include solid_shade;
+ width:100%;
+
+ border-color:#ccc;
+
+ td {
+ padding:8px;
+ border-color:#f1f1f1;
+ background:#fafafa;
+ }
+
+ tr:first-child td:first-child,
+ tr:first-child td:last-child {
+ border-radius:0;
+ }
+
+ 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);
+ }
+ }
+
+ .tree-commit-link {
+ color:#333;
+ }
+
+ a.tree-commit-link {
+ color: #666;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+
+}
diff --git a/app/assets/stylesheets/themes/ui_mars.scss b/app/assets/stylesheets/themes/ui_mars.scss
index 0fea6144431..39dcab1d085 100644
--- a/app/assets/stylesheets/themes/ui_mars.scss
+++ b/app/assets/stylesheets/themes/ui_mars.scss
@@ -70,8 +70,7 @@
}
}
.separator {
- border-color:#444;
- background:#31363E;
+ display:none;
}
}
diff --git a/app/assets/stylesheets/tree.scss b/app/assets/stylesheets/tree.scss
deleted file mode 100644
index 912c63ee16b..00000000000
--- a/app/assets/stylesheets/tree.scss
+++ /dev/null
@@ -1,232 +0,0 @@
-#tree-holder {
- #tree-content-holder {
- float:left;
- width:100%;
- }
- #tree-readme-holder {
- float:left;
- width:100%;
- .readme {
- border:1px solid #ccc;
- padding:12px;
- background: #F7F7F7;
-
- pre {
- overflow: auto;
- }
- }
- }
-
- .tree_progress {
- display:none;
- margin:20px;
- &.loading {
- display:block;
- }
- }
-
-
- /** FILE CONTENT VIEW **/
- .view_file_content{
- .old_line, .new_line {
- background:#ECECEC;
- color:#777;
- width:15px;
- float:left;
- padding: 0px 10px;
- border-right: 1px solid #ccc;
- }
- .old_line{
- display:none;
- }
- }
-
- .view_file .view_file_header,
- .diff_file .diff_file_header {
- 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;
- }
- }
- }
-
- .view_file {
- border:1px solid #CCC;
- margin-bottom:1em;
-
- .view_file_content {
- background:#fff;
- color:#514721;
- font-size: 11px;
- }
- .view_file_content_image {
- background:#eee;
- text-align:center;
- img {
- padding:100px;
- max-width:300px;
- }
- }
- }
-
- 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;
- }
-
- #tree-slider {
- @include border-radius(0);
- .tree-item {
- &:hover {
- td { background: $hover; }
- cursor:pointer;
- }
- }
- }
-
- .tree-item {
- .tree-item-file-name {
- vertical-align:middle;
- font-weight:bold;
- a {
- color:$style_color;
- &:hover {
- color:$blue_link;
- }
- }
-
- img {
- position: relative;
- top:-1px;
- }
- }
- }
-
-
- #tree-slider {
- @include shade;
- width:100%;
-
- border-color:#ccc;
-
- td {
- padding:8px;
- border-color:#f1f1f1;
- background:#fafafa;
- }
-
- tr:first-child td:first-child,
- tr:first-child td:last-child {
- border-radius:0;
- }
-
- 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);
- }
- }
-
- .tree-commit-link {
- color:#333;
- }
-
- #tree-content-holder .view_file{
- @include shade;
- }
-
- #tree-readme-holder .readme {
- @include shade;
- margin-bottom:20px;
- h1, h2 {
- line-height: 56px;
- }
- h3, h4 {
- line-height: 46px;
- }
- }
-
- a.tree-commit-link {
- color: #666;
- &:hover {
- text-decoration: underline;
- }
- }
-
-}
-
-.blame_file {
- .view_file_content {
- tr {
- border-bottom: 1px solid #eee;
- }
- td {
- padding:5px;
- }
- .author,
- .commit {
- background:#f5f5f5;
- vertical-align:top;
- }
- .lines {
- pre {
- padding:0;
- margin:0;
- background:none;
- border:none;
- }
- }
- }
-}
diff --git a/app/contexts/base_context.rb b/app/contexts/base_context.rb
new file mode 100644
index 00000000000..6eb8ee46c80
--- /dev/null
+++ b/app/contexts/base_context.rb
@@ -0,0 +1,8 @@
+class BaseContext
+ attr_accessor :project, :current_user, :params
+
+ def initialize(project, user, params)
+ @project, @current_user, @params = project, user, params.dup
+ end
+end
+
diff --git a/app/contexts/commit_load.rb b/app/contexts/commit_load.rb
new file mode 100644
index 00000000000..bab30d61007
--- /dev/null
+++ b/app/contexts/commit_load.rb
@@ -0,0 +1,26 @@
+class CommitLoad < BaseContext
+ def execute
+ result = {
+ :commit => nil,
+ :suppress_diff => false,
+ :line_notes => [],
+ :notes_count => 0,
+ :note => nil
+ }
+
+ commit = project.commit(params[:id])
+
+ if commit
+ commit = CommitDecorator.decorate(commit)
+ line_notes = project.commit_line_notes(commit)
+
+ result[:suppress_diff] = true if commit.diffs.size > 200 && !params[:force_show_diff]
+ result[:commit] = commit
+ result[:note] = project.build_commit_note(commit)
+ result[:line_notes] = line_notes
+ result[:notes_count] = line_notes.count + project.commit_notes(commit).count
+ end
+
+ result
+ end
+end
diff --git a/app/contexts/merge_requests_load.rb b/app/contexts/merge_requests_load.rb
new file mode 100644
index 00000000000..6778db3bce5
--- /dev/null
+++ b/app/contexts/merge_requests_load.rb
@@ -0,0 +1,16 @@
+class MergeRequestsLoad < BaseContext
+ def execute
+ type = params[:f].to_i
+
+ 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)
+ else merge_requests.opened
+ end.page(params[:page]).per(20)
+
+ merge_requests.includes(:author, :project).order("closed, created_at desc")
+ end
+end
diff --git a/app/contexts/notes_load.rb b/app/contexts/notes_load.rb
new file mode 100644
index 00000000000..d1f8da9ce12
--- /dev/null
+++ b/app/contexts/notes_load.rb
@@ -0,0 +1,30 @@
+class NotesLoad < BaseContext
+ def execute
+ target_type = params[:target_type]
+ target_id = params[:target_id]
+ first_id = params[:first_id]
+ last_id = params[:last_id]
+
+
+ @notes = case target_type
+ when "commit"
+ then project.commit_notes(project.commit(target_id)).fresh.limit(20)
+ when "snippet"
+ then project.snippets.find(target_id).notes
+ when "wall"
+ then project.common_notes.order("created_at DESC").fresh.limit(50)
+ when "issue"
+ then project.issues.find(target_id).notes.inc_author.order("created_at DESC").limit(20)
+ when "merge_request"
+ then project.merge_requests.find(target_id).notes.inc_author.order("created_at DESC").limit(20)
+ end
+
+ @notes = if last_id
+ @notes.where("id > ?", last_id)
+ elsif first_id
+ @notes.where("id < ?", first_id)
+ else
+ @notes
+ end
+ end
+end
diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
new file mode 100644
index 00000000000..7f832fd5697
--- /dev/null
+++ b/app/controllers/admin/hooks_controller.rb
@@ -0,0 +1,44 @@
+class Admin::HooksController < ApplicationController
+ layout "admin"
+ before_filter :authenticate_user!
+ before_filter :authenticate_admin!
+
+ def index
+ @hooks = SystemHook.all
+ @hook = SystemHook.new
+ end
+
+ def create
+ @hook = SystemHook.new(params[:hook])
+
+ if @hook.save
+ redirect_to admin_hooks_path, notice: 'Hook was successfully created.'
+ else
+ @hooks = SystemHook.all
+ render :index
+ end
+ end
+
+ def destroy
+ @hook = SystemHook.find(params[:id])
+ @hook.destroy
+
+ redirect_to admin_hooks_path
+ end
+
+
+ def test
+ @hook = SystemHook.find(params[:hook_id])
+ data = {
+ event_name: "project_create",
+ name: "Ruby",
+ path: "ruby",
+ project_id: 1,
+ owner_name: "Someone",
+ owner_email: "example@gitlabhq.com"
+ }
+ @hook.execute(data)
+
+ redirect_to :back
+ end
+end
diff --git a/app/controllers/admin/mailer_controller.rb b/app/controllers/admin/mailer_controller.rb
deleted file mode 100644
index 2352e189204..00000000000
--- a/app/controllers/admin/mailer_controller.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-class Admin::MailerController < ApplicationController
- layout "admin"
- before_filter :authenticate_user!
- before_filter :authenticate_admin!
-
- def preview
-
- end
-
- def preview_note
- @note = Note.first
- @user = @note.author
- @project = @note.project
- case params[:type]
- when "Commit" then
- @commit = @project.commit
- render :file => 'notify/note_commit_email', :layout => 'notify'
- when "Issue" then
- @issue = Issue.first
- render :file => 'notify/note_issue_email', :layout => 'notify'
- else
- render :file => 'notify/note_wall_email', :layout => 'notify'
- end
- rescue
- render :text => "Preview not available"
- end
-
- def preview_user_new
- @user = User.first
- @password = "DHasJKDHAS!"
-
- render :file => 'notify/new_user_email', :layout => 'notify'
- rescue
- render :text => "Preview not available"
- end
-
- def preview_issue_new
- @issue = Issue.first
- @user = @issue.assignee
- @project = @issue.project
- render :file => 'notify/new_issue_email', :layout => 'notify'
- rescue
- render :text => "Preview not available"
- end
-end
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index 5266b406504..0ff97bf2c32 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -6,7 +6,7 @@ class Admin::ProjectsController < ApplicationController
def index
@admin_projects = Project.scoped
@admin_projects = @admin_projects.search(params[:name]) if params[:name].present?
- @admin_projects = @admin_projects.page(params[:page])
+ @admin_projects = @admin_projects.page(params[:page]).per(20)
end
def show
@@ -72,6 +72,6 @@ class Admin::ProjectsController < ApplicationController
@admin_project = Project.find_by_code(params[:id])
@admin_project.destroy
- redirect_to admin_projects_url
+ redirect_to admin_projects_url, notice: 'Project was successfully deleted.'
end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 9a0f95bf0cb..3265046d2ae 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -52,7 +52,7 @@ class ApplicationController < ActionController::Base
def layout_by_resource
if devise_controller?
- "devise"
+ "devise_layout"
else
"application"
end
diff --git a/app/controllers/commits_controller.rb b/app/controllers/commits_controller.rb
index 25e980e017b..cb1f74527a1 100644
--- a/app/controllers/commits_controller.rb
+++ b/app/controllers/commits_controller.rb
@@ -26,43 +26,31 @@ class CommitsController < ApplicationController
end
def show
- @commit = project.commit(params[:id])
-
- git_not_found! and return unless @commit
-
- @commit = CommitDecorator.decorate(@commit)
-
- @note = @project.build_commit_note(@commit)
- @comments_allowed = true
- @line_notes = project.commit_line_notes(@commit)
-
- @notes_count = @line_notes.count + project.commit_notes(@commit).count
-
- if @commit.diffs.size > 200 && !params[:force_show_diff]
- @suppress_diff = true
+ result = CommitLoad.new(project, current_user, params).execute
+
+ @commit = result[:commit]
+
+ if @commit
+ @suppress_diff = result[:suppress_diff]
+ @note = result[:note]
+ @line_notes = result[:line_notes]
+ @notes_count = result[:notes_count]
+ @comments_allowed = true
+ else
+ return git_not_found!
end
+
rescue Grit::Git::GitTimeout
render "huge_commit"
end
def compare
- first = project.commit(params[:to].try(:strip))
- last = project.commit(params[:from].try(:strip))
+ result = Commit.compare(project, params[:from], params[:to])
- @diffs = []
- @commits = []
+ @commits = result[:commits]
+ @commit = result[:commit]
+ @diffs = result[:diffs]
@line_notes = []
-
- if first && last
- commits = [first, last].sort_by(&:created_at)
- younger = commits.first
- older = commits.last
-
-
- @commits = project.repo.commits_between(younger.id, older.id).map {|c| Commit.new(c)}
- @diffs = project.repo.diff(younger.id, older.id) rescue []
- @commit = Commit.new(older)
- end
end
def patch
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index a054940738e..8508e2454d2 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -2,15 +2,13 @@ class DashboardController < ApplicationController
respond_to :html
def index
- @projects = current_user.projects.includes(:events).order("events.created_at DESC")
- @projects = @projects.page(params[:page]).per(40)
-
- @events = Event.where(:project_id => current_user.projects.map(&:id)).recent.limit(20)
-
+ @projects = current_user.projects_with_events.page(params[:page]).per(40)
+ @events = Event.recent_for_user(current_user).limit(20).offset(params[:offset] || 0)
@last_push = current_user.recent_push
respond_to do |format|
format.html
+ format.js
format.atom { render :layout => false }
end
end
diff --git a/app/controllers/hooks_controller.rb b/app/controllers/hooks_controller.rb
index 9627aba9771..ad2fb3ae781 100644
--- a/app/controllers/hooks_controller.rb
+++ b/app/controllers/hooks_controller.rb
@@ -11,24 +11,24 @@ class HooksController < ApplicationController
respond_to :html
def index
- @hooks = @project.web_hooks.all
- @hook = WebHook.new
+ @hooks = @project.hooks.all
+ @hook = ProjectHook.new
end
def create
- @hook = @project.web_hooks.new(params[:hook])
+ @hook = @project.hooks.new(params[:hook])
@hook.save
if @hook.valid?
redirect_to project_hooks_path(@project)
else
- @hooks = @project.web_hooks.all
+ @hooks = @project.hooks.all
render :index
end
end
def test
- @hook = @project.web_hooks.find(params[:id])
+ @hook = @project.hooks.find(params[:id])
commits = @project.commits(@project.default_branch, nil, 3)
data = @project.post_receive_data(commits.last.id, commits.first.id, "refs/heads/#{@project.default_branch}", current_user)
@hook.execute(data)
@@ -37,7 +37,7 @@ class HooksController < ApplicationController
end
def destroy
- @hook = @project.web_hooks.find(params[:id])
+ @hook = @project.hooks.find(params[:id])
@hook.destroy
redirect_to project_hooks_path(@project)
diff --git a/app/controllers/merge_requests_controller.rb b/app/controllers/merge_requests_controller.rb
index ec4ed45fedf..1cb1d388465 100644
--- a/app/controllers/merge_requests_controller.rb
+++ b/app/controllers/merge_requests_controller.rb
@@ -24,16 +24,7 @@ class MergeRequestsController < ApplicationController
def index
- @merge_requests = @project.merge_requests
-
- @merge_requests = case params[:f].to_i
- when 1 then @merge_requests
- when 2 then @merge_requests.closed
- when 3 then @merge_requests.opened.assigned(current_user)
- else @merge_requests.opened
- end.page(params[:page]).per(20)
-
- @merge_requests = @merge_requests.includes(:author, :project).order("closed, created_at desc")
+ @merge_requests = MergeRequestsLoad.new(project, current_user, params).execute
end
def show
diff --git a/app/controllers/notes_controller.rb b/app/controllers/notes_controller.rb
index a2638d9597c..1c997e380a0 100644
--- a/app/controllers/notes_controller.rb
+++ b/app/controllers/notes_controller.rb
@@ -40,25 +40,6 @@ class NotesController < ApplicationController
protected
def notes
- @notes = case params[:target_type]
- when "commit"
- then project.commit_notes(project.commit((params[:target_id]))).fresh.limit(20)
- when "snippet"
- then project.snippets.find(params[:target_id]).notes
- when "wall"
- then project.common_notes.order("created_at DESC").fresh.limit(50)
- when "issue"
- then project.issues.find(params[:target_id]).notes.inc_author.order("created_at DESC").limit(20)
- when "merge_request"
- then project.merge_requests.find(params[:target_id]).notes.inc_author.order("created_at DESC").limit(20)
- end
-
- @notes = if params[:last_id]
- @notes.where("id > ?", params[:last_id])
- elsif params[:first_id]
- @notes.where("id < ?", params[:first_id])
- else
- @notes
- end
+ @notes = NotesLoad.new(project, current_user, params).execute
end
end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 629b6819fb1..fb759c371c4 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -1,4 +1,17 @@
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
+
+ # Extend the standard message generation to accept our custom exception
+ def failure_message
+ exception = env["omniauth.error"]
+ if exception.class == OmniAuth::Error
+ error = exception.message
+ else
+ error = exception.error_reason if exception.respond_to?(:error_reason)
+ error ||= exception.error if exception.respond_to?(:error)
+ error ||= env["omniauth.error.type"].to_s
+ end
+ error.to_s.humanize if error
+ end
def ldap
# We only find ourselves here if the authentication to LDAP was successful.
diff --git a/app/controllers/refs_controller.rb b/app/controllers/refs_controller.rb
index ddf5f675d0c..b610c9f34d4 100644
--- a/app/controllers/refs_controller.rb
+++ b/app/controllers/refs_controller.rb
@@ -9,7 +9,7 @@ class RefsController < ApplicationController
before_filter :require_non_empty_project
before_filter :ref
- before_filter :define_tree_vars, :only => [:tree, :blob, :blame]
+ before_filter :define_tree_vars, :only => [:tree, :blob, :blame, :logs_tree]
before_filter :render_full_content
layout "project"
@@ -46,6 +46,18 @@ class RefsController < ApplicationController
end
end
+ def logs_tree
+ contents = @tree.contents
+ @logs = contents.map do |content|
+ file = params[:path] ? File.join(params[:path], content.name) : content.name
+ last_commit = @project.commits(@commit.id, file, 1).last
+ {
+ :file_name => content.name,
+ :commit => last_commit
+ }
+ end
+ end
+
def blob
if @tree.is_blob?
if @tree.text?
@@ -79,6 +91,15 @@ class RefsController < ApplicationController
@commit = project.commit(@ref)
@tree = Tree.new(@commit.tree, project, @ref, params[:path])
@tree = TreeDecorator.new(@tree)
+ @hex_path = Digest::SHA1.hexdigest(params[:path] || "/")
+
+ if params[:path]
+ @history_path = tree_file_project_ref_path(@project, @ref, params[:path])
+ @logs_path = logs_file_project_ref_path(@project, @ref, params[:path])
+ else
+ @history_path = tree_project_ref_path(@project, @ref)
+ @logs_path = logs_tree_project_ref_path(@project, @ref)
+ end
rescue
return render_404
end
diff --git a/app/decorators/event_decorator.rb b/app/decorators/event_decorator.rb
new file mode 100644
index 00000000000..50aaa615d49
--- /dev/null
+++ b/app/decorators/event_decorator.rb
@@ -0,0 +1,25 @@
+class EventDecorator < ApplicationDecorator
+ decorates :event
+
+ def feed_title
+ if self.issue?
+ "#{self.author_name} #{self.action_name} issue ##{self.target_id}:" + self.issue_title
+ elsif self.merge_request?
+ "#{self.author_name} #{self.action_name} MR ##{self.target_id}:" + self.merge_request_title
+ elsif self.push?
+ "#{self.author_name} #{self.push_action_name} #{self.ref_type} " + self.ref_name
+ else
+ ""
+ end
+ end
+
+ def feed_url
+ if self.issue?
+ h.project_issue_url(self.project, self.issue)
+ elsif self.merge_request?
+ h.project_merge_request_url(self.project, self.merge_request)
+ elsif self.push?
+ h.project_commits_url(self.project, :ref => self.ref_name)
+ end
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 2697fff433e..3f15fd9237f 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -134,4 +134,8 @@ module ApplicationHelper
end
active ? "current" : nil
end
+
+ def hexdigest(string)
+ Digest::SHA1.hexdigest string
+ end
end
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
new file mode 100644
index 00000000000..ed3053d8af5
--- /dev/null
+++ b/app/helpers/tree_helper.rb
@@ -0,0 +1,27 @@
+module TreeHelper
+ def tree_icon(content)
+ if content.is_a?(Grit::Blob)
+ if content.text?
+ image_tag "file_txt.png"
+ elsif content.image?
+ image_tag "file_img.png"
+ else
+ image_tag "file_bin.png"
+ end
+ else
+ image_tag "file_dir.png"
+ end
+ end
+
+ def tree_hex_class(content)
+ "file_#{hexdigest(content.name)}"
+ end
+
+ def tree_full_path(content)
+ if params[:path]
+ File.join(params[:path], content.name)
+ else
+ content.name
+ end
+ end
+end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 800ad19b9f1..859bee29fa5 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -80,6 +80,29 @@ class Commit
def commits_between(repo, from, to)
repo.commits_between(from, to).map { |c| Commit.new(c) }
end
+
+ def compare(project, from, to)
+ first = project.commit(to.try(:strip))
+ last = project.commit(from.try(:strip))
+
+ result = {
+ :commits => [],
+ :diffs => [],
+ :commit => nil
+ }
+
+ if first && last
+ commits = [first, last].sort_by(&:created_at)
+ younger = commits.first
+ older = commits.last
+
+ result[:commits] = project.repo.commits_between(younger.id, older.id).map {|c| Commit.new(c)}
+ result[:diffs] = project.repo.diff(younger.id, older.id) rescue []
+ result[:commit] = Commit.new(older)
+ end
+
+ result
+ end
end
def persisted?
diff --git a/app/models/event.rb b/app/models/event.rb
index dc7dfa16dd5..c75924e7500 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -28,6 +28,10 @@ class Event < ActiveRecord::Base
end
end
+ def self.recent_for_user user
+ where(:project_id => user.projects.map(&:id)).recent
+ end
+
# Next events currently enabled for system
# - push
# - new issue
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index d3e531f7818..2581f3be4c9 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -22,7 +22,6 @@ class MergeRequest < ActiveRecord::Base
:should_remove_source_branch
validates_presence_of :project_id
- validates_presence_of :assignee_id
validates_presence_of :author_id
validates_presence_of :source_branch
validates_presence_of :target_branch
@@ -36,6 +35,7 @@ class MergeRequest < ActiveRecord::Base
delegate :name,
:email,
:to => :assignee,
+ :allow_nil => true,
:prefix => true
validates :title,
@@ -128,7 +128,7 @@ class MergeRequest < ActiveRecord::Base
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)
+ diffs = project.repo.diff(commits.first.prev_commit.id, commits.last.id) rescue []
end
def last_commit
diff --git a/app/models/project.rb b/app/models/project.rb
index ec4893e2b17..a49b3f519b3 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -19,7 +19,7 @@ class Project < ActiveRecord::Base
has_many :notes, :dependent => :destroy
has_many :snippets, :dependent => :destroy
has_many :deploy_keys, :dependent => :destroy, :foreign_key => "project_id", :class_name => "Key"
- has_many :web_hooks, :dependent => :destroy
+ has_many :hooks, :dependent => :destroy, :class_name => "ProjectHook"
has_many :wikis, :dependent => :destroy
has_many :protected_branches, :dependent => :destroy
@@ -120,7 +120,7 @@ class Project < ActiveRecord::Base
errors.add(:path, " like 'gitolite-admin' is not allowed")
end
end
-
+
def self.access_options
UsersProject.access_roles
end
diff --git a/app/models/project_hook.rb b/app/models/project_hook.rb
new file mode 100644
index 00000000000..06388aaeb4c
--- /dev/null
+++ b/app/models/project_hook.rb
@@ -0,0 +1,3 @@
+class ProjectHook < WebHook
+ belongs_to :project
+end
diff --git a/app/models/system_hook.rb b/app/models/system_hook.rb
new file mode 100644
index 00000000000..8517d43a9de
--- /dev/null
+++ b/app/models/system_hook.rb
@@ -0,0 +1,13 @@
+class SystemHook < WebHook
+
+ def async_execute(data)
+ Resque.enqueue(SystemHookWorker, id, data)
+ end
+
+ def self.all_hooks_fire(data)
+ SystemHook.all.each do |sh|
+ sh.async_execute data
+ end
+ end
+
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 4ead60a92db..ff27660a6ee 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,11 +1,12 @@
class User < ActiveRecord::Base
+
include Account
- devise :database_authenticatable, :token_authenticatable,
+ devise :database_authenticatable, :token_authenticatable, :lockable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
attr_accessible :email, :password, :password_confirmation, :remember_me, :bio,
- :name, :projects_limit, :skype, :linkedin, :twitter, :dark_scheme,
+ :name, :projects_limit, :skype, :linkedin, :twitter, :dark_scheme,
:theme_id, :force_random_password
attr_accessor :force_random_password
@@ -15,6 +16,11 @@ class User < ActiveRecord::Base
has_many :my_own_projects, :class_name => "Project", :foreign_key => :owner_id
has_many :keys, :dependent => :destroy
+ has_many :events,
+ :class_name => "Event",
+ :foreign_key => :author_id,
+ :dependent => :destroy
+
has_many :recent_events,
:class_name => "Event",
:foreign_key => :author_id,
@@ -80,7 +86,8 @@ class User < ActiveRecord::Base
def self.find_for_ldap_auth(omniauth_info)
name = omniauth_info.name.force_encoding("utf-8")
- email = omniauth_info.email.downcase
+ email = omniauth_info.email.downcase unless omniauth_info.email.nil?
+ raise OmniAuth::Error, "LDAP accounts must provide an email address" if email.nil?
if @user = User.find_by_email(email)
@user
diff --git a/app/models/users_project.rb b/app/models/users_project.rb
index 6ba72370931..4ff86290a92 100644
--- a/app/models/users_project.rb
+++ b/app/models/users_project.rb
@@ -68,7 +68,7 @@ class UsersProject < ActiveRecord::Base
end
def repo_access_human
- ""
+ self.class.access_roles.invert[self.project_access]
end
end
# == Schema Information
diff --git a/app/models/web_hook.rb b/app/models/web_hook.rb
index 26288476a6c..85d87898682 100644
--- a/app/models/web_hook.rb
+++ b/app/models/web_hook.rb
@@ -4,8 +4,6 @@ class WebHook < ActiveRecord::Base
# HTTParty timeout
default_timeout 10
- belongs_to :project
-
validates :url,
presence: true,
format: {
@@ -14,9 +12,8 @@ class WebHook < ActiveRecord::Base
def execute(data)
WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" })
- rescue
- # There was a problem calling this web hook, let's forget about it.
end
+
end
# == Schema Information
#
diff --git a/app/observers/mailer_observer.rb b/app/observers/mailer_observer.rb
index 880fd5026a4..451deccd14f 100644
--- a/app/observers/mailer_observer.rb
+++ b/app/observers/mailer_observer.rb
@@ -43,7 +43,7 @@ class MailerObserver < ActiveRecord::Observer
end
def new_merge_request(merge_request)
- if merge_request.assignee != current_user
+ if merge_request.assignee && merge_request.assignee != current_user
Notify.new_merge_request_email(merge_request.id).deliver
end
end
diff --git a/app/observers/system_hook_observer.rb b/app/observers/system_hook_observer.rb
new file mode 100644
index 00000000000..312cd2b3622
--- /dev/null
+++ b/app/observers/system_hook_observer.rb
@@ -0,0 +1,67 @@
+class SystemHookObserver < ActiveRecord::Observer
+ observe :user, :project, :users_project
+
+ def after_create(model)
+ if model.kind_of? Project
+ SystemHook.all_hooks_fire({
+ event_name: "project_create",
+ name: model.name,
+ path: model.path,
+ project_id: model.id,
+ owner_name: model.owner.name,
+ owner_email: model.owner.email,
+ created_at: model.created_at
+ })
+ elsif model.kind_of? User
+ SystemHook.all_hooks_fire({
+ event_name: "user_create",
+ name: model.name,
+ email: model.email,
+ created_at: model.created_at
+ })
+
+ elsif model.kind_of? UsersProject
+ SystemHook.all_hooks_fire({
+ event_name: "user_add_to_team",
+ project_name: model.project.name,
+ project_path: model.project.path,
+ project_id: model.project_id,
+ user_name: model.user.name,
+ user_email: model.user.email,
+ project_access: model.repo_access_human,
+ created_at: model.created_at
+ })
+
+ end
+ end
+
+ def after_destroy(model)
+ if model.kind_of? Project
+ SystemHook.all_hooks_fire({
+ event_name: "project_destroy",
+ name: model.name,
+ path: model.path,
+ project_id: model.id,
+ owner_name: model.owner.name,
+ owner_email: model.owner.email,
+ })
+ elsif model.kind_of? User
+ SystemHook.all_hooks_fire({
+ event_name: "user_destroy",
+ name: model.name,
+ email: model.email
+ })
+
+ elsif model.kind_of? UsersProject
+ SystemHook.all_hooks_fire({
+ event_name: "user_remove_from_team",
+ project_name: model.project.name,
+ project_path: model.project.path,
+ project_id: model.project_id,
+ user_name: model.user.name,
+ user_email: model.user.email,
+ project_access: model.repo_access_human
+ })
+ end
+ end
+end
diff --git a/app/roles/account.rb b/app/roles/account.rb
index afa1f8a347d..e86dc5939e0 100644
--- a/app/roles/account.rb
+++ b/app/roles/account.rb
@@ -55,4 +55,8 @@ module Account
# Take only latest one
events = events.recent.limit(1).first
end
+
+ def projects_with_events
+ projects.includes(:events).order("events.created_at DESC")
+ end
end
diff --git a/app/roles/git_push.rb b/app/roles/git_push.rb
index b4c59472a5a..4ee7e62a69e 100644
--- a/app/roles/git_push.rb
+++ b/app/roles/git_push.rb
@@ -27,7 +27,7 @@ module GitPush
true
end
- def execute_web_hooks(oldrev, newrev, ref, user)
+ def execute_hooks(oldrev, newrev, ref, user)
ref_parts = ref.split('/')
# Return if this is not a push to a branch (e.g. new commits)
@@ -35,7 +35,7 @@ module GitPush
data = post_receive_data(oldrev, newrev, ref, user)
- web_hooks.each { |web_hook| web_hook.execute(data) }
+ hooks.each { |hook| hook.execute(data) }
end
def post_receive_data(oldrev, newrev, ref, user)
@@ -97,7 +97,7 @@ module GitPush
self.update_merge_requests(oldrev, newrev, ref, user)
# Execute web hooks
- self.execute_web_hooks(oldrev, newrev, ref, user)
+ self.execute_hooks(oldrev, newrev, ref, user)
# Create satellite
self.satellite.create unless self.satellite.exists?
diff --git a/app/views/admin/hooks/_data_ex.html.erb b/app/views/admin/hooks/_data_ex.html.erb
new file mode 100644
index 00000000000..652ee5aa56f
--- /dev/null
+++ b/app/views/admin/hooks/_data_ex.html.erb
@@ -0,0 +1,66 @@
+<% data_ex_str = <<eos
+1. Project created:
+{
+ "created_at": "2012-07-21T07:30:54Z",
+ "event_name": "project_create",
+ "name": "StoreCloud",
+ "owner_email": "johnsmith@gmail.com",
+ "owner_name": "John Smith",
+ "path": "storecloud",
+ "project_id": 74
+}
+
+2. Project destroyed:
+{
+ "event_name": "project_destroy",
+ "name": "Underscore",
+ "owner_email": "johnsmith@gmail.com",
+ "owner_name": "John Smith",
+ "path": "underscore",
+ "project_id": 73
+}
+
+3. New Team Member:
+{
+ "created_at": "2012-07-21T07:30:56Z",
+ "event_name": "user_add_to_team",
+ "project_access": "Master",
+ "project_id": 74,
+ "project_name": "StoreCloud",
+ "project_path": "storecloud",
+ "owner_email": "johnsmith@gmail.com",
+ "owner_name": "John Smith",
+}
+
+4. Team Member Removed:
+{
+ "created_at": "2012-07-21T07:30:56Z",
+ "event_name": "user_remove_from_team",
+ "project_access": "Master",
+ "project_id": 74,
+ "project_name": "StoreCloud",
+ "project_path": "storecloud",
+ "owner_email": "johnsmith@gmail.com",
+ "owner_name": "John Smith",
+}
+
+5. User created:
+{
+ "created_at": "2012-07-21T07:44:07Z",
+ "email": "js@gitlabhq.com",
+ "event_name": "user_create",
+ "name": "John Smith"
+}
+
+6. User removed:
+{
+ "created_at": "2012-07-21T07:44:07Z",
+ "email": "js@gitlabhq.com",
+ "event_name": "user_destroy",
+ "name": "John Smith"
+}
+
+eos
+%>
+<% js_lexer = Pygments::Lexer[:js] %>
+<%= raw js_lexer.highlight(data_ex_str) %>
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
new file mode 100644
index 00000000000..030e6136b1f
--- /dev/null
+++ b/app/views/admin/hooks/index.html.haml
@@ -0,0 +1,39 @@
+.alert.alert-info
+ %span
+ Post receive hooks for binding events.
+ %br
+ Read more about system hooks
+ %strong #{link_to "here", help_system_hooks_path, :class => "vlink"}
+
+= form_for @hook, :as => :hook, :url => admin_hooks_path do |f|
+ -if @hook.errors.any?
+ .alert-message.block-message.error
+ - @hook.errors.full_messages.each do |msg|
+ %p= msg
+ .clearfix
+ = f.label :url, "URL:"
+ .input
+ = f.text_field :url, :class => "text_field xxlarge"
+ &nbsp;
+ = f.submit "Add System Hook", :class => "btn primary"
+%hr
+
+-if @hooks.any?
+ %h3
+ Hooks
+ %small (#{@hooks.count})
+ %br
+ %table.admin-table
+ %tr
+ %th URL
+ %th Method
+ %th
+ - @hooks.each do |hook|
+ %tr
+ %td
+ = link_to admin_hook_path(hook) do
+ %strong= hook.url
+ = link_to 'Test Hook', admin_hook_test_path(hook), :class => "btn small right"
+ %td POST
+ %td
+ = link_to 'Remove', admin_hook_path(hook), :confirm => 'Are you sure?', :method => :delete, :class => "danger btn small right"
diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml
index 7963e18adcd..800d3bb288f 100644
--- a/app/views/admin/logs/show.html.haml
+++ b/app/views/admin/logs/show.html.haml
@@ -1,6 +1,9 @@
-%h4
- %i.icon-file
- githost.log
-%pre.logs
- - Gitlab::Logger.read_latest.each do |line|
- %span.log= line
+.file_holder#README
+ .file_title
+ %i.icon-file
+ githost.log
+ .file_content.logs
+ %ol
+ - Gitlab::Logger.read_latest.each do |line|
+ %li
+ %p= line
diff --git a/app/views/admin/mailer/preview.html.haml b/app/views/admin/mailer/preview.html.haml
deleted file mode 100644
index 23ea7381cf5..00000000000
--- a/app/views/admin/mailer/preview.html.haml
+++ /dev/null
@@ -1,28 +0,0 @@
-%p This is page with preview for all system emails that are sent to user
-%p Email previews built based on existing Project/Commit/Issue base - so some preview maybe unavailable unless object appear in system
-
-#accordion
- %h3
- %a New user
- %div
- %iframe{ :src=> admin_mailer_preview_user_new_path, :width=>"100%", :height=>"350"}
- %h3
- %a New issue
- %div
- %iframe{ :src=> admin_mailer_preview_issue_new_path, :width=>"100%", :height=>"350"}
- %h3
- %a Commit note
- %div
- %iframe{ :src=> admin_mailer_preview_note_path(:type => "Commit"), :width=>"100%", :height=>"350"}
- %h3
- %a Issue note
- %div
- %iframe{ :src=> admin_mailer_preview_note_path(:type => "Issue"), :width=>"100%", :height=>"350"}
- %h3
- %a Wall note
- %div
- %iframe{ :src=> admin_mailer_preview_note_path(:type => "Wall"), :width=>"100%", :height=>"350"}
-
-:javascript
- $(function() {
- $("#accordion").accordion(); });
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index 932fb37ddf6..7218eebb62a 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -13,8 +13,8 @@
%th Team Members
%th Post Receive
%th Last Commit
- %th
- %th
+ %th Edit
+ %th.cred Danger Zone!
- @admin_projects.each do |project|
%tr
@@ -24,5 +24,5 @@
%td= check_box_tag :post_receive_file, 1, project.has_post_receive_file?, :disabled => true
%td= last_commit(project)
%td= link_to 'Edit', edit_admin_project_path(project), :id => "edit_#{dom_id(project)}", :class => "btn small"
- %td= link_to 'Destroy', [:admin, project], :confirm => 'Are you sure?', :method => :delete, :class => "btn small danger"
+ %td.bgred= link_to 'Destroy', [:admin, project], :confirm => "REMOVE #{project.name}? Are you sure?", :method => :delete, :class => "btn small danger"
= paginate @admin_projects, :theme => "admin"
diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml
index bd2e136247a..c1955b321d5 100644
--- a/app/views/admin/users/_form.html.haml
+++ b/app/views/admin/users/_form.html.haml
@@ -50,7 +50,7 @@
.alert
.clearfix
- %p Give user ability to manage application.
+ %p Make the user a GitLab administrator.
= f.label :admin, :class => "checkbox" do
= f.check_box :admin
%span Administrator
@@ -59,11 +59,11 @@
- 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 GitLab
+ 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 user will removed from all projects &amp; will not be able to login to GitLab.
+ Blocked users will be removed from all projects &amp; will not be able to login to GitLab.
.actions
= f.submit 'Save', :class => "btn primary"
- if @admin_user.new_record?
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index 481bf37bb0b..5d5320db0e3 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -27,7 +27,7 @@
%th Projects
%th Edit
%th Blocked
- %th
+ %th.cred Danger Zone!
- @admin_users.each do |user|
%tr
@@ -41,6 +41,6 @@
= link_to 'Unblock', unblock_admin_user_path(user), :method => :put, :class => "btn small success"
- else
= link_to 'Block', block_admin_user_path(user), :confirm => 'USER WILL BE BLOCKED! Are you sure?', :method => :put, :class => "btn small danger"
- %td= link_to 'Destroy', [:admin, user], :confirm => 'USER WILL BE REMOVED! Are you sure?', :method => :delete, :class => "btn small danger"
+ %td.bgred= link_to 'Destroy', [:admin, user], :confirm => "USER #{user.name} WILL BE REMOVED! Are you sure?", :method => :delete, :class => "btn small danger"
= paginate @admin_users, :theme => "admin"
diff --git a/app/views/commits/_commits.html.haml b/app/views/commits/_commits.html.haml
index c2c9ca624b2..c3c7d49ce74 100644
--- a/app/views/commits/_commits.html.haml
+++ b/app/views/commits/_commits.html.haml
@@ -1,4 +1,6 @@
- @commits.group_by { |c| c.committed_date.to_date }.each do |day, commits|
%div.ui-box
- %h5= day.stamp("28 Aug, 2010")
+ %h5.small
+ %i.icon-calendar
+ = day.stamp("28 Aug, 2010")
%ul.unstyled= render commits
diff --git a/app/views/commits/_diffs.html.haml b/app/views/commits/_diffs.html.haml
index 02a15633089..d51561d90f8 100644
--- a/app/views/commits/_diffs.html.haml
+++ b/app/views/commits/_diffs.html.haml
@@ -35,7 +35,13 @@
- if file.text?
= render "commits/text_file", :diff => diff, :index => i
- elsif file.image?
- .diff_file_content_image{:class => image_diff_class(diff)}
- %img{:src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
+ - if diff.renamed_file || diff.new_file || diff.deleted_file
+ .diff_file_content_image
+ %img{:class => image_diff_class(diff), :src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
+ - else
+ - old_file = (@commit.prev_commit.tree / diff.old_path)
+ .diff_file_content_image.img_compared
+ %img{:class => "diff_image_removed", :src => "data:#{file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
+ %img{:class => "diff_image_added", :src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
- else
%p.nothing_here_message No preview for this file type
diff --git a/app/views/commits/_head.html.haml b/app/views/commits/_head.html.haml
index 8e9195c5b50..453ca4eac12 100644
--- a/app/views/commits/_head.html.haml
+++ b/app/views/commits/_head.html.haml
@@ -13,12 +13,12 @@
%li{:class => "#{branches_tab_class}"}
= link_to project_repository_path(@project) do
Branches
- %span.number= @project.repo.branch_count
+ %span.badge= @project.repo.branch_count
%li{:class => "#{'active' if current_page?(tags_project_repository_path(@project)) }"}
= link_to tags_project_repository_path(@project) do
Tags
- %span.number= @project.repo.tag_count
+ %span.badge= @project.repo.tag_count
- if current_page?(project_commits_path(@project)) && current_user.private_token
diff --git a/app/views/commits/compare.html.haml b/app/views/commits/compare.html.haml
index c02263296f4..66ed8dad595 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 primary"
+ = submit_tag "Compare", :class => "btn btn-primary"
- unless @commits.empty?
diff --git a/app/views/dashboard/index.atom.builder b/app/views/dashboard/index.atom.builder
index 706b808ee43..fa3bfade28b 100644
--- a/app/views/dashboard/index.atom.builder
+++ b/app/views/dashboard/index.atom.builder
@@ -8,17 +8,10 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
@events.each do |event|
if event.allowed?
+ event = EventDecorator.decorate(event)
xml.entry do
- if event.issue?
- event_link = project_issue_url(event.project, event.issue)
- event_title = event.issue_title
- elsif event.merge_request?
- event_link = project_merge_request_url(event.project, event.merge_request)
- event_title = event.merge_request_title
- elsif event.push?
- event_link = project_commits_url(event.project, :ref => event.ref_name)
- event_title = event.ref_name
- end
+ event_link = event.feed_url
+ event_title = event.feed_title
xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
xml.link :href => event_link
diff --git a/app/views/dashboard/index.html.haml b/app/views/dashboard/index.html.haml
index b38544509b2..e1d7781927a 100644
--- a/app/views/dashboard/index.html.haml
+++ b/app/views/dashboard/index.html.haml
@@ -10,9 +10,10 @@
add new key
to your profile
- if @events.any?
- = render @events
+ .content_list= render @events
- else
%h4.nothing_here_message Projects activity will be displayed here
+ .loading.hide
.side
= render "events/event_last_push", :event => @last_push
.projects_box
@@ -54,3 +55,7 @@
New Project »
- else
If you will be added to project - it will be displayed here
+
+
+:javascript
+ $(function(){ Pager.init(20); });
diff --git a/app/views/dashboard/index.js.haml b/app/views/dashboard/index.js.haml
index aa038e75928..7e5a148e5ef 100644
--- a/app/views/dashboard/index.js.haml
+++ b/app/views/dashboard/index.js.haml
@@ -1,2 +1,2 @@
:plain
- $(".projects .activities").append("#{escape_javascript(render(@events))}");
+ Pager.append(#{@events.count}, "#{escape_javascript(render(@events))}");
diff --git a/app/views/events/_event_issue.html.haml b/app/views/events/_event_issue.html.haml
index 13fb20cd379..4293be8204e 100644
--- a/app/views/events/_event_issue.html.haml
+++ b/app/views/events/_event_issue.html.haml
@@ -1,7 +1,7 @@
= image_tag gravatar_icon(event.author_email), :class => "avatar"
%strong #{event.author_name}
-%span.event_label= event.action_name
-&nbsp;issue
+%span.event_label{:class => event.action_name}= event.action_name
+issue
= link_to project_issue_path(event.project, event.issue) do
%strong= truncate event.issue_title
at
diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml
index fb4a728ed3e..212ef817e60 100644
--- a/app/views/events/_event_last_push.html.haml
+++ b/app/views/events/_event_last_push.html.haml
@@ -5,12 +5,9 @@
%span Your pushed to
= event.ref_type
= link_to project_commits_path(event.project, :ref => event.ref_name) do
- %strong= event.ref_name
+ %strong= truncate(event.ref_name, :length => 28)
at
%strong= link_to event.project.name, event.project
- %span.cgray
- = time_ago_in_words(event.created_at)
- ago.
= link_to new_mr_path_from_push_event(event), :title => "New Merge Request", :class => "btn very_small primary" do
Create Merge Request
diff --git a/app/views/events/_event_merge_request.html.haml b/app/views/events/_event_merge_request.html.haml
index e59a74b9554..774921a7f2a 100644
--- a/app/views/events/_event_merge_request.html.haml
+++ b/app/views/events/_event_merge_request.html.haml
@@ -2,8 +2,8 @@
.event_icon= image_tag "event_mr_merged.png"
= image_tag gravatar_icon(event.author_email), :class => "avatar"
%strong #{event.author_name}
-%span.event_label= event.action_name
-&nbsp;merge request
+%span.event_label{:class => event.action_name}= event.action_name
+merge request
= link_to project_merge_request_path(event.project, event.merge_request) do
%strong= truncate event.merge_request_title
at
diff --git a/app/views/events/_event_push.html.haml b/app/views/events/_event_push.html.haml
index 3aadd226614..59d8962bb16 100644
--- a/app/views/events/_event_push.html.haml
+++ b/app/views/events/_event_push.html.haml
@@ -2,7 +2,7 @@
.event_icon= image_tag "event_push.png"
= image_tag gravatar_icon(event.author_email), :class => "avatar"
%strong #{event.author_name}
- %span.event_label= event.push_action_name
+ %span.event_label.pushed= event.push_action_name
= event.ref_type
= link_to project_commits_path(event.project, :ref => event.ref_name) do
%strong= event.ref_name
diff --git a/app/views/help/api.html.haml b/app/views/help/api.html.haml
new file mode 100644
index 00000000000..4964c1bbd87
--- /dev/null
+++ b/app/views/help/api.html.haml
@@ -0,0 +1,41 @@
+%h3 API
+.back_link
+ = link_to help_path do
+ &larr; to index
+%hr
+
+%ol
+ %li
+ %a{:href => "#README"} README
+ %li
+ %a{:href => "#projects"} Projects
+ %li
+ %a{:href => "#users"} Users
+
+.file_holder#README
+ .file_title
+ %i.icon-file
+ README
+ .file_content.wiki
+ = preserve do
+ = markdown File.read(Rails.root.join("doc", "api", "README.md"))
+
+%br
+
+.file_holder#projects
+ .file_title
+ %i.icon-file
+ Projects
+ .file_content.wiki
+ = preserve do
+ = markdown File.read(Rails.root.join("doc", "api", "projects.md"))
+
+%br
+
+.file_holder#users
+ .file_title
+ %i.icon-file
+ Users
+ .file_content.wiki
+ = preserve do
+ = markdown File.read(Rails.root.join("doc", "api", "users.md"))
diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml
index 25b9e3e5208..e9602e33037 100644
--- a/app/views/help/index.html.haml
+++ b/app/views/help/index.html.haml
@@ -22,3 +22,9 @@
%li
%h5= link_to "Web Hooks", help_web_hooks_path
+
+ %li
+ %h5= link_to "System Hooks", help_system_hooks_path
+
+ %li
+ %h5= link_to "API", help_api_path
diff --git a/app/views/help/system_hooks.html.haml b/app/views/help/system_hooks.html.haml
new file mode 100644
index 00000000000..2088208ad47
--- /dev/null
+++ b/app/views/help/system_hooks.html.haml
@@ -0,0 +1,13 @@
+%h3 System hooks
+.back_link
+ = link_to :back do
+ &larr; back
+%hr
+
+%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.
+ %br
+%h5 Hooks request example:
+= render "admin/hooks/data_ex"
diff --git a/app/views/issues/_issues.html.haml b/app/views/issues/_issues.html.haml
index a20df176afc..17141cc453c 100644
--- a/app/views/issues/_issues.html.haml
+++ b/app/views/issues/_issues.html.haml
@@ -6,7 +6,9 @@
.row
.span7= paginate @issues, :remote => true, :theme => "gitlab"
.span3.right
- %span.cgray.right #{@issues.total_count} issues for this filter
+ %span.cgray.right
+ %span.issue_counter #{@issues.total_count}
+ issues for this filter
- else
%li
%h4.nothing_here_message Nothing to show here
diff --git a/app/views/issues/_show.html.haml b/app/views/issues/_show.html.haml
index 03524901c99..e12c3c1a99c 100644
--- a/app/views/issues/_show.html.haml
+++ b/app/views/issues/_show.html.haml
@@ -12,9 +12,9 @@
= issue.notes.count
- if can? current_user, :modify_issue, issue
- if issue.closed
- = link_to 'Reopen', project_issue_path(issue.project, issue, :issue => {:closed => false }, :status_only => true), :method => :put, :class => "btn small grouped", :remote => true
+ = link_to 'Reopen', project_issue_path(issue.project, issue, :issue => {:closed => false }, :status_only => true), :method => :put, :class => "btn small grouped reopen_issue", :remote => true
- else
- = link_to 'Resolve', project_issue_path(issue.project, issue, :issue => {:closed => true }, :status_only => true), :method => :put, :class => "success btn small grouped", :remote => true
+ = link_to 'Resolve', project_issue_path(issue.project, issue, :issue => {:closed => true }, :status_only => true), :method => :put, :class => "success btn small grouped close_issue", :remote => true
= link_to edit_project_issue_path(issue.project, issue), :class => "btn small edit-issue-link", :remote => true do
%i.icon-edit
Edit
@@ -35,6 +35,4 @@
&nbsp;
- if issue.upvotes > 0
- %span.badge.badge-success= "+#{issue.upvotes}"
-
-
+ %span.badge.badge-success= "+#{issue.upvotes}" \ No newline at end of file
diff --git a/app/views/issues/index.html.haml b/app/views/issues/index.html.haml
index 7328fa88812..fb8b9f8ee8e 100644
--- a/app/views/issues/index.html.haml
+++ b/app/views/issues/index.html.haml
@@ -2,7 +2,7 @@
.issues_content
%h3.page_title
Issues
- %small (#{@issues.total_count})
+ %small (<span class=issue_counter>#{@issues.total_count}</span>)
.right
.span5
- if can? current_user, :write_issue, @project
@@ -45,4 +45,4 @@
:javascript
$(function(){
issuesPage();
- })
+ }) \ No newline at end of file
diff --git a/app/views/keys/new.html.haml b/app/views/keys/new.html.haml
index 277936c6743..02e782b9f85 100644
--- a/app/views/keys/new.html.haml
+++ b/app/views/keys/new.html.haml
@@ -1,4 +1,4 @@
-%h3 New key
+%h3.page_title New key
%hr
= render 'form'
@@ -11,4 +11,4 @@
if( key_mail && key_mail.length > 0 && title.val() == '' ){
$('#key_title').val( key_mail );
}
- }); \ No newline at end of file
+ });
diff --git a/app/views/layouts/_project_menu.html.haml b/app/views/layouts/_project_menu.html.haml
index 62279bb058d..3f58fc5a664 100644
--- a/app/views/layouts/_project_menu.html.haml
+++ b/app/views/layouts/_project_menu.html.haml
@@ -17,14 +17,14 @@
%li{:class => tab_class(:issues)}
= link_to project_issues_filter_path(@project) do
Issues
- %span.count= @project.issues.opened.count
+ %span.count.issue_counter= @project.issues.opened.count
- if @project.repo_exists?
- if @project.merge_requests_enabled
%li{:class => tab_class(:merge_requests)}
= link_to project_merge_requests_path(@project) do
Merge Requests
- %span.count= @project.merge_requests.opened.count
+ %span.count.merge_counter= @project.merge_requests.opened.count
- if @project.wall_enabled
%li{:class => tab_class(:wall)}
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index 8de25821ee7..69304edef3a 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -15,7 +15,7 @@
%li{:class => tab_class(:admin_logs)}
= link_to "Logs", admin_logs_path
%li{:class => tab_class(:admin_emails)}
- = link_to "Emails", admin_emails_path
+ = link_to "Hooks", admin_hooks_path
%li{:class => tab_class(:admin_resque)}
= link_to "Resque", admin_resque_path
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise_layout.html.haml
index c293734b368..c293734b368 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise_layout.html.haml
diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml
index dfe48d68240..957a68bf482 100644
--- a/app/views/layouts/profile.html.haml
+++ b/app/views/layouts/profile.html.haml
@@ -12,16 +12,17 @@
%li{:class => tab_class(:password)}
= link_to "Password", profile_password_path
+ %li{:class => tab_class(:ssh_keys)}
+ = link_to keys_path do
+ SSH Keys
+ %span.count= current_user.keys.count
+
%li{:class => tab_class(:token)}
= link_to "Token", profile_token_path
%li{:class => tab_class(:design)}
= link_to "Design", profile_design_path
- %li{:class => tab_class(:ssh_keys)}
- = link_to keys_path do
- SSH Keys
- %span.count= current_user.keys.count
.content
= yield
diff --git a/app/views/merge_requests/_form.html.haml b/app/views/merge_requests/_form.html.haml
index d69faa142d5..4f20a06fd25 100644
--- a/app/views/merge_requests/_form.html.haml
+++ b/app/views/merge_requests/_form.html.haml
@@ -5,7 +5,8 @@
- @merge_request.errors.full_messages.each do |msg|
%li= msg
- %h3.padded.cgray 1. Select Branches
+ %h4.cdark 1. Select Branches
+ %br
.row
.span6
@@ -30,14 +31,21 @@
.bottom_commit
.mr_target_commit
- %h3.padded.cgray 2. Fill info
+ %h4.cdark 2. Fill info
+
.clearfix
- = f.label :assignee_id, "Assign to", :class => "control-label"
- .controls= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { :include_blank => "Select user" }, :style => "width:250px")
+ .main_box
+ .top_box_content
+ = f.label :title do
+ %strong= "Title *"
+ .input= f.text_field :title, :class => "input-xxlarge pad", :maxlength => 255, :rows => 5
+ .middle_box_content
+ = f.label :assignee_id do
+ %i.icon-user
+ Assign to
+ .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { :include_blank => "Select user" }, :style => "width:250px")
.control-group
- = f.label :title, :class => "control-label"
- .controls= f.text_field :title, :class => "input-xxlarge pad", :maxlength => 255, :rows => 5
.form-actions
= f.submit 'Save', :class => "btn-primary btn"
diff --git a/app/views/merge_requests/_merge_request.html.haml b/app/views/merge_requests/_merge_request.html.haml
index d9634a4fcc8..b9a005e08be 100644
--- a/app/views/merge_requests/_merge_request.html.haml
+++ b/app/views/merge_requests/_merge_request.html.haml
@@ -15,12 +15,14 @@
&rarr;
= merge_request.target_branch
= image_tag gravatar_icon(merge_request.author_email), :class => "avatar"
+
+ = link_to project_merge_request_path(merge_request.project, merge_request) do
+ %p.row_title= truncate(merge_request.title, :length => 80)
+
%span.update-author
- %strong= merge_request.author_name
- authored
+ %small.cdark= "##{merge_request.id}"
+ authored by #{merge_request.author_name}
= time_ago_in_words(merge_request.created_at)
ago
- if merge_request.upvotes > 0
%span.badge.badge-success= "+#{merge_request.upvotes}"
- = link_to project_merge_request_path(merge_request.project, merge_request) do
- %p.row_title= truncate(merge_request.title, :length => 80)
diff --git a/app/views/merge_requests/edit.html.haml b/app/views/merge_requests/edit.html.haml
index 9e4f9327cdc..eee148994d7 100644
--- a/app/views/merge_requests/edit.html.haml
+++ b/app/views/merge_requests/edit.html.haml
@@ -1,4 +1,4 @@
-%h3
+%h3.page_title
= "Edit merge request #{@merge_request.id}"
%hr
= render 'form'
diff --git a/app/views/merge_requests/new.html.haml b/app/views/merge_requests/new.html.haml
index efafa45d758..594089995ea 100644
--- a/app/views/merge_requests/new.html.haml
+++ b/app/views/merge_requests/new.html.haml
@@ -1,3 +1,3 @@
-%h3 New Merge Request
+%h3.page_title New Merge Request
%hr
= render 'form'
diff --git a/app/views/merge_requests/show/_commits.html.haml b/app/views/merge_requests/show/_commits.html.haml
index 78fe1a062d3..d10e8fd597a 100644
--- a/app/views/merge_requests/show/_commits.html.haml
+++ b/app/views/merge_requests/show/_commits.html.haml
@@ -1,6 +1,8 @@
- if @commits.present?
.ui-box
- %h5 Commits (#{@commits.count})
+ %h5
+ %i.icon-list
+ Commits (#{@commits.count})
.merge-request-commits
- if @commits.count > 8
%ul.first_mr_commits.unstyled
diff --git a/app/views/merge_requests/show/_mr_box.html.haml b/app/views/merge_requests/show/_mr_box.html.haml
index 3027719d94d..b542dac98e0 100644
--- a/app/views/merge_requests/show/_mr_box.html.haml
+++ b/app/views/merge_requests/show/_mr_box.html.haml
@@ -13,9 +13,10 @@
= image_tag gravatar_icon(@merge_request.author_email), :width => 16, :class => "lil_av"
%strong.author= link_to_merge_request_author(@merge_request)
- %cite.cgray and currently assigned to
- = image_tag gravatar_icon(@merge_request.assignee_email), :width => 16, :class => "lil_av"
- %strong.author= link_to_merge_request_assignee(@merge_request)
+ - if @merge_request.assignee
+ %cite.cgray and currently assigned to
+ = image_tag gravatar_icon(@merge_request.assignee_email), :width => 16, :class => "lil_av"
+ %strong.author= link_to_merge_request_assignee(@merge_request)
- if @merge_request.closed
diff --git a/app/views/notes/_form.html.haml b/app/views/notes/_form.html.haml
index 03774d160b9..f5aa1495796 100644
--- a/app/views/notes/_form.html.haml
+++ b/app/views/notes/_form.html.haml
@@ -32,4 +32,4 @@
%span Any file less than 10 MB
- = f.submit 'Add Comment', :class => "btn primary", :id => "submit_note"
+ = f.submit 'Add Comment', :class => "btn primary submit_note", :id => "submit_note"
diff --git a/app/views/notes/_per_line_form.html.haml b/app/views/notes/_per_line_form.html.haml
index 94c558029d2..8beaf9b5e0c 100644
--- a/app/views/notes/_per_line_form.html.haml
+++ b/app/views/notes/_per_line_form.html.haml
@@ -24,7 +24,7 @@
= check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
%span Commit author
.actions
- = f.submit 'Add note', :class => "btn primary", :id => "submit_note"
+ = f.submit 'Add note', :class => "btn primary submit_note", :id => "submit_note"
= link_to "Close", "#", :class => "btn hide-button"
:javascript
diff --git a/app/views/notes/_reply_button.html.haml b/app/views/notes/_reply_button.html.haml
index f03ba4d7018..db6b35b7ada 100644
--- a/app/views/notes/_reply_button.html.haml
+++ b/app/views/notes/_reply_button.html.haml
@@ -1,3 +1,4 @@
%tr.line_notes_row.reply
%td{:colspan => 3}
+ %i.icon-comment
= link_to "Reply", "#", :class => "line_note_reply_link", "line_code" => line_code, :title => "Add note for this line"
diff --git a/app/views/refs/_tree.html.haml b/app/views/refs/_tree.html.haml
index ee2f278693c..ba0bd69116b 100644
--- a/app/views/refs/_tree.html.haml
+++ b/app/views/refs/_tree.html.haml
@@ -13,7 +13,7 @@
= render :partial => "refs/tree_file", :locals => { :name => tree.name, :content => tree.data, :file => tree }
- else
- contents = tree.contents
- %table#tree-slider.bordered-table.table
+ %table#tree-slider.bordered-table.table{:class => "table_#{@hex_path}" }
%thead
%th Name
%th Last Update
@@ -29,34 +29,39 @@
%td
%td
+ - index = 0
- contents.select{ |i| i.is_a?(Grit::Tree)}.each do |content|
- = render :partial => "refs/tree_item", :locals => { :content => content }
+ = render :partial => "refs/tree_item", :locals => { :content => content, :index => (index += 1) }
- contents.select{ |i| i.is_a?(Grit::Blob)}.each do |content|
- = render :partial => "refs/tree_item", :locals => { :content => content }
+ = render :partial => "refs/tree_item", :locals => { :content => content, :index => (index += 1) }
- contents.select{ |i| i.is_a?(Grit::Submodule)}.each do |content|
- = render :partial => "refs/submodule_item", :locals => { :content => content }
+ = render :partial => "refs/submodule_item", :locals => { :content => content, :index => (index += 1) }
- if content = contents.select{ |c| c.is_a?(Grit::Blob) and c.name =~ /^readme/i }.first
- #tree-readme-holder
- %h3= content.name
- .readme
+ .file_holder#README
+ .file_title
+ %i.icon-file
+ = content.name
+ .file_content.wiki
- if content.name =~ /\.(md|markdown)$/i
= preserve do
= markdown(content.data)
- else
= simple_format(content.data)
-- if params[:path]
- - history_path = tree_file_project_ref_path(@project, @ref, params[:path])
-- else
- - history_path = tree_project_ref_path(@project, @ref)
:javascript
$(function(){
$('select#branch').selectmenu({style:'popup', width:200});
$('select#tag').selectmenu({style:'popup', width:200});
$('.project-refs-select').chosen();
- history.pushState({ path: this.path }, '', "#{history_path}")
+ history.pushState({ path: this.path }, '', "#{@history_path}");
+
+ });
+
+ // Load last commit log for each file in tree
+ $(window).load(function(){
+ ajaxGet('#{@logs_path}');
});
diff --git a/app/views/refs/_tree_commit.html.haml b/app/views/refs/_tree_commit.html.haml
new file mode 100644
index 00000000000..1f2524a4c3a
--- /dev/null
+++ b/app/views/refs/_tree_commit.html.haml
@@ -0,0 +1,3 @@
+- if tm
+ %strong= link_to "[#{tm.user_name}]", project_team_member_path(@project, tm)
+= link_to truncate(content_commit.safe_message, :length => tm ? 30 : 50), project_commit_path(@project, content_commit.id), :class => "tree-commit-link"
diff --git a/app/views/refs/_tree_file.html.haml b/app/views/refs/_tree_file.html.haml
index ee56ab36194..d45a03df8aa 100644
--- a/app/views/refs/_tree_file.html.haml
+++ b/app/views/refs/_tree_file.html.haml
@@ -1,5 +1,5 @@
-.view_file
- .view_file_header
+.file_holder
+ .file_title
%i.icon-file
%span.file_name
= name
@@ -10,26 +10,28 @@
= link_to "blame", blame_file_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small"
- if file.text?
- if name =~ /\.(md|markdown)$/i
- #tree-readme-holder
- .readme
- = preserve do
- = markdown(file.data)
+ .file_content.wiki
+ = preserve do
+ = markdown(file.data)
- else
- .view_file_content
+ .file_content.code
- unless file.empty?
%div{:class => current_user.dark_scheme ? "black" : "white"}
= preserve do
= raw file.colorize(options: { linenos: 'True'})
- else
%h4.nothing_here_message Empty file
+
- elsif file.image?
- .view_file_content_image
+ .file_content.image_file
%img{ :src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
+
- else
- %center
- = link_to blob_project_ref_path(@project, @ref, :path => params[:path]) do
- %div.padded
- %br
- = image_tag "download.png", :width => 64
- %h3
- Download (#{file.mb_size})
+ .file_content.blob_file
+ %center
+ = link_to blob_project_ref_path(@project, @ref, :path => params[:path]) do
+ %div.padded
+ %br
+ = image_tag "download.png", :width => 64
+ %h3
+ Download (#{file.mb_size})
diff --git a/app/views/refs/_tree_item.html.haml b/app/views/refs/_tree_item.html.haml
index fe2f7293347..4ce16b22ddd 100644
--- a/app/views/refs/_tree_item.html.haml
+++ b/app/views/refs/_tree_item.html.haml
@@ -1,23 +1,11 @@
-- file = params[:path] ? File.join(params[:path], content.name) : content.name
-- content_commit = @project.commits(@commit.id, file, 1).last
-- return unless content_commit
-%tr{ :class => "tree-item", :url => tree_file_project_ref_path(@project, @ref, file) }
+- file = tree_full_path(content)
+%tr{ :class => "tree-item #{tree_hex_class(content)}", :url => tree_file_project_ref_path(@project, @ref, file) }
%td.tree-item-file-name
- - if content.is_a?(Grit::Blob)
- - if content.text?
- = image_tag "file_txt.png"
- - elsif content.image?
- = image_tag "file_img.png"
- - else
- = image_tag "file_bin.png"
- - else
- = image_tag "file_dir.png"
+ = tree_icon(content)
= link_to truncate(content.name, :length => 40), tree_file_project_ref_path(@project, @ref || @commit.id, file), :remote => :true
- %td.cgray
- = time_ago_in_words(content_commit.committed_date)
- ago
- %td.commit
- - tm = @project.team_member_by_name_or_email(content_commit.author_email, content_commit.author_name)
- - if tm
- %strong= link_to "[#{tm.user_name}]", project_team_member_path(@project, tm)
- = link_to truncate(content_commit.safe_message, :length => tm ? 30 : 50), project_commit_path(@project, content_commit.id), :class => "tree-commit-link"
+ %td.tree_time_ago.cgray
+ - if index == 1
+ %span.log_loading
+ Loading commit data..
+ = image_tag "ajax_loader_tree.gif", :width => 14
+ %td.tree_commit
diff --git a/app/views/refs/blame.html.haml b/app/views/refs/blame.html.haml
index 7307d557a30..6a86b91fe74 100644
--- a/app/views/refs/blame.html.haml
+++ b/app/views/refs/blame.html.haml
@@ -11,8 +11,8 @@
%li= link
.clear
- .view_file.blame_file
- .view_file_header
+ .file_holder
+ .file_title
%i.icon-file
%span.file_name
= @tree.name
@@ -21,7 +21,7 @@
= link_to "raw", blob_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small", :target => "_blank"
= link_to "history", project_commits_path(@project, :path => params[:path], :ref => @ref), :class => "btn very_small"
= link_to "source", tree_file_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small"
- .view_file_content
+ .file_content.blame
%table
- @blame.each do |commit, lines|
- commit = Commit.new(commit)
@@ -29,7 +29,7 @@
%td.author
= image_tag gravatar_icon(commit.author_email, 16)
= commit.author_name
- %td.commit
+ %td.blame_commit
&nbsp;
= link_to project_commit_path(@project, :id => commit.id) do
%code= commit.id.to_s[0..10]
@@ -37,8 +37,7 @@
%td.lines
= preserve do
%pre
- - lines.each do |line|
- = line
+ = Gitlab::Encode.utf8 lines.join("\n")
:javascript
$(function(){
diff --git a/app/views/refs/logs_tree.js.haml b/app/views/refs/logs_tree.js.haml
new file mode 100644
index 00000000000..402f5aa72bc
--- /dev/null
+++ b/app/views/refs/logs_tree.js.haml
@@ -0,0 +1,9 @@
+- @logs.each do |content_data|
+ - file_name = content_data[:file_name]
+ - content_commit = content_data[:commit]
+ - tm = @project.team_member_by_name_or_email(content_commit.author_email, content_commit.author_name)
+
+ :plain
+ var row = $("table.table_#{@hex_path} tr.file_#{hexdigest(file_name)}");
+ row.find("td.tree_time_ago").html('#{escape_javascript(time_ago_in_words(content_commit.committed_date))} ago');
+ row.find("td.tree_commit").html('#{escape_javascript(render("tree_commit", :tm => tm, :content_commit => content_commit))}');
diff --git a/app/views/refs/tree.js.haml b/app/views/refs/tree.js.haml
index f4f28adc369..9cf55057a6a 100644
--- a/app/views/refs/tree.js.haml
+++ b/app/views/refs/tree.js.haml
@@ -1,4 +1,10 @@
:plain
+ // Load Files list
$("#tree-holder").html("#{escape_javascript(render(:partial => "tree", :locals => {:repo => @repo, :commit => @commit, :tree => @tree}))}");
$("#tree-content-holder").show("slide", { direction: "right" }, 150);
$('.project-refs-form #path').val("#{params[:path]}");
+
+ // Load last commit log for each file in tree
+ $('#tree-slider').waitForImages(function() {
+ ajaxGet('#{@logs_path}');
+ });
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index c934e262488..b266e4d2156 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -7,16 +7,14 @@
= link_to "Edit", edit_project_snippet_path(@project, @snippet), :class => "btn small right"
%br
-#tree-holder
- #tree-content-holder
- .view_file
- .view_file_header
- %i.icon-file
- %strong= @snippet.file_name
- %span.options
- = link_to "raw", raw_project_snippet_path(@project, @snippet), :class => "btn very_small", :target => "_blank"
- .view_file_content
- %div{:class => current_user.dark_scheme ? "black" : ""}
- = raw @snippet.colorize(options: { linenos: 'True'})
+.file_holder
+ .file_title
+ %i.icon-file
+ %strong= @snippet.file_name
+ %span.options
+ = link_to "raw", raw_project_snippet_path(@project, @snippet), :class => "btn very_small", :target => "_blank"
+ .file_content.code
+ %div{:class => current_user.dark_scheme ? "black" : ""}
+ = raw @snippet.colorize(options: { linenos: 'True'})
= render "notes/notes", :tid => @snippet.id, :tt => "snippet"
diff --git a/app/workers/system_hook_worker.rb b/app/workers/system_hook_worker.rb
new file mode 100644
index 00000000000..ca154136b97
--- /dev/null
+++ b/app/workers/system_hook_worker.rb
@@ -0,0 +1,7 @@
+class SystemHookWorker
+ @queue = :system_hook
+
+ def self.perform(hook_id, data)
+ SystemHook.find(hook_id).execute data
+ end
+end
diff --git a/config/application.rb b/config/application.rb
index 3585c4b0a87..937262237e9 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
+ config.active_record.observers = :mailer_observer, :activity_observer, :project_observer, :key_observer, :issue_observer, :user_observer, :system_hook_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/gitlab.yml.example b/config/gitlab.yml.example
index 12c28675139..1818f2c0d01 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -21,6 +21,8 @@ email:
# Like default project limit for user etc
app:
default_projects_limit: 10
+ # backup_path: "/vol/backups" # default: Rails.root + backups/
+ # backup_keep_time: 604800 # default: 0 (forever) (in seconds)
#
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 8b9ed8aebd6..5c5987a8857 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -95,11 +95,21 @@ class Settings < Settingslogic
end
def gitolite_admin_uri
- git['admin_uri'] || 'git@localhost:gitolite-admin'
+ git_host['admin_uri'] || 'git@localhost:gitolite-admin'
end
def default_projects_limit
app['default_projects_limit'] || 10
end
+
+ def backup_path
+ t = app['backup_path'] || "backups/"
+ t = /^\//.match(t) ? t : File.join(Rails.root + t)
+ t
+ end
+
+ def backup_keep_time
+ app['backup_keep_time'] || 0
+ end
end
end
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index cb1ae0ac0be..54011ba5ea3 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -93,10 +93,6 @@ Devise.setup do |config|
# If true, extends the user's remember period when remembered via cookie.
# config.extend_remember_period = false
- # If true, uses the password salt as remember token. This should be turned
- # to false if you are not using database authenticatable.
- config.use_salt_as_remember_token = true
-
# Options to be passed to the created cookie. For instance, you can set
# :secure => true in order to force SSL only cookies.
# config.cookie_options = {}
@@ -119,7 +115,7 @@ Devise.setup do |config|
# Defines which strategy will be used to lock an account.
# :failed_attempts = Locks an account after a number of failed attempts to sign in.
# :none = No lock strategy. You should handle locking by yourself.
- # config.lock_strategy = :failed_attempts
+ config.lock_strategy = :failed_attempts
# Defines which key will be used when locking and unlocking an account
# config.unlock_keys = [ :email ]
@@ -129,14 +125,14 @@ Devise.setup do |config|
# :time = Re-enables login after a certain amount of time (see :unlock_in below)
# :both = Enables both strategies
# :none = No unlock strategy. You should handle unlocking by yourself.
- # config.unlock_strategy = :both
+ config.unlock_strategy = :time
# Number of authentication tries before locking an account if lock_strategy
# is failed attempts.
- # config.maximum_attempts = 20
+ config.maximum_attempts = 10
# Time interval to unlock the account if :time is enabled as unlock_strategy.
- # config.unlock_in = 1.hour
+ config.unlock_in = 10.minutes
# ==> Configuration for :recoverable
#
@@ -160,9 +156,9 @@ Devise.setup do |config|
# Defines name of the authentication token params key
config.token_authentication_key = :private_token
- # If true, authentication through token does not store user in session and needs
+ # Authentication through token does not store user in session and needs
# to be supplied on each request. Useful if you are using the token as API token.
- config.stateless_token = true
+ config.skip_session_storage << :token_auth
# ==> Scopes configuration
# Turn scoped views on. Before rendering "sessions/new", it will first check for
diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml
index b18263510f8..a78cb6b670b 100644
--- a/config/locales/devise.en.yml
+++ b/config/locales/devise.en.yml
@@ -35,13 +35,11 @@ en:
confirmed: 'Your account was successfully confirmed. You are now signed in.'
registrations:
signed_up: 'Welcome! You have signed up successfully.'
- inactive_signed_up: 'You have signed up successfully. However, we could not sign you in because your account is %{reason}.'
updated: 'You updated your account successfully.'
destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.'
- reasons:
- inactive: 'inactive'
- unconfirmed: 'unconfirmed'
- locked: 'locked'
+ signed_up_but_unconfirmed: 'A message with a confirmation link has been sent to your email address. Please open the link to activate your account.'
+ signed_up_but_inactive: 'You have signed up successfully. However, we could not sign you in because your account is not yet activated.'
+ signed_up_but_locked: 'You have signed up successfully. However, we could not sign you in because your account is locked.'
unlocks:
send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.'
unlocked: 'Your account was successfully unlocked. You are now signed in.'
diff --git a/config/routes.rb b/config/routes.rb
index af99109883f..dea4df46a30 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -26,7 +26,9 @@ Gitlab::Application.routes.draw do
get 'help' => 'help#index'
get 'help/permissions' => 'help#permissions'
get 'help/workflow' => 'help#workflow'
+ get 'help/api' => 'help#api'
get 'help/web_hooks' => 'help#web_hooks'
+ get 'help/system_hooks' => 'help#system_hooks'
#
# Admin Area
@@ -46,11 +48,13 @@ Gitlab::Application.routes.draw do
end
end
resources :team_members, :only => [:edit, :update, :destroy]
- get 'emails', :to => 'mailer#preview'
get 'mailer/preview_note'
get 'mailer/preview_user_new'
get 'mailer/preview_issue_new'
+ resources :hooks, :only => [:index, :create, :destroy] do
+ get :test
+ end
resource :logs
resource :resque, :controller => 'resque'
root :to => "dashboard#index"
@@ -116,6 +120,8 @@ Gitlab::Application.routes.draw do
member do
get "tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ }
+ get "logs_tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ }
+
get "blob",
:constraints => {
:id => /[a-zA-Z.0-9\/_\-]+/,
@@ -131,6 +137,14 @@ Gitlab::Application.routes.draw do
:path => /.*/
}
+ # tree viewer
+ get "logs_tree/:path" => "refs#logs_tree",
+ :as => :logs_file,
+ :constraints => {
+ :id => /[a-zA-Z.0-9\/_\-]+/,
+ :path => /.*/
+ }
+
# blame
get "blame/:path" => "refs#blame",
:as => :blame_file,
diff --git a/db/migrate/20110913200833_devise_create_users.rb b/db/migrate/20110913200833_devise_create_users.rb
index 01869a9e21c..e00f275c55d 100644
--- a/db/migrate/20110913200833_devise_create_users.rb
+++ b/db/migrate/20110913200833_devise_create_users.rb
@@ -1,15 +1,43 @@
class DeviseCreateUsers < ActiveRecord::Migration
def self.up
create_table(:users) do |t|
- t.database_authenticatable :null => false
- t.recoverable
- t.rememberable
- t.trackable
-
- # t.encryptable
- # t.confirmable
- # t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both
- # t.token_authenticatable
+ ## Database authenticatable
+ t.string :email, :null => false, :default => ""
+ t.string :encrypted_password, :null => false, :default => ""
+
+ ## Recoverable
+ t.string :reset_password_token
+ t.datetime :reset_password_sent_at
+
+ ## Rememberable
+ t.datetime :remember_created_at
+
+ ## Trackable
+ t.integer :sign_in_count, :default => 0
+ t.datetime :current_sign_in_at
+ t.datetime :last_sign_in_at
+ t.string :current_sign_in_ip
+ t.string :last_sign_in_ip
+
+ ## Encryptable
+ # t.string :password_salt
+
+ ## Confirmable
+ # t.string :confirmation_token
+ # t.datetime :confirmed_at
+ # t.datetime :confirmation_sent_at
+ # t.string :unconfirmed_email # Only if using reconfirmable
+
+ ## Lockable
+ # t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts
+ # t.string :unlock_token # Only if unlock strategy is :email or :both
+ # t.datetime :locked_at
+
+ # Token authenticatable
+ # t.string :authentication_token
+
+ ## Invitable
+ # t.string :invitation_token
t.timestamps
end
diff --git a/db/migrate/20120706065612_add_lockable_to_users.rb b/db/migrate/20120706065612_add_lockable_to_users.rb
new file mode 100644
index 00000000000..cf86e660876
--- /dev/null
+++ b/db/migrate/20120706065612_add_lockable_to_users.rb
@@ -0,0 +1,6 @@
+class AddLockableToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :failed_attempts, :integer, :default => 0
+ add_column :users, :locked_at, :datetime
+ end
+end
diff --git a/db/migrate/20120712080407_add_type_to_web_hook.rb b/db/migrate/20120712080407_add_type_to_web_hook.rb
new file mode 100644
index 00000000000..18ab024c817
--- /dev/null
+++ b/db/migrate/20120712080407_add_type_to_web_hook.rb
@@ -0,0 +1,5 @@
+class AddTypeToWebHook < ActiveRecord::Migration
+ def change
+ add_column :web_hooks, :type, :string, :default => "ProjectHook"
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index f2bb16937f4..c4c54f562a3 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 => 20120627145613) do
+ActiveRecord::Schema.define(:version => 20120712080407) do
create_table "events", :force => true do |t|
t.string "target_type"
@@ -169,6 +169,8 @@ ActiveRecord::Schema.define(:version => 20120627145613) do
t.integer "theme_id", :default => 1, :null => false
t.string "bio"
t.boolean "blocked", :default => false, :null => false
+ t.integer "failed_attempts", :default => 0
+ t.datetime "locked_at"
end
add_index "users", ["email"], :name => "index_users_on_email", :unique => true
@@ -185,8 +187,9 @@ ActiveRecord::Schema.define(:version => 20120627145613) do
create_table "web_hooks", :force => true do |t|
t.string "url"
t.integer "project_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "type", :default => "ProjectHook"
end
create_table "wikis", :force => true do |t|
diff --git a/doc/installation.md b/doc/installation.md
index bf579b174c3..3dfedfe10ad 100644
--- a/doc/installation.md
+++ b/doc/installation.md
@@ -60,7 +60,7 @@ Also read the [Read this before you submit an issue](https://github.com/gitlabhq
sudo apt-get update
sudo apt-get upgrade
- sudo apt-get install -y wget curl gcc checkinstall libxml2-dev libxslt-dev sqlite3 libsqlite3-dev libcurl4-openssl-dev libreadline-gplv2-dev libc6-dev libssl-dev libmysql++-dev make build-essential zlib1g-dev libicu-dev redis-server openssh-server git-core python-dev python-pip libyaml-dev sendmail
+ sudo apt-get install -y wget curl gcc checkinstall libxml2-dev libxslt-dev sqlite3 libsqlite3-dev libcurl4-openssl-dev libreadline6-dev libc6-dev libssl-dev libmysql++-dev make build-essential zlib1g-dev libicu-dev redis-server openssh-server git-core python-dev python-pip libyaml-dev sendmail
# If you want to use MySQL:
sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
@@ -107,10 +107,10 @@ Get gitolite source code:
Setup:
- sudo -u git sh -c 'echo -e "PATH=\$PATH:/home/git/bin\nexport PATH" > /home/git/.profile'
+ sudo -u git sh -c 'echo -e "PATH=\$PATH:/home/git/bin\nexport PATH" >> /home/git/.profile'
sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; /home/git/gitolite/src/gl-system-install"
sudo cp /home/gitlab/.ssh/id_rsa.pub /home/git/gitlab.pub
- sudo chmod 777 /home/git/gitlab.pub
+ sudo chmod 0444 /home/git/gitlab.pub
sudo -u git -H sed -i 's/0077/0007/g' /home/git/share/gitolite/conf/example.gitolite.rc
sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; gl-setup -q /home/git/gitlab.pub"
@@ -139,6 +139,8 @@ Permissions:
cd /home/gitlab
sudo -H -u gitlab git clone -b stable git://github.com/gitlabhq/gitlabhq.git gitlab
cd gitlab
+
+ sudo -u gitlab mkdir tmp
# Rename config files
sudo -u gitlab cp config/gitlab.yml.example config/gitlab.yml
@@ -216,15 +218,15 @@ Application can be started with next command:
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. Add in **http** section:
+Edit /etc/nginx/nginx.conf. In the *http* section add:
upstream gitlab {
server unix:/home/gitlab/gitlab/tmp/sockets/gitlab.socket;
}
server {
- listen YOUR_SERVER_IP:80;
- server_name gitlab.YOUR_DOMAIN.com;
+ 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
@@ -232,26 +234,26 @@ Edit /etc/nginx/nginx.conf. Add in **http** section:
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;
+ # 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 gitlab.YOUR_SUBDOMAIN.com:80;
+ proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://gitlab;
}
-
}
-gitlab.YOUR_DOMAIN.com - change to your domain.
+Change **YOUR_SERVER_IP** and **YOUR_SERVER_FQDN** to the IP address and fully-qualified domain name of the host serving GitLab.
Restart nginx:
diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb
index e8d20ad90c2..aff13baf67b 100644
--- a/lib/gitlab/logger.rb
+++ b/lib/gitlab/logger.rb
@@ -1,14 +1,24 @@
module Gitlab
- class Logger
+ class Logger < ::Logger
def self.error(message)
- @@logger ||= ::Logger.new(File.join(Rails.root, "log/githost.log"))
- message = Time.now.to_s(:long) + " -> " + message
- @@logger.error(message)
+ build.error(message)
+ end
+
+ def self.info(message)
+ build.info(message)
end
def self.read_latest
path = Rails.root.join("log/githost.log")
- logs = `tail -n 50 #{path}`.split("\n")
+ logs = File.read(path).split("\n")
end
+
+ def self.build
+ new(File.join(Rails.root, "log/githost.log"))
+ end
+
+ def format_message(severity, timestamp, progname, msg)
+ "#{timestamp.to_s(:long)} -> #{severity} -> #{msg}\n"
+ end
end
end
diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake
new file mode 100644
index 00000000000..014483d4e8c
--- /dev/null
+++ b/lib/tasks/gitlab/backup.rake
@@ -0,0 +1,190 @@
+require 'active_record/fixtures'
+
+namespace :gitlab do
+ namespace :app do
+
+ # Create backup of gitlab system
+ desc "GITLAB | Create a backup of the gitlab system"
+ task :backup_create => :environment do
+
+ Rake::Task["gitlab:app:db_dump"].invoke
+ Rake::Task["gitlab:app:repo_dump"].invoke
+
+ Dir.chdir(Gitlab.config.backup_path)
+
+ # saving additional informations
+ s = Hash.new
+ s["db_version"] = "#{ActiveRecord::Migrator.current_version}"
+ s["backup_created_at"] = "#{Time.now}"
+ s["gitlab_version"] = %x{git rev-parse HEAD}.gsub(/\n/,"")
+ s["tar_version"] = %x{tar --version | head -1}.gsub(/\n/,"")
+
+ File.open("#{Gitlab.config.backup_path}/backup_information.yml", "w+") do |file|
+ file << s.to_yaml.gsub(/^---\n/,'')
+ end
+
+ # create archive
+ print "Creating backup archive: #{Time.now.to_i}_gitlab_backup.tar "
+ if Kernel.system("tar -cf #{Time.now.to_i}_gitlab_backup.tar repositories/ db/ backup_information.yml")
+ puts "[DONE]".green
+ else
+ puts "[FAILED]".red
+ end
+
+ # cleanup: remove tmp files
+ print "Deletion of tmp directories..."
+ if Kernel.system("rm -rf repositories/ db/ backup_information.yml")
+ puts "[DONE]".green
+ else
+ puts "[FAILED]".red
+ end
+
+ # delete backups
+ print "Deleting old backups... "
+ if Gitlab.config.backup_keep_time > 0
+ file_list = Dir.glob("*_gitlab_backup.tar").map { |f| f.split(/_/).first.to_i }
+ file_list.sort.each do |timestamp|
+ if Time.at(timestamp) < (Time.now - Gitlab.config.backup_keep_time)
+ %x{rm #{timestamp}_gitlab_backup.tar}
+ end
+ end
+ puts "[DONE]".green
+ else
+ puts "[SKIPPING]".yellow
+ end
+
+ end
+
+
+ # Restore backup of gitlab system
+ desc "GITLAB | Restore a previously created backup"
+ task :backup_restore => :environment do
+
+ Dir.chdir(Gitlab.config.backup_path)
+
+ # check for existing backups in the backup dir
+ file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i }
+ puts "no backup found" if file_list.count == 0
+ if file_list.count > 1 && ENV["BACKUP"].nil?
+ puts "Found more than one backup, please specify which one you want to restore:"
+ puts "rake gitlab:app:backup_restore BACKUP=timestamp_of_backup"
+ exit 1;
+ end
+
+ tar_file = ENV["BACKUP"].nil? ? File.join(file_list.first.to_s + "_gitlab_backup.tar") : File.join(ENV["BACKUP"] + "_gitlab_backup.tar")
+
+ unless File.exists?(tar_file)
+ puts "The specified backup doesn't exist!"
+ exit 1;
+ end
+
+ print "Unpacking backup... "
+ unless Kernel.system("tar -xf #{tar_file}")
+ puts "[FAILED]".red
+ exit 1
+ else
+ puts "[DONE]".green
+ end
+
+ settings = YAML.load_file("backup_information.yml")
+ ENV["VERSION"] = "#{settings["db_version"]}" if settings["db_version"].to_i > 0
+
+ # restoring mismatching backups can lead to unexpected problems
+ if settings["gitlab_version"] != %x{git rev-parse HEAD}.gsub(/\n/,"")
+ puts "gitlab_version mismatch:".red
+ puts " Your current HEAD differs from the HEAD in the backup!".red
+ puts " Please switch to the following revision and try again:".red
+ puts " revision: #{settings["gitlab_version"]}".red
+ exit 1
+ end
+
+ Rake::Task["gitlab:app:db_restore"].invoke
+ Rake::Task["gitlab:app:repo_restore"].invoke
+
+ # cleanup: remove tmp files
+ print "Deletion of tmp directories..."
+ if Kernel.system("rm -rf repositories/ db/ backup_information.yml")
+ puts "[DONE]".green
+ else
+ puts "[FAILED]".red
+ end
+
+ end
+
+
+ ################################################################################
+ ################################# invoked tasks ################################
+
+ ################################# REPOSITORIES #################################
+
+ task :repo_dump => :environment do
+ backup_path_repo = File.join(Gitlab.config.backup_path, "repositories")
+ FileUtils.mkdir_p(backup_path_repo) until Dir.exists?(backup_path_repo)
+ puts "Dumping repositories:"
+ project = Project.all.map { |n| [n.name,n.path_to_repo] }
+ project << ["gitolite-admin.git", File.join(File.dirname(project.first.second), "gitolite-admin.git")]
+ project.each do |project|
+ print "- Dumping repository #{project.first}... "
+ if Kernel.system("cd #{project.second} > /dev/null 2>&1 && git bundle create #{backup_path_repo}/#{project.first}.bundle --all > /dev/null 2>&1")
+ puts "[DONE]".green
+ else
+ puts "[FAILED]".red
+ end
+ end
+ end
+
+ task :repo_restore => :environment do
+ backup_path_repo = File.join(Gitlab.config.backup_path, "repositories")
+ puts "Restoring repositories:"
+ project = Project.all.map { |n| [n.name,n.path_to_repo] }
+ project << ["gitolite-admin.git", File.join(File.dirname(project.first.second), "gitolite-admin.git")]
+ project.each do |project|
+ print "- Restoring repository #{project.first}... "
+ FileUtils.rm_rf(project.second) if File.dirname(project.second) # delet old stuff
+ 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")
+ puts "[DONE]".green
+ else
+ puts "[FAILED]".red
+ end
+ end
+ end
+
+ ###################################### DB ######################################
+
+ task :db_dump => :environment do
+ backup_path_db = File.join(Gitlab.config.backup_path, "db")
+ FileUtils.mkdir_p(backup_path_db) until Dir.exists?(backup_path_db)
+ puts "Dumping database tables:"
+ ActiveRecord::Base.connection.tables.each do |tbl|
+ print "- Dumping table #{tbl}... "
+ count = 1
+ File.open(File.join(backup_path_db, tbl + ".yml"), "w+") do |file|
+ ActiveRecord::Base.connection.select_all("SELECT * FROM `#{tbl}`").each do |line|
+ line.delete_if{|k,v| v.blank?}
+ output = {tbl + '_' + count.to_s => line}
+ file << output.to_yaml.gsub(/^---\n/,'') + "\n"
+ count += 1
+ end
+ puts "[DONE]".green
+ end
+ end
+ end
+
+ task :db_restore=> :environment do
+ backup_path_db = File.join(Gitlab.config.backup_path, "db")
+ puts "Restoring database tables:"
+ Rake::Task["db:reset"].invoke
+ Dir.glob(File.join(backup_path_db, "*.yml") ).each do |dir|
+ fixture_file = File.basename(dir, ".*" )
+ print "- Loading fixture #{fixture_file}..."
+ if File.size(dir) > 0
+ ActiveRecord::Fixtures.create_fixtures(backup_path_db, fixture_file)
+ puts "[DONE]".green
+ else
+ puts "[SKIPPING]".yellow
+ end
+ end
+ end
+
+ end # namespace end: app
+end # namespace end: gitlab
diff --git a/resque.sh b/resque.sh
index ce7c944b735..ab67c650805 100755
--- a/resque.sh
+++ b/resque.sh
@@ -1,2 +1,2 @@
mkdir -p tmp/pids
-bundle exec rake environment resque:work QUEUE=post_receive,mailer RAILS_ENV=production PIDFILE=tmp/pids/resque_worker.pid BACKGROUND=yes
+bundle exec rake environment resque:work QUEUE=post_receive,mailer,system_hook RAILS_ENV=production PIDFILE=tmp/pids/resque_worker.pid BACKGROUND=yes
diff --git a/resque_dev.sh b/resque_dev.sh
index 9df4dc1d087..b09cfd9e383 100755
--- a/resque_dev.sh
+++ b/resque_dev.sh
@@ -1 +1 @@
-bundle exec rake environment resque:work QUEUE=* VVERBOSE=1
+bundle exec rake environment resque:work QUEUE=post_receive,mailer,system_hook VVERBOSE=1
diff --git a/spec/api/projects_spec.rb b/spec/api/projects_spec.rb
index a4e875f73c6..9998ee509bf 100644
--- a/spec/api/projects_spec.rb
+++ b/spec/api/projects_spec.rb
@@ -78,7 +78,7 @@ describe Gitlab::API do
end
describe "DELETE /projects/:id/snippets/:snippet_id" do
- it "should create a new project snippet" do
+ it "should delete existing project snippet" do
expect {
delete "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}?private_token=#{user.private_token}"
}.should change { Snippet.count }.by(-1)
diff --git a/spec/factories.rb b/spec/factories.rb
index ea8c7aef0e2..ab2ca4687da 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -7,6 +7,12 @@ Factory.add(:project, Project) do |obj|
obj.code = 'LGT'
end
+Factory.add(:project_without_owner, Project) do |obj|
+ obj.name = Faker::Internet.user_name
+ obj.path = 'gitlabhq'
+ obj.code = 'LGT'
+end
+
Factory.add(:public_project, Project) do |obj|
obj.name = Faker::Internet.user_name
obj.path = 'gitlabhq'
@@ -60,7 +66,11 @@ Factory.add(:key, Key) do |obj|
obj.key = File.read(File.join(Rails.root, "db", "pkey.example"))
end
-Factory.add(:web_hook, WebHook) do |obj|
+Factory.add(:project_hook, ProjectHook) do |obj|
+ obj.url = Faker::Internet.uri("http")
+end
+
+Factory.add(:system_hook, SystemHook) do |obj|
obj.url = Faker::Internet.uri("http")
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 8d750bef5a5..ac986ccebe3 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -13,7 +13,6 @@ describe MergeRequest 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(:assignee_id) }
end
describe "Scope" do
diff --git a/spec/models/project_hooks_spec.rb b/spec/models/project_hooks_spec.rb
index fcc969ceba5..5544c5a8683 100644
--- a/spec/models/project_hooks_spec.rb
+++ b/spec/models/project_hooks_spec.rb
@@ -21,44 +21,44 @@ describe Project, "Hooks" do
end
end
- describe "Web hooks" do
+ describe "Project hooks" do
context "with no web hooks" do
it "raises no errors" do
lambda {
- project.execute_web_hooks('oldrev', 'newrev', 'ref', @user)
+ project.execute_hooks('oldrev', 'newrev', 'ref', @user)
}.should_not raise_error
end
end
context "with web hooks" do
before do
- @webhook = Factory(:web_hook)
- @webhook_2 = Factory(:web_hook)
- project.web_hooks << [@webhook, @webhook_2]
+ @project_hook = Factory(:project_hook)
+ @project_hook_2 = Factory(:project_hook)
+ project.hooks << [@project_hook, @project_hook_2]
end
it "executes multiple web hook" do
- @webhook.should_receive(:execute).once
- @webhook_2.should_receive(:execute).once
+ @project_hook.should_receive(:execute).once
+ @project_hook_2.should_receive(:execute).once
- project.execute_web_hooks('oldrev', 'newrev', 'refs/heads/master', @user)
+ project.execute_hooks('oldrev', 'newrev', 'refs/heads/master', @user)
end
end
context "does not execute web hooks" do
before do
- @webhook = Factory(:web_hook)
- project.web_hooks << [@webhook]
+ @project_hook = Factory(:project_hook)
+ project.hooks << [@project_hook]
end
it "when pushing a branch for the first time" do
- @webhook.should_not_receive(:execute)
- project.execute_web_hooks('00000000000000000000000000000000', 'newrev', 'refs/heads/master', @user)
+ @project_hook.should_not_receive(:execute)
+ project.execute_hooks('00000000000000000000000000000000', 'newrev', 'refs/heads/master', @user)
end
it "when pushing tags" do
- @webhook.should_not_receive(:execute)
- project.execute_web_hooks('oldrev', 'newrev', 'refs/tags/v1.0.0', @user)
+ @project_hook.should_not_receive(:execute)
+ project.execute_hooks('oldrev', 'newrev', 'refs/tags/v1.0.0', @user)
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 381fe7592c9..351f5748b4d 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -11,7 +11,7 @@ describe Project do
it { should have_many(:issues).dependent(:destroy) }
it { should have_many(:notes).dependent(:destroy) }
it { should have_many(:snippets).dependent(:destroy) }
- it { should have_many(:web_hooks).dependent(:destroy) }
+ it { should have_many(:hooks).dependent(:destroy) }
it { should have_many(:deploy_keys).dependent(:destroy) }
end
diff --git a/spec/models/system_hook_spec.rb b/spec/models/system_hook_spec.rb
new file mode 100644
index 00000000000..4ad4d1681fc
--- /dev/null
+++ b/spec/models/system_hook_spec.rb
@@ -0,0 +1,63 @@
+require "spec_helper"
+
+describe SystemHook do
+ describe "execute" do
+ before(:each) { ActiveRecord::Base.observers.enable(:all) }
+
+ before(:each) do
+ @system_hook = Factory :system_hook
+ WebMock.stub_request(:post, @system_hook.url)
+ end
+
+ it "project_create hook" do
+ user = Factory :user
+ with_resque do
+ project = Factory :project_without_owner, :owner => user
+ 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
+ project.destroy
+ end
+ WebMock.should have_requested(:post, @system_hook.url).with(body: /project_destroy/).once
+ end
+
+ it "user_create hook" do
+ with_resque do
+ Factory :user
+ 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
+ user.destroy
+ 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
+ with_resque do
+ project.users << user
+ 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
+ project.users << user
+ with_resque do
+ project.users_projects.clear
+ end
+ WebMock.should have_requested(:post, @system_hook.url).with(body: /user_remove_from_team/).once
+ end
+ end
+
+end
diff --git a/spec/models/web_hook_spec.rb b/spec/models/web_hook_spec.rb
index 9971bd5819d..885947614d7 100644
--- a/spec/models/web_hook_spec.rb
+++ b/spec/models/web_hook_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe WebHook do
+describe ProjectHook do
describe "Associations" do
it { should belong_to :project }
end
@@ -23,32 +23,32 @@ describe WebHook do
describe "execute" do
before(:each) do
- @webhook = Factory :web_hook
+ @project_hook = Factory :project_hook
@project = Factory :project
- @project.web_hooks << [@webhook]
+ @project.hooks << [@project_hook]
@data = { before: 'oldrev', after: 'newrev', ref: 'ref'}
- WebMock.stub_request(:post, @webhook.url)
+ WebMock.stub_request(:post, @project_hook.url)
end
it "POSTs to the web hook URL" do
- @webhook.execute(@data)
- WebMock.should have_requested(:post, @webhook.url).once
+ @project_hook.execute(@data)
+ WebMock.should have_requested(:post, @project_hook.url).once
end
it "POSTs the data as JSON" do
json = @data.to_json
- @webhook.execute(@data)
- WebMock.should have_requested(:post, @webhook.url).with(body: json).once
+ @project_hook.execute(@data)
+ WebMock.should have_requested(:post, @project_hook.url).with(body: json).once
end
it "catches exceptions" do
WebHook.should_receive(:post).and_raise("Some HTTP Post error")
lambda {
- @webhook.execute(@data)
- }.should_not raise_error
+ @project_hook.execute(@data)
+ }.should raise_error
end
end
end
diff --git a/spec/requests/admin/admin_hooks_spec.rb b/spec/requests/admin/admin_hooks_spec.rb
new file mode 100644
index 00000000000..e8a345b689f
--- /dev/null
+++ b/spec/requests/admin/admin_hooks_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe "Admin::Hooks" do
+ before do
+ @project = Factory :project,
+ :name => "LeGiT",
+ :code => "LGT"
+ login_as :admin
+
+ @system_hook = Factory :system_hook
+
+ end
+
+ describe "GET /admin/hooks" do
+ it "should be ok" do
+ visit admin_root_path
+ within ".main_menu" do
+ click_on "Hooks"
+ end
+ current_path.should == admin_hooks_path
+ end
+
+ it "should have hooks list" do
+ visit admin_hooks_path
+ page.should have_content(@system_hook.url)
+ end
+ end
+
+ describe "New Hook" do
+ before do
+ @url = Faker::Internet.uri("http")
+ visit admin_hooks_path
+ fill_in "hook_url", :with => @url
+ expect { click_button "Add System Hook" }.to change(SystemHook, :count).by(1)
+ end
+
+ it "should open new hook popup" do
+ page.current_path.should == admin_hooks_path
+ page.should have_content(@url)
+ end
+ end
+
+ describe "Test" do
+ before do
+ WebMock.stub_request(:post, @system_hook.url)
+ visit admin_hooks_path
+ click_link "Test Hook"
+ end
+
+ it { page.current_path.should == admin_hooks_path }
+ end
+
+end
diff --git a/spec/requests/admin/security_spec.rb b/spec/requests/admin/security_spec.rb
index 0b0edb85a37..0c369740cff 100644
--- a/spec/requests/admin/security_spec.rb
+++ b/spec/requests/admin/security_spec.rb
@@ -13,9 +13,9 @@ describe "Admin::Projects" do
it { admin_users_path.should be_denied_for :visitor }
end
- describe "GET /admin/emails" do
- it { admin_emails_path.should be_allowed_for :admin }
- it { admin_emails_path.should be_denied_for :user }
- it { admin_emails_path.should be_denied_for :visitor }
+ 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 }
end
end
diff --git a/spec/requests/hooks_spec.rb b/spec/requests/hooks_spec.rb
index a508e5ea517..05432f13f3c 100644
--- a/spec/requests/hooks_spec.rb
+++ b/spec/requests/hooks_spec.rb
@@ -9,7 +9,7 @@ describe "Hooks" do
describe "GET index" do
it "should be available" do
- @hook = Factory :web_hook, :project => @project
+ @hook = Factory :project_hook, :project => @project
visit project_hooks_path(@project)
page.should have_content "Hooks"
page.should have_content @hook.url
@@ -21,7 +21,7 @@ describe "Hooks" do
@url = Faker::Internet.uri("http")
visit project_hooks_path(@project)
fill_in "hook_url", :with => @url
- expect { click_button "Add Web Hook" }.to change(WebHook, :count).by(1)
+ expect { click_button "Add Web Hook" }.to change(ProjectHook, :count).by(1)
end
it "should open new team member popup" do
@@ -32,7 +32,8 @@ describe "Hooks" do
describe "Test" do
before do
- @hook = Factory :web_hook, :project => @project
+ @hook = Factory :project_hook, :project => @project
+ stub_request(:post, @hook.url)
visit project_hooks_path(@project)
click_link "Test Hook"
end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index c70b2f6cebb..5f3b6d3daec 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -22,14 +22,14 @@ describe PostReceive do
Key.stub(find_by_identifier: nil)
project.should_not_receive(:observe_push)
- project.should_not_receive(:execute_web_hooks)
+ project.should_not_receive(:execute_hooks)
PostReceive.perform(project.path, 'sha-old', 'sha-new', 'refs/heads/master', key_id).should be_false
end
it "asks the project to execute web hooks" do
Project.stub(find_by_path: project)
- project.should_receive(:execute_web_hooks).with('sha-old', 'sha-new', 'refs/heads/master', project.owner)
+ project.should_receive(:execute_hooks).with('sha-old', 'sha-new', 'refs/heads/master', project.owner)
PostReceive.perform(project.path, 'sha-old', 'sha-new', 'refs/heads/master', key_id)
end
diff --git a/vendor/assets/javascripts/jquery.waitforimages.js b/vendor/assets/javascripts/jquery.waitforimages.js
new file mode 100644
index 00000000000..95b39c2e074
--- /dev/null
+++ b/vendor/assets/javascripts/jquery.waitforimages.js
@@ -0,0 +1,144 @@
+/*
+ * waitForImages 1.4
+ * -----------------
+ * Provides a callback when all images have loaded in your given selector.
+ * http://www.alexanderdickson.com/
+ *
+ *
+ * Copyright (c) 2011 Alex Dickson
+ * Licensed under the MIT licenses.
+ * See website for more info.
+ *
+ */
+
+;(function($) {
+ // Namespace all events.
+ var eventNamespace = 'waitForImages';
+
+ // CSS properties which contain references to images.
+ $.waitForImages = {
+ hasImageProperties: [
+ 'backgroundImage',
+ 'listStyleImage',
+ 'borderImage',
+ 'borderCornerImage'
+ ]
+ };
+
+ // Custom selector to find `img` elements that have a valid `src` attribute and have not already loaded.
+ $.expr[':'].uncached = function(obj) {
+ // Ensure we are dealing with an `img` element with a valid `src` attribute.
+ if ( ! $(obj).is('img[src!=""]')) {
+ return false;
+ }
+
+ // Firefox's `complete` property will always be`true` even if the image has not been downloaded.
+ // Doing it this way works in Firefox.
+ var img = document.createElement('img');
+ img.src = obj.src;
+ return ! img.complete;
+ };
+
+ $.fn.waitForImages = function(finishedCallback, eachCallback, waitForAll) {
+
+ // Handle options object.
+ if ($.isPlainObject(arguments[0])) {
+ eachCallback = finishedCallback.each;
+ waitForAll = finishedCallback.waitForAll;
+ finishedCallback = finishedCallback.finished;
+ }
+
+ // Handle missing callbacks.
+ finishedCallback = finishedCallback || $.noop;
+ eachCallback = eachCallback || $.noop;
+
+ // Convert waitForAll to Boolean
+ waitForAll = !! waitForAll;
+
+ // Ensure callbacks are functions.
+ if (!$.isFunction(finishedCallback) || !$.isFunction(eachCallback)) {
+ throw new TypeError('An invalid callback was supplied.');
+ };
+
+ return this.each(function() {
+ // Build a list of all imgs, dependent on what images will be considered.
+ var obj = $(this),
+ allImgs = [];
+
+ if (waitForAll) {
+ // CSS properties which may contain an image.
+ var hasImgProperties = $.waitForImages.hasImageProperties || [],
+ matchUrl = /url\((['"]?)(.*?)\1\)/g;
+
+ // Get all elements, as any one of them could have a background image.
+ obj.find('*').each(function() {
+ var element = $(this);
+
+ // If an `img` element, add it. But keep iterating in case it has a background image too.
+ if (element.is('img:uncached')) {
+ allImgs.push({
+ src: element.attr('src'),
+ element: element[0]
+ });
+ }
+
+ $.each(hasImgProperties, function(i, property) {
+ var propertyValue = element.css(property);
+ // If it doesn't contain this property, skip.
+ if ( ! propertyValue) {
+ return true;
+ }
+
+ // Get all url() of this element.
+ var match;
+ while (match = matchUrl.exec(propertyValue)) {
+ allImgs.push({
+ src: match[2],
+ element: element[0]
+ });
+ };
+ });
+ });
+ } else {
+ // For images only, the task is simpler.
+ obj
+ .find('img:uncached')
+ .each(function() {
+ allImgs.push({
+ src: this.src,
+ element: this
+ });
+ });
+ };
+
+ var allImgsLength = allImgs.length,
+ allImgsLoaded = 0;
+
+ // If no images found, don't bother.
+ if (allImgsLength == 0) {
+ finishedCallback.call(obj[0]);
+ };
+
+ $.each(allImgs, function(i, img) {
+
+ var image = new Image;
+
+ // Handle the image loading and error with the same callback.
+ $(image).bind('load.' + eventNamespace + ' error.' + eventNamespace, function(event) {
+ allImgsLoaded++;
+
+ // If an error occurred with loading the image, set the third argument accordingly.
+ eachCallback.call(img.element, allImgsLoaded, allImgsLength, event.type == 'load');
+
+ if (allImgsLoaded == allImgsLength) {
+ finishedCallback.call(obj[0]);
+ return false;
+ };
+
+ });
+
+ image.src = img.src;
+ });
+ });
+ };
+})(jQuery);