Commit 66ebf8d83f894ed361031eb9ede5a8b829fefd36

Authored by Lennart Rosam
2 parents dc13af90 bd948549

Merge remote-tracking branch 'github/master'

Showing 190 changed files with 4007 additions and 974 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 190 files displayed.

Procfile
1 1 web: bundle exec unicorn_rails -p $PORT
2   -worker: bundle exec sidekiq -q post_receive,mailer,system_hook,common,default
  2 +worker: bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,common,default
... ...
app/assets/images/onion_skin_sprites.gif 0 → 100644

1.55 KB

app/assets/images/swipemode_sprites.gif 0 → 100644

1.5 KB

app/assets/javascripts/commit/file.js.coffee 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +class CommitFile
  2 +
  3 + constructor: (file) ->
  4 + if $('.image', file).length
  5 + new ImageFile(file)
  6 +
  7 +this.CommitFile = CommitFile
0 8 \ No newline at end of file
... ...
app/assets/javascripts/commit/image-file.js.coffee 0 → 100644
... ... @@ -0,0 +1,128 @@
  1 +class ImageFile
  2 +
  3 + # Width where images must fits in, for 2-up this gets divided by 2
  4 + @availWidth = 900
  5 + @viewModes = ['two-up', 'swipe']
  6 +
  7 + constructor: (@file) ->
  8 + # Determine if old and new file has same dimensions, if not show 'two-up' view
  9 + this.requestImageInfo $('.two-up.view .frame.deleted img', @file), (deletedWidth, deletedHeight) =>
  10 + this.requestImageInfo $('.two-up.view .frame.added img', @file), (width, height) =>
  11 + if width == deletedWidth && height == deletedHeight
  12 + this.initViewModes()
  13 + else
  14 + this.initView('two-up')
  15 +
  16 + initViewModes: ->
  17 + viewMode = ImageFile.viewModes[0]
  18 +
  19 + $('.view-modes', @file).removeClass 'hide'
  20 + $('.view-modes-menu', @file).on 'click', 'li', (event) =>
  21 + unless $(event.currentTarget).hasClass('active')
  22 + this.activateViewMode(event.currentTarget.className)
  23 +
  24 + this.activateViewMode(viewMode)
  25 +
  26 + activateViewMode: (viewMode) ->
  27 + $('.view-modes-menu li', @file)
  28 + .removeClass('active')
  29 + .filter(".#{viewMode}").addClass 'active'
  30 + $(".view:visible:not(.#{viewMode})", @file).fadeOut 200, =>
  31 + $(".view.#{viewMode}", @file).fadeIn(200)
  32 + this.initView viewMode
  33 +
  34 + initView: (viewMode) ->
  35 + this.views[viewMode].call(this)
  36 +
  37 + prepareFrames = (view) ->
  38 + maxWidth = 0
  39 + maxHeight = 0
  40 + $('.frame', view).each (index, frame) =>
  41 + width = $(frame).width()
  42 + height = $(frame).height()
  43 + maxWidth = if width > maxWidth then width else maxWidth
  44 + maxHeight = if height > maxHeight then height else maxHeight
  45 + .css
  46 + width: maxWidth
  47 + height: maxHeight
  48 +
  49 + [maxWidth, maxHeight]
  50 +
  51 + views:
  52 + 'two-up': ->
  53 + $('.two-up.view .wrap', @file).each (index, wrap) =>
  54 + $('img', wrap).each ->
  55 + currentWidth = $(this).width()
  56 + if currentWidth > ImageFile.availWidth / 2
  57 + $(this).width ImageFile.availWidth / 2
  58 +
  59 + this.requestImageInfo $('img', wrap), (width, height) ->
  60 + $('.image-info .meta-width', wrap).text "#{width}px"
  61 + $('.image-info .meta-height', wrap).text "#{height}px"
  62 + $('.image-info', wrap).removeClass('hide')
  63 +
  64 + 'swipe': ->
  65 + maxWidth = 0
  66 + maxHeight = 0
  67 +
  68 + $('.swipe.view', @file).each (index, view) =>
  69 +
  70 + [maxWidth, maxHeight] = prepareFrames(view)
  71 +
  72 + $('.swipe-frame', view).css
  73 + width: maxWidth + 16
  74 + height: maxHeight + 28
  75 +
  76 + $('.swipe-wrap', view).css
  77 + width: maxWidth + 1
  78 + height: maxHeight + 2
  79 +
  80 + $('.swipe-bar', view).css
  81 + left: 0
  82 + .draggable
  83 + axis: 'x'
  84 + containment: 'parent'
  85 + drag: (event) ->
  86 + $('.swipe-wrap', view).width (maxWidth + 1) - $(this).position().left
  87 + stop: (event) ->
  88 + $('.swipe-wrap', view).width (maxWidth + 1) - $(this).position().left
  89 +
  90 + 'onion-skin': ->
  91 + maxWidth = 0
  92 + maxHeight = 0
  93 +
  94 + dragTrackWidth = $('.drag-track', @file).width() - $('.dragger', @file).width()
  95 +
  96 + $('.onion-skin.view', @file).each (index, view) =>
  97 +
  98 + [maxWidth, maxHeight] = prepareFrames(view)
  99 +
  100 + $('.onion-skin-frame', view).css
  101 + width: maxWidth + 16
  102 + height: maxHeight + 28
  103 +
  104 + $('.swipe-wrap', view).css
  105 + width: maxWidth + 1
  106 + height: maxHeight + 2
  107 +
  108 + $('.dragger', view).css
  109 + left: dragTrackWidth
  110 + .draggable
  111 + axis: 'x'
  112 + containment: 'parent'
  113 + drag: (event) ->
  114 + $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth)
  115 + stop: (event) ->
  116 + $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth)
  117 +
  118 +
  119 +
  120 + requestImageInfo: (img, callback) ->
  121 + domImg = img.get(0)
  122 + if domImg.complete
  123 + callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
  124 + else
  125 + img.on 'load', =>
  126 + callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
  127 +
  128 +this.ImageFile = ImageFile
0 129 \ No newline at end of file
... ...
app/assets/javascripts/commits.js
... ... @@ -1,59 +0,0 @@
1   -var CommitsList = {
2   - ref:null,
3   - limit:0,
4   - offset:0,
5   - disable:false,
6   -
7   - init:
8   - function(ref, limit) {
9   - $(".day-commits-table li.commit").live('click', function(e){
10   - if(e.target.nodeName != "A") {
11   - location.href = $(this).attr("url");
12   - e.stopPropagation();
13   - return false;
14   - }
15   - });
16   -
17   - this.ref=ref;
18   - this.limit=limit;
19   - this.offset=limit;
20   - this.initLoadMore();
21   - $('.loading').show();
22   - },
23   -
24   - getOld:
25   - function() {
26   - $('.loading').show();
27   - $.ajax({
28   - type: "GET",
29   - url: location.href,
30   - data: "limit=" + this.limit + "&offset=" + this.offset + "&ref=" + this.ref,
31   - complete: function(){ $('.loading').hide()},
32   - dataType: "script"});
33   - },
34   -
35   - append:
36   - function(count, html) {
37   - $("#commits_list").append(html);
38   - if(count > 0) {
39   - this.offset += count;
40   - } else {
41   - this.disable = true;
42   - }
43   - },
44   -
45   - initLoadMore:
46   - function() {
47   - $(document).endlessScroll({
48   - bottomPixels: 400,
49   - fireDelay: 1000,
50   - fireOnce:true,
51   - ceaseFire: function() {
52   - return CommitsList.disable;
53   - },
54   - callback: function(i) {
55   - CommitsList.getOld();
56   - }
57   - });
58   - }
59   -}
app/assets/javascripts/commits.js.coffee 0 → 100644
... ... @@ -0,0 +1,54 @@
  1 +class CommitsList
  2 + @data =
  3 + ref: null
  4 + limit: 0
  5 + offset: 0
  6 + @disable = false
  7 +
  8 + @showProgress: ->
  9 + $('.loading').show()
  10 +
  11 + @hideProgress: ->
  12 + $('.loading').hide()
  13 +
  14 + @init: (ref, limit) ->
  15 + $(".day-commits-table li.commit").live 'click', (event) ->
  16 + if event.target.nodeName != "A"
  17 + location.href = $(this).attr("url")
  18 + e.stopPropagation()
  19 + return false
  20 +
  21 + @data.ref = ref
  22 + @data.limit = limit
  23 + @data.offset = limit
  24 +
  25 + this.initLoadMore()
  26 + this.showProgress();
  27 +
  28 + @getOld: ->
  29 + this.showProgress()
  30 + $.ajax
  31 + type: "GET"
  32 + url: location.href
  33 + data: @data
  34 + complete: this.hideProgress
  35 + dataType: "script"
  36 +
  37 + @append: (count, html) ->
  38 + $("#commits-list").append(html)
  39 + if count > 0
  40 + @data.offset += count
  41 + else
  42 + @disable = true
  43 +
  44 + @initLoadMore: ->
  45 + $(document).endlessScroll
  46 + bottomPixels: 400
  47 + fireDelay: 1000
  48 + fireOnce: true
  49 + ceaseFire: =>
  50 + @disable
  51 + callback: =>
  52 + this.getOld()
  53 +
  54 +this.CommitsList = CommitsList
0 55 \ No newline at end of file
... ...
app/assets/javascripts/dashboard.js.coffee
... ... @@ -4,11 +4,11 @@ window.dashboardPage = ->
4 4 event.preventDefault()
5 5 toggleFilter $(this)
6 6 reloadActivities()
7   -
  7 +
8 8 reloadActivities = ->
9 9 $(".content_list").html ''
10 10 Pager.init 20, true
11   -
  11 +
12 12 toggleFilter = (sender) ->
13 13 sender.parent().toggleClass "inactive"
14 14 event_filters = $.cookie("event_filter")
... ... @@ -17,11 +17,11 @@ toggleFilter = (sender) ->
17 17 event_filters = event_filters.split(",")
18 18 else
19 19 event_filters = new Array()
20   -
  20 +
21 21 index = event_filters.indexOf(filter)
22 22 if index is -1
23 23 event_filters.push filter
24 24 else
25 25 event_filters.splice index, 1
26   -
  26 +
27 27 $.cookie "event_filter", event_filters.join(",")
... ...
app/assets/javascripts/merge_requests.js.coffee
1 1 #
2 2 # * Filter merge requests
3   -#
  3 +#
4 4 @merge_requestsPage = ->
5 5 $('#assignee_id').chosen()
6 6 $('#milestone_id').chosen()
... ... @@ -8,16 +8,16 @@
8 8 $(this).closest('form').submit()
9 9  
10 10 class MergeRequest
11   -
  11 +
12 12 constructor: (@opts) ->
13 13 this.$el = $('.merge-request')
14 14 @diffs_loaded = false
15 15 @commits_loaded = false
16   -
  16 +
17 17 this.activateTab(@opts.action)
18   -
  18 +
19 19 this.bindEvents()
20   -
  20 +
21 21 this.initMergeWidget()
22 22 this.$('.show-all-commits').on 'click', =>
23 23 this.showAllCommits()
... ... @@ -28,7 +28,7 @@ class MergeRequest
28 28  
29 29 initMergeWidget: ->
30 30 this.showState( @opts.current_state )
31   -
  31 +
32 32 if this.$('.automerge_widget').length and @opts.check_enable
33 33 $.get @opts.url_to_automerge_check, (data) =>
34 34 this.showState( data.state )
... ... @@ -42,12 +42,12 @@ class MergeRequest
42 42 bindEvents: ->
43 43 this.$('.nav-tabs').on 'click', 'a', (event) =>
44 44 a = $(event.currentTarget)
45   -
  45 +
46 46 href = a.attr('href')
47 47 History.replaceState {path: href}, document.title, href
48   -
  48 +
49 49 event.preventDefault()
50   -
  50 +
51 51 this.$('.nav-tabs').on 'click', 'li', (event) =>
52 52 this.activateTab($(event.currentTarget).data('action'))
53 53  
... ...
app/assets/javascripts/pager.js
... ... @@ -1,56 +0,0 @@
1   -var Pager = {
2   - limit:0,
3   - offset:0,
4   - disable:false,
5   -
6   - init:
7   - function(limit, preload) {
8   - this.limit=limit;
9   -
10   - if(preload) {
11   - this.offset = 0;
12   - this.getOld();
13   - } else {
14   - this.offset = limit;
15   - }
16   -
17   - this.initLoadMore();
18   - },
19   -
20   - getOld:
21   - function() {
22   - $('.loading').show();
23   - $.ajax({
24   - type: "GET",
25   - url: location.href,
26   - data: "limit=" + this.limit + "&offset=" + this.offset,
27   - complete: function(){ $('.loading').hide()},
28   - dataType: "script"});
29   - },
30   -
31   - append:
32   - function(count, html) {
33   - $(".content_list").append(html);
34   - if(count > 0) {
35   - this.offset += count;
36   - } else {
37   - this.disable = true;
38   - }
39   - },
40   -
41   - initLoadMore:
42   - function() {
43   - $(document).endlessScroll({
44   - bottomPixels: 400,
45   - fireDelay: 1000,
46   - fireOnce:true,
47   - ceaseFire: function() {
48   - return Pager.disable;
49   - },
50   - callback: function(i) {
51   - $('.loading').show();
52   - Pager.getOld();
53   - }
54   - });
55   - }
56   -}
app/assets/javascripts/pager.js.coffee 0 → 100644
... ... @@ -0,0 +1,42 @@
  1 +@Pager =
  2 + limit: 0
  3 + offset: 0
  4 + disable: false
  5 + init: (limit, preload) ->
  6 + @limit = limit
  7 + if preload
  8 + @offset = 0
  9 + @getOld()
  10 + else
  11 + @offset = limit
  12 + @initLoadMore()
  13 +
  14 + getOld: ->
  15 + $(".loading").show()
  16 + $.ajax
  17 + type: "GET"
  18 + url: location.href
  19 + data: "limit=" + @limit + "&offset=" + @offset
  20 + complete: ->
  21 + $(".loading").hide()
  22 +
  23 + dataType: "script"
  24 +
  25 + append: (count, html) ->
  26 + $(".content_list").append html
  27 + if count > 0
  28 + @offset += count
  29 + else
  30 + @disable = true
  31 +
  32 + initLoadMore: ->
  33 + $(document).endlessScroll
  34 + bottomPixels: 400
  35 + fireDelay: 1000
  36 + fireOnce: true
  37 + ceaseFire: ->
  38 + Pager.disable
  39 +
  40 + callback: (i) ->
  41 + $(".loading").show()
  42 + Pager.getOld()
... ...
app/assets/stylesheets/common.scss
1 1 html {
2   - overflow-y: scroll;
  2 + overflow-y: scroll;
3 3 }
4 4  
5 5 /** LAYOUT **/
... ... @@ -277,8 +277,20 @@ p.time {
277 277 }
278 278 }
279 279  
  280 +.search-holder {
  281 + label, input {
  282 + height: 30px;
  283 + padding: 0;
  284 + font-size: 14px;
  285 + }
  286 + label {
  287 + line-height: 30px;
  288 + color: #666;
  289 + }
  290 +}
  291 +
280 292 .highlight_word {
281   - background: #EEDC94;
  293 + border-bottom: 2px solid #F90;
282 294 }
283 295  
284 296 .status_info {
... ... @@ -326,7 +338,7 @@ li.note {
326 338 li {
327 339 border-bottom:none !important;
328 340 }
329   - .file {
  341 + .attachment {
330 342 padding-left: 20px;
331 343 background:url("icon-attachment.png") no-repeat left center;
332 344 }
... ...
app/assets/stylesheets/gitlab_bootstrap/files.scss
... ... @@ -135,7 +135,7 @@
135 135 pre {
136 136 border: none;
137 137 border-radius: 0;
138   - font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace;
  138 + font-family: $monospace_font;
139 139 font-size: 12px !important;
140 140 line-height: 16px !important;
141 141 margin: 0;
... ...
app/assets/stylesheets/gitlab_bootstrap/fonts.scss
... ... @@ -4,4 +4,4 @@
4 4 }
5 5  
6 6 /** Typo **/
7   -$monospace: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
  7 +$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
... ...
app/assets/stylesheets/gitlab_bootstrap/typography.scss
... ... @@ -21,7 +21,7 @@ h6 {
21 21  
22 22 /** CODE **/
23 23 pre {
24   - font-family:'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace;
  24 + font-family: $monospace_font;
25 25  
26 26 &.dark {
27 27 background: #333;
... ... @@ -79,7 +79,7 @@ a:focus {
79 79 }
80 80  
81 81 .monospace {
82   - font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace;
  82 + font-family: $monospace_font;
83 83 }
84 84  
85 85 /**
... ...
app/assets/stylesheets/gitlab_bootstrap/variables.scss
1   -/** Colors **/
  1 +/**
  2 + * General Colors
  3 + */
2 4 $primary_color: #2FA0BB;
3 5 $link_color: #3A89A3;
4 6 $style_color: #474D57;
5 7 $hover: #D9EDF7;
  8 +
  9 +/**
  10 + * Commit Diff Colors
  11 + */
  12 +$added: #63c363;
  13 +$deleted: #f77;
... ...
app/assets/stylesheets/sections/commits.scss
1 1 /**
2   - *
3   - * COMMIT SHOw
4   - *
  2 + * Commit file
5 3 */
6 4 .commit-committer-link,
7 5 .commit-author-link {
... ... @@ -12,11 +10,11 @@
12 10 }
13 11 }
14 12  
15   -.diff_file {
  13 +.file {
16 14 border: 1px solid #CCC;
17 15 margin-bottom: 1em;
18 16  
19   - .diff_file_header {
  17 + .header {
20 18 @extend .clearfix;
21 19 padding: 5px 5px 5px 10px;
22 20 color: #555;
... ... @@ -28,32 +26,35 @@
28 26 background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
29 27 background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
30 28  
  29 + a{
  30 + color: $style_color;
  31 + }
  32 +
31 33 > span {
32   - font-family: $monospace;
  34 + font-family: $monospace_font;
33 35 font-size: 14px;
34 36 line-height: 30px;
35 37 }
36 38  
37   - a.view-commit{
  39 + a.view-file{
38 40 font-weight: bold;
39 41 }
40 42  
41 43 .commit-short-id{
42   - font-family: $monospace;
  44 + font-family: $monospace_font;
43 45 font-size: smaller;
44 46 }
45 47  
46 48 .file-mode{
47   - font-family: $monospace;
  49 + font-family: $monospace_font;
48 50 }
49 51 }
50   - .diff_file_content {
  52 + .content {
51 53 overflow: auto;
52 54 overflow-y: hidden;
53   - background: #fff;
  55 + background: #FFF;
54 56 color: #333;
55 57 font-size: 12px;
56   - font-family: $monospace;
57 58 .old{
58 59 span.idiff{
59 60 background-color: #FAA;
... ... @@ -66,114 +67,274 @@
66 67 }
67 68  
68 69 table {
  70 + font-family: $monospace_font;
  71 + border: none;
  72 + margin: 0px;
  73 + padding: 0px;
69 74 td {
70 75 line-height: 18px;
  76 + font-size: 12px;
  77 + }
  78 + }
  79 + .old_line, .new_line {
  80 + margin: 0px;
  81 + padding: 0px;
  82 + border: none;
  83 + background: #EEE;
  84 + color: #666;
  85 + padding: 0px 5px;
  86 + border-right: 1px solid #ccc;
  87 + text-align: right;
  88 + min-width: 35px;
  89 + max-width: 35px;
  90 + width: 35px;
  91 + @include user-select(none);
  92 + a {
  93 + float: left;
  94 + width: 35px;
  95 + font-weight: normal;
  96 + color: #666;
  97 + &:hover {
  98 + text-decoration: underline;
  99 + }
  100 + }
  101 + }
  102 + .line_content {
  103 + white-space: pre;
  104 + height: 14px;
  105 + margin: 0px;
  106 + padding: 0px;
  107 + border: none;
  108 + &.new {
  109 + background: #CFD;
  110 + }
  111 + &.old {
  112 + background: #FDD;
  113 + }
  114 + &.matched {
  115 + color: #ccc;
  116 + background: #fafafa;
71 117 }
72 118 }
73 119 }
74   - .diff_file_content_image {
75   - background: #eee;
  120 + .image {
  121 + background: #ddd;
76 122 text-align: center;
77   - .image {
  123 + padding: 30px;
  124 + .wrap{
78 125 display: inline-block;
79   - margin: 50px;
80   - max-width: 400px;
81   -
  126 + }
  127 +
  128 + .frame {
  129 + display: inline-block;
  130 + background-color: #fff;
  131 + line-height: 0;
82 132 img{
  133 + border: 1px solid #FFF;
83 134 background: url('trans_bg.gif');
84 135 }
  136 + &.deleted {
  137 + border: 1px solid $deleted;
  138 + }
85 139  
86   - &.diff_removed {
87   - img{
88   - border: 1px solid #C00;
89   - }
  140 + &.added {
  141 + border: 1px solid $added;
90 142 }
  143 + }
  144 + .image-info{
  145 + font-size: 12px;
  146 + margin: 5px 0 0 0;
  147 + color: grey;
  148 + }
91 149  
92   - &.diff_added {
93   - img{
94   - border: 1px solid #0C0;
  150 + .view.swipe{
  151 + position: relative;
  152 +
  153 + .swipe-frame{
  154 + display: block;
  155 + margin: auto;
  156 + position: relative;
  157 + }
  158 + .swipe-wrap{
  159 + overflow: hidden;
  160 + border-left: 1px solid #999;
  161 + position: absolute;
  162 + display: block;
  163 + top: 13px;
  164 + right: 7px;
  165 + }
  166 + .frame{
  167 + top: 0;
  168 + right: 0;
  169 + position: absolute;
  170 + &.deleted{
  171 + margin: 0;
  172 + display: block;
  173 + top: 13px;
  174 + right: 7px;
95 175 }
96 176 }
97   -
98   - .image-info{
99   - margin: 5px 0 0 0;
  177 + .swipe-bar{
  178 + display: block;
  179 + height: 100%;
  180 + width: 15px;
  181 + z-index: 100;
  182 + position: absolute;
  183 + cursor: pointer;
  184 + &:hover{
  185 + .top-handle{
  186 + background-position: -15px 3px;
  187 + }
  188 + .bottom-handle{
  189 + background-position: -15px -11px;
  190 + }
  191 + };
  192 + .top-handle{
  193 + display: block;
  194 + height: 14px;
  195 + width: 15px;
  196 + position: absolute;
  197 + top: 0px;
  198 + background: url('swipemode_sprites.gif') 0 3px no-repeat;
  199 + }
  200 + .bottom-handle{
  201 + display: block;
  202 + height: 14px;
  203 + width: 15px;
  204 + position: absolute;
  205 + bottom: 0px;
  206 + background: url('swipemode_sprites.gif') 0 -11px no-repeat;
  207 + }
100 208 }
101   - }
102   -
103   - &.img_compared {
104   - .image {
105   - max-width: 300px;
  209 + } //.view.swipe
  210 + .view.onion-skin{
  211 + .onion-skin-frame{
  212 + display: block;
  213 + margin: auto;
  214 + position: relative;
106 215 }
107   - }
  216 + .frame.added, .frame.deleted {
  217 + position: absolute;
  218 + display: block;
  219 + top: 0px;
  220 + left: 0px;
  221 + }
  222 + .controls{
  223 + display: block;
  224 + height: 14px;
  225 + width: 300px;
  226 + z-index: 100;
  227 + position: absolute;
  228 + bottom: 0px;
  229 + left: 50%;
  230 + margin-left: -150px;
  231 +
  232 + .drag-track{
  233 + display: block;
  234 + position: absolute;
  235 + left: 12px;
  236 + height: 10px;
  237 + width: 276px;
  238 + background: url('onion_skin_sprites.gif') -4px -20px repeat-x;
  239 + }
  240 +
  241 + .dragger {
  242 + display: block;
  243 + position: absolute;
  244 + left: 0px;
  245 + top: 0px;
  246 + height: 14px;
  247 + width: 14px;
  248 + background: url('onion_skin_sprites.gif') 0px -34px repeat-x;
  249 + cursor: pointer;
  250 + }
  251 +
  252 + .transparent {
  253 + display: block;
  254 + position: absolute;
  255 + top: 2px;
  256 + right: 0px;
  257 + height: 10px;
  258 + width: 10px;
  259 + background: url('onion_skin_sprites.gif') -2px 0px no-repeat;
  260 + }
  261 +
  262 + .opaque {
  263 + display: block;
  264 + position: absolute;
  265 + top: 2px;
  266 + left: 0px;
  267 + height: 10px;
  268 + width: 10px;
  269 + background: url('onion_skin_sprites.gif') -2px -10px no-repeat;
  270 + }
  271 + }
  272 + } //.view.onion-skin
108 273 }
109   -}
  274 + .view-modes{
110 275  
111   -.diff_file_content{
112   - table {
113   - border: none;
114   - margin: 0px;
115   - padding: 0px;
116   - tr {
117   - td {
118   - font-size: 12px;
119   - }
  276 + padding: 10px;
  277 + text-align: center;
  278 +
  279 + background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
  280 + background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
  281 + background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
  282 + background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
  283 +
  284 + ul, li{
  285 + list-style: none;
  286 + margin: 0;
  287 + padding: 0;
  288 + display: inline-block;
120 289 }
121   - }
122   - .new_line,
123   - .old_line,
124   - .notes_line {
125   - margin:0px;
126   - padding:0px;
127   - border:none;
128   - background:#EEE;
129   - color:#666;
130   - padding: 0px 5px;
131   - border-right: 1px solid #ccc;
132   - text-align: right;
133   - min-width: 35px;
134   - max-width: 35px;
135   - width: 35px;
136   - moz-user-select: none;
137   - -khtml-user-select: none;
138   - user-select: none;
139   -
140   - a {
141   - float: left;
142   - width: 35px;
143   - font-weight: normal;
144   - color: #666;
145   - &:hover {
  290 +
  291 + li{
  292 + color: grey;
  293 + border-left: 1px solid #c1c1c1;
  294 + padding: 0 12px 0 16px;
  295 + cursor: pointer;
  296 + &:first-child{
  297 + border-left: none;
  298 + }
  299 + &:hover{
146 300 text-decoration: underline;
147 301 }
148   - }
149   - }
150   - .line_content {
151   - white-space: pre;
152   - height: 14px;
153   - margin: 0px;
154   - padding: 0px;
155   - border: none;
156   - &.new {
157   - background: #CFD;
158   - }
159   - &.old {
160   - background: #FDD;
161   - }
162   - &.matched {
163   - color: #ccc;
164   - background: #fafafa;
  302 + &.active{
  303 + &:hover{
  304 + text-decoration: none;
  305 + }
  306 + cursor: default;
  307 + color: #333;
  308 + }
  309 + &.disabled{
  310 + display: none;
  311 + }
165 312 }
166 313 }
167 314 }
168 315  
169 316 /** COMMIT BLOCK **/
170   -.commit-title{display: block;}
171   -.commit-title{margin-bottom: 10px}
172   -.commit-author, .commit-committer{display: block;color: #999; font-weight: normal; font-style: italic;}
173   -.commit-author strong, .commit-committer strong{font-weight: bold; font-style: normal;}
  317 +.commit-title{
  318 + display: block;
  319 +}
  320 +.commit-title{
  321 + margin-bottom: 10px;
  322 +}
  323 +.commit-author, .commit-committer{
  324 + display: block;
  325 + color: #999;
  326 + font-weight: normal;
  327 + font-style: italic;
  328 +}
  329 +.commit-author strong, .commit-committer strong{
  330 + font-weight: bold;
  331 + font-style: normal;
  332 +}
174 333  
175 334  
176   -/** COMMIT ROW **/
  335 +/**
  336 + * COMMIT ROW
  337 + */
177 338 .commit {
178 339 .browse_code_link_holder {
179 340 @extend .span2;
... ... @@ -199,11 +360,10 @@
199 360 float: left;
200 361 @extend .lined;
201 362 min-width: 65px;
202   - font-family: $monospace;
  363 + font-family: $monospace_font;
203 364 }
204 365 }
205 366  
206   -.diff_file_header a,
207 367 .file-stats a {
208 368 color: $style_color;
209 369 }
... ... @@ -237,7 +397,7 @@
237 397 font-size: 13px;
238 398 background: #474D57;
239 399 color: #fff;
240   - font-family: $monospace;
  400 + font-family: $monospace_font;
241 401 }
242 402  
243 403  
... ...
app/assets/stylesheets/sections/merge_requests.scss
... ... @@ -77,7 +77,7 @@ li.merge_request {
77 77 font-size: 14px;
78 78 background: #474D57;
79 79 color: #fff;
80   - font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace;
  80 + font-family: $monospace_font;
81 81 }
82 82  
83 83 .mr_source_commit,
... ...
app/assets/stylesheets/sections/notes.scss
... ... @@ -40,13 +40,13 @@ ul.notes {
40 40 .discussion-body {
41 41 margin-left: 50px;
42 42  
43   - .diff_file,
  43 + .file,
44 44 .discussion-hidden,
45 45 .notes {
46 46 @extend .borders;
47 47 background-color: #F9F9F9;
48 48 }
49   - .diff_file .notes {
  49 + .file .notes {
50 50 /* reset */
51 51 background: inherit;
52 52 border: none;
... ... @@ -109,7 +109,7 @@ ul.notes {
109 109 }
110 110 }
111 111  
112   -.diff_file .notes_holder {
  112 +.file .notes_holder {
113 113 font-family: $sansFontFamily;
114 114 font-size: 13px;
115 115 line-height: 18px;
... ... @@ -134,8 +134,6 @@ ul.notes {
134 134 }
135 135 }
136 136  
137   -
138   -
139 137 /**
140 138 * Actions for Discussions/Notes
141 139 */
... ... @@ -171,7 +169,7 @@ ul.notes {
171 169 }
172 170 }
173 171 }
174   -.diff_file .note .note-actions {
  172 +.file .note .note-actions {
175 173 right: 0;
176 174 top: 0;
177 175 }
... ... @@ -182,7 +180,7 @@ ul.notes {
182 180 * Line note button on the side of diffs
183 181 */
184 182  
185   -.diff_file tr.line_holder {
  183 +.file tr.line_holder {
186 184 .add-diff-note {
187 185 background: url("diff_note_add.png") no-repeat left 0;
188 186 height: 22px;
... ... @@ -212,8 +210,6 @@ ul.notes {
212 210 }
213 211 }
214 212  
215   -
216   -
217 213 /**
218 214 * Note Form
219 215 */
... ... @@ -222,7 +218,12 @@ ul.notes {
222 218 .reply-btn {
223 219 @extend .save-btn;
224 220 }
225   -.diff_file,
  221 +.file .content tr.line_holder:hover > td { background: $hover !important; }
  222 +.file .content tr.line_holder:hover > td .line_note_link {
  223 + opacity: 1.0;
  224 + filter: alpha(opacity=100);
  225 +}
  226 +.file,
226 227 .discussion {
227 228 .new_note {
228 229 margin: 8px 5px 8px 0;
... ...
app/assets/stylesheets/sections/projects.scss
... ... @@ -6,7 +6,6 @@
6 6 .side {
7 7 @extend .right;
8 8  
9   - .groups_box,
10 9 .projects_box {
11 10 > .title {
12 11 padding: 2px 15px;
... ...
app/controllers/admin/application_controller.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +# Provides a base class for Admin controllers to subclass
  2 +#
  3 +# Automatically sets the layout and ensures an administrator is logged in
  4 +class Admin::ApplicationController < ApplicationController
  5 + layout 'admin'
  6 + before_filter :authenticate_admin!
  7 +
  8 + def authenticate_admin!
  9 + return render_404 unless current_user.is_admin?
  10 + end
  11 +end
... ...
app/controllers/admin/dashboard_controller.rb
1   -class Admin::DashboardController < AdminController
  1 +class Admin::DashboardController < Admin::ApplicationController
2 2 def index
3 3 @projects = Project.order("created_at DESC").limit(10)
4 4 @users = User.order("created_at DESC").limit(10)
... ...
app/controllers/admin/groups_controller.rb
1   -class Admin::GroupsController < AdminController
  1 +class Admin::GroupsController < Admin::ApplicationController
2 2 before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :project_teams_update]
3 3  
4 4 def index
... ...
app/controllers/admin/hooks_controller.rb
1   -class Admin::HooksController < AdminController
  1 +class Admin::HooksController < Admin::ApplicationController
2 2 def index
3 3 @hooks = SystemHook.all
4 4 @hook = SystemHook.new
... ...
app/controllers/admin/logs_controller.rb
1   -class Admin::LogsController < AdminController
  1 +class Admin::LogsController < Admin::ApplicationController
2 2 end
... ...
app/controllers/admin/projects/application_controller.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +# Provides a base class for Admin controllers to subclass
  2 +#
  3 +# Automatically sets the layout and ensures an administrator is logged in
  4 +class Admin::Projects::ApplicationController < Admin::ApplicationController
  5 +
  6 + protected
  7 +
  8 + def project
  9 + @project ||= Project.find_with_namespace(params[:project_id])
  10 + end
  11 +end
... ...
app/controllers/admin/projects/members_controller.rb 0 → 100644
... ... @@ -0,0 +1,32 @@
  1 +class Admin::Projects::MembersController < Admin::Projects::ApplicationController
  2 + def edit
  3 + @member = team_member
  4 + @project = project
  5 + @team_member_relation = team_member_relation
  6 + end
  7 +
  8 + def update
  9 + if team_member_relation.update_attributes(params[:team_member])
  10 + redirect_to [:admin, project], notice: 'Project Access was successfully updated.'
  11 + else
  12 + render action: "edit"
  13 + end
  14 + end
  15 +
  16 + def destroy
  17 + team_member_relation.destroy
  18 +
  19 + redirect_to :back
  20 + end
  21 +
  22 + private
  23 +
  24 + def team_member
  25 + @member ||= project.users.find_by_username(params[:id])
  26 + end
  27 +
  28 + def team_member_relation
  29 + team_member.users_projects.find_by_project_id(project)
  30 + end
  31 +
  32 +end
... ...
app/controllers/admin/projects_controller.rb
1   -class Admin::ProjectsController < AdminController
  1 +class Admin::ProjectsController < Admin::ApplicationController
2 2 before_filter :project, only: [:edit, :show, :update, :destroy, :team_update]
3 3  
4 4 def index
... ... @@ -29,7 +29,9 @@ class Admin::ProjectsController &lt; AdminController
29 29 end
30 30  
31 31 def update
32   - status = Projects::UpdateContext.new(project, current_user, params).execute(:admin)
  32 + project.creator = current_user unless project.creator
  33 +
  34 + status = ::Projects::UpdateContext.new(project, current_user, params).execute(:admin)
33 35  
34 36 if status
35 37 redirect_to [:admin, @project], notice: 'Project was successfully updated.'
... ...
app/controllers/admin/resque_controller.rb
1   -class Admin::ResqueController < AdminController
  1 +class Admin::ResqueController < Admin::ApplicationController
2 2 def show
3 3 end
4 4 end
... ...
app/controllers/admin/team_members_controller.rb
... ... @@ -1,22 +0,0 @@
1   -class Admin::TeamMembersController < AdminController
2   - def edit
3   - @admin_team_member = UsersProject.find(params[:id])
4   - end
5   -
6   - def update
7   - @admin_team_member = UsersProject.find(params[:id])
8   -
9   - if @admin_team_member.update_attributes(params[:team_member])
10   - redirect_to [:admin, @admin_team_member.project], notice: 'Project Access was successfully updated.'
11   - else
12   - render action: "edit"
13   - end
14   - end
15   -
16   - def destroy
17   - @admin_team_member = UsersProject.find(params[:id])
18   - @admin_team_member.destroy
19   -
20   - redirect_to :back
21   - end
22   -end
app/controllers/admin/teams/application_controller.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +# Provides a base class for Admin controllers to subclass
  2 +#
  3 +# Automatically sets the layout and ensures an administrator is logged in
  4 +class Admin::Teams::ApplicationController < Admin::ApplicationController
  5 +
  6 + private
  7 +
  8 + def user_team
  9 + @team = UserTeam.find_by_path(params[:team_id])
  10 + end
  11 +end
... ...
app/controllers/admin/teams/members_controller.rb 0 → 100644
... ... @@ -0,0 +1,41 @@
  1 +class Admin::Teams::MembersController < Admin::Teams::ApplicationController
  2 + def new
  3 + @users = User.potential_team_members(user_team)
  4 + @users = UserDecorator.decorate @users
  5 + end
  6 +
  7 + def create
  8 + unless params[:user_ids].blank?
  9 + user_ids = params[:user_ids]
  10 + access = params[:default_project_access]
  11 + is_admin = params[:group_admin]
  12 + user_team.add_members(user_ids, access, is_admin)
  13 + end
  14 +
  15 + redirect_to admin_team_path(user_team), notice: 'Members was successfully added into Team of users.'
  16 + end
  17 +
  18 + def edit
  19 + team_member
  20 + end
  21 +
  22 + def update
  23 + options = {default_projects_access: params[:default_project_access], group_admin: params[:group_admin]}
  24 + if user_team.update_membership(team_member, options)
  25 + redirect_to admin_team_path(user_team), notice: "Membership for #{team_member.name} was successfully updated in Team of users."
  26 + else
  27 + render :edit
  28 + end
  29 + end
  30 +
  31 + def destroy
  32 + user_team.remove_member(team_member)
  33 + redirect_to admin_team_path(user_team), notice: "Member #{team_member.name} was successfully removed from Team of users."
  34 + end
  35 +
  36 + protected
  37 +
  38 + def team_member
  39 + @member ||= user_team.members.find_by_username(params[:id])
  40 + end
  41 +end
... ...
app/controllers/admin/teams/projects_controller.rb 0 → 100644
... ... @@ -0,0 +1,41 @@
  1 +class Admin::Teams::ProjectsController < Admin::Teams::ApplicationController
  2 + def new
  3 + @projects = Project.scoped
  4 + @projects = @projects.without_team(user_team) if user_team.projects.any?
  5 + #@projects.reject!(&:empty_repo?)
  6 + end
  7 +
  8 + def create
  9 + unless params[:project_ids].blank?
  10 + project_ids = params[:project_ids]
  11 + access = params[:greatest_project_access]
  12 + user_team.assign_to_projects(project_ids, access)
  13 + end
  14 +
  15 + redirect_to admin_team_path(user_team), notice: 'Team of users was successfully assgned to projects.'
  16 + end
  17 +
  18 + def edit
  19 + team_project
  20 + end
  21 +
  22 + def update
  23 + if user_team.update_project_access(team_project, params[:greatest_project_access])
  24 + redirect_to admin_team_path(user_team), notice: 'Access was successfully updated.'
  25 + else
  26 + render :edit
  27 + end
  28 + end
  29 +
  30 + def destroy
  31 + user_team.resign_from_project(team_project)
  32 + redirect_to admin_team_path(user_team), notice: 'Team of users was successfully reassigned from project.'
  33 + end
  34 +
  35 + protected
  36 +
  37 + def team_project
  38 + @project ||= user_team.projects.find_with_namespace(params[:id])
  39 + end
  40 +
  41 +end
... ...
app/controllers/admin/teams_controller.rb 0 → 100644
... ... @@ -0,0 +1,59 @@
  1 +class Admin::TeamsController < Admin::ApplicationController
  2 + def index
  3 + @teams = UserTeam.order('name ASC')
  4 + @teams = @teams.search(params[:name]) if params[:name].present?
  5 + @teams = @teams.page(params[:page]).per(20)
  6 + end
  7 +
  8 + def show
  9 + user_team
  10 + end
  11 +
  12 + def new
  13 + @team = UserTeam.new
  14 + end
  15 +
  16 + def edit
  17 + user_team
  18 + end
  19 +
  20 + def create
  21 + @team = UserTeam.new(params[:user_team])
  22 + @team.path = @team.name.dup.parameterize if @team.name
  23 + @team.owner = current_user
  24 +
  25 + if @team.save
  26 + redirect_to admin_team_path(@team), notice: 'Team of users was successfully created.'
  27 + else
  28 + render action: "new"
  29 + end
  30 + end
  31 +
  32 + def update
  33 + user_team_params = params[:user_team].dup
  34 + owner_id = user_team_params.delete(:owner_id)
  35 +
  36 + if owner_id
  37 + user_team.owner = User.find(owner_id)
  38 + end
  39 +
  40 + if user_team.update_attributes(user_team_params)
  41 + redirect_to admin_team_path(user_team), notice: 'Team of users was successfully updated.'
  42 + else
  43 + render action: "edit"
  44 + end
  45 + end
  46 +
  47 + def destroy
  48 + user_team.destroy
  49 +
  50 + redirect_to admin_teams_path, notice: 'Team of users was successfully deleted.'
  51 + end
  52 +
  53 + protected
  54 +
  55 + def user_team
  56 + @team ||= UserTeam.find_by_path(params[:id])
  57 + end
  58 +
  59 +end
... ...
app/controllers/admin/users_controller.rb
1   -class Admin::UsersController < AdminController
  1 +class Admin::UsersController < Admin::ApplicationController
  2 + before_filter :admin_user, only: [:show, :edit, :update, :destroy]
  3 +
2 4 def index
3 5 @admin_users = User.scoped
4 6 @admin_users = @admin_users.filter(params[:filter])
... ... @@ -7,25 +9,18 @@ class Admin::UsersController &lt; AdminController
7 9 end
8 10  
9 11 def show
10   - @admin_user = User.find(params[:id])
11   -
12   - @projects = if @admin_user.authorized_projects.empty?
13   - Project
14   - else
15   - Project.without_user(@admin_user)
16   - end.all
  12 + @projects = Project.scoped
  13 + @projects = @projects.without_user(admin_user) if admin_user.authorized_projects.present?
17 14 end
18 15  
19 16 def team_update
20   - @admin_user = User.find(params[:id])
21   -
22 17 UsersProject.add_users_into_projects(
23 18 params[:project_ids],
24   - [@admin_user.id],
  19 + [admin_user.id],
25 20 params[:project_access]
26 21 )
27 22  
28   - redirect_to [:admin, @admin_user], notice: 'Teams were successfully updated.'
  23 + redirect_to [:admin, admin_user], notice: 'Teams were successfully updated.'
29 24 end
30 25  
31 26  
... ... @@ -34,13 +29,11 @@ class Admin::UsersController &lt; AdminController
34 29 end
35 30  
36 31 def edit
37   - @admin_user = User.find(params[:id])
  32 + admin_user
38 33 end
39 34  
40 35 def block
41   - @admin_user = User.find(params[:id])
42   -
43   - if @admin_user.block
  36 + if admin_user.block
44 37 redirect_to :back, alert: "Successfully blocked"
45 38 else
46 39 redirect_to :back, alert: "Error occured. User was not blocked"
... ... @@ -48,9 +41,7 @@ class Admin::UsersController &lt; AdminController
48 41 end
49 42  
50 43 def unblock
51   - @admin_user = User.find(params[:id])
52   -
53   - if @admin_user.update_attribute(:blocked, false)
  44 + if admin_user.update_attribute(:blocked, false)
54 45 redirect_to :back, alert: "Successfully unblocked"
55 46 else
56 47 redirect_to :back, alert: "Error occured. User was not unblocked"
... ... @@ -82,30 +73,34 @@ class Admin::UsersController &lt; AdminController
82 73 params[:user].delete(:password_confirmation)
83 74 end
84 75  
85   - @admin_user = User.find(params[:id])
86   - @admin_user.admin = (admin && admin.to_i > 0)
  76 + admin_user.admin = (admin && admin.to_i > 0)
87 77  
88 78 respond_to do |format|
89   - if @admin_user.update_attributes(params[:user], as: :admin)
90   - format.html { redirect_to [:admin, @admin_user], notice: 'User was successfully updated.' }
  79 + if admin_user.update_attributes(params[:user], as: :admin)
  80 + format.html { redirect_to [:admin, admin_user], notice: 'User was successfully updated.' }
91 81 format.json { head :ok }
92 82 else
93 83 format.html { render action: "edit" }
94   - format.json { render json: @admin_user.errors, status: :unprocessable_entity }
  84 + format.json { render json: admin_user.errors, status: :unprocessable_entity }
95 85 end
96 86 end
97 87 end
98 88  
99 89 def destroy
100   - @admin_user = User.find(params[:id])
101   - if @admin_user.personal_projects.count > 0
  90 + if admin_user.personal_projects.count > 0
102 91 redirect_to admin_users_path, alert: "User is a project owner and can't be removed." and return
103 92 end
104   - @admin_user.destroy
  93 + admin_user.destroy
105 94  
106 95 respond_to do |format|
107   - format.html { redirect_to admin_users_url }
  96 + format.html { redirect_to admin_users_path }
108 97 format.json { head :ok }
109 98 end
110 99 end
  100 +
  101 + protected
  102 +
  103 + def admin_user
  104 + @admin_user ||= User.find_by_username!(params[:id])
  105 + end
111 106 end
... ...
app/controllers/admin_controller.rb
... ... @@ -1,11 +0,0 @@
1   -# Provides a base class for Admin controllers to subclass
2   -#
3   -# Automatically sets the layout and ensures an administrator is logged in
4   -class AdminController < ApplicationController
5   - layout 'admin'
6   - before_filter :authenticate_admin!
7   -
8   - def authenticate_admin!
9   - return render_404 unless current_user.is_admin?
10   - end
11   -end
app/controllers/application_controller.rb
... ... @@ -94,6 +94,18 @@ class ApplicationController &lt; ActionController::Base
94 94 return access_denied! unless can?(current_user, :download_code, project)
95 95 end
96 96  
  97 + def authorize_create_team!
  98 + return access_denied! unless can?(current_user, :create_team, nil)
  99 + end
  100 +
  101 + def authorize_manage_user_team!
  102 + return access_denied! unless user_team.present? && can?(current_user, :manage_user_team, user_team)
  103 + end
  104 +
  105 + def authorize_admin_user_team!
  106 + return access_denied! unless user_team.present? && can?(current_user, :admin_user_team, user_team)
  107 + end
  108 +
97 109 def access_denied!
98 110 render "errors/access_denied", layout: "errors", status: 404
99 111 end
... ... @@ -135,4 +147,5 @@ class ApplicationController &lt; ActionController::Base
135 147 def dev_tools
136 148 Rack::MiniProfiler.authorize_request
137 149 end
  150 +
138 151 end
... ...
app/controllers/dashboard_controller.rb
1 1 class DashboardController < ApplicationController
2 2 respond_to :html
3 3  
4   - before_filter :projects
5   - before_filter :event_filter, only: :index
  4 + before_filter :load_projects
  5 + before_filter :event_filter, only: :show
6 6  
7   - def index
  7 + def show
8 8 @groups = current_user.authorized_groups
9   -
10 9 @has_authorized_projects = @projects.count > 0
11   -
12   - @projects = case params[:scope]
13   - when 'personal' then
14   - @projects.personal(current_user)
15   - when 'joined' then
16   - @projects.joined(current_user)
17   - else
18   - @projects
19   - end
20   -
21   - @projects = @projects.page(params[:page]).per(30)
  10 + @teams = current_user.authorized_teams
  11 + @projects_count = @projects.count
  12 + @projects = @projects.limit(20)
22 13  
23 14 @events = Event.in_projects(current_user.authorized_projects.pluck(:id))
24 15 @events = @event_filter.apply_filter(@events)
... ... @@ -33,6 +24,19 @@ class DashboardController &lt; ApplicationController
33 24 end
34 25 end
35 26  
  27 + def projects
  28 + @projects = case params[:scope]
  29 + when 'personal' then
  30 + @projects.personal(current_user)
  31 + when 'joined' then
  32 + @projects.joined(current_user)
  33 + else
  34 + @projects
  35 + end
  36 +
  37 + @projects = @projects.page(params[:page]).per(30)
  38 + end
  39 +
36 40 # Get authored or assigned open merge requests
37 41 def merge_requests
38 42 @merge_requests = current_user.cared_merge_requests
... ... @@ -55,7 +59,7 @@ class DashboardController &lt; ApplicationController
55 59  
56 60 protected
57 61  
58   - def projects
  62 + def load_projects
59 63 @projects = current_user.authorized_projects.sorted_by_activity
60 64 end
61 65  
... ...
app/controllers/groups_controller.rb
1 1 class GroupsController < ApplicationController
2 2 respond_to :html
3   - layout 'group'
  3 + layout 'group', except: [:new, :create]
4 4  
5   - before_filter :group
6   - before_filter :projects
  5 + before_filter :group, except: [:new, :create]
7 6  
8 7 # Authorize
9   - before_filter :authorize_read_group!
  8 + before_filter :authorize_read_group!, except: [:new, :create]
  9 + before_filter :authorize_create_group!, only: [:new, :create]
  10 +
  11 + # Load group projects
  12 + before_filter :projects, except: [:new, :create]
  13 +
  14 + def new
  15 + @group = Group.new
  16 + end
  17 +
  18 + def create
  19 + @group = Group.new(params[:group])
  20 + @group.path = @group.name.dup.parameterize if @group.name
  21 + @group.owner = current_user
  22 +
  23 + if @group.save
  24 + redirect_to @group, notice: 'Group was successfully created.'
  25 + else
  26 + render action: "new"
  27 + end
  28 + end
10 29  
11 30 def show
12 31 @events = Event.in_projects(project_ids).limit(20).offset(params[:offset] || 0)
... ... @@ -85,4 +104,8 @@ class GroupsController &lt; ApplicationController
85 104 return render_404
86 105 end
87 106 end
  107 +
  108 + def authorize_create_group!
  109 + can?(current_user, :create_group, nil)
  110 + end
88 111 end
... ...
app/controllers/projects/application_controller.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +class Projects::ApplicationController < ApplicationController
  2 +
  3 + before_filter :authorize_admin_team_member!
  4 +
  5 + protected
  6 +
  7 + def user_team
  8 + @team ||= UserTeam.find_by_path(params[:id])
  9 + end
  10 +
  11 +end
... ...
app/controllers/projects/teams_controller.rb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +class Projects::TeamsController < Projects::ApplicationController
  2 +
  3 + def available
  4 + @teams = current_user.is_admin? ? UserTeam.scoped : current_user.user_teams
  5 + @teams = @teams.without_project(project)
  6 + unless @teams.any?
  7 + redirect_to project_team_index_path(project), notice: "No avaliable teams for assigment."
  8 + end
  9 + end
  10 +
  11 + def assign
  12 + unless params[:team_id].blank?
  13 + team = UserTeam.find(params[:team_id])
  14 + access = params[:greatest_project_access]
  15 + team.assign_to_project(project, access)
  16 + end
  17 + redirect_to project_team_index_path(project)
  18 + end
  19 +
  20 + def resign
  21 + team = project.user_teams.find_by_path(params[:id])
  22 + team.resign_from_project(project)
  23 +
  24 + redirect_to project_team_index_path(project)
  25 + end
  26 +
  27 +end
... ...
app/controllers/projects_controller.rb
... ... @@ -19,7 +19,7 @@ class ProjectsController &lt; ProjectResourceController
19 19 end
20 20  
21 21 def create
22   - @project = Projects::CreateContext.new(current_user, params[:project]).execute
  22 + @project = ::Projects::CreateContext.new(current_user, params[:project]).execute
23 23  
24 24 respond_to do |format|
25 25 flash[:notice] = 'Project was successfully created.' if @project.saved?
... ... @@ -35,7 +35,7 @@ class ProjectsController &lt; ProjectResourceController
35 35 end
36 36  
37 37 def update
38   - status = Projects::UpdateContext.new(project, current_user, params).execute
  38 + status = ::Projects::UpdateContext.new(project, current_user, params).execute
39 39  
40 40 respond_to do |format|
41 41 if status
... ...
app/controllers/search_controller.rb
1 1 class SearchController < ApplicationController
2 2 def show
3   - result = SearchContext.new(current_user.authorized_projects.map(&:id), params).execute
  3 + project_id = params[:project_id]
  4 + group_id = params[:group_id]
  5 +
  6 + project_ids = current_user.authorized_projects.map(&:id)
  7 +
  8 + if group_id.present?
  9 + group_project_ids = Group.find(group_id).projects.map(&:id)
  10 + project_ids.select! { |id| group_project_ids.include?(id)}
  11 + elsif project_id.present?
  12 + project_ids.select! { |id| id == project_id.to_i}
  13 + end
  14 +
  15 + result = SearchContext.new(project_ids, params).execute
4 16  
5 17 @projects = result[:projects]
6 18 @merge_requests = result[:merge_requests]
... ...
app/controllers/team_members_controller.rb
... ... @@ -4,15 +4,16 @@ class TeamMembersController &lt; ProjectResourceController
4 4 before_filter :authorize_admin_project!, except: [:index, :show]
5 5  
6 6 def index
  7 + @teams = UserTeam.scoped
7 8 end
8 9  
9 10 def show
10   - @team_member = project.users_projects.find(params[:id])
11   - @events = @team_member.user.recent_events.where(:project_id => @project.id).limit(7)
  11 + @user_project_relation = project.users_projects.find_by_user_id(member)
  12 + @events = member.recent_events.in_projects(project).limit(7)
12 13 end
13 14  
14 15 def new
15   - @team_member = project.users_projects.new
  16 + @user_project_relation = project.users_projects.new
16 17 end
17 18  
18 19 def create
... ... @@ -28,18 +29,18 @@ class TeamMembersController &lt; ProjectResourceController
28 29 end
29 30  
30 31 def update
31   - @team_member = project.users_projects.find(params[:id])
32   - @team_member.update_attributes(params[:team_member])
  32 + @user_project_relation = project.users_projects.find_by_user_id(member)
  33 + @user_project_relation.update_attributes(params[:team_member])
33 34  
34   - unless @team_member.valid?
  35 + unless @user_project_relation.valid?
35 36 flash[:alert] = "User should have at least one role"
36 37 end
37 38 redirect_to project_team_index_path(@project)
38 39 end
39 40  
40 41 def destroy
41   - @team_member = project.users_projects.find(params[:id])
42   - @team_member.destroy
  42 + @user_project_relation = project.users_projects.find_by_user_id(member)
  43 + @user_project_relation.destroy
43 44  
44 45 respond_to do |format|
45 46 format.html { redirect_to project_team_index_path(@project) }
... ... @@ -54,4 +55,10 @@ class TeamMembersController &lt; ProjectResourceController
54 55  
55 56 redirect_to project_team_members_path(project), notice: notice
56 57 end
  58 +
  59 + protected
  60 +
  61 + def member
  62 + @member ||= User.find_by_username(params[:id])
  63 + end
57 64 end
... ...
app/controllers/teams/application_controller.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +class Teams::ApplicationController < ApplicationController
  2 +
  3 + layout 'user_team'
  4 +
  5 + before_filter :authorize_manage_user_team!
  6 +
  7 + protected
  8 +
  9 + def user_team
  10 + @team ||= UserTeam.find_by_path(params[:team_id])
  11 + end
  12 +
  13 +end
... ...
app/controllers/teams/members_controller.rb 0 → 100644
... ... @@ -0,0 +1,49 @@
  1 +class Teams::MembersController < Teams::ApplicationController
  2 +
  3 + skip_before_filter :authorize_manage_user_team!, only: [:index]
  4 +
  5 + def index
  6 + @members = user_team.members
  7 + end
  8 +
  9 + def new
  10 + @users = User.potential_team_members(user_team)
  11 + @users = UserDecorator.decorate @users
  12 + end
  13 +
  14 + def create
  15 + unless params[:user_ids].blank?
  16 + user_ids = params[:user_ids]
  17 + access = params[:default_project_access]
  18 + is_admin = params[:group_admin]
  19 + user_team.add_members(user_ids, access, is_admin)
  20 + end
  21 +
  22 + redirect_to team_members_path(user_team), notice: 'Members was successfully added into Team of users.'
  23 + end
  24 +
  25 + def edit
  26 + team_member
  27 + end
  28 +
  29 + def update
  30 + options = {default_projects_access: params[:default_project_access], group_admin: params[:group_admin]}
  31 + if user_team.update_membership(team_member, options)
  32 + redirect_to team_members_path(user_team), notice: "Membership for #{team_member.name} was successfully updated in Team of users."
  33 + else
  34 + render :edit
  35 + end
  36 + end
  37 +
  38 + def destroy
  39 + user_team.remove_member(team_member)
  40 + redirect_to team_path(user_team), notice: "Member #{team_member.name} was successfully removed from Team of users."
  41 + end
  42 +
  43 + protected
  44 +
  45 + def team_member
  46 + @member ||= user_team.members.find_by_username(params[:id])
  47 + end
  48 +
  49 +end
... ...
app/controllers/teams/projects_controller.rb 0 → 100644
... ... @@ -0,0 +1,57 @@
  1 +class Teams::ProjectsController < Teams::ApplicationController
  2 +
  3 + skip_before_filter :authorize_manage_user_team!, only: [:index]
  4 +
  5 + def index
  6 + @projects = user_team.projects
  7 + @avaliable_projects = current_user.admin? ? Project.without_team(user_team) : current_user.owned_projects.without_team(user_team)
  8 + end
  9 +
  10 + def new
  11 + user_team
  12 + @avaliable_projects = current_user.owned_projects.scoped
  13 + @avaliable_projects = @avaliable_projects.without_team(user_team) if user_team.projects.any?
  14 +
  15 + redirect_to team_projects_path(user_team), notice: "No avalible projects." unless @avaliable_projects.any?
  16 + end
  17 +
  18 + def create
  19 + redirect_to :back if params[:project_ids].blank?
  20 +
  21 + project_ids = params[:project_ids]
  22 + access = params[:greatest_project_access]
  23 +
  24 + # Reject non-allowed projects
  25 + allowed_project_ids = current_user.owned_projects.map(&:id)
  26 + project_ids.select! { |id| allowed_project_ids.include?(id.to_i) }
  27 +
  28 + # Assign projects to team
  29 + user_team.assign_to_projects(project_ids, access)
  30 +
  31 + redirect_to team_projects_path(user_team), notice: 'Team of users was successfully assigned to projects.'
  32 + end
  33 +
  34 + def edit
  35 + team_project
  36 + end
  37 +
  38 + def update
  39 + if user_team.update_project_access(team_project, params[:greatest_project_access])
  40 + redirect_to team_projects_path(user_team), notice: 'Access was successfully updated.'
  41 + else
  42 + render :edit
  43 + end
  44 + end
  45 +
  46 + def destroy
  47 + user_team.resign_from_project(team_project)
  48 + redirect_to team_projects_path(user_team), notice: 'Team of users was successfully reassigned from project.'
  49 + end
  50 +
  51 + private
  52 +
  53 + def team_project
  54 + @project ||= user_team.projects.find_with_namespace(params[:id])
  55 + end
  56 +
  57 +end
... ...
app/controllers/teams_controller.rb 0 → 100644
... ... @@ -0,0 +1,76 @@
  1 +class TeamsController < ApplicationController
  2 + # Authorize
  3 + before_filter :authorize_create_team!, only: [:new, :create]
  4 + before_filter :authorize_manage_user_team!, only: [:edit, :update]
  5 + before_filter :authorize_admin_user_team!, only: [:destroy]
  6 +
  7 + before_filter :user_team, except: [:new, :create]
  8 +
  9 + layout 'user_team', except: [:new, :create]
  10 +
  11 + def show
  12 + user_team
  13 + projects
  14 + @events = Event.in_projects(user_team.project_ids).limit(20).offset(params[:offset] || 0)
  15 + end
  16 +
  17 + def edit
  18 + user_team
  19 + end
  20 +
  21 + def update
  22 + if user_team.update_attributes(params[:user_team])
  23 + redirect_to team_path(user_team)
  24 + else
  25 + render action: :edit
  26 + end
  27 + end
  28 +
  29 + def destroy
  30 + user_team.destroy
  31 + redirect_to dashboard_path
  32 + end
  33 +
  34 + def new
  35 + @team = UserTeam.new
  36 + end
  37 +
  38 + def create
  39 + @team = UserTeam.new(params[:user_team])
  40 + @team.owner = current_user unless params[:owner]
  41 + @team.path = @team.name.dup.parameterize if @team.name
  42 +
  43 + if @team.save
  44 + redirect_to team_path(@team)
  45 + else
  46 + render action: :new
  47 + end
  48 + end
  49 +
  50 + # Get authored or assigned open merge requests
  51 + def merge_requests
  52 + projects
  53 + @merge_requests = MergeRequest.of_user_team(user_team)
  54 + @merge_requests = FilterContext.new(@merge_requests, params).execute
  55 + @merge_requests = @merge_requests.recent.page(params[:page]).per(20)
  56 + end
  57 +
  58 + # Get only assigned issues
  59 + def issues
  60 + projects
  61 + @issues = Issue.of_user_team(user_team)
  62 + @issues = FilterContext.new(@issues, params).execute
  63 + @issues = @issues.recent.page(params[:page]).per(20)
  64 + @issues = @issues.includes(:author, :project)
  65 + end
  66 +
  67 + protected
  68 +
  69 + def projects
  70 + @projects ||= user_team.projects.sorted_by_activity
  71 + end
  72 +
  73 + def user_team
  74 + @team ||= current_user.authorized_teams.find_by_path(params[:id])
  75 + end
  76 +end
... ...
app/decorators/user_decorator.rb
... ... @@ -8,4 +8,8 @@ class UserDecorator &lt; ApplicationDecorator
8 8 def tm_of(project)
9 9 project.team_member_by_id(self.id)
10 10 end
  11 +
  12 + def name_with_email
  13 + "#{name} (#{email})"
  14 + end
11 15 end
... ...
app/helpers/admin/teams/members_helper.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +module Admin::Teams::MembersHelper
  2 + def member_since(team, member)
  3 + team.user_team_user_relationships.find_by_user_id(member).created_at
  4 + end
  5 +end
... ...
app/helpers/admin/teams/projects_helper.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +module Admin::Teams::ProjectsHelper
  2 + def assigned_since(team, project)
  3 + team.user_team_project_relationships.find_by_project_id(project).created_at
  4 + end
  5 +end
... ...
app/helpers/application_helper.rb
... ... @@ -72,8 +72,9 @@ module ApplicationHelper
72 72 end
73 73  
74 74 def search_autocomplete_source
75   - projects = current_user.authorized_projects.map { |p| { label: p.name_with_namespace, url: project_path(p) } }
76   - groups = current_user.authorized_groups.map { |group| { label: "<group> #{group.name}", url: group_path(group) } }
  75 + projects = current_user.authorized_projects.map { |p| { label: "project: #{p.name_with_namespace}", url: project_path(p) } }
  76 + groups = current_user.authorized_groups.map { |group| { label: "group: #{group.name}", url: group_path(group) } }
  77 + teams = current_user.authorized_teams.map { |team| { label: "team: #{team.name}", url: team_path(team) } }
77 78  
78 79 default_nav = [
79 80 { label: "My Profile", url: profile_path },
... ... @@ -83,29 +84,29 @@ module ApplicationHelper
83 84 ]
84 85  
85 86 help_nav = [
86   - { label: "API Help", url: help_api_path },
87   - { label: "Markdown Help", url: help_markdown_path },
88   - { label: "Permissions Help", url: help_permissions_path },
89   - { label: "Public Access Help", url: help_public_access_path },
90   - { label: "Rake Tasks Help", url: help_raketasks_path },
91   - { label: "SSH Keys Help", url: help_ssh_path },
92   - { label: "System Hooks Help", url: help_system_hooks_path },
93   - { label: "Web Hooks Help", url: help_web_hooks_path },
94   - { label: "Workflow Help", url: help_workflow_path },
  87 + { label: "help: API Help", url: help_api_path },
  88 + { label: "help: Markdown Help", url: help_markdown_path },
  89 + { label: "help: Permissions Help", url: help_permissions_path },
  90 + { label: "help: Public Access Help", url: help_public_access_path },
  91 + { label: "help: Rake Tasks Help", url: help_raketasks_path },
  92 + { label: "help: SSH Keys Help", url: help_ssh_path },
  93 + { label: "help: System Hooks Help", url: help_system_hooks_path },
  94 + { label: "help: Web Hooks Help", url: help_web_hooks_path },
  95 + { label: "help: Workflow Help", url: help_workflow_path },
95 96 ]
96 97  
97 98 project_nav = []
98 99 if @project && @project.repository && @project.repository.root_ref
99 100 project_nav = [
100   - { label: "#{@project.name} Issues", url: project_issues_path(@project) },
101   - { label: "#{@project.name} Commits", url: project_commits_path(@project, @ref || @project.repository.root_ref) },
102   - { label: "#{@project.name} Merge Requests", url: project_merge_requests_path(@project) },
103   - { label: "#{@project.name} Milestones", url: project_milestones_path(@project) },
104   - { label: "#{@project.name} Snippets", url: project_snippets_path(@project) },
105   - { label: "#{@project.name} Team", url: project_team_index_path(@project) },
106   - { label: "#{@project.name} Tree", url: project_tree_path(@project, @ref || @project.repository.root_ref) },
107   - { label: "#{@project.name} Wall", url: wall_project_path(@project) },
108   - { label: "#{@project.name} Wiki", url: project_wikis_path(@project) },
  101 + { label: "#{@project.name_with_namespace} - Issues", url: project_issues_path(@project) },
  102 + { label: "#{@project.name_with_namespace} - Commits", url: project_commits_path(@project, @ref || @project.repository.root_ref) },
  103 + { label: "#{@project.name_with_namespace} - Merge Requests", url: project_merge_requests_path(@project) },
  104 + { label: "#{@project.name_with_namespace} - Milestones", url: project_milestones_path(@project) },
  105 + { label: "#{@project.name_with_namespace} - Snippets", url: project_snippets_path(@project) },
  106 + { label: "#{@project.name_with_namespace} - Team", url: project_team_index_path(@project) },
  107 + { label: "#{@project.name_with_namespace} - Tree", url: project_tree_path(@project, @ref || @project.repository.root_ref) },
  108 + { label: "#{@project.name_with_namespace} - Wall", url: wall_project_path(@project) },
  109 + { label: "#{@project.name_with_namespace} - Wiki", url: project_wikis_path(@project) },
109 110 ]
110 111 end
111 112  
... ...
app/helpers/commits_helper.rb
... ... @@ -59,9 +59,9 @@ module CommitsHelper
59 59  
60 60 def image_diff_class(diff)
61 61 if diff.deleted_file
62   - "diff_removed"
  62 + "deleted"
63 63 elsif diff.new_file
64   - "diff_added"
  64 + "added"
65 65 else
66 66 nil
67 67 end
... ...
app/helpers/dashboard_helper.rb
... ... @@ -9,9 +9,9 @@ module DashboardHelper
9 9  
10 10 case entity
11 11 when 'issue' then
12   - dashboard_issues_path(options)
  12 + issues_dashboard_path(options)
13 13 when 'merge_request'
14   - dashboard_merge_requests_path(options)
  14 + merge_requests_dashboard_path(options)
15 15 end
16 16 end
17 17  
... ...
app/helpers/projects_helper.rb
... ... @@ -3,8 +3,12 @@ module ProjectsHelper
3 3 @project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access)
4 4 end
5 5  
6   - def remove_from_team_message(project, member)
7   - "You are going to remove #{member.user_name} from #{project.name}. Are you sure?"
  6 + def grouper_project_teams(project)
  7 + @project.user_team_project_relationships.sort_by(&:greatest_access).reverse.group_by(&:greatest_access)
  8 + end
  9 +
  10 + def remove_from_project_team_message(project, user)
  11 + "You are going to remove #{user.name} from #{project.name} project team. Are you sure?"
8 12 end
9 13  
10 14 def link_to_project project
... ... @@ -51,7 +55,9 @@ module ProjectsHelper
51 55  
52 56 def project_title project
53 57 if project.group
54   - project.name_with_namespace
  58 + content_tag :span do
  59 + link_to(project.group.name, group_path(project.group)) + " / " + project.name
  60 + end
55 61 else
56 62 project.name
57 63 end
... ...
app/helpers/tab_helper.rb
... ... @@ -39,7 +39,12 @@ module TabHelper
39 39 # Returns a list item element String
40 40 def nav_link(options = {}, &block)
41 41 if path = options.delete(:path)
42   - c, a, _ = path.split('#')
  42 + if path.respond_to?(:each)
  43 + c = path.map { |p| p.split('#').first }
  44 + a = path.map { |p| p.split('#').last }
  45 + else
  46 + c, a, _ = path.split('#')
  47 + end
43 48 else
44 49 c = options.delete(:controller)
45 50 a = options.delete(:action)
... ...
app/helpers/user_teams_helper.rb 0 → 100644
... ... @@ -0,0 +1,26 @@
  1 +module UserTeamsHelper
  2 + def team_filter_path(entity, options={})
  3 + exist_opts = {
  4 + status: params[:status],
  5 + project_id: params[:project_id],
  6 + }
  7 +
  8 + options = exist_opts.merge(options)
  9 +
  10 + case entity
  11 + when 'issue' then
  12 + issues_team_path(@team, options)
  13 + when 'merge_request'
  14 + merge_requests_team_path(@team, options)
  15 + end
  16 + end
  17 +
  18 + def grouped_user_team_members(team)
  19 + team.user_team_user_relationships.sort_by(&:permission).reverse.group_by(&:permission)
  20 + end
  21 +
  22 + def remove_from_user_team_message(team, member)
  23 + "You are going to remove #{member.name} from #{team.name}. Are you sure?"
  24 + end
  25 +
  26 +end
... ...
app/models/ability.rb
1 1 class Ability
2 2 class << self
3   - def allowed(object, subject)
  3 + def allowed(user, subject)
  4 + return [] unless user.kind_of?(User)
  5 +
4 6 case subject.class.name
5   - when "Project" then project_abilities(object, subject)
6   - when "Issue" then issue_abilities(object, subject)
7   - when "Note" then note_abilities(object, subject)
8   - when "Snippet" then snippet_abilities(object, subject)
9   - when "MergeRequest" then merge_request_abilities(object, subject)
10   - when "Group", "Namespace" then group_abilities(object, subject)
  7 + when "Project" then project_abilities(user, subject)
  8 + when "Issue" then issue_abilities(user, subject)
  9 + when "Note" then note_abilities(user, subject)
  10 + when "Snippet" then snippet_abilities(user, subject)
  11 + when "MergeRequest" then merge_request_abilities(user, subject)
  12 + when "Group", "Namespace" then group_abilities(user, subject)
  13 + when "UserTeam" then user_team_abilities(user, subject)
11 14 else []
12   - end
  15 + end.concat(global_abilities(user))
  16 + end
  17 +
  18 + def global_abilities(user)
  19 + rules = []
  20 + rules << :create_group if user.can_create_group
  21 + rules << :create_team if user.can_create_team
  22 + rules
13 23 end
14 24  
15 25 def project_abilities(user, project)
... ... @@ -110,6 +120,22 @@ class Ability
110 120 rules.flatten
111 121 end
112 122  
  123 + def user_team_abilities user, team
  124 + rules = []
  125 +
  126 + # Only group owner and administrators can manage group
  127 + if team.owner == user || team.admin?(user) || user.admin?
  128 + rules << [ :manage_user_team ]
  129 + end
  130 +
  131 + if team.owner == user || user.admin?
  132 + rules << [ :admin_user_team ]
  133 + end
  134 +
  135 + rules.flatten
  136 + end
  137 +
  138 +
113 139 [:issue, :note, :snippet, :merge_request].each do |name|
114 140 define_method "#{name}_abilities" do |user, subject|
115 141 if subject.author == user
... ...
app/models/concerns/issuable.rb
... ... @@ -22,6 +22,7 @@ module Issuable
22 22 scope :opened, where(closed: false)
23 23 scope :closed, where(closed: true)
24 24 scope :of_group, ->(group) { where(project_id: group.project_ids) }
  25 + scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
25 26 scope :assigned, ->(u) { where(assignee_id: u.id)}
26 27 scope :recent, order("created_at DESC")
27 28  
... ...
app/models/project.rb
... ... @@ -33,28 +33,31 @@ class Project &lt; ActiveRecord::Base
33 33 attr_accessor :error_code
34 34  
35 35 # Relations
36   - belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'"
  36 + belongs_to :creator, foreign_key: "creator_id", class_name: "User"
  37 + belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'"
37 38 belongs_to :namespace
38 39  
39   - belongs_to :creator,
40   - class_name: "User",
41   - foreign_key: "creator_id"
42   -
43   - has_many :users, through: :users_projects
44   - has_many :events, dependent: :destroy
45   - has_many :merge_requests, dependent: :destroy
46   - has_many :issues, dependent: :destroy, order: "closed, created_at DESC"
47   - has_many :milestones, dependent: :destroy
48   - has_many :users_projects, dependent: :destroy
49   - has_many :notes, dependent: :destroy
50   - has_many :snippets, dependent: :destroy
51   - has_many :deploy_keys, dependent: :destroy, foreign_key: "project_id", class_name: "Key"
52   - has_many :hooks, dependent: :destroy, class_name: "ProjectHook"
53   - has_many :wikis, dependent: :destroy
54   - has_many :protected_branches, dependent: :destroy
55 40 has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id'
56 41 has_one :gitlab_ci_service, dependent: :destroy
57 42  
  43 + has_many :events, dependent: :destroy
  44 + has_many :merge_requests, dependent: :destroy
  45 + has_many :issues, dependent: :destroy, order: "closed, created_at DESC"
  46 + has_many :milestones, dependent: :destroy
  47 + has_many :users_projects, dependent: :destroy
  48 + has_many :notes, dependent: :destroy
  49 + has_many :snippets, dependent: :destroy
  50 + has_many :deploy_keys, dependent: :destroy, class_name: "Key", foreign_key: "project_id"
  51 + has_many :hooks, dependent: :destroy, class_name: "ProjectHook"
  52 + has_many :wikis, dependent: :destroy
  53 + has_many :protected_branches, dependent: :destroy
  54 + has_many :user_team_project_relationships, dependent: :destroy
  55 +
  56 + has_many :users, through: :users_projects
  57 + has_many :user_teams, through: :user_team_project_relationships
  58 + has_many :user_team_user_relationships, through: :user_teams
  59 + has_many :user_teams_members, through: :user_team_user_relationships
  60 +
58 61 delegate :name, to: :owner, allow_nil: true, prefix: true
59 62  
60 63 # Validations
... ... @@ -77,6 +80,8 @@ class Project &lt; ActiveRecord::Base
77 80 # Scopes
78 81 scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) }
79 82 scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) }
  83 + scope :without_team, ->(team) { team.projects.present? ? where("id NOT IN (:ids)", ids: team.projects.map(&:id)) : scoped }
  84 + scope :in_team, ->(team) { where("id IN (:ids)", ids: team.projects.map(&:id)) }
80 85 scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) }
81 86 scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") }
82 87 scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
... ... @@ -122,7 +127,7 @@ class Project &lt; ActiveRecord::Base
122 127 end
123 128  
124 129 def team
125   - @team ||= Team.new(self)
  130 + @team ||= ProjectTeam.new(self)
126 131 end
127 132  
128 133 def repository
... ... @@ -335,7 +340,7 @@ class Project &lt; ActiveRecord::Base
335 340 end
336 341  
337 342 def execute_hooks(data)
338   - hooks.each { |hook| hook.execute(data) }
  343 + hooks.each { |hook| hook.async_execute(data) }
339 344 end
340 345  
341 346 def execute_services(data)
... ... @@ -489,6 +494,11 @@ class Project &lt; ActiveRecord::Base
489 494 http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
490 495 end
491 496  
  497 + def project_access_human(member)
  498 + project_user_relation = self.users_projects.find_by_user_id(member.id)
  499 + self.class.access_options.key(project_user_relation.project_access)
  500 + end
  501 +
492 502 # Check if current branch name is marked as protected in the system
493 503 def protected_branch? branch_name
494 504 protected_branches.map(&:name).include?(branch_name)
... ...
app/models/project_team.rb 0 → 100644
... ... @@ -0,0 +1,122 @@
  1 +class ProjectTeam
  2 + attr_accessor :project
  3 +
  4 + def initialize(project)
  5 + @project = project
  6 + end
  7 +
  8 + # Shortcut to add users
  9 + #
  10 + # Use:
  11 + # @team << [@user, :master]
  12 + # @team << [@users, :master]
  13 + #
  14 + def << args
  15 + users = args.first
  16 +
  17 + if users.respond_to?(:each)
  18 + add_users(users, args.second)
  19 + else
  20 + add_user(users, args.second)
  21 + end
  22 + end
  23 +
  24 + def get_tm user_id
  25 + project.users_projects.find_by_user_id(user_id)
  26 + end
  27 +
  28 + def add_user(user, access)
  29 + add_users_ids([user.id], access)
  30 + end
  31 +
  32 + def add_users(users, access)
  33 + add_users_ids(users.map(&:id), access)
  34 + end
  35 +
  36 + def add_users_ids(user_ids, access)
  37 + UsersProject.add_users_into_projects(
  38 + [project.id],
  39 + user_ids,
  40 + access
  41 + )
  42 + end
  43 +
  44 + # Remove all users from project team
  45 + def truncate
  46 + UsersProject.truncate_team(project)
  47 + end
  48 +
  49 + def members
  50 + project.users_projects
  51 + end
  52 +
  53 + def guests
  54 + members.guests.map(&:user)
  55 + end
  56 +
  57 + def reporters
  58 + members.reporters.map(&:user)
  59 + end
  60 +
  61 + def developers
  62 + members.developers.map(&:user)
  63 + end
  64 +
  65 + def masters
  66 + members.masters.map(&:user)
  67 + end
  68 +
  69 + def repository_readers
  70 + repository_members[UsersProject::REPORTER]
  71 + end
  72 +
  73 + def repository_writers
  74 + repository_members[UsersProject::DEVELOPER]
  75 + end
  76 +
  77 + def repository_masters
  78 + repository_members[UsersProject::MASTER]
  79 + end
  80 +
  81 + def repository_members
  82 + keys = Hash.new {|h,k| h[k] = [] }
  83 + UsersProject.select("keys.identifier, project_access").
  84 + joins(user: :keys).where(project_id: project.id).
  85 + each {|row| keys[row.project_access] << [row.identifier] }
  86 +
  87 + keys[UsersProject::REPORTER] += project.deploy_keys.pluck(:identifier)
  88 + keys
  89 + end
  90 +
  91 + def import(source_project)
  92 + target_project = project
  93 +
  94 + source_team = source_project.users_projects.all
  95 + target_team = target_project.users_projects.all
  96 + target_user_ids = target_team.map(&:user_id)
  97 +
  98 + source_team.reject! do |tm|
  99 + # Skip if user already present in team
  100 + target_user_ids.include?(tm.user_id)
  101 + end
  102 +
  103 + source_team.map! do |tm|
  104 + new_tm = tm.dup
  105 + new_tm.id = nil
  106 + new_tm.project_id = target_project.id
  107 + new_tm.skip_git = true
  108 + new_tm
  109 + end
  110 +
  111 + UsersProject.transaction do
  112 + source_team.each do |tm|
  113 + tm.save
  114 + end
  115 + target_project.update_repository
  116 + end
  117 +
  118 + true
  119 + rescue
  120 + false
  121 + end
  122 +end
... ...
app/models/team.rb
... ... @@ -1,122 +0,0 @@
1   -class Team
2   - attr_accessor :project
3   -
4   - def initialize(project)
5   - @project = project
6   - end
7   -
8   - # Shortcut to add users
9   - #
10   - # Use:
11   - # @team << [@user, :master]
12   - # @team << [@users, :master]
13   - #
14   - def << args
15   - users = args.first
16   -
17   - if users.respond_to?(:each)
18   - add_users(users, args.second)
19   - else
20   - add_user(users, args.second)
21   - end
22   - end
23   -
24   - def get_tm user_id
25   - project.users_projects.find_by_user_id(user_id)
26   - end
27   -
28   - def add_user(user, access)
29   - add_users_ids([user.id], access)
30   - end
31   -
32   - def add_users(users, access)
33   - add_users_ids(users.map(&:id), access)
34   - end
35   -
36   - def add_users_ids(user_ids, access)
37   - UsersProject.add_users_into_projects(
38   - [project.id],
39   - user_ids,
40   - access
41   - )
42   - end
43   -
44   - # Remove all users from project team
45   - def truncate
46   - UsersProject.truncate_team(project)
47   - end
48   -
49   - def members
50   - project.users_projects
51   - end
52   -
53   - def guests
54   - members.guests.map(&:user)
55   - end
56   -
57   - def reporters
58   - members.reporters.map(&:user)
59   - end
60   -
61   - def developers
62   - members.developers.map(&:user)
63   - end
64   -
65   - def masters
66   - members.masters.map(&:user)
67   - end
68   -
69   - def repository_readers
70   - repository_members[UsersProject::REPORTER]
71   - end
72   -
73   - def repository_writers
74   - repository_members[UsersProject::DEVELOPER]
75   - end
76   -
77   - def repository_masters
78   - repository_members[UsersProject::MASTER]
79   - end
80   -
81   - def repository_members
82   - keys = Hash.new {|h,k| h[k] = [] }
83   - UsersProject.select("keys.identifier, project_access").
84   - joins(user: :keys).where(project_id: project.id).
85   - each {|row| keys[row.project_access] << [row.identifier] }
86   -
87   - keys[UsersProject::REPORTER] += project.deploy_keys.pluck(:identifier)
88   - keys
89   - end
90   -
91   - def import(source_project)
92   - target_project = project
93   -
94   - source_team = source_project.users_projects.all
95   - target_team = target_project.users_projects.all
96   - target_user_ids = target_team.map(&:user_id)
97   -
98   - source_team.reject! do |tm|
99   - # Skip if user already present in team
100   - target_user_ids.include?(tm.user_id)
101   - end
102   -
103   - source_team.map! do |tm|
104   - new_tm = tm.dup
105   - new_tm.id = nil
106   - new_tm.project_id = target_project.id
107   - new_tm.skip_git = true
108   - new_tm
109   - end
110   -
111   - UsersProject.transaction do
112   - source_team.each do |tm|
113   - tm.save
114   - end
115   - target_project.update_repository
116   - end
117   -
118   - true
119   - rescue
120   - false
121   - end
122   -end
app/models/user.rb
... ... @@ -40,23 +40,32 @@ class User &lt; ActiveRecord::Base
40 40 attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username,
41 41 :skype, :linkedin, :twitter, :dark_scheme, :theme_id, :force_random_password,
42 42 :extern_uid, :provider, as: [:default, :admin]
43   - attr_accessible :projects_limit, as: :admin
  43 + attr_accessible :projects_limit, :can_create_team, :can_create_group, as: :admin
44 44  
45 45 attr_accessor :force_random_password
46 46  
47 47 # Namespace for personal projects
48   - has_one :namespace, class_name: "Namespace", foreign_key: :owner_id, conditions: 'type IS NULL', dependent: :destroy
49   - has_many :groups, class_name: "Group", foreign_key: :owner_id
50   -
51   - has_many :keys, dependent: :destroy
52   - has_many :users_projects, dependent: :destroy
53   - has_many :issues, foreign_key: :author_id, dependent: :destroy
54   - has_many :notes, foreign_key: :author_id, dependent: :destroy
55   - has_many :merge_requests, foreign_key: :author_id, dependent: :destroy
56   - has_many :events, class_name: "Event", foreign_key: :author_id, dependent: :destroy
57   - has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC"
58   - has_many :assigned_issues, class_name: "Issue", foreign_key: :assignee_id, dependent: :destroy
59   - has_many :assigned_merge_requests, class_name: "MergeRequest", foreign_key: :assignee_id, dependent: :destroy
  48 + has_one :namespace, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace", conditions: 'type IS NULL'
  49 +
  50 + has_many :keys, dependent: :destroy
  51 + has_many :users_projects, dependent: :destroy
  52 + has_many :issues, dependent: :destroy, foreign_key: :author_id
  53 + has_many :notes, dependent: :destroy, foreign_key: :author_id
  54 + has_many :merge_requests, dependent: :destroy, foreign_key: :author_id
  55 + has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event"
  56 + has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue"
  57 + has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
  58 +
  59 + has_many :groups, class_name: "Group", foreign_key: :owner_id
  60 + has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC"
  61 +
  62 + has_many :projects, through: :users_projects
  63 +
  64 + has_many :user_team_user_relationships, dependent: :destroy
  65 +
  66 + has_many :user_teams, through: :user_team_user_relationships
  67 + has_many :user_team_project_relationships, through: :user_teams
  68 + has_many :team_projects, through: :user_team_project_relationships
60 69  
61 70 validates :name, presence: true
62 71 validates :bio, length: { within: 0..255 }
... ... @@ -80,6 +89,9 @@ class User &lt; ActiveRecord::Base
80 89 scope :blocked, where(blocked: true)
81 90 scope :active, where(blocked: false)
82 91 scope :alphabetically, order('name ASC')
  92 + scope :in_team, ->(team){ where(id: team.member_ids) }
  93 + scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
  94 + scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
83 95  
84 96 #
85 97 # Class methods
... ... @@ -131,6 +143,11 @@ class User &lt; ActiveRecord::Base
131 143 #
132 144 # Instance methods
133 145 #
  146 +
  147 + def to_param
  148 + username
  149 + end
  150 +
134 151 def generate_password
135 152 if self.force_random_password
136 153 self.password = self.password_confirmation = Devise.friendly_token.first(8)
... ... @@ -220,7 +237,7 @@ class User &lt; ActiveRecord::Base
220 237 end
221 238  
222 239 def can_create_group?
223   - is_admin?
  240 + can?(:create_group, nil)
224 241 end
225 242  
226 243 def abilities
... ... @@ -283,4 +300,15 @@ class User &lt; ActiveRecord::Base
283 300 def namespace_id
284 301 namespace.try :id
285 302 end
  303 +
  304 + def authorized_teams
  305 + @authorized_teams ||= begin
  306 + ids = []
  307 + ids << UserTeam.with_member(self).pluck('user_teams.id')
  308 + ids << UserTeam.created_by(self).pluck('user_teams.id')
  309 + ids.flatten
  310 +
  311 + UserTeam.where(id: ids)
  312 + end
  313 + end
286 314 end
... ...
app/models/user_team.rb 0 → 100644
... ... @@ -0,0 +1,97 @@
  1 +class UserTeam < ActiveRecord::Base
  2 + attr_accessible :name, :owner_id, :path
  3 +
  4 + belongs_to :owner, class_name: User
  5 +
  6 + has_many :user_team_project_relationships, dependent: :destroy
  7 + has_many :user_team_user_relationships, dependent: :destroy
  8 +
  9 + has_many :projects, through: :user_team_project_relationships
  10 + has_many :members, through: :user_team_user_relationships, source: :user
  11 +
  12 + validates :name, presence: true, uniqueness: true
  13 + validates :owner, presence: true
  14 + validates :path, uniqueness: true, presence: true, length: { within: 1..255 },
  15 + format: { with: Gitlab::Regex.path_regex,
  16 + message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
  17 +
  18 + scope :with_member, ->(user){ joins(:user_team_user_relationships).where(user_team_user_relationships: {user_id: user.id}) }
  19 + scope :with_project, ->(project){ joins(:user_team_project_relationships).where(user_team_project_relationships: {project_id: project})}
  20 + scope :without_project, ->(project){ where("user_teams.id NOT IN (:ids)", ids: (a = with_project(project); a.blank? ? 0 : a))}
  21 + scope :created_by, ->(user){ where(owner_id: user) }
  22 +
  23 + class << self
  24 + def search query
  25 + where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
  26 + end
  27 +
  28 + def global_id
  29 + 'GLN'
  30 + end
  31 +
  32 + def access_roles
  33 + UsersProject.access_roles
  34 + end
  35 + end
  36 +
  37 + def to_param
  38 + path
  39 + end
  40 +
  41 + def assign_to_projects(projects, access)
  42 + projects.each do |project|
  43 + assign_to_project(project, access)
  44 + end
  45 + end
  46 +
  47 + def assign_to_project(project, access)
  48 + Gitlab::UserTeamManager.assign(self, project, access)
  49 + end
  50 +
  51 + def resign_from_project(project)
  52 + Gitlab::UserTeamManager.resign(self, project)
  53 + end
  54 +
  55 + def add_members(users, access, group_admin)
  56 + users.each do |user|
  57 + add_member(user, access, group_admin)
  58 + end
  59 + end
  60 +
  61 + def add_member(user, access, group_admin)
  62 + Gitlab::UserTeamManager.add_member_into_team(self, user, access, group_admin)
  63 + end
  64 +
  65 + def remove_member(user)
  66 + Gitlab::UserTeamManager.remove_member_from_team(self, user)
  67 + end
  68 +
  69 + def update_membership(user, options)
  70 + Gitlab::UserTeamManager.update_team_user_membership(self, user, options)
  71 + end
  72 +
  73 + def update_project_access(project, permission)
  74 + Gitlab::UserTeamManager.update_project_greates_access(self, project, permission)
  75 + end
  76 +
  77 + def max_project_access(project)
  78 + user_team_project_relationships.find_by_project_id(project).greatest_access
  79 + end
  80 +
  81 + def human_max_project_access(project)
  82 + self.class.access_roles.invert[max_project_access(project)]
  83 + end
  84 +
  85 + def default_projects_access(member)
  86 + user_team_user_relationships.find_by_user_id(member).permission
  87 + end
  88 +
  89 + def human_default_projects_access(member)
  90 + self.class.access_roles.invert[default_projects_access(member)]
  91 + end
  92 +
  93 + def admin?(member)
  94 + user_team_user_relationships.with_user(member).first.group_admin?
  95 + end
  96 +
  97 +end
... ...
app/models/user_team_project_relationship.rb 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +class UserTeamProjectRelationship < ActiveRecord::Base
  2 + attr_accessible :greatest_access, :project_id, :user_team_id
  3 +
  4 + belongs_to :user_team
  5 + belongs_to :project
  6 +
  7 + validates :project, presence: true
  8 + validates :user_team, presence: true
  9 + validate :check_greatest_access
  10 +
  11 + scope :with_project, ->(project){ where(project_id: project.id) }
  12 +
  13 + def team_name
  14 + user_team.name
  15 + end
  16 +
  17 + private
  18 +
  19 + def check_greatest_access
  20 + errors.add(:base, :incorrect_access_code) unless correct_access?
  21 + end
  22 +
  23 + def correct_access?
  24 + return false if greatest_access.blank?
  25 + return true if UsersProject.access_roles.has_value?(greatest_access)
  26 + false
  27 + end
  28 +end
... ...
app/models/user_team_user_relationship.rb 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +class UserTeamUserRelationship < ActiveRecord::Base
  2 + attr_accessible :group_admin, :permission, :user_id, :user_team_id
  3 +
  4 + belongs_to :user_team
  5 + belongs_to :user
  6 +
  7 + validates :user_team, presence: true
  8 + validates :user, presence: true
  9 +
  10 + scope :with_user, ->(user) { where(user_id: user.id) }
  11 +
  12 + def user_name
  13 + user.name
  14 + end
  15 +
  16 + def access_human
  17 + UsersProject.access_roles.invert[permission]
  18 + end
  19 +end
... ...
app/models/users_project.rb
... ... @@ -39,7 +39,10 @@ class UsersProject &lt; ActiveRecord::Base
39 39 scope :reporters, where(project_access: REPORTER)
40 40 scope :developers, where(project_access: DEVELOPER)
41 41 scope :masters, where(project_access: MASTER)
  42 +
42 43 scope :in_project, ->(project) { where(project_id: project.id) }
  44 + scope :in_projects, ->(projects) { where(project_id: project_ids) }
  45 + scope :with_user, ->(user) { where(user_id: user.id) }
43 46  
44 47 class << self
45 48  
... ...
app/models/web_hook.rb
... ... @@ -34,4 +34,8 @@ class WebHook &lt; ActiveRecord::Base
34 34 basic_auth: {username: parsed_url.user, password: parsed_url.password})
35 35 end
36 36 end
  37 +
  38 + def async_execute(data)
  39 + Sidekiq::Client.enqueue(ProjectWebHookWorker, id, data)
  40 + end
37 41 end
... ...
app/views/admin/groups/show.html.haml
... ... @@ -72,16 +72,17 @@
72 72 %th Users
73 73 %th Project Access:
74 74  
75   - - @group.users.each do |u|
76   - %tr{class: "user_#{u.id}"}
77   - %td.name= link_to u.name, admin_user_path(u)
  75 + - @group.users.each do |user|
  76 + - next unless user
  77 + %tr{class: "user_#{user.id}"}
  78 + %td.name= link_to user.name, admin_user_path(user)
78 79 %td.projects_access
79   - - u.authorized_projects.in_namespace(@group).each do |project|
80   - - u_p = u.users_projects.in_project(project).first
  80 + - user.authorized_projects.in_namespace(@group).each do |project|
  81 + - u_p = user.users_projects.in_project(project).first
81 82 - next unless u_p
82 83 %span
83   - = project.name
84   - = link_to "(#{ u_p.project_access_human })", edit_admin_team_member_path(u_p)
  84 + = project.name_with_namespace
  85 + = link_to "(#{ u_p.project_access_human })", edit_admin_project_member_path(project, user)
85 86 %tr
86 87 %td.input= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
87 88 %td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"}
... ...
app/views/admin/projects/members/_form.html.haml 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 += form_for @team_member_relation, as: :team_member, url: admin_project_member_path(@project, @member) do |f|
  2 + -if @team_member_relation.errors.any?
  3 + .alert-message.block-message.error
  4 + %ul
  5 + - @team_member_relation.errors.full_messages.each do |msg|
  6 + %li= msg
  7 +
  8 + .clearfix
  9 + %label Project Access:
  10 + .input
  11 + = f.select :project_access, options_for_select(Project.access_options, @team_member_relation.project_access), {}, class: "project-access-select chosen span3"
  12 +
  13 + %br
  14 + .actions
  15 + = f.submit 'Save', class: "btn primary"
  16 + = link_to 'Cancel', :back, class: "btn"
... ...
app/views/admin/projects/members/edit.html.haml 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +%p.slead
  2 + Edit access for
  3 + = link_to @member.name, admin_user_path(@member)
  4 + in
  5 + = link_to @project.name_with_namespace, admin_project_path(@project)
  6 +
  7 +%hr
  8 += render 'form'
... ...
app/views/admin/projects/show.html.haml
... ... @@ -114,9 +114,9 @@
114 114 %h5
115 115 Team
116 116 %small
117   - (#{@project.users_projects.count})
  117 + (#{@project.users.count})
118 118 %br
119   -%table.zebra-striped
  119 +%table.zebra-striped.team_members
120 120 %thead
121 121 %tr
122 122 %th Name
... ... @@ -124,13 +124,13 @@
124 124 %th Repository Access
125 125 %th
126 126  
127   - - @project.users_projects.each do |tm|
  127 + - @project.users.each do |tm|
128 128 %tr
129 129 %td
130   - = link_to tm.user_name, admin_user_path(tm.user)
131   - %td= tm.project_access_human
132   - %td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small"
133   - %td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn danger small"
  130 + = link_to tm.name, admin_user_path(tm)
  131 + %td= @project.project_access_human(tm)
  132 + %td= link_to 'Edit Access', edit_admin_project_member_path(@project, tm), class: "btn small"
  133 + %td= link_to 'Remove from team', admin_project_member_path(@project, tm), confirm: 'Are you sure?', method: :delete, class: "btn danger small"
134 134  
135 135 %br
136 136 %h5 Add new team member
... ...
app/views/admin/team_members/_form.html.haml
... ... @@ -1,16 +0,0 @@
1   -= form_for @admin_team_member, as: :team_member, url: admin_team_member_path(@admin_team_member) do |f|
2   - -if @admin_team_member.errors.any?
3   - .alert-message.block-message.error
4   - %ul
5   - - @admin_team_member.errors.full_messages.each do |msg|
6   - %li= msg
7   -
8   - .clearfix
9   - %label Project Access:
10   - .input
11   - = f.select :project_access, options_for_select(Project.access_options, @admin_team_member.project_access), {}, class: "project-access-select chosen span3"
12   -
13   - %br
14   - .actions
15   - = f.submit 'Save', class: "btn primary"
16   - = link_to 'Cancel', :back, class: "btn"
app/views/admin/team_members/edit.html.haml
... ... @@ -1,8 +0,0 @@
1   -%p.slead
2   - Edit access for
3   - = link_to @admin_team_member.user_name, admin_user_path(@admin_team_member)
4   - in
5   - = link_to @admin_team_member.project.name_with_namespace, admin_project_path(@admin_team_member)
6   -
7   -%hr
8   -= render 'form'
app/views/admin/teams/edit.html.haml 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +%h3.page_title Rename Team
  2 +%hr
  3 += form_for @team, url: admin_team_path(@team), method: :put do |f|
  4 + - if @team.errors.any?
  5 + .alert-message.block-message.error
  6 + %span= @team.errors.full_messages.first
  7 + .clearfix.team_name_holder
  8 + = f.label :name do
  9 + Team name is
  10 + .input
  11 + = f.text_field :name, placeholder: "Example Team", class: "xxlarge"
  12 +
  13 + .clearfix.team_name_holder
  14 + = f.label :path do
  15 + %span.cred Team path is
  16 + .input
  17 + = f.text_field :path, placeholder: "example-team", class: "xxlarge danger"
  18 + %ul.cred
  19 + %li It will change web url for access team and team projects.
  20 +
  21 + .form-actions
  22 + = f.submit 'Rename team', class: "btn danger"
  23 + = link_to 'Cancel', admin_teams_path, class: "btn cancel-btn"
... ...
app/views/admin/teams/index.html.haml 0 → 100644
... ... @@ -0,0 +1,38 @@
  1 +%h3.page_title
  2 + Teams
  3 + %small
  4 + simple Teams description
  5 +
  6 + = link_to 'New Team', new_admin_team_path, class: "btn small right"
  7 + %br
  8 +
  9 += form_tag admin_teams_path, method: :get, class: 'form-inline' do
  10 + = text_field_tag :name, params[:name], class: "xlarge"
  11 + = submit_tag "Search", class: "btn submit primary"
  12 +
  13 +%table
  14 + %thead
  15 + %tr
  16 + %th
  17 + Name
  18 + %i.icon-sort-down
  19 + %th Path
  20 + %th Projects
  21 + %th Members
  22 + %th Owner
  23 + %th.cred Danger Zone!
  24 +
  25 + - @teams.each do |team|
  26 + %tr
  27 + %td
  28 + %strong= link_to team.name, admin_team_path(team)
  29 + %td= team.path
  30 + %td= team.projects.count
  31 + %td= team.members.count
  32 + %td
  33 + = link_to team.owner.name, admin_user_path(team.owner_id)
  34 + %td.bgred
  35 + = link_to 'Rename', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn small"
  36 + = link_to 'Destroy', admin_team_path(team), confirm: "REMOVE #{team.name}? Are you sure?", method: :delete, class: "btn small danger"
  37 +
  38 += paginate @teams, theme: "admin"
... ...
app/views/admin/teams/members/_form.html.haml 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 += form_tag admin_team_member_path(@team, @member), method: :put do
  2 + -if @member.errors.any?
  3 + .alert-message.block-message.error
  4 + %ul
  5 + - @member.errors.full_messages.each do |msg|
  6 + %li= msg
  7 +
  8 + .clearfix
  9 + %label Default access for Team projects:
  10 + .input
  11 + = select_tag :default_project_access, options_for_select(UserTeam.access_roles, @team.default_projects_access(@member)), class: "project-access-select chosen span3"
  12 + .clearfix
  13 + %label Team admin?
  14 + .input
  15 + = check_box_tag :group_admin, true, @team.admin?(@member)
  16 +
  17 + %br
  18 + .actions
  19 + = submit_tag 'Save', class: "btn primary"
  20 + = link_to 'Cancel', :back, class: "btn"
... ...
app/views/admin/teams/members/edit.html.haml 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +%h3
  2 + Edit access #{@member.name} in #{@team.name} team
  3 +
  4 +%hr
  5 +%table.zebra-striped
  6 + %tr
  7 + %td User:
  8 + %td= @member.name
  9 + %tr
  10 + %td Team:
  11 + %td= @team.name
  12 + %tr
  13 + %td Since:
  14 + %td= member_since(@team, @member).stamp("Nov 11, 2010")
  15 +
  16 += render 'form'
... ...
app/views/admin/teams/members/new.html.haml 0 → 100644
... ... @@ -0,0 +1,29 @@
  1 +%h3.page_title
  2 + Team: #{@team.name}
  3 +
  4 +%fieldset
  5 + %legend Members (#{@team.members.count})
  6 + = form_tag admin_team_members_path(@team), id: "team_members", class: "bulk_import", method: :post do
  7 + %table#members_list
  8 + %thead
  9 + %tr
  10 + %th User name
  11 + %th Default project access
  12 + %th Team access
  13 + %th
  14 + - @team.members.each do |member|
  15 + %tr.member
  16 + %td
  17 + = link_to [:admin, member] do
  18 + = member.name
  19 + %small= "(#{member.email})"
  20 + %td= @team.human_default_projects_access(member)
  21 + %td= @team.admin?(member) ? "Admin" : "Member"
  22 + %td
  23 + %tr
  24 + %td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name_with_email), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
  25 + %td= select_tag :default_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
  26 + %td
  27 + %span= check_box_tag :group_admin
  28 + %span Admin?
  29 + %td= submit_tag 'Add', class: "btn primary", id: :add_members_to_team
... ...
app/views/admin/teams/new.html.haml 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +%h3.page_title New Team
  2 +%hr
  3 += form_for @team, url: admin_teams_path do |f|
  4 + - if @team.errors.any?
  5 + .alert-message.block-message.error
  6 + %span= @team.errors.full_messages.first
  7 + .clearfix
  8 + = f.label :name do
  9 + Team name is
  10 + .input
  11 + = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
  12 + &nbsp;
  13 + = f.submit 'Create team', class: "btn primary"
  14 + %hr
  15 + .padded
  16 + %ul
  17 + %li All created teams are public (users can view who enter into team and which project are assigned for this team)
  18 + %li People within a team see only projects they have access to
  19 + %li You will be able to assign existing projects for team
... ...
app/views/admin/teams/projects/_form.html.haml 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 += form_tag admin_team_project_path(@team, @project), method: :put do
  2 + -if @project.errors.any?
  3 + .alert-message.block-message.error
  4 + %ul
  5 + - @project.errors.full_messages.each do |msg|
  6 + %li= msg
  7 +
  8 + .clearfix
  9 + %label Max access for Team members:
  10 + .input
  11 + = select_tag :greatest_project_access, options_for_select(UserTeam.access_roles, @team.max_project_access(@project)), class: "project-access-select chosen span3"
  12 +
  13 + %br
  14 + .actions
  15 + = submit_tag 'Save', class: "btn primary"
  16 + = link_to 'Cancel', :back, class: "btn"
... ...
app/views/admin/teams/projects/edit.html.haml 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +%h3
  2 + Edit max access in #{@project.name} for #{@team.name} team
  3 +
  4 +%hr
  5 +%table.zebra-striped
  6 + %tr
  7 + %td Project:
  8 + %td= @project.name
  9 + %tr
  10 + %td Team:
  11 + %td= @team.name
  12 + %tr
  13 + %td Since:
  14 + %td= assigned_since(@team, @project).stamp("Nov 11, 2010")
  15 +
  16 += render 'form'
... ...
app/views/admin/teams/projects/new.html.haml 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +%h3.page_title
  2 + Team: #{@team.name}
  3 +
  4 +%fieldset
  5 + %legend Projects (#{@team.projects.count})
  6 + = form_tag admin_team_projects_path(@team), id: "assign_projects", class: "bulk_import", method: :post do
  7 + %table#projects_list
  8 + %thead
  9 + %tr
  10 + %th Project name
  11 + %th Max access
  12 + %th
  13 + - @team.projects.each do |project|
  14 + %tr.project
  15 + %td
  16 + = link_to project.name_with_namespace, [:admin, project]
  17 + %td
  18 + %span= @team.human_max_project_access(project)
  19 + %td
  20 + %tr
  21 + %td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
  22 + %td= select_tag :greatest_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
  23 + %td= submit_tag 'Add', class: "btn primary", id: :assign_projects_to_team
... ...
app/views/admin/teams/show.html.haml 0 → 100644
... ... @@ -0,0 +1,101 @@
  1 +%h3.page_title
  2 + Team: #{@team.name}
  3 +
  4 +%br
  5 +%table.zebra-striped
  6 + %thead
  7 + %tr
  8 + %th Team
  9 + %th
  10 + %tr
  11 + %td
  12 + %b
  13 + Name:
  14 + %td
  15 + = @team.name
  16 + &nbsp;
  17 + = link_to edit_admin_team_path(@team), class: "btn btn-small right" do
  18 + %i.icon-edit
  19 + Rename
  20 + %tr
  21 + %td
  22 + %b
  23 + Owner:
  24 + %td
  25 + = @team.owner.name
  26 + .right
  27 + = link_to "#", class: "btn btn-small change-owner-link" do
  28 + %i.icon-edit
  29 + Change owner
  30 +
  31 + %tr.change-owner-holder.hide
  32 + %td.bgred
  33 + %b.cred
  34 + New Owner:
  35 + %td.bgred
  36 + = form_for @team, url: admin_team_path(@team) do |f|
  37 + = f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'}
  38 + %div
  39 + = f.submit 'Change Owner', class: "btn danger"
  40 + = link_to "Cancel", "#", class: "btn change-owner-cancel-link"
  41 +
  42 +%fieldset
  43 + %legend
  44 + Members (#{@team.members.count})
  45 + %span= link_to 'Add members', new_admin_team_member_path(@team), class: "btn success small right", id: :add_members_to_team
  46 + - if @team.members.any?
  47 + %table#members_list
  48 + %thead
  49 + %tr
  50 + %th User name
  51 + %th Default project access
  52 + %th Team access
  53 + %th.cred.span3 Danger Zone!
  54 + - @team.members.each do |member|
  55 + %tr.member{ class: "user_#{member.id}"}
  56 + %td
  57 + = link_to [:admin, member] do
  58 + = member.name
  59 + %small= "(#{member.email})"
  60 + %td= @team.human_default_projects_access(member)
  61 + %td= @team.admin?(member) ? "Admin" : "Member"
  62 + %td.bgred
  63 + = link_to 'Edit', edit_admin_team_member_path(@team, member), class: "btn small"
  64 + &nbsp;
  65 + = link_to 'Remove', admin_team_member_path(@team, member), confirm: 'Remove member from team. Are you sure?', method: :delete, class: "btn danger small", id: "remove_member_#{member.id}"
  66 +
  67 +%fieldset
  68 + %legend
  69 + Projects (#{@team.projects.count})
  70 + %span= link_to 'Add projects', new_admin_team_project_path(@team), class: "btn success small right", id: :assign_projects_to_team
  71 + - if @team.projects.any?
  72 + %table#projects_list
  73 + %thead
  74 + %tr
  75 + %th Project name
  76 + %th Max access
  77 + %th.cred.span3 Danger Zone!
  78 + - @team.projects.each do |project|
  79 + %tr.project
  80 + %td
  81 + = link_to project.name_with_namespace, [:admin, project]
  82 + %td
  83 + %span= @team.human_max_project_access(project)
  84 + %td.bgred
  85 + = link_to 'Edit', edit_admin_team_project_path(@team, project), class: "btn small"
  86 + &nbsp;
  87 + = link_to 'Relegate', admin_team_project_path(@team, project), confirm: 'Remove project from team. Are you sure?', method: :delete, class: "btn danger small", id: "relegate_project_#{project.id}"
  88 +
  89 +:javascript
  90 + $(function(){
  91 + var modal = $('.change-owner-holder');
  92 + $('.change-owner-link').bind("click", function(){
  93 + $(this).hide();
  94 + modal.show();
  95 + });
  96 + $('.change-owner-cancel-link').bind("click", function(){
  97 + modal.hide();
  98 + $('.change-owner-link').show();
  99 + })
  100 + })
  101 +
... ...
app/views/admin/users/_form.html.haml
... ... @@ -47,6 +47,14 @@
47 47 .input= f.number_field :projects_limit
48 48  
49 49 .clearfix
  50 + = f.label :can_create_group
  51 + .input= f.check_box :can_create_group
  52 +
  53 + .clearfix
  54 + = f.label :can_create_team
  55 + .input= f.check_box :can_create_team
  56 +
  57 + .clearfix
50 58 = f.label :admin do
51 59 %strong.cred Administrator
52 60 .input= f.check_box :admin
... ...
app/views/admin/users/show.html.haml
... ... @@ -123,5 +123,5 @@
123 123 %tr
124 124 %td= link_to project.name_with_namespace, admin_project_path(project)
125 125 %td= tm.project_access_human
126   - %td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small"
127   - %td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn small danger"
  126 + %td= link_to 'Edit Access', edit_admin_project_member_path(project, tm.user), class: "btn small"
  127 + %td= link_to 'Remove from team', admin_project_member_path(project, tm.user), confirm: 'Are you sure?', method: :delete, class: "btn small danger"
... ...
app/views/commit/show.html.haml
... ... @@ -11,19 +11,7 @@
11 11  
12 12 :javascript
13 13 $(function(){
14   - var w, h;
15   - $('.diff_file').each(function(){
16   - $('.image.diff_removed img', this).on('load', $.proxy(function(event){
17   - var w = event.currentTarget.naturalWidth
18   - , h = event.currentTarget.naturalHeight;
19   - $('.image.diff_removed .image-info', this).append(' | <b>W:</b> ' + w + 'px | <b>H:</b> ' + h + 'px');
20   - }, this));
21   - $('.image.diff_added img', this).on('load', $.proxy(function(event){
22   - var w = event.currentTarget.naturalWidth
23   - , h = event.currentTarget.naturalHeight;
24   - $('.image.diff_added .image-info', this).append(' | <b>W:</b> ' + w + 'px | <b>H:</b> ' + h + 'px');
25   - }, this));
26   -
  14 + $('.files .file').each(function(){
  15 + new CommitFile(this);
27 16 });
28   -
29 17 });
... ...
app/views/commits/_commits.html.haml
... ... @@ -2,5 +2,5 @@
2 2 %div.ui-box
3 3 %h5.title
4 4 %i.icon-calendar
5   - = day.stamp("28 Aug, 2010")
  5 + %span= day.stamp("28 Aug, 2010")
6 6 %ul.well-list= render commits
... ...
app/views/commits/_diffs.html.haml
... ... @@ -12,50 +12,38 @@
12 12 .file-stats
13 13 = render "commits/diff_head", diffs: diffs
14 14  
15   -- unless @suppress_diff
16   - - diffs.each_with_index do |diff, i|
17   - - next if diff.diff.empty?
18   - - file = (@commit.tree / diff.new_path)
19   - - file = (@commit.prev_commit.tree / diff.old_path) unless file
20   - - next unless file
21   - .diff_file{id: "diff-#{i}"}
22   - .diff_file_header
23   - - if diff.deleted_file
24   - %span= diff.old_path
  15 +.files
  16 + - unless @suppress_diff
  17 + - diffs.each_with_index do |diff, i|
  18 + - next if diff.diff.empty?
  19 + - file = (@commit.tree / diff.new_path)
  20 + - file = (@commit.prev_commit.tree / diff.old_path) unless file
  21 + - next unless file
  22 + .file{id: "diff-#{i}"}
  23 + .header
  24 + - if diff.deleted_file
  25 + %span= diff.old_path
25 26  
26   - - if @commit.prev_commit
27   - = link_to project_tree_path(@project, tree_join(@commit.prev_commit_id, diff.new_path)), {:class => 'btn right view-commit'} do
  27 + - if @commit.prev_commit
  28 + = link_to project_tree_path(@project, tree_join(@commit.prev_commit_id, diff.new_path)), {:class => 'btn right view-file'} do
  29 + View file @
  30 + %span.commit-short-id= @commit.short_id(6)
  31 + - else
  32 + %span= diff.new_path
  33 + - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
  34 + %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
  35 +
  36 + = link_to project_tree_path(@project, tree_join(@commit.id, diff.new_path)), {:class => 'btn very_small right view-file'} do
28 37 View file @
29 38 %span.commit-short-id= @commit.short_id(6)
30   - - else
31   - %span= diff.new_path
32   - - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
33   - %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
34   -
35   - = link_to project_tree_path(@project, tree_join(@commit.id, diff.new_path)), {:class => 'btn very_small right view-commit'} do
36   - View file @
37   - %span.commit-short-id= @commit.short_id(6)
38 39  
39   - %br/
40   - .diff_file_content
41   - -# Skip all non-supported blobs
42   - - next unless file.respond_to?('text?')
43   - - if file.text?
44   - = render "commits/text_diff", diff: diff, index: i
45   - - elsif file.image?
46   - - old_file = (@commit.prev_commit.tree / diff.old_path) if !@commit.prev_commit.nil?
47   - - if diff.renamed_file || diff.new_file || diff.deleted_file
48   - .diff_file_content_image
49   - .image{class: image_diff_class(diff)}
50   - %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
51   - %div.image-info= "#{number_to_human_size file.size}"
  40 + .content
  41 + -# Skipp all non non-supported blobs
  42 + - next unless file.respond_to?('text?')
  43 + - if file.text?
  44 + = render "commits/text_file", diff: diff, index: i
  45 + - elsif file.image?
  46 + - old_file = (@commit.prev_commit.tree / diff.old_path) if !@commit.prev_commit.nil?
  47 + = render "commits/image", diff: diff, old_file: old_file, file: file, index: i
52 48 - else
53   - .diff_file_content_image.img_compared
54   - .image.diff_removed
55   - %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
56   - %div.image-info= "#{number_to_human_size file.size}"
57   - .image.diff_added
58   - %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
59   - %div.image-info= "#{number_to_human_size file.size}"
60   - - else
61   - %p.nothing_here_message No preview for this file type
  49 + %p.nothing_here_message No preview for this file type
... ...
app/views/commits/_image.html.haml 0 → 100644
... ... @@ -0,0 +1,63 @@
  1 +- if diff.renamed_file || diff.new_file || diff.deleted_file
  2 + .image
  3 + %span.wrap
  4 + .frame{class: image_diff_class(diff)}
  5 + %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
  6 + %p.image-info= "#{number_to_human_size file.size}"
  7 +- else
  8 + .image
  9 + %div.two-up.view
  10 + %span.wrap
  11 + .frame.deleted
  12 + %a{href: project_tree_path(@project, tree_join(@commit.id, diff.old_path))}
  13 + %img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
  14 + %p.image-info.hide
  15 + %span.meta-filesize= "#{number_to_human_size old_file.size}"
  16 + |
  17 + %b W:
  18 + %span.meta-width
  19 + |
  20 + %b H:
  21 + %span.meta-height
  22 + %span.wrap
  23 + .frame.added
  24 + %a{href: project_tree_path(@project, tree_join(@commit.id, diff.new_path))}
  25 + %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
  26 + %p.image-info.hide
  27 + %span.meta-filesize= "#{number_to_human_size file.size}"
  28 + |
  29 + %b W:
  30 + %span.meta-width
  31 + |
  32 + %b H:
  33 + %span.meta-height
  34 +
  35 + %div.swipe.view.hide
  36 + .swipe-frame
  37 + .frame.deleted
  38 + %img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
  39 + .swipe-wrap
  40 + .frame.added
  41 + %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
  42 + %span.swipe-bar
  43 + %span.top-handle
  44 + %span.bottom-handle
  45 +
  46 + %div.onion-skin.view.hide
  47 + .onion-skin-frame
  48 + .frame.deleted
  49 + %img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
  50 + .frame.added
  51 + %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
  52 + .controls
  53 + .transparent
  54 + .drag-track
  55 + .dragger{:style => "left: 0px;"}
  56 + .opaque
  57 +
  58 +
  59 + .view-modes.hide
  60 + %ul.view-modes-menu
  61 + %li.two-up{data: {mode: 'two-up'}} 2-up
  62 + %li.swipe{data: {mode: 'swipe'}} Swipe
  63 + %li.onion-skin{data: {mode: 'onion-skin'}} Onion skin
0 64 \ No newline at end of file
... ...
app/views/commits/_text_diff.html.haml
... ... @@ -1,23 +0,0 @@
1   -- too_big = diff.diff.lines.count > 1000
2   -- if too_big
3   - %a.supp_diff_link Diff suppressed. Click to show
4   -
5   -%table{class: "#{'hide' if too_big}"}
6   - - each_diff_line(diff, index) do |line, type, line_code, line_new, line_old|
7   - %tr.line_holder{ id: line_code }
8   - - if type == "match"
9   - %td.old_line= "..."
10   - %td.new_line= "..."
11   - %td.line_content.matched= line
12   - - else
13   - %td.old_line
14   - = link_to raw(type == "new" ? "&nbsp;" : line_old), "##{line_code}", id: line_code
15   - - if @comments_allowed
16   - = render "notes/diff_note_link", line_code: line_code
17   - %td.new_line= link_to raw(type == "old" ? "&nbsp;" : line_new) , "##{line_code}", id: line_code
18   - %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line)
19   -
20   - - if @reply_allowed
21   - - comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at)
22   - - unless comments.empty?
23   - = render "notes/diff_notes_with_reply", notes: comments
app/views/commits/_text_file.html.haml 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +- too_big = diff.diff.lines.count > 1000
  2 +- if too_big
  3 + %a.supp_diff_link Diff suppressed. Click to show
  4 +
  5 +%table.text-file{class: "#{'hide' if too_big}"}
  6 + - each_diff_line(diff, index) do |line, type, line_code, line_new, line_old|
  7 + %tr.line_holder{ id: line_code }
  8 + - if type == "match"
  9 + %td.old_line= "..."
  10 + %td.new_line= "..."
  11 + %td.line_content.matched= line
  12 + - else
  13 + %td.old_line
  14 + = link_to raw(type == "new" ? "&nbsp;" : line_old), "##{line_code}", id: line_code
  15 + - if @comments_allowed
  16 + = render "notes/diff_note_link", line_code: line_code
  17 + %td.new_line= link_to raw(type == "old" ? "&nbsp;" : line_new) , "##{line_code}", id: line_code
  18 + %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line)
  19 +
  20 + - if @reply_allowed
  21 + - comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at)
  22 + - unless comments.empty?
  23 + = render "notes/diff_notes_with_reply", notes: comments
... ...
app/views/commits/show.html.haml
... ... @@ -5,7 +5,7 @@
5 5 = breadcrumbs
6 6  
7 7 %div{id: dom_id(@project)}
8   - #commits_list= render "commits"
  8 + #commits-list= render "commits"
9 9 .clear
10 10 .loading{ style: "display:none;"}
11 11  
... ...
app/views/dashboard/_groups.html.haml
1   -.groups_box
  1 +.ui-box
2 2 %h5.title
3 3 Groups
4 4 %small
5 5 (#{groups.count})
6 6 - if current_user.can_create_group?
7 7 %span.right
8   - = link_to new_admin_group_path, class: "btn very_small info" do
  8 + = link_to new_group_path, class: "btn very_small info" do
9 9 %i.icon-plus
10 10 New Group
11 11 %ul.well-list
... ... @@ -13,8 +13,6 @@
13 13 %li
14 14 = link_to group_path(id: group.path), class: dom_class(group) do
15 15 %strong.well-title= truncate(group.name, length: 35)
16   - %span.arrow
17   - &rarr;
18   - %span.last_activity
19   - %strong Projects:
20   - %span= current_user.authorized_projects.where(namespace_id: group.id).count
  16 + %span.right.light
  17 + - if group.owner == current_user
  18 + %i.icon-wrench
... ...
app/views/dashboard/_projects.html.haml
... ... @@ -2,19 +2,12 @@
2 2 %h5.title
3 3 Projects
4 4 %small
5   - (#{projects.total_count})
  5 + (#{@projects_count})
6 6 - if current_user.can_create_project?
7 7 %span.right
8 8 = link_to new_project_path, class: "btn very_small info" do
9 9 %i.icon-plus
10 10 New Project
11   - %ul.nav.nav-projects-tabs
12   - = nav_tab :scope, nil do
13   - = link_to "All", dashboard_path
14   - = nav_tab :scope, 'personal' do
15   - = link_to "Personal", dashboard_path(scope: 'personal')
16   - = nav_tab :scope, 'joined' do
17   - = link_to "Joined", dashboard_path(scope: 'joined')
18 11  
19 12 %ul.well-list
20 13 - projects.each do |project|
... ... @@ -33,4 +26,6 @@
33 26 - if projects.blank?
34 27 %li
35 28 %h3.nothing_here_message There are no projects here.
36   - .bottom= paginate projects, theme: "gitlab"
  29 + - if @projects_count > 20
  30 + %li.bottom
  31 + %strong= link_to "show all projects", projects_dashboard_path
... ...
app/views/dashboard/_sidebar.html.haml
  1 +- if @teams.present?
  2 + = render "teams", teams: @teams
1 3 - if @groups.present?
2 4 = render "groups", groups: @groups
3 5 = render "projects", projects: @projects
... ...
app/views/dashboard/_teams.html.haml 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +.ui-box.teams-box
  2 + %h5.title
  3 + Teams
  4 + %small
  5 + (#{@teams.count})
  6 + %span.right
  7 + = link_to new_team_path, class: "btn very_small info" do
  8 + %i.icon-plus
  9 + New Team
  10 + %ul.well-list
  11 + - @teams.each do |team|
  12 + %li
  13 + = link_to team_path(id: team.path), class: dom_class(team) do
  14 + %strong.well-title= truncate(team.name, length: 35)
  15 + %span.right.light
  16 + - if team.owner == current_user
  17 + %i.icon-wrench
  18 + - tm = current_user.user_team_user_relationships.find_by_user_team_id(team.id)
  19 + - if tm
  20 + = tm.access_human
... ...
app/views/dashboard/index.atom.builder
... ... @@ -1,30 +0,0 @@
1   -xml.instruct!
2   -xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
3   - xml.title "Dashboard feed#{" - #{current_user.name}" if current_user.name.present?}"
4   - xml.link :href => projects_url(:atom), :rel => "self", :type => "application/atom+xml"
5   - xml.link :href => projects_url, :rel => "alternate", :type => "text/html"
6   - xml.id projects_url
7   - xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
8   -
9   - @events.each do |event|
10   - if event.proper?
11   - event = EventDecorator.decorate(event)
12   - xml.entry do
13   - event_link = event.feed_url
14   - event_title = event.feed_title
15   - event_summary = event.feed_summary
16   -
17   - xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
18   - xml.link :href => event_link
19   - xml.title truncate(event_title, :length => 80)
20   - xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
21   - xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(event.author_email)
22   - xml.author do |author|
23   - xml.name event.author_name
24   - xml.email event.author_email
25   - end
26   - xml.summary(:type => "xhtml") { |x| x << event_summary unless event_summary.nil? }
27   - end
28   - end
29   - end
30   -end
app/views/dashboard/index.html.haml
... ... @@ -1,12 +0,0 @@
1   -- if @has_authorized_projects
2   - .projects
3   - .activities.span8
4   - = render 'activities'
5   - .side.span4
6   - = render 'sidebar'
7   -
8   -- else
9   - = render "zero_authorized_projects"
10   -
11   -:javascript
12   - dashboardPage();
app/views/dashboard/index.js.haml
... ... @@ -1,2 +0,0 @@
1   -:plain
2   - Pager.append(#{@events.count}, "#{escape_javascript(render(@events))}");