Commit eca823c1c7cef45cc18c6ab36d2327650c85bfc3

Authored by Nihad Abbasov
2 parents 024e0348 8b7e404b

Merge branch 'master' into api

Showing 125 changed files with 1752 additions and 666 deletions   Show diff stats
@@ -6,6 +6,7 @@ log/*.log @@ -6,6 +6,7 @@ log/*.log
6 tmp/ 6 tmp/
7 .sass-cache/ 7 .sass-cache/
8 coverage/* 8 coverage/*
  9 +backups/*
9 *.swp 10 *.swp
10 public/uploads/ 11 public/uploads/
11 .rvmrc 12 .rvmrc
1 v 2.7.0 1 v 2.7.0
2 - Issue Labels 2 - Issue Labels
  3 + - Inline diff
  4 + - Git HTTP
  5 + - API
  6 + - UI improved
  7 + - System hooks
  8 + - UI improved
  9 + - Dashboard events endless scroll
  10 + - Source perfomance increased
3 11
4 v 2.6.0 12 v 2.6.0
5 - UI polished 13 - UI polished
@@ -7,7 +7,7 @@ gem "sqlite3" @@ -7,7 +7,7 @@ gem "sqlite3"
7 gem "mysql2" 7 gem "mysql2"
8 8
9 # Auth 9 # Auth
10 -gem "devise", "~> 1.5" 10 +gem "devise", "~> 2.1.0"
11 11
12 # GITLAB patched libs 12 # GITLAB patched libs
13 gem "grit", :git => "https://github.com/gitlabhq/grit.git", :ref => "7f35cb98ff17d534a07e3ce6ec3d580f67402837" 13 gem "grit", :git => "https://github.com/gitlabhq/grit.git", :ref => "7f35cb98ff17d534a07e3ce6ec3d580f67402837"
@@ -71,7 +71,6 @@ group :development, :test do @@ -71,7 +71,6 @@ group :development, :test do
71 gem "awesome_print" 71 gem "awesome_print"
72 gem "database_cleaner" 72 gem "database_cleaner"
73 gem "launchy" 73 gem "launchy"
74 - gem "webmock"  
75 end 74 end
76 75
77 group :test do 76 group :test do
@@ -82,4 +81,5 @@ group :test do @@ -82,4 +81,5 @@ group :test do
82 gem "shoulda-matchers" 81 gem "shoulda-matchers"
83 gem 'email_spec' 82 gem 'email_spec'
84 gem 'resque_spec' 83 gem 'resque_spec'
  84 + gem "webmock"
85 end 85 end
@@ -148,10 +148,11 @@ GEM @@ -148,10 +148,11 @@ GEM
148 nokogiri (>= 1.5.0) 148 nokogiri (>= 1.5.0)
149 daemons (1.1.8) 149 daemons (1.1.8)
150 database_cleaner (0.8.0) 150 database_cleaner (0.8.0)
151 - devise (1.5.3) 151 + devise (2.1.2)
152 bcrypt-ruby (~> 3.0) 152 bcrypt-ruby (~> 3.0)
153 - orm_adapter (~> 0.0.3)  
154 - warden (~> 1.1) 153 + orm_adapter (~> 0.1)
  154 + railties (~> 3.1)
  155 + warden (~> 1.2.1)
155 diff-lcs (1.1.3) 156 diff-lcs (1.1.3)
156 drapper (0.8.4) 157 drapper (0.8.4)
157 email_spec (1.2.1) 158 email_spec (1.2.1)
@@ -225,7 +226,7 @@ GEM @@ -225,7 +226,7 @@ GEM
225 omniauth (1.1.0) 226 omniauth (1.1.0)
226 hashie (~> 1.2) 227 hashie (~> 1.2)
227 rack 228 rack
228 - orm_adapter (0.0.7) 229 + orm_adapter (0.3.0)
229 polyglot (0.3.3) 230 polyglot (0.3.3)
230 posix-spawn (0.3.6) 231 posix-spawn (0.3.6)
231 pry (0.9.9.6) 232 pry (0.9.9.6)
@@ -356,7 +357,7 @@ GEM @@ -356,7 +357,7 @@ GEM
356 raindrops (~> 0.7) 357 raindrops (~> 0.7)
357 vegas (0.1.11) 358 vegas (0.1.11)
358 rack (>= 1.0.0) 359 rack (>= 1.0.0)
359 - warden (1.2.0) 360 + warden (1.2.1)
360 rack (>= 1.0) 361 rack (>= 1.0)
361 webmock (1.8.7) 362 webmock (1.8.7)
362 addressable (>= 2.2.7) 363 addressable (>= 2.2.7)
@@ -383,7 +384,7 @@ DEPENDENCIES @@ -383,7 +384,7 @@ DEPENDENCIES
383 colored 384 colored
384 cucumber-rails 385 cucumber-rails
385 database_cleaner 386 database_cleaner
386 - devise (~> 1.5) 387 + devise (~> 2.1.0)
387 drapper 388 drapper
388 email_spec 389 email_spec
389 ffaker 390 ffaker
1 -2.7.0pre 1 +2.7.0
app/assets/images/ajax_loader_tree.gif 0 → 100644

6.38 KB

app/assets/javascripts/application.js
@@ -12,6 +12,7 @@ @@ -12,6 +12,7 @@
12 //= require jquery.cookie 12 //= require jquery.cookie
13 //= require jquery.endless-scroll 13 //= require jquery.endless-scroll
14 //= require jquery.highlight 14 //= require jquery.highlight
  15 +//= require jquery.waitforimages
15 //= require bootstrap-modal 16 //= require bootstrap-modal
16 //= require modernizr 17 //= require modernizr
17 //= require chosen-jquery 18 //= require chosen-jquery
@@ -20,10 +21,26 @@ @@ -20,10 +21,26 @@
20 //= require_tree . 21 //= require_tree .
21 22
22 $(document).ready(function(){ 23 $(document).ready(function(){
  24 +
23 $(".one_click_select").live("click", function(){ 25 $(".one_click_select").live("click", function(){
24 $(this).select(); 26 $(this).select();
25 }); 27 });
26 28
  29 +
  30 + $('body').on('ajax:complete, ajax:beforeSend, submit', 'form', function(e){
  31 + var buttons = $('[type="submit"]', this);
  32 + switch( e.type ){
  33 + case 'ajax:beforeSend':
  34 + case 'submit':
  35 + buttons.attr('disabled', 'disabled');
  36 + break;
  37 + case ' ajax:complete':
  38 + default:
  39 + buttons.removeAttr('disabled');
  40 + break;
  41 + }
  42 + })
  43 +
27 $(".account-box").mouseenter(showMenu); 44 $(".account-box").mouseenter(showMenu);
28 $(".account-box").mouseleave(resetMenu); 45 $(".account-box").mouseleave(resetMenu);
29 46
@@ -97,3 +114,8 @@ function showDiff(link) { @@ -97,3 +114,8 @@ function showDiff(link) {
97 return _chosen.apply(this, [default_options]); 114 return _chosen.apply(this, [default_options]);
98 }}) 115 }})
99 })(jQuery); 116 })(jQuery);
  117 +
  118 +
  119 +function ajaxGet(url) {
  120 + $.ajax({type: "GET", url: url, dataType: "script"});
  121 +}
app/assets/javascripts/issues.js
@@ -73,4 +73,25 @@ function issuesPage(){ @@ -73,4 +73,25 @@ function issuesPage(){
73 $("#milestone_id, #assignee_id, #label_name").on("change", function(){ 73 $("#milestone_id, #assignee_id, #label_name").on("change", function(){
74 $(this).closest("form").submit(); 74 $(this).closest("form").submit();
75 }); 75 });
  76 +
  77 + $('body').on('ajax:success', '.close_issue, .reopen_issue, #new_issue', function(){
  78 + var t = $(this),
  79 + totalIssues,
  80 + reopen = t.hasClass('reopen_issue'),
  81 + newIssue = false;
  82 + if( this.id == 'new_issue' ){
  83 + newIssue = true;
  84 + }
  85 + $('.issue_counter, #new_issue').each(function(){
  86 + var issue = $(this);
  87 + totalIssues = parseInt( $(this).html(), 10 );
  88 +
  89 + if( newIssue || ( reopen && issue.closest('.main_menu').length ) ){
  90 + $(this).html( totalIssues+1 );
  91 + }else {
  92 + $(this).html( totalIssues-1 );
  93 + }
  94 + });
  95 +
  96 + });
76 } 97 }
app/assets/javascripts/note.js
@@ -25,11 +25,11 @@ init: @@ -25,11 +25,11 @@ init:
25 $(this).closest('li').fadeOut(); }); 25 $(this).closest('li').fadeOut(); });
26 26
27 $("#new_note").live("ajax:before", function(){ 27 $("#new_note").live("ajax:before", function(){
28 - $("#submit_note").attr("disabled", "disabled"); 28 + $(".submit_note").attr("disabled", "disabled");
29 }) 29 })
30 30
31 $("#new_note").live("ajax:complete", function(){ 31 $("#new_note").live("ajax:complete", function(){
32 - $("#submit_note").removeAttr("disabled"); 32 + $(".submit_note").removeAttr("disabled");
33 }) 33 })
34 34
35 $("#note_note").live("focus", function(){ 35 $("#note_note").live("focus", function(){
app/assets/stylesheets/common.scss
@@ -604,7 +604,11 @@ li.note { @@ -604,7 +604,11 @@ li.note {
604 border-style: solid; 604 border-style: solid;
605 border-width: 1px; 605 border-width: 1px;
606 @include border-radius(4px); 606 @include border-radius(4px);
607 - min-height:42px; 607 + min-height:22px;
  608 +
  609 + .avatar {
  610 + width:24px;
  611 + }
608 } 612 }
609 613
610 .supp_diff_link, 614 .supp_diff_link,
app/assets/stylesheets/gitlab_bootstrap.scss
@@ -202,6 +202,10 @@ a:focus { @@ -202,6 +202,10 @@ a:focus {
202 color:$style_color; 202 color:$style_color;
203 } 203 }
204 204
  205 +.nav-tabs > .active > a {
  206 + font-weight:bold;
  207 +}
  208 +
205 /** COLORS **/ 209 /** COLORS **/
206 .cgray { color:gray; } 210 .cgray { color:gray; }
207 .cred { color:#D12F19; } 211 .cred { color:#D12F19; }
@@ -209,6 +213,7 @@ a:focus { @@ -209,6 +213,7 @@ a:focus {
209 .cblack { color:#111; } 213 .cblack { color:#111; }
210 .cdark { color:#444 } 214 .cdark { color:#444 }
211 .cwhite { color:#fff !important } 215 .cwhite { color:#fff !important }
  216 +.bgred { background: #F2DEDE !important}
212 217
213 /** COMMON STYLES **/ 218 /** COMMON STYLES **/
214 .left { 219 .left {
@@ -299,9 +304,24 @@ table.no-borders { @@ -299,9 +304,24 @@ table.no-borders {
299 } 304 }
300 305
301 .event_label { 306 .event_label {
302 - background: #FCEEC1;  
303 - padding: 2px 2px 0;  
304 - font-family: monospace; 307 + @extend .label;
  308 + background-color: #999;
  309 +
  310 + &.pushed {
  311 + background-color: #3A87AD;
  312 + }
  313 +
  314 + &.opened {
  315 + background-color: #468847;
  316 + }
  317 +
  318 + &.closed {
  319 + background-color: #B94A48;
  320 + }
  321 +
  322 + &.merged {
  323 + background-color: #2A2;
  324 + }
305 } 325 }
306 326
307 img.avatar { 327 img.avatar {
@@ -425,9 +445,10 @@ form { @@ -425,9 +445,10 @@ form {
425 */ 445 */
426 .ui-box { 446 .ui-box {
427 background:#F9F9F9; 447 background:#F9F9F9;
428 - margin-bottom: 40px; 448 + margin-bottom: 25px;
429 @include round-borders-all(4px); 449 @include round-borders-all(4px);
430 border-color: #CCC; 450 border-color: #CCC;
  451 + @include solid_shade;
431 452
432 ul { 453 ul {
433 margin:0; 454 margin:0;
@@ -443,6 +464,13 @@ form { @@ -443,6 +464,13 @@ form {
443 background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); 464 background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
444 background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); 465 background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
445 466
  467 + &.small {
  468 + line-height: 28px;
  469 + font-size: 14px;
  470 + line-height:28px;
  471 + text-shadow: 0 1px 1px white;
  472 + }
  473 +
446 form { 474 form {
447 padding:9px 0; 475 padding:9px 0;
448 margin:0px; 476 margin:0px;
@@ -511,6 +539,7 @@ form { @@ -511,6 +539,7 @@ form {
511 table.admin-table { 539 table.admin-table {
512 @extend .table-bordered; 540 @extend .table-bordered;
513 @extend .zebra-striped; 541 @extend .zebra-striped;
  542 + @include solid_shade;
514 th { 543 th {
515 border-color: #CCC; 544 border-color: #CCC;
516 border-bottom: 1px solid #bbb; 545 border-bottom: 1px solid #bbb;
@@ -568,6 +597,8 @@ ul.breadcrumb { @@ -568,6 +597,8 @@ ul.breadcrumb {
568 @extend .prepend-top-20; 597 @extend .prepend-top-20;
569 @extend .append-bottom-20; 598 @extend .append-bottom-20;
570 border-width:1px; 599 border-width:1px;
  600 + @include solid_shade;
  601 +
571 602
572 img { max-width: 100%; } 603 img { max-width: 100%; }
573 604
@@ -624,13 +655,166 @@ p { @@ -624,13 +655,166 @@ p {
624 h3.page_title { 655 h3.page_title {
625 color:#456; 656 color:#456;
626 font-size:20px; 657 font-size:20px;
627 - font-weight: 600; 658 + font-weight: normal;
628 line-height: 28px; 659 line-height: 28px;
629 } 660 }
630 661
631 -pre.logs {  
632 - .log {  
633 - font-size:12px;  
634 - line-height:18px; 662 +/**
  663 + * File content holder
  664 + *
  665 + */
  666 +.file_holder {
  667 + border:1px solid #CCC;
  668 + margin-bottom:1em;
  669 + @include solid_shade;
  670 +
  671 + .file_title {
  672 + border-bottom: 1px solid #bbb;
  673 + background:#eee;
  674 + background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
  675 + background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
  676 + background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
  677 + background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
  678 + margin: 0;
  679 + font-weight: normal;
  680 + font-weight: bold;
  681 + text-align: left;
  682 + color: #666;
  683 + padding: 9px 10px;
  684 + height:18px;
  685 +
  686 + .options {
  687 + float:right;
  688 + margin-top: -5px;
  689 + }
  690 +
  691 + .file_name {
  692 + color:$style_color;
  693 + font-size:14px;
  694 + text-shadow: 0 1px 1px #fff;
  695 + small {
  696 + color:#999;
  697 + font-size:13px;
  698 + }
  699 + }
  700 + }
  701 + .file_content {
  702 + background:#fff;
  703 + font-size: 11px;
  704 +
  705 + &.wiki {
  706 + font-size: 13px;
  707 + code {
  708 + padding:0 4px;
  709 + }
  710 + padding:20px;
  711 + h1, h2 {
  712 + line-height: 46px;
  713 + }
  714 + h3, h4 {
  715 + line-height: 40px;
  716 + }
  717 + }
  718 +
  719 + &.image_file {
  720 + background:#eee;
  721 + text-align:center;
  722 + img {
  723 + padding:100px;
  724 + max-width:300px;
  725 + }
  726 + }
  727 +
  728 + &.blob_file {
  729 +
  730 + }
  731 +
  732 + /**
  733 + * Blame file
  734 + */
  735 + &.blame {
  736 + tr {
  737 + border-bottom: 1px solid #eee;
  738 + }
  739 + td {
  740 + padding:5px;
  741 + }
  742 + .author,
  743 + .blame_commit {
  744 + background:#f5f5f5;
  745 + vertical-align:top;
  746 + }
  747 + .lines {
  748 + pre {
  749 + padding:0;
  750 + margin:0;
  751 + background:none;
  752 + border:none;
  753 + }
  754 + }
  755 + }
  756 +
  757 + &.logs {
  758 + background:#eee;
  759 + max-height: 700px;
  760 + overflow-y: auto;
  761 +
  762 + ol {
  763 + margin-left:40px;
  764 + padding: 10px 0;
  765 + border-left: 1px solid #CCC;
  766 + margin-bottom:0;
  767 + background: white;
  768 + li {
  769 + color:#888;
  770 + p {
  771 + margin:0;
  772 + color:#333;
  773 + line-height:24px;
  774 + padding-left: 10px;
  775 + }
  776 +
  777 + &:hover {
  778 + background:$hover;
  779 + }
  780 + }
  781 + }
  782 + }
  783 +
  784 + /**
  785 + * Code file
  786 + */
  787 + &.code {
  788 + padding:0;
  789 + td.code {
  790 + width: 100%;
  791 + .highlight {
  792 + margin-left: 55px;
  793 + overflow:auto;
  794 + overflow-y:hidden;
  795 + }
  796 + }
  797 + .highlight pre {
  798 + white-space: pre;
  799 + word-wrap:normal;
  800 + }
  801 +
  802 + table.highlighttable {
  803 + border: none;
  804 + }
  805 + body.project-page table.highlighttable td { border: none }
  806 + table.highlighttable tr:hover { background:none;}
  807 +
  808 + table.highlighttable pre{
  809 + line-height:16px !important;
  810 + font-size:12px !important;
  811 + }
  812 +
  813 + table.highlighttable .linenodiv pre {
  814 + text-align: right;
  815 + padding-right: 4px;
  816 + color:#666;
  817 + }
  818 + }
635 } 819 }
636 } 820 }
app/assets/stylesheets/header.scss
@@ -96,7 +96,7 @@ header { @@ -96,7 +96,7 @@ header {
96 */ 96 */
97 .search { 97 .search {
98 float: right; 98 float: right;
99 - margin-right: 55px; 99 + margin-right: 50px;
100 100
101 .search-input { 101 .search-input {
102 @extend .span2; 102 @extend .span2;
@@ -126,10 +126,10 @@ header { @@ -126,10 +126,10 @@ header {
126 cursor: pointer; 126 cursor: pointer;
127 img { 127 img {
128 border-radius: 4px; 128 border-radius: 4px;
129 - right: 0px; 129 + right: 5px;
130 position: absolute; 130 position: absolute;
131 - width: 33px;  
132 - height: 33px; 131 + width: 31px;
  132 + height: 31px;
133 display: block; 133 display: block;
134 top: 0; 134 top: 0;
135 &:after { 135 &:after {
app/assets/stylesheets/main.scss
@@ -31,6 +31,12 @@ $hover: #FDF5D9; @@ -31,6 +31,12 @@ $hover: #FDF5D9;
31 box-shadow: 0 0 3px #ddd; 31 box-shadow: 0 0 3px #ddd;
32 } 32 }
33 33
  34 +@mixin solid_shade {
  35 + -moz-box-shadow: 0 0 0 3px #eee;
  36 + -webkit-box-shadow: 0 0 0 3px #eee;
  37 + box-shadow: 0 0 0 3px #eee;
  38 +}
  39 +
34 @mixin border-radius($radius) { 40 @mixin border-radius($radius) {
35 -moz-border-radius: $radius; 41 -moz-border-radius: $radius;
36 -webkit-border-radius: $radius; 42 -webkit-border-radius: $radius;
@@ -136,7 +142,7 @@ $hover: #FDF5D9; @@ -136,7 +142,7 @@ $hover: #FDF5D9;
136 /** 142 /**
137 * Code (files list) styles. Browsing project files there 143 * Code (files list) styles. Browsing project files there
138 */ 144 */
139 -@import "tree.scss"; 145 +@import "sections/tree.scss";
140 146
141 /** 147 /**
142 * This file represent notes(comments) styles 148 * This file represent notes(comments) styles
app/assets/stylesheets/notes.scss
@@ -63,18 +63,22 @@ p.notify_controls span{ @@ -63,18 +63,22 @@ p.notify_controls span{
63 63
64 tr.line_notes_row { 64 tr.line_notes_row {
65 border-bottom:1px solid #DDD; 65 border-bottom:1px solid #DDD;
  66 + border-left: 7px solid #2A79A3;
  67 +
66 &.reply { 68 &.reply {
67 background:#eee; 69 background:#eee;
68 - 70 + border-left: 7px solid #2A79A3;
  71 + border-top:1px solid #ddd;
69 td { 72 td {
70 padding:7px 10px; 73 padding:7px 10px;
71 } 74 }
72 a.line_note_reply_link { 75 a.line_note_reply_link {
73 @include round-borders-all(4px); 76 @include round-borders-all(4px);
74 - border-color:#aaa;  
75 - background: #bbb;  
76 - padding: 3px 20px; 77 + padding: 3px 10px;
  78 + margin-left:5px;
77 color: white; 79 color: white;
  80 + background: #2A79A3;
  81 + border-color: #2A79A3;
78 } 82 }
79 } 83 }
80 ul { 84 ul {
@@ -95,6 +99,9 @@ tr.line_notes_row { @@ -95,6 +99,9 @@ tr.line_notes_row {
95 td { 99 td {
96 border-bottom:1px solid #ddd; 100 border-bottom:1px solid #ddd;
97 } 101 }
  102 + .actions {
  103 + margin:0;
  104 + }
98 } 105 }
99 106
100 td .line_note_link { 107 td .line_note_link {
app/assets/stylesheets/sections/commits.scss
@@ -101,18 +101,21 @@ @@ -101,18 +101,21 @@
101 margin:50px; 101 margin:50px;
102 padding:1px; 102 padding:1px;
103 max-width:400px; 103 max-width:400px;
104 - }  
105 - &.diff_image_removed {  
106 - img { 104 +
  105 + &.diff_image_removed {
107 border: 1px solid #C00; 106 border: 1px solid #C00;
108 } 107 }
109 - }  
110 108
111 - &.diff_image_added {  
112 - img { 109 + &.diff_image_added {
113 border: 1px solid #0C0;; 110 border: 1px solid #0C0;;
114 } 111 }
115 } 112 }
  113 +
  114 + &.img_compared {
  115 + img {
  116 + max-width:300px;
  117 + }
  118 + }
116 } 119 }
117 } 120 }
118 121
app/assets/stylesheets/sections/merge_requests.scss
@@ -82,3 +82,15 @@ @@ -82,3 +82,15 @@
82 } 82 }
83 } 83 }
84 } 84 }
  85 +
  86 +li.merge_request {
  87 + padding:7px 10px;
  88 + img.avatar {
  89 + width: 32px;
  90 + margin-top: 4px;
  91 + }
  92 + p {
  93 + padding: 0px;
  94 + padding-bottom: 2px;
  95 + }
  96 +}
app/assets/stylesheets/sections/tree.scss 0 → 100644
@@ -0,0 +1,96 @@ @@ -0,0 +1,96 @@
  1 +#tree-holder {
  2 + #tree-content-holder {
  3 + float:left;
  4 + width:100%;
  5 + }
  6 + #tree-readme-holder {
  7 + float:left;
  8 + width:100%;
  9 + .readme {
  10 + border:1px solid #ccc;
  11 + padding:12px;
  12 + background: #F7F7F7;
  13 +
  14 + pre {
  15 + overflow: auto;
  16 + }
  17 + }
  18 + }
  19 +
  20 + .tree_progress {
  21 + display:none;
  22 + margin:20px;
  23 + &.loading {
  24 + display:block;
  25 + }
  26 + }
  27 +
  28 + #tree-slider {
  29 + @include border-radius(0);
  30 + .tree-item {
  31 + &:hover {
  32 + td { background: $hover; }
  33 + cursor:pointer;
  34 + }
  35 + }
  36 + }
  37 +
  38 + .tree-item {
  39 + .tree-item-file-name {
  40 + vertical-align:middle;
  41 + font-weight:bold;
  42 + a {
  43 + color:$style_color;
  44 + &:hover {
  45 + color:$blue_link;
  46 + }
  47 + }
  48 +
  49 + img {
  50 + position: relative;
  51 + top:-1px;
  52 + }
  53 + }
  54 + }
  55 +
  56 +
  57 + #tree-slider {
  58 + @include solid_shade;
  59 + width:100%;
  60 +
  61 + border-color:#ccc;
  62 +
  63 + td {
  64 + padding:8px;
  65 + border-color:#f1f1f1;
  66 + background:#fafafa;
  67 + }
  68 +
  69 + tr:first-child td:first-child,
  70 + tr:first-child td:last-child {
  71 + border-radius:0;
  72 + }
  73 +
  74 + th {
  75 + border-color: #CCC;
  76 + border-bottom: 1px solid #bbb;
  77 + background:#eee;
  78 + background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
  79 + background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
  80 + background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
  81 + background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
  82 + }
  83 + }
  84 +
  85 + .tree-commit-link {
  86 + color:#333;
  87 + }
  88 +
  89 + a.tree-commit-link {
  90 + color: #666;
  91 + &:hover {
  92 + text-decoration: underline;
  93 + }
  94 + }
  95 +
  96 +}
app/assets/stylesheets/themes/ui_mars.scss
@@ -70,8 +70,7 @@ @@ -70,8 +70,7 @@
70 } 70 }
71 } 71 }
72 .separator { 72 .separator {
73 - border-color:#444;  
74 - background:#31363E; 73 + display:none;
75 } 74 }
76 75
77 } 76 }
app/assets/stylesheets/tree.scss
@@ -1,232 +0,0 @@ @@ -1,232 +0,0 @@
1 -#tree-holder {  
2 - #tree-content-holder {  
3 - float:left;  
4 - width:100%;  
5 - }  
6 - #tree-readme-holder {  
7 - float:left;  
8 - width:100%;  
9 - .readme {  
10 - border:1px solid #ccc;  
11 - padding:12px;  
12 - background: #F7F7F7;  
13 -  
14 - pre {  
15 - overflow: auto;  
16 - }  
17 - }  
18 - }  
19 -  
20 - .tree_progress {  
21 - display:none;  
22 - margin:20px;  
23 - &.loading {  
24 - display:block;  
25 - }  
26 - }  
27 -  
28 -  
29 - /** FILE CONTENT VIEW **/  
30 - .view_file_content{  
31 - .old_line, .new_line {  
32 - background:#ECECEC;  
33 - color:#777;  
34 - width:15px;  
35 - float:left;  
36 - padding: 0px 10px;  
37 - border-right: 1px solid #ccc;  
38 - }  
39 - .old_line{  
40 - display:none;  
41 - }  
42 - }  
43 -  
44 - .view_file .view_file_header,  
45 - .diff_file .diff_file_header {  
46 - border-bottom: 1px solid #bbb;  
47 - background:#eee;  
48 - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));  
49 - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);  
50 - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);  
51 - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);  
52 - margin: 0;  
53 - font-weight: normal;  
54 - font-weight: bold;  
55 - text-align: left;  
56 - color: #666;  
57 - padding: 9px 10px;  
58 - height:18px;  
59 -  
60 - .options {  
61 - float:right;  
62 - margin-top: -5px;  
63 - }  
64 -  
65 - .file_name {  
66 - color:$style_color;  
67 - font-size:14px;  
68 - text-shadow: 0 1px 1px #fff;  
69 - small {  
70 - color:#999;  
71 - font-size:13px;  
72 - }  
73 - }  
74 - }  
75 -  
76 - .view_file {  
77 - border:1px solid #CCC;  
78 - margin-bottom:1em;  
79 -  
80 - .view_file_content {  
81 - background:#fff;  
82 - color:#514721;  
83 - font-size: 11px;  
84 - }  
85 - .view_file_content_image {  
86 - background:#eee;  
87 - text-align:center;  
88 - img {  
89 - padding:100px;  
90 - max-width:300px;  
91 - }  
92 - }  
93 - }  
94 -  
95 - td.code {  
96 - width: 100%;  
97 - .highlight {  
98 - margin-left: 55px;  
99 - overflow:auto;  
100 - overflow-y:hidden;  
101 - }  
102 - }  
103 - .highlight pre {  
104 - white-space: pre;  
105 - word-wrap:normal;  
106 - }  
107 -  
108 - table.highlighttable {  
109 - border: none;  
110 - }  
111 - body.project-page table.highlighttable td { border: none }  
112 - table.highlighttable tr:hover { background:none;}  
113 -  
114 - table.highlighttable pre{  
115 - line-height:16px !important;  
116 - font-size:12px !important;  
117 - }  
118 -  
119 - table.highlighttable .linenodiv pre {  
120 - text-align: right;  
121 - padding-right: 4px;  
122 - color:#666;  
123 - }  
124 -  
125 - #tree-slider {  
126 - @include border-radius(0);  
127 - .tree-item {  
128 - &:hover {  
129 - td { background: $hover; }  
130 - cursor:pointer;  
131 - }  
132 - }  
133 - }  
134 -  
135 - .tree-item {  
136 - .tree-item-file-name {  
137 - vertical-align:middle;  
138 - font-weight:bold;  
139 - a {  
140 - color:$style_color;  
141 - &:hover {  
142 - color:$blue_link;  
143 - }  
144 - }  
145 -  
146 - img {  
147 - position: relative;  
148 - top:-1px;  
149 - }  
150 - }  
151 - }  
152 -  
153 -  
154 - #tree-slider {  
155 - @include shade;  
156 - width:100%;  
157 -  
158 - border-color:#ccc;  
159 -  
160 - td {  
161 - padding:8px;  
162 - border-color:#f1f1f1;  
163 - background:#fafafa;  
164 - }  
165 -  
166 - tr:first-child td:first-child,  
167 - tr:first-child td:last-child {  
168 - border-radius:0;  
169 - }  
170 -  
171 - th {  
172 - border-color: #CCC;  
173 - border-bottom: 1px solid #bbb;  
174 - background:#eee;  
175 - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));  
176 - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);  
177 - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);  
178 - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);  
179 - }  
180 - }  
181 -  
182 - .tree-commit-link {  
183 - color:#333;  
184 - }  
185 -  
186 - #tree-content-holder .view_file{  
187 - @include shade;  
188 - }  
189 -  
190 - #tree-readme-holder .readme {  
191 - @include shade;  
192 - margin-bottom:20px;  
193 - h1, h2 {  
194 - line-height: 56px;  
195 - }  
196 - h3, h4 {  
197 - line-height: 46px;  
198 - }  
199 - }  
200 -  
201 - a.tree-commit-link {  
202 - color: #666;  
203 - &:hover {  
204 - text-decoration: underline;  
205 - }  
206 - }  
207 -  
208 -}  
209 -  
210 -.blame_file {  
211 - .view_file_content {  
212 - tr {  
213 - border-bottom: 1px solid #eee;  
214 - }  
215 - td {  
216 - padding:5px;  
217 - }  
218 - .author,  
219 - .commit {  
220 - background:#f5f5f5;  
221 - vertical-align:top;  
222 - }  
223 - .lines {  
224 - pre {  
225 - padding:0;  
226 - margin:0;  
227 - background:none;  
228 - border:none;  
229 - }  
230 - }  
231 - }  
232 -}  
app/contexts/base_context.rb 0 → 100644
@@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
  1 +class BaseContext
  2 + attr_accessor :project, :current_user, :params
  3 +
  4 + def initialize(project, user, params)
  5 + @project, @current_user, @params = project, user, params.dup
  6 + end
  7 +end
  8 +
app/contexts/commit_load.rb 0 → 100644
@@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
  1 +class CommitLoad < BaseContext
  2 + def execute
  3 + result = {
  4 + :commit => nil,
  5 + :suppress_diff => false,
  6 + :line_notes => [],
  7 + :notes_count => 0,
  8 + :note => nil
  9 + }
  10 +
  11 + commit = project.commit(params[:id])
  12 +
  13 + if commit
  14 + commit = CommitDecorator.decorate(commit)
  15 + line_notes = project.commit_line_notes(commit)
  16 +
  17 + result[:suppress_diff] = true if commit.diffs.size > 200 && !params[:force_show_diff]
  18 + result[:commit] = commit
  19 + result[:note] = project.build_commit_note(commit)
  20 + result[:line_notes] = line_notes
  21 + result[:notes_count] = line_notes.count + project.commit_notes(commit).count
  22 + end
  23 +
  24 + result
  25 + end
  26 +end
app/contexts/merge_requests_load.rb 0 → 100644
@@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
  1 +class MergeRequestsLoad < BaseContext
  2 + def execute
  3 + type = params[:f].to_i
  4 +
  5 + merge_requests = project.merge_requests
  6 +
  7 + merge_requests = case type
  8 + when 1 then merge_requests
  9 + when 2 then merge_requests.closed
  10 + when 3 then merge_requests.opened.assigned(current_user)
  11 + else merge_requests.opened
  12 + end.page(params[:page]).per(20)
  13 +
  14 + merge_requests.includes(:author, :project).order("closed, created_at desc")
  15 + end
  16 +end
app/contexts/notes_load.rb 0 → 100644
@@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
  1 +class NotesLoad < BaseContext
  2 + def execute
  3 + target_type = params[:target_type]
  4 + target_id = params[:target_id]
  5 + first_id = params[:first_id]
  6 + last_id = params[:last_id]
  7 +
  8 +
  9 + @notes = case target_type
  10 + when "commit"
  11 + then project.commit_notes(project.commit(target_id)).fresh.limit(20)
  12 + when "snippet"
  13 + then project.snippets.find(target_id).notes
  14 + when "wall"
  15 + then project.common_notes.order("created_at DESC").fresh.limit(50)
  16 + when "issue"
  17 + then project.issues.find(target_id).notes.inc_author.order("created_at DESC").limit(20)
  18 + when "merge_request"
  19 + then project.merge_requests.find(target_id).notes.inc_author.order("created_at DESC").limit(20)
  20 + end
  21 +
  22 + @notes = if last_id
  23 + @notes.where("id > ?", last_id)
  24 + elsif first_id
  25 + @notes.where("id < ?", first_id)
  26 + else
  27 + @notes
  28 + end
  29 + end
  30 +end
app/controllers/admin/hooks_controller.rb 0 → 100644
@@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
  1 +class Admin::HooksController < ApplicationController
  2 + layout "admin"
  3 + before_filter :authenticate_user!
  4 + before_filter :authenticate_admin!
  5 +
  6 + def index
  7 + @hooks = SystemHook.all
  8 + @hook = SystemHook.new
  9 + end
  10 +
  11 + def create
  12 + @hook = SystemHook.new(params[:hook])
  13 +
  14 + if @hook.save
  15 + redirect_to admin_hooks_path, notice: 'Hook was successfully created.'
  16 + else
  17 + @hooks = SystemHook.all
  18 + render :index
  19 + end
  20 + end
  21 +
  22 + def destroy
  23 + @hook = SystemHook.find(params[:id])
  24 + @hook.destroy
  25 +
  26 + redirect_to admin_hooks_path
  27 + end
  28 +
  29 +
  30 + def test
  31 + @hook = SystemHook.find(params[:hook_id])
  32 + data = {
  33 + event_name: "project_create",
  34 + name: "Ruby",
  35 + path: "ruby",
  36 + project_id: 1,
  37 + owner_name: "Someone",
  38 + owner_email: "example@gitlabhq.com"
  39 + }
  40 + @hook.execute(data)
  41 +
  42 + redirect_to :back
  43 + end
  44 +end
app/controllers/admin/mailer_controller.rb
@@ -1,45 +0,0 @@ @@ -1,45 +0,0 @@
1 -class Admin::MailerController < ApplicationController  
2 - layout "admin"  
3 - before_filter :authenticate_user!  
4 - before_filter :authenticate_admin!  
5 -  
6 - def preview  
7 -  
8 - end  
9 -  
10 - def preview_note  
11 - @note = Note.first  
12 - @user = @note.author  
13 - @project = @note.project  
14 - case params[:type]  
15 - when "Commit" then  
16 - @commit = @project.commit  
17 - render :file => 'notify/note_commit_email', :layout => 'notify'  
18 - when "Issue" then  
19 - @issue = Issue.first  
20 - render :file => 'notify/note_issue_email', :layout => 'notify'  
21 - else  
22 - render :file => 'notify/note_wall_email', :layout => 'notify'  
23 - end  
24 - rescue  
25 - render :text => "Preview not available"  
26 - end  
27 -  
28 - def preview_user_new  
29 - @user = User.first  
30 - @password = "DHasJKDHAS!"  
31 -  
32 - render :file => 'notify/new_user_email', :layout => 'notify'  
33 - rescue  
34 - render :text => "Preview not available"  
35 - end  
36 -  
37 - def preview_issue_new  
38 - @issue = Issue.first  
39 - @user = @issue.assignee  
40 - @project = @issue.project  
41 - render :file => 'notify/new_issue_email', :layout => 'notify'  
42 - rescue  
43 - render :text => "Preview not available"  
44 - end  
45 -end  
app/controllers/admin/projects_controller.rb
@@ -6,7 +6,7 @@ class Admin::ProjectsController &lt; ApplicationController @@ -6,7 +6,7 @@ class Admin::ProjectsController &lt; ApplicationController
6 def index 6 def index
7 @admin_projects = Project.scoped 7 @admin_projects = Project.scoped
8 @admin_projects = @admin_projects.search(params[:name]) if params[:name].present? 8 @admin_projects = @admin_projects.search(params[:name]) if params[:name].present?
9 - @admin_projects = @admin_projects.page(params[:page]) 9 + @admin_projects = @admin_projects.page(params[:page]).per(20)
10 end 10 end
11 11
12 def show 12 def show
@@ -72,6 +72,6 @@ class Admin::ProjectsController &lt; ApplicationController @@ -72,6 +72,6 @@ class Admin::ProjectsController &lt; ApplicationController
72 @admin_project = Project.find_by_code(params[:id]) 72 @admin_project = Project.find_by_code(params[:id])
73 @admin_project.destroy 73 @admin_project.destroy
74 74
75 - redirect_to admin_projects_url 75 + redirect_to admin_projects_url, notice: 'Project was successfully deleted.'
76 end 76 end
77 end 77 end
app/controllers/application_controller.rb
@@ -52,7 +52,7 @@ class ApplicationController &lt; ActionController::Base @@ -52,7 +52,7 @@ class ApplicationController &lt; ActionController::Base
52 52
53 def layout_by_resource 53 def layout_by_resource
54 if devise_controller? 54 if devise_controller?
55 - "devise" 55 + "devise_layout"
56 else 56 else
57 "application" 57 "application"
58 end 58 end
app/controllers/commits_controller.rb
@@ -26,43 +26,31 @@ class CommitsController &lt; ApplicationController @@ -26,43 +26,31 @@ class CommitsController &lt; ApplicationController
26 end 26 end
27 27
28 def show 28 def show
29 - @commit = project.commit(params[:id])  
30 -  
31 - git_not_found! and return unless @commit  
32 -  
33 - @commit = CommitDecorator.decorate(@commit)  
34 -  
35 - @note = @project.build_commit_note(@commit)  
36 - @comments_allowed = true  
37 - @line_notes = project.commit_line_notes(@commit)  
38 -  
39 - @notes_count = @line_notes.count + project.commit_notes(@commit).count  
40 -  
41 - if @commit.diffs.size > 200 && !params[:force_show_diff]  
42 - @suppress_diff = true 29 + result = CommitLoad.new(project, current_user, params).execute
  30 +
  31 + @commit = result[:commit]
  32 +
  33 + if @commit
  34 + @suppress_diff = result[:suppress_diff]
  35 + @note = result[:note]
  36 + @line_notes = result[:line_notes]
  37 + @notes_count = result[:notes_count]
  38 + @comments_allowed = true
  39 + else
  40 + return git_not_found!
43 end 41 end
  42 +
44 rescue Grit::Git::GitTimeout 43 rescue Grit::Git::GitTimeout
45 render "huge_commit" 44 render "huge_commit"
46 end 45 end
47 46
48 def compare 47 def compare
49 - first = project.commit(params[:to].try(:strip))  
50 - last = project.commit(params[:from].try(:strip)) 48 + result = Commit.compare(project, params[:from], params[:to])
51 49
52 - @diffs = []  
53 - @commits = [] 50 + @commits = result[:commits]
  51 + @commit = result[:commit]
  52 + @diffs = result[:diffs]
54 @line_notes = [] 53 @line_notes = []
55 -  
56 - if first && last  
57 - commits = [first, last].sort_by(&:created_at)  
58 - younger = commits.first  
59 - older = commits.last  
60 -  
61 -  
62 - @commits = project.repo.commits_between(younger.id, older.id).map {|c| Commit.new(c)}  
63 - @diffs = project.repo.diff(younger.id, older.id) rescue []  
64 - @commit = Commit.new(older)  
65 - end  
66 end 54 end
67 55
68 def patch 56 def patch
app/controllers/dashboard_controller.rb
@@ -2,15 +2,13 @@ class DashboardController &lt; ApplicationController @@ -2,15 +2,13 @@ class DashboardController &lt; ApplicationController
2 respond_to :html 2 respond_to :html
3 3
4 def index 4 def index
5 - @projects = current_user.projects.includes(:events).order("events.created_at DESC")  
6 - @projects = @projects.page(params[:page]).per(40)  
7 -  
8 - @events = Event.where(:project_id => current_user.projects.map(&:id)).recent.limit(20)  
9 - 5 + @projects = current_user.projects_with_events.page(params[:page]).per(40)
  6 + @events = Event.recent_for_user(current_user).limit(20).offset(params[:offset] || 0)
10 @last_push = current_user.recent_push 7 @last_push = current_user.recent_push
11 8
12 respond_to do |format| 9 respond_to do |format|
13 format.html 10 format.html
  11 + format.js
14 format.atom { render :layout => false } 12 format.atom { render :layout => false }
15 end 13 end
16 end 14 end
app/controllers/hooks_controller.rb
@@ -11,24 +11,24 @@ class HooksController &lt; ApplicationController @@ -11,24 +11,24 @@ class HooksController &lt; ApplicationController
11 respond_to :html 11 respond_to :html
12 12
13 def index 13 def index
14 - @hooks = @project.web_hooks.all  
15 - @hook = WebHook.new 14 + @hooks = @project.hooks.all
  15 + @hook = ProjectHook.new
16 end 16 end
17 17
18 def create 18 def create
19 - @hook = @project.web_hooks.new(params[:hook]) 19 + @hook = @project.hooks.new(params[:hook])
20 @hook.save 20 @hook.save
21 21
22 if @hook.valid? 22 if @hook.valid?
23 redirect_to project_hooks_path(@project) 23 redirect_to project_hooks_path(@project)
24 else 24 else
25 - @hooks = @project.web_hooks.all 25 + @hooks = @project.hooks.all
26 render :index 26 render :index
27 end 27 end
28 end 28 end
29 29
30 def test 30 def test
31 - @hook = @project.web_hooks.find(params[:id]) 31 + @hook = @project.hooks.find(params[:id])
32 commits = @project.commits(@project.default_branch, nil, 3) 32 commits = @project.commits(@project.default_branch, nil, 3)
33 data = @project.post_receive_data(commits.last.id, commits.first.id, "refs/heads/#{@project.default_branch}", current_user) 33 data = @project.post_receive_data(commits.last.id, commits.first.id, "refs/heads/#{@project.default_branch}", current_user)
34 @hook.execute(data) 34 @hook.execute(data)
@@ -37,7 +37,7 @@ class HooksController &lt; ApplicationController @@ -37,7 +37,7 @@ class HooksController &lt; ApplicationController
37 end 37 end
38 38
39 def destroy 39 def destroy
40 - @hook = @project.web_hooks.find(params[:id]) 40 + @hook = @project.hooks.find(params[:id])
41 @hook.destroy 41 @hook.destroy
42 42
43 redirect_to project_hooks_path(@project) 43 redirect_to project_hooks_path(@project)
app/controllers/merge_requests_controller.rb
@@ -24,16 +24,7 @@ class MergeRequestsController &lt; ApplicationController @@ -24,16 +24,7 @@ class MergeRequestsController &lt; ApplicationController
24 24
25 25
26 def index 26 def index
27 - @merge_requests = @project.merge_requests  
28 -  
29 - @merge_requests = case params[:f].to_i  
30 - when 1 then @merge_requests  
31 - when 2 then @merge_requests.closed  
32 - when 3 then @merge_requests.opened.assigned(current_user)  
33 - else @merge_requests.opened  
34 - end.page(params[:page]).per(20)  
35 -  
36 - @merge_requests = @merge_requests.includes(:author, :project).order("closed, created_at desc") 27 + @merge_requests = MergeRequestsLoad.new(project, current_user, params).execute
37 end 28 end
38 29
39 def show 30 def show
app/controllers/notes_controller.rb
@@ -40,25 +40,6 @@ class NotesController &lt; ApplicationController @@ -40,25 +40,6 @@ class NotesController &lt; ApplicationController
40 protected 40 protected
41 41
42 def notes 42 def notes
43 - @notes = case params[:target_type]  
44 - when "commit"  
45 - then project.commit_notes(project.commit((params[:target_id]))).fresh.limit(20)  
46 - when "snippet"  
47 - then project.snippets.find(params[:target_id]).notes  
48 - when "wall"  
49 - then project.common_notes.order("created_at DESC").fresh.limit(50)  
50 - when "issue"  
51 - then project.issues.find(params[:target_id]).notes.inc_author.order("created_at DESC").limit(20)  
52 - when "merge_request"  
53 - then project.merge_requests.find(params[:target_id]).notes.inc_author.order("created_at DESC").limit(20)  
54 - end  
55 -  
56 - @notes = if params[:last_id]  
57 - @notes.where("id > ?", params[:last_id])  
58 - elsif params[:first_id]  
59 - @notes.where("id < ?", params[:first_id])  
60 - else  
61 - @notes  
62 - end 43 + @notes = NotesLoad.new(project, current_user, params).execute
63 end 44 end
64 end 45 end
app/controllers/omniauth_callbacks_controller.rb
1 class OmniauthCallbacksController < Devise::OmniauthCallbacksController 1 class OmniauthCallbacksController < Devise::OmniauthCallbacksController
  2 +
  3 + # Extend the standard message generation to accept our custom exception
  4 + def failure_message
  5 + exception = env["omniauth.error"]
  6 + if exception.class == OmniAuth::Error
  7 + error = exception.message
  8 + else
  9 + error = exception.error_reason if exception.respond_to?(:error_reason)
  10 + error ||= exception.error if exception.respond_to?(:error)
  11 + error ||= env["omniauth.error.type"].to_s
  12 + end
  13 + error.to_s.humanize if error
  14 + end
2 15
3 def ldap 16 def ldap
4 # We only find ourselves here if the authentication to LDAP was successful. 17 # We only find ourselves here if the authentication to LDAP was successful.
app/controllers/refs_controller.rb
@@ -9,7 +9,7 @@ class RefsController &lt; ApplicationController @@ -9,7 +9,7 @@ class RefsController &lt; ApplicationController
9 before_filter :require_non_empty_project 9 before_filter :require_non_empty_project
10 10
11 before_filter :ref 11 before_filter :ref
12 - before_filter :define_tree_vars, :only => [:tree, :blob, :blame] 12 + before_filter :define_tree_vars, :only => [:tree, :blob, :blame, :logs_tree]
13 before_filter :render_full_content 13 before_filter :render_full_content
14 14
15 layout "project" 15 layout "project"
@@ -46,6 +46,18 @@ class RefsController &lt; ApplicationController @@ -46,6 +46,18 @@ class RefsController &lt; ApplicationController
46 end 46 end
47 end 47 end
48 48
  49 + def logs_tree
  50 + contents = @tree.contents
  51 + @logs = contents.map do |content|
  52 + file = params[:path] ? File.join(params[:path], content.name) : content.name
  53 + last_commit = @project.commits(@commit.id, file, 1).last
  54 + {
  55 + :file_name => content.name,
  56 + :commit => last_commit
  57 + }
  58 + end
  59 + end
  60 +
49 def blob 61 def blob
50 if @tree.is_blob? 62 if @tree.is_blob?
51 if @tree.text? 63 if @tree.text?
@@ -79,6 +91,15 @@ class RefsController &lt; ApplicationController @@ -79,6 +91,15 @@ class RefsController &lt; ApplicationController
79 @commit = project.commit(@ref) 91 @commit = project.commit(@ref)
80 @tree = Tree.new(@commit.tree, project, @ref, params[:path]) 92 @tree = Tree.new(@commit.tree, project, @ref, params[:path])
81 @tree = TreeDecorator.new(@tree) 93 @tree = TreeDecorator.new(@tree)
  94 + @hex_path = Digest::SHA1.hexdigest(params[:path] || "/")
  95 +
  96 + if params[:path]
  97 + @history_path = tree_file_project_ref_path(@project, @ref, params[:path])
  98 + @logs_path = logs_file_project_ref_path(@project, @ref, params[:path])
  99 + else
  100 + @history_path = tree_project_ref_path(@project, @ref)
  101 + @logs_path = logs_tree_project_ref_path(@project, @ref)
  102 + end
82 rescue 103 rescue
83 return render_404 104 return render_404
84 end 105 end
app/decorators/event_decorator.rb 0 → 100644
@@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
  1 +class EventDecorator < ApplicationDecorator
  2 + decorates :event
  3 +
  4 + def feed_title
  5 + if self.issue?
  6 + "#{self.author_name} #{self.action_name} issue ##{self.target_id}:" + self.issue_title
  7 + elsif self.merge_request?
  8 + "#{self.author_name} #{self.action_name} MR ##{self.target_id}:" + self.merge_request_title
  9 + elsif self.push?
  10 + "#{self.author_name} #{self.push_action_name} #{self.ref_type} " + self.ref_name
  11 + else
  12 + ""
  13 + end
  14 + end
  15 +
  16 + def feed_url
  17 + if self.issue?
  18 + h.project_issue_url(self.project, self.issue)
  19 + elsif self.merge_request?
  20 + h.project_merge_request_url(self.project, self.merge_request)
  21 + elsif self.push?
  22 + h.project_commits_url(self.project, :ref => self.ref_name)
  23 + end
  24 + end
  25 +end
app/helpers/application_helper.rb
@@ -134,4 +134,8 @@ module ApplicationHelper @@ -134,4 +134,8 @@ module ApplicationHelper
134 end 134 end
135 active ? "current" : nil 135 active ? "current" : nil
136 end 136 end
  137 +
  138 + def hexdigest(string)
  139 + Digest::SHA1.hexdigest string
  140 + end
137 end 141 end
app/helpers/tree_helper.rb 0 → 100644
@@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
  1 +module TreeHelper
  2 + def tree_icon(content)
  3 + if content.is_a?(Grit::Blob)
  4 + if content.text?
  5 + image_tag "file_txt.png"
  6 + elsif content.image?
  7 + image_tag "file_img.png"
  8 + else
  9 + image_tag "file_bin.png"
  10 + end
  11 + else
  12 + image_tag "file_dir.png"
  13 + end
  14 + end
  15 +
  16 + def tree_hex_class(content)
  17 + "file_#{hexdigest(content.name)}"
  18 + end
  19 +
  20 + def tree_full_path(content)
  21 + if params[:path]
  22 + File.join(params[:path], content.name)
  23 + else
  24 + content.name
  25 + end
  26 + end
  27 +end
app/models/commit.rb
@@ -80,6 +80,29 @@ class Commit @@ -80,6 +80,29 @@ class Commit
80 def commits_between(repo, from, to) 80 def commits_between(repo, from, to)
81 repo.commits_between(from, to).map { |c| Commit.new(c) } 81 repo.commits_between(from, to).map { |c| Commit.new(c) }
82 end 82 end
  83 +
  84 + def compare(project, from, to)
  85 + first = project.commit(to.try(:strip))
  86 + last = project.commit(from.try(:strip))
  87 +
  88 + result = {
  89 + :commits => [],
  90 + :diffs => [],
  91 + :commit => nil
  92 + }
  93 +
  94 + if first && last
  95 + commits = [first, last].sort_by(&:created_at)
  96 + younger = commits.first
  97 + older = commits.last
  98 +
  99 + result[:commits] = project.repo.commits_between(younger.id, older.id).map {|c| Commit.new(c)}
  100 + result[:diffs] = project.repo.diff(younger.id, older.id) rescue []
  101 + result[:commit] = Commit.new(older)
  102 + end
  103 +
  104 + result
  105 + end
83 end 106 end
84 107
85 def persisted? 108 def persisted?
app/models/event.rb
@@ -28,6 +28,10 @@ class Event &lt; ActiveRecord::Base @@ -28,6 +28,10 @@ class Event &lt; ActiveRecord::Base
28 end 28 end
29 end 29 end
30 30
  31 + def self.recent_for_user user
  32 + where(:project_id => user.projects.map(&:id)).recent
  33 + end
  34 +
31 # Next events currently enabled for system 35 # Next events currently enabled for system
32 # - push 36 # - push
33 # - new issue 37 # - new issue
app/models/merge_request.rb
@@ -22,7 +22,6 @@ class MergeRequest &lt; ActiveRecord::Base @@ -22,7 +22,6 @@ class MergeRequest &lt; ActiveRecord::Base
22 :should_remove_source_branch 22 :should_remove_source_branch
23 23
24 validates_presence_of :project_id 24 validates_presence_of :project_id
25 - validates_presence_of :assignee_id  
26 validates_presence_of :author_id 25 validates_presence_of :author_id
27 validates_presence_of :source_branch 26 validates_presence_of :source_branch
28 validates_presence_of :target_branch 27 validates_presence_of :target_branch
@@ -36,6 +35,7 @@ class MergeRequest &lt; ActiveRecord::Base @@ -36,6 +35,7 @@ class MergeRequest &lt; ActiveRecord::Base
36 delegate :name, 35 delegate :name,
37 :email, 36 :email,
38 :to => :assignee, 37 :to => :assignee,
  38 + :allow_nil => true,
39 :prefix => true 39 :prefix => true
40 40
41 validates :title, 41 validates :title,
@@ -128,7 +128,7 @@ class MergeRequest &lt; ActiveRecord::Base @@ -128,7 +128,7 @@ class MergeRequest &lt; ActiveRecord::Base
128 128
129 def unmerged_diffs 129 def unmerged_diffs
130 commits = project.repo.commits_between(target_branch, source_branch).map {|c| Commit.new(c)} 130 commits = project.repo.commits_between(target_branch, source_branch).map {|c| Commit.new(c)}
131 - diffs = project.repo.diff(commits.first.prev_commit.id, commits.last.id) 131 + diffs = project.repo.diff(commits.first.prev_commit.id, commits.last.id) rescue []
132 end 132 end
133 133
134 def last_commit 134 def last_commit
app/models/project.rb
@@ -19,7 +19,7 @@ class Project &lt; ActiveRecord::Base @@ -19,7 +19,7 @@ class Project &lt; ActiveRecord::Base
19 has_many :notes, :dependent => :destroy 19 has_many :notes, :dependent => :destroy
20 has_many :snippets, :dependent => :destroy 20 has_many :snippets, :dependent => :destroy
21 has_many :deploy_keys, :dependent => :destroy, :foreign_key => "project_id", :class_name => "Key" 21 has_many :deploy_keys, :dependent => :destroy, :foreign_key => "project_id", :class_name => "Key"
22 - has_many :web_hooks, :dependent => :destroy 22 + has_many :hooks, :dependent => :destroy, :class_name => "ProjectHook"
23 has_many :wikis, :dependent => :destroy 23 has_many :wikis, :dependent => :destroy
24 has_many :protected_branches, :dependent => :destroy 24 has_many :protected_branches, :dependent => :destroy
25 25
@@ -120,7 +120,7 @@ class Project &lt; ActiveRecord::Base @@ -120,7 +120,7 @@ class Project &lt; ActiveRecord::Base
120 errors.add(:path, " like 'gitolite-admin' is not allowed") 120 errors.add(:path, " like 'gitolite-admin' is not allowed")
121 end 121 end
122 end 122 end
123 - 123 +
124 def self.access_options 124 def self.access_options
125 UsersProject.access_roles 125 UsersProject.access_roles
126 end 126 end
app/models/project_hook.rb 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +class ProjectHook < WebHook
  2 + belongs_to :project
  3 +end
app/models/system_hook.rb 0 → 100644
@@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
  1 +class SystemHook < WebHook
  2 +
  3 + def async_execute(data)
  4 + Resque.enqueue(SystemHookWorker, id, data)
  5 + end
  6 +
  7 + def self.all_hooks_fire(data)
  8 + SystemHook.all.each do |sh|
  9 + sh.async_execute data
  10 + end
  11 + end
  12 +
  13 +end
app/models/user.rb
1 class User < ActiveRecord::Base 1 class User < ActiveRecord::Base
  2 +
2 include Account 3 include Account
3 4
4 - devise :database_authenticatable, :token_authenticatable, 5 + devise :database_authenticatable, :token_authenticatable, :lockable,
5 :recoverable, :rememberable, :trackable, :validatable, :omniauthable 6 :recoverable, :rememberable, :trackable, :validatable, :omniauthable
6 7
7 attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, 8 attr_accessible :email, :password, :password_confirmation, :remember_me, :bio,
8 - :name, :projects_limit, :skype, :linkedin, :twitter, :dark_scheme, 9 + :name, :projects_limit, :skype, :linkedin, :twitter, :dark_scheme,
9 :theme_id, :force_random_password 10 :theme_id, :force_random_password
10 11
11 attr_accessor :force_random_password 12 attr_accessor :force_random_password
@@ -15,6 +16,11 @@ class User &lt; ActiveRecord::Base @@ -15,6 +16,11 @@ class User &lt; ActiveRecord::Base
15 has_many :my_own_projects, :class_name => "Project", :foreign_key => :owner_id 16 has_many :my_own_projects, :class_name => "Project", :foreign_key => :owner_id
16 has_many :keys, :dependent => :destroy 17 has_many :keys, :dependent => :destroy
17 18
  19 + has_many :events,
  20 + :class_name => "Event",
  21 + :foreign_key => :author_id,
  22 + :dependent => :destroy
  23 +
18 has_many :recent_events, 24 has_many :recent_events,
19 :class_name => "Event", 25 :class_name => "Event",
20 :foreign_key => :author_id, 26 :foreign_key => :author_id,
@@ -80,7 +86,8 @@ class User &lt; ActiveRecord::Base @@ -80,7 +86,8 @@ class User &lt; ActiveRecord::Base
80 86
81 def self.find_for_ldap_auth(omniauth_info) 87 def self.find_for_ldap_auth(omniauth_info)
82 name = omniauth_info.name.force_encoding("utf-8") 88 name = omniauth_info.name.force_encoding("utf-8")
83 - email = omniauth_info.email.downcase 89 + email = omniauth_info.email.downcase unless omniauth_info.email.nil?
  90 + raise OmniAuth::Error, "LDAP accounts must provide an email address" if email.nil?
84 91
85 if @user = User.find_by_email(email) 92 if @user = User.find_by_email(email)
86 @user 93 @user
app/models/users_project.rb
@@ -68,7 +68,7 @@ class UsersProject &lt; ActiveRecord::Base @@ -68,7 +68,7 @@ class UsersProject &lt; ActiveRecord::Base
68 end 68 end
69 69
70 def repo_access_human 70 def repo_access_human
71 - "" 71 + self.class.access_roles.invert[self.project_access]
72 end 72 end
73 end 73 end
74 # == Schema Information 74 # == Schema Information
app/models/web_hook.rb
@@ -4,8 +4,6 @@ class WebHook &lt; ActiveRecord::Base @@ -4,8 +4,6 @@ class WebHook &lt; ActiveRecord::Base
4 # HTTParty timeout 4 # HTTParty timeout
5 default_timeout 10 5 default_timeout 10
6 6
7 - belongs_to :project  
8 -  
9 validates :url, 7 validates :url,
10 presence: true, 8 presence: true,
11 format: { 9 format: {
@@ -14,9 +12,8 @@ class WebHook &lt; ActiveRecord::Base @@ -14,9 +12,8 @@ class WebHook &lt; ActiveRecord::Base
14 12
15 def execute(data) 13 def execute(data)
16 WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }) 14 WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" })
17 - rescue  
18 - # There was a problem calling this web hook, let's forget about it.  
19 end 15 end
  16 +
20 end 17 end
21 # == Schema Information 18 # == Schema Information
22 # 19 #
app/observers/mailer_observer.rb
@@ -43,7 +43,7 @@ class MailerObserver &lt; ActiveRecord::Observer @@ -43,7 +43,7 @@ class MailerObserver &lt; ActiveRecord::Observer
43 end 43 end
44 44
45 def new_merge_request(merge_request) 45 def new_merge_request(merge_request)
46 - if merge_request.assignee != current_user 46 + if merge_request.assignee && merge_request.assignee != current_user
47 Notify.new_merge_request_email(merge_request.id).deliver 47 Notify.new_merge_request_email(merge_request.id).deliver
48 end 48 end
49 end 49 end
app/observers/system_hook_observer.rb 0 → 100644
@@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
  1 +class SystemHookObserver < ActiveRecord::Observer
  2 + observe :user, :project, :users_project
  3 +
  4 + def after_create(model)
  5 + if model.kind_of? Project
  6 + SystemHook.all_hooks_fire({
  7 + event_name: "project_create",
  8 + name: model.name,
  9 + path: model.path,
  10 + project_id: model.id,
  11 + owner_name: model.owner.name,
  12 + owner_email: model.owner.email,
  13 + created_at: model.created_at
  14 + })
  15 + elsif model.kind_of? User
  16 + SystemHook.all_hooks_fire({
  17 + event_name: "user_create",
  18 + name: model.name,
  19 + email: model.email,
  20 + created_at: model.created_at
  21 + })
  22 +
  23 + elsif model.kind_of? UsersProject
  24 + SystemHook.all_hooks_fire({
  25 + event_name: "user_add_to_team",
  26 + project_name: model.project.name,
  27 + project_path: model.project.path,
  28 + project_id: model.project_id,
  29 + user_name: model.user.name,
  30 + user_email: model.user.email,
  31 + project_access: model.repo_access_human,
  32 + created_at: model.created_at
  33 + })
  34 +
  35 + end
  36 + end
  37 +
  38 + def after_destroy(model)
  39 + if model.kind_of? Project
  40 + SystemHook.all_hooks_fire({
  41 + event_name: "project_destroy",
  42 + name: model.name,
  43 + path: model.path,
  44 + project_id: model.id,
  45 + owner_name: model.owner.name,
  46 + owner_email: model.owner.email,
  47 + })
  48 + elsif model.kind_of? User
  49 + SystemHook.all_hooks_fire({
  50 + event_name: "user_destroy",
  51 + name: model.name,
  52 + email: model.email
  53 + })
  54 +
  55 + elsif model.kind_of? UsersProject
  56 + SystemHook.all_hooks_fire({
  57 + event_name: "user_remove_from_team",
  58 + project_name: model.project.name,
  59 + project_path: model.project.path,
  60 + project_id: model.project_id,
  61 + user_name: model.user.name,
  62 + user_email: model.user.email,
  63 + project_access: model.repo_access_human
  64 + })
  65 + end
  66 + end
  67 +end
app/roles/account.rb
@@ -55,4 +55,8 @@ module Account @@ -55,4 +55,8 @@ module Account
55 # Take only latest one 55 # Take only latest one
56 events = events.recent.limit(1).first 56 events = events.recent.limit(1).first
57 end 57 end
  58 +
  59 + def projects_with_events
  60 + projects.includes(:events).order("events.created_at DESC")
  61 + end
58 end 62 end
app/roles/git_push.rb
@@ -27,7 +27,7 @@ module GitPush @@ -27,7 +27,7 @@ module GitPush
27 true 27 true
28 end 28 end
29 29
30 - def execute_web_hooks(oldrev, newrev, ref, user) 30 + def execute_hooks(oldrev, newrev, ref, user)
31 ref_parts = ref.split('/') 31 ref_parts = ref.split('/')
32 32
33 # Return if this is not a push to a branch (e.g. new commits) 33 # Return if this is not a push to a branch (e.g. new commits)
@@ -35,7 +35,7 @@ module GitPush @@ -35,7 +35,7 @@ module GitPush
35 35
36 data = post_receive_data(oldrev, newrev, ref, user) 36 data = post_receive_data(oldrev, newrev, ref, user)
37 37
38 - web_hooks.each { |web_hook| web_hook.execute(data) } 38 + hooks.each { |hook| hook.execute(data) }
39 end 39 end
40 40
41 def post_receive_data(oldrev, newrev, ref, user) 41 def post_receive_data(oldrev, newrev, ref, user)
@@ -97,7 +97,7 @@ module GitPush @@ -97,7 +97,7 @@ module GitPush
97 self.update_merge_requests(oldrev, newrev, ref, user) 97 self.update_merge_requests(oldrev, newrev, ref, user)
98 98
99 # Execute web hooks 99 # Execute web hooks
100 - self.execute_web_hooks(oldrev, newrev, ref, user) 100 + self.execute_hooks(oldrev, newrev, ref, user)
101 101
102 # Create satellite 102 # Create satellite
103 self.satellite.create unless self.satellite.exists? 103 self.satellite.create unless self.satellite.exists?
app/views/admin/hooks/_data_ex.html.erb 0 → 100644
@@ -0,0 +1,66 @@ @@ -0,0 +1,66 @@
  1 +<% data_ex_str = <<eos
  2 +1. Project created:
  3 +{
  4 + "created_at": "2012-07-21T07:30:54Z",
  5 + "event_name": "project_create",
  6 + "name": "StoreCloud",
  7 + "owner_email": "johnsmith@gmail.com",
  8 + "owner_name": "John Smith",
  9 + "path": "storecloud",
  10 + "project_id": 74
  11 +}
  12 +
  13 +2. Project destroyed:
  14 +{
  15 + "event_name": "project_destroy",
  16 + "name": "Underscore",
  17 + "owner_email": "johnsmith@gmail.com",
  18 + "owner_name": "John Smith",
  19 + "path": "underscore",
  20 + "project_id": 73
  21 +}
  22 +
  23 +3. New Team Member:
  24 +{
  25 + "created_at": "2012-07-21T07:30:56Z",
  26 + "event_name": "user_add_to_team",
  27 + "project_access": "Master",
  28 + "project_id": 74,
  29 + "project_name": "StoreCloud",
  30 + "project_path": "storecloud",
  31 + "owner_email": "johnsmith@gmail.com",
  32 + "owner_name": "John Smith",
  33 +}
  34 +
  35 +4. Team Member Removed:
  36 +{
  37 + "created_at": "2012-07-21T07:30:56Z",
  38 + "event_name": "user_remove_from_team",
  39 + "project_access": "Master",
  40 + "project_id": 74,
  41 + "project_name": "StoreCloud",
  42 + "project_path": "storecloud",
  43 + "owner_email": "johnsmith@gmail.com",
  44 + "owner_name": "John Smith",
  45 +}
  46 +
  47 +5. User created:
  48 +{
  49 + "created_at": "2012-07-21T07:44:07Z",
  50 + "email": "js@gitlabhq.com",
  51 + "event_name": "user_create",
  52 + "name": "John Smith"
  53 +}
  54 +
  55 +6. User removed:
  56 +{
  57 + "created_at": "2012-07-21T07:44:07Z",
  58 + "email": "js@gitlabhq.com",
  59 + "event_name": "user_destroy",
  60 + "name": "John Smith"
  61 +}
  62 +
  63 +eos
  64 +%>
  65 +<% js_lexer = Pygments::Lexer[:js] %>
  66 +<%= raw js_lexer.highlight(data_ex_str) %>
app/views/admin/hooks/index.html.haml 0 → 100644
@@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
  1 +.alert.alert-info
  2 + %span
  3 + Post receive hooks for binding events.
  4 + %br
  5 + Read more about system hooks
  6 + %strong #{link_to "here", help_system_hooks_path, :class => "vlink"}
  7 +
  8 += form_for @hook, :as => :hook, :url => admin_hooks_path do |f|
  9 + -if @hook.errors.any?
  10 + .alert-message.block-message.error
  11 + - @hook.errors.full_messages.each do |msg|
  12 + %p= msg
  13 + .clearfix
  14 + = f.label :url, "URL:"
  15 + .input
  16 + = f.text_field :url, :class => "text_field xxlarge"
  17 + &nbsp;
  18 + = f.submit "Add System Hook", :class => "btn primary"
  19 +%hr
  20 +
  21 +-if @hooks.any?
  22 + %h3
  23 + Hooks
  24 + %small (#{@hooks.count})
  25 + %br
  26 + %table.admin-table
  27 + %tr
  28 + %th URL
  29 + %th Method
  30 + %th
  31 + - @hooks.each do |hook|
  32 + %tr
  33 + %td
  34 + = link_to admin_hook_path(hook) do
  35 + %strong= hook.url
  36 + = link_to 'Test Hook', admin_hook_test_path(hook), :class => "btn small right"
  37 + %td POST
  38 + %td
  39 + = link_to 'Remove', admin_hook_path(hook), :confirm => 'Are you sure?', :method => :delete, :class => "danger btn small right"
app/views/admin/logs/show.html.haml
1 -%h4  
2 - %i.icon-file  
3 - githost.log  
4 -%pre.logs  
5 - - Gitlab::Logger.read_latest.each do |line|  
6 - %span.log= line 1 +.file_holder#README
  2 + .file_title
  3 + %i.icon-file
  4 + githost.log
  5 + .file_content.logs
  6 + %ol
  7 + - Gitlab::Logger.read_latest.each do |line|
  8 + %li
  9 + %p= line
app/views/admin/mailer/preview.html.haml
@@ -1,28 +0,0 @@ @@ -1,28 +0,0 @@
1 -%p This is page with preview for all system emails that are sent to user  
2 -%p Email previews built based on existing Project/Commit/Issue base - so some preview maybe unavailable unless object appear in system  
3 -  
4 -#accordion  
5 - %h3  
6 - %a New user  
7 - %div  
8 - %iframe{ :src=> admin_mailer_preview_user_new_path, :width=>"100%", :height=>"350"}  
9 - %h3  
10 - %a New issue  
11 - %div  
12 - %iframe{ :src=> admin_mailer_preview_issue_new_path, :width=>"100%", :height=>"350"}  
13 - %h3  
14 - %a Commit note  
15 - %div  
16 - %iframe{ :src=> admin_mailer_preview_note_path(:type => "Commit"), :width=>"100%", :height=>"350"}  
17 - %h3  
18 - %a Issue note  
19 - %div  
20 - %iframe{ :src=> admin_mailer_preview_note_path(:type => "Issue"), :width=>"100%", :height=>"350"}  
21 - %h3  
22 - %a Wall note  
23 - %div  
24 - %iframe{ :src=> admin_mailer_preview_note_path(:type => "Wall"), :width=>"100%", :height=>"350"}  
25 -  
26 -:javascript  
27 - $(function() {  
28 - $("#accordion").accordion(); });  
app/views/admin/projects/index.html.haml
@@ -13,8 +13,8 @@ @@ -13,8 +13,8 @@
13 %th Team Members 13 %th Team Members
14 %th Post Receive 14 %th Post Receive
15 %th Last Commit 15 %th Last Commit
16 - %th  
17 - %th 16 + %th Edit
  17 + %th.cred Danger Zone!
18 18
19 - @admin_projects.each do |project| 19 - @admin_projects.each do |project|
20 %tr 20 %tr
@@ -24,5 +24,5 @@ @@ -24,5 +24,5 @@
24 %td= check_box_tag :post_receive_file, 1, project.has_post_receive_file?, :disabled => true 24 %td= check_box_tag :post_receive_file, 1, project.has_post_receive_file?, :disabled => true
25 %td= last_commit(project) 25 %td= last_commit(project)
26 %td= link_to 'Edit', edit_admin_project_path(project), :id => "edit_#{dom_id(project)}", :class => "btn small" 26 %td= link_to 'Edit', edit_admin_project_path(project), :id => "edit_#{dom_id(project)}", :class => "btn small"
27 - %td= link_to 'Destroy', [:admin, project], :confirm => 'Are you sure?', :method => :delete, :class => "btn small danger" 27 + %td.bgred= link_to 'Destroy', [:admin, project], :confirm => "REMOVE #{project.name}? Are you sure?", :method => :delete, :class => "btn small danger"
28 = paginate @admin_projects, :theme => "admin" 28 = paginate @admin_projects, :theme => "admin"
app/views/admin/users/_form.html.haml
@@ -50,7 +50,7 @@ @@ -50,7 +50,7 @@
50 50
51 .alert 51 .alert
52 .clearfix 52 .clearfix
53 - %p Give user ability to manage application. 53 + %p Make the user a GitLab administrator.
54 = f.label :admin, :class => "checkbox" do 54 = f.label :admin, :class => "checkbox" do
55 = f.check_box :admin 55 = f.check_box :admin
56 %span Administrator 56 %span Administrator
@@ -59,11 +59,11 @@ @@ -59,11 +59,11 @@
59 - if @admin_user.blocked 59 - if @admin_user.blocked
60 %span 60 %span
61 = link_to 'Unblock', unblock_admin_user_path(@admin_user), :method => :put, :class => "btn small" 61 = link_to 'Unblock', unblock_admin_user_path(@admin_user), :method => :put, :class => "btn small"
62 - This user is blocked and is not able to login GitLab 62 + This user is blocked and is not able to login to GitLab
63 - else 63 - else
64 %span 64 %span
65 = link_to 'Block', block_admin_user_path(@admin_user), :confirm => 'USER WILL BE BLOCKED! Are you sure?', :method => :put, :class => "btn small danger" 65 = link_to 'Block', block_admin_user_path(@admin_user), :confirm => 'USER WILL BE BLOCKED! Are you sure?', :method => :put, :class => "btn small danger"
66 - Blocked user will removed from all projects &amp; will not be able to login to GitLab. 66 + Blocked users will be removed from all projects &amp; will not be able to login to GitLab.
67 .actions 67 .actions
68 = f.submit 'Save', :class => "btn primary" 68 = f.submit 'Save', :class => "btn primary"
69 - if @admin_user.new_record? 69 - if @admin_user.new_record?
app/views/admin/users/index.html.haml
@@ -27,7 +27,7 @@ @@ -27,7 +27,7 @@
27 %th Projects 27 %th Projects
28 %th Edit 28 %th Edit
29 %th Blocked 29 %th Blocked
30 - %th 30 + %th.cred Danger Zone!
31 31
32 - @admin_users.each do |user| 32 - @admin_users.each do |user|
33 %tr 33 %tr
@@ -41,6 +41,6 @@ @@ -41,6 +41,6 @@
41 = link_to 'Unblock', unblock_admin_user_path(user), :method => :put, :class => "btn small success" 41 = link_to 'Unblock', unblock_admin_user_path(user), :method => :put, :class => "btn small success"
42 - else 42 - else
43 = link_to 'Block', block_admin_user_path(user), :confirm => 'USER WILL BE BLOCKED! Are you sure?', :method => :put, :class => "btn small danger" 43 = link_to 'Block', block_admin_user_path(user), :confirm => 'USER WILL BE BLOCKED! Are you sure?', :method => :put, :class => "btn small danger"
44 - %td= link_to 'Destroy', [:admin, user], :confirm => 'USER WILL BE REMOVED! Are you sure?', :method => :delete, :class => "btn small danger" 44 + %td.bgred= link_to 'Destroy', [:admin, user], :confirm => "USER #{user.name} WILL BE REMOVED! Are you sure?", :method => :delete, :class => "btn small danger"
45 45
46 = paginate @admin_users, :theme => "admin" 46 = paginate @admin_users, :theme => "admin"
app/views/commits/_commits.html.haml
1 - @commits.group_by { |c| c.committed_date.to_date }.each do |day, commits| 1 - @commits.group_by { |c| c.committed_date.to_date }.each do |day, commits|
2 %div.ui-box 2 %div.ui-box
3 - %h5= day.stamp("28 Aug, 2010") 3 + %h5.small
  4 + %i.icon-calendar
  5 + = day.stamp("28 Aug, 2010")
4 %ul.unstyled= render commits 6 %ul.unstyled= render commits
app/views/commits/_diffs.html.haml
@@ -35,7 +35,13 @@ @@ -35,7 +35,13 @@
35 - if file.text? 35 - if file.text?
36 = render "commits/text_file", :diff => diff, :index => i 36 = render "commits/text_file", :diff => diff, :index => i
37 - elsif file.image? 37 - elsif file.image?
38 - .diff_file_content_image{:class => image_diff_class(diff)}  
39 - %img{:src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} 38 + - if diff.renamed_file || diff.new_file || diff.deleted_file
  39 + .diff_file_content_image
  40 + %img{:class => image_diff_class(diff), :src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
  41 + - else
  42 + - old_file = (@commit.prev_commit.tree / diff.old_path)
  43 + .diff_file_content_image.img_compared
  44 + %img{:class => "diff_image_removed", :src => "data:#{file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
  45 + %img{:class => "diff_image_added", :src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
40 - else 46 - else
41 %p.nothing_here_message No preview for this file type 47 %p.nothing_here_message No preview for this file type
app/views/commits/_head.html.haml
@@ -13,12 +13,12 @@ @@ -13,12 +13,12 @@
13 %li{:class => "#{branches_tab_class}"} 13 %li{:class => "#{branches_tab_class}"}
14 = link_to project_repository_path(@project) do 14 = link_to project_repository_path(@project) do
15 Branches 15 Branches
16 - %span.number= @project.repo.branch_count 16 + %span.badge= @project.repo.branch_count
17 17
18 %li{:class => "#{'active' if current_page?(tags_project_repository_path(@project)) }"} 18 %li{:class => "#{'active' if current_page?(tags_project_repository_path(@project)) }"}
19 = link_to tags_project_repository_path(@project) do 19 = link_to tags_project_repository_path(@project) do
20 Tags 20 Tags
21 - %span.number= @project.repo.tag_count 21 + %span.badge= @project.repo.tag_count
22 22
23 23
24 - if current_page?(project_commits_path(@project)) && current_user.private_token 24 - if current_page?(project_commits_path(@project)) && current_user.private_token
app/views/commits/compare.html.haml
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 = "..." 20 = "..."
21 = text_field_tag :to, params[:to], :placeholder => "aa8b4ef", :class => "xlarge" 21 = text_field_tag :to, params[:to], :placeholder => "aa8b4ef", :class => "xlarge"
22 .actions 22 .actions
23 - = submit_tag "Compare", :class => "btn primary" 23 + = submit_tag "Compare", :class => "btn btn-primary"
24 24
25 25
26 - unless @commits.empty? 26 - unless @commits.empty?
app/views/dashboard/index.atom.builder
@@ -8,17 +8,10 @@ xml.feed &quot;xmlns&quot; =&gt; &quot;http://www.w3.org/2005/Atom&quot;, &quot;xmlns:media&quot; =&gt; &quot;http://sear @@ -8,17 +8,10 @@ xml.feed &quot;xmlns&quot; =&gt; &quot;http://www.w3.org/2005/Atom&quot;, &quot;xmlns:media&quot; =&gt; &quot;http://sear
8 8
9 @events.each do |event| 9 @events.each do |event|
10 if event.allowed? 10 if event.allowed?
  11 + event = EventDecorator.decorate(event)
11 xml.entry do 12 xml.entry do
12 - if event.issue?  
13 - event_link = project_issue_url(event.project, event.issue)  
14 - event_title = event.issue_title  
15 - elsif event.merge_request?  
16 - event_link = project_merge_request_url(event.project, event.merge_request)  
17 - event_title = event.merge_request_title  
18 - elsif event.push?  
19 - event_link = project_commits_url(event.project, :ref => event.ref_name)  
20 - event_title = event.ref_name  
21 - end 13 + event_link = event.feed_url
  14 + event_title = event.feed_title
22 15
23 xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}" 16 xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
24 xml.link :href => event_link 17 xml.link :href => event_link
app/views/dashboard/index.html.haml
@@ -10,9 +10,10 @@ @@ -10,9 +10,10 @@
10 add new key 10 add new key
11 to your profile 11 to your profile
12 - if @events.any? 12 - if @events.any?
13 - = render @events 13 + .content_list= render @events
14 - else 14 - else
15 %h4.nothing_here_message Projects activity will be displayed here 15 %h4.nothing_here_message Projects activity will be displayed here
  16 + .loading.hide
16 .side 17 .side
17 = render "events/event_last_push", :event => @last_push 18 = render "events/event_last_push", :event => @last_push
18 .projects_box 19 .projects_box
@@ -54,3 +55,7 @@ @@ -54,3 +55,7 @@
54 New Project » 55 New Project »
55 - else 56 - else
56 If you will be added to project - it will be displayed here 57 If you will be added to project - it will be displayed here
  58 +
  59 +
  60 +:javascript
  61 + $(function(){ Pager.init(20); });
app/views/dashboard/index.js.haml
1 :plain 1 :plain
2 - $(".projects .activities").append("#{escape_javascript(render(@events))}"); 2 + Pager.append(#{@events.count}, "#{escape_javascript(render(@events))}");
app/views/events/_event_issue.html.haml
1 = image_tag gravatar_icon(event.author_email), :class => "avatar" 1 = image_tag gravatar_icon(event.author_email), :class => "avatar"
2 %strong #{event.author_name} 2 %strong #{event.author_name}
3 -%span.event_label= event.action_name  
4 -&nbsp;issue 3 +%span.event_label{:class => event.action_name}= event.action_name
  4 +issue
5 = link_to project_issue_path(event.project, event.issue) do 5 = link_to project_issue_path(event.project, event.issue) do
6 %strong= truncate event.issue_title 6 %strong= truncate event.issue_title
7 at 7 at
app/views/events/_event_last_push.html.haml
@@ -5,12 +5,9 @@ @@ -5,12 +5,9 @@
5 %span Your pushed to 5 %span Your pushed to
6 = event.ref_type 6 = event.ref_type
7 = link_to project_commits_path(event.project, :ref => event.ref_name) do 7 = link_to project_commits_path(event.project, :ref => event.ref_name) do
8 - %strong= event.ref_name 8 + %strong= truncate(event.ref_name, :length => 28)
9 at 9 at
10 %strong= link_to event.project.name, event.project 10 %strong= link_to event.project.name, event.project
11 - %span.cgray  
12 - = time_ago_in_words(event.created_at)  
13 - ago.  
14 11
15 = link_to new_mr_path_from_push_event(event), :title => "New Merge Request", :class => "btn very_small primary" do 12 = link_to new_mr_path_from_push_event(event), :title => "New Merge Request", :class => "btn very_small primary" do
16 Create Merge Request 13 Create Merge Request
app/views/events/_event_merge_request.html.haml
@@ -2,8 +2,8 @@ @@ -2,8 +2,8 @@
2 .event_icon= image_tag "event_mr_merged.png" 2 .event_icon= image_tag "event_mr_merged.png"
3 = image_tag gravatar_icon(event.author_email), :class => "avatar" 3 = image_tag gravatar_icon(event.author_email), :class => "avatar"
4 %strong #{event.author_name} 4 %strong #{event.author_name}
5 -%span.event_label= event.action_name  
6 -&nbsp;merge request 5 +%span.event_label{:class => event.action_name}= event.action_name
  6 +merge request
7 = link_to project_merge_request_path(event.project, event.merge_request) do 7 = link_to project_merge_request_path(event.project, event.merge_request) do
8 %strong= truncate event.merge_request_title 8 %strong= truncate event.merge_request_title
9 at 9 at
app/views/events/_event_push.html.haml
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 .event_icon= image_tag "event_push.png" 2 .event_icon= image_tag "event_push.png"
3 = image_tag gravatar_icon(event.author_email), :class => "avatar" 3 = image_tag gravatar_icon(event.author_email), :class => "avatar"
4 %strong #{event.author_name} 4 %strong #{event.author_name}
5 - %span.event_label= event.push_action_name 5 + %span.event_label.pushed= event.push_action_name
6 = event.ref_type 6 = event.ref_type
7 = link_to project_commits_path(event.project, :ref => event.ref_name) do 7 = link_to project_commits_path(event.project, :ref => event.ref_name) do
8 %strong= event.ref_name 8 %strong= event.ref_name
app/views/help/api.html.haml 0 → 100644
@@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
  1 +%h3 API
  2 +.back_link
  3 + = link_to help_path do
  4 + &larr; to index
  5 +%hr
  6 +
  7 +%ol
  8 + %li
  9 + %a{:href => "#README"} README
  10 + %li
  11 + %a{:href => "#projects"} Projects
  12 + %li
  13 + %a{:href => "#users"} Users
  14 +
  15 +.file_holder#README
  16 + .file_title
  17 + %i.icon-file
  18 + README
  19 + .file_content.wiki
  20 + = preserve do
  21 + = markdown File.read(Rails.root.join("doc", "api", "README.md"))
  22 +
  23 +%br
  24 +
  25 +.file_holder#projects
  26 + .file_title
  27 + %i.icon-file
  28 + Projects
  29 + .file_content.wiki
  30 + = preserve do
  31 + = markdown File.read(Rails.root.join("doc", "api", "projects.md"))
  32 +
  33 +%br
  34 +
  35 +.file_holder#users
  36 + .file_title
  37 + %i.icon-file
  38 + Users
  39 + .file_content.wiki
  40 + = preserve do
  41 + = markdown File.read(Rails.root.join("doc", "api", "users.md"))
app/views/help/index.html.haml
@@ -22,3 +22,9 @@ @@ -22,3 +22,9 @@
22 22
23 %li 23 %li
24 %h5= link_to "Web Hooks", help_web_hooks_path 24 %h5= link_to "Web Hooks", help_web_hooks_path
  25 +
  26 + %li
  27 + %h5= link_to "System Hooks", help_system_hooks_path
  28 +
  29 + %li
  30 + %h5= link_to "API", help_api_path
app/views/help/system_hooks.html.haml 0 → 100644
@@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
  1 +%h3 System hooks
  2 +.back_link
  3 + = link_to :back do
  4 + &larr; back
  5 +%hr
  6 +
  7 +%p.slead
  8 + Your Gitlab instance can perform HTTP POST request on next event: create_project, delete_project, create_user, delete_user, change_team_member.
  9 + %br
  10 + System Hooks can be used for logging or change information in LDAP server.
  11 + %br
  12 +%h5 Hooks request example:
  13 += render "admin/hooks/data_ex"
app/views/issues/_issues.html.haml
@@ -6,7 +6,9 @@ @@ -6,7 +6,9 @@
6 .row 6 .row
7 .span7= paginate @issues, :remote => true, :theme => "gitlab" 7 .span7= paginate @issues, :remote => true, :theme => "gitlab"
8 .span3.right 8 .span3.right
9 - %span.cgray.right #{@issues.total_count} issues for this filter 9 + %span.cgray.right
  10 + %span.issue_counter #{@issues.total_count}
  11 + issues for this filter
10 - else 12 - else
11 %li 13 %li
12 %h4.nothing_here_message Nothing to show here 14 %h4.nothing_here_message Nothing to show here
app/views/issues/_show.html.haml
@@ -12,9 +12,9 @@ @@ -12,9 +12,9 @@
12 = issue.notes.count 12 = issue.notes.count
13 - if can? current_user, :modify_issue, issue 13 - if can? current_user, :modify_issue, issue
14 - if issue.closed 14 - if issue.closed
15 - = link_to 'Reopen', project_issue_path(issue.project, issue, :issue => {:closed => false }, :status_only => true), :method => :put, :class => "btn small grouped", :remote => true 15 + = 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
16 - else 16 - else
17 - = link_to 'Resolve', project_issue_path(issue.project, issue, :issue => {:closed => true }, :status_only => true), :method => :put, :class => "success btn small grouped", :remote => true 17 + = 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
18 = link_to edit_project_issue_path(issue.project, issue), :class => "btn small edit-issue-link", :remote => true do 18 = link_to edit_project_issue_path(issue.project, issue), :class => "btn small edit-issue-link", :remote => true do
19 %i.icon-edit 19 %i.icon-edit
20 Edit 20 Edit
@@ -35,6 +35,4 @@ @@ -35,6 +35,4 @@
35 &nbsp; 35 &nbsp;
36 36
37 - if issue.upvotes > 0 37 - if issue.upvotes > 0
38 - %span.badge.badge-success= "+#{issue.upvotes}"  
39 -  
40 - 38 + %span.badge.badge-success= "+#{issue.upvotes}"
41 \ No newline at end of file 39 \ No newline at end of file
app/views/issues/index.html.haml
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 .issues_content 2 .issues_content
3 %h3.page_title 3 %h3.page_title
4 Issues 4 Issues
5 - %small (#{@issues.total_count}) 5 + %small (<span class=issue_counter>#{@issues.total_count}</span>)
6 .right 6 .right
7 .span5 7 .span5
8 - if can? current_user, :write_issue, @project 8 - if can? current_user, :write_issue, @project
@@ -45,4 +45,4 @@ @@ -45,4 +45,4 @@
45 :javascript 45 :javascript
46 $(function(){ 46 $(function(){
47 issuesPage(); 47 issuesPage();
48 - }) 48 - })
  49 + })
49 \ No newline at end of file 50 \ No newline at end of file
app/views/keys/new.html.haml
1 -%h3 New key 1 +%h3.page_title New key
2 %hr 2 %hr
3 = render 'form' 3 = render 'form'
4 4
@@ -11,4 +11,4 @@ @@ -11,4 +11,4 @@
11 if( key_mail && key_mail.length > 0 && title.val() == '' ){ 11 if( key_mail && key_mail.length > 0 && title.val() == '' ){
12 $('#key_title').val( key_mail ); 12 $('#key_title').val( key_mail );
13 } 13 }
14 - });  
15 \ No newline at end of file 14 \ No newline at end of file
  15 + });
app/views/layouts/_project_menu.html.haml
@@ -17,14 +17,14 @@ @@ -17,14 +17,14 @@
17 %li{:class => tab_class(:issues)} 17 %li{:class => tab_class(:issues)}
18 = link_to project_issues_filter_path(@project) do 18 = link_to project_issues_filter_path(@project) do
19 Issues 19 Issues
20 - %span.count= @project.issues.opened.count 20 + %span.count.issue_counter= @project.issues.opened.count
21 21
22 - if @project.repo_exists? 22 - if @project.repo_exists?
23 - if @project.merge_requests_enabled 23 - if @project.merge_requests_enabled
24 %li{:class => tab_class(:merge_requests)} 24 %li{:class => tab_class(:merge_requests)}
25 = link_to project_merge_requests_path(@project) do 25 = link_to project_merge_requests_path(@project) do
26 Merge Requests 26 Merge Requests
27 - %span.count= @project.merge_requests.opened.count 27 + %span.count.merge_counter= @project.merge_requests.opened.count
28 28
29 - if @project.wall_enabled 29 - if @project.wall_enabled
30 %li{:class => tab_class(:wall)} 30 %li{:class => tab_class(:wall)}
app/views/layouts/admin.html.haml
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 %li{:class => tab_class(:admin_logs)} 15 %li{:class => tab_class(:admin_logs)}
16 = link_to "Logs", admin_logs_path 16 = link_to "Logs", admin_logs_path
17 %li{:class => tab_class(:admin_emails)} 17 %li{:class => tab_class(:admin_emails)}
18 - = link_to "Emails", admin_emails_path 18 + = link_to "Hooks", admin_hooks_path
19 %li{:class => tab_class(:admin_resque)} 19 %li{:class => tab_class(:admin_resque)}
20 = link_to "Resque", admin_resque_path 20 = link_to "Resque", admin_resque_path
21 21
app/views/layouts/devise.html.haml
@@ -1,6 +0,0 @@ @@ -1,6 +0,0 @@
1 -!!! 5  
2 -%html{ :lang => "en"}  
3 - = render "layouts/head"  
4 - %body.ui_basic.login-page  
5 - = render :partial => "layouts/flash"  
6 - .container= yield  
app/views/layouts/devise_layout.html.haml 0 → 100644
@@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
  1 +!!! 5
  2 +%html{ :lang => "en"}
  3 + = render "layouts/head"
  4 + %body.ui_basic.login-page
  5 + = render :partial => "layouts/flash"
  6 + .container= yield
app/views/layouts/profile.html.haml
@@ -12,16 +12,17 @@ @@ -12,16 +12,17 @@
12 %li{:class => tab_class(:password)} 12 %li{:class => tab_class(:password)}
13 = link_to "Password", profile_password_path 13 = link_to "Password", profile_password_path
14 14
  15 + %li{:class => tab_class(:ssh_keys)}
  16 + = link_to keys_path do
  17 + SSH Keys
  18 + %span.count= current_user.keys.count
  19 +
15 %li{:class => tab_class(:token)} 20 %li{:class => tab_class(:token)}
16 = link_to "Token", profile_token_path 21 = link_to "Token", profile_token_path
17 22
18 %li{:class => tab_class(:design)} 23 %li{:class => tab_class(:design)}
19 = link_to "Design", profile_design_path 24 = link_to "Design", profile_design_path
20 25
21 - %li{:class => tab_class(:ssh_keys)}  
22 - = link_to keys_path do  
23 - SSH Keys  
24 - %span.count= current_user.keys.count  
25 26
26 .content 27 .content
27 = yield 28 = yield
app/views/merge_requests/_form.html.haml
@@ -5,7 +5,8 @@ @@ -5,7 +5,8 @@
5 - @merge_request.errors.full_messages.each do |msg| 5 - @merge_request.errors.full_messages.each do |msg|
6 %li= msg 6 %li= msg
7 7
8 - %h3.padded.cgray 1. Select Branches 8 + %h4.cdark 1. Select Branches
  9 + %br
9 10
10 .row 11 .row
11 .span6 12 .span6
@@ -30,14 +31,21 @@ @@ -30,14 +31,21 @@
30 .bottom_commit 31 .bottom_commit
31 .mr_target_commit 32 .mr_target_commit
32 33
33 - %h3.padded.cgray 2. Fill info 34 + %h4.cdark 2. Fill info
  35 +
34 .clearfix 36 .clearfix
35 - = f.label :assignee_id, "Assign to", :class => "control-label"  
36 - .controls= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { :include_blank => "Select user" }, :style => "width:250px") 37 + .main_box
  38 + .top_box_content
  39 + = f.label :title do
  40 + %strong= "Title *"
  41 + .input= f.text_field :title, :class => "input-xxlarge pad", :maxlength => 255, :rows => 5
  42 + .middle_box_content
  43 + = f.label :assignee_id do
  44 + %i.icon-user
  45 + Assign to
  46 + .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { :include_blank => "Select user" }, :style => "width:250px")
37 47
38 .control-group 48 .control-group
39 - = f.label :title, :class => "control-label"  
40 - .controls= f.text_field :title, :class => "input-xxlarge pad", :maxlength => 255, :rows => 5  
41 49
42 .form-actions 50 .form-actions
43 = f.submit 'Save', :class => "btn-primary btn" 51 = f.submit 'Save', :class => "btn-primary btn"
app/views/merge_requests/_merge_request.html.haml
@@ -15,12 +15,14 @@ @@ -15,12 +15,14 @@
15 &rarr; 15 &rarr;
16 = merge_request.target_branch 16 = merge_request.target_branch
17 = image_tag gravatar_icon(merge_request.author_email), :class => "avatar" 17 = image_tag gravatar_icon(merge_request.author_email), :class => "avatar"
  18 +
  19 + = link_to project_merge_request_path(merge_request.project, merge_request) do
  20 + %p.row_title= truncate(merge_request.title, :length => 80)
  21 +
18 %span.update-author 22 %span.update-author
19 - %strong= merge_request.author_name  
20 - authored 23 + %small.cdark= "##{merge_request.id}"
  24 + authored by #{merge_request.author_name}
21 = time_ago_in_words(merge_request.created_at) 25 = time_ago_in_words(merge_request.created_at)
22 ago 26 ago
23 - if merge_request.upvotes > 0 27 - if merge_request.upvotes > 0
24 %span.badge.badge-success= "+#{merge_request.upvotes}" 28 %span.badge.badge-success= "+#{merge_request.upvotes}"
25 - = link_to project_merge_request_path(merge_request.project, merge_request) do  
26 - %p.row_title= truncate(merge_request.title, :length => 80)  
app/views/merge_requests/edit.html.haml
1 -%h3 1 +%h3.page_title
2 = "Edit merge request #{@merge_request.id}" 2 = "Edit merge request #{@merge_request.id}"
3 %hr 3 %hr
4 = render 'form' 4 = render 'form'
app/views/merge_requests/new.html.haml
1 -%h3 New Merge Request 1 +%h3.page_title New Merge Request
2 %hr 2 %hr
3 = render 'form' 3 = render 'form'
app/views/merge_requests/show/_commits.html.haml
1 - if @commits.present? 1 - if @commits.present?
2 .ui-box 2 .ui-box
3 - %h5 Commits (#{@commits.count}) 3 + %h5
  4 + %i.icon-list
  5 + Commits (#{@commits.count})
4 .merge-request-commits 6 .merge-request-commits
5 - if @commits.count > 8 7 - if @commits.count > 8
6 %ul.first_mr_commits.unstyled 8 %ul.first_mr_commits.unstyled
app/views/merge_requests/show/_mr_box.html.haml
@@ -13,9 +13,10 @@ @@ -13,9 +13,10 @@
13 = image_tag gravatar_icon(@merge_request.author_email), :width => 16, :class => "lil_av" 13 = image_tag gravatar_icon(@merge_request.author_email), :width => 16, :class => "lil_av"
14 %strong.author= link_to_merge_request_author(@merge_request) 14 %strong.author= link_to_merge_request_author(@merge_request)
15 15
16 - %cite.cgray and currently assigned to  
17 - = image_tag gravatar_icon(@merge_request.assignee_email), :width => 16, :class => "lil_av"  
18 - %strong.author= link_to_merge_request_assignee(@merge_request) 16 + - if @merge_request.assignee
  17 + %cite.cgray and currently assigned to
  18 + = image_tag gravatar_icon(@merge_request.assignee_email), :width => 16, :class => "lil_av"
  19 + %strong.author= link_to_merge_request_assignee(@merge_request)
19 20
20 21
21 - if @merge_request.closed 22 - if @merge_request.closed
app/views/notes/_form.html.haml
@@ -32,4 +32,4 @@ @@ -32,4 +32,4 @@
32 %span Any file less than 10 MB 32 %span Any file less than 10 MB
33 33
34 34
35 - = f.submit 'Add Comment', :class => "btn primary", :id => "submit_note" 35 + = f.submit 'Add Comment', :class => "btn primary submit_note", :id => "submit_note"
app/views/notes/_per_line_form.html.haml
@@ -24,7 +24,7 @@ @@ -24,7 +24,7 @@
24 = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit" 24 = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
25 %span Commit author 25 %span Commit author
26 .actions 26 .actions
27 - = f.submit 'Add note', :class => "btn primary", :id => "submit_note" 27 + = f.submit 'Add note', :class => "btn primary submit_note", :id => "submit_note"
28 = link_to "Close", "#", :class => "btn hide-button" 28 = link_to "Close", "#", :class => "btn hide-button"
29 29
30 :javascript 30 :javascript
app/views/notes/_reply_button.html.haml
1 %tr.line_notes_row.reply 1 %tr.line_notes_row.reply
2 %td{:colspan => 3} 2 %td{:colspan => 3}
  3 + %i.icon-comment
3 = link_to "Reply", "#", :class => "line_note_reply_link", "line_code" => line_code, :title => "Add note for this line" 4 = link_to "Reply", "#", :class => "line_note_reply_link", "line_code" => line_code, :title => "Add note for this line"
app/views/refs/_tree.html.haml
@@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
13 = render :partial => "refs/tree_file", :locals => { :name => tree.name, :content => tree.data, :file => tree } 13 = render :partial => "refs/tree_file", :locals => { :name => tree.name, :content => tree.data, :file => tree }
14 - else 14 - else
15 - contents = tree.contents 15 - contents = tree.contents
16 - %table#tree-slider.bordered-table.table 16 + %table#tree-slider.bordered-table.table{:class => "table_#{@hex_path}" }
17 %thead 17 %thead
18 %th Name 18 %th Name
19 %th Last Update 19 %th Last Update
@@ -29,34 +29,39 @@ @@ -29,34 +29,39 @@
29 %td 29 %td
30 %td 30 %td
31 31
  32 + - index = 0
32 - contents.select{ |i| i.is_a?(Grit::Tree)}.each do |content| 33 - contents.select{ |i| i.is_a?(Grit::Tree)}.each do |content|
33 - = render :partial => "refs/tree_item", :locals => { :content => content } 34 + = render :partial => "refs/tree_item", :locals => { :content => content, :index => (index += 1) }
34 - contents.select{ |i| i.is_a?(Grit::Blob)}.each do |content| 35 - contents.select{ |i| i.is_a?(Grit::Blob)}.each do |content|
35 - = render :partial => "refs/tree_item", :locals => { :content => content } 36 + = render :partial => "refs/tree_item", :locals => { :content => content, :index => (index += 1) }
36 - contents.select{ |i| i.is_a?(Grit::Submodule)}.each do |content| 37 - contents.select{ |i| i.is_a?(Grit::Submodule)}.each do |content|
37 - = render :partial => "refs/submodule_item", :locals => { :content => content } 38 + = render :partial => "refs/submodule_item", :locals => { :content => content, :index => (index += 1) }
38 39
39 - if content = contents.select{ |c| c.is_a?(Grit::Blob) and c.name =~ /^readme/i }.first 40 - if content = contents.select{ |c| c.is_a?(Grit::Blob) and c.name =~ /^readme/i }.first
40 - #tree-readme-holder  
41 - %h3= content.name  
42 - .readme 41 + .file_holder#README
  42 + .file_title
  43 + %i.icon-file
  44 + = content.name
  45 + .file_content.wiki
43 - if content.name =~ /\.(md|markdown)$/i 46 - if content.name =~ /\.(md|markdown)$/i
44 = preserve do 47 = preserve do
45 = markdown(content.data) 48 = markdown(content.data)
46 - else 49 - else
47 = simple_format(content.data) 50 = simple_format(content.data)
48 51
49 -- if params[:path]  
50 - - history_path = tree_file_project_ref_path(@project, @ref, params[:path])  
51 -- else  
52 - - history_path = tree_project_ref_path(@project, @ref)  
53 :javascript 52 :javascript
54 $(function(){ 53 $(function(){
55 $('select#branch').selectmenu({style:'popup', width:200}); 54 $('select#branch').selectmenu({style:'popup', width:200});
56 $('select#tag').selectmenu({style:'popup', width:200}); 55 $('select#tag').selectmenu({style:'popup', width:200});
57 $('.project-refs-select').chosen(); 56 $('.project-refs-select').chosen();
58 57
59 - history.pushState({ path: this.path }, '', "#{history_path}") 58 + history.pushState({ path: this.path }, '', "#{@history_path}");
  59 +
  60 + });
  61 +
  62 + // Load last commit log for each file in tree
  63 + $(window).load(function(){
  64 + ajaxGet('#{@logs_path}');
60 }); 65 });
61 66
62 67
app/views/refs/_tree_commit.html.haml 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +- if tm
  2 + %strong= link_to "[#{tm.user_name}]", project_team_member_path(@project, tm)
  3 += link_to truncate(content_commit.safe_message, :length => tm ? 30 : 50), project_commit_path(@project, content_commit.id), :class => "tree-commit-link"
app/views/refs/_tree_file.html.haml
1 -.view_file  
2 - .view_file_header 1 +.file_holder
  2 + .file_title
3 %i.icon-file 3 %i.icon-file
4 %span.file_name 4 %span.file_name
5 = name 5 = name
@@ -10,26 +10,28 @@ @@ -10,26 +10,28 @@
10 = link_to "blame", blame_file_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small" 10 = link_to "blame", blame_file_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small"
11 - if file.text? 11 - if file.text?
12 - if name =~ /\.(md|markdown)$/i 12 - if name =~ /\.(md|markdown)$/i
13 - #tree-readme-holder  
14 - .readme  
15 - = preserve do  
16 - = markdown(file.data) 13 + .file_content.wiki
  14 + = preserve do
  15 + = markdown(file.data)
17 - else 16 - else
18 - .view_file_content 17 + .file_content.code
19 - unless file.empty? 18 - unless file.empty?
20 %div{:class => current_user.dark_scheme ? "black" : "white"} 19 %div{:class => current_user.dark_scheme ? "black" : "white"}
21 = preserve do 20 = preserve do
22 = raw file.colorize(options: { linenos: 'True'}) 21 = raw file.colorize(options: { linenos: 'True'})
23 - else 22 - else
24 %h4.nothing_here_message Empty file 23 %h4.nothing_here_message Empty file
  24 +
25 - elsif file.image? 25 - elsif file.image?
26 - .view_file_content_image 26 + .file_content.image_file
27 %img{ :src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} 27 %img{ :src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
  28 +
28 - else 29 - else
29 - %center  
30 - = link_to blob_project_ref_path(@project, @ref, :path => params[:path]) do  
31 - %div.padded  
32 - %br  
33 - = image_tag "download.png", :width => 64  
34 - %h3  
35 - Download (#{file.mb_size}) 30 + .file_content.blob_file
  31 + %center
  32 + = link_to blob_project_ref_path(@project, @ref, :path => params[:path]) do
  33 + %div.padded
  34 + %br
  35 + = image_tag "download.png", :width => 64
  36 + %h3
  37 + Download (#{file.mb_size})
app/views/refs/_tree_item.html.haml
1 -- file = params[:path] ? File.join(params[:path], content.name) : content.name  
2 -- content_commit = @project.commits(@commit.id, file, 1).last  
3 -- return unless content_commit  
4 -%tr{ :class => "tree-item", :url => tree_file_project_ref_path(@project, @ref, file) } 1 +- file = tree_full_path(content)
  2 +%tr{ :class => "tree-item #{tree_hex_class(content)}", :url => tree_file_project_ref_path(@project, @ref, file) }
5 %td.tree-item-file-name 3 %td.tree-item-file-name
6 - - if content.is_a?(Grit::Blob)  
7 - - if content.text?  
8 - = image_tag "file_txt.png"  
9 - - elsif content.image?  
10 - = image_tag "file_img.png"  
11 - - else  
12 - = image_tag "file_bin.png"  
13 - - else  
14 - = image_tag "file_dir.png" 4 + = tree_icon(content)
15 = link_to truncate(content.name, :length => 40), tree_file_project_ref_path(@project, @ref || @commit.id, file), :remote => :true 5 = link_to truncate(content.name, :length => 40), tree_file_project_ref_path(@project, @ref || @commit.id, file), :remote => :true
16 - %td.cgray  
17 - = time_ago_in_words(content_commit.committed_date)  
18 - ago  
19 - %td.commit  
20 - - tm = @project.team_member_by_name_or_email(content_commit.author_email, content_commit.author_name)  
21 - - if tm  
22 - %strong= link_to "[#{tm.user_name}]", project_team_member_path(@project, tm)  
23 - = link_to truncate(content_commit.safe_message, :length => tm ? 30 : 50), project_commit_path(@project, content_commit.id), :class => "tree-commit-link" 6 + %td.tree_time_ago.cgray
  7 + - if index == 1
  8 + %span.log_loading
  9 + Loading commit data..
  10 + = image_tag "ajax_loader_tree.gif", :width => 14
  11 + %td.tree_commit
app/views/refs/blame.html.haml
@@ -11,8 +11,8 @@ @@ -11,8 +11,8 @@
11 %li= link 11 %li= link
12 .clear 12 .clear
13 13
14 - .view_file.blame_file  
15 - .view_file_header 14 + .file_holder
  15 + .file_title
16 %i.icon-file 16 %i.icon-file
17 %span.file_name 17 %span.file_name
18 = @tree.name 18 = @tree.name
@@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
21 = link_to "raw", blob_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small", :target => "_blank" 21 = link_to "raw", blob_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small", :target => "_blank"
22 = link_to "history", project_commits_path(@project, :path => params[:path], :ref => @ref), :class => "btn very_small" 22 = link_to "history", project_commits_path(@project, :path => params[:path], :ref => @ref), :class => "btn very_small"
23 = link_to "source", tree_file_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small" 23 = link_to "source", tree_file_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small"
24 - .view_file_content 24 + .file_content.blame
25 %table 25 %table
26 - @blame.each do |commit, lines| 26 - @blame.each do |commit, lines|
27 - commit = Commit.new(commit) 27 - commit = Commit.new(commit)
@@ -29,7 +29,7 @@ @@ -29,7 +29,7 @@
29 %td.author 29 %td.author
30 = image_tag gravatar_icon(commit.author_email, 16) 30 = image_tag gravatar_icon(commit.author_email, 16)
31 = commit.author_name 31 = commit.author_name
32 - %td.commit 32 + %td.blame_commit
33 &nbsp; 33 &nbsp;
34 = link_to project_commit_path(@project, :id => commit.id) do 34 = link_to project_commit_path(@project, :id => commit.id) do
35 %code= commit.id.to_s[0..10] 35 %code= commit.id.to_s[0..10]
@@ -37,8 +37,7 @@ @@ -37,8 +37,7 @@
37 %td.lines 37 %td.lines
38 = preserve do 38 = preserve do
39 %pre 39 %pre
40 - - lines.each do |line|  
41 - = line 40 + = Gitlab::Encode.utf8 lines.join("\n")
42 41
43 :javascript 42 :javascript
44 $(function(){ 43 $(function(){
app/views/refs/logs_tree.js.haml 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +- @logs.each do |content_data|
  2 + - file_name = content_data[:file_name]
  3 + - content_commit = content_data[:commit]
  4 + - tm = @project.team_member_by_name_or_email(content_commit.author_email, content_commit.author_name)
  5 +
  6 + :plain
  7 + var row = $("table.table_#{@hex_path} tr.file_#{hexdigest(file_name)}");
  8 + row.find("td.tree_time_ago").html('#{escape_javascript(time_ago_in_words(content_commit.committed_date))} ago');
  9 + row.find("td.tree_commit").html('#{escape_javascript(render("tree_commit", :tm => tm, :content_commit => content_commit))}');
app/views/refs/tree.js.haml
1 :plain 1 :plain
  2 + // Load Files list
2 $("#tree-holder").html("#{escape_javascript(render(:partial => "tree", :locals => {:repo => @repo, :commit => @commit, :tree => @tree}))}"); 3 $("#tree-holder").html("#{escape_javascript(render(:partial => "tree", :locals => {:repo => @repo, :commit => @commit, :tree => @tree}))}");
3 $("#tree-content-holder").show("slide", { direction: "right" }, 150); 4 $("#tree-content-holder").show("slide", { direction: "right" }, 150);
4 $('.project-refs-form #path').val("#{params[:path]}"); 5 $('.project-refs-form #path').val("#{params[:path]}");
  6 +
  7 + // Load last commit log for each file in tree
  8 + $('#tree-slider').waitForImages(function() {
  9 + ajaxGet('#{@logs_path}');
  10 + });
app/views/snippets/show.html.haml
@@ -7,16 +7,14 @@ @@ -7,16 +7,14 @@
7 = link_to "Edit", edit_project_snippet_path(@project, @snippet), :class => "btn small right" 7 = link_to "Edit", edit_project_snippet_path(@project, @snippet), :class => "btn small right"
8 8
9 %br 9 %br
10 -#tree-holder  
11 - #tree-content-holder  
12 - .view_file  
13 - .view_file_header  
14 - %i.icon-file  
15 - %strong= @snippet.file_name  
16 - %span.options  
17 - = link_to "raw", raw_project_snippet_path(@project, @snippet), :class => "btn very_small", :target => "_blank"  
18 - .view_file_content  
19 - %div{:class => current_user.dark_scheme ? "black" : ""}  
20 - = raw @snippet.colorize(options: { linenos: 'True'}) 10 +.file_holder
  11 + .file_title
  12 + %i.icon-file
  13 + %strong= @snippet.file_name
  14 + %span.options
  15 + = link_to "raw", raw_project_snippet_path(@project, @snippet), :class => "btn very_small", :target => "_blank"
  16 + .file_content.code
  17 + %div{:class => current_user.dark_scheme ? "black" : ""}
  18 + = raw @snippet.colorize(options: { linenos: 'True'})
21 19
22 = render "notes/notes", :tid => @snippet.id, :tt => "snippet" 20 = render "notes/notes", :tid => @snippet.id, :tt => "snippet"
app/workers/system_hook_worker.rb 0 → 100644
@@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
  1 +class SystemHookWorker
  2 + @queue = :system_hook
  3 +
  4 + def self.perform(hook_id, data)
  5 + SystemHook.find(hook_id).execute data
  6 + end
  7 +end
config/application.rb
@@ -23,7 +23,7 @@ module Gitlab @@ -23,7 +23,7 @@ module Gitlab
23 # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 23 # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
24 24
25 # Activate observers that should always be running. 25 # Activate observers that should always be running.
26 - config.active_record.observers = :mailer_observer, :activity_observer, :project_observer, :key_observer, :issue_observer, :user_observer 26 + config.active_record.observers = :mailer_observer, :activity_observer, :project_observer, :key_observer, :issue_observer, :user_observer, :system_hook_observer
27 27
28 # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 28 # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
29 # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 29 # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
config/gitlab.yml.example
@@ -21,6 +21,8 @@ email: @@ -21,6 +21,8 @@ email:
21 # Like default project limit for user etc 21 # Like default project limit for user etc
22 app: 22 app:
23 default_projects_limit: 10 23 default_projects_limit: 10
  24 + # backup_path: "/vol/backups" # default: Rails.root + backups/
  25 + # backup_keep_time: 604800 # default: 0 (forever) (in seconds)
24 26
25 27
26 # 28 #
config/initializers/1_settings.rb
@@ -95,11 +95,21 @@ class Settings &lt; Settingslogic @@ -95,11 +95,21 @@ class Settings &lt; Settingslogic
95 end 95 end
96 96
97 def gitolite_admin_uri 97 def gitolite_admin_uri
98 - git['admin_uri'] || 'git@localhost:gitolite-admin' 98 + git_host['admin_uri'] || 'git@localhost:gitolite-admin'
99 end 99 end
100 100
101 def default_projects_limit 101 def default_projects_limit
102 app['default_projects_limit'] || 10 102 app['default_projects_limit'] || 10
103 end 103 end
  104 +
  105 + def backup_path
  106 + t = app['backup_path'] || "backups/"
  107 + t = /^\//.match(t) ? t : File.join(Rails.root + t)
  108 + t
  109 + end
  110 +
  111 + def backup_keep_time
  112 + app['backup_keep_time'] || 0
  113 + end
104 end 114 end
105 end 115 end
config/initializers/devise.rb
@@ -93,10 +93,6 @@ Devise.setup do |config| @@ -93,10 +93,6 @@ Devise.setup do |config|
93 # If true, extends the user's remember period when remembered via cookie. 93 # If true, extends the user's remember period when remembered via cookie.
94 # config.extend_remember_period = false 94 # config.extend_remember_period = false
95 95
96 - # If true, uses the password salt as remember token. This should be turned  
97 - # to false if you are not using database authenticatable.  
98 - config.use_salt_as_remember_token = true  
99 -  
100 # Options to be passed to the created cookie. For instance, you can set 96 # Options to be passed to the created cookie. For instance, you can set
101 # :secure => true in order to force SSL only cookies. 97 # :secure => true in order to force SSL only cookies.
102 # config.cookie_options = {} 98 # config.cookie_options = {}
@@ -119,7 +115,7 @@ Devise.setup do |config| @@ -119,7 +115,7 @@ Devise.setup do |config|
119 # Defines which strategy will be used to lock an account. 115 # Defines which strategy will be used to lock an account.
120 # :failed_attempts = Locks an account after a number of failed attempts to sign in. 116 # :failed_attempts = Locks an account after a number of failed attempts to sign in.
121 # :none = No lock strategy. You should handle locking by yourself. 117 # :none = No lock strategy. You should handle locking by yourself.
122 - # config.lock_strategy = :failed_attempts 118 + config.lock_strategy = :failed_attempts
123 119
124 # Defines which key will be used when locking and unlocking an account 120 # Defines which key will be used when locking and unlocking an account
125 # config.unlock_keys = [ :email ] 121 # config.unlock_keys = [ :email ]
@@ -129,14 +125,14 @@ Devise.setup do |config| @@ -129,14 +125,14 @@ Devise.setup do |config|
129 # :time = Re-enables login after a certain amount of time (see :unlock_in below) 125 # :time = Re-enables login after a certain amount of time (see :unlock_in below)
130 # :both = Enables both strategies 126 # :both = Enables both strategies
131 # :none = No unlock strategy. You should handle unlocking by yourself. 127 # :none = No unlock strategy. You should handle unlocking by yourself.
132 - # config.unlock_strategy = :both 128 + config.unlock_strategy = :time
133 129
134 # Number of authentication tries before locking an account if lock_strategy 130 # Number of authentication tries before locking an account if lock_strategy
135 # is failed attempts. 131 # is failed attempts.
136 - # config.maximum_attempts = 20 132 + config.maximum_attempts = 10
137 133
138 # Time interval to unlock the account if :time is enabled as unlock_strategy. 134 # Time interval to unlock the account if :time is enabled as unlock_strategy.
139 - # config.unlock_in = 1.hour 135 + config.unlock_in = 10.minutes
140 136
141 # ==> Configuration for :recoverable 137 # ==> Configuration for :recoverable
142 # 138 #
@@ -160,9 +156,9 @@ Devise.setup do |config| @@ -160,9 +156,9 @@ Devise.setup do |config|
160 # Defines name of the authentication token params key 156 # Defines name of the authentication token params key
161 config.token_authentication_key = :private_token 157 config.token_authentication_key = :private_token
162 158
163 - # If true, authentication through token does not store user in session and needs 159 + # Authentication through token does not store user in session and needs
164 # to be supplied on each request. Useful if you are using the token as API token. 160 # to be supplied on each request. Useful if you are using the token as API token.
165 - config.stateless_token = true 161 + config.skip_session_storage << :token_auth
166 162
167 # ==> Scopes configuration 163 # ==> Scopes configuration
168 # Turn scoped views on. Before rendering "sessions/new", it will first check for 164 # Turn scoped views on. Before rendering "sessions/new", it will first check for
config/locales/devise.en.yml
@@ -35,13 +35,11 @@ en: @@ -35,13 +35,11 @@ en:
35 confirmed: 'Your account was successfully confirmed. You are now signed in.' 35 confirmed: 'Your account was successfully confirmed. You are now signed in.'
36 registrations: 36 registrations:
37 signed_up: 'Welcome! You have signed up successfully.' 37 signed_up: 'Welcome! You have signed up successfully.'
38 - inactive_signed_up: 'You have signed up successfully. However, we could not sign you in because your account is %{reason}.'  
39 updated: 'You updated your account successfully.' 38 updated: 'You updated your account successfully.'
40 destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.' 39 destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.'
41 - reasons:  
42 - inactive: 'inactive'  
43 - unconfirmed: 'unconfirmed'  
44 - locked: 'locked' 40 + 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.'
  41 + signed_up_but_inactive: 'You have signed up successfully. However, we could not sign you in because your account is not yet activated.'
  42 + signed_up_but_locked: 'You have signed up successfully. However, we could not sign you in because your account is locked.'
45 unlocks: 43 unlocks:
46 send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.' 44 send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.'
47 unlocked: 'Your account was successfully unlocked. You are now signed in.' 45 unlocked: 'Your account was successfully unlocked. You are now signed in.'
config/routes.rb
@@ -26,7 +26,9 @@ Gitlab::Application.routes.draw do @@ -26,7 +26,9 @@ Gitlab::Application.routes.draw do
26 get 'help' => 'help#index' 26 get 'help' => 'help#index'
27 get 'help/permissions' => 'help#permissions' 27 get 'help/permissions' => 'help#permissions'
28 get 'help/workflow' => 'help#workflow' 28 get 'help/workflow' => 'help#workflow'
  29 + get 'help/api' => 'help#api'
29 get 'help/web_hooks' => 'help#web_hooks' 30 get 'help/web_hooks' => 'help#web_hooks'
  31 + get 'help/system_hooks' => 'help#system_hooks'
30 32
31 # 33 #
32 # Admin Area 34 # Admin Area
@@ -46,11 +48,13 @@ Gitlab::Application.routes.draw do @@ -46,11 +48,13 @@ Gitlab::Application.routes.draw do
46 end 48 end
47 end 49 end
48 resources :team_members, :only => [:edit, :update, :destroy] 50 resources :team_members, :only => [:edit, :update, :destroy]
49 - get 'emails', :to => 'mailer#preview'  
50 get 'mailer/preview_note' 51 get 'mailer/preview_note'
51 get 'mailer/preview_user_new' 52 get 'mailer/preview_user_new'
52 get 'mailer/preview_issue_new' 53 get 'mailer/preview_issue_new'
53 54
  55 + resources :hooks, :only => [:index, :create, :destroy] do
  56 + get :test
  57 + end
54 resource :logs 58 resource :logs
55 resource :resque, :controller => 'resque' 59 resource :resque, :controller => 'resque'
56 root :to => "dashboard#index" 60 root :to => "dashboard#index"
@@ -116,6 +120,8 @@ Gitlab::Application.routes.draw do @@ -116,6 +120,8 @@ Gitlab::Application.routes.draw do
116 120
117 member do 121 member do
118 get "tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ } 122 get "tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ }
  123 + get "logs_tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ }
  124 +
119 get "blob", 125 get "blob",
120 :constraints => { 126 :constraints => {
121 :id => /[a-zA-Z.0-9\/_\-]+/, 127 :id => /[a-zA-Z.0-9\/_\-]+/,
@@ -131,6 +137,14 @@ Gitlab::Application.routes.draw do @@ -131,6 +137,14 @@ Gitlab::Application.routes.draw do
131 :path => /.*/ 137 :path => /.*/
132 } 138 }
133 139
  140 + # tree viewer
  141 + get "logs_tree/:path" => "refs#logs_tree",
  142 + :as => :logs_file,
  143 + :constraints => {
  144 + :id => /[a-zA-Z.0-9\/_\-]+/,
  145 + :path => /.*/
  146 + }
  147 +
134 # blame 148 # blame
135 get "blame/:path" => "refs#blame", 149 get "blame/:path" => "refs#blame",
136 :as => :blame_file, 150 :as => :blame_file,
db/migrate/20110913200833_devise_create_users.rb
1 class DeviseCreateUsers < ActiveRecord::Migration 1 class DeviseCreateUsers < ActiveRecord::Migration
2 def self.up 2 def self.up
3 create_table(:users) do |t| 3 create_table(:users) do |t|
4 - t.database_authenticatable :null => false  
5 - t.recoverable  
6 - t.rememberable  
7 - t.trackable  
8 -  
9 - # t.encryptable  
10 - # t.confirmable  
11 - # t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both  
12 - # t.token_authenticatable 4 + ## Database authenticatable
  5 + t.string :email, :null => false, :default => ""
  6 + t.string :encrypted_password, :null => false, :default => ""
  7 +
  8 + ## Recoverable
  9 + t.string :reset_password_token
  10 + t.datetime :reset_password_sent_at
  11 +
  12 + ## Rememberable
  13 + t.datetime :remember_created_at
  14 +
  15 + ## Trackable
  16 + t.integer :sign_in_count, :default => 0
  17 + t.datetime :current_sign_in_at
  18 + t.datetime :last_sign_in_at
  19 + t.string :current_sign_in_ip
  20 + t.string :last_sign_in_ip
  21 +
  22 + ## Encryptable
  23 + # t.string :password_salt
  24 +
  25 + ## Confirmable
  26 + # t.string :confirmation_token
  27 + # t.datetime :confirmed_at
  28 + # t.datetime :confirmation_sent_at
  29 + # t.string :unconfirmed_email # Only if using reconfirmable
  30 +
  31 + ## Lockable
  32 + # t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts
  33 + # t.string :unlock_token # Only if unlock strategy is :email or :both
  34 + # t.datetime :locked_at
  35 +
  36 + # Token authenticatable
  37 + # t.string :authentication_token
  38 +
  39 + ## Invitable
  40 + # t.string :invitation_token
13 41
14 t.timestamps 42 t.timestamps
15 end 43 end
db/migrate/20120706065612_add_lockable_to_users.rb 0 → 100644
@@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
  1 +class AddLockableToUsers < ActiveRecord::Migration
  2 + def change
  3 + add_column :users, :failed_attempts, :integer, :default => 0
  4 + add_column :users, :locked_at, :datetime
  5 + end
  6 +end
db/migrate/20120712080407_add_type_to_web_hook.rb 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +class AddTypeToWebHook < ActiveRecord::Migration
  2 + def change
  3 + add_column :web_hooks, :type, :string, :default => "ProjectHook"
  4 + end
  5 +end
@@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
11 # 11 #
12 # It's strongly recommended to check this file into your version control system. 12 # It's strongly recommended to check this file into your version control system.
13 13
14 -ActiveRecord::Schema.define(:version => 20120627145613) do 14 +ActiveRecord::Schema.define(:version => 20120712080407) do
15 15
16 create_table "events", :force => true do |t| 16 create_table "events", :force => true do |t|
17 t.string "target_type" 17 t.string "target_type"
@@ -169,6 +169,8 @@ ActiveRecord::Schema.define(:version =&gt; 20120627145613) do @@ -169,6 +169,8 @@ ActiveRecord::Schema.define(:version =&gt; 20120627145613) do
169 t.integer "theme_id", :default => 1, :null => false 169 t.integer "theme_id", :default => 1, :null => false
170 t.string "bio" 170 t.string "bio"
171 t.boolean "blocked", :default => false, :null => false 171 t.boolean "blocked", :default => false, :null => false
  172 + t.integer "failed_attempts", :default => 0
  173 + t.datetime "locked_at"
172 end 174 end
173 175
174 add_index "users", ["email"], :name => "index_users_on_email", :unique => true 176 add_index "users", ["email"], :name => "index_users_on_email", :unique => true
@@ -185,8 +187,9 @@ ActiveRecord::Schema.define(:version =&gt; 20120627145613) do @@ -185,8 +187,9 @@ ActiveRecord::Schema.define(:version =&gt; 20120627145613) do
185 create_table "web_hooks", :force => true do |t| 187 create_table "web_hooks", :force => true do |t|
186 t.string "url" 188 t.string "url"
187 t.integer "project_id" 189 t.integer "project_id"
188 - t.datetime "created_at", :null => false  
189 - t.datetime "updated_at", :null => false 190 + t.datetime "created_at", :null => false
  191 + t.datetime "updated_at", :null => false
  192 + t.string "type", :default => "ProjectHook"
190 end 193 end
191 194
192 create_table "wikis", :force => true do |t| 195 create_table "wikis", :force => true do |t|
doc/installation.md
@@ -60,7 +60,7 @@ Also read the [Read this before you submit an issue](https://github.com/gitlabhq @@ -60,7 +60,7 @@ Also read the [Read this before you submit an issue](https://github.com/gitlabhq
60 sudo apt-get update 60 sudo apt-get update
61 sudo apt-get upgrade 61 sudo apt-get upgrade
62 62
63 - 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 63 + 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
64 64
65 # If you want to use MySQL: 65 # If you want to use MySQL:
66 sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev 66 sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
@@ -107,10 +107,10 @@ Get gitolite source code: @@ -107,10 +107,10 @@ Get gitolite source code:
107 107
108 Setup: 108 Setup:
109 109
110 - sudo -u git sh -c 'echo -e "PATH=\$PATH:/home/git/bin\nexport PATH" > /home/git/.profile' 110 + sudo -u git sh -c 'echo -e "PATH=\$PATH:/home/git/bin\nexport PATH" >> /home/git/.profile'
111 sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; /home/git/gitolite/src/gl-system-install" 111 sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; /home/git/gitolite/src/gl-system-install"
112 sudo cp /home/gitlab/.ssh/id_rsa.pub /home/git/gitlab.pub 112 sudo cp /home/gitlab/.ssh/id_rsa.pub /home/git/gitlab.pub
113 - sudo chmod 777 /home/git/gitlab.pub 113 + sudo chmod 0444 /home/git/gitlab.pub
114 114
115 sudo -u git -H sed -i 's/0077/0007/g' /home/git/share/gitolite/conf/example.gitolite.rc 115 sudo -u git -H sed -i 's/0077/0007/g' /home/git/share/gitolite/conf/example.gitolite.rc
116 sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; gl-setup -q /home/git/gitlab.pub" 116 sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; gl-setup -q /home/git/gitlab.pub"
@@ -139,6 +139,8 @@ Permissions: @@ -139,6 +139,8 @@ Permissions:
139 cd /home/gitlab 139 cd /home/gitlab
140 sudo -H -u gitlab git clone -b stable git://github.com/gitlabhq/gitlabhq.git gitlab 140 sudo -H -u gitlab git clone -b stable git://github.com/gitlabhq/gitlabhq.git gitlab
141 cd gitlab 141 cd gitlab
  142 +
  143 + sudo -u gitlab mkdir tmp
142 144
143 # Rename config files 145 # Rename config files
144 sudo -u gitlab cp config/gitlab.yml.example config/gitlab.yml 146 sudo -u gitlab cp config/gitlab.yml.example config/gitlab.yml
@@ -216,15 +218,15 @@ Application can be started with next command: @@ -216,15 +218,15 @@ Application can be started with next command:
216 sudo -u gitlab cp config/unicorn.rb.orig config/unicorn.rb 218 sudo -u gitlab cp config/unicorn.rb.orig config/unicorn.rb
217 sudo -u gitlab bundle exec unicorn_rails -c config/unicorn.rb -E production -D 219 sudo -u gitlab bundle exec unicorn_rails -c config/unicorn.rb -E production -D
218 220
219 -Edit /etc/nginx/nginx.conf. Add in **http** section: 221 +Edit /etc/nginx/nginx.conf. In the *http* section add:
220 222
221 upstream gitlab { 223 upstream gitlab {
222 server unix:/home/gitlab/gitlab/tmp/sockets/gitlab.socket; 224 server unix:/home/gitlab/gitlab/tmp/sockets/gitlab.socket;
223 } 225 }
224 226
225 server { 227 server {
226 - listen YOUR_SERVER_IP:80;  
227 - server_name gitlab.YOUR_DOMAIN.com; 228 + listen YOUR_SERVER_IP:80; # e.g., listen 192.168.1.1:80;
  229 + server_name YOUR_SERVER_FQDN; # e.g., server_name source.example.com;
228 root /home/gitlab/gitlab/public; 230 root /home/gitlab/gitlab/public;
229 231
230 # individual nginx logs for this gitlab vhost 232 # individual nginx logs for this gitlab vhost
@@ -232,26 +234,26 @@ Edit /etc/nginx/nginx.conf. Add in **http** section: @@ -232,26 +234,26 @@ Edit /etc/nginx/nginx.conf. Add in **http** section:
232 error_log /var/log/nginx/gitlab_error.log; 234 error_log /var/log/nginx/gitlab_error.log;
233 235
234 location / { 236 location / {
235 - # serve static files from defined root folder;.  
236 - # @gitlab is a named location for the upstream fallback, see below  
237 - try_files $uri $uri/index.html $uri.html @gitlab; 237 + # serve static files from defined root folder;.
  238 + # @gitlab is a named location for the upstream fallback, see below
  239 + try_files $uri $uri/index.html $uri.html @gitlab;
238 } 240 }
239 241
240 # if a file, which is not found in the root folder is requested, 242 # if a file, which is not found in the root folder is requested,
241 # then the proxy pass the request to the upsteam (gitlab unicorn) 243 # then the proxy pass the request to the upsteam (gitlab unicorn)
242 location @gitlab { 244 location @gitlab {
243 proxy_redirect off; 245 proxy_redirect off;
  246 +
244 # you need to change this to "https", if you set "ssl" directive to "on" 247 # you need to change this to "https", if you set "ssl" directive to "on"
245 proxy_set_header X-FORWARDED_PROTO http; 248 proxy_set_header X-FORWARDED_PROTO http;
246 - proxy_set_header Host gitlab.YOUR_SUBDOMAIN.com:80; 249 + proxy_set_header Host $http_host;
247 proxy_set_header X-Real-IP $remote_addr; 250 proxy_set_header X-Real-IP $remote_addr;
248 251
249 proxy_pass http://gitlab; 252 proxy_pass http://gitlab;
250 } 253 }
251 -  
252 } 254 }
253 255
254 -gitlab.YOUR_DOMAIN.com - change to your domain. 256 +Change **YOUR_SERVER_IP** and **YOUR_SERVER_FQDN** to the IP address and fully-qualified domain name of the host serving GitLab.
255 257
256 Restart nginx: 258 Restart nginx:
257 259
lib/gitlab/logger.rb
1 module Gitlab 1 module Gitlab
2 - class Logger 2 + class Logger < ::Logger
3 def self.error(message) 3 def self.error(message)
4 - @@logger ||= ::Logger.new(File.join(Rails.root, "log/githost.log"))  
5 - message = Time.now.to_s(:long) + " -> " + message  
6 - @@logger.error(message) 4 + build.error(message)
  5 + end
  6 +
  7 + def self.info(message)
  8 + build.info(message)
7 end 9 end
8 10
9 def self.read_latest 11 def self.read_latest
10 path = Rails.root.join("log/githost.log") 12 path = Rails.root.join("log/githost.log")
11 - logs = `tail -n 50 #{path}`.split("\n") 13 + logs = File.read(path).split("\n")
12 end 14 end
  15 +
  16 + def self.build
  17 + new(File.join(Rails.root, "log/githost.log"))
  18 + end
  19 +
  20 + def format_message(severity, timestamp, progname, msg)
  21 + "#{timestamp.to_s(:long)} -> #{severity} -> #{msg}\n"
  22 + end
13 end 23 end
14 end 24 end
lib/tasks/gitlab/backup.rake 0 → 100644
@@ -0,0 +1,190 @@ @@ -0,0 +1,190 @@
  1 +require 'active_record/fixtures'
  2 +
  3 +namespace :gitlab do
  4 + namespace :app do
  5 +
  6 + # Create backup of gitlab system
  7 + desc "GITLAB | Create a backup of the gitlab system"
  8 + task :backup_create => :environment do
  9 +
  10 + Rake::Task["gitlab:app:db_dump"].invoke
  11 + Rake::Task["gitlab:app:repo_dump"].invoke
  12 +
  13 + Dir.chdir(Gitlab.config.backup_path)
  14 +
  15 + # saving additional informations
  16 + s = Hash.new
  17 + s["db_version"] = "#{ActiveRecord::Migrator.current_version}"
  18 + s["backup_created_at"] = "#{Time.now}"
  19 + s["gitlab_version"] = %x{git rev-parse HEAD}.gsub(/\n/,"")
  20 + s["tar_version"] = %x{tar --version | head -1}.gsub(/\n/,"")
  21 +
  22 + File.open("#{Gitlab.config.backup_path}/backup_information.yml", "w+") do |file|
  23 + file << s.to_yaml.gsub(/^---\n/,'')
  24 + end
  25 +
  26 + # create archive
  27 + print "Creating backup archive: #{Time.now.to_i}_gitlab_backup.tar "
  28 + if Kernel.system("tar -cf #{Time.now.to_i}_gitlab_backup.tar repositories/ db/ backup_information.yml")
  29 + puts "[DONE]".green
  30 + else
  31 + puts "[FAILED]".red
  32 + end
  33 +
  34 + # cleanup: remove tmp files
  35 + print "Deletion of tmp directories..."
  36 + if Kernel.system("rm -rf repositories/ db/ backup_information.yml")
  37 + puts "[DONE]".green
  38 + else
  39 + puts "[FAILED]".red
  40 + end
  41 +
  42 + # delete backups
  43 + print "Deleting old backups... "
  44 + if Gitlab.config.backup_keep_time > 0
  45 + file_list = Dir.glob("*_gitlab_backup.tar").map { |f| f.split(/_/).first.to_i }
  46 + file_list.sort.each do |timestamp|
  47 + if Time.at(timestamp) < (Time.now - Gitlab.config.backup_keep_time)
  48 + %x{rm #{timestamp}_gitlab_backup.tar}
  49 + end
  50 + end
  51 + puts "[DONE]".green
  52 + else
  53 + puts "[SKIPPING]".yellow
  54 + end
  55 +
  56 + end
  57 +
  58 +
  59 + # Restore backup of gitlab system
  60 + desc "GITLAB | Restore a previously created backup"
  61 + task :backup_restore => :environment do
  62 +
  63 + Dir.chdir(Gitlab.config.backup_path)
  64 +
  65 + # check for existing backups in the backup dir
  66 + file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i }
  67 + puts "no backup found" if file_list.count == 0
  68 + if file_list.count > 1 && ENV["BACKUP"].nil?
  69 + puts "Found more than one backup, please specify which one you want to restore:"
  70 + puts "rake gitlab:app:backup_restore BACKUP=timestamp_of_backup"
  71 + exit 1;
  72 + end
  73 +
  74 + tar_file = ENV["BACKUP"].nil? ? File.join(file_list.first.to_s + "_gitlab_backup.tar") : File.join(ENV["BACKUP"] + "_gitlab_backup.tar")
  75 +
  76 + unless File.exists?(tar_file)
  77 + puts "The specified backup doesn't exist!"
  78 + exit 1;
  79 + end
  80 +
  81 + print "Unpacking backup... "
  82 + unless Kernel.system("tar -xf #{tar_file}")
  83 + puts "[FAILED]".red
  84 + exit 1
  85 + else
  86 + puts "[DONE]".green
  87 + end
  88 +
  89 + settings = YAML.load_file("backup_information.yml")
  90 + ENV["VERSION"] = "#{settings["db_version"]}" if settings["db_version"].to_i > 0
  91 +
  92 + # restoring mismatching backups can lead to unexpected problems
  93 + if settings["gitlab_version"] != %x{git rev-parse HEAD}.gsub(/\n/,"")
  94 + puts "gitlab_version mismatch:".red
  95 + puts " Your current HEAD differs from the HEAD in the backup!".red
  96 + puts " Please switch to the following revision and try again:".red
  97 + puts " revision: #{settings["gitlab_version"]}".red
  98 + exit 1
  99 + end
  100 +
  101 + Rake::Task["gitlab:app:db_restore"].invoke
  102 + Rake::Task["gitlab:app:repo_restore"].invoke
  103 +
  104 + # cleanup: remove tmp files
  105 + print "Deletion of tmp directories..."
  106 + if Kernel.system("rm -rf repositories/ db/ backup_information.yml")
  107 + puts "[DONE]".green
  108 + else
  109 + puts "[FAILED]".red
  110 + end
  111 +
  112 + end
  113 +
  114 +
  115 + ################################################################################
  116 + ################################# invoked tasks ################################
  117 +
  118 + ################################# REPOSITORIES #################################
  119 +
  120 + task :repo_dump => :environment do
  121 + backup_path_repo = File.join(Gitlab.config.backup_path, "repositories")
  122 + FileUtils.mkdir_p(backup_path_repo) until Dir.exists?(backup_path_repo)
  123 + puts "Dumping repositories:"
  124 + project = Project.all.map { |n| [n.name,n.path_to_repo] }
  125 + project << ["gitolite-admin.git", File.join(File.dirname(project.first.second), "gitolite-admin.git")]
  126 + project.each do |project|
  127 + print "- Dumping repository #{project.first}... "
  128 + if Kernel.system("cd #{project.second} > /dev/null 2>&1 && git bundle create #{backup_path_repo}/#{project.first}.bundle --all > /dev/null 2>&1")
  129 + puts "[DONE]".green
  130 + else
  131 + puts "[FAILED]".red
  132 + end
  133 + end
  134 + end
  135 +
  136 + task :repo_restore => :environment do
  137 + backup_path_repo = File.join(Gitlab.config.backup_path, "repositories")
  138 + puts "Restoring repositories:"
  139 + project = Project.all.map { |n| [n.name,n.path_to_repo] }
  140 + project << ["gitolite-admin.git", File.join(File.dirname(project.first.second), "gitolite-admin.git")]
  141 + project.each do |project|
  142 + print "- Restoring repository #{project.first}... "
  143 + FileUtils.rm_rf(project.second) if File.dirname(project.second) # delet old stuff
  144 + 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")
  145 + puts "[DONE]".green
  146 + else
  147 + puts "[FAILED]".red
  148 + end
  149 + end
  150 + end
  151 +
  152 + ###################################### DB ######################################
  153 +
  154 + task :db_dump => :environment do
  155 + backup_path_db = File.join(Gitlab.config.backup_path, "db")
  156 + FileUtils.mkdir_p(backup_path_db) until Dir.exists?(backup_path_db)
  157 + puts "Dumping database tables:"
  158 + ActiveRecord::Base.connection.tables.each do |tbl|
  159 + print "- Dumping table #{tbl}... "
  160 + count = 1
  161 + File.open(File.join(backup_path_db, tbl + ".yml"), "w+") do |file|
  162 + ActiveRecord::Base.connection.select_all("SELECT * FROM `#{tbl}`").each do |line|
  163 + line.delete_if{|k,v| v.blank?}
  164 + output = {tbl + '_' + count.to_s => line}
  165 + file << output.to_yaml.gsub(/^---\n/,'') + "\n"
  166 + count += 1
  167 + end
  168 + puts "[DONE]".green
  169 + end
  170 + end
  171 + end
  172 +
  173 + task :db_restore=> :environment do
  174 + backup_path_db = File.join(Gitlab.config.backup_path, "db")
  175 + puts "Restoring database tables:"
  176 + Rake::Task["db:reset"].invoke
  177 + Dir.glob(File.join(backup_path_db, "*.yml") ).each do |dir|
  178 + fixture_file = File.basename(dir, ".*" )
  179 + print "- Loading fixture #{fixture_file}..."
  180 + if File.size(dir) > 0
  181 + ActiveRecord::Fixtures.create_fixtures(backup_path_db, fixture_file)
  182 + puts "[DONE]".green
  183 + else
  184 + puts "[SKIPPING]".yellow
  185 + end
  186 + end
  187 + end
  188 +
  189 + end # namespace end: app
  190 +end # namespace end: gitlab
1 mkdir -p tmp/pids 1 mkdir -p tmp/pids
2 -bundle exec rake environment resque:work QUEUE=post_receive,mailer RAILS_ENV=production PIDFILE=tmp/pids/resque_worker.pid BACKGROUND=yes 2 +bundle exec rake environment resque:work QUEUE=post_receive,mailer,system_hook RAILS_ENV=production PIDFILE=tmp/pids/resque_worker.pid BACKGROUND=yes
1 -bundle exec rake environment resque:work QUEUE=* VVERBOSE=1 1 +bundle exec rake environment resque:work QUEUE=post_receive,mailer,system_hook VVERBOSE=1
spec/api/projects_spec.rb
@@ -78,7 +78,7 @@ describe Gitlab::API do @@ -78,7 +78,7 @@ describe Gitlab::API do
78 end 78 end
79 79
80 describe "DELETE /projects/:id/snippets/:snippet_id" do 80 describe "DELETE /projects/:id/snippets/:snippet_id" do
81 - it "should create a new project snippet" do 81 + it "should delete existing project snippet" do
82 expect { 82 expect {
83 delete "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}?private_token=#{user.private_token}" 83 delete "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}?private_token=#{user.private_token}"
84 }.should change { Snippet.count }.by(-1) 84 }.should change { Snippet.count }.by(-1)
spec/factories.rb
@@ -7,6 +7,12 @@ Factory.add(:project, Project) do |obj| @@ -7,6 +7,12 @@ Factory.add(:project, Project) do |obj|
7 obj.code = 'LGT' 7 obj.code = 'LGT'
8 end 8 end
9 9
  10 +Factory.add(:project_without_owner, Project) do |obj|
  11 + obj.name = Faker::Internet.user_name
  12 + obj.path = 'gitlabhq'
  13 + obj.code = 'LGT'
  14 +end
  15 +
10 Factory.add(:public_project, Project) do |obj| 16 Factory.add(:public_project, Project) do |obj|
11 obj.name = Faker::Internet.user_name 17 obj.name = Faker::Internet.user_name
12 obj.path = 'gitlabhq' 18 obj.path = 'gitlabhq'
@@ -60,7 +66,11 @@ Factory.add(:key, Key) do |obj| @@ -60,7 +66,11 @@ Factory.add(:key, Key) do |obj|
60 obj.key = File.read(File.join(Rails.root, "db", "pkey.example")) 66 obj.key = File.read(File.join(Rails.root, "db", "pkey.example"))
61 end 67 end
62 68
63 -Factory.add(:web_hook, WebHook) do |obj| 69 +Factory.add(:project_hook, ProjectHook) do |obj|
  70 + obj.url = Faker::Internet.uri("http")
  71 +end
  72 +
  73 +Factory.add(:system_hook, SystemHook) do |obj|
64 obj.url = Faker::Internet.uri("http") 74 obj.url = Faker::Internet.uri("http")
65 end 75 end
66 76
spec/models/merge_request_spec.rb
@@ -13,7 +13,6 @@ describe MergeRequest do @@ -13,7 +13,6 @@ describe MergeRequest do
13 it { should validate_presence_of(:title) } 13 it { should validate_presence_of(:title) }
14 it { should validate_presence_of(:author_id) } 14 it { should validate_presence_of(:author_id) }
15 it { should validate_presence_of(:project_id) } 15 it { should validate_presence_of(:project_id) }
16 - it { should validate_presence_of(:assignee_id) }  
17 end 16 end
18 17
19 describe "Scope" do 18 describe "Scope" do
spec/models/project_hooks_spec.rb
@@ -21,44 +21,44 @@ describe Project, &quot;Hooks&quot; do @@ -21,44 +21,44 @@ describe Project, &quot;Hooks&quot; do
21 end 21 end
22 end 22 end
23 23
24 - describe "Web hooks" do 24 + describe "Project hooks" do
25 context "with no web hooks" do 25 context "with no web hooks" do
26 it "raises no errors" do 26 it "raises no errors" do
27 lambda { 27 lambda {
28 - project.execute_web_hooks('oldrev', 'newrev', 'ref', @user) 28 + project.execute_hooks('oldrev', 'newrev', 'ref', @user)
29 }.should_not raise_error 29 }.should_not raise_error
30 end 30 end
31 end 31 end
32 32
33 context "with web hooks" do 33 context "with web hooks" do
34 before do 34 before do
35 - @webhook = Factory(:web_hook)  
36 - @webhook_2 = Factory(:web_hook)  
37 - project.web_hooks << [@webhook, @webhook_2] 35 + @project_hook = Factory(:project_hook)
  36 + @project_hook_2 = Factory(:project_hook)
  37 + project.hooks << [@project_hook, @project_hook_2]
38 end 38 end
39 39
40 it "executes multiple web hook" do 40 it "executes multiple web hook" do
41 - @webhook.should_receive(:execute).once  
42 - @webhook_2.should_receive(:execute).once 41 + @project_hook.should_receive(:execute).once
  42 + @project_hook_2.should_receive(:execute).once
43 43
44 - project.execute_web_hooks('oldrev', 'newrev', 'refs/heads/master', @user) 44 + project.execute_hooks('oldrev', 'newrev', 'refs/heads/master', @user)
45 end 45 end
46 end 46 end
47 47
48 context "does not execute web hooks" do 48 context "does not execute web hooks" do
49 before do 49 before do
50 - @webhook = Factory(:web_hook)  
51 - project.web_hooks << [@webhook] 50 + @project_hook = Factory(:project_hook)
  51 + project.hooks << [@project_hook]
52 end 52 end
53 53
54 it "when pushing a branch for the first time" do 54 it "when pushing a branch for the first time" do
55 - @webhook.should_not_receive(:execute)  
56 - project.execute_web_hooks('00000000000000000000000000000000', 'newrev', 'refs/heads/master', @user) 55 + @project_hook.should_not_receive(:execute)
  56 + project.execute_hooks('00000000000000000000000000000000', 'newrev', 'refs/heads/master', @user)
57 end 57 end
58 58
59 it "when pushing tags" do 59 it "when pushing tags" do
60 - @webhook.should_not_receive(:execute)  
61 - project.execute_web_hooks('oldrev', 'newrev', 'refs/tags/v1.0.0', @user) 60 + @project_hook.should_not_receive(:execute)
  61 + project.execute_hooks('oldrev', 'newrev', 'refs/tags/v1.0.0', @user)
62 end 62 end
63 end 63 end
64 64
spec/models/project_spec.rb
@@ -11,7 +11,7 @@ describe Project do @@ -11,7 +11,7 @@ describe Project do
11 it { should have_many(:issues).dependent(:destroy) } 11 it { should have_many(:issues).dependent(:destroy) }
12 it { should have_many(:notes).dependent(:destroy) } 12 it { should have_many(:notes).dependent(:destroy) }
13 it { should have_many(:snippets).dependent(:destroy) } 13 it { should have_many(:snippets).dependent(:destroy) }
14 - it { should have_many(:web_hooks).dependent(:destroy) } 14 + it { should have_many(:hooks).dependent(:destroy) }
15 it { should have_many(:deploy_keys).dependent(:destroy) } 15 it { should have_many(:deploy_keys).dependent(:destroy) }
16 end 16 end
17 17
spec/models/system_hook_spec.rb 0 → 100644
@@ -0,0 +1,63 @@ @@ -0,0 +1,63 @@
  1 +require "spec_helper"
  2 +
  3 +describe SystemHook do
  4 + describe "execute" do
  5 + before(:each) { ActiveRecord::Base.observers.enable(:all) }
  6 +
  7 + before(:each) do
  8 + @system_hook = Factory :system_hook
  9 + WebMock.stub_request(:post, @system_hook.url)
  10 + end
  11 +
  12 + it "project_create hook" do
  13 + user = Factory :user
  14 + with_resque do
  15 + project = Factory :project_without_owner, :owner => user
  16 + end
  17 + WebMock.should have_requested(:post, @system_hook.url).with(body: /project_create/).once
  18 + end
  19 +
  20 + it "project_destroy hook" do
  21 + project = Factory :project
  22 + with_resque do
  23 + project.destroy
  24 + end
  25 + WebMock.should have_requested(:post, @system_hook.url).with(body: /project_destroy/).once
  26 + end
  27 +
  28 + it "user_create hook" do
  29 + with_resque do
  30 + Factory :user
  31 + end
  32 + WebMock.should have_requested(:post, @system_hook.url).with(body: /user_create/).once
  33 + end
  34 +
  35 + it "user_destroy hook" do
  36 + user = Factory :user
  37 + with_resque do
  38 + user.destroy
  39 + end
  40 + WebMock.should have_requested(:post, @system_hook.url).with(body: /user_destroy/).once
  41 + end
  42 +
  43 + it "project_create hook" do
  44 + user = Factory :user
  45 + project = Factory :project
  46 + with_resque do
  47 + project.users << user
  48 + end
  49 + WebMock.should have_requested(:post, @system_hook.url).with(body: /user_add_to_team/).once
  50 + end
  51 +
  52 + it "project_destroy hook" do
  53 + user = Factory :user
  54 + project = Factory :project
  55 + project.users << user
  56 + with_resque do
  57 + project.users_projects.clear
  58 + end
  59 + WebMock.should have_requested(:post, @system_hook.url).with(body: /user_remove_from_team/).once
  60 + end
  61 + end
  62 +
  63 +end
spec/models/web_hook_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 -describe WebHook do 3 +describe ProjectHook do
4 describe "Associations" do 4 describe "Associations" do
5 it { should belong_to :project } 5 it { should belong_to :project }
6 end 6 end
@@ -23,32 +23,32 @@ describe WebHook do @@ -23,32 +23,32 @@ describe WebHook do
23 23
24 describe "execute" do 24 describe "execute" do
25 before(:each) do 25 before(:each) do
26 - @webhook = Factory :web_hook 26 + @project_hook = Factory :project_hook
27 @project = Factory :project 27 @project = Factory :project
28 - @project.web_hooks << [@webhook] 28 + @project.hooks << [@project_hook]
29 @data = { before: 'oldrev', after: 'newrev', ref: 'ref'} 29 @data = { before: 'oldrev', after: 'newrev', ref: 'ref'}
30 30
31 - WebMock.stub_request(:post, @webhook.url) 31 + WebMock.stub_request(:post, @project_hook.url)
32 end 32 end
33 33
34 it "POSTs to the web hook URL" do 34 it "POSTs to the web hook URL" do
35 - @webhook.execute(@data)  
36 - WebMock.should have_requested(:post, @webhook.url).once 35 + @project_hook.execute(@data)
  36 + WebMock.should have_requested(:post, @project_hook.url).once
37 end 37 end
38 38
39 it "POSTs the data as JSON" do 39 it "POSTs the data as JSON" do
40 json = @data.to_json 40 json = @data.to_json
41 41
42 - @webhook.execute(@data)  
43 - WebMock.should have_requested(:post, @webhook.url).with(body: json).once 42 + @project_hook.execute(@data)
  43 + WebMock.should have_requested(:post, @project_hook.url).with(body: json).once
44 end 44 end
45 45
46 it "catches exceptions" do 46 it "catches exceptions" do
47 WebHook.should_receive(:post).and_raise("Some HTTP Post error") 47 WebHook.should_receive(:post).and_raise("Some HTTP Post error")
48 48
49 lambda { 49 lambda {
50 - @webhook.execute(@data)  
51 - }.should_not raise_error 50 + @project_hook.execute(@data)
  51 + }.should raise_error
52 end 52 end
53 end 53 end
54 end 54 end
spec/requests/admin/admin_hooks_spec.rb 0 → 100644
@@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
  1 +require 'spec_helper'
  2 +
  3 +describe "Admin::Hooks" do
  4 + before do
  5 + @project = Factory :project,
  6 + :name => "LeGiT",
  7 + :code => "LGT"
  8 + login_as :admin
  9 +
  10 + @system_hook = Factory :system_hook
  11 +
  12 + end
  13 +
  14 + describe "GET /admin/hooks" do
  15 + it "should be ok" do
  16 + visit admin_root_path
  17 + within ".main_menu" do
  18 + click_on "Hooks"
  19 + end
  20 + current_path.should == admin_hooks_path
  21 + end
  22 +
  23 + it "should have hooks list" do
  24 + visit admin_hooks_path
  25 + page.should have_content(@system_hook.url)
  26 + end
  27 + end
  28 +
  29 + describe "New Hook" do
  30 + before do
  31 + @url = Faker::Internet.uri("http")
  32 + visit admin_hooks_path
  33 + fill_in "hook_url", :with => @url
  34 + expect { click_button "Add System Hook" }.to change(SystemHook, :count).by(1)
  35 + end
  36 +
  37 + it "should open new hook popup" do
  38 + page.current_path.should == admin_hooks_path
  39 + page.should have_content(@url)
  40 + end
  41 + end
  42 +
  43 + describe "Test" do
  44 + before do
  45 + WebMock.stub_request(:post, @system_hook.url)
  46 + visit admin_hooks_path
  47 + click_link "Test Hook"
  48 + end
  49 +
  50 + it { page.current_path.should == admin_hooks_path }
  51 + end
  52 +
  53 +end
spec/requests/admin/security_spec.rb
@@ -13,9 +13,9 @@ describe &quot;Admin::Projects&quot; do @@ -13,9 +13,9 @@ describe &quot;Admin::Projects&quot; do
13 it { admin_users_path.should be_denied_for :visitor } 13 it { admin_users_path.should be_denied_for :visitor }
14 end 14 end
15 15
16 - describe "GET /admin/emails" do  
17 - it { admin_emails_path.should be_allowed_for :admin }  
18 - it { admin_emails_path.should be_denied_for :user }  
19 - it { admin_emails_path.should be_denied_for :visitor } 16 + describe "GET /admin/hooks" do
  17 + it { admin_hooks_path.should be_allowed_for :admin }
  18 + it { admin_hooks_path.should be_denied_for :user }
  19 + it { admin_hooks_path.should be_denied_for :visitor }
20 end 20 end
21 end 21 end
spec/requests/hooks_spec.rb
@@ -9,7 +9,7 @@ describe &quot;Hooks&quot; do @@ -9,7 +9,7 @@ describe &quot;Hooks&quot; do
9 9
10 describe "GET index" do 10 describe "GET index" do
11 it "should be available" do 11 it "should be available" do
12 - @hook = Factory :web_hook, :project => @project 12 + @hook = Factory :project_hook, :project => @project
13 visit project_hooks_path(@project) 13 visit project_hooks_path(@project)
14 page.should have_content "Hooks" 14 page.should have_content "Hooks"
15 page.should have_content @hook.url 15 page.should have_content @hook.url
@@ -21,7 +21,7 @@ describe &quot;Hooks&quot; do @@ -21,7 +21,7 @@ describe &quot;Hooks&quot; do
21 @url = Faker::Internet.uri("http") 21 @url = Faker::Internet.uri("http")
22 visit project_hooks_path(@project) 22 visit project_hooks_path(@project)
23 fill_in "hook_url", :with => @url 23 fill_in "hook_url", :with => @url
24 - expect { click_button "Add Web Hook" }.to change(WebHook, :count).by(1) 24 + expect { click_button "Add Web Hook" }.to change(ProjectHook, :count).by(1)
25 end 25 end
26 26
27 it "should open new team member popup" do 27 it "should open new team member popup" do
@@ -32,7 +32,8 @@ describe &quot;Hooks&quot; do @@ -32,7 +32,8 @@ describe &quot;Hooks&quot; do
32 32
33 describe "Test" do 33 describe "Test" do
34 before do 34 before do
35 - @hook = Factory :web_hook, :project => @project 35 + @hook = Factory :project_hook, :project => @project
  36 + stub_request(:post, @hook.url)
36 visit project_hooks_path(@project) 37 visit project_hooks_path(@project)
37 click_link "Test Hook" 38 click_link "Test Hook"
38 end 39 end
spec/workers/post_receive_spec.rb
@@ -22,14 +22,14 @@ describe PostReceive do @@ -22,14 +22,14 @@ describe PostReceive do
22 Key.stub(find_by_identifier: nil) 22 Key.stub(find_by_identifier: nil)
23 23
24 project.should_not_receive(:observe_push) 24 project.should_not_receive(:observe_push)
25 - project.should_not_receive(:execute_web_hooks) 25 + project.should_not_receive(:execute_hooks)
26 26
27 PostReceive.perform(project.path, 'sha-old', 'sha-new', 'refs/heads/master', key_id).should be_false 27 PostReceive.perform(project.path, 'sha-old', 'sha-new', 'refs/heads/master', key_id).should be_false
28 end 28 end
29 29
30 it "asks the project to execute web hooks" do 30 it "asks the project to execute web hooks" do
31 Project.stub(find_by_path: project) 31 Project.stub(find_by_path: project)
32 - project.should_receive(:execute_web_hooks).with('sha-old', 'sha-new', 'refs/heads/master', project.owner) 32 + project.should_receive(:execute_hooks).with('sha-old', 'sha-new', 'refs/heads/master', project.owner)
33 33
34 PostReceive.perform(project.path, 'sha-old', 'sha-new', 'refs/heads/master', key_id) 34 PostReceive.perform(project.path, 'sha-old', 'sha-new', 'refs/heads/master', key_id)
35 end 35 end
vendor/assets/javascripts/jquery.waitforimages.js 0 → 100644
@@ -0,0 +1,144 @@ @@ -0,0 +1,144 @@
  1 +/*
  2 + * waitForImages 1.4
  3 + * -----------------
  4 + * Provides a callback when all images have loaded in your given selector.
  5 + * http://www.alexanderdickson.com/
  6 + *
  7 + *
  8 + * Copyright (c) 2011 Alex Dickson
  9 + * Licensed under the MIT licenses.
  10 + * See website for more info.
  11 + *
  12 + */
  13 +
  14 +;(function($) {
  15 + // Namespace all events.
  16 + var eventNamespace = 'waitForImages';
  17 +
  18 + // CSS properties which contain references to images.
  19 + $.waitForImages = {
  20 + hasImageProperties: [
  21 + 'backgroundImage',
  22 + 'listStyleImage',
  23 + 'borderImage',
  24 + 'borderCornerImage'
  25 + ]
  26 + };
  27 +
  28 + // Custom selector to find `img` elements that have a valid `src` attribute and have not already loaded.
  29 + $.expr[':'].uncached = function(obj) {
  30 + // Ensure we are dealing with an `img` element with a valid `src` attribute.
  31 + if ( ! $(obj).is('img[src!=""]')) {
  32 + return false;
  33 + }
  34 +
  35 + // Firefox's `complete` property will always be`true` even if the image has not been downloaded.
  36 + // Doing it this way works in Firefox.
  37 + var img = document.createElement('img');
  38 + img.src = obj.src;
  39 + return ! img.complete;
  40 + };
  41 +
  42 + $.fn.waitForImages = function(finishedCallback, eachCallback, waitForAll) {
  43 +
  44 + // Handle options object.
  45 + if ($.isPlainObject(arguments[0])) {
  46 + eachCallback = finishedCallback.each;
  47 + waitForAll = finishedCallback.waitForAll;
  48 + finishedCallback = finishedCallback.finished;
  49 + }
  50 +
  51 + // Handle missing callbacks.
  52 + finishedCallback = finishedCallback || $.noop;
  53 + eachCallback = eachCallback || $.noop;
  54 +
  55 + // Convert waitForAll to Boolean
  56 + waitForAll = !! waitForAll;
  57 +
  58 + // Ensure callbacks are functions.
  59 + if (!$.isFunction(finishedCallback) || !$.isFunction(eachCallback)) {
  60 + throw new TypeError('An invalid callback was supplied.');
  61 + };
  62 +
  63 + return this.each(function() {
  64 + // Build a list of all imgs, dependent on what images will be considered.
  65 + var obj = $(this),
  66 + allImgs = [];
  67 +
  68 + if (waitForAll) {
  69 + // CSS properties which may contain an image.
  70 + var hasImgProperties = $.waitForImages.hasImageProperties || [],
  71 + matchUrl = /url\((['"]?)(.*?)\1\)/g;
  72 +
  73 + // Get all elements, as any one of them could have a background image.
  74 + obj.find('*').each(function() {
  75 + var element = $(this);
  76 +
  77 + // If an `img` element, add it. But keep iterating in case it has a background image too.
  78 + if (element.is('img:uncached')) {
  79 + allImgs.push({
  80 + src: element.attr('src'),
  81 + element: element[0]
  82 + });
  83 + }
  84 +
  85 + $.each(hasImgProperties, function(i, property) {
  86 + var propertyValue = element.css(property);
  87 + // If it doesn't contain this property, skip.
  88 + if ( ! propertyValue) {
  89 + return true;
  90 + }
  91 +
  92 + // Get all url() of this element.
  93 + var match;
  94 + while (match = matchUrl.exec(propertyValue)) {
  95 + allImgs.push({
  96 + src: match[2],
  97 + element: element[0]
  98 + });
  99 + };
  100 + });
  101 + });
  102 + } else {
  103 + // For images only, the task is simpler.
  104 + obj
  105 + .find('img:uncached')
  106 + .each(function() {
  107 + allImgs.push({
  108 + src: this.src,
  109 + element: this
  110 + });
  111 + });
  112 + };
  113 +
  114 + var allImgsLength = allImgs.length,
  115 + allImgsLoaded = 0;
  116 +
  117 + // If no images found, don't bother.
  118 + if (allImgsLength == 0) {
  119 + finishedCallback.call(obj[0]);
  120 + };
  121 +
  122 + $.each(allImgs, function(i, img) {
  123 +
  124 + var image = new Image;
  125 +
  126 + // Handle the image loading and error with the same callback.
  127 + $(image).bind('load.' + eventNamespace + ' error.' + eventNamespace, function(event) {
  128 + allImgsLoaded++;
  129 +
  130 + // If an error occurred with loading the image, set the third argument accordingly.
  131 + eachCallback.call(img.element, allImgsLoaded, allImgsLength, event.type == 'load');
  132 +
  133 + if (allImgsLoaded == allImgsLength) {
  134 + finishedCallback.call(obj[0]);
  135 + return false;
  136 + };
  137 +
  138 + });
  139 +
  140 + image.src = img.src;
  141 + });
  142 + });
  143 + };
  144 +})(jQuery);