Commit eca823c1c7cef45cc18c6ab36d2327650c85bfc3
Exists in
master
and in
4 other branches
Merge branch 'master' into api
Showing
125 changed files
with
1752 additions
and
666 deletions
Show diff stats
.gitignore
CHANGELOG
Gemfile
@@ -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 |
Gemfile.lock
@@ -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 |
VERSION
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
@@ -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
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 | -} |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 < ApplicationController | @@ -6,7 +6,7 @@ class Admin::ProjectsController < 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 < ApplicationController | @@ -72,6 +72,6 @@ class Admin::ProjectsController < 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 < ActionController::Base | @@ -52,7 +52,7 @@ class ApplicationController < 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 < ApplicationController | @@ -26,43 +26,31 @@ class CommitsController < 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 < ApplicationController | @@ -2,15 +2,13 @@ class DashboardController < 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 < ApplicationController | @@ -11,24 +11,24 @@ class HooksController < 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 < ApplicationController | @@ -37,7 +37,7 @@ class HooksController < 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 < ApplicationController | @@ -24,16 +24,7 @@ class MergeRequestsController < 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 < ApplicationController | @@ -40,25 +40,6 @@ class NotesController < 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 < ApplicationController | @@ -9,7 +9,7 @@ class RefsController < 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 < ApplicationController | @@ -46,6 +46,18 @@ class RefsController < 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 < ApplicationController | @@ -79,6 +91,15 @@ class RefsController < 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 |
@@ -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
@@ -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 < ActiveRecord::Base | @@ -28,6 +28,10 @@ class Event < 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 < ActiveRecord::Base | @@ -22,7 +22,6 @@ class MergeRequest < 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 < ActiveRecord::Base | @@ -36,6 +35,7 @@ class MergeRequest < 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 < ActiveRecord::Base | @@ -128,7 +128,7 @@ class MergeRequest < 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 < ActiveRecord::Base | @@ -19,7 +19,7 @@ class Project < 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 < ActiveRecord::Base | @@ -120,7 +120,7 @@ class Project < 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/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 < ActiveRecord::Base | @@ -15,6 +16,11 @@ class User < 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 < ActiveRecord::Base | @@ -80,7 +86,8 @@ class User < 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 < ActiveRecord::Base | @@ -68,7 +68,7 @@ class UsersProject < 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 < ActiveRecord::Base | @@ -4,8 +4,6 @@ class WebHook < 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 < ActiveRecord::Base | @@ -14,9 +12,8 @@ class WebHook < 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 < ActiveRecord::Observer | @@ -43,7 +43,7 @@ class MailerObserver < 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 |
@@ -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? |
@@ -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) %> |
@@ -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 | + | ||
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
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 & will not be able to login to GitLab. | 66 | + Blocked users will be removed from all projects & 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 "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear | @@ -8,17 +8,10 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "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
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 | - 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 | - 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 |
@@ -0,0 +1,41 @@ | @@ -0,0 +1,41 @@ | ||
1 | +%h3 API | ||
2 | +.back_link | ||
3 | + = link_to help_path do | ||
4 | + ← 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
@@ -0,0 +1,13 @@ | @@ -0,0 +1,13 @@ | ||
1 | +%h3 System hooks | ||
2 | +.back_link | ||
3 | + = link_to :back do | ||
4 | + ← 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 | | 35 | |
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
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 | → | 15 | → |
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
app/views/merge_requests/new.html.haml
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
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_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 | | 33 | |
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(){ |
@@ -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" |
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 < Settingslogic | @@ -95,11 +95,21 @@ class Settings < 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/schema.rb
@@ -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 => 20120627145613) do | @@ -169,6 +169,8 @@ ActiveRecord::Schema.define(:version => 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 => 20120627145613) do | @@ -185,8 +187,9 @@ ActiveRecord::Schema.define(:version => 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 |
@@ -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 |
resque.sh
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 |
resque_dev.sh
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, "Hooks" do | @@ -21,44 +21,44 @@ describe Project, "Hooks" 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 |
@@ -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 |
@@ -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 "Admin::Projects" do | @@ -13,9 +13,9 @@ describe "Admin::Projects" 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 "Hooks" do | @@ -9,7 +9,7 @@ describe "Hooks" 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 "Hooks" do | @@ -21,7 +21,7 @@ describe "Hooks" 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 "Hooks" do | @@ -32,7 +32,8 @@ describe "Hooks" 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 |
@@ -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); |