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 | gem "mysql2" |
8 | 8 | |
9 | 9 | # Auth |
10 | -gem "devise", "~> 1.5" | |
10 | +gem "devise", "~> 2.1.0" | |
11 | 11 | |
12 | 12 | # GITLAB patched libs |
13 | 13 | gem "grit", :git => "https://github.com/gitlabhq/grit.git", :ref => "7f35cb98ff17d534a07e3ce6ec3d580f67402837" |
... | ... | @@ -71,7 +71,6 @@ group :development, :test do |
71 | 71 | gem "awesome_print" |
72 | 72 | gem "database_cleaner" |
73 | 73 | gem "launchy" |
74 | - gem "webmock" | |
75 | 74 | end |
76 | 75 | |
77 | 76 | group :test do |
... | ... | @@ -82,4 +81,5 @@ group :test do |
82 | 81 | gem "shoulda-matchers" |
83 | 82 | gem 'email_spec' |
84 | 83 | gem 'resque_spec' |
84 | + gem "webmock" | |
85 | 85 | end | ... | ... |
Gemfile.lock
... | ... | @@ -148,10 +148,11 @@ GEM |
148 | 148 | nokogiri (>= 1.5.0) |
149 | 149 | daemons (1.1.8) |
150 | 150 | database_cleaner (0.8.0) |
151 | - devise (1.5.3) | |
151 | + devise (2.1.2) | |
152 | 152 | bcrypt-ruby (~> 3.0) |
153 | - orm_adapter (~> 0.0.3) | |
154 | - warden (~> 1.1) | |
153 | + orm_adapter (~> 0.1) | |
154 | + railties (~> 3.1) | |
155 | + warden (~> 1.2.1) | |
155 | 156 | diff-lcs (1.1.3) |
156 | 157 | drapper (0.8.4) |
157 | 158 | email_spec (1.2.1) |
... | ... | @@ -225,7 +226,7 @@ GEM |
225 | 226 | omniauth (1.1.0) |
226 | 227 | hashie (~> 1.2) |
227 | 228 | rack |
228 | - orm_adapter (0.0.7) | |
229 | + orm_adapter (0.3.0) | |
229 | 230 | polyglot (0.3.3) |
230 | 231 | posix-spawn (0.3.6) |
231 | 232 | pry (0.9.9.6) |
... | ... | @@ -356,7 +357,7 @@ GEM |
356 | 357 | raindrops (~> 0.7) |
357 | 358 | vegas (0.1.11) |
358 | 359 | rack (>= 1.0.0) |
359 | - warden (1.2.0) | |
360 | + warden (1.2.1) | |
360 | 361 | rack (>= 1.0) |
361 | 362 | webmock (1.8.7) |
362 | 363 | addressable (>= 2.2.7) |
... | ... | @@ -383,7 +384,7 @@ DEPENDENCIES |
383 | 384 | colored |
384 | 385 | cucumber-rails |
385 | 386 | database_cleaner |
386 | - devise (~> 1.5) | |
387 | + devise (~> 2.1.0) | |
387 | 388 | drapper |
388 | 389 | email_spec |
389 | 390 | ffaker | ... | ... |
VERSION
6.38 KB
app/assets/javascripts/application.js
... | ... | @@ -12,6 +12,7 @@ |
12 | 12 | //= require jquery.cookie |
13 | 13 | //= require jquery.endless-scroll |
14 | 14 | //= require jquery.highlight |
15 | +//= require jquery.waitforimages | |
15 | 16 | //= require bootstrap-modal |
16 | 17 | //= require modernizr |
17 | 18 | //= require chosen-jquery |
... | ... | @@ -20,10 +21,26 @@ |
20 | 21 | //= require_tree . |
21 | 22 | |
22 | 23 | $(document).ready(function(){ |
24 | + | |
23 | 25 | $(".one_click_select").live("click", function(){ |
24 | 26 | $(this).select(); |
25 | 27 | }); |
26 | 28 | |
29 | + | |
30 | + $('body').on('ajax:complete, ajax:beforeSend, submit', 'form', function(e){ | |
31 | + var buttons = $('[type="submit"]', this); | |
32 | + switch( e.type ){ | |
33 | + case 'ajax:beforeSend': | |
34 | + case 'submit': | |
35 | + buttons.attr('disabled', 'disabled'); | |
36 | + break; | |
37 | + case ' ajax:complete': | |
38 | + default: | |
39 | + buttons.removeAttr('disabled'); | |
40 | + break; | |
41 | + } | |
42 | + }) | |
43 | + | |
27 | 44 | $(".account-box").mouseenter(showMenu); |
28 | 45 | $(".account-box").mouseleave(resetMenu); |
29 | 46 | |
... | ... | @@ -97,3 +114,8 @@ function showDiff(link) { |
97 | 114 | return _chosen.apply(this, [default_options]); |
98 | 115 | }}) |
99 | 116 | })(jQuery); |
117 | + | |
118 | + | |
119 | +function ajaxGet(url) { | |
120 | + $.ajax({type: "GET", url: url, dataType: "script"}); | |
121 | +} | ... | ... |
app/assets/javascripts/issues.js
... | ... | @@ -73,4 +73,25 @@ function issuesPage(){ |
73 | 73 | $("#milestone_id, #assignee_id, #label_name").on("change", function(){ |
74 | 74 | $(this).closest("form").submit(); |
75 | 75 | }); |
76 | + | |
77 | + $('body').on('ajax:success', '.close_issue, .reopen_issue, #new_issue', function(){ | |
78 | + var t = $(this), | |
79 | + totalIssues, | |
80 | + reopen = t.hasClass('reopen_issue'), | |
81 | + newIssue = false; | |
82 | + if( this.id == 'new_issue' ){ | |
83 | + newIssue = true; | |
84 | + } | |
85 | + $('.issue_counter, #new_issue').each(function(){ | |
86 | + var issue = $(this); | |
87 | + totalIssues = parseInt( $(this).html(), 10 ); | |
88 | + | |
89 | + if( newIssue || ( reopen && issue.closest('.main_menu').length ) ){ | |
90 | + $(this).html( totalIssues+1 ); | |
91 | + }else { | |
92 | + $(this).html( totalIssues-1 ); | |
93 | + } | |
94 | + }); | |
95 | + | |
96 | + }); | |
76 | 97 | } | ... | ... |
app/assets/javascripts/note.js
... | ... | @@ -25,11 +25,11 @@ init: |
25 | 25 | $(this).closest('li').fadeOut(); }); |
26 | 26 | |
27 | 27 | $("#new_note").live("ajax:before", function(){ |
28 | - $("#submit_note").attr("disabled", "disabled"); | |
28 | + $(".submit_note").attr("disabled", "disabled"); | |
29 | 29 | }) |
30 | 30 | |
31 | 31 | $("#new_note").live("ajax:complete", function(){ |
32 | - $("#submit_note").removeAttr("disabled"); | |
32 | + $(".submit_note").removeAttr("disabled"); | |
33 | 33 | }) |
34 | 34 | |
35 | 35 | $("#note_note").live("focus", function(){ | ... | ... |
app/assets/stylesheets/common.scss
app/assets/stylesheets/gitlab_bootstrap.scss
... | ... | @@ -202,6 +202,10 @@ a:focus { |
202 | 202 | color:$style_color; |
203 | 203 | } |
204 | 204 | |
205 | +.nav-tabs > .active > a { | |
206 | + font-weight:bold; | |
207 | +} | |
208 | + | |
205 | 209 | /** COLORS **/ |
206 | 210 | .cgray { color:gray; } |
207 | 211 | .cred { color:#D12F19; } |
... | ... | @@ -209,6 +213,7 @@ a:focus { |
209 | 213 | .cblack { color:#111; } |
210 | 214 | .cdark { color:#444 } |
211 | 215 | .cwhite { color:#fff !important } |
216 | +.bgred { background: #F2DEDE !important} | |
212 | 217 | |
213 | 218 | /** COMMON STYLES **/ |
214 | 219 | .left { |
... | ... | @@ -299,9 +304,24 @@ table.no-borders { |
299 | 304 | } |
300 | 305 | |
301 | 306 | .event_label { |
302 | - background: #FCEEC1; | |
303 | - padding: 2px 2px 0; | |
304 | - font-family: monospace; | |
307 | + @extend .label; | |
308 | + background-color: #999; | |
309 | + | |
310 | + &.pushed { | |
311 | + background-color: #3A87AD; | |
312 | + } | |
313 | + | |
314 | + &.opened { | |
315 | + background-color: #468847; | |
316 | + } | |
317 | + | |
318 | + &.closed { | |
319 | + background-color: #B94A48; | |
320 | + } | |
321 | + | |
322 | + &.merged { | |
323 | + background-color: #2A2; | |
324 | + } | |
305 | 325 | } |
306 | 326 | |
307 | 327 | img.avatar { |
... | ... | @@ -425,9 +445,10 @@ form { |
425 | 445 | */ |
426 | 446 | .ui-box { |
427 | 447 | background:#F9F9F9; |
428 | - margin-bottom: 40px; | |
448 | + margin-bottom: 25px; | |
429 | 449 | @include round-borders-all(4px); |
430 | 450 | border-color: #CCC; |
451 | + @include solid_shade; | |
431 | 452 | |
432 | 453 | ul { |
433 | 454 | margin:0; |
... | ... | @@ -443,6 +464,13 @@ form { |
443 | 464 | background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); |
444 | 465 | background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); |
445 | 466 | |
467 | + &.small { | |
468 | + line-height: 28px; | |
469 | + font-size: 14px; | |
470 | + line-height:28px; | |
471 | + text-shadow: 0 1px 1px white; | |
472 | + } | |
473 | + | |
446 | 474 | form { |
447 | 475 | padding:9px 0; |
448 | 476 | margin:0px; |
... | ... | @@ -511,6 +539,7 @@ form { |
511 | 539 | table.admin-table { |
512 | 540 | @extend .table-bordered; |
513 | 541 | @extend .zebra-striped; |
542 | + @include solid_shade; | |
514 | 543 | th { |
515 | 544 | border-color: #CCC; |
516 | 545 | border-bottom: 1px solid #bbb; |
... | ... | @@ -568,6 +597,8 @@ ul.breadcrumb { |
568 | 597 | @extend .prepend-top-20; |
569 | 598 | @extend .append-bottom-20; |
570 | 599 | border-width:1px; |
600 | + @include solid_shade; | |
601 | + | |
571 | 602 | |
572 | 603 | img { max-width: 100%; } |
573 | 604 | |
... | ... | @@ -624,13 +655,166 @@ p { |
624 | 655 | h3.page_title { |
625 | 656 | color:#456; |
626 | 657 | font-size:20px; |
627 | - font-weight: 600; | |
658 | + font-weight: normal; | |
628 | 659 | line-height: 28px; |
629 | 660 | } |
630 | 661 | |
631 | -pre.logs { | |
632 | - .log { | |
633 | - font-size:12px; | |
634 | - line-height:18px; | |
662 | +/** | |
663 | + * File content holder | |
664 | + * | |
665 | + */ | |
666 | +.file_holder { | |
667 | + border:1px solid #CCC; | |
668 | + margin-bottom:1em; | |
669 | + @include solid_shade; | |
670 | + | |
671 | + .file_title { | |
672 | + border-bottom: 1px solid #bbb; | |
673 | + background:#eee; | |
674 | + background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); | |
675 | + background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); | |
676 | + background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); | |
677 | + background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); | |
678 | + margin: 0; | |
679 | + font-weight: normal; | |
680 | + font-weight: bold; | |
681 | + text-align: left; | |
682 | + color: #666; | |
683 | + padding: 9px 10px; | |
684 | + height:18px; | |
685 | + | |
686 | + .options { | |
687 | + float:right; | |
688 | + margin-top: -5px; | |
689 | + } | |
690 | + | |
691 | + .file_name { | |
692 | + color:$style_color; | |
693 | + font-size:14px; | |
694 | + text-shadow: 0 1px 1px #fff; | |
695 | + small { | |
696 | + color:#999; | |
697 | + font-size:13px; | |
698 | + } | |
699 | + } | |
700 | + } | |
701 | + .file_content { | |
702 | + background:#fff; | |
703 | + font-size: 11px; | |
704 | + | |
705 | + &.wiki { | |
706 | + font-size: 13px; | |
707 | + code { | |
708 | + padding:0 4px; | |
709 | + } | |
710 | + padding:20px; | |
711 | + h1, h2 { | |
712 | + line-height: 46px; | |
713 | + } | |
714 | + h3, h4 { | |
715 | + line-height: 40px; | |
716 | + } | |
717 | + } | |
718 | + | |
719 | + &.image_file { | |
720 | + background:#eee; | |
721 | + text-align:center; | |
722 | + img { | |
723 | + padding:100px; | |
724 | + max-width:300px; | |
725 | + } | |
726 | + } | |
727 | + | |
728 | + &.blob_file { | |
729 | + | |
730 | + } | |
731 | + | |
732 | + /** | |
733 | + * Blame file | |
734 | + */ | |
735 | + &.blame { | |
736 | + tr { | |
737 | + border-bottom: 1px solid #eee; | |
738 | + } | |
739 | + td { | |
740 | + padding:5px; | |
741 | + } | |
742 | + .author, | |
743 | + .blame_commit { | |
744 | + background:#f5f5f5; | |
745 | + vertical-align:top; | |
746 | + } | |
747 | + .lines { | |
748 | + pre { | |
749 | + padding:0; | |
750 | + margin:0; | |
751 | + background:none; | |
752 | + border:none; | |
753 | + } | |
754 | + } | |
755 | + } | |
756 | + | |
757 | + &.logs { | |
758 | + background:#eee; | |
759 | + max-height: 700px; | |
760 | + overflow-y: auto; | |
761 | + | |
762 | + ol { | |
763 | + margin-left:40px; | |
764 | + padding: 10px 0; | |
765 | + border-left: 1px solid #CCC; | |
766 | + margin-bottom:0; | |
767 | + background: white; | |
768 | + li { | |
769 | + color:#888; | |
770 | + p { | |
771 | + margin:0; | |
772 | + color:#333; | |
773 | + line-height:24px; | |
774 | + padding-left: 10px; | |
775 | + } | |
776 | + | |
777 | + &:hover { | |
778 | + background:$hover; | |
779 | + } | |
780 | + } | |
781 | + } | |
782 | + } | |
783 | + | |
784 | + /** | |
785 | + * Code file | |
786 | + */ | |
787 | + &.code { | |
788 | + padding:0; | |
789 | + td.code { | |
790 | + width: 100%; | |
791 | + .highlight { | |
792 | + margin-left: 55px; | |
793 | + overflow:auto; | |
794 | + overflow-y:hidden; | |
795 | + } | |
796 | + } | |
797 | + .highlight pre { | |
798 | + white-space: pre; | |
799 | + word-wrap:normal; | |
800 | + } | |
801 | + | |
802 | + table.highlighttable { | |
803 | + border: none; | |
804 | + } | |
805 | + body.project-page table.highlighttable td { border: none } | |
806 | + table.highlighttable tr:hover { background:none;} | |
807 | + | |
808 | + table.highlighttable pre{ | |
809 | + line-height:16px !important; | |
810 | + font-size:12px !important; | |
811 | + } | |
812 | + | |
813 | + table.highlighttable .linenodiv pre { | |
814 | + text-align: right; | |
815 | + padding-right: 4px; | |
816 | + color:#666; | |
817 | + } | |
818 | + } | |
635 | 819 | } |
636 | 820 | } | ... | ... |
app/assets/stylesheets/header.scss
... | ... | @@ -96,7 +96,7 @@ header { |
96 | 96 | */ |
97 | 97 | .search { |
98 | 98 | float: right; |
99 | - margin-right: 55px; | |
99 | + margin-right: 50px; | |
100 | 100 | |
101 | 101 | .search-input { |
102 | 102 | @extend .span2; |
... | ... | @@ -126,10 +126,10 @@ header { |
126 | 126 | cursor: pointer; |
127 | 127 | img { |
128 | 128 | border-radius: 4px; |
129 | - right: 0px; | |
129 | + right: 5px; | |
130 | 130 | position: absolute; |
131 | - width: 33px; | |
132 | - height: 33px; | |
131 | + width: 31px; | |
132 | + height: 31px; | |
133 | 133 | display: block; |
134 | 134 | top: 0; |
135 | 135 | &:after { | ... | ... |
app/assets/stylesheets/main.scss
... | ... | @@ -31,6 +31,12 @@ $hover: #FDF5D9; |
31 | 31 | box-shadow: 0 0 3px #ddd; |
32 | 32 | } |
33 | 33 | |
34 | +@mixin solid_shade { | |
35 | + -moz-box-shadow: 0 0 0 3px #eee; | |
36 | + -webkit-box-shadow: 0 0 0 3px #eee; | |
37 | + box-shadow: 0 0 0 3px #eee; | |
38 | +} | |
39 | + | |
34 | 40 | @mixin border-radius($radius) { |
35 | 41 | -moz-border-radius: $radius; |
36 | 42 | -webkit-border-radius: $radius; |
... | ... | @@ -136,7 +142,7 @@ $hover: #FDF5D9; |
136 | 142 | /** |
137 | 143 | * Code (files list) styles. Browsing project files there |
138 | 144 | */ |
139 | -@import "tree.scss"; | |
145 | +@import "sections/tree.scss"; | |
140 | 146 | |
141 | 147 | /** |
142 | 148 | * This file represent notes(comments) styles | ... | ... |
app/assets/stylesheets/notes.scss
... | ... | @@ -63,18 +63,22 @@ p.notify_controls span{ |
63 | 63 | |
64 | 64 | tr.line_notes_row { |
65 | 65 | border-bottom:1px solid #DDD; |
66 | + border-left: 7px solid #2A79A3; | |
67 | + | |
66 | 68 | &.reply { |
67 | 69 | background:#eee; |
68 | - | |
70 | + border-left: 7px solid #2A79A3; | |
71 | + border-top:1px solid #ddd; | |
69 | 72 | td { |
70 | 73 | padding:7px 10px; |
71 | 74 | } |
72 | 75 | a.line_note_reply_link { |
73 | 76 | @include round-borders-all(4px); |
74 | - border-color:#aaa; | |
75 | - background: #bbb; | |
76 | - padding: 3px 20px; | |
77 | + padding: 3px 10px; | |
78 | + margin-left:5px; | |
77 | 79 | color: white; |
80 | + background: #2A79A3; | |
81 | + border-color: #2A79A3; | |
78 | 82 | } |
79 | 83 | } |
80 | 84 | ul { |
... | ... | @@ -95,6 +99,9 @@ tr.line_notes_row { |
95 | 99 | td { |
96 | 100 | border-bottom:1px solid #ddd; |
97 | 101 | } |
102 | + .actions { | |
103 | + margin:0; | |
104 | + } | |
98 | 105 | } |
99 | 106 | |
100 | 107 | td .line_note_link { | ... | ... |
app/assets/stylesheets/sections/commits.scss
... | ... | @@ -101,18 +101,21 @@ |
101 | 101 | margin:50px; |
102 | 102 | padding:1px; |
103 | 103 | max-width:400px; |
104 | - } | |
105 | - &.diff_image_removed { | |
106 | - img { | |
104 | + | |
105 | + &.diff_image_removed { | |
107 | 106 | border: 1px solid #C00; |
108 | 107 | } |
109 | - } | |
110 | 108 | |
111 | - &.diff_image_added { | |
112 | - img { | |
109 | + &.diff_image_added { | |
113 | 110 | border: 1px solid #0C0;; |
114 | 111 | } |
115 | 112 | } |
113 | + | |
114 | + &.img_compared { | |
115 | + img { | |
116 | + max-width:300px; | |
117 | + } | |
118 | + } | |
116 | 119 | } |
117 | 120 | } |
118 | 121 | ... | ... |
app/assets/stylesheets/sections/merge_requests.scss
... | ... | @@ -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 | -#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 @@ |
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 @@ |
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 @@ |
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 @@ |
1 | +class Admin::HooksController < ApplicationController | |
2 | + layout "admin" | |
3 | + before_filter :authenticate_user! | |
4 | + before_filter :authenticate_admin! | |
5 | + | |
6 | + def index | |
7 | + @hooks = SystemHook.all | |
8 | + @hook = SystemHook.new | |
9 | + end | |
10 | + | |
11 | + def create | |
12 | + @hook = SystemHook.new(params[:hook]) | |
13 | + | |
14 | + if @hook.save | |
15 | + redirect_to admin_hooks_path, notice: 'Hook was successfully created.' | |
16 | + else | |
17 | + @hooks = SystemHook.all | |
18 | + render :index | |
19 | + end | |
20 | + end | |
21 | + | |
22 | + def destroy | |
23 | + @hook = SystemHook.find(params[:id]) | |
24 | + @hook.destroy | |
25 | + | |
26 | + redirect_to admin_hooks_path | |
27 | + end | |
28 | + | |
29 | + | |
30 | + def test | |
31 | + @hook = SystemHook.find(params[:hook_id]) | |
32 | + data = { | |
33 | + event_name: "project_create", | |
34 | + name: "Ruby", | |
35 | + path: "ruby", | |
36 | + project_id: 1, | |
37 | + owner_name: "Someone", | |
38 | + owner_email: "example@gitlabhq.com" | |
39 | + } | |
40 | + @hook.execute(data) | |
41 | + | |
42 | + redirect_to :back | |
43 | + end | |
44 | +end | ... | ... |
app/controllers/admin/mailer_controller.rb
... | ... | @@ -1,45 +0,0 @@ |
1 | -class Admin::MailerController < ApplicationController | |
2 | - layout "admin" | |
3 | - before_filter :authenticate_user! | |
4 | - before_filter :authenticate_admin! | |
5 | - | |
6 | - def preview | |
7 | - | |
8 | - end | |
9 | - | |
10 | - def preview_note | |
11 | - @note = Note.first | |
12 | - @user = @note.author | |
13 | - @project = @note.project | |
14 | - case params[:type] | |
15 | - when "Commit" then | |
16 | - @commit = @project.commit | |
17 | - render :file => 'notify/note_commit_email', :layout => 'notify' | |
18 | - when "Issue" then | |
19 | - @issue = Issue.first | |
20 | - render :file => 'notify/note_issue_email', :layout => 'notify' | |
21 | - else | |
22 | - render :file => 'notify/note_wall_email', :layout => 'notify' | |
23 | - end | |
24 | - rescue | |
25 | - render :text => "Preview not available" | |
26 | - end | |
27 | - | |
28 | - def preview_user_new | |
29 | - @user = User.first | |
30 | - @password = "DHasJKDHAS!" | |
31 | - | |
32 | - render :file => 'notify/new_user_email', :layout => 'notify' | |
33 | - rescue | |
34 | - render :text => "Preview not available" | |
35 | - end | |
36 | - | |
37 | - def preview_issue_new | |
38 | - @issue = Issue.first | |
39 | - @user = @issue.assignee | |
40 | - @project = @issue.project | |
41 | - render :file => 'notify/new_issue_email', :layout => 'notify' | |
42 | - rescue | |
43 | - render :text => "Preview not available" | |
44 | - end | |
45 | -end |
app/controllers/admin/projects_controller.rb
... | ... | @@ -6,7 +6,7 @@ class Admin::ProjectsController < ApplicationController |
6 | 6 | def index |
7 | 7 | @admin_projects = Project.scoped |
8 | 8 | @admin_projects = @admin_projects.search(params[:name]) if params[:name].present? |
9 | - @admin_projects = @admin_projects.page(params[:page]) | |
9 | + @admin_projects = @admin_projects.page(params[:page]).per(20) | |
10 | 10 | end |
11 | 11 | |
12 | 12 | def show |
... | ... | @@ -72,6 +72,6 @@ class Admin::ProjectsController < ApplicationController |
72 | 72 | @admin_project = Project.find_by_code(params[:id]) |
73 | 73 | @admin_project.destroy |
74 | 74 | |
75 | - redirect_to admin_projects_url | |
75 | + redirect_to admin_projects_url, notice: 'Project was successfully deleted.' | |
76 | 76 | end |
77 | 77 | end | ... | ... |
app/controllers/application_controller.rb
app/controllers/commits_controller.rb
... | ... | @@ -26,43 +26,31 @@ class CommitsController < ApplicationController |
26 | 26 | end |
27 | 27 | |
28 | 28 | def show |
29 | - @commit = project.commit(params[:id]) | |
30 | - | |
31 | - git_not_found! and return unless @commit | |
32 | - | |
33 | - @commit = CommitDecorator.decorate(@commit) | |
34 | - | |
35 | - @note = @project.build_commit_note(@commit) | |
36 | - @comments_allowed = true | |
37 | - @line_notes = project.commit_line_notes(@commit) | |
38 | - | |
39 | - @notes_count = @line_notes.count + project.commit_notes(@commit).count | |
40 | - | |
41 | - if @commit.diffs.size > 200 && !params[:force_show_diff] | |
42 | - @suppress_diff = true | |
29 | + result = CommitLoad.new(project, current_user, params).execute | |
30 | + | |
31 | + @commit = result[:commit] | |
32 | + | |
33 | + if @commit | |
34 | + @suppress_diff = result[:suppress_diff] | |
35 | + @note = result[:note] | |
36 | + @line_notes = result[:line_notes] | |
37 | + @notes_count = result[:notes_count] | |
38 | + @comments_allowed = true | |
39 | + else | |
40 | + return git_not_found! | |
43 | 41 | end |
42 | + | |
44 | 43 | rescue Grit::Git::GitTimeout |
45 | 44 | render "huge_commit" |
46 | 45 | end |
47 | 46 | |
48 | 47 | def compare |
49 | - first = project.commit(params[:to].try(:strip)) | |
50 | - last = project.commit(params[:from].try(:strip)) | |
48 | + result = Commit.compare(project, params[:from], params[:to]) | |
51 | 49 | |
52 | - @diffs = [] | |
53 | - @commits = [] | |
50 | + @commits = result[:commits] | |
51 | + @commit = result[:commit] | |
52 | + @diffs = result[:diffs] | |
54 | 53 | @line_notes = [] |
55 | - | |
56 | - if first && last | |
57 | - commits = [first, last].sort_by(&:created_at) | |
58 | - younger = commits.first | |
59 | - older = commits.last | |
60 | - | |
61 | - | |
62 | - @commits = project.repo.commits_between(younger.id, older.id).map {|c| Commit.new(c)} | |
63 | - @diffs = project.repo.diff(younger.id, older.id) rescue [] | |
64 | - @commit = Commit.new(older) | |
65 | - end | |
66 | 54 | end |
67 | 55 | |
68 | 56 | def patch | ... | ... |
app/controllers/dashboard_controller.rb
... | ... | @@ -2,15 +2,13 @@ class DashboardController < ApplicationController |
2 | 2 | respond_to :html |
3 | 3 | |
4 | 4 | def index |
5 | - @projects = current_user.projects.includes(:events).order("events.created_at DESC") | |
6 | - @projects = @projects.page(params[:page]).per(40) | |
7 | - | |
8 | - @events = Event.where(:project_id => current_user.projects.map(&:id)).recent.limit(20) | |
9 | - | |
5 | + @projects = current_user.projects_with_events.page(params[:page]).per(40) | |
6 | + @events = Event.recent_for_user(current_user).limit(20).offset(params[:offset] || 0) | |
10 | 7 | @last_push = current_user.recent_push |
11 | 8 | |
12 | 9 | respond_to do |format| |
13 | 10 | format.html |
11 | + format.js | |
14 | 12 | format.atom { render :layout => false } |
15 | 13 | end |
16 | 14 | end | ... | ... |
app/controllers/hooks_controller.rb
... | ... | @@ -11,24 +11,24 @@ class HooksController < ApplicationController |
11 | 11 | respond_to :html |
12 | 12 | |
13 | 13 | def index |
14 | - @hooks = @project.web_hooks.all | |
15 | - @hook = WebHook.new | |
14 | + @hooks = @project.hooks.all | |
15 | + @hook = ProjectHook.new | |
16 | 16 | end |
17 | 17 | |
18 | 18 | def create |
19 | - @hook = @project.web_hooks.new(params[:hook]) | |
19 | + @hook = @project.hooks.new(params[:hook]) | |
20 | 20 | @hook.save |
21 | 21 | |
22 | 22 | if @hook.valid? |
23 | 23 | redirect_to project_hooks_path(@project) |
24 | 24 | else |
25 | - @hooks = @project.web_hooks.all | |
25 | + @hooks = @project.hooks.all | |
26 | 26 | render :index |
27 | 27 | end |
28 | 28 | end |
29 | 29 | |
30 | 30 | def test |
31 | - @hook = @project.web_hooks.find(params[:id]) | |
31 | + @hook = @project.hooks.find(params[:id]) | |
32 | 32 | commits = @project.commits(@project.default_branch, nil, 3) |
33 | 33 | data = @project.post_receive_data(commits.last.id, commits.first.id, "refs/heads/#{@project.default_branch}", current_user) |
34 | 34 | @hook.execute(data) |
... | ... | @@ -37,7 +37,7 @@ class HooksController < ApplicationController |
37 | 37 | end |
38 | 38 | |
39 | 39 | def destroy |
40 | - @hook = @project.web_hooks.find(params[:id]) | |
40 | + @hook = @project.hooks.find(params[:id]) | |
41 | 41 | @hook.destroy |
42 | 42 | |
43 | 43 | redirect_to project_hooks_path(@project) | ... | ... |
app/controllers/merge_requests_controller.rb
... | ... | @@ -24,16 +24,7 @@ class MergeRequestsController < ApplicationController |
24 | 24 | |
25 | 25 | |
26 | 26 | def index |
27 | - @merge_requests = @project.merge_requests | |
28 | - | |
29 | - @merge_requests = case params[:f].to_i | |
30 | - when 1 then @merge_requests | |
31 | - when 2 then @merge_requests.closed | |
32 | - when 3 then @merge_requests.opened.assigned(current_user) | |
33 | - else @merge_requests.opened | |
34 | - end.page(params[:page]).per(20) | |
35 | - | |
36 | - @merge_requests = @merge_requests.includes(:author, :project).order("closed, created_at desc") | |
27 | + @merge_requests = MergeRequestsLoad.new(project, current_user, params).execute | |
37 | 28 | end |
38 | 29 | |
39 | 30 | def show | ... | ... |
app/controllers/notes_controller.rb
... | ... | @@ -40,25 +40,6 @@ class NotesController < ApplicationController |
40 | 40 | protected |
41 | 41 | |
42 | 42 | def notes |
43 | - @notes = case params[:target_type] | |
44 | - when "commit" | |
45 | - then project.commit_notes(project.commit((params[:target_id]))).fresh.limit(20) | |
46 | - when "snippet" | |
47 | - then project.snippets.find(params[:target_id]).notes | |
48 | - when "wall" | |
49 | - then project.common_notes.order("created_at DESC").fresh.limit(50) | |
50 | - when "issue" | |
51 | - then project.issues.find(params[:target_id]).notes.inc_author.order("created_at DESC").limit(20) | |
52 | - when "merge_request" | |
53 | - then project.merge_requests.find(params[:target_id]).notes.inc_author.order("created_at DESC").limit(20) | |
54 | - end | |
55 | - | |
56 | - @notes = if params[:last_id] | |
57 | - @notes.where("id > ?", params[:last_id]) | |
58 | - elsif params[:first_id] | |
59 | - @notes.where("id < ?", params[:first_id]) | |
60 | - else | |
61 | - @notes | |
62 | - end | |
43 | + @notes = NotesLoad.new(project, current_user, params).execute | |
63 | 44 | end |
64 | 45 | end | ... | ... |
app/controllers/omniauth_callbacks_controller.rb
1 | 1 | class OmniauthCallbacksController < Devise::OmniauthCallbacksController |
2 | + | |
3 | + # Extend the standard message generation to accept our custom exception | |
4 | + def failure_message | |
5 | + exception = env["omniauth.error"] | |
6 | + if exception.class == OmniAuth::Error | |
7 | + error = exception.message | |
8 | + else | |
9 | + error = exception.error_reason if exception.respond_to?(:error_reason) | |
10 | + error ||= exception.error if exception.respond_to?(:error) | |
11 | + error ||= env["omniauth.error.type"].to_s | |
12 | + end | |
13 | + error.to_s.humanize if error | |
14 | + end | |
2 | 15 | |
3 | 16 | def ldap |
4 | 17 | # We only find ourselves here if the authentication to LDAP was successful. | ... | ... |
app/controllers/refs_controller.rb
... | ... | @@ -9,7 +9,7 @@ class RefsController < ApplicationController |
9 | 9 | before_filter :require_non_empty_project |
10 | 10 | |
11 | 11 | before_filter :ref |
12 | - before_filter :define_tree_vars, :only => [:tree, :blob, :blame] | |
12 | + before_filter :define_tree_vars, :only => [:tree, :blob, :blame, :logs_tree] | |
13 | 13 | before_filter :render_full_content |
14 | 14 | |
15 | 15 | layout "project" |
... | ... | @@ -46,6 +46,18 @@ class RefsController < ApplicationController |
46 | 46 | end |
47 | 47 | end |
48 | 48 | |
49 | + def logs_tree | |
50 | + contents = @tree.contents | |
51 | + @logs = contents.map do |content| | |
52 | + file = params[:path] ? File.join(params[:path], content.name) : content.name | |
53 | + last_commit = @project.commits(@commit.id, file, 1).last | |
54 | + { | |
55 | + :file_name => content.name, | |
56 | + :commit => last_commit | |
57 | + } | |
58 | + end | |
59 | + end | |
60 | + | |
49 | 61 | def blob |
50 | 62 | if @tree.is_blob? |
51 | 63 | if @tree.text? |
... | ... | @@ -79,6 +91,15 @@ class RefsController < ApplicationController |
79 | 91 | @commit = project.commit(@ref) |
80 | 92 | @tree = Tree.new(@commit.tree, project, @ref, params[:path]) |
81 | 93 | @tree = TreeDecorator.new(@tree) |
94 | + @hex_path = Digest::SHA1.hexdigest(params[:path] || "/") | |
95 | + | |
96 | + if params[:path] | |
97 | + @history_path = tree_file_project_ref_path(@project, @ref, params[:path]) | |
98 | + @logs_path = logs_file_project_ref_path(@project, @ref, params[:path]) | |
99 | + else | |
100 | + @history_path = tree_project_ref_path(@project, @ref) | |
101 | + @logs_path = logs_tree_project_ref_path(@project, @ref) | |
102 | + end | |
82 | 103 | rescue |
83 | 104 | return render_404 |
84 | 105 | end | ... | ... |
... | ... | @@ -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 @@ |
1 | +module TreeHelper | |
2 | + def tree_icon(content) | |
3 | + if content.is_a?(Grit::Blob) | |
4 | + if content.text? | |
5 | + image_tag "file_txt.png" | |
6 | + elsif content.image? | |
7 | + image_tag "file_img.png" | |
8 | + else | |
9 | + image_tag "file_bin.png" | |
10 | + end | |
11 | + else | |
12 | + image_tag "file_dir.png" | |
13 | + end | |
14 | + end | |
15 | + | |
16 | + def tree_hex_class(content) | |
17 | + "file_#{hexdigest(content.name)}" | |
18 | + end | |
19 | + | |
20 | + def tree_full_path(content) | |
21 | + if params[:path] | |
22 | + File.join(params[:path], content.name) | |
23 | + else | |
24 | + content.name | |
25 | + end | |
26 | + end | |
27 | +end | ... | ... |
app/models/commit.rb
... | ... | @@ -80,6 +80,29 @@ class Commit |
80 | 80 | def commits_between(repo, from, to) |
81 | 81 | repo.commits_between(from, to).map { |c| Commit.new(c) } |
82 | 82 | end |
83 | + | |
84 | + def compare(project, from, to) | |
85 | + first = project.commit(to.try(:strip)) | |
86 | + last = project.commit(from.try(:strip)) | |
87 | + | |
88 | + result = { | |
89 | + :commits => [], | |
90 | + :diffs => [], | |
91 | + :commit => nil | |
92 | + } | |
93 | + | |
94 | + if first && last | |
95 | + commits = [first, last].sort_by(&:created_at) | |
96 | + younger = commits.first | |
97 | + older = commits.last | |
98 | + | |
99 | + result[:commits] = project.repo.commits_between(younger.id, older.id).map {|c| Commit.new(c)} | |
100 | + result[:diffs] = project.repo.diff(younger.id, older.id) rescue [] | |
101 | + result[:commit] = Commit.new(older) | |
102 | + end | |
103 | + | |
104 | + result | |
105 | + end | |
83 | 106 | end |
84 | 107 | |
85 | 108 | def persisted? | ... | ... |
app/models/event.rb
app/models/merge_request.rb
... | ... | @@ -22,7 +22,6 @@ class MergeRequest < ActiveRecord::Base |
22 | 22 | :should_remove_source_branch |
23 | 23 | |
24 | 24 | validates_presence_of :project_id |
25 | - validates_presence_of :assignee_id | |
26 | 25 | validates_presence_of :author_id |
27 | 26 | validates_presence_of :source_branch |
28 | 27 | validates_presence_of :target_branch |
... | ... | @@ -36,6 +35,7 @@ class MergeRequest < ActiveRecord::Base |
36 | 35 | delegate :name, |
37 | 36 | :email, |
38 | 37 | :to => :assignee, |
38 | + :allow_nil => true, | |
39 | 39 | :prefix => true |
40 | 40 | |
41 | 41 | validates :title, |
... | ... | @@ -128,7 +128,7 @@ class MergeRequest < ActiveRecord::Base |
128 | 128 | |
129 | 129 | def unmerged_diffs |
130 | 130 | commits = project.repo.commits_between(target_branch, source_branch).map {|c| Commit.new(c)} |
131 | - diffs = project.repo.diff(commits.first.prev_commit.id, commits.last.id) | |
131 | + diffs = project.repo.diff(commits.first.prev_commit.id, commits.last.id) rescue [] | |
132 | 132 | end |
133 | 133 | |
134 | 134 | def last_commit | ... | ... |
app/models/project.rb
... | ... | @@ -19,7 +19,7 @@ class Project < ActiveRecord::Base |
19 | 19 | has_many :notes, :dependent => :destroy |
20 | 20 | has_many :snippets, :dependent => :destroy |
21 | 21 | has_many :deploy_keys, :dependent => :destroy, :foreign_key => "project_id", :class_name => "Key" |
22 | - has_many :web_hooks, :dependent => :destroy | |
22 | + has_many :hooks, :dependent => :destroy, :class_name => "ProjectHook" | |
23 | 23 | has_many :wikis, :dependent => :destroy |
24 | 24 | has_many :protected_branches, :dependent => :destroy |
25 | 25 | |
... | ... | @@ -120,7 +120,7 @@ class Project < ActiveRecord::Base |
120 | 120 | errors.add(:path, " like 'gitolite-admin' is not allowed") |
121 | 121 | end |
122 | 122 | end |
123 | - | |
123 | + | |
124 | 124 | def self.access_options |
125 | 125 | UsersProject.access_roles |
126 | 126 | end | ... | ... |
app/models/user.rb
1 | 1 | class User < ActiveRecord::Base |
2 | + | |
2 | 3 | include Account |
3 | 4 | |
4 | - devise :database_authenticatable, :token_authenticatable, | |
5 | + devise :database_authenticatable, :token_authenticatable, :lockable, | |
5 | 6 | :recoverable, :rememberable, :trackable, :validatable, :omniauthable |
6 | 7 | |
7 | 8 | attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, |
8 | - :name, :projects_limit, :skype, :linkedin, :twitter, :dark_scheme, | |
9 | + :name, :projects_limit, :skype, :linkedin, :twitter, :dark_scheme, | |
9 | 10 | :theme_id, :force_random_password |
10 | 11 | |
11 | 12 | attr_accessor :force_random_password |
... | ... | @@ -15,6 +16,11 @@ class User < ActiveRecord::Base |
15 | 16 | has_many :my_own_projects, :class_name => "Project", :foreign_key => :owner_id |
16 | 17 | has_many :keys, :dependent => :destroy |
17 | 18 | |
19 | + has_many :events, | |
20 | + :class_name => "Event", | |
21 | + :foreign_key => :author_id, | |
22 | + :dependent => :destroy | |
23 | + | |
18 | 24 | has_many :recent_events, |
19 | 25 | :class_name => "Event", |
20 | 26 | :foreign_key => :author_id, |
... | ... | @@ -80,7 +86,8 @@ class User < ActiveRecord::Base |
80 | 86 | |
81 | 87 | def self.find_for_ldap_auth(omniauth_info) |
82 | 88 | name = omniauth_info.name.force_encoding("utf-8") |
83 | - email = omniauth_info.email.downcase | |
89 | + email = omniauth_info.email.downcase unless omniauth_info.email.nil? | |
90 | + raise OmniAuth::Error, "LDAP accounts must provide an email address" if email.nil? | |
84 | 91 | |
85 | 92 | if @user = User.find_by_email(email) |
86 | 93 | @user | ... | ... |
app/models/users_project.rb
app/models/web_hook.rb
... | ... | @@ -4,8 +4,6 @@ class WebHook < ActiveRecord::Base |
4 | 4 | # HTTParty timeout |
5 | 5 | default_timeout 10 |
6 | 6 | |
7 | - belongs_to :project | |
8 | - | |
9 | 7 | validates :url, |
10 | 8 | presence: true, |
11 | 9 | format: { |
... | ... | @@ -14,9 +12,8 @@ class WebHook < ActiveRecord::Base |
14 | 12 | |
15 | 13 | def execute(data) |
16 | 14 | WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }) |
17 | - rescue | |
18 | - # There was a problem calling this web hook, let's forget about it. | |
19 | 15 | end |
16 | + | |
20 | 17 | end |
21 | 18 | # == Schema Information |
22 | 19 | # | ... | ... |
app/observers/mailer_observer.rb
... | ... | @@ -43,7 +43,7 @@ class MailerObserver < ActiveRecord::Observer |
43 | 43 | end |
44 | 44 | |
45 | 45 | def new_merge_request(merge_request) |
46 | - if merge_request.assignee != current_user | |
46 | + if merge_request.assignee && merge_request.assignee != current_user | |
47 | 47 | Notify.new_merge_request_email(merge_request.id).deliver |
48 | 48 | end |
49 | 49 | end | ... | ... |
... | ... | @@ -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
app/roles/git_push.rb
... | ... | @@ -27,7 +27,7 @@ module GitPush |
27 | 27 | true |
28 | 28 | end |
29 | 29 | |
30 | - def execute_web_hooks(oldrev, newrev, ref, user) | |
30 | + def execute_hooks(oldrev, newrev, ref, user) | |
31 | 31 | ref_parts = ref.split('/') |
32 | 32 | |
33 | 33 | # Return if this is not a push to a branch (e.g. new commits) |
... | ... | @@ -35,7 +35,7 @@ module GitPush |
35 | 35 | |
36 | 36 | data = post_receive_data(oldrev, newrev, ref, user) |
37 | 37 | |
38 | - web_hooks.each { |web_hook| web_hook.execute(data) } | |
38 | + hooks.each { |hook| hook.execute(data) } | |
39 | 39 | end |
40 | 40 | |
41 | 41 | def post_receive_data(oldrev, newrev, ref, user) |
... | ... | @@ -97,7 +97,7 @@ module GitPush |
97 | 97 | self.update_merge_requests(oldrev, newrev, ref, user) |
98 | 98 | |
99 | 99 | # Execute web hooks |
100 | - self.execute_web_hooks(oldrev, newrev, ref, user) | |
100 | + self.execute_hooks(oldrev, newrev, ref, user) | |
101 | 101 | |
102 | 102 | # Create satellite |
103 | 103 | self.satellite.create unless self.satellite.exists? | ... | ... |
... | ... | @@ -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 @@ |
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
1 | -%h4 | |
2 | - %i.icon-file | |
3 | - githost.log | |
4 | -%pre.logs | |
5 | - - Gitlab::Logger.read_latest.each do |line| | |
6 | - %span.log= line | |
1 | +.file_holder#README | |
2 | + .file_title | |
3 | + %i.icon-file | |
4 | + githost.log | |
5 | + .file_content.logs | |
6 | + %ol | |
7 | + - Gitlab::Logger.read_latest.each do |line| | |
8 | + %li | |
9 | + %p= line | ... | ... |
app/views/admin/mailer/preview.html.haml
... | ... | @@ -1,28 +0,0 @@ |
1 | -%p This is page with preview for all system emails that are sent to user | |
2 | -%p Email previews built based on existing Project/Commit/Issue base - so some preview maybe unavailable unless object appear in system | |
3 | - | |
4 | -#accordion | |
5 | - %h3 | |
6 | - %a New user | |
7 | - %div | |
8 | - %iframe{ :src=> admin_mailer_preview_user_new_path, :width=>"100%", :height=>"350"} | |
9 | - %h3 | |
10 | - %a New issue | |
11 | - %div | |
12 | - %iframe{ :src=> admin_mailer_preview_issue_new_path, :width=>"100%", :height=>"350"} | |
13 | - %h3 | |
14 | - %a Commit note | |
15 | - %div | |
16 | - %iframe{ :src=> admin_mailer_preview_note_path(:type => "Commit"), :width=>"100%", :height=>"350"} | |
17 | - %h3 | |
18 | - %a Issue note | |
19 | - %div | |
20 | - %iframe{ :src=> admin_mailer_preview_note_path(:type => "Issue"), :width=>"100%", :height=>"350"} | |
21 | - %h3 | |
22 | - %a Wall note | |
23 | - %div | |
24 | - %iframe{ :src=> admin_mailer_preview_note_path(:type => "Wall"), :width=>"100%", :height=>"350"} | |
25 | - | |
26 | -:javascript | |
27 | - $(function() { | |
28 | - $("#accordion").accordion(); }); |
app/views/admin/projects/index.html.haml
... | ... | @@ -13,8 +13,8 @@ |
13 | 13 | %th Team Members |
14 | 14 | %th Post Receive |
15 | 15 | %th Last Commit |
16 | - %th | |
17 | - %th | |
16 | + %th Edit | |
17 | + %th.cred Danger Zone! | |
18 | 18 | |
19 | 19 | - @admin_projects.each do |project| |
20 | 20 | %tr |
... | ... | @@ -24,5 +24,5 @@ |
24 | 24 | %td= check_box_tag :post_receive_file, 1, project.has_post_receive_file?, :disabled => true |
25 | 25 | %td= last_commit(project) |
26 | 26 | %td= link_to 'Edit', edit_admin_project_path(project), :id => "edit_#{dom_id(project)}", :class => "btn small" |
27 | - %td= link_to 'Destroy', [:admin, project], :confirm => 'Are you sure?', :method => :delete, :class => "btn small danger" | |
27 | + %td.bgred= link_to 'Destroy', [:admin, project], :confirm => "REMOVE #{project.name}? Are you sure?", :method => :delete, :class => "btn small danger" | |
28 | 28 | = paginate @admin_projects, :theme => "admin" | ... | ... |
app/views/admin/users/_form.html.haml
... | ... | @@ -50,7 +50,7 @@ |
50 | 50 | |
51 | 51 | .alert |
52 | 52 | .clearfix |
53 | - %p Give user ability to manage application. | |
53 | + %p Make the user a GitLab administrator. | |
54 | 54 | = f.label :admin, :class => "checkbox" do |
55 | 55 | = f.check_box :admin |
56 | 56 | %span Administrator |
... | ... | @@ -59,11 +59,11 @@ |
59 | 59 | - if @admin_user.blocked |
60 | 60 | %span |
61 | 61 | = link_to 'Unblock', unblock_admin_user_path(@admin_user), :method => :put, :class => "btn small" |
62 | - This user is blocked and is not able to login GitLab | |
62 | + This user is blocked and is not able to login to GitLab | |
63 | 63 | - else |
64 | 64 | %span |
65 | 65 | = link_to 'Block', block_admin_user_path(@admin_user), :confirm => 'USER WILL BE BLOCKED! Are you sure?', :method => :put, :class => "btn small danger" |
66 | - Blocked user will removed from all projects & 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 | 67 | .actions |
68 | 68 | = f.submit 'Save', :class => "btn primary" |
69 | 69 | - if @admin_user.new_record? | ... | ... |
app/views/admin/users/index.html.haml
... | ... | @@ -27,7 +27,7 @@ |
27 | 27 | %th Projects |
28 | 28 | %th Edit |
29 | 29 | %th Blocked |
30 | - %th | |
30 | + %th.cred Danger Zone! | |
31 | 31 | |
32 | 32 | - @admin_users.each do |user| |
33 | 33 | %tr |
... | ... | @@ -41,6 +41,6 @@ |
41 | 41 | = link_to 'Unblock', unblock_admin_user_path(user), :method => :put, :class => "btn small success" |
42 | 42 | - else |
43 | 43 | = link_to 'Block', block_admin_user_path(user), :confirm => 'USER WILL BE BLOCKED! Are you sure?', :method => :put, :class => "btn small danger" |
44 | - %td= link_to 'Destroy', [:admin, user], :confirm => 'USER WILL BE REMOVED! Are you sure?', :method => :delete, :class => "btn small danger" | |
44 | + %td.bgred= link_to 'Destroy', [:admin, user], :confirm => "USER #{user.name} WILL BE REMOVED! Are you sure?", :method => :delete, :class => "btn small danger" | |
45 | 45 | |
46 | 46 | = paginate @admin_users, :theme => "admin" | ... | ... |
app/views/commits/_commits.html.haml
app/views/commits/_diffs.html.haml
... | ... | @@ -35,7 +35,13 @@ |
35 | 35 | - if file.text? |
36 | 36 | = render "commits/text_file", :diff => diff, :index => i |
37 | 37 | - elsif file.image? |
38 | - .diff_file_content_image{:class => image_diff_class(diff)} | |
39 | - %img{:src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} | |
38 | + - if diff.renamed_file || diff.new_file || diff.deleted_file | |
39 | + .diff_file_content_image | |
40 | + %img{:class => image_diff_class(diff), :src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} | |
41 | + - else | |
42 | + - old_file = (@commit.prev_commit.tree / diff.old_path) | |
43 | + .diff_file_content_image.img_compared | |
44 | + %img{:class => "diff_image_removed", :src => "data:#{file.mime_type};base64,#{Base64.encode64(old_file.data)}"} | |
45 | + %img{:class => "diff_image_added", :src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} | |
40 | 46 | - else |
41 | 47 | %p.nothing_here_message No preview for this file type | ... | ... |
app/views/commits/_head.html.haml
... | ... | @@ -13,12 +13,12 @@ |
13 | 13 | %li{:class => "#{branches_tab_class}"} |
14 | 14 | = link_to project_repository_path(@project) do |
15 | 15 | Branches |
16 | - %span.number= @project.repo.branch_count | |
16 | + %span.badge= @project.repo.branch_count | |
17 | 17 | |
18 | 18 | %li{:class => "#{'active' if current_page?(tags_project_repository_path(@project)) }"} |
19 | 19 | = link_to tags_project_repository_path(@project) do |
20 | 20 | Tags |
21 | - %span.number= @project.repo.tag_count | |
21 | + %span.badge= @project.repo.tag_count | |
22 | 22 | |
23 | 23 | |
24 | 24 | - if current_page?(project_commits_path(@project)) && current_user.private_token | ... | ... |
app/views/commits/compare.html.haml
... | ... | @@ -20,7 +20,7 @@ |
20 | 20 | = "..." |
21 | 21 | = text_field_tag :to, params[:to], :placeholder => "aa8b4ef", :class => "xlarge" |
22 | 22 | .actions |
23 | - = submit_tag "Compare", :class => "btn primary" | |
23 | + = submit_tag "Compare", :class => "btn btn-primary" | |
24 | 24 | |
25 | 25 | |
26 | 26 | - unless @commits.empty? | ... | ... |
app/views/dashboard/index.atom.builder
... | ... | @@ -8,17 +8,10 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear |
8 | 8 | |
9 | 9 | @events.each do |event| |
10 | 10 | if event.allowed? |
11 | + event = EventDecorator.decorate(event) | |
11 | 12 | xml.entry do |
12 | - if event.issue? | |
13 | - event_link = project_issue_url(event.project, event.issue) | |
14 | - event_title = event.issue_title | |
15 | - elsif event.merge_request? | |
16 | - event_link = project_merge_request_url(event.project, event.merge_request) | |
17 | - event_title = event.merge_request_title | |
18 | - elsif event.push? | |
19 | - event_link = project_commits_url(event.project, :ref => event.ref_name) | |
20 | - event_title = event.ref_name | |
21 | - end | |
13 | + event_link = event.feed_url | |
14 | + event_title = event.feed_title | |
22 | 15 | |
23 | 16 | xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}" |
24 | 17 | xml.link :href => event_link | ... | ... |
app/views/dashboard/index.html.haml
... | ... | @@ -10,9 +10,10 @@ |
10 | 10 | add new key |
11 | 11 | to your profile |
12 | 12 | - if @events.any? |
13 | - = render @events | |
13 | + .content_list= render @events | |
14 | 14 | - else |
15 | 15 | %h4.nothing_here_message Projects activity will be displayed here |
16 | + .loading.hide | |
16 | 17 | .side |
17 | 18 | = render "events/event_last_push", :event => @last_push |
18 | 19 | .projects_box |
... | ... | @@ -54,3 +55,7 @@ |
54 | 55 | New Project » |
55 | 56 | - else |
56 | 57 | If you will be added to project - it will be displayed here |
58 | + | |
59 | + | |
60 | +:javascript | |
61 | + $(function(){ Pager.init(20); }); | ... | ... |
app/views/dashboard/index.js.haml
app/views/events/_event_issue.html.haml
1 | 1 | = image_tag gravatar_icon(event.author_email), :class => "avatar" |
2 | 2 | %strong #{event.author_name} |
3 | -%span.event_label= event.action_name | |
4 | - issue | |
3 | +%span.event_label{:class => event.action_name}= event.action_name | |
4 | +issue | |
5 | 5 | = link_to project_issue_path(event.project, event.issue) do |
6 | 6 | %strong= truncate event.issue_title |
7 | 7 | at | ... | ... |
app/views/events/_event_last_push.html.haml
... | ... | @@ -5,12 +5,9 @@ |
5 | 5 | %span Your pushed to |
6 | 6 | = event.ref_type |
7 | 7 | = link_to project_commits_path(event.project, :ref => event.ref_name) do |
8 | - %strong= event.ref_name | |
8 | + %strong= truncate(event.ref_name, :length => 28) | |
9 | 9 | at |
10 | 10 | %strong= link_to event.project.name, event.project |
11 | - %span.cgray | |
12 | - = time_ago_in_words(event.created_at) | |
13 | - ago. | |
14 | 11 | |
15 | 12 | = link_to new_mr_path_from_push_event(event), :title => "New Merge Request", :class => "btn very_small primary" do |
16 | 13 | Create Merge Request | ... | ... |
app/views/events/_event_merge_request.html.haml
... | ... | @@ -2,8 +2,8 @@ |
2 | 2 | .event_icon= image_tag "event_mr_merged.png" |
3 | 3 | = image_tag gravatar_icon(event.author_email), :class => "avatar" |
4 | 4 | %strong #{event.author_name} |
5 | -%span.event_label= event.action_name | |
6 | - merge request | |
5 | +%span.event_label{:class => event.action_name}= event.action_name | |
6 | +merge request | |
7 | 7 | = link_to project_merge_request_path(event.project, event.merge_request) do |
8 | 8 | %strong= truncate event.merge_request_title |
9 | 9 | at | ... | ... |
app/views/events/_event_push.html.haml
... | ... | @@ -2,7 +2,7 @@ |
2 | 2 | .event_icon= image_tag "event_push.png" |
3 | 3 | = image_tag gravatar_icon(event.author_email), :class => "avatar" |
4 | 4 | %strong #{event.author_name} |
5 | - %span.event_label= event.push_action_name | |
5 | + %span.event_label.pushed= event.push_action_name | |
6 | 6 | = event.ref_type |
7 | 7 | = link_to project_commits_path(event.project, :ref => event.ref_name) do |
8 | 8 | %strong= event.ref_name | ... | ... |
... | ... | @@ -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 @@ |
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 | 6 | .row |
7 | 7 | .span7= paginate @issues, :remote => true, :theme => "gitlab" |
8 | 8 | .span3.right |
9 | - %span.cgray.right #{@issues.total_count} issues for this filter | |
9 | + %span.cgray.right | |
10 | + %span.issue_counter #{@issues.total_count} | |
11 | + issues for this filter | |
10 | 12 | - else |
11 | 13 | %li |
12 | 14 | %h4.nothing_here_message Nothing to show here | ... | ... |
app/views/issues/_show.html.haml
... | ... | @@ -12,9 +12,9 @@ |
12 | 12 | = issue.notes.count |
13 | 13 | - if can? current_user, :modify_issue, issue |
14 | 14 | - if issue.closed |
15 | - = link_to 'Reopen', project_issue_path(issue.project, issue, :issue => {:closed => false }, :status_only => true), :method => :put, :class => "btn small grouped", :remote => true | |
15 | + = link_to 'Reopen', project_issue_path(issue.project, issue, :issue => {:closed => false }, :status_only => true), :method => :put, :class => "btn small grouped reopen_issue", :remote => true | |
16 | 16 | - else |
17 | - = link_to 'Resolve', project_issue_path(issue.project, issue, :issue => {:closed => true }, :status_only => true), :method => :put, :class => "success btn small grouped", :remote => true | |
17 | + = link_to 'Resolve', project_issue_path(issue.project, issue, :issue => {:closed => true }, :status_only => true), :method => :put, :class => "success btn small grouped close_issue", :remote => true | |
18 | 18 | = link_to edit_project_issue_path(issue.project, issue), :class => "btn small edit-issue-link", :remote => true do |
19 | 19 | %i.icon-edit |
20 | 20 | Edit |
... | ... | @@ -35,6 +35,4 @@ |
35 | 35 | |
36 | 36 | |
37 | 37 | - if issue.upvotes > 0 |
38 | - %span.badge.badge-success= "+#{issue.upvotes}" | |
39 | - | |
40 | - | |
38 | + %span.badge.badge-success= "+#{issue.upvotes}" | |
41 | 39 | \ No newline at end of file | ... | ... |
app/views/issues/index.html.haml
... | ... | @@ -2,7 +2,7 @@ |
2 | 2 | .issues_content |
3 | 3 | %h3.page_title |
4 | 4 | Issues |
5 | - %small (#{@issues.total_count}) | |
5 | + %small (<span class=issue_counter>#{@issues.total_count}</span>) | |
6 | 6 | .right |
7 | 7 | .span5 |
8 | 8 | - if can? current_user, :write_issue, @project |
... | ... | @@ -45,4 +45,4 @@ |
45 | 45 | :javascript |
46 | 46 | $(function(){ |
47 | 47 | issuesPage(); |
48 | 48 | - }) |
49 | + }) | |
49 | 50 | \ No newline at end of file | ... | ... |
app/views/keys/new.html.haml
app/views/layouts/_project_menu.html.haml
... | ... | @@ -17,14 +17,14 @@ |
17 | 17 | %li{:class => tab_class(:issues)} |
18 | 18 | = link_to project_issues_filter_path(@project) do |
19 | 19 | Issues |
20 | - %span.count= @project.issues.opened.count | |
20 | + %span.count.issue_counter= @project.issues.opened.count | |
21 | 21 | |
22 | 22 | - if @project.repo_exists? |
23 | 23 | - if @project.merge_requests_enabled |
24 | 24 | %li{:class => tab_class(:merge_requests)} |
25 | 25 | = link_to project_merge_requests_path(@project) do |
26 | 26 | Merge Requests |
27 | - %span.count= @project.merge_requests.opened.count | |
27 | + %span.count.merge_counter= @project.merge_requests.opened.count | |
28 | 28 | |
29 | 29 | - if @project.wall_enabled |
30 | 30 | %li{:class => tab_class(:wall)} | ... | ... |
app/views/layouts/admin.html.haml
... | ... | @@ -15,7 +15,7 @@ |
15 | 15 | %li{:class => tab_class(:admin_logs)} |
16 | 16 | = link_to "Logs", admin_logs_path |
17 | 17 | %li{:class => tab_class(:admin_emails)} |
18 | - = link_to "Emails", admin_emails_path | |
18 | + = link_to "Hooks", admin_hooks_path | |
19 | 19 | %li{:class => tab_class(:admin_resque)} |
20 | 20 | = link_to "Resque", admin_resque_path |
21 | 21 | ... | ... |
app/views/layouts/devise.html.haml
app/views/layouts/profile.html.haml
... | ... | @@ -12,16 +12,17 @@ |
12 | 12 | %li{:class => tab_class(:password)} |
13 | 13 | = link_to "Password", profile_password_path |
14 | 14 | |
15 | + %li{:class => tab_class(:ssh_keys)} | |
16 | + = link_to keys_path do | |
17 | + SSH Keys | |
18 | + %span.count= current_user.keys.count | |
19 | + | |
15 | 20 | %li{:class => tab_class(:token)} |
16 | 21 | = link_to "Token", profile_token_path |
17 | 22 | |
18 | 23 | %li{:class => tab_class(:design)} |
19 | 24 | = link_to "Design", profile_design_path |
20 | 25 | |
21 | - %li{:class => tab_class(:ssh_keys)} | |
22 | - = link_to keys_path do | |
23 | - SSH Keys | |
24 | - %span.count= current_user.keys.count | |
25 | 26 | |
26 | 27 | .content |
27 | 28 | = yield | ... | ... |
app/views/merge_requests/_form.html.haml
... | ... | @@ -5,7 +5,8 @@ |
5 | 5 | - @merge_request.errors.full_messages.each do |msg| |
6 | 6 | %li= msg |
7 | 7 | |
8 | - %h3.padded.cgray 1. Select Branches | |
8 | + %h4.cdark 1. Select Branches | |
9 | + %br | |
9 | 10 | |
10 | 11 | .row |
11 | 12 | .span6 |
... | ... | @@ -30,14 +31,21 @@ |
30 | 31 | .bottom_commit |
31 | 32 | .mr_target_commit |
32 | 33 | |
33 | - %h3.padded.cgray 2. Fill info | |
34 | + %h4.cdark 2. Fill info | |
35 | + | |
34 | 36 | .clearfix |
35 | - = f.label :assignee_id, "Assign to", :class => "control-label" | |
36 | - .controls= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { :include_blank => "Select user" }, :style => "width:250px") | |
37 | + .main_box | |
38 | + .top_box_content | |
39 | + = f.label :title do | |
40 | + %strong= "Title *" | |
41 | + .input= f.text_field :title, :class => "input-xxlarge pad", :maxlength => 255, :rows => 5 | |
42 | + .middle_box_content | |
43 | + = f.label :assignee_id do | |
44 | + %i.icon-user | |
45 | + Assign to | |
46 | + .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { :include_blank => "Select user" }, :style => "width:250px") | |
37 | 47 | |
38 | 48 | .control-group |
39 | - = f.label :title, :class => "control-label" | |
40 | - .controls= f.text_field :title, :class => "input-xxlarge pad", :maxlength => 255, :rows => 5 | |
41 | 49 | |
42 | 50 | .form-actions |
43 | 51 | = f.submit 'Save', :class => "btn-primary btn" | ... | ... |
app/views/merge_requests/_merge_request.html.haml
... | ... | @@ -15,12 +15,14 @@ |
15 | 15 | → |
16 | 16 | = merge_request.target_branch |
17 | 17 | = image_tag gravatar_icon(merge_request.author_email), :class => "avatar" |
18 | + | |
19 | + = link_to project_merge_request_path(merge_request.project, merge_request) do | |
20 | + %p.row_title= truncate(merge_request.title, :length => 80) | |
21 | + | |
18 | 22 | %span.update-author |
19 | - %strong= merge_request.author_name | |
20 | - authored | |
23 | + %small.cdark= "##{merge_request.id}" | |
24 | + authored by #{merge_request.author_name} | |
21 | 25 | = time_ago_in_words(merge_request.created_at) |
22 | 26 | ago |
23 | 27 | - if merge_request.upvotes > 0 |
24 | 28 | %span.badge.badge-success= "+#{merge_request.upvotes}" |
25 | - = link_to project_merge_request_path(merge_request.project, merge_request) do | |
26 | - %p.row_title= truncate(merge_request.title, :length => 80) | ... | ... |
app/views/merge_requests/edit.html.haml
app/views/merge_requests/new.html.haml
app/views/merge_requests/show/_commits.html.haml
app/views/merge_requests/show/_mr_box.html.haml
... | ... | @@ -13,9 +13,10 @@ |
13 | 13 | = image_tag gravatar_icon(@merge_request.author_email), :width => 16, :class => "lil_av" |
14 | 14 | %strong.author= link_to_merge_request_author(@merge_request) |
15 | 15 | |
16 | - %cite.cgray and currently assigned to | |
17 | - = image_tag gravatar_icon(@merge_request.assignee_email), :width => 16, :class => "lil_av" | |
18 | - %strong.author= link_to_merge_request_assignee(@merge_request) | |
16 | + - if @merge_request.assignee | |
17 | + %cite.cgray and currently assigned to | |
18 | + = image_tag gravatar_icon(@merge_request.assignee_email), :width => 16, :class => "lil_av" | |
19 | + %strong.author= link_to_merge_request_assignee(@merge_request) | |
19 | 20 | |
20 | 21 | |
21 | 22 | - if @merge_request.closed | ... | ... |
app/views/notes/_form.html.haml
app/views/notes/_per_line_form.html.haml
... | ... | @@ -24,7 +24,7 @@ |
24 | 24 | = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit" |
25 | 25 | %span Commit author |
26 | 26 | .actions |
27 | - = f.submit 'Add note', :class => "btn primary", :id => "submit_note" | |
27 | + = f.submit 'Add note', :class => "btn primary submit_note", :id => "submit_note" | |
28 | 28 | = link_to "Close", "#", :class => "btn hide-button" |
29 | 29 | |
30 | 30 | :javascript | ... | ... |
app/views/notes/_reply_button.html.haml
app/views/refs/_tree.html.haml
... | ... | @@ -13,7 +13,7 @@ |
13 | 13 | = render :partial => "refs/tree_file", :locals => { :name => tree.name, :content => tree.data, :file => tree } |
14 | 14 | - else |
15 | 15 | - contents = tree.contents |
16 | - %table#tree-slider.bordered-table.table | |
16 | + %table#tree-slider.bordered-table.table{:class => "table_#{@hex_path}" } | |
17 | 17 | %thead |
18 | 18 | %th Name |
19 | 19 | %th Last Update |
... | ... | @@ -29,34 +29,39 @@ |
29 | 29 | %td |
30 | 30 | %td |
31 | 31 | |
32 | + - index = 0 | |
32 | 33 | - contents.select{ |i| i.is_a?(Grit::Tree)}.each do |content| |
33 | - = render :partial => "refs/tree_item", :locals => { :content => content } | |
34 | + = render :partial => "refs/tree_item", :locals => { :content => content, :index => (index += 1) } | |
34 | 35 | - contents.select{ |i| i.is_a?(Grit::Blob)}.each do |content| |
35 | - = render :partial => "refs/tree_item", :locals => { :content => content } | |
36 | + = render :partial => "refs/tree_item", :locals => { :content => content, :index => (index += 1) } | |
36 | 37 | - contents.select{ |i| i.is_a?(Grit::Submodule)}.each do |content| |
37 | - = render :partial => "refs/submodule_item", :locals => { :content => content } | |
38 | + = render :partial => "refs/submodule_item", :locals => { :content => content, :index => (index += 1) } | |
38 | 39 | |
39 | 40 | - if content = contents.select{ |c| c.is_a?(Grit::Blob) and c.name =~ /^readme/i }.first |
40 | - #tree-readme-holder | |
41 | - %h3= content.name | |
42 | - .readme | |
41 | + .file_holder#README | |
42 | + .file_title | |
43 | + %i.icon-file | |
44 | + = content.name | |
45 | + .file_content.wiki | |
43 | 46 | - if content.name =~ /\.(md|markdown)$/i |
44 | 47 | = preserve do |
45 | 48 | = markdown(content.data) |
46 | 49 | - else |
47 | 50 | = simple_format(content.data) |
48 | 51 | |
49 | -- if params[:path] | |
50 | - - history_path = tree_file_project_ref_path(@project, @ref, params[:path]) | |
51 | -- else | |
52 | - - history_path = tree_project_ref_path(@project, @ref) | |
53 | 52 | :javascript |
54 | 53 | $(function(){ |
55 | 54 | $('select#branch').selectmenu({style:'popup', width:200}); |
56 | 55 | $('select#tag').selectmenu({style:'popup', width:200}); |
57 | 56 | $('.project-refs-select').chosen(); |
58 | 57 | |
59 | - history.pushState({ path: this.path }, '', "#{history_path}") | |
58 | + history.pushState({ path: this.path }, '', "#{@history_path}"); | |
59 | + | |
60 | + }); | |
61 | + | |
62 | + // Load last commit log for each file in tree | |
63 | + $(window).load(function(){ | |
64 | + ajaxGet('#{@logs_path}'); | |
60 | 65 | }); |
61 | 66 | |
62 | 67 | ... | ... |
app/views/refs/_tree_file.html.haml
1 | -.view_file | |
2 | - .view_file_header | |
1 | +.file_holder | |
2 | + .file_title | |
3 | 3 | %i.icon-file |
4 | 4 | %span.file_name |
5 | 5 | = name |
... | ... | @@ -10,26 +10,28 @@ |
10 | 10 | = link_to "blame", blame_file_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small" |
11 | 11 | - if file.text? |
12 | 12 | - if name =~ /\.(md|markdown)$/i |
13 | - #tree-readme-holder | |
14 | - .readme | |
15 | - = preserve do | |
16 | - = markdown(file.data) | |
13 | + .file_content.wiki | |
14 | + = preserve do | |
15 | + = markdown(file.data) | |
17 | 16 | - else |
18 | - .view_file_content | |
17 | + .file_content.code | |
19 | 18 | - unless file.empty? |
20 | 19 | %div{:class => current_user.dark_scheme ? "black" : "white"} |
21 | 20 | = preserve do |
22 | 21 | = raw file.colorize(options: { linenos: 'True'}) |
23 | 22 | - else |
24 | 23 | %h4.nothing_here_message Empty file |
24 | + | |
25 | 25 | - elsif file.image? |
26 | - .view_file_content_image | |
26 | + .file_content.image_file | |
27 | 27 | %img{ :src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} |
28 | + | |
28 | 29 | - else |
29 | - %center | |
30 | - = link_to blob_project_ref_path(@project, @ref, :path => params[:path]) do | |
31 | - %div.padded | |
32 | - %br | |
33 | - = image_tag "download.png", :width => 64 | |
34 | - %h3 | |
35 | - Download (#{file.mb_size}) | |
30 | + .file_content.blob_file | |
31 | + %center | |
32 | + = link_to blob_project_ref_path(@project, @ref, :path => params[:path]) do | |
33 | + %div.padded | |
34 | + %br | |
35 | + = image_tag "download.png", :width => 64 | |
36 | + %h3 | |
37 | + Download (#{file.mb_size}) | ... | ... |
app/views/refs/_tree_item.html.haml
1 | -- file = params[:path] ? File.join(params[:path], content.name) : content.name | |
2 | -- content_commit = @project.commits(@commit.id, file, 1).last | |
3 | -- return unless content_commit | |
4 | -%tr{ :class => "tree-item", :url => tree_file_project_ref_path(@project, @ref, file) } | |
1 | +- file = tree_full_path(content) | |
2 | +%tr{ :class => "tree-item #{tree_hex_class(content)}", :url => tree_file_project_ref_path(@project, @ref, file) } | |
5 | 3 | %td.tree-item-file-name |
6 | - - if content.is_a?(Grit::Blob) | |
7 | - - if content.text? | |
8 | - = image_tag "file_txt.png" | |
9 | - - elsif content.image? | |
10 | - = image_tag "file_img.png" | |
11 | - - else | |
12 | - = image_tag "file_bin.png" | |
13 | - - else | |
14 | - = image_tag "file_dir.png" | |
4 | + = tree_icon(content) | |
15 | 5 | = link_to truncate(content.name, :length => 40), tree_file_project_ref_path(@project, @ref || @commit.id, file), :remote => :true |
16 | - %td.cgray | |
17 | - = time_ago_in_words(content_commit.committed_date) | |
18 | - ago | |
19 | - %td.commit | |
20 | - - tm = @project.team_member_by_name_or_email(content_commit.author_email, content_commit.author_name) | |
21 | - - if tm | |
22 | - %strong= link_to "[#{tm.user_name}]", project_team_member_path(@project, tm) | |
23 | - = link_to truncate(content_commit.safe_message, :length => tm ? 30 : 50), project_commit_path(@project, content_commit.id), :class => "tree-commit-link" | |
6 | + %td.tree_time_ago.cgray | |
7 | + - if index == 1 | |
8 | + %span.log_loading | |
9 | + Loading commit data.. | |
10 | + = image_tag "ajax_loader_tree.gif", :width => 14 | |
11 | + %td.tree_commit | ... | ... |
app/views/refs/blame.html.haml
... | ... | @@ -11,8 +11,8 @@ |
11 | 11 | %li= link |
12 | 12 | .clear |
13 | 13 | |
14 | - .view_file.blame_file | |
15 | - .view_file_header | |
14 | + .file_holder | |
15 | + .file_title | |
16 | 16 | %i.icon-file |
17 | 17 | %span.file_name |
18 | 18 | = @tree.name |
... | ... | @@ -21,7 +21,7 @@ |
21 | 21 | = link_to "raw", blob_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small", :target => "_blank" |
22 | 22 | = link_to "history", project_commits_path(@project, :path => params[:path], :ref => @ref), :class => "btn very_small" |
23 | 23 | = link_to "source", tree_file_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small" |
24 | - .view_file_content | |
24 | + .file_content.blame | |
25 | 25 | %table |
26 | 26 | - @blame.each do |commit, lines| |
27 | 27 | - commit = Commit.new(commit) |
... | ... | @@ -29,7 +29,7 @@ |
29 | 29 | %td.author |
30 | 30 | = image_tag gravatar_icon(commit.author_email, 16) |
31 | 31 | = commit.author_name |
32 | - %td.commit | |
32 | + %td.blame_commit | |
33 | 33 | |
34 | 34 | = link_to project_commit_path(@project, :id => commit.id) do |
35 | 35 | %code= commit.id.to_s[0..10] |
... | ... | @@ -37,8 +37,7 @@ |
37 | 37 | %td.lines |
38 | 38 | = preserve do |
39 | 39 | %pre |
40 | - - lines.each do |line| | |
41 | - = line | |
40 | + = Gitlab::Encode.utf8 lines.join("\n") | |
42 | 41 | |
43 | 42 | :javascript |
44 | 43 | $(function(){ | ... | ... |
... | ... | @@ -0,0 +1,9 @@ |
1 | +- @logs.each do |content_data| | |
2 | + - file_name = content_data[:file_name] | |
3 | + - content_commit = content_data[:commit] | |
4 | + - tm = @project.team_member_by_name_or_email(content_commit.author_email, content_commit.author_name) | |
5 | + | |
6 | + :plain | |
7 | + var row = $("table.table_#{@hex_path} tr.file_#{hexdigest(file_name)}"); | |
8 | + row.find("td.tree_time_ago").html('#{escape_javascript(time_ago_in_words(content_commit.committed_date))} ago'); | |
9 | + row.find("td.tree_commit").html('#{escape_javascript(render("tree_commit", :tm => tm, :content_commit => content_commit))}'); | ... | ... |
app/views/refs/tree.js.haml
1 | 1 | :plain |
2 | + // Load Files list | |
2 | 3 | $("#tree-holder").html("#{escape_javascript(render(:partial => "tree", :locals => {:repo => @repo, :commit => @commit, :tree => @tree}))}"); |
3 | 4 | $("#tree-content-holder").show("slide", { direction: "right" }, 150); |
4 | 5 | $('.project-refs-form #path').val("#{params[:path]}"); |
6 | + | |
7 | + // Load last commit log for each file in tree | |
8 | + $('#tree-slider').waitForImages(function() { | |
9 | + ajaxGet('#{@logs_path}'); | |
10 | + }); | ... | ... |
app/views/snippets/show.html.haml
... | ... | @@ -7,16 +7,14 @@ |
7 | 7 | = link_to "Edit", edit_project_snippet_path(@project, @snippet), :class => "btn small right" |
8 | 8 | |
9 | 9 | %br |
10 | -#tree-holder | |
11 | - #tree-content-holder | |
12 | - .view_file | |
13 | - .view_file_header | |
14 | - %i.icon-file | |
15 | - %strong= @snippet.file_name | |
16 | - %span.options | |
17 | - = link_to "raw", raw_project_snippet_path(@project, @snippet), :class => "btn very_small", :target => "_blank" | |
18 | - .view_file_content | |
19 | - %div{:class => current_user.dark_scheme ? "black" : ""} | |
20 | - = raw @snippet.colorize(options: { linenos: 'True'}) | |
10 | +.file_holder | |
11 | + .file_title | |
12 | + %i.icon-file | |
13 | + %strong= @snippet.file_name | |
14 | + %span.options | |
15 | + = link_to "raw", raw_project_snippet_path(@project, @snippet), :class => "btn very_small", :target => "_blank" | |
16 | + .file_content.code | |
17 | + %div{:class => current_user.dark_scheme ? "black" : ""} | |
18 | + = raw @snippet.colorize(options: { linenos: 'True'}) | |
21 | 19 | |
22 | 20 | = render "notes/notes", :tid => @snippet.id, :tt => "snippet" | ... | ... |
config/application.rb
... | ... | @@ -23,7 +23,7 @@ module Gitlab |
23 | 23 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] |
24 | 24 | |
25 | 25 | # Activate observers that should always be running. |
26 | - config.active_record.observers = :mailer_observer, :activity_observer, :project_observer, :key_observer, :issue_observer, :user_observer | |
26 | + config.active_record.observers = :mailer_observer, :activity_observer, :project_observer, :key_observer, :issue_observer, :user_observer, :system_hook_observer | |
27 | 27 | |
28 | 28 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. |
29 | 29 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. | ... | ... |
config/gitlab.yml.example
config/initializers/1_settings.rb
... | ... | @@ -95,11 +95,21 @@ class Settings < Settingslogic |
95 | 95 | end |
96 | 96 | |
97 | 97 | def gitolite_admin_uri |
98 | - git['admin_uri'] || 'git@localhost:gitolite-admin' | |
98 | + git_host['admin_uri'] || 'git@localhost:gitolite-admin' | |
99 | 99 | end |
100 | 100 | |
101 | 101 | def default_projects_limit |
102 | 102 | app['default_projects_limit'] || 10 |
103 | 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 | 114 | end |
105 | 115 | end | ... | ... |
config/initializers/devise.rb
... | ... | @@ -93,10 +93,6 @@ Devise.setup do |config| |
93 | 93 | # If true, extends the user's remember period when remembered via cookie. |
94 | 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 | 96 | # Options to be passed to the created cookie. For instance, you can set |
101 | 97 | # :secure => true in order to force SSL only cookies. |
102 | 98 | # config.cookie_options = {} |
... | ... | @@ -119,7 +115,7 @@ Devise.setup do |config| |
119 | 115 | # Defines which strategy will be used to lock an account. |
120 | 116 | # :failed_attempts = Locks an account after a number of failed attempts to sign in. |
121 | 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 | 120 | # Defines which key will be used when locking and unlocking an account |
125 | 121 | # config.unlock_keys = [ :email ] |
... | ... | @@ -129,14 +125,14 @@ Devise.setup do |config| |
129 | 125 | # :time = Re-enables login after a certain amount of time (see :unlock_in below) |
130 | 126 | # :both = Enables both strategies |
131 | 127 | # :none = No unlock strategy. You should handle unlocking by yourself. |
132 | - # config.unlock_strategy = :both | |
128 | + config.unlock_strategy = :time | |
133 | 129 | |
134 | 130 | # Number of authentication tries before locking an account if lock_strategy |
135 | 131 | # is failed attempts. |
136 | - # config.maximum_attempts = 20 | |
132 | + config.maximum_attempts = 10 | |
137 | 133 | |
138 | 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 | 137 | # ==> Configuration for :recoverable |
142 | 138 | # |
... | ... | @@ -160,9 +156,9 @@ Devise.setup do |config| |
160 | 156 | # Defines name of the authentication token params key |
161 | 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 | 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 | 163 | # ==> Scopes configuration |
168 | 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 | 35 | confirmed: 'Your account was successfully confirmed. You are now signed in.' |
36 | 36 | registrations: |
37 | 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 | 38 | updated: 'You updated your account successfully.' |
40 | 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 | 43 | unlocks: |
46 | 44 | send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.' |
47 | 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 | 26 | get 'help' => 'help#index' |
27 | 27 | get 'help/permissions' => 'help#permissions' |
28 | 28 | get 'help/workflow' => 'help#workflow' |
29 | + get 'help/api' => 'help#api' | |
29 | 30 | get 'help/web_hooks' => 'help#web_hooks' |
31 | + get 'help/system_hooks' => 'help#system_hooks' | |
30 | 32 | |
31 | 33 | # |
32 | 34 | # Admin Area |
... | ... | @@ -46,11 +48,13 @@ Gitlab::Application.routes.draw do |
46 | 48 | end |
47 | 49 | end |
48 | 50 | resources :team_members, :only => [:edit, :update, :destroy] |
49 | - get 'emails', :to => 'mailer#preview' | |
50 | 51 | get 'mailer/preview_note' |
51 | 52 | get 'mailer/preview_user_new' |
52 | 53 | get 'mailer/preview_issue_new' |
53 | 54 | |
55 | + resources :hooks, :only => [:index, :create, :destroy] do | |
56 | + get :test | |
57 | + end | |
54 | 58 | resource :logs |
55 | 59 | resource :resque, :controller => 'resque' |
56 | 60 | root :to => "dashboard#index" |
... | ... | @@ -116,6 +120,8 @@ Gitlab::Application.routes.draw do |
116 | 120 | |
117 | 121 | member do |
118 | 122 | get "tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ } |
123 | + get "logs_tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ } | |
124 | + | |
119 | 125 | get "blob", |
120 | 126 | :constraints => { |
121 | 127 | :id => /[a-zA-Z.0-9\/_\-]+/, |
... | ... | @@ -131,6 +137,14 @@ Gitlab::Application.routes.draw do |
131 | 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 | 148 | # blame |
135 | 149 | get "blame/:path" => "refs#blame", |
136 | 150 | :as => :blame_file, | ... | ... |
db/migrate/20110913200833_devise_create_users.rb
1 | 1 | class DeviseCreateUsers < ActiveRecord::Migration |
2 | 2 | def self.up |
3 | 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 | 42 | t.timestamps |
15 | 43 | end | ... | ... |
db/schema.rb
... | ... | @@ -11,7 +11,7 @@ |
11 | 11 | # |
12 | 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 | 16 | create_table "events", :force => true do |t| |
17 | 17 | t.string "target_type" |
... | ... | @@ -169,6 +169,8 @@ ActiveRecord::Schema.define(:version => 20120627145613) do |
169 | 169 | t.integer "theme_id", :default => 1, :null => false |
170 | 170 | t.string "bio" |
171 | 171 | t.boolean "blocked", :default => false, :null => false |
172 | + t.integer "failed_attempts", :default => 0 | |
173 | + t.datetime "locked_at" | |
172 | 174 | end |
173 | 175 | |
174 | 176 | add_index "users", ["email"], :name => "index_users_on_email", :unique => true |
... | ... | @@ -185,8 +187,9 @@ ActiveRecord::Schema.define(:version => 20120627145613) do |
185 | 187 | create_table "web_hooks", :force => true do |t| |
186 | 188 | t.string "url" |
187 | 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 | 193 | end |
191 | 194 | |
192 | 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 | 60 | sudo apt-get update |
61 | 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 | 65 | # If you want to use MySQL: |
66 | 66 | sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev |
... | ... | @@ -107,10 +107,10 @@ Get gitolite source code: |
107 | 107 | |
108 | 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 | 111 | sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; /home/git/gitolite/src/gl-system-install" |
112 | 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 | 115 | sudo -u git -H sed -i 's/0077/0007/g' /home/git/share/gitolite/conf/example.gitolite.rc |
116 | 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 | 139 | cd /home/gitlab |
140 | 140 | sudo -H -u gitlab git clone -b stable git://github.com/gitlabhq/gitlabhq.git gitlab |
141 | 141 | cd gitlab |
142 | + | |
143 | + sudo -u gitlab mkdir tmp | |
142 | 144 | |
143 | 145 | # Rename config files |
144 | 146 | sudo -u gitlab cp config/gitlab.yml.example config/gitlab.yml |
... | ... | @@ -216,15 +218,15 @@ Application can be started with next command: |
216 | 218 | sudo -u gitlab cp config/unicorn.rb.orig config/unicorn.rb |
217 | 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 | 223 | upstream gitlab { |
222 | 224 | server unix:/home/gitlab/gitlab/tmp/sockets/gitlab.socket; |
223 | 225 | } |
224 | 226 | |
225 | 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 | 230 | root /home/gitlab/gitlab/public; |
229 | 231 | |
230 | 232 | # individual nginx logs for this gitlab vhost |
... | ... | @@ -232,26 +234,26 @@ Edit /etc/nginx/nginx.conf. Add in **http** section: |
232 | 234 | error_log /var/log/nginx/gitlab_error.log; |
233 | 235 | |
234 | 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 | 242 | # if a file, which is not found in the root folder is requested, |
241 | 243 | # then the proxy pass the request to the upsteam (gitlab unicorn) |
242 | 244 | location @gitlab { |
243 | 245 | proxy_redirect off; |
246 | + | |
244 | 247 | # you need to change this to "https", if you set "ssl" directive to "on" |
245 | 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 | 250 | proxy_set_header X-Real-IP $remote_addr; |
248 | 251 | |
249 | 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 | 258 | Restart nginx: |
257 | 259 | ... | ... |
lib/gitlab/logger.rb
1 | 1 | module Gitlab |
2 | - class Logger | |
2 | + class Logger < ::Logger | |
3 | 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 | 9 | end |
8 | 10 | |
9 | 11 | def self.read_latest |
10 | 12 | path = Rails.root.join("log/githost.log") |
11 | - logs = `tail -n 50 #{path}`.split("\n") | |
13 | + logs = File.read(path).split("\n") | |
12 | 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 | 23 | end |
14 | 24 | end | ... | ... |
... | ... | @@ -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 | 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 | 78 | end |
79 | 79 | |
80 | 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 | 82 | expect { |
83 | 83 | delete "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}?private_token=#{user.private_token}" |
84 | 84 | }.should change { Snippet.count }.by(-1) | ... | ... |
spec/factories.rb
... | ... | @@ -7,6 +7,12 @@ Factory.add(:project, Project) do |obj| |
7 | 7 | obj.code = 'LGT' |
8 | 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 | 16 | Factory.add(:public_project, Project) do |obj| |
11 | 17 | obj.name = Faker::Internet.user_name |
12 | 18 | obj.path = 'gitlabhq' |
... | ... | @@ -60,7 +66,11 @@ Factory.add(:key, Key) do |obj| |
60 | 66 | obj.key = File.read(File.join(Rails.root, "db", "pkey.example")) |
61 | 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 | 74 | obj.url = Faker::Internet.uri("http") |
65 | 75 | end |
66 | 76 | ... | ... |
spec/models/merge_request_spec.rb
... | ... | @@ -13,7 +13,6 @@ describe MergeRequest do |
13 | 13 | it { should validate_presence_of(:title) } |
14 | 14 | it { should validate_presence_of(:author_id) } |
15 | 15 | it { should validate_presence_of(:project_id) } |
16 | - it { should validate_presence_of(:assignee_id) } | |
17 | 16 | end |
18 | 17 | |
19 | 18 | describe "Scope" do | ... | ... |
spec/models/project_hooks_spec.rb
... | ... | @@ -21,44 +21,44 @@ describe Project, "Hooks" do |
21 | 21 | end |
22 | 22 | end |
23 | 23 | |
24 | - describe "Web hooks" do | |
24 | + describe "Project hooks" do | |
25 | 25 | context "with no web hooks" do |
26 | 26 | it "raises no errors" do |
27 | 27 | lambda { |
28 | - project.execute_web_hooks('oldrev', 'newrev', 'ref', @user) | |
28 | + project.execute_hooks('oldrev', 'newrev', 'ref', @user) | |
29 | 29 | }.should_not raise_error |
30 | 30 | end |
31 | 31 | end |
32 | 32 | |
33 | 33 | context "with web hooks" do |
34 | 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 | 38 | end |
39 | 39 | |
40 | 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 | 45 | end |
46 | 46 | end |
47 | 47 | |
48 | 48 | context "does not execute web hooks" do |
49 | 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 | 52 | end |
53 | 53 | |
54 | 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 | 57 | end |
58 | 58 | |
59 | 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 | 62 | end |
63 | 63 | end |
64 | 64 | ... | ... |
spec/models/project_spec.rb
... | ... | @@ -11,7 +11,7 @@ describe Project do |
11 | 11 | it { should have_many(:issues).dependent(:destroy) } |
12 | 12 | it { should have_many(:notes).dependent(:destroy) } |
13 | 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 | 15 | it { should have_many(:deploy_keys).dependent(:destroy) } |
16 | 16 | end |
17 | 17 | ... | ... |
... | ... | @@ -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 | 1 | require 'spec_helper' |
2 | 2 | |
3 | -describe WebHook do | |
3 | +describe ProjectHook do | |
4 | 4 | describe "Associations" do |
5 | 5 | it { should belong_to :project } |
6 | 6 | end |
... | ... | @@ -23,32 +23,32 @@ describe WebHook do |
23 | 23 | |
24 | 24 | describe "execute" do |
25 | 25 | before(:each) do |
26 | - @webhook = Factory :web_hook | |
26 | + @project_hook = Factory :project_hook | |
27 | 27 | @project = Factory :project |
28 | - @project.web_hooks << [@webhook] | |
28 | + @project.hooks << [@project_hook] | |
29 | 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 | 32 | end |
33 | 33 | |
34 | 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 | 37 | end |
38 | 38 | |
39 | 39 | it "POSTs the data as JSON" do |
40 | 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 | 44 | end |
45 | 45 | |
46 | 46 | it "catches exceptions" do |
47 | 47 | WebHook.should_receive(:post).and_raise("Some HTTP Post error") |
48 | 48 | |
49 | 49 | lambda { |
50 | - @webhook.execute(@data) | |
51 | - }.should_not raise_error | |
50 | + @project_hook.execute(@data) | |
51 | + }.should raise_error | |
52 | 52 | end |
53 | 53 | end |
54 | 54 | end | ... | ... |
... | ... | @@ -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 | 13 | it { admin_users_path.should be_denied_for :visitor } |
14 | 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 | 20 | end |
21 | 21 | end | ... | ... |
spec/requests/hooks_spec.rb
... | ... | @@ -9,7 +9,7 @@ describe "Hooks" do |
9 | 9 | |
10 | 10 | describe "GET index" do |
11 | 11 | it "should be available" do |
12 | - @hook = Factory :web_hook, :project => @project | |
12 | + @hook = Factory :project_hook, :project => @project | |
13 | 13 | visit project_hooks_path(@project) |
14 | 14 | page.should have_content "Hooks" |
15 | 15 | page.should have_content @hook.url |
... | ... | @@ -21,7 +21,7 @@ describe "Hooks" do |
21 | 21 | @url = Faker::Internet.uri("http") |
22 | 22 | visit project_hooks_path(@project) |
23 | 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 | 25 | end |
26 | 26 | |
27 | 27 | it "should open new team member popup" do |
... | ... | @@ -32,7 +32,8 @@ describe "Hooks" do |
32 | 32 | |
33 | 33 | describe "Test" do |
34 | 34 | before do |
35 | - @hook = Factory :web_hook, :project => @project | |
35 | + @hook = Factory :project_hook, :project => @project | |
36 | + stub_request(:post, @hook.url) | |
36 | 37 | visit project_hooks_path(@project) |
37 | 38 | click_link "Test Hook" |
38 | 39 | end | ... | ... |
spec/workers/post_receive_spec.rb
... | ... | @@ -22,14 +22,14 @@ describe PostReceive do |
22 | 22 | Key.stub(find_by_identifier: nil) |
23 | 23 | |
24 | 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 | 27 | PostReceive.perform(project.path, 'sha-old', 'sha-new', 'refs/heads/master', key_id).should be_false |
28 | 28 | end |
29 | 29 | |
30 | 30 | it "asks the project to execute web hooks" do |
31 | 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 | 34 | PostReceive.perform(project.path, 'sha-old', 'sha-new', 'refs/heads/master', key_id) |
35 | 35 | end | ... | ... |
... | ... | @@ -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); | ... | ... |