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

Too many changes.

To preserve performance only 100 of 125 files displayed.

.gitignore
... ... @@ -6,6 +6,7 @@ log/*.log
6 6 tmp/
7 7 .sass-cache/
8 8 coverage/*
  9 +backups/*
9 10 *.swp
10 11 public/uploads/
11 12 .rvmrc
... ...
CHANGELOG
1 1 v 2.7.0
2 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 12 v 2.6.0
5 13 - UI polished
... ...
Gemfile
... ... @@ -7,7 +7,7 @@ gem "sqlite3"
7 7 gem "mysql2"
8 8  
9 9 # Auth
10   -gem "devise", "~> 1.5"
  10 +gem "devise", "~> 2.1.0"
11 11  
12 12 # GITLAB patched libs
13 13 gem "grit", :git => "https://github.com/gitlabhq/grit.git", :ref => "7f35cb98ff17d534a07e3ce6ec3d580f67402837"
... ... @@ -71,7 +71,6 @@ group :development, :test do
71 71 gem "awesome_print"
72 72 gem "database_cleaner"
73 73 gem "launchy"
74   - gem "webmock"
75 74 end
76 75  
77 76 group :test do
... ... @@ -82,4 +81,5 @@ group :test do
82 81 gem "shoulda-matchers"
83 82 gem 'email_spec'
84 83 gem 'resque_spec'
  84 + gem "webmock"
85 85 end
... ...
Gemfile.lock
... ... @@ -148,10 +148,11 @@ GEM
148 148 nokogiri (>= 1.5.0)
149 149 daemons (1.1.8)
150 150 database_cleaner (0.8.0)
151   - devise (1.5.3)
  151 + devise (2.1.2)
152 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 156 diff-lcs (1.1.3)
156 157 drapper (0.8.4)
157 158 email_spec (1.2.1)
... ... @@ -225,7 +226,7 @@ GEM
225 226 omniauth (1.1.0)
226 227 hashie (~> 1.2)
227 228 rack
228   - orm_adapter (0.0.7)
  229 + orm_adapter (0.3.0)
229 230 polyglot (0.3.3)
230 231 posix-spawn (0.3.6)
231 232 pry (0.9.9.6)
... ... @@ -356,7 +357,7 @@ GEM
356 357 raindrops (~> 0.7)
357 358 vegas (0.1.11)
358 359 rack (>= 1.0.0)
359   - warden (1.2.0)
  360 + warden (1.2.1)
360 361 rack (>= 1.0)
361 362 webmock (1.8.7)
362 363 addressable (>= 2.2.7)
... ... @@ -383,7 +384,7 @@ DEPENDENCIES
383 384 colored
384 385 cucumber-rails
385 386 database_cleaner
386   - devise (~> 1.5)
  387 + devise (~> 2.1.0)
387 388 drapper
388 389 email_spec
389 390 ffaker
... ...
VERSION
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 12 //= require jquery.cookie
13 13 //= require jquery.endless-scroll
14 14 //= require jquery.highlight
  15 +//= require jquery.waitforimages
15 16 //= require bootstrap-modal
16 17 //= require modernizr
17 18 //= require chosen-jquery
... ... @@ -20,10 +21,26 @@
20 21 //= require_tree .
21 22  
22 23 $(document).ready(function(){
  24 +
23 25 $(".one_click_select").live("click", function(){
24 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 44 $(".account-box").mouseenter(showMenu);
28 45 $(".account-box").mouseleave(resetMenu);
29 46  
... ... @@ -97,3 +114,8 @@ function showDiff(link) {
97 114 return _chosen.apply(this, [default_options]);
98 115 }})
99 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 73 $("#milestone_id, #assignee_id, #label_name").on("change", function(){
74 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 25 $(this).closest('li').fadeOut(); });
26 26  
27 27 $("#new_note").live("ajax:before", function(){
28   - $("#submit_note").attr("disabled", "disabled");
  28 + $(".submit_note").attr("disabled", "disabled");
29 29 })
30 30  
31 31 $("#new_note").live("ajax:complete", function(){
32   - $("#submit_note").removeAttr("disabled");
  32 + $(".submit_note").removeAttr("disabled");
33 33 })
34 34  
35 35 $("#note_note").live("focus", function(){
... ...
app/assets/stylesheets/common.scss
... ... @@ -604,7 +604,11 @@ li.note {
604 604 border-style: solid;
605 605 border-width: 1px;
606 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 614 .supp_diff_link,
... ...
app/assets/stylesheets/gitlab_bootstrap.scss
... ... @@ -202,6 +202,10 @@ a:focus {
202 202 color:$style_color;
203 203 }
204 204  
  205 +.nav-tabs > .active > a {
  206 + font-weight:bold;
  207 +}
  208 +
205 209 /** COLORS **/
206 210 .cgray { color:gray; }
207 211 .cred { color:#D12F19; }
... ... @@ -209,6 +213,7 @@ a:focus {
209 213 .cblack { color:#111; }
210 214 .cdark { color:#444 }
211 215 .cwhite { color:#fff !important }
  216 +.bgred { background: #F2DEDE !important}
212 217  
213 218 /** COMMON STYLES **/
214 219 .left {
... ... @@ -299,9 +304,24 @@ table.no-borders {
299 304 }
300 305  
301 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 327 img.avatar {
... ... @@ -425,9 +445,10 @@ form {
425 445 */
426 446 .ui-box {
427 447 background:#F9F9F9;
428   - margin-bottom: 40px;
  448 + margin-bottom: 25px;
429 449 @include round-borders-all(4px);
430 450 border-color: #CCC;
  451 + @include solid_shade;
431 452  
432 453 ul {
433 454 margin:0;
... ... @@ -443,6 +464,13 @@ form {
443 464 background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
444 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 474 form {
447 475 padding:9px 0;
448 476 margin:0px;
... ... @@ -511,6 +539,7 @@ form {
511 539 table.admin-table {
512 540 @extend .table-bordered;
513 541 @extend .zebra-striped;
  542 + @include solid_shade;
514 543 th {
515 544 border-color: #CCC;
516 545 border-bottom: 1px solid #bbb;
... ... @@ -568,6 +597,8 @@ ul.breadcrumb {
568 597 @extend .prepend-top-20;
569 598 @extend .append-bottom-20;
570 599 border-width:1px;
  600 + @include solid_shade;
  601 +
571 602  
572 603 img { max-width: 100%; }
573 604  
... ... @@ -624,13 +655,166 @@ p {
624 655 h3.page_title {
625 656 color:#456;
626 657 font-size:20px;
627   - font-weight: 600;
  658 + font-weight: normal;
628 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 96 */
97 97 .search {
98 98 float: right;
99   - margin-right: 55px;
  99 + margin-right: 50px;
100 100  
101 101 .search-input {
102 102 @extend .span2;
... ... @@ -126,10 +126,10 @@ header {
126 126 cursor: pointer;
127 127 img {
128 128 border-radius: 4px;
129   - right: 0px;
  129 + right: 5px;
130 130 position: absolute;
131   - width: 33px;
132   - height: 33px;
  131 + width: 31px;
  132 + height: 31px;
133 133 display: block;
134 134 top: 0;
135 135 &:after {
... ...
app/assets/stylesheets/main.scss
... ... @@ -31,6 +31,12 @@ $hover: #FDF5D9;
31 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 40 @mixin border-radius($radius) {
35 41 -moz-border-radius: $radius;
36 42 -webkit-border-radius: $radius;
... ... @@ -136,7 +142,7 @@ $hover: #FDF5D9;
136 142 /**
137 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 148 * This file represent notes(comments) styles
... ...
app/assets/stylesheets/notes.scss
... ... @@ -63,18 +63,22 @@ p.notify_controls span{
63 63  
64 64 tr.line_notes_row {
65 65 border-bottom:1px solid #DDD;
  66 + border-left: 7px solid #2A79A3;
  67 +
66 68 &.reply {
67 69 background:#eee;
68   -
  70 + border-left: 7px solid #2A79A3;
  71 + border-top:1px solid #ddd;
69 72 td {
70 73 padding:7px 10px;
71 74 }
72 75 a.line_note_reply_link {
73 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 79 color: white;
  80 + background: #2A79A3;
  81 + border-color: #2A79A3;
78 82 }
79 83 }
80 84 ul {
... ... @@ -95,6 +99,9 @@ tr.line_notes_row {
95 99 td {
96 100 border-bottom:1px solid #ddd;
97 101 }
  102 + .actions {
  103 + margin:0;
  104 + }
98 105 }
99 106  
100 107 td .line_note_link {
... ...
app/assets/stylesheets/sections/commits.scss
... ... @@ -101,18 +101,21 @@
101 101 margin:50px;
102 102 padding:1px;
103 103 max-width:400px;
104   - }
105   - &.diff_image_removed {
106   - img {
  104 +
  105 + &.diff_image_removed {
107 106 border: 1px solid #C00;
108 107 }
109   - }
110 108  
111   - &.diff_image_added {
112   - img {
  109 + &.diff_image_added {
113 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 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 @@
  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 70 }
71 71 }
72 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   -#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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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   -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 6 def index
7 7 @admin_projects = Project.scoped
8 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 10 end
11 11  
12 12 def show
... ... @@ -72,6 +72,6 @@ class Admin::ProjectsController &lt; ApplicationController
72 72 @admin_project = Project.find_by_code(params[:id])
73 73 @admin_project.destroy
74 74  
75   - redirect_to admin_projects_url
  75 + redirect_to admin_projects_url, notice: 'Project was successfully deleted.'
76 76 end
77 77 end
... ...
app/controllers/application_controller.rb
... ... @@ -52,7 +52,7 @@ class ApplicationController &lt; ActionController::Base
52 52  
53 53 def layout_by_resource
54 54 if devise_controller?
55   - "devise"
  55 + "devise_layout"
56 56 else
57 57 "application"
58 58 end
... ...
app/controllers/commits_controller.rb
... ... @@ -26,43 +26,31 @@ class CommitsController &lt; ApplicationController
26 26 end
27 27  
28 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 41 end
  42 +
44 43 rescue Grit::Git::GitTimeout
45 44 render "huge_commit"
46 45 end
47 46  
48 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 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 54 end
67 55  
68 56 def patch
... ...
app/controllers/dashboard_controller.rb
... ... @@ -2,15 +2,13 @@ class DashboardController &lt; ApplicationController
2 2 respond_to :html
3 3  
4 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 7 @last_push = current_user.recent_push
11 8  
12 9 respond_to do |format|
13 10 format.html
  11 + format.js
14 12 format.atom { render :layout => false }
15 13 end
16 14 end
... ...
app/controllers/hooks_controller.rb
... ... @@ -11,24 +11,24 @@ class HooksController &lt; ApplicationController
11 11 respond_to :html
12 12  
13 13 def index
14   - @hooks = @project.web_hooks.all
15   - @hook = WebHook.new
  14 + @hooks = @project.hooks.all
  15 + @hook = ProjectHook.new
16 16 end
17 17  
18 18 def create
19   - @hook = @project.web_hooks.new(params[:hook])
  19 + @hook = @project.hooks.new(params[:hook])
20 20 @hook.save
21 21  
22 22 if @hook.valid?
23 23 redirect_to project_hooks_path(@project)
24 24 else
25   - @hooks = @project.web_hooks.all
  25 + @hooks = @project.hooks.all
26 26 render :index
27 27 end
28 28 end
29 29  
30 30 def test
31   - @hook = @project.web_hooks.find(params[:id])
  31 + @hook = @project.hooks.find(params[:id])
32 32 commits = @project.commits(@project.default_branch, nil, 3)
33 33 data = @project.post_receive_data(commits.last.id, commits.first.id, "refs/heads/#{@project.default_branch}", current_user)
34 34 @hook.execute(data)
... ... @@ -37,7 +37,7 @@ class HooksController &lt; ApplicationController
37 37 end
38 38  
39 39 def destroy
40   - @hook = @project.web_hooks.find(params[:id])
  40 + @hook = @project.hooks.find(params[:id])
41 41 @hook.destroy
42 42  
43 43 redirect_to project_hooks_path(@project)
... ...
app/controllers/merge_requests_controller.rb
... ... @@ -24,16 +24,7 @@ class MergeRequestsController &lt; ApplicationController
24 24  
25 25  
26 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 28 end
38 29  
39 30 def show
... ...
app/controllers/notes_controller.rb
... ... @@ -40,25 +40,6 @@ class NotesController &lt; ApplicationController
40 40 protected
41 41  
42 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 44 end
64 45 end
... ...
app/controllers/omniauth_callbacks_controller.rb
1 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 16 def ldap
4 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 9 before_filter :require_non_empty_project
10 10  
11 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 13 before_filter :render_full_content
14 14  
15 15 layout "project"
... ... @@ -46,6 +46,18 @@ class RefsController &lt; ApplicationController
46 46 end
47 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 61 def blob
50 62 if @tree.is_blob?
51 63 if @tree.text?
... ... @@ -79,6 +91,15 @@ class RefsController &lt; ApplicationController
79 91 @commit = project.commit(@ref)
80 92 @tree = Tree.new(@commit.tree, project, @ref, params[:path])
81 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 103 rescue
83 104 return render_404
84 105 end
... ...
app/decorators/event_decorator.rb 0 → 100644
... ... @@ -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 134 end
135 135 active ? "current" : nil
136 136 end
  137 +
  138 + def hexdigest(string)
  139 + Digest::SHA1.hexdigest string
  140 + end
137 141 end
... ...
app/helpers/tree_helper.rb 0 → 100644
... ... @@ -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 80 def commits_between(repo, from, to)
81 81 repo.commits_between(from, to).map { |c| Commit.new(c) }
82 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 106 end
84 107  
85 108 def persisted?
... ...
app/models/event.rb
... ... @@ -28,6 +28,10 @@ class Event &lt; ActiveRecord::Base
28 28 end
29 29 end
30 30  
  31 + def self.recent_for_user user
  32 + where(:project_id => user.projects.map(&:id)).recent
  33 + end
  34 +
31 35 # Next events currently enabled for system
32 36 # - push
33 37 # - new issue
... ...
app/models/merge_request.rb
... ... @@ -22,7 +22,6 @@ class MergeRequest &lt; ActiveRecord::Base
22 22 :should_remove_source_branch
23 23  
24 24 validates_presence_of :project_id
25   - validates_presence_of :assignee_id
26 25 validates_presence_of :author_id
27 26 validates_presence_of :source_branch
28 27 validates_presence_of :target_branch
... ... @@ -36,6 +35,7 @@ class MergeRequest &lt; ActiveRecord::Base
36 35 delegate :name,
37 36 :email,
38 37 :to => :assignee,
  38 + :allow_nil => true,
39 39 :prefix => true
40 40  
41 41 validates :title,
... ... @@ -128,7 +128,7 @@ class MergeRequest &lt; ActiveRecord::Base
128 128  
129 129 def unmerged_diffs
130 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 132 end
133 133  
134 134 def last_commit
... ...
app/models/project.rb
... ... @@ -19,7 +19,7 @@ class Project &lt; ActiveRecord::Base
19 19 has_many :notes, :dependent => :destroy
20 20 has_many :snippets, :dependent => :destroy
21 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 23 has_many :wikis, :dependent => :destroy
24 24 has_many :protected_branches, :dependent => :destroy
25 25  
... ... @@ -120,7 +120,7 @@ class Project &lt; ActiveRecord::Base
120 120 errors.add(:path, " like 'gitolite-admin' is not allowed")
121 121 end
122 122 end
123   -
  123 +
124 124 def self.access_options
125 125 UsersProject.access_roles
126 126 end
... ...
app/models/project_hook.rb 0 → 100644
... ... @@ -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 @@
  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 1 class User < ActiveRecord::Base
  2 +
2 3 include Account
3 4  
4   - devise :database_authenticatable, :token_authenticatable,
  5 + devise :database_authenticatable, :token_authenticatable, :lockable,
5 6 :recoverable, :rememberable, :trackable, :validatable, :omniauthable
6 7  
7 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 10 :theme_id, :force_random_password
10 11  
11 12 attr_accessor :force_random_password
... ... @@ -15,6 +16,11 @@ class User &lt; ActiveRecord::Base
15 16 has_many :my_own_projects, :class_name => "Project", :foreign_key => :owner_id
16 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 24 has_many :recent_events,
19 25 :class_name => "Event",
20 26 :foreign_key => :author_id,
... ... @@ -80,7 +86,8 @@ class User &lt; ActiveRecord::Base
80 86  
81 87 def self.find_for_ldap_auth(omniauth_info)
82 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 92 if @user = User.find_by_email(email)
86 93 @user
... ...
app/models/users_project.rb
... ... @@ -68,7 +68,7 @@ class UsersProject &lt; ActiveRecord::Base
68 68 end
69 69  
70 70 def repo_access_human
71   - ""
  71 + self.class.access_roles.invert[self.project_access]
72 72 end
73 73 end
74 74 # == Schema Information
... ...
app/models/web_hook.rb
... ... @@ -4,8 +4,6 @@ class WebHook &lt; ActiveRecord::Base
4 4 # HTTParty timeout
5 5 default_timeout 10
6 6  
7   - belongs_to :project
8   -
9 7 validates :url,
10 8 presence: true,
11 9 format: {
... ... @@ -14,9 +12,8 @@ class WebHook &lt; ActiveRecord::Base
14 12  
15 13 def execute(data)
16 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 15 end
  16 +
20 17 end
21 18 # == Schema Information
22 19 #
... ...
app/observers/mailer_observer.rb
... ... @@ -43,7 +43,7 @@ class MailerObserver &lt; ActiveRecord::Observer
43 43 end
44 44  
45 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 47 Notify.new_merge_request_email(merge_request.id).deliver
48 48 end
49 49 end
... ...
app/observers/system_hook_observer.rb 0 → 100644
... ... @@ -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 55 # Take only latest one
56 56 events = events.recent.limit(1).first
57 57 end
  58 +
  59 + def projects_with_events
  60 + projects.includes(:events).order("events.created_at DESC")
  61 + end
58 62 end
... ...
app/roles/git_push.rb
... ... @@ -27,7 +27,7 @@ module GitPush
27 27 true
28 28 end
29 29  
30   - def execute_web_hooks(oldrev, newrev, ref, user)
  30 + def execute_hooks(oldrev, newrev, ref, user)
31 31 ref_parts = ref.split('/')
32 32  
33 33 # Return if this is not a push to a branch (e.g. new commits)
... ... @@ -35,7 +35,7 @@ module GitPush
35 35  
36 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 39 end
40 40  
41 41 def post_receive_data(oldrev, newrev, ref, user)
... ... @@ -97,7 +97,7 @@ module GitPush
97 97 self.update_merge_requests(oldrev, newrev, ref, user)
98 98  
99 99 # Execute web hooks
100   - self.execute_web_hooks(oldrev, newrev, ref, user)
  100 + self.execute_hooks(oldrev, newrev, ref, user)
101 101  
102 102 # Create satellite
103 103 self.satellite.create unless self.satellite.exists?
... ...
app/views/admin/hooks/_data_ex.html.erb 0 → 100644
... ... @@ -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 @@
  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   -%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 13 %th Team Members
14 14 %th Post Receive
15 15 %th Last Commit
16   - %th
17   - %th
  16 + %th Edit
  17 + %th.cred Danger Zone!
18 18  
19 19 - @admin_projects.each do |project|
20 20 %tr
... ... @@ -24,5 +24,5 @@
24 24 %td= check_box_tag :post_receive_file, 1, project.has_post_receive_file?, :disabled => true
25 25 %td= last_commit(project)
26 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 28 = paginate @admin_projects, :theme => "admin"
... ...
app/views/admin/users/_form.html.haml
... ... @@ -50,7 +50,7 @@
50 50  
51 51 .alert
52 52 .clearfix
53   - %p Give user ability to manage application.
  53 + %p Make the user a GitLab administrator.
54 54 = f.label :admin, :class => "checkbox" do
55 55 = f.check_box :admin
56 56 %span Administrator
... ... @@ -59,11 +59,11 @@
59 59 - if @admin_user.blocked
60 60 %span
61 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 63 - else
64 64 %span
65 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 67 .actions
68 68 = f.submit 'Save', :class => "btn primary"
69 69 - if @admin_user.new_record?
... ...
app/views/admin/users/index.html.haml
... ... @@ -27,7 +27,7 @@
27 27 %th Projects
28 28 %th Edit
29 29 %th Blocked
30   - %th
  30 + %th.cred Danger Zone!
31 31  
32 32 - @admin_users.each do |user|
33 33 %tr
... ... @@ -41,6 +41,6 @@
41 41 = link_to 'Unblock', unblock_admin_user_path(user), :method => :put, :class => "btn small success"
42 42 - else
43 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 46 = paginate @admin_users, :theme => "admin"
... ...
app/views/commits/_commits.html.haml
1 1 - @commits.group_by { |c| c.committed_date.to_date }.each do |day, commits|
2 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 6 %ul.unstyled= render commits
... ...
app/views/commits/_diffs.html.haml
... ... @@ -35,7 +35,13 @@
35 35 - if file.text?
36 36 = render "commits/text_file", :diff => diff, :index => i
37 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 46 - else
41 47 %p.nothing_here_message No preview for this file type
... ...
app/views/commits/_head.html.haml
... ... @@ -13,12 +13,12 @@
13 13 %li{:class => "#{branches_tab_class}"}
14 14 = link_to project_repository_path(@project) do
15 15 Branches
16   - %span.number= @project.repo.branch_count
  16 + %span.badge= @project.repo.branch_count
17 17  
18 18 %li{:class => "#{'active' if current_page?(tags_project_repository_path(@project)) }"}
19 19 = link_to tags_project_repository_path(@project) do
20 20 Tags
21   - %span.number= @project.repo.tag_count
  21 + %span.badge= @project.repo.tag_count
22 22  
23 23  
24 24 - if current_page?(project_commits_path(@project)) && current_user.private_token
... ...
app/views/commits/compare.html.haml
... ... @@ -20,7 +20,7 @@
20 20 = "..."
21 21 = text_field_tag :to, params[:to], :placeholder => "aa8b4ef", :class => "xlarge"
22 22 .actions
23   - = submit_tag "Compare", :class => "btn primary"
  23 + = submit_tag "Compare", :class => "btn btn-primary"
24 24  
25 25  
26 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 8  
9 9 @events.each do |event|
10 10 if event.allowed?
  11 + event = EventDecorator.decorate(event)
11 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 16 xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
24 17 xml.link :href => event_link
... ...
app/views/dashboard/index.html.haml
... ... @@ -10,9 +10,10 @@
10 10 add new key
11 11 to your profile
12 12 - if @events.any?
13   - = render @events
  13 + .content_list= render @events
14 14 - else
15 15 %h4.nothing_here_message Projects activity will be displayed here
  16 + .loading.hide
16 17 .side
17 18 = render "events/event_last_push", :event => @last_push
18 19 .projects_box
... ... @@ -54,3 +55,7 @@
54 55 New Project »
55 56 - else
56 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 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 1 = image_tag gravatar_icon(event.author_email), :class => "avatar"
2 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 5 = link_to project_issue_path(event.project, event.issue) do
6 6 %strong= truncate event.issue_title
7 7 at
... ...
app/views/events/_event_last_push.html.haml
... ... @@ -5,12 +5,9 @@
5 5 %span Your pushed to
6 6 = event.ref_type
7 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 9 at
10 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 12 = link_to new_mr_path_from_push_event(event), :title => "New Merge Request", :class => "btn very_small primary" do
16 13 Create Merge Request
... ...
app/views/events/_event_merge_request.html.haml
... ... @@ -2,8 +2,8 @@
2 2 .event_icon= image_tag "event_mr_merged.png"
3 3 = image_tag gravatar_icon(event.author_email), :class => "avatar"
4 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 7 = link_to project_merge_request_path(event.project, event.merge_request) do
8 8 %strong= truncate event.merge_request_title
9 9 at
... ...
app/views/events/_event_push.html.haml
... ... @@ -2,7 +2,7 @@
2 2 .event_icon= image_tag "event_push.png"
3 3 = image_tag gravatar_icon(event.author_email), :class => "avatar"
4 4 %strong #{event.author_name}
5   - %span.event_label= event.push_action_name
  5 + %span.event_label.pushed= event.push_action_name
6 6 = event.ref_type
7 7 = link_to project_commits_path(event.project, :ref => event.ref_name) do
8 8 %strong= event.ref_name
... ...
app/views/help/api.html.haml 0 → 100644
... ... @@ -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 22  
23 23 %li
24 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 @@
  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 6 .row
7 7 .span7= paginate @issues, :remote => true, :theme => "gitlab"
8 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 12 - else
11 13 %li
12 14 %h4.nothing_here_message Nothing to show here
... ...
app/views/issues/_show.html.haml
... ... @@ -12,9 +12,9 @@
12 12 = issue.notes.count
13 13 - if can? current_user, :modify_issue, issue
14 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 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 18 = link_to edit_project_issue_path(issue.project, issue), :class => "btn small edit-issue-link", :remote => true do
19 19 %i.icon-edit
20 20 Edit
... ... @@ -35,6 +35,4 @@
35 35 &nbsp;
36 36  
37 37 - if issue.upvotes > 0
38   - %span.badge.badge-success= "+#{issue.upvotes}"
39   -
40   -
  38 + %span.badge.badge-success= "+#{issue.upvotes}"
41 39 \ No newline at end of file
... ...
app/views/issues/index.html.haml
... ... @@ -2,7 +2,7 @@
2 2 .issues_content
3 3 %h3.page_title
4 4 Issues
5   - %small (#{@issues.total_count})
  5 + %small (<span class=issue_counter>#{@issues.total_count}</span>)
6 6 .right
7 7 .span5
8 8 - if can? current_user, :write_issue, @project
... ... @@ -45,4 +45,4 @@
45 45 :javascript
46 46 $(function(){
47 47 issuesPage();
48 48 - })
  49 + })
49 50 \ No newline at end of file
... ...
app/views/keys/new.html.haml
1   -%h3 New key
  1 +%h3.page_title New key
2 2 %hr
3 3 = render 'form'
4 4  
... ... @@ -11,4 +11,4 @@
11 11 if( key_mail && key_mail.length > 0 && title.val() == '' ){
12 12 $('#key_title').val( key_mail );
13 13 }
14   - });
15 14 \ No newline at end of file
  15 + });
... ...
app/views/layouts/_project_menu.html.haml
... ... @@ -17,14 +17,14 @@
17 17 %li{:class => tab_class(:issues)}
18 18 = link_to project_issues_filter_path(@project) do
19 19 Issues
20   - %span.count= @project.issues.opened.count
  20 + %span.count.issue_counter= @project.issues.opened.count
21 21  
22 22 - if @project.repo_exists?
23 23 - if @project.merge_requests_enabled
24 24 %li{:class => tab_class(:merge_requests)}
25 25 = link_to project_merge_requests_path(@project) do
26 26 Merge Requests
27   - %span.count= @project.merge_requests.opened.count
  27 + %span.count.merge_counter= @project.merge_requests.opened.count
28 28  
29 29 - if @project.wall_enabled
30 30 %li{:class => tab_class(:wall)}
... ...
app/views/layouts/admin.html.haml
... ... @@ -15,7 +15,7 @@
15 15 %li{:class => tab_class(:admin_logs)}
16 16 = link_to "Logs", admin_logs_path
17 17 %li{:class => tab_class(:admin_emails)}
18   - = link_to "Emails", admin_emails_path
  18 + = link_to "Hooks", admin_hooks_path
19 19 %li{:class => tab_class(:admin_resque)}
20 20 = link_to "Resque", admin_resque_path
21 21  
... ...
app/views/layouts/devise.html.haml
... ... @@ -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 @@
  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 12 %li{:class => tab_class(:password)}
13 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 20 %li{:class => tab_class(:token)}
16 21 = link_to "Token", profile_token_path
17 22  
18 23 %li{:class => tab_class(:design)}
19 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 27 .content
27 28 = yield
... ...
app/views/merge_requests/_form.html.haml
... ... @@ -5,7 +5,8 @@
5 5 - @merge_request.errors.full_messages.each do |msg|
6 6 %li= msg
7 7  
8   - %h3.padded.cgray 1. Select Branches
  8 + %h4.cdark 1. Select Branches
  9 + %br
9 10  
10 11 .row
11 12 .span6
... ... @@ -30,14 +31,21 @@
30 31 .bottom_commit
31 32 .mr_target_commit
32 33  
33   - %h3.padded.cgray 2. Fill info
  34 + %h4.cdark 2. Fill info
  35 +
34 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 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 50 .form-actions
43 51 = f.submit 'Save', :class => "btn-primary btn"
... ...
app/views/merge_requests/_merge_request.html.haml
... ... @@ -15,12 +15,14 @@
15 15 &rarr;
16 16 = merge_request.target_branch
17 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 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 25 = time_ago_in_words(merge_request.created_at)
22 26 ago
23 27 - if merge_request.upvotes > 0
24 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 2 = "Edit merge request #{@merge_request.id}"
3 3 %hr
4 4 = render 'form'
... ...
app/views/merge_requests/new.html.haml
1   -%h3 New Merge Request
  1 +%h3.page_title New Merge Request
2 2 %hr
3 3 = render 'form'
... ...
app/views/merge_requests/show/_commits.html.haml
1 1 - if @commits.present?
2 2 .ui-box
3   - %h5 Commits (#{@commits.count})
  3 + %h5
  4 + %i.icon-list
  5 + Commits (#{@commits.count})
4 6 .merge-request-commits
5 7 - if @commits.count > 8
6 8 %ul.first_mr_commits.unstyled
... ...
app/views/merge_requests/show/_mr_box.html.haml
... ... @@ -13,9 +13,10 @@
13 13 = image_tag gravatar_icon(@merge_request.author_email), :width => 16, :class => "lil_av"
14 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 22 - if @merge_request.closed
... ...
app/views/notes/_form.html.haml
... ... @@ -32,4 +32,4 @@
32 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 24 = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
25 25 %span Commit author
26 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 28 = link_to "Close", "#", :class => "btn hide-button"
29 29  
30 30 :javascript
... ...
app/views/notes/_reply_button.html.haml
1 1 %tr.line_notes_row.reply
2 2 %td{:colspan => 3}
  3 + %i.icon-comment
3 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 13 = render :partial => "refs/tree_file", :locals => { :name => tree.name, :content => tree.data, :file => tree }
14 14 - else
15 15 - contents = tree.contents
16   - %table#tree-slider.bordered-table.table
  16 + %table#tree-slider.bordered-table.table{:class => "table_#{@hex_path}" }
17 17 %thead
18 18 %th Name
19 19 %th Last Update
... ... @@ -29,34 +29,39 @@
29 29 %td
30 30 %td
31 31  
  32 + - index = 0
32 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 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 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 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 46 - if content.name =~ /\.(md|markdown)$/i
44 47 = preserve do
45 48 = markdown(content.data)
46 49 - else
47 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 52 :javascript
54 53 $(function(){
55 54 $('select#branch').selectmenu({style:'popup', width:200});
56 55 $('select#tag').selectmenu({style:'popup', width:200});
57 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 @@
  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 3 %i.icon-file
4 4 %span.file_name
5 5 = name
... ... @@ -10,26 +10,28 @@
10 10 = link_to "blame", blame_file_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small"
11 11 - if file.text?
12 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 16 - else
18   - .view_file_content
  17 + .file_content.code
19 18 - unless file.empty?
20 19 %div{:class => current_user.dark_scheme ? "black" : "white"}
21 20 = preserve do
22 21 = raw file.colorize(options: { linenos: 'True'})
23 22 - else
24 23 %h4.nothing_here_message Empty file
  24 +
25 25 - elsif file.image?
26   - .view_file_content_image
  26 + .file_content.image_file
27 27 %img{ :src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
  28 +
28 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 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 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 11 %li= link
12 12 .clear
13 13  
14   - .view_file.blame_file
15   - .view_file_header
  14 + .file_holder
  15 + .file_title
16 16 %i.icon-file
17 17 %span.file_name
18 18 = @tree.name
... ... @@ -21,7 +21,7 @@
21 21 = link_to "raw", blob_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small", :target => "_blank"
22 22 = link_to "history", project_commits_path(@project, :path => params[:path], :ref => @ref), :class => "btn very_small"
23 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 25 %table
26 26 - @blame.each do |commit, lines|
27 27 - commit = Commit.new(commit)
... ... @@ -29,7 +29,7 @@
29 29 %td.author
30 30 = image_tag gravatar_icon(commit.author_email, 16)
31 31 = commit.author_name
32   - %td.commit
  32 + %td.blame_commit
33 33 &nbsp;
34 34 = link_to project_commit_path(@project, :id => commit.id) do
35 35 %code= commit.id.to_s[0..10]
... ... @@ -37,8 +37,7 @@
37 37 %td.lines
38 38 = preserve do
39 39 %pre
40   - - lines.each do |line|
41   - = line
  40 + = Gitlab::Encode.utf8 lines.join("\n")
42 41  
43 42 :javascript
44 43 $(function(){
... ...
app/views/refs/logs_tree.js.haml 0 → 100644
... ... @@ -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 1 :plain
  2 + // Load Files list
2 3 $("#tree-holder").html("#{escape_javascript(render(:partial => "tree", :locals => {:repo => @repo, :commit => @commit, :tree => @tree}))}");
3 4 $("#tree-content-holder").show("slide", { direction: "right" }, 150);
4 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 7 = link_to "Edit", edit_project_snippet_path(@project, @snippet), :class => "btn small right"
8 8  
9 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 20 = render "notes/notes", :tid => @snippet.id, :tt => "snippet"
... ...
app/workers/system_hook_worker.rb 0 → 100644
... ... @@ -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 23 # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
24 24  
25 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 28 # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
29 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 21 # Like default project limit for user etc
22 22 app:
23 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 #
... ...